Fix special case of alternate if statements
[hiphop-php.git] / hphp / hack / src / hh_format.ml
blobed7d4fa378650c2b2d83c4aad8fcb82614d252bd
1 (**
2 * Copyright (c) 2015, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
10 open Hh_core
11 open Utils
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
19 * the errors.
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
29 let debug () fnl =
30 let modes = [Some FileInfo.Mstrict; Some FileInfo.Mpartial] in
31 List.iter fnl begin fun (filepath : Path.t) ->
32 try
33 (* Checking that we can parse the output *)
34 let parsing_errors1, parser_output1 = Errors.do_ begin fun () ->
35 let rp =
36 Relative_path.create Relative_path.Dummy (filepath :> string) in
37 Full_fidelity_ast.(from_file_with_legacy (make_env ~fail_open:false rp))
38 end in
39 if parser_output1.Parser_hack.file_mode = None
40 || not (Errors.is_empty parsing_errors1)
41 then raise Exit;
43 if not (Errors.is_empty parsing_errors1)
44 then begin
45 Printf.eprintf
46 "The file had a syntax error before we even started: %s\n%!"
47 (filepath :> string);
48 end;
50 let content = parser_output1.Parser_hack.content in
51 let content =
52 match Format_hack.program modes filepath content with
53 | Format_hack.Success content -> content
54 | Format_hack.Disabled_mode ->
55 raise Exit
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
66 let content2 =
67 match content2 with
68 | Format_hack.Success content2 -> content2
69 | _ -> raise Format_error
71 if content <> content2
72 then begin
73 Printf.eprintf
74 "Applying the formatter twice lead to different results: %s\n%!"
75 (filepath :> string);
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;
80 close_out oc;
81 let oc = open_out file2 in
82 output_string oc content2;
83 close_out oc;
84 if Sys.win32 then
85 ignore (Sys.command ("fc "^file1^" "^file2))
86 else
87 ignore (Sys.command ("diff "^file1^" "^file2));
88 Sys_utils.unlink_no_fail file1;
89 Sys_utils.unlink_no_fail file2;
90 flush stdout
91 end;
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
97 end in
98 if not (Errors.is_empty parsing_errors2)
99 then begin
100 Printf.eprintf
101 "The output of the formatter could not be parsed: %s\n%!"
102 (filepath :> string);
103 end;
106 with
107 | Format_error ->
108 Printf.eprintf "Format error: %s\n%!" (filepath :> string);
109 | Exit ->
113 let debug_directory ~handle dir =
114 let path = Path.make dir in
115 let next = compose
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
119 MultiWorker.call
120 (Some workers)
121 ~job:debug
122 ~neutral:()
123 ~merge:(fun () () -> ())
124 ~next
126 (*****************************************************************************)
127 (* Parsing the command line *)
128 (*****************************************************************************)
130 let parse_args() =
131 let from = ref 0 in
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
146 Arg.parse
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;
195 close_out oc;
196 None
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 ->
202 None
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
211 | None -> acc
212 | Some err -> err :: acc
213 end ~init:acc
215 let directory modes ~handle dir =
216 let path = Path.make dir in
217 let next = compose
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
221 let messages =
222 MultiWorker.call
223 (Some workers)
224 ~job:(job_in_place modes)
225 ~neutral:[]
226 ~merge:List.rev_append
227 ~next
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);
242 exit 2
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)));
246 exit 2
247 | Format_hack.Disabled_mode ->
248 exit 0
250 (*****************************************************************************)
251 (* Helpers *)
252 (*****************************************************************************)
254 let read_stdin () =
255 let buf = Buffer.create 256 in
257 while true do
258 Buffer.add_string buf (read_line());
259 Buffer.add_char buf '\n';
260 done;
261 assert false
262 with End_of_file ->
263 Buffer.contents buf
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 (*****************************************************************************)
273 let () =
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 =
277 parse_args() in
278 if not test then FormatEventLogger.init (Unix.gettimeofday());
279 match files with
280 | [] when diff ->
281 let prefix =
282 match root with
283 | None ->
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;
287 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
293 | _ when diff ->
294 Printf.eprintf "--diff mode expects no files\n";
295 exit 2
296 | [] when apply_mode <> Format_mode.Print ->
297 Printf.eprintf "Cannot modify stdin in-place\n";
298 exit 2
299 | [] -> format_stdin modes from to_
300 | [dir] when Sys.is_directory dir ->
301 if debug
302 then debug_directory ~handle dir
303 else directory modes ~handle dir
304 | [filename] ->
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
311 | None -> ()
312 | Some error ->
313 Printf.eprintf "Error: %s\n" error;
314 exit 2
316 | Format_mode.Patch ->
317 Printf.eprintf "Error: --patch only supported in diff mode\n";
318 exit 2);
319 | _ ->
320 Printf.eprintf "More than one file given\n";
321 exit 2