From a59e3c64ec9c49629b2914d0650a0fc38e224d70 Mon Sep 17 00:00:00 2001 From: Guillaume Petiot Date: Wed, 15 Jan 2020 22:10:30 +0700 Subject: [PATCH] New option --numeric to print indentation for emacs --- CHANGES.md | 2 + bin/ocamlformat.ml | 12 ++++ emacs/ocamlformat.el | 88 ++++++++++++++++++++++++ lib/Conf.ml | 31 ++++++++- lib/Conf.mli | 11 ++- lib/Indent.ml | 124 +++++++++++++++++++++++++++++++++ lib/Indent.mli | 26 +++++++ lib/Source.ml | 39 +++++++++++ lib/Source.mli | 2 + lib/Translation_unit.ml | 44 ++++++++++++ lib/Translation_unit.mli | 21 +++++- ocamlformat-help.txt | 5 ++ test/unit/test_unit.ml | 143 ++++++++++++++++++++++++++++++++++++++- test/unit/testable.ml | 6 ++ test/unit/testable.mli | 1 + 15 files changed, 549 insertions(+), 6 deletions(-) create mode 100644 lib/Indent.ml create mode 100644 lib/Indent.mli create mode 100644 test/unit/testable.ml create mode 100644 test/unit/testable.mli diff --git a/CHANGES.md b/CHANGES.md index 6be1cf0118..9bf9f69fcb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -44,6 +44,8 @@ #### New features + + Line/region indentation feature (emacs integration) (#1207, @gpetiot) + ### 0.15.0 (2020-08-06) #### Changes diff --git a/bin/ocamlformat.ml b/bin/ocamlformat.ml index a9621d7a67..1d7bc2436b 100644 --- a/bin/ocamlformat.ml +++ b/bin/ocamlformat.ml @@ -59,6 +59,18 @@ let run_action action opts = | Error e -> Error (fun () -> print_error conf opts ~input_name e) in Result.combine_errors_unit (List.map inputs ~f) + | In_out ({kind= Conf.Kind k; file; name= input_name; conf}, None) + when Option.is_some opts.numeric -> ( + let source = source_from_file file in + let range = Option.value_exn opts.numeric in + match + Translation_unit.indentation k ~input_name ~source ~range conf opts + with + | Ok indents -> + List.iter indents ~f:(fun i -> + Stdio.print_endline (Int.to_string i)) ; + Ok () + | Error e -> Error [(fun () -> print_error conf opts ~input_name e)] ) | In_out ({kind; file; name= input_name; conf}, output_file) -> ( let source = source_from_file file in match format ?output_file ~kind ~input_name ~source conf opts with diff --git a/emacs/ocamlformat.el b/emacs/ocamlformat.el index 343fa2efc3..24e31247c6 100644 --- a/emacs/ocamlformat.el +++ b/emacs/ocamlformat.el @@ -287,6 +287,94 @@ function." (delete-file bufferfile) (delete-file outputfile))) +(defun ocamlformat-args (name start-line end-line) + (let* + ((margin-args + (cond + ((equal ocamlformat-margin-mode 'window) + (list "--margin" (number-to-string (window-body-width)))) + ((equal ocamlformat-margin-mode 'fill) + (list "--margin" (number-to-string fill-column))) + (t + '()))) + (enable-args + (cond + ((equal ocamlformat-enable 'disable) + (list "--disable")) + ((equal ocamlformat-enable 'enable-outside-detected-project) + (list "--enable-outside-detected-project")) + (t + '()))) + (extension-args + (cond + ((eq ocamlformat-file-kind 'implementation) + (list "--impl")) + ((eq ocamlformat-file-kind 'interface) + (list "--intf"))))) + (append margin-args enable-args extension-args + (list + "-" + "--name" name + "--numeric" (format "%d-%d" start-line end-line))))) + +(defun ocamlformat-region (start end) + (interactive "r") + (let* + ((ext (file-name-extension buffer-file-name t)) + (bufferfile (file-truename (make-temp-file "ocamlformat" nil ext))) + (errorfile (file-truename (make-temp-file "ocamlformat" nil ext))) + (errbuf + (cond + ((eq ocamlformat-show-errors 'buffer) + (get-buffer-create "*compilation*")) + ((eq ocamlformat-show-errors 'echo) + (get-buffer-create "*OCamlFormat stderr*")))) + (start-line (line-number-at-pos start)) + (end-line (line-number-at-pos end)) + (indents-str + (with-output-to-string + (if (/= 0 + (apply 'call-process-region + (point-min) (point-max) ocamlformat-command nil + (list standard-output errorfile) nil + (ocamlformat-args buffer-file-name start-line end-line))) + (progn + (if errbuf + (progn + (with-current-buffer errbuf + (setq buffer-read-only nil) + (erase-buffer)) + (ocamlformat--process-errors + (buffer-file-name) bufferfile errorfile errbuf))) + (message "Could not apply ocamlformat"))))) + (indents (mapcar 'string-to-number (split-string indents-str)))) + (save-excursion + (goto-char start) + (mapcar + #'(lambda (indent) (indent-line-to indent) (forward-line)) + indents)) + (delete-file errorfile) + (delete-file bufferfile))) + +(defun ocamlformat-line () + (interactive nil) + (ocamlformat-region (point) (point))) + +;;;###autoload +(defun ocamlformat-setup-indent () + (interactive nil) + (set (make-local-variable 'indent-line-function) #'ocamlformat-line) + (set (make-local-variable 'indent-region-function) #'ocamlformat-region)) + +;;;###autoload +(defun ocamlformat-caml-mode-setup () + (ocamlformat-setup-indent) + (local-unset-key "\t")) ;; caml-mode rebinds TAB ! + +(add-hook 'tuareg-mode-hook 'ocamlformat-setup-indent t) + +(add-hook 'caml-mode-hook 'ocamlformat-caml-mode-setup t) + (provide 'ocamlformat) ;;; ocamlformat.el ends here diff --git a/lib/Conf.ml b/lib/Conf.ml index 376e0cf9a6..08c4057051 100644 --- a/lib/Conf.ml +++ b/lib/Conf.ml @@ -1305,6 +1305,20 @@ let name = mk ~default Arg.(value & opt (some string) default & info ["name"] ~doc ~docs ~docv) +let numeric = + let doc = + "Instead of re-indenting the file, output one integer per line \ + representing the indentation value, printing as many values as lines \ + in the range between lines X and Y (included)." + in + let default = None in + let docv = "X-Y" in + mk ~default + Arg.( + value + & opt (some (pair ~sep:'-' int int)) default + & info ["numeric"] ~doc ~docs ~docv) + let ocp_indent_options = let unsupported ocp_indent = (ocp_indent, ([], "")) in let alias ocp_indent ocamlformat = @@ -2193,7 +2207,17 @@ let make_action ~enable_outside_detected_project ~root action inputs = Ok (Check (List.map files ~f)) | `Check, `Stdin (name, kind) -> Ok (Check [make_stdin ?name kind]) -type opts = {debug: bool; margin_check: bool; format_invalid_files: bool} +type opts = + { debug: bool + ; margin_check: bool + ; format_invalid_files: bool + ; numeric: (int * int) option } + +let default_opts = + { debug= false + ; margin_check= false + ; format_invalid_files= false + ; numeric= None } let validate () = let root = @@ -2221,7 +2245,10 @@ let validate () = match !format_invalid_files with Some `Auto -> true | _ -> false in let opts = - {debug= !debug; margin_check= !margin_check; format_invalid_files} + { debug= !debug + ; margin_check= !margin_check + ; format_invalid_files + ; numeric= !numeric } in `Ok (action, opts) diff --git a/lib/Conf.mli b/lib/Conf.mli index 06c7794e10..3c589c863f 100644 --- a/lib/Conf.mli +++ b/lib/Conf.mli @@ -98,12 +98,21 @@ type action = (** Check whether the input files already are formatted. *) | Print_config of t (** Print the configuration and exit. *) +val conventional_profile : t + +val ocamlformat_profile : t + +val janestreet_profile : t + (** Options changing the tool's behavior *) type opts = { debug: bool (** Generate debugging output if true. *) ; margin_check: bool (** Check whether the formatted output exceeds the margin. *) - ; format_invalid_files: bool } + ; format_invalid_files: bool + ; numeric: (int * int) option } + +val default_opts : opts val action : unit -> (action * opts) Cmdliner.Term.result (** Formatting action: input type and source, and output destination. *) diff --git a/lib/Indent.ml b/lib/Indent.ml new file mode 100644 index 0000000000..7b99291fb4 --- /dev/null +++ b/lib/Indent.ml @@ -0,0 +1,124 @@ +(**************************************************************************) +(* *) +(* OCamlFormat *) +(* *) +(* Copyright (c) Facebook, Inc. and its affiliates. *) +(* *) +(* This source code is licensed under the MIT license found in *) +(* the LICENSE file in the root directory of this source tree. *) +(* *) +(**************************************************************************) + +open Migrate_ast +open Result.Monad_infix + +let rec loc_of_line loctree locs line = + match locs with + | [] -> None + | (h : Location.t) :: t -> + if h.loc_start.pos_lnum = line then Some h + else if h.loc_start.pos_lnum <= line && line <= h.loc_end.pos_lnum then + match Loc_tree.children loctree h with + | [] -> Some h + | children -> ( + match loc_of_line loctree children line with + | Some loc -> Some loc + | None -> Some h ) + else loc_of_line loctree t line + +let matching_loc loc locs locs' = + match List.zip locs locs' with + | Ok assoc -> ( + let equal x y = Location.compare x y = 0 in + match List.Assoc.find assoc ~equal loc with + | Some loc -> Ok loc + | None -> + Error (`Msg "Cannot find matching location in formatted output.") ) + | Unequal_lengths -> + Error (`Msg "Cannot match pre-post formatting locations.") + +let indentation_of_line l = String.(length l - length (lstrip l)) + +let indentation_1_line ?prev (loctree, locs) (_, locs') formatted_src nlines + ~line = + if line = nlines + 1 then Ok 0 + else + match loc_of_line loctree locs line with + | Some loc -> ( + matching_loc loc locs locs' + >>= fun (loc' : Location.t) -> + let line_nb = loc'.loc_start.pos_lnum - 1 in + let line' = + List.nth_exn (String.split_lines formatted_src) line_nb + in + let indent = indentation_of_line line' in + match prev with + | Some (prev_indent, prev_line) + when indent = prev_indent || indent = 0 -> ( + (* in case this is a line that is split but could fit on a single + line, consecutive lines will have the same indentation, we try + to infer some artificial indentation here, even though it will + be squeezed together and fit on a single line when the whole + file is reformatted *) + match + Source.infer_indent_from_prev_line (Source.create prev_line) + with + | Some i -> Ok (prev_indent + i) + | None -> Ok indent ) + | _ -> Ok indent ) + | None -> Ok 0 + +let indentation_1_line_fallback ?prev src nlines ~line = + if line = nlines + 1 then Ok 0 + else + let line_nb = line - 1 in + let line' = List.nth_exn (String.split_lines src) line_nb in + let indent = indentation_of_line line' in + match prev with + | Some (prev_indent, prev_line) -> ( + match Source.infer_indent_from_prev_line (Source.create prev_line) with + | Some i -> Ok (prev_indent + i) + | None -> Ok indent ) + | _ -> Ok indent + +type 'a parsed = {ast: 'a; source: string} + +let indent_from_locs fragment ~unformatted:{ast; source} + ~formatted:{ast= formatted_ast; source= formatted_source} ~lines + ~range:(low, high) = + let nlines = List.length lines in + let prev = + Option.map + (List.nth lines (low - 2)) + ~f:(fun line -> (indentation_of_line line, line)) + in + let locs = Loc_tree.of_ast fragment ast (Source.create source) in + let locs' = + Loc_tree.of_ast fragment formatted_ast (Source.create formatted_source) + in + let rec aux ?prev acc i = + if i > high then Ok (List.rev acc) + else + indentation_1_line ?prev locs locs' formatted_source nlines ~line:i + >>= fun indent -> + let line = Option.value (List.nth lines (i - 1)) ~default:"" in + aux ~prev:(indent, line) (indent :: acc) (i + 1) + in + aux ?prev [] low + +let indent_from_lines ~source ~lines ~range:(low, high) = + let nlines = List.length lines in + let prev = + Option.map + (List.nth lines (low - 2)) + ~f:(fun line -> (indentation_of_line line, line)) + in + let rec aux ?prev acc i = + if i > high then Ok (List.rev acc) + else + indentation_1_line_fallback ?prev source nlines ~line:i + >>= fun indent -> + let line = Option.value (List.nth lines (i - 1)) ~default:"" in + aux ~prev:(indent, line) (indent :: acc) (i + 1) + in + aux ?prev [] low diff --git a/lib/Indent.mli b/lib/Indent.mli new file mode 100644 index 0000000000..f84f0f9b34 --- /dev/null +++ b/lib/Indent.mli @@ -0,0 +1,26 @@ +(**************************************************************************) +(* *) +(* OCamlFormat *) +(* *) +(* Copyright (c) Facebook, Inc. and its affiliates. *) +(* *) +(* This source code is licensed under the MIT license found in *) +(* the LICENSE file in the root directory of this source tree. *) +(* *) +(**************************************************************************) + +type 'a parsed = {ast: 'a; source: string} + +val indent_from_locs : + 'a Migrate_ast.Mapper.fragment + -> unformatted:'a parsed + -> formatted:'a parsed + -> lines:string list + -> range:int * int + -> (int list, [`Msg of string]) Result.t + +val indent_from_lines : + source:string + -> lines:string list + -> range:int * int + -> (int list, [`Msg of string]) Result.t diff --git a/lib/Source.ml b/lib/Source.ml index eae4e62d3c..4f30327609 100644 --- a/lib/Source.ml +++ b/lib/Source.ml @@ -372,3 +372,42 @@ let loc_of_first_token_at t loc kwd = match tokens_at t loc ~filter:(Poly.( = ) kwd) with | [] -> None | (_, loc) :: _ -> Some loc + +let indent_after_token (tok : Parser.token) = + match tok with + (* indent further *) + | AMPERAMPER | AMPERSAND | AND | AS | BAR | BARBAR | BARRBRACKET | BEGIN + |CLASS | COLON | COLONCOLON | COLONEQUAL | COLONGREATER | CONSTRAINT + |DO | DOT | DOTDOT | DOWNTO | ELSE | EQUAL | EXCEPTION | EXTERNAL | FOR + |FUN | FUNCTION | FUNCTOR | GREATER | IF | INCLUDE | INFIXOP0 _ + |INFIXOP1 _ | INFIXOP2 _ | INFIXOP3 _ | INFIXOP4 _ | DOTOP _ | LETOP _ + |ANDOP _ | INHERIT | INITIALIZER | LAZY | LBRACE | LBRACELESS | LBRACKET + |LBRACKETBAR | LBRACKETLESS | LBRACKETGREATER | LBRACKETPERCENT + |LBRACKETPERCENTPERCENT | LESS | LESSMINUS | LET | LPAREN | LBRACKETAT + |LBRACKETATAT | LBRACKETATATAT | MATCH | METHOD | MINUS | MINUSDOT + |MINUSGREATER | MODULE | MUTABLE | NEW | NONREC | OBJECT | OF | OPEN + |OR | PERCENT | PLUS | PLUSDOT | PLUSEQ | PREFIXOP _ | PRIVATE + |QUESTION | REC | HASH | HASHOP _ | SIG | STAR | STRUCT | THEN | TILDE + |TO | TRY | TYPE | VAL | VIRTUAL | WHEN | WHILE | WITH -> + Some 2 + (* same indent *) + | IN | DONE | END | SEMI | COMMA -> Some 0 + (* cannot tell *) + | ASSERT | BACKQUOTE | BANG | CHAR _ | EOF | EOL | FALSE | FLOAT _ + |GREATERRBRACE | GREATERRBRACKET | INT _ | LABEL _ | LIDENT _ + |OPTLABEL _ | QUOTE | RBRACE | RBRACKET | RPAREN | SEMISEMI | STRING _ + |TRUE | UIDENT _ | UNDERSCORE | COMMENT _ | DOCSTRING _ + |QUOTED_STRING_ITEM _ | QUOTED_STRING_EXPR _ -> + None + +let infer_indent_from_prev_line t = + let toks = + let lexbuf = Lexing.from_string t in + let rec loop acc = + match Lexer.token lexbuf with + | Parser.EOF -> acc + | tok -> loop ((tok, Location.curr lexbuf) :: acc) + in + loop [] + in + match toks with [] -> None | (tok, _) :: _ -> indent_after_token tok diff --git a/lib/Source.mli b/lib/Source.mli index 8b12f5d76f..8d6d64af0b 100644 --- a/lib/Source.mli +++ b/lib/Source.mli @@ -85,3 +85,5 @@ val is_quoted_string : t -> Location.t -> bool val loc_of_first_token_at : t -> Location.t -> Parser.token -> Location.t option + +val infer_indent_from_prev_line : t -> int option diff --git a/lib/Translation_unit.ml b/lib/Translation_unit.ml index 223cc64fcf..09e3bc2687 100644 --- a/lib/Translation_unit.ml +++ b/lib/Translation_unit.ml @@ -13,6 +13,7 @@ open Migrate_ast open Parse_with_comments +open Result.Monad_infix exception Internal_error of @@ -397,3 +398,46 @@ let parse_and_format fragment ?output_file ~input_name ~source conf opts = parse_result fragment conf opts ~source ~input_name >>= fun parsed -> format fragment ?output_file ~input_name ~source ~parsed conf opts + +let force_parse_quiet fragment ~source conf = + match parse fragment conf ~source with + | exception _ -> ( + match parse fragment conf ~source:(recover fragment source) with + | exception exn -> Error (Invalid_source {exn}) + | parsed -> Ok parsed ) + | parsed -> Ok parsed + +let check_line nlines i = + (* the last line of the buffer (nlines + 1) should not raise an error *) + if 1 <= i && i <= nlines + 1 then Ok () + else Error (User_error (Format.sprintf "Invalid line number %i." i)) + +let check_range nlines (low, high) = + check_line nlines low + >>= fun () -> + check_line nlines high + >>= fun () -> + if low <= high then Ok () + else Error (User_error (Format.sprintf "Invalid range %i-%i." low high)) + +let indentation fragment ~input_name ~source ~range:(low, high) conf opts = + let lines = String.split_lines source in + let nlines = List.length lines in + check_range nlines (low, high) + >>= fun () -> + Location.input_name := input_name ; + ( match + force_parse_quiet fragment ~source conf + >>= fun parsed -> + format fragment ~input_name ~source ~parsed conf opts + >>= fun formatted_source -> + force_parse_quiet fragment ~source:formatted_source conf + >>| fun formatted_ast -> + ( Indent.{ast= parsed.ast; source} + , Indent.{ast= formatted_ast.ast; source= formatted_source} ) + with + | Ok (unformatted, formatted) -> + Indent.indent_from_locs fragment ~unformatted ~formatted ~lines + ~range:(low, high) + | Error _ -> Indent.indent_from_lines ~source ~lines ~range:(low, high) ) + |> Result.map_error ~f:(fun (`Msg s) -> Ocamlformat_bug {exn= failwith s}) diff --git a/lib/Translation_unit.mli b/lib/Translation_unit.mli index 7ffff26078..25f63c69c0 100644 --- a/lib/Translation_unit.mli +++ b/lib/Translation_unit.mli @@ -9,7 +9,11 @@ (* *) (**************************************************************************) -type error +type error = + | Invalid_source of {exn: exn} + | Unstable of {iteration: int; prev: string; next: string} + | Ocamlformat_bug of {exn: exn} + | User_error of string val parse_and_format : _ list Migrate_ast.Mapper.fragment @@ -19,9 +23,22 @@ val parse_and_format : -> Conf.t -> Conf.opts -> (string, error) Result.t -(** [parse_and_format_impl conf ?output_file ~input_name ~source] parses and +(** [parse_and_format conf ?output_file ~input_name ~source] parses and formats [source] as a list of fragments. *) +val indentation : + _ list Migrate_ast.Mapper.fragment + -> input_name:string + -> source:string + -> range:int * int + -> Conf.t + -> Conf.opts + -> (int list, error) Result.t +(** [indentation ~input_name ~source ~range conf opts] returns the + indentation of the range of lines [range] (line numbers ranging from 1 to + number of lines), where the line numbers are relative to [source] and the + indentation is relative to the formatted output. *) + val print_error : ?fmt:Format.formatter -> debug:bool diff --git a/ocamlformat-help.txt b/ocamlformat-help.txt index ca0a9c1707..8aae415460 100644 --- a/ocamlformat-help.txt +++ b/ocamlformat-help.txt @@ -560,6 +560,11 @@ OPTIONS Do not check that the version matches the one specified in .ocamlformat. + --numeric=X-Y + Instead of re-indenting the file, output one integer per line + representing the indentation value, printing as many values as + lines in the range between lines X and Y (included). + -o DST, --output=DST Output file. Mutually exclusive with --inplace. Write to stdout if omitted. diff --git a/test/unit/test_unit.ml b/test/unit/test_unit.ml index 51a782088d..048b5cd629 100644 --- a/test/unit/test_unit.ml +++ b/test/unit/test_unit.ml @@ -110,10 +110,151 @@ module Test_noit = struct let tests = test_dump @ test_roots @ test_children end +let reindent ~source ~range:(low, high) indents = + let lines = String.split_lines source in + let low = low - 1 and high = high - 1 in + let lines = + List.mapi lines ~f:(fun i line -> + if i < low then line + else if low <= i && i <= high then + let indent = List.nth_exn indents (i - low) in + let line = String.lstrip line in + let spaces = String.make indent ' ' in + spaces ^ line + else line) + in + String.concat ~sep:"\n" lines + +module Test_translation_unit = struct + let test_indent = + let test name ~source ~range expected = + let test_name = "indent: " ^ name in + ( test_name + , `Quick + , fun () -> + let got = + Translation_unit.indentation Use_file ~input_name:"_" ~source + ~range Conf.ocamlformat_profile Conf.default_opts + |> Result.map ~f:(reindent ~source ~range) + in + Alcotest.check + Alcotest.(result string Testable.error) + test_name expected got ) + in + [ test "invalid low" ~source:"foo\nbar" ~range:(0, 1) + (Error (User_error "Invalid line number 0.")) + ; test "invalid high" ~source:"foo\nbar" ~range:(1, 4) + (Error (User_error "Invalid line number 4.")) + ; test "invalid range" ~source:"foo\nbar" ~range:(2, 1) + (Error (User_error "Invalid range 2-1.")) + ; test "empty buffer" ~source:"" ~range:(1, 1) (Ok "") + ; test "last buffer line" ~source:"foo\nbar" ~range:(2, 3) + (Ok "foo\nbar") + ; test "already formatted" + ~source: + {|let foooooo = + let baaaaar = + let woooooo = foooooo in + let xooooo = bar + foo in + woooooo + in + bar +|} + ~range:(1, 7) + (Ok + {|let foooooo = + let baaaaar = + let woooooo = foooooo in + let xooooo = bar + foo in + woooooo + in + bar|}) + ; test "not already formatted" + ~source: + {|let foooooooooooo = let foooooooo = foooooooooooo in foooooooooooo + +let foooooooooooo = let foooooooooooooo = +let woooooooooooooooo = koooooooooooooooo in +baaaaaar +in +hooohoooo +|} + ~range:(1, 7) + (Ok + {|let foooooooooooo = let foooooooo = foooooooooooo in foooooooooooo + +let foooooooooooo = let foooooooooooooo = + let woooooooooooooooo = koooooooooooooooo in + baaaaaar + in + hooohoooo|}) + ; test "with parens and begin/end" + ~source: + {|let x = begin + let y = + (if (k = x) + then + begin match k,v with [x; y] -> ( foo; + (if (z) then foo else bar) ) + end + else + foo) + in + foooooo + end|} + ~range:(1, 12) + (Ok + {|let x = begin + let y = + (if (k = x) + then + begin match k,v with [x; y] -> ( foo; + (if (z) then foo else bar) ) + end + else + foo) + in + foooooo + end|}) + ; test "split over multiple lines" + ~source:{|let fooooo = +[ +foooooo ; +foooooooo ; +fooooooo +]|} + ~range:(1, 6) + (Ok {|let fooooo = + [ + foooooo ; + foooooooo ; + fooooooo +]|}) + ; test "invalid file" + ~source:{|let foooooo = +let foooooooooooo = +( +[ +fun x -> +foooooo|} + ~range:(1, 6) + (Ok + {|let foooooo = + let foooooooooooo = + ( + [ + fun x -> + foooooo|}) + ] + + let tests = test_indent +end + let tests = [ ("Location", Test_location.tests) ; ("non overlapping interval tree", Test_noit.tests) ; ("Ast", Test_ast.tests) - ; ("Literal_lexer", Test_literal_lexer.tests) ] + ; ("Literal_lexer", Test_literal_lexer.tests) + ; ("Translation_unit", Test_translation_unit.tests) ] let () = Alcotest.run "ocamlformat" tests diff --git a/test/unit/testable.ml b/test/unit/testable.ml new file mode 100644 index 0000000000..3b1c443643 --- /dev/null +++ b/test/unit/testable.ml @@ -0,0 +1,6 @@ +let error = + let pp fs = + Ocamlformat_lib.Translation_unit.print_error ~fmt:fs ~debug:false + ~quiet:false ~input_name:"_" + in + Alcotest.testable pp ( = ) diff --git a/test/unit/testable.mli b/test/unit/testable.mli new file mode 100644 index 0000000000..ab5923f741 --- /dev/null +++ b/test/unit/testable.mli @@ -0,0 +1 @@ +val error : Ocamlformat_lib.Translation_unit.error Alcotest.testable