Skip to content
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

CodeAction: add docstring template #764

Merged
merged 3 commits into from
Apr 27, 2023
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

## master

#### :rocket: New Feature

- Docstring template Code Action. https://github.com/rescript-lang/rescript-vscode/pull/764

## 1.16.0

#### :rocket: New Feature
Expand Down
234 changes: 227 additions & 7 deletions analysis/src/Xform.ml
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,191 @@ module AddTypeAnnotation = struct
| _ -> ()))
end

let parse ~filename =
module AddDocTemplate = struct
let createTemplate () =
let docContent = ["\n"; "\n"] in
let expression =
Ast_helper.Exp.constant
(Parsetree.Pconst_string (String.concat "" docContent, None))
in
let structureItemDesc = Parsetree.Pstr_eval (expression, []) in
let structureItem = Ast_helper.Str.mk structureItemDesc in
let attrLoc =
{
Location.none with
loc_start = Lexing.dummy_pos;
loc_end =
{
Lexing.dummy_pos with
pos_lnum = Lexing.dummy_pos.pos_lnum (* force line break *);
};
}
in
(Location.mkloc "res.doc" attrLoc, Parsetree.PStr [structureItem])

module Interface = struct
let mkIterator ~pos ~result =
let signature_item (iterator : Ast_iterator.iterator)
(item : Parsetree.signature_item) =
match item.psig_desc with
| Psig_value value_description as r
when Loc.hasPos ~pos value_description.pval_loc
&& ProcessAttributes.findDocAttribute
value_description.pval_attributes
= None ->
result := Some (r, item.psig_loc)
| Psig_type (_, hd :: _) as r
when Loc.hasPos ~pos hd.ptype_loc
&& ProcessAttributes.findDocAttribute hd.ptype_attributes = None
->
result := Some (r, item.psig_loc)
| Psig_module {pmd_name = {loc}} as r ->
if Loc.start loc = pos then result := Some (r, item.psig_loc)
else Ast_iterator.default_iterator.signature_item iterator item
| _ -> Ast_iterator.default_iterator.signature_item iterator item
in
{Ast_iterator.default_iterator with signature_item}

let processSigValue (valueDesc : Parsetree.value_description) loc =
let attr = createTemplate () in
let newValueBinding =
{valueDesc with pval_attributes = attr :: valueDesc.pval_attributes}
in
let signature_item_desc = Parsetree.Psig_value newValueBinding in
Ast_helper.Sig.mk ~loc signature_item_desc

let processTypeDecl (typ : Parsetree.type_declaration) =
let attr = createTemplate () in
let newTypeDeclaration =
{typ with ptype_attributes = attr :: typ.ptype_attributes}
in
newTypeDeclaration

let processModDecl (modDecl : Parsetree.module_declaration) loc =
let attr = createTemplate () in
let newModDecl =
{modDecl with pmd_attributes = attr :: modDecl.pmd_attributes}
in
Ast_helper.Sig.mk ~loc (Parsetree.Psig_module newModDecl)

let xform ~path ~pos ~codeActions ~signature ~printSignatureItem =
let result = ref None in
let iterator = mkIterator ~pos ~result in
iterator.signature iterator signature;
match !result with
| Some (signatureItem, loc) -> (
let newSignatureItem =
match signatureItem with
| Psig_value value_desc ->
Some (processSigValue value_desc value_desc.pval_loc) (* Some loc *)
| Psig_type (flag, hd :: tl) ->
let newFirstTypeDecl = processTypeDecl hd in
Some
(Ast_helper.Sig.mk ~loc
(Parsetree.Psig_type (flag, newFirstTypeDecl :: tl)))
| Psig_module modDecl -> Some (processModDecl modDecl loc)
| _ -> None
in

match newSignatureItem with
| Some signatureItem ->
let range = rangeOfLoc signatureItem.psig_loc in
let newText = printSignatureItem ~range signatureItem in
let codeAction =
CodeActions.make ~title:"Add Documentation template"
~kind:RefactorRewrite ~uri:path ~newText ~range
in
codeActions := codeAction :: !codeActions
| None -> ())
| None -> ()
end

