Skip to content

Fix references to file modules #254

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 11 commits into from
Jun 5, 2021
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
12 changes: 10 additions & 2 deletions analysis/src/Cli.ml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ API examples:
./rescript-editor-analysis.exe documentSymbol src/Foo.res
./rescript-editor-analysis.exe hover src/MyFile.res 10 2
./rescript-editor-analysis.exe references src/MyFile.res 10 2
./rescript-editor-analysis.exe rename src/MyFile.res 10 2 foo

Dev-time examples:
./rescript-editor-analysis.exe dump src/MyFile.res src/MyFile2.res
Expand Down Expand Up @@ -38,6 +39,10 @@ Options:

./rescript-editor-analysis.exe references src/MyFile.res 10 2

rename: rename all appearances of item in MyFile.res at line 10 column 2 with foo:

./rescript-editor-analysis.exe rename src/MyFile.res 10 2 foo

dump: for debugging, show all definitions and hovers for MyFile.res and MyFile.res:

./rescript-editor-analysis.exe dump src/Foo.res src/MyFile.res
Expand All @@ -50,8 +55,8 @@ Options:
let main () =
match Array.to_list Sys.argv with
| [_; "completion"; path; line; col; currentFile] ->
Commands.completion ~path ~line:(int_of_string line) ~col:(int_of_string col)
~currentFile
Commands.completion ~path ~line:(int_of_string line)
~col:(int_of_string col) ~currentFile
| [_; "definition"; path; line; col] ->
Commands.definition ~path ~line:(int_of_string line)
~col:(int_of_string col)
Expand All @@ -62,6 +67,9 @@ let main () =
| [_; "references"; path; line; col] ->
Commands.references ~path ~line:(int_of_string line)
~col:(int_of_string col)
| [_; "rename"; path; line; col; newName] ->
Commands.rename ~path ~line:(int_of_string line) ~col:(int_of_string col)
~newName
| [_; "test"; path] -> Commands.test ~path
| args when List.mem "-h" args || List.mem "--help" args -> prerr_endline help
| _ ->
Expand Down
81 changes: 81 additions & 0 deletions analysis/src/Commands.ml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,78 @@ let documentSymbol ~path =
in
print_endline ("[\n" ^ (allSymbols |> String.concat ",\n") ^ "\n]")

