Skip to content

Code action: Extract module to file #983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
- Statically linked Linux binaries
- Emit `%todo` instead of `failwith("TODO")` when we can (ReScript >= v11.1). https://github.com/rescript-lang/rescript-vscode/pull/981
- Complete `%todo`. https://github.com/rescript-lang/rescript-vscode/pull/981
- Add code action for extracting a locally defined module into its own file. https://github.com/rescript-lang/rescript-vscode/pull/983

## 1.50.0

Expand Down
11 changes: 10 additions & 1 deletion analysis/src/CodeActions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ let make ~title ~kind ~uri ~newText ~range =
edit =
{
documentChanges =
[{textDocument = {version = None; uri}; edits = [{newText; range}]}];
[
TextDocumentEdit
{
Protocol.textDocument = {version = None; uri};
edits = [{newText; range}];
};
];
};
}

let makeWithDocumentChanges ~title ~kind ~documentChanges =
{Protocol.title; codeActionKind = kind; edit = {documentChanges}}
2 changes: 1 addition & 1 deletion analysis/src/Codemod.ml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let rec collectPatterns p =
| _ -> [p]

let transform ~path ~pos ~debug ~typ ~hint =
let structure, printExpr, _ = Xform.parseImplementation ~filename:path in
let structure, printExpr, _, _ = Xform.parseImplementation ~filename:path in
match typ with
| AddMissingCases -> (
let source = "let " ^ hint ^ " = ()" in
Expand Down
26 changes: 17 additions & 9 deletions analysis/src/Commands.ml
Original file line number Diff line number Diff line change
Expand Up @@ -447,15 +447,23 @@ let test ~path =
|> List.iter (fun {Protocol.title; edit = {documentChanges}} ->
Printf.printf "Hit: %s\n" title;
documentChanges
|> List.iter (fun {Protocol.edits} ->
edits
|> List.iter (fun {Protocol.range; newText} ->
let indent =
String.make range.start.character ' '
in
Printf.printf "%s\nnewText:\n%s<--here\n%s%s\n"
(Protocol.stringifyRange range)
indent indent newText)))
|> List.iter (fun dc ->
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section just improves the test output a bit now that we have several document change actions.

match dc with
| Protocol.TextDocumentEdit tde ->
Printf.printf "\nTextDocumentEdit: %s\n"
tde.textDocument.uri;

tde.edits
|> List.iter (fun {Protocol.range; newText} ->
let indent =
String.make range.start.character ' '
in
Printf.printf
"%s\nnewText:\n%s<--here\n%s%s\n"
(Protocol.stringifyRange range)
indent indent newText)
| CreateFile cf ->
Printf.printf "\nCreateFile: %s\n" cf.uri))
| "c-a" ->
let hint = String.sub rest 3 (String.length rest - 3) in
print_endline
Expand Down
39 changes: 37 additions & 2 deletions analysis/src/Protocol.ml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,14 @@ type textDocumentEdit = {
edits: textEdit list;
}

type codeActionEdit = {documentChanges: textDocumentEdit list}
type createFileOptions = {overwrite: bool option; ignoreIfExists: bool option}
type createFile = {uri: string; options: createFileOptions option}

type documentChange =
| TextDocumentEdit of textDocumentEdit
| CreateFile of createFile

type codeActionEdit = {documentChanges: documentChange list}
Comment on lines +78 to +85
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type codeActionKind = RefactorRewrite