module Implementation = struct
let mkIterator ~pos ~result =
let structure_item (iterator : Ast_iterator.iterator)
(si : Parsetree.structure_item) =
match si.pstr_desc with
| Pstr_value (_, {pvb_pat = {ppat_loc}; pvb_attributes} :: _) as r
when Loc.hasPos ~pos ppat_loc
&& ProcessAttributes.findDocAttribute pvb_attributes = None ->
result := Some (r, si.pstr_loc)
| Pstr_primitive value_description as r
when Loc.hasPos ~pos value_description.pval_loc
&& ProcessAttributes.findDocAttribute
value_description.pval_attributes
= None ->
result := Some (r, si.pstr_loc)
| Pstr_module {pmb_name = {loc}} as r ->
if Loc.start loc = pos then result := Some (r, si.pstr_loc)
else Ast_iterator.default_iterator.structure_item iterator si
| Pstr_type (_, hd :: _) as r
when Loc.hasPos ~pos hd.ptype_loc
&& ProcessAttributes.findDocAttribute hd.ptype_attributes = None
->
result := Some (r, si.pstr_loc)
| _ -> Ast_iterator.default_iterator.structure_item iterator si
in
{Ast_iterator.default_iterator with structure_item}

let processValueBinding (valueBinding : Parsetree.value_binding) =
let attr = createTemplate () in
let newValueBinding =
{valueBinding with pvb_attributes = attr :: valueBinding.pvb_attributes}
in
newValueBinding

let processPrimitive (valueDesc : Parsetree.value_description) loc =
let attr = createTemplate () in
let newValueDesc =
{valueDesc with pval_attributes = attr :: valueDesc.pval_attributes}
in
Ast_helper.Str.primitive ~loc newValueDesc

let processModuleBinding (modBind : Parsetree.module_binding) loc =
let attr = createTemplate () in
let newModBinding =
{modBind with pmb_attributes = attr :: modBind.pmb_attributes}
in
Ast_helper.Str.module_ ~loc newModBinding

let xform ~pos ~codeActions ~path ~printStructureItem ~structure =
let result = ref None in
let iterator = mkIterator ~pos ~result in
iterator.structure iterator structure;
match !result with
| None -> ()
| Some (structureItem, loc) -> (
let newStructureItem =
match structureItem with
| Pstr_value (flag, hd :: tl) ->
let newValueBinding = processValueBinding hd in
Some
(Ast_helper.Str.mk ~loc
(Parsetree.Pstr_value (flag, newValueBinding :: tl)))
| Pstr_primitive valueDesc -> Some (processPrimitive valueDesc loc)
| Pstr_module modBind -> Some (processModuleBinding modBind loc)
| Pstr_type (flag, hd :: tl) ->
let newFirstTypeDecl = Interface.processTypeDecl hd in
Some
(Ast_helper.Str.mk ~loc
(Parsetree.Pstr_type (flag, newFirstTypeDecl :: tl)))
| _ -> None
in

match newStructureItem with
| Some structureItem ->
let range = rangeOfLoc structureItem.pstr_loc in
let newText = printStructureItem ~range structureItem in
let codeAction =
CodeActions.make ~title:"Add Documentation template"
~kind:RefactorRewrite ~uri:path ~newText ~range
in
codeActions := codeAction :: !codeActions
| None -> ())
end
end

let parseImplementation ~filename =
let {Res_driver.parsetree = structure; comments} =
Res_driver.parsingEngine.parseImplementation ~forPrinter:false ~filename
in
Expand Down Expand Up @@ -280,15 +464,51 @@ let parse ~filename =
in
(structure, printExpr, printStructureItem)

let parseInterface ~filename =
let {Res_driver.parsetree = structure; comments} =
Res_driver.parsingEngine.parseInterface ~forPrinter:false ~filename
in
let filterComments ~loc comments =
(* Relevant comments in the range of the expression *)
let filter comment =
Loc.hasPos ~pos:(Loc.start (Res_comment.loc comment)) loc
in
comments |> List.filter filter
in
let printSignatureItem ~(range : Protocol.range)
(item : Parsetree.signature_item) =
let signature_item = [item] in
signature_item
|> Res_printer.printInterface ~width:!Res_cli.ResClflags.width
~comments:(comments |> filterComments ~loc:item.psig_loc)
|> Utils.indent range.start.character
in
(structure, printSignatureItem)