let rename ~path ~line ~col ~newName =
let uri = Uri2.fromPath path in
let result =
match ProcessCmt.getFullFromCmt ~uri with
| None -> Protocol.null
| Some full -> (
let pos = Utils.protocolLineColToCmtLoc ~line ~col in
match References.locItemForPos ~full pos with
| None -> Protocol.null
| Some locItem ->
let allReferences = References.allReferencesForLocItem ~full locItem in
Copy link
Collaborator

Choose a reason for hiding this comment

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

This list I think can be non-deterministic.
Some fixed order should be chosen (e.g. by sorting the results on Uri then loc).

Copy link
Collaborator

@cristianoc cristianoc Jun 4, 2021

Choose a reason for hiding this comment

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

It might be enough to make fileReferences inside extra in SharedTypes.ml store a set of locations instead of a list.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will check what's going wrong on a windows setup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems like there was an issue checking for interface file on windows

Copy link
Collaborator

Choose a reason for hiding this comment

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

Great thanks. I've also looked at removing some nondeterminism:
#263

Which I might add later on. To make debugging across platforms easier.

let referencesToToplevelModules, referencesToItems =
allReferences
|> List.fold_left
(fun acc (uri2, references) ->
(references |> List.map (fun loc -> (uri2, loc))) @ acc)
[]
|> List.partition (fun (_, loc) -> Utils.isTopLoc loc)
in
let fileRenames =
referencesToToplevelModules
|> List.map (fun (uri, _) ->
let path = Uri2.toPath uri in
let dir = Filename.dirname path in
let ext = Filename.extension path in
let sep = Filename.dir_sep in
let newPath = dir ^ sep ^ newName ^ ext in
Copy link
Collaborator

Choose a reason for hiding this comment

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

Better use Filename.concat instead of string concat with sep.

let newUri = Uri2.fromPath newPath in
Protocol.
{
kind = `rename;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: I think it's easier to expose a constructor function in the protocol that only takes 2 parameters: oldUri and newUri so the type of kind does not leak here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, I would just remove kind and use in the printing function only.

oldUri = uri |> Uri2.toString;
newUri = newUri |> Uri2.toString;
})
in
let textDocumentEdits =
let module StringMap = Misc.StringMap in
let textEditsByUri =
referencesToItems
|> List.map (fun (uri, loc) -> (Uri2.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

let test ~path =
Uri2.stripPath := true;
match Files.readFile path with
Expand Down Expand Up @@ -211,6 +283,15 @@ let test ~path =
| "doc" ->
print_endline ("DocumentSymbol " ^ path);
documentSymbol ~path
| "ren" ->
let newName = String.sub rest 4 (len - mlen - 4) in
let () =
print_endline
("Rename " ^ path ^ " " ^ string_of_int line ^ ":"
^ string_of_int col ^ " " ^ newName)
in

rename ~path ~line ~col ~newName
| "com" ->
print_endline
("Complete " ^ path ^ " " ^ string_of_int line ^ ":"
Expand Down
48 changes: 47 additions & 1 deletion analysis/src/Protocol.ml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ type location = {uri : string; range : range}

type documentSymbolItem = {name : string; kind : int; location : location}

type renameFile = {kind : [`rename]; oldUri : string; newUri : string}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion: remove kind.


type textEdit = {range : range; newText : string}

type optionalVersionedTextDocumentIdentifier = {
version : int option;
uri : string;
}

type textDocumentEdit = {
textDocument : optionalVersionedTextDocumentIdentifier;
edits : textEdit list;
}

let null = "null"

let array l = "[" ^ String.concat ", " l ^ "]"
Expand Down Expand Up @@ -52,7 +66,7 @@ let stringifyCompletionItem c =
let stringifyHover h =
Printf.sprintf {|{"contents": "%s"}|} (Json.escape h.contents)

let stringifyLocation h =
let stringifyLocation (h : location) =
Printf.sprintf {|{"uri": "%s", "range": %s}|} (Json.escape h.uri)
(stringifyRange h.range)

Expand All @@ -65,3 +79,35 @@ let stringifyDocumentSymbolItem i =
}|}
(Json.escape i.name) i.kind
(stringifyLocation i.location)

