Skip to content

Commit 0e6c70e

Browse files
SamChou19815facebook-github-bot
authored andcommitted
[flow] Introduce flow apply-code-action 'experimental.quickfix' command
Reviewed By: panagosg7 Differential Revision: D82492454 fbshipit-source-id: b7164bc0a254a7a4b4f0679d68e7277d7c248786
1 parent 337157a commit 0e6c70e

File tree

9 files changed

+263
-7
lines changed

9 files changed

+263
-7
lines changed

src/commands/applyCodeActionCommand.ml

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,77 @@ open CommandSpec
1111
(* This module implements the flow command `apply-code-action` which exposes LSP code-actions via the CLI *)
1212
let handle_error ?(code = Exit.Unknown_error) msg = Exit.(exit ~msg code)
1313

14+
module Quickfix = struct
15+
let spec =
16+
{
17+
name = "Apply available quickfixes";
18+
doc = "Runs all safe quickfixes. If requested, run one additional best effort quickfix";
19+
usage =
20+
Printf.sprintf
21+
"Usage: %s apply-code-action 'experimental.quickfix' [OPTION]... FILE"
22+
exe_name;
23+
args =
24+
ArgSpec.(
25+
empty
26+
|> base_flags
27+
|> connect_flags
28+
|> root_flag
29+
|> path_flag
30+
|> wait_for_recheck_flag
31+
|> flag "--in-place" truthy ~doc:"Overwrite the input file"
32+
|> flag
33+
"--include-best-effort-fix"
34+
truthy
35+
~doc:"Whether to include one best effort quickfix"
36+
|> anon "file" (required string)
37+
);
38+
}
39+
40+
let handle_ok in_place patch source_path input =
41+
let write_patch content =
42+
let output_channel =
43+
if in_place then
44+
open_out source_path
45+
else
46+
stdout
47+
in
48+
output_string output_channel @@ Replacement_printer.print patch content;
49+
close_out output_channel
50+
in
51+
match File_input.content_of_file_input input with
52+
| Ok content -> write_patch content
53+
| Error msg -> handle_error msg
54+
55+
let main
56+
base_flags
57+
connect_params
58+
root_arg
59+
path
60+
wait_for_recheck
61+
in_place
62+
include_best_effort_fix
63+
file
64+
() =
65+
let source_path = expand_path file in
66+
let input = get_file_from_filename_or_stdin ~cmd:spec.name path (Some source_path) in
67+
let root = get_the_root ~base_flags ~input root_arg in
68+
let flowconfig_name = base_flags.Base_flags.flowconfig_name in
69+
let request =
70+
ServerProt.Request.APPLY_CODE_ACTION
71+
{
72+
input;
73+
action = ServerProt.Code_action.Quickfix { include_best_effort_fix };
74+
wait_for_recheck;
75+
}
76+
in
77+
let result = connect_and_make_request flowconfig_name connect_params root request in
78+
match result with
79+
| ServerProt.Response.APPLY_CODE_ACTION (Ok patch) -> handle_ok in_place patch source_path input
80+
| _ -> handle_error "Flow: invalid server response"
81+
82+
let command = CommandSpec.command spec main
83+
end
84+
1485
module SourceAddMissingImports = struct
1586
let spec =
1687
{
@@ -24,7 +95,7 @@ module SourceAddMissingImports = struct
2495
ArgSpec.(
2596
empty
2697
|> base_flags
27-
|> connect_and_json_flags
98+
|> connect_flags
2899
|> root_flag
29100
|> path_flag
30101
|> wait_for_recheck_flag
@@ -48,7 +119,7 @@ module SourceAddMissingImports = struct
48119
| Ok content -> write_patch content
49120
| Error msg -> handle_error msg
50121

51-
let main base_flags connect_params _json _pretty root_arg path wait_for_recheck in_place file () =
122+
let main base_flags connect_params root_arg path wait_for_recheck in_place file () =
52123
let source_path = expand_path file in
53124
let input = get_file_from_filename_or_stdin ~cmd:spec.name path (Some source_path) in
54125
let root = get_the_root ~base_flags ~input root_arg in
@@ -118,6 +189,7 @@ let command =
118189
~name:"apply-code-action"
119190
~doc:""
120191
[
192+
("experimental.quickfix", Quickfix.command);
121193
("source.addMissingImports", SourceAddMissingImports.command);
122194
("suggestImports", SuggestImports.command);
123195
]

src/server/command_handler/commandHandler.ml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,30 @@ let autocomplete_on_parsed
558558
Autocomplete_js.autocomplete_unset_hooks ();
559559
(initial_json_props, ac_result)
560560

561+
let autofix_errors_cli
562+
~options
563+
~env
564+
~profiling
565+
~loc_of_aloc
566+
~get_ast_from_shared_mem
567+
~module_system_info
568+
~get_type_sig
569+
~include_best_effort_fix
570+
~input =
571+
let file_key = file_key_of_file_input ~options ~env input in
572+
File_input.content_of_file_input input >>= fun file_content ->
573+
Code_action_service.autofix_errors_cli
574+
~options
575+
~profiling
576+
~env
577+
~loc_of_aloc
578+
~get_ast_from_shared_mem
579+
~module_system_info
580+
~get_type_sig
581+
~include_best_effort_fix
582+
~file_key
583+
~file_content
584+
561585
let autofix_imports_cli ~options ~env ~profiling ~loc_of_aloc ~module_system_info ~input =
562586
let file_key = file_key_of_file_input ~options ~env input in
563587
File_input.content_of_file_input input >>= fun file_content ->
@@ -1603,6 +1627,22 @@ let rank_autoimports_by_usage ~options client =
16031627
let handle_apply_code_action ~options ~reader ~profiling ~env action file_input =
16041628
ServerProt.Code_action.(
16051629
match action with
1630+
| Quickfix { include_best_effort_fix } ->
1631+
let result =
1632+
try_with (fun () ->
1633+
autofix_errors_cli
1634+
~options
1635+
~profiling
1636+
~env
1637+
~loc_of_aloc:(Parsing_heaps.Reader.loc_of_aloc ~reader)
1638+
~get_ast_from_shared_mem:(Parsing_heaps.Reader.get_ast ~reader)
1639+
~module_system_info:(mk_module_system_info ~options ~reader)
1640+
~get_type_sig:(Parsing_heaps.Reader.get_type_sig ~reader)
1641+
~include_best_effort_fix
1642+
~input:file_input
1643+
)
1644+
in
1645+
Lwt.return (ServerProt.Response.APPLY_CODE_ACTION result, None)
16061646
| SourceAddMissingImports ->
16071647
let result =
16081648
try_with (fun () ->

src/server/protocol/serverProt.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,13 @@ end
4949

5050
module Code_action = struct
5151
type t =
52+
| Quickfix of { include_best_effort_fix: bool }
5253
| SourceAddMissingImports
5354
| SuggestImports
5455

5556
let to_string = function
57+
| Quickfix { include_best_effort_fix = false } -> "quickfix.safe"
58+
| Quickfix { include_best_effort_fix = true } -> "quickfix.include_best_effort_fix"
5659
| SourceAddMissingImports -> "source.addMissingImports"
5760
| SuggestImports -> "suggestImports"
5861
end

src/services/code_action/autofix_type_name.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class mapper target_loc ~incorrect_name ~replacement_name =
1212
method! generic_type loc t =
1313
let open Flow_ast.Type in
1414
let { Generic.id; targs; comments } = t in
15-
if Option.is_none targs || (not @@ this#is_target loc) then
15+
if not @@ this#is_target loc then
1616
super#generic_type loc t
1717
else
1818
match id with

src/services/code_action/code_action_service.ml

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,13 +1950,89 @@ let with_type_checked_file ~options ~profiling ~env ~file_key ~file_content ~f =
19501950
intermediate_result
19511951
in
19521952
match file_artifacts with
1953-
| Ok (Parse_artifacts { ast; _ }, Typecheck_artifacts { cx; _ }) -> f ~cx ~ast
1953+
| Ok (Parse_artifacts { ast; file_sig; _ }, Typecheck_artifacts { cx; typed_ast; _ }) ->
1954+
f ~cx ~file_sig ~ast ~typed_ast
19541955
| _ -> Error "Failed to parse or check file"
19551956

1957+
let autofix_errors_cli
1958+
~options
1959+
~profiling
1960+
~env
1961+
~loc_of_aloc
1962+
~get_ast_from_shared_mem
1963+
~module_system_info
1964+
~get_type_sig
1965+
~include_best_effort_fix
1966+
~file_key
1967+
~file_content =
1968+
let get_edits ~cx ~file_sig ~ast ~typed_ast =
1969+
let (safe_transforms, best_effort_transforms) =
1970+
Flow_error.ErrorSet.fold
1971+
(fun error acc ->
1972+
let lazy_error_loc =
1973+
lazy
1974+
(let { Flow_intermediate_error_types.loc; _ } =
1975+
Flow_intermediate_error.make_intermediate_error ~loc_of_aloc error
1976+
in
1977+
loc
1978+
)
1979+
in
1980+
match
1981+
ast_transforms_of_error
1982+
~loc_of_aloc
1983+
~lazy_error_loc
1984+
~get_ast_from_shared_mem
1985+
~get_haste_module_info:module_system_info.Lsp_module_system_info.get_haste_module_info
1986+
~get_type_sig
1987+
(error |> Flow_error.map_loc_of_error loc_of_aloc |> Flow_error.msg_of_error)
1988+
|> Base.List.hd
1989+
with
1990+
| None -> acc
1991+
| Some transform ->
1992+
let (safe_transforms, best_effort_transforms) = acc in
1993+
(match transform.confidence with
1994+
| WillFixErrorAndSafeForRunningOnSave ->
1995+
(transform :: safe_transforms, best_effort_transforms)
1996+
| BestEffort ->
1997+
if include_best_effort_fix then
1998+
(safe_transforms, transform :: best_effort_transforms)
1999+
else
2000+
acc))
2001+
(Context.errors cx)
2002+
([], [])
2003+
in
2004+
let (new_ast, _) =
2005+
Base.List.fold
2006+
(safe_transforms @ best_effort_transforms)
2007+
~init:(ast, false)
2008+
~f:(fun (ast, has_run_best_effort_fix) transform ->
2009+
match (transform.confidence, has_run_best_effort_fix) with
2010+
| (BestEffort, true) -> (ast, true)
2011+
| (WillFixErrorAndSafeForRunningOnSave, _) ->
2012+
let new_ast =
2013+
Base.Option.value
2014+
~default:ast
2015+
(transform.transform ~cx ~file_sig ~ast ~typed_ast transform.target_loc)
2016+
in
2017+
(new_ast, has_run_best_effort_fix)
2018+
| (BestEffort, false) ->
2019+
let new_ast =
2020+
Base.Option.value
2021+
~default:ast
2022+
(transform.transform ~cx ~file_sig ~ast ~typed_ast transform.target_loc)
2023+
in
2024+
(new_ast, new_ast != ast)
2025+
)
2026+
in
2027+
let opts = layout_options options in
2028+
Ok (Insert_type.mk_patch ~opts ast new_ast file_content)
2029+
in
2030+
with_type_checked_file ~options ~profiling ~env ~file_key ~file_content ~f:get_edits
2031+
19562032
let suggest_imports_cli
19572033
~options ~profiling ~env ~loc_of_aloc ~module_system_info ~file_key ~file_content =
19582034
let uri = File_key.to_string file_key |> Lsp_helpers.path_to_lsp_uri ~default_path:"" in
1959-
let get_edits ~cx ~ast =
2035+
let get_edits ~cx ~file_sig:_ ~ast ~typed_ast:_ =
19602036
let errors = Context.errors cx in
19612037
let (imports, _) =
19622038
Flow_error.ErrorSet.fold
@@ -1995,7 +2071,7 @@ let suggest_imports_cli
19952071
let autofix_imports_cli
19962072
~options ~profiling ~env ~loc_of_aloc ~module_system_info ~file_key ~file_content =
19972073
let src_dir = File_key.to_string file_key |> Filename.dirname |> Base.Option.return in
1998-
let get_edits ~cx ~ast =
2074+
let get_edits ~cx ~file_sig:_ ~ast ~typed_ast:_ =
19992075
let edits = autofix_imports ~options ~env ~loc_of_aloc ~module_system_info ~cx ~ast ~src_dir in
20002076
Ok (Replacement_printer.loc_patch_to_patch file_content edits)
20012077
in

src/services/code_action/code_action_service.mli

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ val code_actions_at_loc :
6767
loc:Loc.t ->
6868
(Lsp.CodeAction.command_or_action list, string) result
6969

70+
val autofix_errors_cli :
71+
options:Options.t ->
72+
profiling:Profiling_js.running ->
73+
env:ServerEnv.env ->
74+
loc_of_aloc:(ALoc.t -> Loc.t) ->
75+
get_ast_from_shared_mem:(File_key.t -> (Loc.t, Loc.t) Flow_ast.Program.t option) ->
76+
module_system_info:Lsp_module_system_info.t ->
77+
get_type_sig:(File_key.t -> Type_sig_collections.Locs.index Packed_type_sig.Module.t option) ->
78+
include_best_effort_fix:bool ->
79+
file_key:File_key.t ->
80+
file_content:string ->
81+
(Replacement_printer.patch, string) result
82+
7083
val autofix_imports_cli :
7184
options:Options.t ->
7285
profiling:Profiling_js.running ->

tests/apply_code_action_command/apply_code_action_command.exp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,36 @@ Found 0 errors
8181
"title":"Import from ./ExportFoo2"
8282
}
8383
]
84-
}
84+
}
85+
> apply-code-action 'experimental.quickfix' tmp/test-quickfixes.js
86+
// $FlowFixMe[internal-type]
87+
type T1 = React.Node;
88+
// $FlowFixMe[internal-type]
89+
type T2 = React.Node;
90+
91+
type Expected = {
92+
foo?: number,
93+
bar: 'foo' | 'bar',
94+
}
95+
96+
declare export function assertExpected(expected: Expected): void;
97+
98+
const obj = {bar: 'foo' as const};
99+
// $FlowFixMe[incompatible-type]
100+
assertExpected(obj);
101+
> apply-code-action 'experimental.quickfix' tmp/test-quickfixes.js --include-best-effort-fix
102+
// $FlowFixMe[internal-type]
103+
type T1 = React.Node;
104+
// $FlowFixMe[internal-type]
105+
type T2 = React.Node;
106+
107+
type Expected = {
108+
foo?: number,
109+
bar: 'foo' | 'bar',
110+
}
111+
112+
declare export function assertExpected(expected: Expected): void;
113+
114+
const obj: Expected = {bar: 'foo' as const};
115+
// $FlowFixMe[incompatible-type]
116+
assertExpected(obj);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// $FlowFixMe[internal-type]
2+
type T1 = React$Node;
3+
// $FlowFixMe[internal-type]
4+
type T2 = React$Node;
5+
6+
type Expected = {
7+
foo?: number,
8+
bar: 'foo' | 'bar',
9+
}
10+
11+
declare export function assertExpected(expected: Expected): void;
12+
13+
const obj = {bar: 'foo' as const};
14+
// $FlowFixMe[incompatible-type]
15+
assertExpected(obj);

tests/apply_code_action_command/test.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,8 @@ echo '> Confirm no errors'
2222
assert_ok "$FLOW" focus-check tmp/multi.js
2323
echo '> apply-code-action suggestImports tmp/suggest_imports.js'
2424
"$FLOW" apply-code-action suggestImports --pretty tmp/suggest_imports.js | sed -e 's/file:.*:/file:tmp\/suggest_imports.js": /'
25+
26+
echo '> apply-code-action '\''experimental.quickfix'\'' tmp/test-quickfixes.js'
27+
assert_ok "$FLOW" apply-code-action 'experimental.quickfix' tmp/test-quickfixes.js
28+
echo '> apply-code-action '\''experimental.quickfix'\'' tmp/test-quickfixes.js --include-best-effort-fix'
29+
assert_ok "$FLOW" apply-code-action 'experimental.quickfix' tmp/test-quickfixes.js --include-best-effort-fix

0 commit comments

Comments
 (0)