From 73a00cf502faba6579bf65ae890123d6e80b1420 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 13 Jul 2022 18:53:11 -0300 Subject: [PATCH 1/7] test --- analysis/src/Xform.ml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index a402ca92e..908487673 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -257,6 +257,18 @@ module AddTypeAnnotation = struct | _ -> ())) end +module TypeToModule = struct + let xform ~path ~pos ~full ~structure ~codeActions = + let structure_item (iterator : Ast_iterator.iterator) + (item : Parsetree.structure_item) = + match item.pstr_desc with + | Pstr_type (_recFlag, typeDecls) -> + () + | _ -> () + in + {Ast_iterator.default_iterator with structure_item} +end + let indent n text = let spaces = String.make n ' ' in let len = String.length text in @@ -310,5 +322,6 @@ let extractCodeActions ~path ~pos ~currentFile ~debug = AddTypeAnnotation.xform ~path ~pos ~full ~structure ~codeActions ~debug; IfThenElse.xform ~pos ~codeActions ~printExpr ~path structure; AddBracesToFn.xform ~pos ~codeActions ~path ~printStructureItem structure; + TypeToModule.xform ~path ~pos ~full ~structure ~codeActions; !codeActions | _ -> [] From 1ddd96d4a334ca2f11b7047b756fc45041d96734 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Fri, 29 Jul 2022 20:29:55 -0300 Subject: [PATCH 2/7] codeaction: type to module --- CHANGELOG.md | 1 + analysis/src/CodeActions.ml | 11 +- analysis/src/Commands.ml | 91 ++--------- analysis/src/Protocol.ml | 19 ++- analysis/src/Rename.ml | 58 +++++++ analysis/src/Xform.ml | 148 ++++++++++++++++-- analysis/tests/src/Xform.res | 5 + analysis/tests/src/expected/Cross.res.txt | 24 +-- analysis/tests/src/expected/Rename.res.txt | 12 +- .../src/expected/RenameWithInterface.res.txt | 12 +- .../src/expected/RenameWithInterface.resi.txt | 12 +- analysis/tests/src/expected/Xform.res.txt | 13 ++ server/src/server.ts | 6 +- 13 files changed, 268 insertions(+), 144 deletions(-) create mode 100644 analysis/src/Rename.ml diff --git a/CHANGELOG.md b/CHANGELOG.md index d3979e4e5..e28a0e864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Code Lenses for functions (experimetal). `rescript.settings.codeLens: true`. Turned off by default. https://github.com/rescript-lang/rescript-vscode/pull/513 - Markdown code blocks tagged as `rescript` now get basic syntax highlighting. https://github.com/rescript-lang/rescript-vscode/pull/97 - Hover support for doc comments on v10 compiler `/** this is a doc comment */` +- Code action to convert type to module. #### :bug: Bug Fix diff --git a/analysis/src/CodeActions.ml b/analysis/src/CodeActions.ml index 844e25bce..9c5fd3125 100644 --- a/analysis/src/CodeActions.ml +++ b/analysis/src/CodeActions.ml @@ -5,13 +5,4 @@ let stringifyCodeActions codeActions = Printf.sprintf {|%s|} (codeActions |> List.map Protocol.stringifyCodeAction |> Protocol.array) -let make ~title ~kind ~uri ~newText ~range = - { - Protocol.title; - codeActionKind = kind; - edit = - { - documentChanges = - [{textDocument = {version = None; uri}; edits = [{newText; range}]}]; - }; - } +let make ~title ~kind ~edit = {Protocol.title; codeActionKind = kind; edit} diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index 5fcbca9ac..bc248473c 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -175,73 +175,9 @@ let references ~path ~pos ~debug = let rename ~path ~pos ~newName ~debug = let result = - match Cmt.fullFromPath ~path with + match Rename.command ~path ~pos ~newName ~debug with + | Some workspaceEdit -> workspaceEdit |> Protocol.stringifyWorkspaceEdit | None -> Protocol.null - | Some full -> ( - match References.getLocItem ~full ~pos ~debug with - | None -> Protocol.null - | Some locItem -> - let allReferences = References.allReferencesForLocItem ~full locItem in - let referencesToToplevelModules = - allReferences - |> Utils.filterMap (fun {References.uri = uri2; locOpt} -> - if locOpt = None then Some uri2 else None) - in - let referencesToItems = - allReferences - |> Utils.filterMap (function - | {References.uri = uri2; locOpt = Some loc} -> Some (uri2, loc) - | {locOpt = None} -> None) - in - let fileRenames = - referencesToToplevelModules - |> List.map (fun uri -> - let path = Uri.toPath uri in - let dir = Filename.dirname path in - let newPath = - Filename.concat dir (newName ^ Filename.extension path) - in - let newUri = Uri.fromPath newPath in - Protocol. - { - oldUri = uri |> Uri.toString; - newUri = newUri |> Uri.toString; - }) - in - let textDocumentEdits = - let module StringMap = Misc.StringMap in - let textEditsByUri = - referencesToItems - |> List.map (fun (uri, loc) -> (Uri.toString uri, loc)) - |> List.fold_left - (fun acc (uri, loc) -> - let textEdit = - Protocol. - {range = Utils.cmtLocToRange loc; newText = newName} - in - match StringMap.find_opt uri acc with - | None -> StringMap.add uri [textEdit] acc - | Some prevEdits -> - StringMap.add uri (textEdit :: prevEdits) acc) - StringMap.empty - in - StringMap.fold - (fun uri edits acc -> - let textDocumentEdit = - Protocol.{textDocument = {uri; version = None}; edits} - in - textDocumentEdit :: acc) - textEditsByUri [] - in - let fileRenamesString = - fileRenames |> List.map Protocol.stringifyRenameFile - in - let textDocumentEditsString = - textDocumentEdits |> List.map Protocol.stringifyTextDocumentEdit - in - "[\n" - ^ (fileRenamesString @ textDocumentEditsString |> String.concat ",\n") - ^ "\n]") in print_endline result @@ -383,15 +319,20 @@ 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 (documentChange : Protocol.documentChange) -> + match documentChange with + | TextDocumentEdit {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) + | _ -> ())) | "dia" -> diagnosticSyntax ~path | "hin" -> let line_start = 0 in diff --git a/analysis/src/Protocol.ml b/analysis/src/Protocol.ml index a1440aa85..3f56ae125 100644 --- a/analysis/src/Protocol.ml +++ b/analysis/src/Protocol.ml @@ -41,13 +41,17 @@ type textDocumentEdit = { edits: textEdit list; } -type codeActionEdit = {documentChanges: textDocumentEdit list} +type documentChange = + | TextDocumentEdit of textDocumentEdit + | RenameFile of renameFile + +type workspaceEdit = {documentChanges: documentChange list} type codeActionKind = RefactorRewrite type codeAction = { title: string; codeActionKind: codeActionKind; - edit: codeActionEdit; + edit: workspaceEdit; } let null = "null" @@ -133,14 +137,19 @@ let codeActionKindToString kind = match kind with | RefactorRewrite -> "refactor.rewrite" -let stringifyCodeActionEdit cae = +let stringifyWorkspaceEdit we = Printf.sprintf {|{"documentChanges": %s}|} - (cae.documentChanges |> List.map stringifyTextDocumentEdit |> array) + (we.documentChanges + |> List.map (fun documentChange -> + match documentChange with + | TextDocumentEdit textEdit -> textEdit |> stringifyTextDocumentEdit + | RenameFile renameFile -> renameFile |> stringifyRenameFile) + |> array) let stringifyCodeAction ca = Printf.sprintf {|{"title": "%s", "kind": "%s", "edit": %s}|} ca.title (codeActionKindToString ca.codeActionKind) - (ca.edit |> stringifyCodeActionEdit) + (ca.edit |> stringifyWorkspaceEdit) let stringifyHint hint = Printf.sprintf diff --git a/analysis/src/Rename.ml b/analysis/src/Rename.ml new file mode 100644 index 000000000..231e538e0 --- /dev/null +++ b/analysis/src/Rename.ml @@ -0,0 +1,58 @@ +let command ~path ~pos ~newName ~debug = + match Cmt.fullFromPath ~path with + | None -> None + | Some full -> ( + match References.getLocItem ~full ~pos ~debug with + | None -> None + | Some locItem -> + let allReferences = References.allReferencesForLocItem ~full locItem in + let referencesToToplevelModules = + allReferences + |> Utils.filterMap (fun {References.uri = uri2; locOpt} -> + if locOpt = None then Some uri2 else None) + in + let referencesToItems = + allReferences + |> Utils.filterMap (function + | {References.uri = uri2; locOpt = Some loc} -> Some (uri2, loc) + | {locOpt = None} -> None) + in + let fileRenames = + referencesToToplevelModules + |> List.map (fun uri -> + let path = Uri.toPath uri in + let dir = Filename.dirname path in + let newPath = + Filename.concat dir (newName ^ Filename.extension path) + in + let newUri = Uri.fromPath newPath in + Protocol.RenameFile + {oldUri = uri |> Uri.toString; newUri = newUri |> Uri.toString}) + in + let textDocumentEdits = + let module StringMap = Misc.StringMap in + let textEditsByUri = + referencesToItems + |> List.map (fun (uri, loc) -> (Uri.toString uri, loc)) + |> List.fold_left + (fun acc (uri, loc) -> + let textEdit = + Protocol.{range = Utils.cmtLocToRange loc; newText = newName} + in + match StringMap.find_opt uri acc with + | None -> StringMap.add uri [textEdit] acc + | Some prevEdits -> + StringMap.add uri (textEdit :: prevEdits) acc) + StringMap.empty + in + StringMap.fold + (fun uri edits acc -> + let textDocumentEdit = + Protocol.TextDocumentEdit + {textDocument = {uri; version = None}; edits} + in + textDocumentEdit :: acc) + textEditsByUri [] + in + let documentChanges = fileRenames @ textDocumentEdits in + Some Protocol.{documentChanges}) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index 908487673..5bc94748e 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -110,9 +110,20 @@ module IfThenElse = struct | Some newExpr -> let range = rangeOfLoc newExpr.pexp_loc in let newText = printExpr ~range newExpr in + let uri = Uri.fromPath path |> Uri.toString in let codeAction = CodeActions.make ~title:"Replace with switch" ~kind:RefactorRewrite - ~uri:path ~newText ~range + ~edit: + { + documentChanges = + [ + TextDocumentEdit + { + textDocument = {version = None; uri}; + edits = [{newText; range}]; + }; + ]; + } in codeActions := codeAction :: !codeActions end @@ -174,9 +185,20 @@ module AddBracesToFn = struct | Some newStructureItem -> let range = rangeOfLoc newStructureItem.pstr_loc in let newText = printStructureItem ~range newStructureItem in + let uri = Uri.fromPath path |> Uri.toString in let codeAction = CodeActions.make ~title:"Add braces to function" ~kind:RefactorRewrite - ~uri:path ~newText ~range + ~edit: + { + documentChanges = + [ + TextDocumentEdit + { + textDocument = {version = None; uri}; + edits = [{newText; range}]; + }; + ]; + } in codeActions := codeAction :: !codeActions end @@ -249,24 +271,131 @@ module AddTypeAnnotation = struct ( rangeOfLoc locItem.loc, "(" ^ name ^ ": " ^ (typ |> Shared.typeToString) ^ ")" ) in + let uri = Uri.fromPath path |> Uri.toString in let codeAction = CodeActions.make ~title:"Add type annotation" ~kind:RefactorRewrite - ~uri:path ~newText ~range + ~edit: + { + documentChanges = + [ + TextDocumentEdit + { + textDocument = {version = None; uri}; + edits = [{newText; range}]; + }; + ]; + } in codeActions := codeAction :: !codeActions | _ -> ())) end module TypeToModule = struct - let xform ~path ~pos ~full ~structure ~codeActions = + (* Convert a type it into its own submodule *) + let mkIterator ~pos ~result ~newTypeName = + let changeTypeDecl (typ : Parsetree.type_declaration) ~txt = + match typ.ptype_manifest with + | None -> + Ast_helper.Type.mk + {txt; loc = typ.ptype_name.loc} + ~loc:typ.ptype_loc ~attrs:typ.ptype_attributes + ~params:typ.ptype_params ~cstrs:typ.ptype_cstrs ~kind:typ.ptype_kind + ~priv:typ.ptype_private + | Some manifest -> + Ast_helper.Type.mk + {txt; loc = typ.ptype_name.loc} + ~loc:typ.ptype_loc ~attrs:typ.ptype_attributes + ~params:typ.ptype_params ~cstrs:typ.ptype_cstrs ~kind:typ.ptype_kind + ~priv:typ.ptype_private ~manifest + in + let structure_item (iterator : Ast_iterator.iterator) - (item : Parsetree.structure_item) = - match item.pstr_desc with - | Pstr_type (_recFlag, typeDecls) -> - () + (si : Parsetree.structure_item) = + match si.pstr_desc with + | Pstr_type (Nonrecursive, firstypeDec :: rest) + when Loc.hasPos ~pos si.pstr_loc -> + let loc = si.pstr_loc in + let firsLetterOfModule = + String.get firstypeDec.ptype_name.txt 0 + |> Char.uppercase_ascii |> String.make 1 + in + let restName = + String.sub firstypeDec.ptype_name.txt 1 + (String.length firstypeDec.ptype_name.txt - 1) + in + let modName = firsLetterOfModule ^ restName in + let newFistType = changeTypeDecl firstypeDec ~txt:newTypeName in + let restStrucTypes = + Ast_helper.Str.type_ ~loc Nonrecursive ([newFistType] @ rest) + in + let pmb_expr = Ast_helper.Mod.structure ~loc [restStrucTypes] in + let mod_expr = + Parsetree.Pstr_module + { + pmb_name = {txt = modName; loc}; + pmb_expr; + pmb_attributes = []; + pmb_loc = loc; + } + in + result := + Some + ( Ast_helper.Str.mk ~loc mod_expr, + firstypeDec.ptype_name.loc, + modName ); + Ast_iterator.default_iterator.structure_item iterator si | _ -> () in {Ast_iterator.default_iterator with structure_item} + + let xform ~path ~pos ~codeActions ~printStructureItem structure ~debug = + let result = ref None in + let newTypeName = "t" in + let iterator = mkIterator ~pos ~result ~newTypeName in + iterator.structure iterator structure; + match !result with + | None -> () + | Some (newStructureItem, locOfRef, modName) -> + let range = rangeOfLoc newStructureItem.pstr_loc in + let newText = printStructureItem ~range newStructureItem in + let rangeRef = rangeOfLoc locOfRef in + let newName = modName ^ "." ^ newTypeName in + let uri = Uri.fromPath path |> Uri.toString in + let renameReferences = + Rename.command ~path + ~pos:(rangeRef.start.line, rangeRef.start.character) + ~newName ~debug + in + let initialChange = + Protocol.TextDocumentEdit + {textDocument = {version = None; uri}; edits = [{newText; range}]} + in + let documentChanges = + match renameReferences with + | None -> [initialChange] + | Some workspaceEdit -> + let referencesChanged = + workspaceEdit.documentChanges + |> List.map (fun (documentChange : Protocol.documentChange) -> + match documentChange with + | TextDocumentEdit textEdit -> + let textDocument = textEdit.textDocument in + let edits = + textEdit.edits + |> List.filter (fun (edits : Protocol.textEdit) -> + edits.range <> rangeRef) + in + Protocol.TextDocumentEdit {textDocument; edits} + | _ -> documentChange) + in + [initialChange] @ referencesChanged + in + let codeAction = + CodeActions.make + ~title:("Convert type to module " ^ modName) + ~kind:RefactorRewrite ~edit:{documentChanges} + in + codeActions := codeAction :: !codeActions end let indent n text = @@ -322,6 +451,7 @@ let extractCodeActions ~path ~pos ~currentFile ~debug = AddTypeAnnotation.xform ~path ~pos ~full ~structure ~codeActions ~debug; IfThenElse.xform ~pos ~codeActions ~printExpr ~path structure; AddBracesToFn.xform ~pos ~codeActions ~path ~printStructureItem structure; - TypeToModule.xform ~path ~pos ~full ~structure ~codeActions; + TypeToModule.xform ~path ~pos ~codeActions ~printStructureItem structure + ~debug; !codeActions | _ -> [] diff --git a/analysis/tests/src/Xform.res b/analysis/tests/src/Xform.res index 52baad9d8..0fe701120 100644 --- a/analysis/tests/src/Xform.res +++ b/analysis/tests/src/Xform.res @@ -65,3 +65,8 @@ let bar = () => { } Inner.foo(1) } + +type readState = New | Unread | Read +//^xfm + +type refState = readState \ No newline at end of file diff --git a/analysis/tests/src/expected/Cross.res.txt b/analysis/tests/src/expected/Cross.res.txt index b045e8a02..013485cfa 100644 --- a/analysis/tests/src/expected/Cross.res.txt +++ b/analysis/tests/src/expected/Cross.res.txt @@ -18,18 +18,15 @@ References src/Cross.res 9:31 ] Rename src/Cross.res 18:13 RenameWithInterfacePrime -[ -{ +{"documentChanges": [{ "kind": "rename", "oldUri": "RenameWithInterface.resi", "newUri": "RenameWithInterfacePrime.resi" -}, -{ +}, { "kind": "rename", "oldUri": "RenameWithInterface.res", "newUri": "RenameWithInterfacePrime.res" -}, -{ +}, { "textDocument": { "version": null, "uri": "Cross.res" @@ -41,12 +38,10 @@ Rename src/Cross.res 18:13 RenameWithInterfacePrime "range": {"start": {"line": 21, "character": 8}, "end": {"line": 21, "character": 27}}, "newText": "RenameWithInterfacePrime" }] - } -] + }]} Rename src/Cross.res 21:28 xPrime -[ -{ +{"documentChanges": [{ "textDocument": { "version": null, "uri": "RenameWithInterface.resi" @@ -55,8 +50,7 @@ Rename src/Cross.res 21:28 xPrime "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, "newText": "xPrime" }] - }, -{ + }, { "textDocument": { "version": null, "uri": "RenameWithInterface.res" @@ -65,8 +59,7 @@ Rename src/Cross.res 21:28 xPrime "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, "newText": "xPrime" }] - }, -{ + }, { "textDocument": { "version": null, "uri": "Cross.res" @@ -78,8 +71,7 @@ Rename src/Cross.res 21:28 xPrime "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}}, "newText": "xPrime" }] - } -] + }]} TypeDefinition src/Cross.res 24:5 {"uri": "TypeDefinition.res", "range": {"start": {"line": 2, "character": 0}, "end": {"line": 2, "character": 28}}} diff --git a/analysis/tests/src/expected/Rename.res.txt b/analysis/tests/src/expected/Rename.res.txt index 5cd2adfee..6b6d6cc68 100644 --- a/analysis/tests/src/expected/Rename.res.txt +++ b/analysis/tests/src/expected/Rename.res.txt @@ -1,6 +1,5 @@ Rename src/Rename.res 0:4 y -[ -{ +{"documentChanges": [{ "textDocument": { "version": null, "uri": "Rename.res" @@ -15,12 +14,10 @@ Rename src/Rename.res 0:4 y "range": {"start": {"line": 7, "character": 8}, "end": {"line": 7, "character": 9}}, "newText": "y" }] - } -] + }]} Rename src/Rename.res 9:19 yy -[ -{ +{"documentChanges": [{ "textDocument": { "version": null, "uri": "Rename.res" @@ -32,6 +29,5 @@ Rename src/Rename.res 9:19 yy "range": {"start": {"line": 9, "character": 19}, "end": {"line": 9, "character": 21}}, "newText": "yy" }] - } -] + }]} diff --git a/analysis/tests/src/expected/RenameWithInterface.res.txt b/analysis/tests/src/expected/RenameWithInterface.res.txt index a13988fa9..10c866089 100644 --- a/analysis/tests/src/expected/RenameWithInterface.res.txt +++ b/analysis/tests/src/expected/RenameWithInterface.res.txt @@ -1,6 +1,5 @@ Rename src/RenameWithInterface.res 0:4 y -[ -{ +{"documentChanges": [{ "textDocument": { "version": null, "uri": "RenameWithInterface.resi" @@ -9,8 +8,7 @@ Rename src/RenameWithInterface.res 0:4 y "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, "newText": "y" }] - }, -{ + }, { "textDocument": { "version": null, "uri": "RenameWithInterface.res" @@ -19,8 +17,7 @@ Rename src/RenameWithInterface.res 0:4 y "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, "newText": "y" }] - }, -{ + }, { "textDocument": { "version": null, "uri": "Cross.res" @@ -32,6 +29,5 @@ Rename src/RenameWithInterface.res 0:4 y "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}}, "newText": "y" }] - } -] + }]} diff --git a/analysis/tests/src/expected/RenameWithInterface.resi.txt b/analysis/tests/src/expected/RenameWithInterface.resi.txt index 2a1dabb44..4a9c40d36 100644 --- a/analysis/tests/src/expected/RenameWithInterface.resi.txt +++ b/analysis/tests/src/expected/RenameWithInterface.resi.txt @@ -1,6 +1,5 @@ Rename src/RenameWithInterface.resi 0:4 y -[ -{ +{"documentChanges": [{ "textDocument": { "version": null, "uri": "RenameWithInterface.resi" @@ -9,8 +8,7 @@ Rename src/RenameWithInterface.resi 0:4 y "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, "newText": "y" }] - }, -{ + }, { "textDocument": { "version": null, "uri": "RenameWithInterface.res" @@ -19,8 +17,7 @@ Rename src/RenameWithInterface.resi 0:4 y "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, "newText": "y" }] - }, -{ + }, { "textDocument": { "version": null, "uri": "Cross.res" @@ -32,6 +29,5 @@ Rename src/RenameWithInterface.resi 0:4 y "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}}, "newText": "y" }] - } -] + }]} diff --git a/analysis/tests/src/expected/Xform.res.txt b/analysis/tests/src/expected/Xform.res.txt index d298604ae..ecd19870f 100644 --- a/analysis/tests/src/expected/Xform.res.txt +++ b/analysis/tests/src/expected/Xform.res.txt @@ -99,3 +99,16 @@ newText: } } +Xform src/Xform.res 68:2 +Hit: Convert type to module ReadState +{"start": {"line": 68, "character": 0}, "end": {"line": 68, "character": 36}} +newText: +<--here +module ReadState = { + type t = New | Unread | Read +} +{"start": {"line": 71, "character": 16}, "end": {"line": 71, "character": 25}} +newText: + <--here + ReadState.t + diff --git a/server/src/server.ts b/server/src/server.ts index 76c78e0b7..54c078e3e 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -531,7 +531,7 @@ function rename(msg: p.RequestMessage) { // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename let params = msg.params as p.RenameParams; let filePath = fileURLToPath(params.textDocument.uri); - let documentChanges: (p.RenameFile | p.TextDocumentEdit)[] | null = + let result: WorkspaceEdit | null = utils.runAnalysisAfterSanityCheck(filePath, [ "rename", filePath, @@ -539,10 +539,6 @@ function rename(msg: p.RequestMessage) { params.position.character, params.newName, ]); - let result: WorkspaceEdit | null = null; - if (documentChanges !== null) { - result = { documentChanges }; - } let response: p.ResponseMessage = { jsonrpc: c.jsonrpcVersion, id: msg.id, From a9c63c04cf92faea7016a87b9995a98c1d037528 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 3 Aug 2022 00:29:05 -0300 Subject: [PATCH 3/7] handle with record/variant --- analysis/src/Xform.ml | 89 +++++++++++++---------- analysis/tests/src/Xform.res | 36 ++++++++- analysis/tests/src/expected/Xform.res.txt | 58 ++++++++++++++- 3 files changed, 144 insertions(+), 39 deletions(-) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index 5bc94748e..8414ff9f6 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -291,7 +291,7 @@ module AddTypeAnnotation = struct end module TypeToModule = struct - (* Convert a type it into its own submodule *) + (* Convert type definition into its own module *) let mkIterator ~pos ~result ~newTypeName = let changeTypeDecl (typ : Parsetree.type_declaration) ~txt = match typ.ptype_manifest with @@ -338,11 +338,19 @@ module TypeToModule = struct pmb_loc = loc; } in - result := - Some - ( Ast_helper.Str.mk ~loc mod_expr, - firstypeDec.ptype_name.loc, - modName ); + let processTypeKind (typeDec : Parsetree.type_declaration) = + match typeDec.ptype_kind with + | Ptype_record [{pld_name = {loc; txt}}; _] -> Some (loc, txt) + | Ptype_variant [{pcd_name = {loc; txt}}; _] -> Some (loc, txt) + | _ -> None + in + let references = + let referencesInfo = + [firstypeDec] @ rest |> List.filter_map processTypeKind + in + [(firstypeDec.ptype_name.loc, newTypeName)] @ referencesInfo + in + result := Some (Ast_helper.Str.mk ~loc mod_expr, references, modName); Ast_iterator.default_iterator.structure_item iterator si | _ -> () in @@ -355,45 +363,52 @@ module TypeToModule = struct iterator.structure iterator structure; match !result with | None -> () - | Some (newStructureItem, locOfRef, modName) -> + | Some (newStructureItem, references, modName) -> let range = rangeOfLoc newStructureItem.pstr_loc in let newText = printStructureItem ~range newStructureItem in - let rangeRef = rangeOfLoc locOfRef in - let newName = modName ^ "." ^ newTypeName in let uri = Uri.fromPath path |> Uri.toString in - let renameReferences = - Rename.command ~path - ~pos:(rangeRef.start.line, rangeRef.start.character) - ~newName ~debug - in - let initialChange = + let typeToModuleEdit = Protocol.TextDocumentEdit {textDocument = {version = None; uri}; edits = [{newText; range}]} in - let documentChanges = - match renameReferences with - | None -> [initialChange] - | Some workspaceEdit -> - let referencesChanged = - workspaceEdit.documentChanges - |> List.map (fun (documentChange : Protocol.documentChange) -> - match documentChange with - | TextDocumentEdit textEdit -> - let textDocument = textEdit.textDocument in - let edits = - textEdit.edits - |> List.filter (fun (edits : Protocol.textEdit) -> - edits.range <> rangeRef) - in - Protocol.TextDocumentEdit {textDocument; edits} - | _ -> documentChange) - in - [initialChange] @ referencesChanged + (* Before apply code action find all references and rename with new name *) + let relatedChanges = + references + |> List.filter_map (fun (loc, txt) -> + let range = rangeOfLoc loc in + let newName = modName ^ "." ^ txt in + match + Rename.command ~path + ~pos:(range.start.line, range.start.character) + ~newName ~debug + with + | Some workspaceEdit -> + let result = + workspaceEdit.documentChanges + |> List.map + (fun (documentChange : Protocol.documentChange) -> + match documentChange with + | TextDocumentEdit textEdit -> + let textDocument = textEdit.textDocument in + (* Ignores first edit as it is within range of code action *) + let edits = + match textEdit.edits with + | _ :: rest -> rest + | _ -> [] + in + Protocol.TextDocumentEdit {textDocument; edits} + | _ -> documentChange) + in + Some result + | None -> None) + |> List.flatten + in + let edit : Protocol.workspaceEdit = + {documentChanges = [typeToModuleEdit] @ relatedChanges} in let codeAction = - CodeActions.make - ~title:("Convert type to module " ^ modName) - ~kind:RefactorRewrite ~edit:{documentChanges} + CodeActions.make ~title:"Move type definition into its own module" + ~kind:RefactorRewrite ~edit in codeActions := codeAction :: !codeActions end diff --git a/analysis/tests/src/Xform.res b/analysis/tests/src/Xform.res index 0fe701120..15ed72964 100644 --- a/analysis/tests/src/Xform.res +++ b/analysis/tests/src/Xform.res @@ -69,4 +69,38 @@ let bar = () => { type readState = New | Unread | Read //^xfm -type refState = readState \ No newline at end of file +type refState = readState + +type account = + | None + | Instagram(string) + | Facebook(string, int) +//^xfm + +type person = { + "age": int, + "name": string +} +//^xfm + +type user = { + name: string, + age: int, +} and response = Yes | No +//^xfm + +type myType = This | That +//^xfm + +let fun1 = (x: myType) => x + +let fun2 = b => b ? This : That + +let fun3 = b => b ? {name: "Lhs", age: 2} : {name: "Rhs", age: 3} + +let fun4 = b => b ? Yes : No + +let me: person = { + "age": 5, + "name": "Big ReScript" +} \ No newline at end of file diff --git a/analysis/tests/src/expected/Xform.res.txt b/analysis/tests/src/expected/Xform.res.txt index ecd19870f..504ca9bcf 100644 --- a/analysis/tests/src/expected/Xform.res.txt +++ b/analysis/tests/src/expected/Xform.res.txt @@ -100,7 +100,7 @@ newText: } Xform src/Xform.res 68:2 -Hit: Convert type to module ReadState +Hit: Move type definition into its own module {"start": {"line": 68, "character": 0}, "end": {"line": 68, "character": 36}} newText: <--here @@ -112,3 +112,59 @@ newText: <--here ReadState.t +Xform src/Xform.res 76:2 +Hit: Move type definition into its own module +{"start": {"line": 73, "character": 0}, "end": {"line": 76, "character": 25}} +newText: +<--here +module Account = { + type t = + | None + | Instagram(string) + | Facebook(string, int) +} + +Xform src/Xform.res 82:2 + +Xform src/Xform.res 88:2 +Hit: Move type definition into its own module +{"start": {"line": 85, "character": 0}, "end": {"line": 88, "character": 25}} +newText: +<--here +module User = { + type t = { + name: string, + age: int, + } + and response = Yes | No +} +{"start": {"line": 98, "character": 21}, "end": {"line": 98, "character": 25}} +newText: + <--here + User.name +{"start": {"line": 98, "character": 45}, "end": {"line": 98, "character": 49}} +newText: + <--here + User.name +{"start": {"line": 100, "character": 20}, "end": {"line": 100, "character": 23}} +newText: + <--here + User.Yes + +Xform src/Xform.res 91:2 +Hit: Move type definition into its own module +{"start": {"line": 91, "character": 0}, "end": {"line": 91, "character": 25}} +newText: +<--here +module MyType = { + type t = This | That +} +{"start": {"line": 94, "character": 15}, "end": {"line": 94, "character": 21}} +newText: + <--here + MyType.t +{"start": {"line": 96, "character": 20}, "end": {"line": 96, "character": 24}} +newText: + <--here + MyType.This + From 09bc060ab01badbb2753bffe6b58a54778785573 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 3 Aug 2022 00:59:42 -0300 Subject: [PATCH 4/7] support multiple file --- analysis/src/Xform.ml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index 8414ff9f6..1a1836727 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -390,11 +390,13 @@ module TypeToModule = struct match documentChange with | TextDocumentEdit textEdit -> let textDocument = textEdit.textDocument in - (* Ignores first edit as it is within range of code action *) let edits = - match textEdit.edits with - | _ :: rest -> rest - | _ -> [] + if textEdit.textDocument.uri = uri then + (* Ignores first edit as it is within range of code action *) + match textEdit.edits with + | _ :: rest -> rest + | _ -> [] + else textEdit.edits in Protocol.TextDocumentEdit {textDocument; edits} | _ -> documentChange) From 6598a2384669fcd80f7d9fe173e893467e7140c5 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 3 Aug 2022 02:08:44 -0300 Subject: [PATCH 5/7] refactor --- analysis/src/Xform.ml | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index 1a1836727..1b5cc26ad 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -294,19 +294,12 @@ module TypeToModule = struct (* Convert type definition into its own module *) let mkIterator ~pos ~result ~newTypeName = let changeTypeDecl (typ : Parsetree.type_declaration) ~txt = - match typ.ptype_manifest with - | None -> - Ast_helper.Type.mk - {txt; loc = typ.ptype_name.loc} - ~loc:typ.ptype_loc ~attrs:typ.ptype_attributes - ~params:typ.ptype_params ~cstrs:typ.ptype_cstrs ~kind:typ.ptype_kind - ~priv:typ.ptype_private - | Some manifest -> - Ast_helper.Type.mk - {txt; loc = typ.ptype_name.loc} - ~loc:typ.ptype_loc ~attrs:typ.ptype_attributes - ~params:typ.ptype_params ~cstrs:typ.ptype_cstrs ~kind:typ.ptype_kind - ~priv:typ.ptype_private ~manifest + let manifest = typ.ptype_manifest in + Ast_helper.Type.mk + {txt; loc = typ.ptype_name.loc} + ~loc:typ.ptype_loc ~attrs:typ.ptype_attributes ~params:typ.ptype_params + ~cstrs:typ.ptype_cstrs ~kind:typ.ptype_kind ~priv:typ.ptype_private + ?manifest in let structure_item (iterator : Ast_iterator.iterator) From 7a4c6cba87e72235a9e069c08d9c35b3dd5daca9 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 3 Aug 2022 05:05:14 -0300 Subject: [PATCH 6/7] refactor --- analysis/src/Xform.ml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index 1b5cc26ad..6616fc873 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -384,11 +384,12 @@ module TypeToModule = struct | TextDocumentEdit textEdit -> let textDocument = textEdit.textDocument in let edits = - if textEdit.textDocument.uri = uri then - (* Ignores first edit as it is within range of code action *) - match textEdit.edits with - | _ :: rest -> rest - | _ -> [] + if textDocument.uri = uri then + textEdit.edits + |> List.filter + (fun (textEdit : Protocol.textEdit) -> + (* Ignore some text edit because it refers to the type that will be moved to its own module *) + textEdit.range <> range) else textEdit.edits in Protocol.TextDocumentEdit {textDocument; edits} From e9e6295a8525e57e7a7b95957fabe191b6066349 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 3 Aug 2022 15:40:42 -0300 Subject: [PATCH 7/7] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8de3d604..8ab615cee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ - Code Lenses for functions (experimetal). `rescript.settings.codeLens: true`. Turned off by default. https://github.com/rescript-lang/rescript-vscode/pull/513 - Markdown code blocks tagged as `rescript` now get basic syntax highlighting. https://github.com/rescript-lang/rescript-vscode/pull/97 - Hover support for doc comments on v10 compiler `/** this is a doc comment */` -- Code action to convert type to module. +- Code action to convert type to module. https://github.com/rescript-lang/rescript-vscode/issues/430 #### :bug: Bug Fix