Skip to content

Commit 34b4c23

Browse files
authored
Code action: Extract module to file (#983)
* code action for extracting a module to a new file * make sure comments are retained * fix broken serialization * changelog
1 parent 2d8d942 commit 34b4c23

11 files changed

+268
-15
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
- Statically linked Linux binaries
3232
- Emit `%todo` instead of `failwith("TODO")` when we can (ReScript >= v11.1). https://github.com/rescript-lang/rescript-vscode/pull/981
3333
- Complete `%todo`. https://github.com/rescript-lang/rescript-vscode/pull/981
34+
- Add code action for extracting a locally defined module into its own file. https://github.com/rescript-lang/rescript-vscode/pull/983
3435

3536
## 1.50.0
3637

analysis/src/CodeActions.ml

+10-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ let make ~title ~kind ~uri ~newText ~range =
1313
edit =
1414
{
1515
documentChanges =
16-
[{textDocument = {version = None; uri}; edits = [{newText; range}]}];
16+
[
17+
TextDocumentEdit
18+
{
19+
Protocol.textDocument = {version = None; uri};
20+
edits = [{newText; range}];
21+
};
22+
];
1723
};
1824
}
25+
26+
let makeWithDocumentChanges ~title ~kind ~documentChanges =
27+
{Protocol.title; codeActionKind = kind; edit = {documentChanges}}

analysis/src/Codemod.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ let rec collectPatterns p =
66
| _ -> [p]
77

88
let transform ~path ~pos ~debug ~typ ~hint =
9-
let structure, printExpr, _ = Xform.parseImplementation ~filename:path in
9+
let structure, printExpr, _, _ = Xform.parseImplementation ~filename:path in
1010
match typ with
1111
| AddMissingCases -> (
1212
let source = "let " ^ hint ^ " = ()" in

analysis/src/Commands.ml

+17-9
Original file line numberDiff line numberDiff line change
@@ -453,15 +453,23 @@ let test ~path =
453453
|> List.iter (fun {Protocol.title; edit = {documentChanges}} ->
454454
Printf.printf "Hit: %s\n" title;
455455
documentChanges
456-
|> List.iter (fun {Protocol.edits} ->
457-
edits
458-
|> List.iter (fun {Protocol.range; newText} ->
459-
let indent =
460-
String.make range.start.character ' '
461-
in
462-
Printf.printf "%s\nnewText:\n%s<--here\n%s%s\n"
463-
(Protocol.stringifyRange range)
464-
indent indent newText)))
456+
|> List.iter (fun dc ->
457+
match dc with
458+
| Protocol.TextDocumentEdit tde ->
459+
Printf.printf "\nTextDocumentEdit: %s\n"
460+
tde.textDocument.uri;
461+
462+
tde.edits
463+
|> List.iter (fun {Protocol.range; newText} ->
464+
let indent =
465+
String.make range.start.character ' '
466+
in
467+
Printf.printf
468+
"%s\nnewText:\n%s<--here\n%s%s\n"
469+
(Protocol.stringifyRange range)
470+
indent indent newText)
471+
| CreateFile cf ->
472+
Printf.printf "\nCreateFile: %s\n" cf.uri))
465473
| "c-a" ->
466474
let hint = String.sub rest 3 (String.length rest - 3) in
467475
print_endline

analysis/src/Protocol.ml

+37-2
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,14 @@ type textDocumentEdit = {
7575
edits: textEdit list;
7676
}
7777

78-
type codeActionEdit = {documentChanges: textDocumentEdit list}
78+
type createFileOptions = {overwrite: bool option; ignoreIfExists: bool option}
79+
type createFile = {uri: string; options: createFileOptions option}
80+
81+
type documentChange =
82+
| TextDocumentEdit of textDocumentEdit
83+
| CreateFile of createFile
84+
85+
type codeActionEdit = {documentChanges: documentChange list}
7986
type codeActionKind = RefactorRewrite
8087

