From e91a63bb49545cc6ee99001461fe604cb429b01b Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 16 Sep 2024 21:53:36 +0200 Subject: [PATCH 1/6] PoC: emit code actions from the compiler that the editor tooling can use --- jscomp/bsc/rescript_compiler_main.ml | 3 ++ jscomp/common/js_config.ml | 1 + jscomp/common/js_config.mli | 2 + jscomp/ml/code_action_data.ml | 62 ++++++++++++++++++++++++++++ jscomp/ml/code_action_data.mli | 13 ++++++ jscomp/ml/typecore.ml | 29 ++++++++----- jscomp/ml/typecore.mli | 4 +- 7 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 jscomp/ml/code_action_data.ml create mode 100644 jscomp/ml/code_action_data.mli diff --git a/jscomp/bsc/rescript_compiler_main.ml b/jscomp/bsc/rescript_compiler_main.ml index 16e927f42b..369abd3fcb 100644 --- a/jscomp/bsc/rescript_compiler_main.ml +++ b/jscomp/bsc/rescript_compiler_main.ml @@ -273,6 +273,9 @@ let buckle_script_flags : (string * Bsc_args.spec * string) array = "-bs-ast", unit_call(fun _ -> Js_config.binary_ast := true; Js_config.syntax_only := true), "*internal* Generate binary .mli_ast and ml_ast and stop"; + "-code-action-data", unit_call(fun _ -> Js_config.code_action_data := true), + "*internal* Emit code action data"; + "-bs-syntax-only", set Js_config.syntax_only, "*internal* Only check syntax"; diff --git a/jscomp/common/js_config.ml b/jscomp/common/js_config.ml index 84f4e22f68..c5854cc3b1 100644 --- a/jscomp/common/js_config.ml +++ b/jscomp/common/js_config.ml @@ -57,6 +57,7 @@ let all_module_aliases = ref false let no_stdlib = ref false let no_export = ref false let as_ppx = ref false +let code_action_data = ref false let int_of_jsx_version = function | Jsx_v3 -> 3 diff --git a/jscomp/common/js_config.mli b/jscomp/common/js_config.mli index 31855eaca7..58253c291a 100644 --- a/jscomp/common/js_config.mli +++ b/jscomp/common/js_config.mli @@ -112,3 +112,5 @@ val as_pp : bool ref val self_stack : string Stack.t val modules : bool ref + +val code_action_data : bool ref diff --git a/jscomp/ml/code_action_data.ml b/jscomp/ml/code_action_data.ml new file mode 100644 index 0000000000..02af539fe3 --- /dev/null +++ b/jscomp/ml/code_action_data.ml @@ -0,0 +1,62 @@ +type code_action_type = WrapWith of {left: string; right: string} +type code_action_style = Regular | QuickFix +type code_action = { + style: code_action_style; + type_: code_action_type; + loc: Location.t; + title: string; +} + +let code_action_data = ref [] +let add_code_action (data : code_action) = + code_action_data := data :: !code_action_data +let get_code_action_data () = !code_action_data + +let escape text = + let ln = String.length text in + let buf = Buffer.create ln in + let rec loop i = + if i < ln then ( + (match text.[i] with + | '\012' -> Buffer.add_string buf "\\f" + | '\\' -> Buffer.add_string buf "\\\\" + | '"' -> Buffer.add_string buf "\\\"" + | '\n' -> Buffer.add_string buf "\\n" + | '\b' -> Buffer.add_string buf "\\b" + | '\r' -> Buffer.add_string buf "\\r" + | '\t' -> Buffer.add_string buf "\\t" + | c -> Buffer.add_char buf c); + loop (i + 1)) + in + loop 0; + Buffer.contents buf + +let loc_to_json loc = + Printf.sprintf + "{\"start\": {\"line\": %s, \"col\": %s},\"end\": {\"line\": %s, \"col\": \ + %s}}" + (loc.Location.loc_start.pos_lnum |> string_of_int) + (loc.loc_start.pos_cnum |> string_of_int) + (loc.loc_end.pos_lnum |> string_of_int) + (loc.loc_end.pos_cnum |> string_of_int) + +let code_action_type_to_json = function + | WrapWith {left; right} -> + Printf.sprintf "\"type\": \"wrapWith\", \"wrapLeft\": \"%s\", \"wrapRight\": \"%s\"" + (escape left) (escape right) + +let emit_code_actions_data ppf = + match !code_action_data with + | [] -> () + | code_actions -> + Format.fprintf ppf "@\n=== CODE ACTIONS ===@\n"; + code_actions + |> List.iter (fun data -> + Format.fprintf ppf + "{\"title\": \"%s\", \"kind\": \"%s\" \"loc\": %s, %s}" + (escape data.title) + (match data.style with + | Regular -> "regular" + | QuickFix -> "quickfix") + (loc_to_json data.loc) + (code_action_type_to_json data.type_)) diff --git a/jscomp/ml/code_action_data.mli b/jscomp/ml/code_action_data.mli new file mode 100644 index 0000000000..815114fc18 --- /dev/null +++ b/jscomp/ml/code_action_data.mli @@ -0,0 +1,13 @@ +type code_action_type = WrapWith of {left: string; right: string} +type code_action_style = Regular | QuickFix +type code_action = { + style: code_action_style; + type_: code_action_type; + loc: Location.t; + title: string; +} + +val add_code_action: code_action -> unit +val get_code_action_data: unit -> code_action list + +val emit_code_actions_data: Format.formatter -> unit diff --git a/jscomp/ml/typecore.ml b/jscomp/ml/typecore.ml index 564a828db4..383e3f1b35 100644 --- a/jscomp/ml/typecore.ml +++ b/jscomp/ml/typecore.ml @@ -633,9 +633,18 @@ let simple_conversions = [ (("string", "int"), "Belt.Int.fromString"); ] -let print_simple_conversion ppf (actual, expected) = +let print_simple_conversion loc ppf (actual, expected) = try ( let converter = List.assoc (actual, expected) simple_conversions in + Code_action_data.add_code_action { + Code_action_data.style = QuickFix; + type_ = WrapWith { + left = converter ^ "("; + right = ")" + }; + loc; + title = Printf.sprintf "Convert %s to %s" actual expected + }; fprintf ppf "@,@,@[You can convert @{%s@} to @{%s@} with @{%s@}.@]" actual expected converter ) with | Not_found -> () @@ -644,12 +653,12 @@ let print_simple_message ppf = function | ("int", "float") -> fprintf ppf "@ If this is a literal, try a number with a trailing dot (e.g. @{20.@})." | _ -> () -let show_extra_help ppf _env trace = begin +let show_extra_help (loc: Location.t) ppf _env trace = begin match bottom_aliases trace with | Some ({Types.desc = Tconstr (actual_path, actual_args, _)}, {desc = Tconstr (expected_path, expexted_args, _)}) -> begin match (actual_path, actual_args, expected_path, expexted_args) with | (Pident {name = actual_name}, [], Pident {name = expected_name}, []) -> begin - print_simple_conversion ppf (actual_name, expected_name); + print_simple_conversion loc ppf (actual_name, expected_name); print_simple_message ppf (actual_name, expected_name); end | _ -> () @@ -671,7 +680,7 @@ let rec collect_missing_arguments env type1 type2 = match type1 with collect_missing_arguments env typ type2 | _ -> None -let print_expr_type_clash ?type_clash_context env trace ppf = begin +let print_expr_type_clash ?type_clash_context loc env trace ppf = begin (* this is the most frequent error. We should do whatever we can to provide specific guidance to this generic error before giving up *) let bottom_aliases_result = bottom_aliases trace in @@ -723,7 +732,7 @@ let print_expr_type_clash ?type_clash_context env trace ppf = begin (function ppf -> error_expected_type_text ppf type_clash_context); print_extra_type_clash_help ppf trace type_clash_context; - show_extra_help ppf env trace; + show_extra_help loc ppf env trace; end let report_arity_mismatch ~arity_a ~arity_b ppf = @@ -3762,7 +3771,7 @@ let type_expr ppf typ = (* print a type and avoid infinite loops *) Printtyp.reset_and_mark_loops typ; Printtyp.type_expr ppf typ -let report_error env ppf = function +let report_error loc env ppf = function | Polymorphic_label lid -> fprintf ppf "@[The record field %a is polymorphic.@ %s@]" longident lid "You cannot instantiate it in a pattern." @@ -3826,7 +3835,7 @@ let report_error env ppf = function | Expr_type_clash (trace, type_clash_context) -> (* modified *) fprintf ppf "@["; - print_expr_type_clash ?type_clash_context env trace ppf; + print_expr_type_clash ?type_clash_context loc env trace ppf; fprintf ppf "@]" | Apply_non_function typ -> (* modified *) @@ -4045,14 +4054,14 @@ let report_error env ppf = function let super_report_error_no_wrap_printing_env = report_error -let report_error env ppf err = - Printtyp.wrap_printing_env env (fun () -> report_error env ppf err) +let report_error loc env ppf err = + Printtyp.wrap_printing_env env (fun () -> report_error loc env ppf err; Code_action_data.emit_code_actions_data ppf;) let () = Location.register_error_of_exn (function | Error (loc, env, err) -> - Some (Location.error_of_printer loc (report_error env) err) + Some (Location.error_of_printer loc (report_error loc env) err) | Error_forward err -> Some err | _ -> diff --git a/jscomp/ml/typecore.mli b/jscomp/ml/typecore.mli index 4b1fcc5368..cf2b01b1e6 100644 --- a/jscomp/ml/typecore.mli +++ b/jscomp/ml/typecore.mli @@ -111,10 +111,10 @@ exception Error of Location.t * Env.t * error exception Error_forward of Location.error -val super_report_error_no_wrap_printing_env: Env.t -> formatter -> error -> unit +val super_report_error_no_wrap_printing_env: Location.t -> Env.t -> formatter -> error -> unit -val report_error: Env.t -> formatter -> error -> unit +val report_error: Location.t -> Env.t -> formatter -> error -> unit (* Deprecated. Use Location.{error_of_exn, report_error}. *) (* Forward declaration, to be filled in by Typemod.type_module *) From 46d03306fbc40d71299d78559b9e3fecce08235b Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 16 Sep 2024 22:05:50 +0200 Subject: [PATCH 2/6] small cleanup --- jscomp/ml/code_action_data.ml | 5 ++--- jscomp/ml/code_action_data.mli | 3 +-- jscomp/ml/typecore.ml | 17 ++++++++--------- jscomp/ml/typecore.mli | 2 +- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/jscomp/ml/code_action_data.ml b/jscomp/ml/code_action_data.ml index 02af539fe3..e142934dd7 100644 --- a/jscomp/ml/code_action_data.ml +++ b/jscomp/ml/code_action_data.ml @@ -3,7 +3,6 @@ type code_action_style = Regular | QuickFix type code_action = { style: code_action_style; type_: code_action_type; - loc: Location.t; title: string; } @@ -45,7 +44,7 @@ let code_action_type_to_json = function Printf.sprintf "\"type\": \"wrapWith\", \"wrapLeft\": \"%s\", \"wrapRight\": \"%s\"" (escape left) (escape right) -let emit_code_actions_data ppf = +let emit_code_actions_data loc ppf = match !code_action_data with | [] -> () | code_actions -> @@ -58,5 +57,5 @@ let emit_code_actions_data ppf = (match data.style with | Regular -> "regular" | QuickFix -> "quickfix") - (loc_to_json data.loc) + (loc_to_json loc) (code_action_type_to_json data.type_)) diff --git a/jscomp/ml/code_action_data.mli b/jscomp/ml/code_action_data.mli index 815114fc18..cb59d0d9dd 100644 --- a/jscomp/ml/code_action_data.mli +++ b/jscomp/ml/code_action_data.mli @@ -3,11 +3,10 @@ type code_action_style = Regular | QuickFix type code_action = { style: code_action_style; type_: code_action_type; - loc: Location.t; title: string; } val add_code_action: code_action -> unit val get_code_action_data: unit -> code_action list -val emit_code_actions_data: Format.formatter -> unit +val emit_code_actions_data: Location.t -> Format.formatter -> unit diff --git a/jscomp/ml/typecore.ml b/jscomp/ml/typecore.ml index 383e3f1b35..ae7c86bd56 100644 --- a/jscomp/ml/typecore.ml +++ b/jscomp/ml/typecore.ml @@ -633,7 +633,7 @@ let simple_conversions = [ (("string", "int"), "Belt.Int.fromString"); ] -let print_simple_conversion loc ppf (actual, expected) = +let print_simple_conversion ppf (actual, expected) = try ( let converter = List.assoc (actual, expected) simple_conversions in Code_action_data.add_code_action { @@ -642,7 +642,6 @@ let print_simple_conversion loc ppf (actual, expected) = left = converter ^ "("; right = ")" }; - loc; title = Printf.sprintf "Convert %s to %s" actual expected }; fprintf ppf "@,@,@[You can convert @{%s@} to @{%s@} with @{%s@}.@]" actual expected converter @@ -653,12 +652,12 @@ let print_simple_message ppf = function | ("int", "float") -> fprintf ppf "@ If this is a literal, try a number with a trailing dot (e.g. @{20.@})." | _ -> () -let show_extra_help (loc: Location.t) ppf _env trace = begin +let show_extra_help ppf _env trace = begin match bottom_aliases trace with | Some ({Types.desc = Tconstr (actual_path, actual_args, _)}, {desc = Tconstr (expected_path, expexted_args, _)}) -> begin match (actual_path, actual_args, expected_path, expexted_args) with | (Pident {name = actual_name}, [], Pident {name = expected_name}, []) -> begin - print_simple_conversion loc ppf (actual_name, expected_name); + print_simple_conversion ppf (actual_name, expected_name); print_simple_message ppf (actual_name, expected_name); end | _ -> () @@ -680,7 +679,7 @@ let rec collect_missing_arguments env type1 type2 = match type1 with collect_missing_arguments env typ type2 | _ -> None -let print_expr_type_clash ?type_clash_context loc env trace ppf = begin +let print_expr_type_clash ?type_clash_context env trace ppf = begin (* this is the most frequent error. We should do whatever we can to provide specific guidance to this generic error before giving up *) let bottom_aliases_result = bottom_aliases trace in @@ -732,7 +731,7 @@ let print_expr_type_clash ?type_clash_context loc env trace ppf = begin (function ppf -> error_expected_type_text ppf type_clash_context); print_extra_type_clash_help ppf trace type_clash_context; - show_extra_help loc ppf env trace; + show_extra_help ppf env trace; end let report_arity_mismatch ~arity_a ~arity_b ppf = @@ -3771,7 +3770,7 @@ let type_expr ppf typ = (* print a type and avoid infinite loops *) Printtyp.reset_and_mark_loops typ; Printtyp.type_expr ppf typ -let report_error loc env ppf = function +let report_error env ppf = function | Polymorphic_label lid -> fprintf ppf "@[The record field %a is polymorphic.@ %s@]" longident lid "You cannot instantiate it in a pattern." @@ -3835,7 +3834,7 @@ let report_error loc env ppf = function | Expr_type_clash (trace, type_clash_context) -> (* modified *) fprintf ppf "@["; - print_expr_type_clash ?type_clash_context loc env trace ppf; + print_expr_type_clash ?type_clash_context env trace ppf; fprintf ppf "@]" | Apply_non_function typ -> (* modified *) @@ -4055,7 +4054,7 @@ let super_report_error_no_wrap_printing_env = report_error let report_error loc env ppf err = - Printtyp.wrap_printing_env env (fun () -> report_error loc env ppf err; Code_action_data.emit_code_actions_data ppf;) + Printtyp.wrap_printing_env env (fun () -> report_error env ppf err; Code_action_data.emit_code_actions_data loc ppf;) let () = Location.register_error_of_exn diff --git a/jscomp/ml/typecore.mli b/jscomp/ml/typecore.mli index cf2b01b1e6..3ba25b1b3e 100644 --- a/jscomp/ml/typecore.mli +++ b/jscomp/ml/typecore.mli @@ -111,7 +111,7 @@ exception Error of Location.t * Env.t * error exception Error_forward of Location.error -val super_report_error_no_wrap_printing_env: Location.t -> Env.t -> formatter -> error -> unit +val super_report_error_no_wrap_printing_env: Env.t -> formatter -> error -> unit val report_error: Location.t -> Env.t -> formatter -> error -> unit From c89af81ac1271ed123e8d78d319dab7f8d87c68c Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 16 Sep 2024 22:11:12 +0200 Subject: [PATCH 3/6] its an array --- jscomp/ml/code_action_data.ml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/jscomp/ml/code_action_data.ml b/jscomp/ml/code_action_data.ml index e142934dd7..7db8a295c5 100644 --- a/jscomp/ml/code_action_data.ml +++ b/jscomp/ml/code_action_data.ml @@ -48,14 +48,17 @@ let emit_code_actions_data loc ppf = match !code_action_data with | [] -> () | code_actions -> - Format.fprintf ppf "@\n=== CODE ACTIONS ===@\n"; - code_actions - |> List.iter (fun data -> - Format.fprintf ppf - "{\"title\": \"%s\", \"kind\": \"%s\" \"loc\": %s, %s}" - (escape data.title) - (match data.style with - | Regular -> "regular" - | QuickFix -> "quickfix") - (loc_to_json loc) - (code_action_type_to_json data.type_)) + Format.fprintf ppf "@\n=== CODE ACTIONS ===@\n["; + Format.fprintf ppf "%s" + (code_actions + |> List.map (fun data -> + Format.sprintf + "{\"title\": \"%s\", \"kind\": \"%s\" \"loc\": %s, %s}" + (escape data.title) + (match data.style with + | Regular -> "regular" + | QuickFix -> "quickfix") + (loc_to_json loc) + (code_action_type_to_json data.type_)) + |> String.concat ","); + Format.fprintf ppf "]" From 8415e754001b7bc40c5e8ce6103d9e153b8568b6 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 17 Sep 2024 19:15:07 +0200 Subject: [PATCH 4/6] automatic code action for spellcheck 'did you mean' text --- jscomp/ml/code_action_data.ml | 19 ++++++++++++++++++- jscomp/ml/code_action_data.mli | 6 +++++- jscomp/ml/typecore.ml | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/jscomp/ml/code_action_data.ml b/jscomp/ml/code_action_data.ml index 7db8a295c5..1f37ef3a09 100644 --- a/jscomp/ml/code_action_data.ml +++ b/jscomp/ml/code_action_data.ml @@ -1,4 +1,4 @@ -type code_action_type = WrapWith of {left: string; right: string} +type code_action_type = WrapWith of {left: string; right: string} | ReplaceWith of string type code_action_style = Regular | QuickFix type code_action = { style: code_action_style; @@ -6,6 +6,8 @@ type code_action = { title: string; } +let code_actions_enabled = ref true + let code_action_data = ref [] let add_code_action (data : code_action) = code_action_data := data :: !code_action_data @@ -43,6 +45,9 @@ let code_action_type_to_json = function | WrapWith {left; right} -> Printf.sprintf "\"type\": \"wrapWith\", \"wrapLeft\": \"%s\", \"wrapRight\": \"%s\"" (escape left) (escape right) + | ReplaceWith text -> + Printf.sprintf "\"type\": \"replaceWith\", \"replaceWith\": \"%s\"" + (escape text) let emit_code_actions_data loc ppf = match !code_action_data with @@ -62,3 +67,15 @@ let emit_code_actions_data loc ppf = (code_action_type_to_json data.type_)) |> String.concat ","); Format.fprintf ppf "]" + + +module Actions = struct + let add_replace_with name = + if !code_actions_enabled then + add_code_action + { + style = QuickFix; + type_ = ReplaceWith name; + title = "Replace with `" ^ name ^ "`"; + } +end \ No newline at end of file diff --git a/jscomp/ml/code_action_data.mli b/jscomp/ml/code_action_data.mli index cb59d0d9dd..561c4424e7 100644 --- a/jscomp/ml/code_action_data.mli +++ b/jscomp/ml/code_action_data.mli @@ -1,4 +1,4 @@ -type code_action_type = WrapWith of {left: string; right: string} +type code_action_type = WrapWith of {left: string; right: string} | ReplaceWith of string type code_action_style = Regular | QuickFix type code_action = { style: code_action_style; @@ -10,3 +10,7 @@ val add_code_action: code_action -> unit val get_code_action_data: unit -> code_action list val emit_code_actions_data: Location.t -> Format.formatter -> unit + +module Actions : sig + val add_replace_with: string -> unit +end \ No newline at end of file diff --git a/jscomp/ml/typecore.ml b/jscomp/ml/typecore.ml index ae7c86bd56..43a5871f60 100644 --- a/jscomp/ml/typecore.ml +++ b/jscomp/ml/typecore.ml @@ -3754,6 +3754,7 @@ let type_expression env sexp = (* Error report *) let spellcheck ppf unbound_name valid_names = + Code_action_data.Actions.add_replace_with unbound_name; Misc.did_you_mean ppf (fun () -> Misc.spellcheck valid_names unbound_name ) From edacb5b62ecc5803aa1f9e7997486762cac8a33e Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 17 Sep 2024 19:23:27 +0200 Subject: [PATCH 5/6] add 'wrap in Some()' code action --- jscomp/ml/code_action_data.ml | 8 ++++++++ jscomp/ml/code_action_data.mli | 1 + jscomp/ml/error_message_utils.ml | 1 + 3 files changed, 10 insertions(+) diff --git a/jscomp/ml/code_action_data.ml b/jscomp/ml/code_action_data.ml index 1f37ef3a09..aa8cc91dc7 100644 --- a/jscomp/ml/code_action_data.ml +++ b/jscomp/ml/code_action_data.ml @@ -78,4 +78,12 @@ module Actions = struct type_ = ReplaceWith name; title = "Replace with `" ^ name ^ "`"; } + let add_wrap_in_constructor name = + if !code_actions_enabled then + add_code_action + { + style = QuickFix; + type_ = WrapWith {left = name ^ "("; right = ")"}; + title = "Wrap in `" ^ name ^ "()`"; + } end \ No newline at end of file diff --git a/jscomp/ml/code_action_data.mli b/jscomp/ml/code_action_data.mli index 561c4424e7..eca254d803 100644 --- a/jscomp/ml/code_action_data.mli +++ b/jscomp/ml/code_action_data.mli @@ -13,4 +13,5 @@ val emit_code_actions_data: Location.t -> Format.formatter -> unit module Actions : sig val add_replace_with: string -> unit + val add_wrap_in_constructor: string -> unit end \ No newline at end of file diff --git a/jscomp/ml/error_message_utils.ml b/jscomp/ml/error_message_utils.ml index d178f2129f..e332820874 100644 --- a/jscomp/ml/error_message_utils.ml +++ b/jscomp/ml/error_message_utils.ml @@ -234,6 +234,7 @@ let print_contextual_unification_error ppf t1 t2 = | Tconstr (p1, _, _), Tconstr (p2, _, _) when Path.same p2 Predef.path_option && Path.same p1 Predef.path_option <> true -> + Code_action_data.Actions.add_wrap_in_constructor "Some"; fprintf ppf "@,@\n\ @[The value you're pattern matching on here is wrapped in an \ From 03dcd2f2bca28fc7e8e355dc02849985235b6dce Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 17 Sep 2024 21:23:59 +0200 Subject: [PATCH 6/6] add tests for editor enhancements mode --- .../editor_tooling_enhancements/.gitignore | 1 + .../editor_tooling_enhancements/README.md | 5 ++ .../expected/replace_name.res.expected | 16 +++++ .../expected/wrap_option_in_some.res.expected | 16 +++++ .../fixtures/replace_name.res | 9 +++ .../fixtures/wrap_option_in_some.res | 4 ++ .../editor_tooling_enhancements/input.js | 64 +++++++++++++++++++ 7 files changed, 115 insertions(+) create mode 100644 jscomp/build_tests/editor_tooling_enhancements/.gitignore create mode 100644 jscomp/build_tests/editor_tooling_enhancements/README.md create mode 100644 jscomp/build_tests/editor_tooling_enhancements/expected/replace_name.res.expected create mode 100644 jscomp/build_tests/editor_tooling_enhancements/expected/wrap_option_in_some.res.expected create mode 100644 jscomp/build_tests/editor_tooling_enhancements/fixtures/replace_name.res create mode 100644 jscomp/build_tests/editor_tooling_enhancements/fixtures/wrap_option_in_some.res create mode 100644 jscomp/build_tests/editor_tooling_enhancements/input.js diff --git a/jscomp/build_tests/editor_tooling_enhancements/.gitignore b/jscomp/build_tests/editor_tooling_enhancements/.gitignore new file mode 100644 index 0000000000..e0473fd9cf --- /dev/null +++ b/jscomp/build_tests/editor_tooling_enhancements/.gitignore @@ -0,0 +1 @@ +fixtures/*.js diff --git a/jscomp/build_tests/editor_tooling_enhancements/README.md b/jscomp/build_tests/editor_tooling_enhancements/README.md new file mode 100644 index 0000000000..12ae608779 --- /dev/null +++ b/jscomp/build_tests/editor_tooling_enhancements/README.md @@ -0,0 +1,5 @@ +Special tests for the editor tooling enhancements mode. + +Follow CONTRIBUTING.md and build the project, then run `node ./jscomp/build_tests/editor_tooling_enhancements/input.js` at the root of the project to check the tests against previous snapshots. + +Run `node ./jscomp/build_tests/editor_tooling_enhancements/input.js update` to update the snapshots. diff --git a/jscomp/build_tests/editor_tooling_enhancements/expected/replace_name.res.expected b/jscomp/build_tests/editor_tooling_enhancements/expected/replace_name.res.expected new file mode 100644 index 0000000000..780d265269 --- /dev/null +++ b/jscomp/build_tests/editor_tooling_enhancements/expected/replace_name.res.expected @@ -0,0 +1,16 @@ + + We've found a bug for you! + /.../fixtures/replace_name.res:8:3-6 + + 6 │ let x: x = { + 7 │ name: "hello", + 8 │ agee: 10, + 9 │ } + 10 │ + + The field agee does not belong to type x + + This record expression is expected to have type x +Hint: Did you mean age? +=== CODE ACTIONS === +[{"title": "Replace with `agee`", "kind": "quickfix" "loc": {"start": {"line": 8, "col": 74},"end": {"line": 8, "col": 78}}, "type": "replaceWith", "replaceWith": "agee"}] \ No newline at end of file diff --git a/jscomp/build_tests/editor_tooling_enhancements/expected/wrap_option_in_some.res.expected b/jscomp/build_tests/editor_tooling_enhancements/expected/wrap_option_in_some.res.expected new file mode 100644 index 0000000000..64a3618793 --- /dev/null +++ b/jscomp/build_tests/editor_tooling_enhancements/expected/wrap_option_in_some.res.expected @@ -0,0 +1,16 @@ + + We've found a bug for you! + /.../fixtures/wrap_option_in_some.res:2:3 + + 1 │ switch Some(1) { + 2 │ | 1 => () + 3 │ | _ => () + 4 │ } + + This pattern matches values of type int + but a pattern was expected which matches values of type option + + The value you're pattern matching on here is wrapped in an option, but you're trying to match on the actual value. + Wrap the highlighted pattern in Some() to make it work. +=== CODE ACTIONS === +[{"title": "Wrap in `Some()`", "kind": "quickfix" "loc": {"start": {"line": 2, "col": 19},"end": {"line": 2, "col": 20}}, "type": "wrapWith", "wrapLeft": "Some(", "wrapRight": ")"}] \ No newline at end of file diff --git a/jscomp/build_tests/editor_tooling_enhancements/fixtures/replace_name.res b/jscomp/build_tests/editor_tooling_enhancements/fixtures/replace_name.res new file mode 100644 index 0000000000..b154fbaf76 --- /dev/null +++ b/jscomp/build_tests/editor_tooling_enhancements/fixtures/replace_name.res @@ -0,0 +1,9 @@ +type x = { + name: string, + age: int, +} + +let x: x = { + name: "hello", + agee: 10, +} diff --git a/jscomp/build_tests/editor_tooling_enhancements/fixtures/wrap_option_in_some.res b/jscomp/build_tests/editor_tooling_enhancements/fixtures/wrap_option_in_some.res new file mode 100644 index 0000000000..1674431e5c --- /dev/null +++ b/jscomp/build_tests/editor_tooling_enhancements/fixtures/wrap_option_in_some.res @@ -0,0 +1,4 @@ +switch Some(1) { +| 1 => () +| _ => () +} diff --git a/jscomp/build_tests/editor_tooling_enhancements/input.js b/jscomp/build_tests/editor_tooling_enhancements/input.js new file mode 100644 index 0000000000..64d8890b56 --- /dev/null +++ b/jscomp/build_tests/editor_tooling_enhancements/input.js @@ -0,0 +1,64 @@ +const fs = require("fs"); +const path = require("path"); +const child_process = require("child_process"); + +const { bsc_exe: bsc } = require("#cli/bin_path"); + +const expectedDir = path.join(__dirname, "expected"); + +const fixtures = fs + .readdirSync(path.join(__dirname, "fixtures")) + .filter((fileName) => path.extname(fileName) === ".res"); + +// const runtime = path.join(__dirname, '..', '..', 'runtime') +const prefix = `${bsc} -w +A -code-action-data`; + +const updateTests = process.argv[2] === "update"; + +function postProcessErrorOutput(output) { + output = output.trimRight(); + output = output.replace( + /\/[^ ]+?jscomp\/build_tests\/editor_tooling_enhancements\//g, + "/.../" + ); + return output; +} + +let doneTasksCount = 0; +let atLeastOneTaskFailed = false; + +fixtures.forEach((fileName) => { + const fullFilePath = path.join(__dirname, "fixtures", fileName); + const command = `${prefix} -color always ${fullFilePath}`; + console.log(`running ${command}`); + child_process.exec(command, (err, stdout, stderr) => { + doneTasksCount++; + // careful of: + // - warning test that actually succeeded in compiling (warning's still in stderr, so the code path is shared here) + // - accidentally succeeding tests (not likely in this context), + // actual, correctly erroring test case + const actualErrorOutput = postProcessErrorOutput(stderr.toString()); + const expectedFilePath = path.join(expectedDir, fileName + ".expected"); + if (updateTests) { + fs.writeFileSync(expectedFilePath, actualErrorOutput); + } else { + const expectedErrorOutput = postProcessErrorOutput( + fs.readFileSync(expectedFilePath, { encoding: "utf-8" }) + ); + if (expectedErrorOutput !== actualErrorOutput) { + console.error( + `The old and new error output for the test ${fullFilePath} aren't the same` + ); + console.error("\n=== Old:"); + console.error(expectedErrorOutput); + console.error("\n=== New:"); + console.error(actualErrorOutput); + atLeastOneTaskFailed = true; + } + + if (doneTasksCount === fixtures.length && atLeastOneTaskFailed) { + process.exit(1); + } + } + }); +});