type codeAction = {
Expand Down Expand Up @@ -232,13 +239,41 @@ let stringifyTextDocumentEdit tde =
(stringifyoptionalVersionedTextDocumentIdentifier tde.textDocument)
(tde.edits |> List.map stringifyTextEdit |> array)

let stringifyCreateFile cf =
stringifyObject
[
("kind", Some (wrapInQuotes "create"));
("uri", Some (wrapInQuotes cf.uri));
( "options",
match cf.options with
| None -> None
| Some options ->
Some
(stringifyObject
[
( "overwrite",
match options.overwrite with
| None -> None
| Some ov -> Some (string_of_bool ov) );
( "ignoreIfExists",
match options.ignoreIfExists with
| None -> None
| Some i -> Some (string_of_bool i) );
]) );
]

let stringifyDocumentChange dc =
match dc with
| TextDocumentEdit tde -> stringifyTextDocumentEdit tde
| CreateFile cf -> stringifyCreateFile cf

let codeActionKindToString kind =
match kind with
| RefactorRewrite -> "refactor.rewrite"

let stringifyCodeActionEdit cae =
Printf.sprintf {|{"documentChanges": %s}|}
(cae.documentChanges |> List.map stringifyTextDocumentEdit |> array)
(cae.documentChanges |> List.map stringifyDocumentChange |> array)

let stringifyCodeAction ca =
Printf.sprintf {|{"title": "%s", "kind": "%s", "edit": %s}|} ca.title
Expand Down
80 changes: 78 additions & 2 deletions analysis/src/Xform.ml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,75 @@ module IfThenElse = struct
codeActions := codeAction :: !codeActions
end

module ModuleToFile = struct
let mkIterator ~pos ~changed ~path ~printStandaloneStructure =
let structure_item (iterator : Ast_iterator.iterator)
(structure_item : Parsetree.structure_item) =
(match structure_item.pstr_desc with
| Pstr_module
{pmb_loc; pmb_name; pmb_expr = {pmod_desc = Pmod_structure structure}}
when structure_item.pstr_loc |> Loc.hasPos ~pos ->
let range = rangeOfLoc structure_item.pstr_loc in
let newTextInCurrentFile = "" in
let textForExtractedFile =
printStandaloneStructure ~loc:pmb_loc structure
in
let moduleName = pmb_name.txt in
let newFilePath =
Uri.fromPath
(Filename.concat (Filename.dirname path) moduleName ^ ".res")
in
changed :=
Some
(CodeActions.makeWithDocumentChanges ~title:"Extract module as file"
~kind:RefactorRewrite
~documentChanges:
[
Protocol.CreateFile
{
uri = newFilePath |> Uri.toString;
options =
Some
{overwrite = Some false; ignoreIfExists = Some true};
};
TextDocumentEdit
{
textDocument =
{uri = newFilePath |> Uri.toString; version = None};
edits =
[
{
newText = textForExtractedFile;
range =
{
start = {line = 0; character = 0};
end_ = {line = 0; character = 0};
};
};
];
};
TextDocumentEdit
{
textDocument = {uri = path; version = None};
edits = [{newText = newTextInCurrentFile; range}];
};
]);
()
| _ -> ());
Ast_iterator.default_iterator.structure_item iterator structure_item
in

{Ast_iterator.default_iterator with structure_item}

let xform ~pos ~codeActions ~path ~printStandaloneStructure structure =
let changed = ref None in
let iterator = mkIterator ~pos ~path ~changed ~printStandaloneStructure in
iterator.structure iterator structure;
match !changed with
| None -> ()
| Some codeAction -> codeActions := codeAction :: !codeActions
end

module AddBracesToFn = struct
(* Add braces to fn without braces *)

Expand Down Expand Up @@ -626,7 +695,12 @@ let parseImplementation ~filename =
~comments:(comments |> filterComments ~loc:item.pstr_loc)
|> Utils.indent range.start.character
in
(structure, printExpr, printStructureItem)
let printStandaloneStructure ~(loc : Location.t) structure =
structure
|> Res_printer.printImplementation ~width:!Res_cli.ResClflags.width
~comments:(comments |> filterComments ~loc)
in
(structure, printExpr, printStructureItem, printStandaloneStructure)

let parseInterface ~filename =
let {Res_driver.parsetree = structure; comments} =
Expand Down Expand Up @@ -654,10 +728,12 @@ let extractCodeActions ~path ~startPos ~endPos ~currentFile ~debug =
let codeActions = ref [] in
match Files.classifySourceFile currentFile with
| Res ->
let structure, printExpr, printStructureItem =
let structure, printExpr, printStructureItem, printStandaloneStructure =
parseImplementation ~filename:currentFile
in
IfThenElse.xform ~pos ~codeActions ~printExpr ~path structure;
ModuleToFile.xform ~pos ~codeActions ~path ~printStandaloneStructure
structure;
AddBracesToFn.xform ~pos ~codeActions ~path ~printStructureItem structure;
AddDocTemplate.Implementation.xform ~pos ~codeActions ~path
~printStructureItem ~structure;
Expand Down
50 changes: 50 additions & 0 deletions analysis/tests/not_compiled/expected/DocTemplate.res.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Xform not_compiled/DocTemplate.res 3:3
can't find module DocTemplate
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.res
{"start": {"line": 3, "character": 0}, "end": {"line": 5, "character": 9}}
newText:
<--here
Expand All @@ -14,6 +16,8 @@ and e = C
Xform not_compiled/DocTemplate.res 6:15
can't find module DocTemplate
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.res
{"start": {"line": 6, "character": 0}, "end": {"line": 6, "character": 33}}
newText:
<--here
Expand All @@ -26,6 +30,8 @@ type name = Name(string)
Xform not_compiled/DocTemplate.res 8:4
can't find module DocTemplate
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.res
{"start": {"line": 8, "character": 0}, "end": {"line": 8, "character": 9}}
newText:
<--here
Expand All @@ -37,6 +43,8 @@ let a = 1
Xform not_compiled/DocTemplate.res 10:4
can't find module DocTemplate
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.res
{"start": {"line": 10, "character": 0}, "end": {"line": 10, "character": 20}}
newText:
<--here
Expand All @@ -48,6 +56,8 @@ let inc = x => x + 1
Xform not_compiled/DocTemplate.res 12:7
can't find module DocTemplate
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.res
{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
newText:
<--here
Expand All @@ -59,21 +69,61 @@ module T = {
let b = 1
// ^xfm
}
Hit: Extract module as file

CreateFile: T.res

TextDocumentEdit: T.res
{"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}
newText:
<--here
// ^xfm
let b = 1
// ^xfm


TextDocumentEdit: not_compiled/DocTemplate.res
{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
newText:
<--here


Xform not_compiled/DocTemplate.res 14:6
can't find module DocTemplate
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.res
{"start": {"line": 14, "character": 2}, "end": {"line": 14, "character": 11}}
newText:
<--here
/**

*/
let b = 1
Hit: Extract module as file

CreateFile: T.res

TextDocumentEdit: T.res
{"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}
newText:
<--here
// ^xfm
let b = 1
// ^xfm


TextDocumentEdit: not_compiled/DocTemplate.res
{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
newText:
<--here


Xform not_compiled/DocTemplate.res 18:2
can't find module DocTemplate
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.res
{"start": {"line": 17, "character": 0}, "end": {"line": 18, "character": 46}}
newText:
<--here
Expand Down
14 changes: 14 additions & 0 deletions analysis/tests/not_compiled/expected/DocTemplate.resi.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Xform not_compiled/DocTemplate.resi 3:3
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.resi
{"start": {"line": 3, "character": 0}, "end": {"line": 5, "character": 9}}
newText:
<--here
Expand All @@ -12,6 +14,8 @@ and e = C

Xform not_compiled/DocTemplate.resi 6:15
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.resi
{"start": {"line": 6, "character": 0}, "end": {"line": 6, "character": 33}}
newText:
<--here
Expand All @@ -23,6 +27,8 @@ type name = Name(string)

Xform not_compiled/DocTemplate.resi 8:4
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.resi
{"start": {"line": 8, "character": 0}, "end": {"line": 8, "character": 10}}
newText:
<--here
Expand All @@ -33,6 +39,8 @@ let a: int

Xform not_compiled/DocTemplate.resi 10:4
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.resi
{"start": {"line": 10, "character": 0}, "end": {"line": 10, "character": 19}}
newText:
<--here
Expand All @@ -43,6 +51,8 @@ let inc: int => int

Xform not_compiled/DocTemplate.resi 12:7
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.resi
{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
newText:
<--here
Expand All @@ -57,6 +67,8 @@ module T: {

Xform not_compiled/DocTemplate.resi 14:6
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.resi
{"start": {"line": 14, "character": 2}, "end": {"line": 14, "character": 12}}
newText:
<--here
Expand All @@ -67,6 +79,8 @@ newText:

Xform not_compiled/DocTemplate.resi 18:2
Hit: Add Documentation template

TextDocumentEdit: DocTemplate.resi
{"start": {"line": 17, "character": 0}, "end": {"line": 18, "character": 46}}
newText:
<--here
Expand Down
Loading
Loading