1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
open Migrate_ast
module T = struct
type t =
| Docstring of {txt: string; loc: Location.t}
let loc (Comment {loc; _} | Docstring {loc; _}) = loc
let txt (Comment {txt; _} | Docstring {txt; _}) = txt
let txt loc = Comment {txt; loc}
let create_docstring txt loc = Docstring {txt; loc}
let is_docstring = function Comment _ -> false | Docstring _ -> true
let compare = Poly.compare
let sexp_of_t cmt =
let kind, txt, loc =
match cmt with
| Comment {txt; loc} -> ("comment", txt, loc)
| Docstring {txt; loc} -> ("docstring", txt, loc)
in
Sexp.List
[ Sexp.Atom kind
; Sexp.Atom txt
; Sexp.Atom (Format.asprintf "%a" Migrate_ast.Location.fmt loc) ]
end
include T
include Comparator.Make (T)
type error =
{ kind: [`Added of t | `Modified of t * t | `Dropped of t]
; cmt_kind: [`Comment | `Doc_comment] }
let pp_error fs {kind; cmt_kind} =
let pp_cmt fs x =
match cmt_kind with
| `Doc_comment -> Format.fprintf fs "(** %s *)" (txt x)
| `Comment -> Format.fprintf fs "(* %s *)" (txt x)
in
let s_kind =
match cmt_kind with
| `Doc_comment -> "doc-comment"
| `Comment -> "comment"
in
match kind with
| `Added x ->
Format.fprintf fs "%!@{<loc>%a@}:@,@{<error>Error@}: %s %a added.\n%!"
Location.print_loc (loc x) s_kind pp_cmt x
| `Dropped x ->
Format.fprintf fs
"%!@{<loc>%a@}:@,@{<error>Error@}: %s %a dropped.\n%!"
Location.print_loc (loc x) s_kind pp_cmt x
| `Modified (x, y) -> (
Format.fprintf fs
"%!@{<loc>%a@}:@,\
@{<error>Error@}: formatting of %s is unstable.\n\
\ before: %a\n\
\ after: %a\n\
%!"
Location.print_loc (loc x) s_kind pp_cmt x pp_cmt y ;
match cmt_kind with
| `Comment -> ()
| `Doc_comment ->
Format.fprintf fs
"Please tighten up this comment in the source or disable the \
formatting using the option --no-parse-docstrings.\n\
%!" )
type pos = Before | Within | After
type decoded_kind =
| Verbatim of string
| Doc of string
| Normal of string
| Code of string
| Asterisk_prefixed of string list
type decoded = {prefix: string; suffix: string; kind: decoded_kind}
(** [~content_offset] indicates at which column the body of the comment
starts (1-indexed). [~max_idnent] indicates the maximum amount of
indentation to trim. *)
let unindent_lines ?(max_indent = Stdlib.max_int) ~content_offset first_line
tl_lines =
let tl_indent =
List.fold_left ~init:max_indent
~f:(fun acc s ->
Option.value_map ~default:acc ~f:(min acc) (String.indent_of_line s) )
tl_lines
in
let fl_trim, fl_indent =
match String.indent_of_line first_line with
| Some i ->
(max 0 (min i (tl_indent - content_offset)), i + content_offset - 1)
| None -> (String.length first_line, max_indent)
in
let min_indent = min tl_indent fl_indent in
let first_line = String.drop_prefix first_line fl_trim in
first_line
:: List.map ~f:(fun s -> String.drop_prefix s min_indent) tl_lines
let unindent_lines ?max_indent ~content_offset txt =
match String.split ~on:'\n' txt with
| [] -> []
| hd :: tl -> unindent_lines ?max_indent ~content_offset hd tl
let is_all_whitespace s = String.for_all s ~f:Char.is_whitespace
let split_asterisk_prefixed =
let prefix = "*" in
let drop_prefix s = String.drop_prefix s (String.length prefix) in
let rec lines_are_asterisk_prefixed = function
| [] -> true
| [last] when is_all_whitespace last -> true
| hd :: tl ->
String.is_prefix hd ~prefix && lines_are_asterisk_prefixed tl
in
function
| fst_line :: (snd_line :: _ as tl)
when lines_are_asterisk_prefixed tl && not (is_all_whitespace snd_line)
->
Some (fst_line :: List.map tl ~f:drop_prefix)
| _ -> None
let mk ?(prefix = "") ?(suffix = "") kind = {prefix; suffix; kind}
let ~ txt loc =
let txt =
let f = function '\r' -> false | _ -> true in
String.filter txt ~f
in
let opn_offset =
let {Lexing.pos_cnum; pos_bol; _} = loc.Location.loc_start in
pos_cnum - pos_bol + 1
in
if String.length txt >= 2 then
match txt.[0] with
| '$' when not (Char.is_whitespace txt.[1]) -> mk (Verbatim txt)
| '$' ->
let dollar_suf = Char.equal txt.[String.length txt - 1] '$' in
let suffix = if dollar_suf then "$" else "" in
let code =
let len = String.length txt - if dollar_suf then 2 else 1 in
String.sub ~pos:1 ~len txt
in
mk ~prefix:"$" ~suffix (Code code)
| '=' -> mk (Verbatim txt)
| _ when is_all_whitespace txt ->
mk (Verbatim " ")
| _ when parse_comments_as_doc -> mk (Doc txt)
| _ -> (
let lines =
let content_offset = opn_offset + 2 in
unindent_lines ~content_offset txt
in
match split_asterisk_prefixed lines with
| Some deprefixed_lines -> mk (Asterisk_prefixed deprefixed_lines)
| None -> mk (Normal txt) )
else
match txt with
| "*" when Location.width loc = 4 -> mk (Verbatim "")
| ("*" | "$") as txt -> mk (Verbatim txt)
| "\n" | " " -> mk (Verbatim " ")
| _ -> mk (Normal txt)
let decode_docstring _loc = function
| "" -> mk (Verbatim "")
| ("*" | "$") as txt -> mk (Verbatim txt)
| "\n" | " " -> mk (Verbatim " ")
| txt -> mk ~prefix:"*" (Doc txt)
let decode ~ = function
| Comment {txt; loc} -> decode_comment ~parse_comments_as_doc txt loc
| Docstring {txt; loc} -> decode_docstring loc txt