Skip to content

Commit 73f1673

Browse files
SamChou19815meta-codesync[bot]
authored andcommitted
[flow][ide] Introduce code action to fix all invalid type cast issue
Reviewed By: gkz Differential Revision: D86531982 fbshipit-source-id: 11f03d5fe2c9a4fceb43653ee5986c5733717d59
1 parent d11007d commit 73f1673

File tree

7 files changed

+255
-16
lines changed

7 files changed

+255
-16
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
{
2+
"method": "textDocument/codeAction",
3+
"result": [
4+
{
5+
"title": "Convert all colon casts to `as` expressions",
6+
"kind": "quickfix",
7+
"diagnostics": [],
8+
"edit": {
9+
"changes": {
10+
"<PLACEHOLDER_PROJECT_URL>/fix-all-colon-cast.js": [
11+
{
12+
"range": {
13+
"start": {
14+
"line": 2,
15+
"character": 18
16+
},
17+
"end": {
18+
"line": 2,
19+
"character": 29
20+
}
21+
},
22+
"newText": "1 as number"
23+
},
24+
{
25+
"range": {
26+
"start": {
27+
"line": 4,
28+
"character": 18
29+
},
30+
"end": {
31+
"line": 4,
32+
"character": 35
33+
}
34+
},
35+
"newText": "\"hello\" as string"
36+
},
37+
{
38+
"range": {
39+
"start": {
40+
"line": 5,
41+
"character": 19
42+
},
43+
"end": {
44+
"line": 5,
45+
"character": 34
46+
}
47+
},
48+
"newText": "true as boolean"
49+
}
50+
]
51+
}
52+
},
53+
"command": {
54+
"title": "",
55+
"command": "log:org.flow:<PLACEHOLDER_PROJECT_URL>",
56+
"arguments": [
57+
"textDocument/codeAction",
58+
"convert_all_colon_cast",
59+
"Convert all colon casts to `as` expressions"
60+
]
61+
}
62+
},
63+
{
64+
"title": "Convert to `as` expression `<expr> as <type>`",
65+
"kind": "quickfix",
66+
"diagnostics": [],
67+
"edit": {
68+
"changes": {
69+
"<PLACEHOLDER_PROJECT_URL>/fix-all-colon-cast.js": [
70+
{
71+
"range": {
72+
"start": {
73+
"line": 2,
74+
"character": 18
75+
},
76+
"end": {
77+
"line": 2,
78+
"character": 29
79+
}
80+
},
81+
"newText": "1 as number"
82+
}
83+
]
84+
}
85+
},
86+
"command": {
87+
"title": "",
88+
"command": "log:org.flow:<PLACEHOLDER_PROJECT_URL>",
89+
"arguments": [
90+
"textDocument/codeAction",
91+
"convert_colon_cast",
92+
"Convert to `as` expression `<expr> as <type>`"
93+
]
94+
}
95+
}
96+
]
97+
}