let extractCodeActions ~path ~pos ~currentFile ~debug =
match Cmt.loadFullCmtFromPath ~path with
| Some full when Files.classifySourceFile currentFile = Res ->
let codeActions = ref [] in
match Files.classifySourceFile currentFile with
| Res ->
let structure, printExpr, printStructureItem =
parse ~filename:currentFile
parseImplementation ~filename:currentFile
in
let codeActions = ref [] in
AddTypeAnnotation.xform ~path ~pos ~full ~structure ~codeActions ~debug;
IfThenElse.xform ~pos ~codeActions ~printExpr ~path structure;
AddBracesToFn.xform ~pos ~codeActions ~path ~printStructureItem structure;
AddDocTemplate.Implementation.xform ~pos ~codeActions ~path
~printStructureItem ~structure;

(* This Code Action needs type info *)
let () =
match Cmt.loadFullCmtFromPath ~path with
| Some full ->
AddTypeAnnotation.xform ~path ~pos ~full ~structure ~codeActions ~debug
| None -> ()
in

!codeActions
| Resi ->
let signature, printSignatureItem = parseInterface ~filename:currentFile in
AddDocTemplate.Interface.xform ~pos ~codeActions ~path ~signature
~printSignatureItem;
!codeActions
| _ -> []
| Other -> []
20 changes: 20 additions & 0 deletions analysis/tests/not_compiled/DocTemplate.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type a = {a: int}
// ^xfm

type rec t = A | B
// ^xfm
and e = C
@unboxed type name = Name(string)
// ^xfm
let a = 1
// ^xfm
let inc = x => x + 1
// ^xfm
module T = {
// ^xfm
let b = 1
// ^xfm
}
@module("path")
external dirname: string => string = "dirname"
//^xfm
20 changes: 20 additions & 0 deletions analysis/tests/not_compiled/DocTemplate.resi
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type a = {a: int}
// ^xfm

type rec t = A | B
// ^xfm
and e = C
@unboxed type name = Name(string)
// ^xfm
let a: int
// ^xfm
let inc: int => int
// ^xfm
module T: {
// ^xfm
let b: int
// ^xfm
}
@module("path")
external dirname: string => string = "dirname"
//^xfm
85 changes: 85 additions & 0 deletions analysis/tests/not_compiled/expected/DocTemplate.res.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
Xform not_compiled/DocTemplate.res 3:3
can't find module DocTemplate
Hit: Add Documentation template
{"start": {"line": 3, "character": 0}, "end": {"line": 5, "character": 9}}
newText:
<--here
/**

*/
type rec t = A | B
// ^xfm
and e = C

Xform not_compiled/DocTemplate.res 6:15
can't find module DocTemplate
Hit: Add Documentation template
{"start": {"line": 6, "character": 0}, "end": {"line": 6, "character": 33}}
newText:
<--here
/**

*/
@unboxed
type name = Name(string)

Xform not_compiled/DocTemplate.res 8:4
can't find module DocTemplate
Hit: Add Documentation template
{"start": {"line": 8, "character": 0}, "end": {"line": 8, "character": 9}}
newText:
<--here
/**

*/
let a = 1

Xform not_compiled/DocTemplate.res 10:4
can't find module DocTemplate
Hit: Add Documentation template
{"start": {"line": 10, "character": 0}, "end": {"line": 10, "character": 20}}
newText:
<--here
/**

*/
let inc = x => x + 1

Xform not_compiled/DocTemplate.res 12:7
can't find module DocTemplate
Hit: Add Documentation template
{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
newText:
<--here
/**

*/
module T = {
// ^xfm
let b = 1
// ^xfm
}

Xform not_compiled/DocTemplate.res 14:6
can't find module DocTemplate
Hit: Add Documentation template
{"start": {"line": 14, "character": 2}, "end": {"line": 14, "character": 11}}
newText:
<--here
/**

*/
let b = 1

Xform not_compiled/DocTemplate.res 18:2
can't find module DocTemplate
Hit: Add Documentation template
{"start": {"line": 17, "character": 0}, "end": {"line": 18, "character": 46}}
newText:
<--here
/**

*/
@module("path")
external dirname: string => string = "dirname"

Loading