2 * Copyright (c) 2015, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
13 (*****************************************************************************)
14 (* Debugging sections.
16 * This is useful when we modify the formatter and we want to make sure
17 * we didn't introduce errors.
18 * You can run 'hh_format --debug directory_name' and it will output all
21 * It takes all the files in a directory and verifies that:
22 * -) The result parses
23 * -) formatting is idempotent (for each file)
26 (*****************************************************************************)
27 exception Format_error
30 let modes = [Some
FileInfo.Mstrict
; Some
FileInfo.Mpartial
] in
31 List.iter fnl
begin fun (filepath
: Path.t
) ->
33 (* Checking that we can parse the output *)
34 let parsing_errors1, parser_output1
= Errors.do_
begin fun () ->
36 Relative_path.create
Relative_path.Dummy
(filepath
:> string) in
37 Full_fidelity_ast.(from_file_with_legacy
(make_env ~fail_open
:false rp))
39 if parser_output1
.Parser_hack.file_mode
= None
40 || not
(Errors.is_empty
parsing_errors1)
43 if not
(Errors.is_empty
parsing_errors1)
46 "The file had a syntax error before we even started: %s\n%!"
50 let content = parser_output1
.Parser_hack.content in
52 match Format_hack.program
modes filepath
content with
53 | Format_hack.Success
content -> content
54 | Format_hack.Disabled_mode
->
56 | Format_hack.Parsing_error _
->
57 Printf.eprintf
"Parsing: %s\n%!" (filepath
:> string);
59 | Format_hack.Internal_error
->
60 Printf.eprintf
"Internal: %s\n%!" (filepath
:> string);
64 (* Checking for idempotence *)
65 let content2 = Format_hack.program
modes filepath
content in
68 | Format_hack.Success
content2 -> content2
69 | _
-> raise Format_error
71 if content <> content2
74 "Applying the formatter twice lead to different results: %s\n%!"
76 let file1 = Filename.temp_file
"xx_" "_1.php" in
77 let file2 = Filename.temp_file
"xx_" "_2.php" in
78 let oc = open_out
file1 in
79 output_string
oc content;
81 let oc = open_out
file2 in
82 output_string
oc content2;
85 ignore
(Sys.command
("fc "^
file1^
" "^
file2))
87 ignore
(Sys.command
("diff "^
file1^
" "^
file2));
88 Sys_utils.unlink_no_fail
file1;
89 Sys_utils.unlink_no_fail
file2;
93 (* Checking that we can parse the output *)
94 let parsing_errors2, _parser_output2
= Errors.do_
begin fun () ->
95 let rp = Relative_path.(create Dummy
(filepath
:> string)) in
96 Parser_hack.program_with_default_popt
rp content
98 if not
(Errors.is_empty
parsing_errors2)
101 "The output of the formatter could not be parsed: %s\n%!"
102 (filepath
:> string);
108 Printf.eprintf
"Format error: %s\n%!" (filepath
:> string);
113 let debug_directory ~handle dir
=
114 let path = Path.make dir
in
116 (fun paths
-> paths
|> List.map ~f
:Path.make
|> Bucket.of_list
)
117 (Find.make_next_files ~filter
:FindUtils.is_php
path) in
118 let workers = ServerWorker.make
GlobalConfig.gc_control handle
in
123 ~merge
:(fun () () -> ())
126 (*****************************************************************************)
127 (* Parsing the command line *)
128 (*****************************************************************************)
132 let to_ = ref max_int
in
133 let files = ref [] in
134 let diff = ref false in
135 let modes = ref [Some
FileInfo.Mstrict
; Some
FileInfo.Mpartial
] in
136 let root = ref None
in
137 let debug = ref false in
138 let test = ref false in
139 let apply_mode = ref Format_mode.Print
in
140 let set_apply_mode mode
() = match !apply_mode with
141 | Format_mode.Patch
-> () (* Patch implies In_place but not vice versa *)
142 | Format_mode.In_place
when mode
= Format_mode.Patch
-> apply_mode := mode
143 | Format_mode.In_place
-> ()
144 | Format_mode.Print
-> apply_mode := mode
148 "--from", Arg.Set_int
from,
149 "[int] start after character position";
151 "--to", Arg.Set_int
to_,
152 "[int] stop after character position";
154 "-i", Arg.Unit
(set_apply_mode Format_mode.In_place
),
155 "modify the files in place";
157 "--in-place", Arg.Unit
(set_apply_mode Format_mode.In_place
),
158 "modify the files in place";
160 "-p", Arg.Unit
(set_apply_mode Format_mode.Patch
),
161 "interactively choose hunks of patches to apply (implies --in-place)";
163 "--patch", Arg.Unit
(set_apply_mode Format_mode.Patch
),
164 "interactively choose hunks of patches to apply (implies --in-place)";
166 "--diff", Arg.Set
diff,
167 "formats the changed lines in a diff "^
168 "(example: git diff | hh_format --diff)";
170 "--yolo", Arg.Unit
(fun () ->
171 modes := [Some
FileInfo.Mdecl
; None
(* PHP *)]),
172 "Formats *only* PHP and decl-mode files. Results may be unreliable; "^
173 "you should *always* inspect the formatted output before committing it!";
175 "--root", Arg.String
(fun x
-> root := Some x
),
176 "specifies a root directory (useful in diff mode)";
178 "--debug", Arg.Set
debug, "";
179 "--test", Arg.Set
test, "";
181 (fun file
-> files := file
:: !files)
182 (Printf.sprintf
"Usage: %s (filename|directory)" Sys.argv
.(0));
183 !files, !from, !to_, !apply_mode, !debug, !diff, !modes, !root, !test
185 (*****************************************************************************)
186 (* Formats a file in place *)
187 (*****************************************************************************)
189 let format_in_place modes (filepath
: Path.t
) =
190 let content = Sys_utils.cat
(filepath
:> string) in
191 match Format_hack.program
modes filepath
content with
192 | Format_hack.Success result
->
193 let oc = open_out
(filepath
:> string) in
194 output_string
oc result
;
197 | Format_hack.Internal_error
->
198 Some
(Printf.sprintf
"Internal error in %s\n" (Path.to_string filepath
))
199 | Format_hack.Parsing_error errorl
->
200 Some
(Errors.to_string
(Errors.to_absolute
(List.hd_exn errorl
)))
201 | Format_hack.Disabled_mode
->
204 (*****************************************************************************)
205 (* Formats all the hack files in a directory (in place) *)
206 (*****************************************************************************)
208 let job_in_place modes acc fnl
=
209 List.fold_left fnl ~f
:begin fun acc filename
->
210 match format_in_place modes filename
with
212 | Some err
-> err
:: acc
215 let directory modes ~handle dir
=
216 let path = Path.make dir
in
218 (fun paths
-> paths
|> List.map ~f
:Path.make
|> Bucket.of_list
)
219 (Find.make_next_files ~filter
:FindUtils.is_php
path) in
220 let workers = ServerWorker.make
GlobalConfig.gc_control handle
in
224 ~job
:(job_in_place modes)
226 ~merge
:List.rev_append
229 List.iter
messages (Printf.eprintf
"%s\n")
231 (*****************************************************************************)
232 (* Applies the formatter directly to a string. *)
233 (*****************************************************************************)
235 let format_string modes file
from to_ content =
236 match Format_hack.region
modes file
from to_ content with
237 | Format_hack.Success
content ->
238 output_string stdout
content
239 | Format_hack.Internal_error
->
240 Printf.eprintf
"Internal error in %s\n%!"
241 (Path.to_string file
);
243 | Format_hack.Parsing_error error
->
244 Printf.eprintf
"Parsing error\n%s\n%!"
245 (Errors.to_string
(Errors.to_absolute
(List.hd_exn error
)));
247 | Format_hack.Disabled_mode
->
250 (*****************************************************************************)
252 (*****************************************************************************)
255 let buf = Buffer.create
256 in
258 Buffer.add_string
buf (read_line
());
259 Buffer.add_char
buf '
\n'
;
265 let format_stdin modes from to_ =
266 let content = read_stdin () in
267 format_string modes Path.dummy_path
from to_ content
269 (*****************************************************************************)
270 (* The main entry point. *)
271 (*****************************************************************************)
274 let handle = SharedMem.init
GlobalConfig.default_sharedmem_config
in
275 PidLog.log_oc
:= Some
(open_out
Sys_utils.null_path
);
276 let files, from, to_, apply_mode, debug, diff, modes, root, test =
278 if not
test then FormatEventLogger.init
(Unix.gettimeofday
());
284 Printf.eprintf
"No root specified, trying to guess one\n";
285 let root = ClientArgsUtils.get_root None
in
286 Printf.eprintf
"Guessed root: %a\n%!" Path.output
root;
288 | Some
root -> Path.make
root
290 let diff = read_stdin () in
291 let file_and_modified_lines = Format_diff.parse_diff
prefix diff in
292 Format_diff.apply
modes apply_mode ~
diff:file_and_modified_lines
294 Printf.eprintf
"--diff mode expects no files\n";
296 | [] when apply_mode <> Format_mode.Print
->
297 Printf.eprintf
"Cannot modify stdin in-place\n";
299 | [] -> format_stdin modes from to_
300 | [dir
] when Sys.is_directory dir
->
302 then debug_directory ~
handle dir
303 else directory modes ~
handle dir
305 let filepath = Path.make filename
in
306 (match apply_mode with
307 | Format_mode.Print
->
308 format_string modes filepath from to_ (Path.cat
filepath)
309 | Format_mode.In_place
-> begin
310 match format_in_place modes filepath with
313 Printf.eprintf
"Error: %s\n" error
;
316 | Format_mode.Patch
->
317 Printf.eprintf
"Error: --patch only supported in diff mode\n";
320 Printf.eprintf
"More than one file given\n";