newtests/lsp/code-action/quickfix/ts_and_legacy_syntax/__snapshots__/quickfix-old-flow-type-cast.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,39 @@
11
{
22
"method": "textDocument/codeAction",
33
"result": [
4+
{
5+
"title": "Convert all colon casts to `as` expressions",
6+
"kind": "quickfix",
7+
"diagnostics": [],
8+
"edit": {
9+
"changes": {
10+
"<PLACEHOLDER_PROJECT_URL>/fix-colon-cast.js": [
11+
{
12+
"range": {
13+
"start": {
14+
"line": 2,
15+
"character": 0
16+
},
17+
"end": {
18+
"line": 2,
19+
"character": 14
20+
}
21+
},
22+
"newText": "\"foo\" as mixed"
23+
}
24+
]
25+
}
26+
},
27+
"command": {
28+
"title": "",
29+
"command": "log:org.flow:<PLACEHOLDER_PROJECT_URL>",
30+
"arguments": [
31+
"textDocument/codeAction",
32+
"convert_all_colon_cast",
33+
"Convert all colon casts to `as` expressions"
34+
]
35+
}
36+
},
437
{
538
"title": "Convert to `as` expression `<expr> as <type>`",
639
"kind": "quickfix",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @flow
2+
3+
const x: number = (1: number);
4+
// ^
5+
const y: string = ("hello": string);
6+
const z: boolean = (true: boolean);

newtests/lsp/code-action/quickfix/ts_and_legacy_syntax/test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import type {SuiteType} from '../../../../Tester';
77
const path = require('path');
88
const {suite, test} = require('../../../../Tester');
9+
const {generateSimpleTests} = require('../../test-utils');
910

1011
module.exports = (suite(
1112
({
@@ -624,5 +625,20 @@ module.exports = (suite(
624625
['textDocument/publishDiagnostics', ...lspIgnoreStatusAndCancellation],
625626
),
626627
]),
628+
test(
629+
'provide fix-all-in-file quickfix for colon casts',
630+
generateSimpleTests(
631+
'quickfix',
632+
{
633+
addFile,
634+
lspIgnoreStatusAndCancellation,
635+
lspStartAndConnect,
636+
lspRequestAndWaitUntilResponse,
637+
},
638+
__dirname,
639+
'fix-all-colon-cast.js',
640+
'quickfix-fix-all-colon-cast',
641+
),
642+
).flowConfig('_flowconfig_casting_syntax'),
627643
],
628644
): SuiteType);

src/services/code_action/autofix_casting_syntax.ml

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,14 @@
55
* LICENSE file in the root directory of this source tree.
66
*)
77

8-
class mapper ~enabled_casting_syntax target_loc kind =
8+
class mapper target_loc kind =
99
object (this)
1010
inherit Flow_ast_contains_mapper.mapper target_loc as super
1111

1212
method private build_cast ?comments expression annot =
1313
let expression = super#expression expression in
1414
let annot = super#type_ annot in
15-
let open Options.CastingSyntax in
16-
match enabled_casting_syntax with
17-
| As
18-
| Both ->
19-
Ast_builder.Expressions.as_expression ?comments expression annot
15+
Ast_builder.Expressions.as_expression ?comments expression annot
2016

2117
method! expression e =
2218
let open Flow_ast.Expression in
@@ -30,10 +26,39 @@ class mapper ~enabled_casting_syntax target_loc kind =
3026
| _ -> super#expression e
3127
end
3228

33-
let convert_satisfies_expression ~enabled_casting_syntax ast loc =
34-
let mapper = new mapper ~enabled_casting_syntax loc `SatisfiesExpression in
29+
let convert_satisfies_expression ast loc =
30+
let mapper = new mapper loc `SatisfiesExpression in
3531
mapper#program ast
3632

37-
let convert_colon_cast ~enabled_casting_syntax ast loc =
38-
let mapper = new mapper ~enabled_casting_syntax loc `ColonCast in
33+
let convert_colon_cast ast loc =
34+
let mapper = new mapper loc `ColonCast in
35+
mapper#program ast
36+
37+
class all_mapper kind =
38+
object (this)
39+
inherit [Loc.t] Flow_ast_mapper.mapper as super
40+
41+
method private build_cast ?comments expression annot =
42+
let expression = super#expression expression in
43+
let annot = super#type_ annot in
44+
Ast_builder.Expressions.as_expression ?comments expression annot
45+
46+
method! expression e =
47+
let open Flow_ast.Expression in
48+
match e with
49+
| (_, TypeCast { TypeCast.expression; annot = (_, annot); comments }) when kind = `ColonCast
50+
->
51+
this#build_cast ?comments expression annot
52+
| (_, TSSatisfies { TSSatisfies.expression; annot = (_, annot); comments })
53+
when kind = `SatisfiesExpression ->
54+
this#build_cast ?comments expression annot
55+
| _ -> super#expression e
56+
end
57+
58+
let convert_all_colon_casts ast =
59+
let mapper = new all_mapper `ColonCast in
60+
mapper#program ast
61+
62+
let convert_all_satisfies_expressions ast =
63+
let mapper = new all_mapper `SatisfiesExpression in
3964
mapper#program ast

src/services/code_action/autofix_casting_syntax.mli

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
*)
77

88
val convert_satisfies_expression :
9-
enabled_casting_syntax:Options.CastingSyntax.t ->
109
(Loc.t, Loc.t) Flow_ast_mapper.Ast.Program.t ->
1110
Loc.t ->
1211
(Loc.t, Loc.t) Flow_ast_mapper.Ast.Program.t
1312

1413
val convert_colon_cast :
15-
enabled_casting_syntax:Options.CastingSyntax.t ->
1614
(Loc.t, Loc.t) Flow_ast_mapper.Ast.Program.t ->
1715
Loc.t ->
1816
(Loc.t, Loc.t) Flow_ast_mapper.Ast.Program.t
17+
18+
val convert_all_colon_casts :
19+
(Loc.t, Loc.t) Flow_ast_mapper.Ast.Program.t -> (Loc.t, Loc.t) Flow_ast_mapper.Ast.Program.t
20+
21+
val convert_all_satisfies_expressions :
22+
(Loc.t, Loc.t) Flow_ast_mapper.Ast.Program.t -> (Loc.t, Loc.t) Flow_ast_mapper.Ast.Program.t

src/services/code_action/code_action_service.ml

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,9 +1063,7 @@ let ast_transforms_of_error
10631063
{
10641064
title;
10651065
diagnostic_title = "convert_satisfies_expression";
1066-
transform =
1067-
untyped_ast_transform
1068-
(Autofix_casting_syntax.convert_satisfies_expression ~enabled_casting_syntax);
1066+
transform = untyped_ast_transform Autofix_casting_syntax.convert_satisfies_expression;
10691067
target_loc = error_loc;
10701068
confidence = WillFixErrorAndSafeForRunningOnSave;
10711069
};
@@ -1116,7 +1114,7 @@ let ast_transforms_of_error
11161114
{
11171115
title;
11181116
diagnostic_title;
1119-
transform = untyped_ast_transform (fix ~enabled_casting_syntax);
1117+
transform = untyped_ast_transform fix;
11201118
target_loc = error_loc;
11211119
confidence = WillFixErrorAndSafeForRunningOnSave;
11221120
};
@@ -1607,6 +1605,52 @@ let ast_transforms_of_error
16071605
| _ -> [])
16081606
| _ -> [])
16091607