8188
type codeAction = {
@@ -232,13 +239,41 @@ let stringifyTextDocumentEdit tde =
232239
(stringifyoptionalVersionedTextDocumentIdentifier tde.textDocument)
233240
(tde.edits |> List.map stringifyTextEdit |> array)
234241

242+
let stringifyCreateFile cf =
243+
stringifyObject
244+
[
245+
("kind", Some (wrapInQuotes "create"));
246+
("uri", Some (wrapInQuotes cf.uri));
247+
( "options",
248+
match cf.options with
249+
| None -> None
250+
| Some options ->
251+
Some
252+
(stringifyObject
253+
[
254+
( "overwrite",
255+
match options.overwrite with
256+
| None -> None
257+
| Some ov -> Some (string_of_bool ov) );
258+
( "ignoreIfExists",
259+
match options.ignoreIfExists with
260+
| None -> None
261+
| Some i -> Some (string_of_bool i) );
262+
]) );
263+
]
264+
265+
let stringifyDocumentChange dc =
266+
match dc with
267+
| TextDocumentEdit tde -> stringifyTextDocumentEdit tde
268+
| CreateFile cf -> stringifyCreateFile cf
269+
235270
let codeActionKindToString kind =
236271
match kind with
237272
| RefactorRewrite -> "refactor.rewrite"
238273

239274
let stringifyCodeActionEdit cae =
240275
Printf.sprintf {|{"documentChanges": %s}|}
241-
(cae.documentChanges |> List.map stringifyTextDocumentEdit |> array)
276+
(cae.documentChanges |> List.map stringifyDocumentChange |> array)
242277

243278
let stringifyCodeAction ca =
244279
Printf.sprintf {|{"title": "%s", "kind": "%s", "edit": %s}|} ca.title

analysis/src/Xform.ml

+78-2
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,75 @@ module IfThenElse = struct
117117
codeActions := codeAction :: !codeActions
118118
end
119119

120+
module ModuleToFile = struct
121+
let mkIterator ~pos ~changed ~path ~printStandaloneStructure =
122+
let structure_item (iterator : Ast_iterator.iterator)
123+
(structure_item : Parsetree.structure_item) =
124+
(match structure_item.pstr_desc with
125+
| Pstr_module
126+
{pmb_loc; pmb_name; pmb_expr = {pmod_desc = Pmod_structure structure}}
127+
when structure_item.pstr_loc |> Loc.hasPos ~pos ->
128+
let range = rangeOfLoc structure_item.pstr_loc in
129+
let newTextInCurrentFile = "" in
130+
let textForExtractedFile =
131+
printStandaloneStructure ~loc:pmb_loc structure
132+
in
133+
let moduleName = pmb_name.txt in
134+
let newFilePath =
135+
Uri.fromPath
136+
(Filename.concat (Filename.dirname path) moduleName ^ ".res")
137+
in
138+
changed :=
139+
Some
140+
(CodeActions.makeWithDocumentChanges ~title:"Extract module as file"
141+
~kind:RefactorRewrite
142+
~documentChanges:
143+
[
144+
Protocol.CreateFile
145+
{
146+
uri = newFilePath |> Uri.toString;
147+
options =
148+
Some
149+
{overwrite = Some false; ignoreIfExists = Some true};
150+
};
151+
TextDocumentEdit
152+
{
153+
textDocument =
154+
{uri = newFilePath |> Uri.toString; version = None};
155+
edits =
156+
[
157+
{
158+
newText = textForExtractedFile;
159+
range =
160+
{
161+
start = {line = 0; character = 0};
162+
end_ = {line = 0; character = 0};
163+
};
164+
};
165+
];
166+
};
167+
TextDocumentEdit
168+
{
169+
textDocument = {uri = path; version = None};
170+
edits = [{newText = newTextInCurrentFile; range}];
171+
};
172+
]);
173+
()
174+
| _ -> ());
175+
Ast_iterator.default_iterator.structure_item iterator structure_item
176+
in
177+
178+
{Ast_iterator.default_iterator with structure_item}
179+
180+
let xform ~pos ~codeActions ~path ~printStandaloneStructure structure =
181+
let changed = ref None in
182+
let iterator = mkIterator ~pos ~path ~changed ~printStandaloneStructure in
183+
iterator.structure iterator structure;
184+
match !changed with
185+
| None -> ()
186+
| Some codeAction -> codeActions := codeAction :: !codeActions
187+
end
188+
120189
module AddBracesToFn = struct
121190
(* Add braces to fn without braces *)
122191

@@ -626,7 +695,12 @@ let parseImplementation ~filename =
626695
~comments:(comments |> filterComments ~loc:item.pstr_loc)
627696
|> Utils.indent range.start.character
628697
in
629-
(structure, printExpr, printStructureItem)
698+
let printStandaloneStructure ~(loc : Location.t) structure =
699+
structure
700+
|> Res_printer.printImplementation ~width:!Res_cli.ResClflags.width
701+
~comments:(comments |> filterComments ~loc)
702+
in
703+
(structure, printExpr, printStructureItem, printStandaloneStructure)
630704

631705
let parseInterface ~filename =
632706
let {Res_driver.parsetree = structure; comments} =
@@ -654,10 +728,12 @@ let extractCodeActions ~path ~startPos ~endPos ~currentFile ~debug =
654728
let codeActions = ref [] in
655729
match Files.classifySourceFile currentFile with
656730
| Res ->
657-
let structure, printExpr, printStructureItem =
731+
let structure, printExpr, printStructureItem, printStandaloneStructure =
658732
parseImplementation ~filename:currentFile
659733
in
660734
IfThenElse.xform ~pos ~codeActions ~printExpr ~path structure;
735+
ModuleToFile.xform ~pos ~codeActions ~path ~printStandaloneStructure
736+
structure;
661737
AddBracesToFn.xform ~pos ~codeActions ~path ~printStructureItem structure;
662738
AddDocTemplate.Implementation.xform ~pos ~codeActions ~path
663739
~printStructureItem ~structure;

analysis/tests/not_compiled/expected/DocTemplate.res.txt

+50
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Xform not_compiled/DocTemplate.res 3:3
22
can't find module DocTemplate
33
Hit: Add Documentation template
4+
5+
TextDocumentEdit: DocTemplate.res
46
{"start": {"line": 3, "character": 0}, "end": {"line": 5, "character": 9}}
57
newText:
68
<--here
@@ -14,6 +16,8 @@ and e = C
1416
Xform not_compiled/DocTemplate.res 6:15
1517
can't find module DocTemplate
1618
Hit: Add Documentation template
19+
20+
TextDocumentEdit: DocTemplate.res
1721
{"start": {"line": 6, "character": 0}, "end": {"line": 6, "character": 33}}
1822
newText:
1923
<--here
@@ -26,6 +30,8 @@ type name = Name(string)
2630
Xform not_compiled/DocTemplate.res 8:4
2731
can't find module DocTemplate
2832
Hit: Add Documentation template
33+
34+
TextDocumentEdit: DocTemplate.res
2935
{"start": {"line": 8, "character": 0}, "end": {"line": 8, "character": 9}}
3036
newText:
3137
<--here
@@ -37,6 +43,8 @@ let a = 1
3743
Xform not_compiled/DocTemplate.res 10:4
3844
can't find module DocTemplate
3945
Hit: Add Documentation template
46+
47+
TextDocumentEdit: DocTemplate.res
4048
{"start": {"line": 10, "character": 0}, "end": {"line": 10, "character": 20}}
4149
newText:
4250
<--here
@@ -48,6 +56,8 @@ let inc = x => x + 1
4856
Xform not_compiled/DocTemplate.res 12:7
4957
can't find module DocTemplate
5058
Hit: Add Documentation template
59+
60+
TextDocumentEdit: DocTemplate.res
5161
{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
5262
newText:
5363
<--here
@@ -59,21 +69,61 @@ module T = {
5969
let b = 1
6070
// ^xfm
6171
}
72+
Hit: Extract module as file
73+
74+
CreateFile: T.res
75+
76+
TextDocumentEdit: T.res
77+
{"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}
78+
newText:
79+
<--here
80+
// ^xfm
81+
let b = 1
82+
// ^xfm
83+
84+
85+
TextDocumentEdit: not_compiled/DocTemplate.res
86+
{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
87+
newText:
88+
<--here
89+
6290

6391
Xform not_compiled/DocTemplate.res 14:6
6492
can't find module DocTemplate
6593
Hit: Add Documentation template
94+
95+
TextDocumentEdit: DocTemplate.res
6696
{"start": {"line": 14, "character": 2}, "end": {"line": 14, "character": 11}}
6797
newText:
6898
<--here
6999
/**
70100

71101
*/
72102
let b = 1
103+
Hit: Extract module as file
104+
105+
CreateFile: T.res
106+
107+
TextDocumentEdit: T.res
108+
{"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}
109+
newText:
110+
<--here
111+
// ^xfm
112+
let b = 1
113+
// ^xfm
114+
115+
116+
TextDocumentEdit: not_compiled/DocTemplate.res
117+
{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
118+
newText:
119+
<--here
120+
73121

74122
Xform not_compiled/DocTemplate.res 18:2
75123
can't find module DocTemplate
76124
Hit: Add Documentation template
125+
126+
TextDocumentEdit: DocTemplate.res
77127
{"start": {"line": 17, "character": 0}, "end": {"line": 18, "character": 46}}
78128
newText:
79129
<--here

analysis/tests/not_compiled/expected/DocTemplate.resi.txt

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
Xform not_compiled/DocTemplate.resi 3:3
22
Hit: Add Documentation template
3+
4+
TextDocumentEdit: DocTemplate.resi
35
{"start": {"line": 3, "character": 0}, "end": {"line": 5, "character": 9}}
46
newText:
57
<--here
@@ -12,6 +14,8 @@ and e = C
1214

1315
Xform not_compiled/DocTemplate.resi 6:15
1416
Hit: Add Documentation template
17+
18+
TextDocumentEdit: DocTemplate.resi
1519
{"start": {"line": 6, "character": 0}, "end": {"line": 6, "character": 33}}
1620
newText:
1721
<--here
@@ -23,6 +27,8 @@ type name = Name(string)
2327

2428
Xform not_compiled/DocTemplate.resi 8:4
2529
Hit: Add Documentation template
30+
31+
TextDocumentEdit: DocTemplate.resi
2632
{"start": {"line": 8, "character": 0}, "end": {"line": 8, "character": 10}}
2733
newText:
2834
<--here
@@ -33,6 +39,8 @@ let a: int
3339

3440
Xform not_compiled/DocTemplate.resi 10:4
3541
Hit: Add Documentation template
42+
43+
TextDocumentEdit: DocTemplate.resi
3644
{"start": {"line": 10, "character": 0}, "end": {"line": 10, "character": 19}}
3745
newText:
3846
<--here
@@ -43,6 +51,8 @@ let inc: int => int
4351

4452
Xform not_compiled/DocTemplate.resi 12:7
4553
Hit: Add Documentation template
54+
55+
TextDocumentEdit: DocTemplate.resi
4656
{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
4757
newText:
4858
<--here
@@ -57,6 +67,8 @@ module T: {
5767

5868
Xform not_compiled/DocTemplate.resi 14:6
5969
Hit: Add Documentation template
70+
71+
TextDocumentEdit: DocTemplate.resi
6072
{"start": {"line": 14, "character": 2}, "end": {"line": 14, "character": 12}}
6173
newText:
6274
<--here
@@ -67,6 +79,8 @@ newText:
6779

6880
Xform not_compiled/DocTemplate.resi 18:2
6981
Hit: Add Documentation template
82+
83+
TextDocumentEdit: DocTemplate.resi
7084
{"start": {"line": 17, "character": 0}, "end": {"line": 18, "character": 46}}
7185
newText:
7286
<--here

0 commit comments

Comments
 (0)