let stringifyRenameFile rf =
Printf.sprintf {|{
"kind": "%s",
"oldUri": "%s",
"newUri": "%s"
}|}
(match rf.kind with `rename -> "rename")
Copy link
Collaborator

Choose a reason for hiding this comment

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

this can go once field kind is removed, but the printing of "rename" stays.

(Json.escape rf.oldUri) (Json.escape rf.newUri)

let stringifyTextEdit te =
Printf.sprintf {|{
"range": %s,
"newText": "%s"
}|}
(stringifyRange te.range) (Json.escape te.newText)

let stringifyoptionalVersionedTextDocumentIdentifier td =
Printf.sprintf {|{
"version": %s,
"uri": "%s"
}|}
(match td.version with None -> null | Some v -> string_of_int v)
(Json.escape td.uri)

let stringifyTextDocumentEdit tde =
Printf.sprintf {|{
"textDocument": %s,
"edits": %s
}|}
(stringifyoptionalVersionedTextDocumentIdentifier tde.textDocument)
(tde.edits |> List.map stringifyTextEdit |> array)
30 changes: 22 additions & 8 deletions analysis/src/References.ml
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ let alternateDeclared ~(file : File.t) ~package declared tip =
maybeLog "Have both!!";
let resiUri = Uri2.fromPath resi in
let resUri = Uri2.fromPath res in
if resiUri = file.uri then
if Uri2.isInterface file.uri then
match ProcessCmt.getFullFromCmt ~uri:resUri with
| None -> None
| Some {file; extra} -> (
Expand Down Expand Up @@ -459,15 +459,29 @@ let forLocalStamp ~full:{file; extra; package} stamp tip =
let allReferencesForLocItem ~full:({file; package} as full) locItem =
match locItem.locType with
| TopLevelModule moduleName ->
let locs =
match Hashtbl.find_opt full.extra.fileReferences moduleName with
let otherModulesReferences =
package.projectFiles
|> Utils.filterMap (fun name ->
match ProcessCmt.fileForModule ~package name with
| None -> None
| Some file -> ProcessCmt.getFullFromCmt ~uri:file.uri)
|> List.map (fun full ->
match Hashtbl.find_opt full.extra.fileReferences moduleName with
| None -> []
| Some locs ->
locs
|> List.map (fun loc ->
(Uri2.fromPath loc.Location.loc_start.pos_fname, [loc])))
|> List.flatten
in
let targetModuleReferences =
match Hashtbl.find_opt package.pathsForModule moduleName with
| None -> []
| Some locs ->
locs
|> List.map (fun loc ->
(Uri2.fromPath loc.Location.loc_start.pos_fname, [loc]))
| Some paths ->
let moduleSrcToRef src = (Uri2.fromPath src, [Utils.topLoc src]) in
getSrc paths |> List.map moduleSrcToRef
in
locs
List.append targetModuleReferences otherModulesReferences
| Typed (_, _, NotFound) | LModule NotFound | Constant _ -> []
| TypeDefinition (_, _, stamp) -> forLocalStamp ~full stamp Type
| Typed (_, _, (LocalReference (stamp, tip) | Definition (stamp, tip)))
Expand Down
6 changes: 6 additions & 0 deletions analysis/src/SharedTypes.ml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ let showPaths paths =
| IntfAndImpl {cmti; resi; cmt; res} ->
Printf.sprintf "IntfAndImpl(%s, %s, %s, %s)" cmti resi cmt res

let getSrc p =
match p with
| Impl {res} -> [res]
| Namespace _ -> []
| IntfAndImpl {resi; res} -> [resi; res]

let getUri p =
match p with
| Impl {res} -> Uri2.fromPath res
Expand Down
6 changes: 6 additions & 0 deletions analysis/src/Utils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ let topLoc fname =
loc_ghost = false;
}

let isTopLoc (loc : Warnings.loc) =
Copy link
Collaborator

Choose a reason for hiding this comment

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

It might be possible to clean this up further, but looks OK for now.

let isTopPos (pos : Lexing.position) =
pos.pos_lnum = 1 && pos.pos_bol = 0 && pos.pos_cnum = 0
in
isTopPos loc.loc_start && isTopPos loc.loc_end && loc.loc_ghost = false

(**
* `startsWith(string, prefix)`
* true if the string starts with the prefix
Expand Down
17 changes: 17 additions & 0 deletions analysis/tests/src/Cross.res
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,20 @@ let crossRef2 = References.x
module Ref = References

let crossRef3 = References.x


let crossRefWithInterface = ReferencesWithInterface.x
// ^ref

let crossRefWithInterface2 = ReferencesWithInterface.x

module RefWithInterface = ReferencesWithInterface

let crossRefWithInterface3 = ReferencesWithInterface.x


let _ = RenameWithInterface.x
// ^ren RenameWithInterfacePrime

let _ = RenameWithInterface.x
// ^ren xPrime
2 changes: 2 additions & 0 deletions analysis/tests/src/ReferencesWithInterface.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let x = 2
// ^ref
2 changes: 2 additions & 0 deletions analysis/tests/src/ReferencesWithInterface.resi
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let x: int
// ^ref
11 changes: 11 additions & 0 deletions analysis/tests/src/Rename.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
let x = 12
// ^ren y

let a = x

let b = a

let c = x

let foo = (~xx) => xx + 1
// ^ren yy
2 changes: 2 additions & 0 deletions analysis/tests/src/RenameWithInterface.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let x = 2
// ^ren y
2 changes: 2 additions & 0 deletions analysis/tests/src/RenameWithInterface.resi
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let x: int
// ^ren y
Loading