1608+
let fix_all_in_file_code_actions ~options ~loc_of_aloc ~ast ~diagnostics ~errors ~loc uri =
1609+
(* Step 1: Check if there's an EInvalidTypeCastSyntax error at the requested location *)
1610+
let has_invalid_cast_at_loc =
1611+
Flow_error.ErrorSet.exists
1612+
(fun error ->
1613+
let error_message =
1614+
Flow_error.msg_of_error error |> Error_message.map_loc_of_error_message loc_of_aloc
1615+
in
1616+
match error_message with
1617+
| Error_message.EInvalidTypeCastSyntax _ ->
1618+
(match Error_message.loc_of_msg error_message with
1619+
| Some error_loc -> Loc.intersects error_loc loc
1620+
| None -> false)
1621+
| _ -> false)
1622+
errors
1623+
in
1624+
if not has_invalid_cast_at_loc then
1625+
[]
1626+
else
1627+
(* Step 2: Generate code action using convert_all_colon_casts *)
1628+
let new_ast = Autofix_casting_syntax.convert_all_colon_casts ast in
1629+
if new_ast == ast then
1630+
[]
1631+
else
1632+
let opts = layout_options options in
1633+
let edits =
1634+
Replacement_printer.mk_loc_patch_ast_differ ~opts (Flow_ast_differ.program ast new_ast)
1635+
|> flow_loc_patch_to_lsp_edits
1636+
in
1637+
let open Lsp in
1638+
[
1639+
CodeAction.Action
1640+
{
1641+
CodeAction.title = "Convert all colon casts to `as` expressions";
1642+
kind = CodeActionKind.quickfix;
1643+
diagnostics;
1644+
action =
1645+
CodeAction.BothEditThenCommand
1646+
( WorkspaceEdit.{ changes = UriMap.singleton uri edits },
1647+
mk_log_command
1648+
~title:"Convert all colon casts to `as` expressions"
1649+
~diagnostic_title:"convert_all_colon_cast"
1650+
);
1651+
};
1652+
]
1653+
16101654
let code_actions_of_errors
16111655
~options
16121656
~loc_of_aloc
@@ -1939,6 +1983,19 @@ let code_actions_at_loc
19391983
uri
19401984
loc
19411985
in
1986+
let fix_all_in_file_actions =
1987+
if include_quick_fixes only then
1988+
fix_all_in_file_code_actions
1989+
~options
1990+
~loc_of_aloc
1991+
~ast
1992+
~diagnostics
1993+
~errors:(Context.errors cx)
1994+
~loc
1995+
uri
1996+
else
1997+
[]
1998+
in
19421999
let error_fixes =
19432000
code_actions_of_errors
19442001
~options
@@ -2005,6 +2062,7 @@ let code_actions_at_loc
20052062
parse_error_fixes
20062063
@ autofix_exports_code_actions
20072064
@ autofix_missing_local_annot_code_actions
2065+
@ fix_all_in_file_actions
20082066
@ error_fixes
20092067
@ scope_based_auto_import_fixes
20102068
@ insert_inferred_render_type_code_actions

0 commit comments

Comments
 (0)