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

Complete nested objects #318

Merged
merged 3 commits into from
Nov 23, 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
- Fix issue in JSX autocomplete when the component is declared external.
- Fix jump-to-definition for uncurried calls.
- Fix issue where values for autocomplete were pulled from implementations instead of interfaces.
- Add autocompletion for object access of the form foo["bar"].
- Add autocompletion for object access of the form `foo["x"]` and `foo["x"]["y"]["z"]`.
- Fix issue with autocomplete then punned props are used in JSX. E.g. `<M foo ...>`.
- Fix issue with JSX autocompletion not working after `foo=#variant`.
- Fix issue in JSX autocompletion where the `key` label would always appear.

## 1.1.3

Expand Down
60 changes: 37 additions & 23 deletions analysis/src/NewCompletions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -879,14 +879,16 @@ let processCompletable ~findItems ~full ~package ~rawOpens
mkItem ~name ~kind:4 ~deprecated:None ~detail:typString ~docstring:[]
in
let mkLabel (name, typ) = mkLabel_ name typ in
let keyLabel = mkLabel_ "key" "string" in
let keyLabels =
if Utils.startsWith "key" prefix then [mkLabel_ "key" "string"] else []
in
if domLabels = [] then []
else
(domLabels
|> List.filter (fun (name, _t) ->
Utils.startsWith name prefix && not (List.mem name identsSeen))
|> List.map mkLabel)
@ [keyLabel]
@ keyLabels
| Cjsx (componentPath, prefix, identsSeen) ->
let items = findItems ~exact:true (componentPath @ ["make"]) in
let labels =
Expand Down Expand Up @@ -936,14 +938,16 @@ let processCompletable ~findItems ~full ~package ~rawOpens
mkItem ~name ~kind:4 ~deprecated:None ~detail:typString ~docstring:[]
in
let mkLabel (name, typ) = mkLabel_ name (typ |> Shared.typeToString) in
let keyLabel = mkLabel_ "key" "string" in
let keyLabels =
if Utils.startsWith "key" prefix then [mkLabel_ "key" "string"] else []
in
if labels = [] then []
else
(labels
|> List.filter (fun (name, _t) ->
Utils.startsWith name prefix && not (List.mem name identsSeen))
|> List.map mkLabel)
@ [keyLabel]
@ keyLabels
| Cpath parts ->
let items = parts |> findItems ~exact:false in
(* TODO(#107): figure out why we're getting duplicates. *)
Expand Down Expand Up @@ -1145,28 +1149,38 @@ let processCompletable ~findItems ~full ~package ~rawOpens
|> List.filter (fun (name, _t) ->
Utils.startsWith name prefix && not (List.mem name identsSeen))
|> List.map mkLabel
| Cobj (lhs, prefix) ->
let labels =
| Cobj (lhs, path, prefix) ->
let rec getFields (texp : Types.type_expr) =
match texp.desc with
| Tfield (name, _, t1, t2) ->
let fields = t2 |> getFields in
(name, t1) :: fields
| Tlink te -> te |> getFields
| Tvar None -> []
| _ -> []
in
let rec getObj (t : Types.type_expr) =
match t.desc with
| Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> getObj t1
| Tobject (tObj, _) -> getFields tObj
| _ -> []
in
let fields =
match [lhs] |> findItems ~exact:true with
| {SharedTypes.item = Value typ} :: _ ->
let rec getFields (texp : Types.type_expr) =
match texp.desc with
| Tfield (name, _, t1, t2) ->
let fields = t2 |> getFields in
(name, t1) :: fields
| Tlink te -> te |> getFields
| Tvar None -> []
| _ -> []
in
let rec getObj (t : Types.type_expr) =
match t.desc with
| Tlink t1 | Tsubst t1 -> getObj t1
| Tobject (tObj, _) -> getFields tObj
| _ -> []
in
getObj typ
| {SharedTypes.item = Value typ} :: _ -> getObj typ
| _ -> []
in
let rec resolvePath fields path =
match path with
| name :: restPath -> (
match fields |> List.find_opt (fun (n, _) -> n = name) with
| Some (_, fieldType) ->
let innerFields = getObj fieldType in
resolvePath innerFields restPath
| None -> [])
| [] -> fields
in
let labels = resolvePath fields path in
let mkLabel_ name typString =
mkItem ~name ~kind:4 ~deprecated:None ~detail:typString ~docstring:[]
in
Expand Down
22 changes: 15 additions & 7 deletions analysis/src/PartialParser.ml
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ type completable =
| Cpath of string list (** e.g. ["M", "foo"] for M.foo *)
| Cjsx of string list * string * string list
(** E.g. (["M", "Comp"], "id", ["id1", "id2"]) for <M.Comp id1=... id2=... ... id *)
| Cobj of string * string (** e.g. ("foo", "bar") for foo['bar *)
| Cobj of string * string list * string
(** e.g. ("foo", ["a", "b"], "bar") for foo["a"]["b"]["bar" *)
| Cpipe of pipe * string (** E.g. ("x", "foo") for "x->foo" *)

let isLowercaseIdent id =
Expand Down Expand Up @@ -234,16 +235,23 @@ let findCompletable text offset =
in
let mkObj ~off ~partialName =
let off = skipWhite text off in
let rec loop i =
if i < 0 then Some (String.sub text 0 (i - 1))
let rec loop off path i =
if i < 0 then Some ([], String.sub text 0 (i - 1))
else
match text.[i] with
| 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' -> loop (i - 1)
| _ -> Some (String.sub text (i + 1) (off - i))
| 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' -> loop off path (i - 1)
| ']' when i > 1 && text.[i - 1] = '"' ->
let i0 = i - 2 in
let i1 = startOfLident text i0 in
let ident = String.sub text i1 (i0 - i1 + 1) in
if ident <> "" && i1 > 1 && text.[i1 - 1] = '"' && text.[i1 - 2] = '['
then loop (off - i + i1 - 3) (ident :: path) (i1 - 3)
else None
| _ -> Some (path, String.sub text (i + 1) (off - i))
in
match loop off with
match loop off [] off with
| None -> None
| Some lhs -> Some (Cobj (lhs, partialName))
| Some (path, lhs) -> Some (Cobj (lhs, path, partialName))
in

let suffix i = String.sub text (i + 1) (offset - (i + 1)) in
Expand Down
4 changes: 4 additions & 0 deletions analysis/tests/src/Completion.res
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,7 @@ let _ = Lib.foo(//~age,
let someObj = {"name": "a", "age": 32}

//^com someObj["a

let nestedObj = {"x": {"y": {"name": "a", "age": 32}}}

//^com nestedObj["x"]["y"]["
26 changes: 20 additions & 6 deletions analysis/tests/src/expected/Completion.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -446,12 +446,6 @@ Complete tests/src/Completion.res 52:2
"tags": [],
"detail": "option<int>",
"documentation": null
}, {
"label": "key",
"kind": 4,
"tags": [],
"detail": "string",
"documentation": null
}]

DocumentSymbol tests/src/Completion.res
Expand Down Expand Up @@ -540,6 +534,11 @@ DocumentSymbol tests/src/Completion.res
"name": "someObj",
"kind": 19,
"location": {"uri": "Completion.res", "range": {"start": {"line": 71, "character": 4}, "end": {"line": 71, "character": 11}}}
},
{
"name": "nestedObj",
"kind": 19,
"location": {"uri": "Completion.res", "range": {"start": {"line": 75, "character": 4}, "end": {"line": 75, "character": 13}}}
}
]

Expand Down Expand Up @@ -612,3 +611,18 @@ Complete tests/src/Completion.res 72:2
"documentation": null
}]

