diff --git a/CHANGELOG.md b/CHANGELOG.md index c2b8bec39..3f5d548b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. ``. - 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 diff --git a/analysis/src/NewCompletions.ml b/analysis/src/NewCompletions.ml index b69a43af6..091d104f8 100644 --- a/analysis/src/NewCompletions.ml +++ b/analysis/src/NewCompletions.ml @@ -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 = @@ -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. *) @@ -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 diff --git a/analysis/src/PartialParser.ml b/analysis/src/PartialParser.ml index e7cb8da88..9917ea0c7 100644 --- a/analysis/src/PartialParser.ml +++ b/analysis/src/PartialParser.ml @@ -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 foo" *) let isLowercaseIdent id = @@ -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 diff --git a/analysis/tests/src/Completion.res b/analysis/tests/src/Completion.res index b8457f67b..ff845d5cc 100644 --- a/analysis/tests/src/Completion.res +++ b/analysis/tests/src/Completion.res @@ -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"][" diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index 0737535ab..f49246691 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -446,12 +446,6 @@ Complete tests/src/Completion.res 52:2 "tags": [], "detail": "option", "documentation": null - }, { - "label": "key", - "kind": 4, - "tags": [], - "detail": "string", - "documentation": null }] DocumentSymbol tests/src/Completion.res @@ -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}}} } ] @@ -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 + }] + diff --git a/analysis/tests/src/expected/Div.res.txt b/analysis/tests/src/expected/Div.res.txt index 0c0057dba..2d9a106ab 100644 --- a/analysis/tests/src/expected/Div.res.txt +++ b/analysis/tests/src/expected/Div.res.txt @@ -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 }] diff --git a/analysis/tests/src/expected/Jsx.res.txt b/analysis/tests/src/expected/Jsx.res.txt index 464155fce..6c6ff43d3 100644 --- a/analysis/tests/src/expected/Jsx.res.txt +++ b/analysis/tests/src/expected/Jsx.res.txt @@ -35,12 +35,6 @@ Complete tests/src/Jsx.res 9:2 "tags": [], "detail": "option", "documentation": null - }, { - "label": "key", - "kind": 4, - "tags": [], - "detail": "string", - "documentation": null }] Complete tests/src/Jsx.res 11:2 @@ -182,12 +176,6 @@ Complete tests/src/Jsx.res 52:2 "tags": [], "detail": "option", "documentation": null - }, { - "label": "key", - "kind": 4, - "tags": [], - "detail": "string", - "documentation": null }] Complete tests/src/Jsx.res 54:2