Complete tests/src/Completion.res 76:2
[{
"label": "age",
"kind": 4,
"tags": [],
"detail": "int",
"documentation": null
}, {
"label": "name",
"kind": 4,
"tags": [],
"detail": "string",
"documentation": null
}]

6 changes: 0 additions & 6 deletions analysis/tests/src/expected/Div.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,5 @@ Complete tests/src/Div.res 3:3
"tags": [],
"detail": "{\"__html\": string}",
"documentation": null
}, {
"label": "key",
"kind": 4,
"tags": [],
"detail": "string",
"documentation": null
}]

12 changes: 0 additions & 12 deletions analysis/tests/src/expected/Jsx.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,6 @@ Complete tests/src/Jsx.res 9:2
"tags": [],
"detail": "option<string>",
"documentation": null
}, {
"label": "key",
"kind": 4,
"tags": [],
"detail": "string",
"documentation": null
}]

Complete tests/src/Jsx.res 11:2
Expand Down Expand Up @@ -182,12 +176,6 @@ Complete tests/src/Jsx.res 52:2
"tags": [],
"detail": "option<string>",
"documentation": null
}, {
"label": "key",
"kind": 4,
"tags": [],
"detail": "string",
"documentation": null
}]

Complete tests/src/Jsx.res 54:2
Expand Down