diff --git a/CHANGELOG.md b/CHANGELOG.md index 752439edc..a054d487c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ - Add support from completing polyvariants as values. https://github.com/rescript-lang/rescript-vscode/pull/669 - Add support for completion in patterns. https://github.com/rescript-lang/rescript-vscode/pull/670 - Add support for pattern completion of unsaved tuples. https://github.com/rescript-lang/rescript-vscode/pull/679 +- Add support for completion in typed expressions. https://github.com/rescript-lang/rescript-vscode/pull/682 +- Complete for `React.element` creator functions (`React.string` etc) when in JSX context. https://github.com/rescript-lang/rescript-vscode/pull/681 +- Handle optional record fields in expression/pattern completion. https://github.com/rescript-lang/rescript-vscode/pull/691 +- Expand options in completion to make working with options a bit more ergonomic. https://github.com/rescript-lang/rescript-vscode/pull/690 #### :nail_care: Polish @@ -30,6 +34,7 @@ #### :bug: Bug Fix - Highlight `catch` like a keyword https://github.com/rescript-lang/rescript-vscode/pull/677 +- Make signature help work in calls nested inside of other calls. https://github.com/rescript-lang/rescript-vscode/pull/687 ## v1.10.0 diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 69a66cdba..ac8dc013b 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -931,6 +931,30 @@ let findLocalCompletionsForValuesAndConstructors ~(localTables : LocalTables.t) (processLocalModule ~prefix ~exact ~env ~localTables); List.rev_append localTables.resultRev valuesFromOpens +let findLocalCompletionsForValues ~(localTables : LocalTables.t) ~env ~prefix + ~exact ~opens ~scope = + localTables |> LocalTables.populateValues ~env; + localTables |> LocalTables.populateModules ~env; + scope + |> Scope.iterValuesBeforeFirstOpen + (processLocalValue ~prefix ~exact ~env ~localTables); + scope + |> Scope.iterModulesBeforeFirstOpen + (processLocalModule ~prefix ~exact ~env ~localTables); + + let valuesFromOpens = + getItemsFromOpens ~opens ~localTables ~prefix ~exact + ~completionContext:Value + in + + scope + |> Scope.iterValuesAfterFirstOpen + (processLocalValue ~prefix ~exact ~env ~localTables); + scope + |> Scope.iterModulesAfterFirstOpen + (processLocalModule ~prefix ~exact ~env ~localTables); + List.rev_append localTables.resultRev valuesFromOpens + let findLocalCompletionsForTypes ~(localTables : LocalTables.t) ~env ~prefix ~exact ~opens ~scope = localTables |> LocalTables.populateTypes ~env; @@ -1098,6 +1122,28 @@ let extractFunctionType ~env ~package typ = in loop ~env [] typ +let getComplementaryCompletionsForTypedValue ~opens ~allFiles ~scope ~env prefix + = + let exact = false in + let localCompletionsWithOpens = + let localTables = LocalTables.create () in + findLocalCompletionsForValues ~localTables ~env ~prefix ~exact ~opens ~scope + in + let fileModules = + allFiles |> FileSet.elements + |> Utils.filterMap (fun name -> + if + checkName name ~prefix ~exact + && not + (* TODO complete the namespaced name too *) + (String.contains name '-') + then + Some + (Completion.create ~name ~env ~kind:(Completion.FileModule name)) + else None) + in + localCompletionsWithOpens @ fileModules + let getCompletionsForPath ~package ~opens ~allFiles ~pos ~exact ~scope ~completionContext ~env path = match path with @@ -1193,6 +1239,111 @@ let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~full ~debug = | _ -> None) | _ -> None +let getJsxLabels ~componentPath ~findTypeOfValue ~package = + match componentPath @ ["make"] |> findTypeOfValue with + | Some (typ, make_env) -> + let rec getFieldsV3 (texp : Types.type_expr) = + match texp.desc with + | Tfield (name, _, t1, t2) -> + let fields = t2 |> getFieldsV3 in + if name = "children" then fields else (name, t1, make_env) :: fields + | Tlink te | Tsubst te | Tpoly (te, []) -> te |> getFieldsV3 + | Tvar None -> [] + | _ -> [] + in + let getFieldsV4 ~path ~typeArgs = + match References.digConstructor ~env:make_env ~package path with + | Some + ( env, + { + item = + { + decl = + { + type_kind = Type_record (labelDecls, _repr); + type_params = typeParams; + }; + }; + } ) -> + labelDecls + |> List.map (fun (ld : Types.label_declaration) -> + let name = Ident.name ld.ld_id in + let t = ld.ld_type |> instantiateType ~typeParams ~typeArgs in + (name, t, env)) + | _ -> [] + in + let rec getLabels (t : Types.type_expr) = + match t.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> getLabels t1 + | Tarrow + ( Nolabel, + { + desc = + ( Tconstr (* Js.t *) (_, [{desc = Tobject (tObj, _)}], _) + | Tobject (tObj, _) ); + }, + _, + _ ) -> + (* JSX V3 *) + getFieldsV3 tObj + | Tarrow (Nolabel, {desc = Tconstr (path, typeArgs, _)}, _, _) + when Path.last path = "props" -> + (* JSX V4 *) + getFieldsV4 ~path ~typeArgs + | Tconstr + ( clPath, + [ + { + desc = + ( Tconstr (* Js.t *) (_, [{desc = Tobject (tObj, _)}], _) + | Tobject (tObj, _) ); + }; + _; + ], + _ ) + when Path.name clPath = "React.componentLike" -> + (* JSX V3 external or interface *) + getFieldsV3 tObj + | Tconstr (clPath, [{desc = Tconstr (path, typeArgs, _)}; _], _) + when Path.name clPath = "React.componentLike" + && Path.last path = "props" -> + (* JSX V4 external or interface *) + getFieldsV4 ~path ~typeArgs + | _ -> [] + in + typ |> getLabels + | None -> [] + +let getArgs ~env (t : Types.type_expr) ~full = + let rec getArgsLoop ~env (t : Types.type_expr) ~full ~currentArgumentPosition + = + match t.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> + getArgsLoop ~full ~env ~currentArgumentPosition t1 + | Tarrow (Labelled l, tArg, tRet, _) -> + (SharedTypes.Completable.Labelled l, tArg) + :: getArgsLoop ~full ~env ~currentArgumentPosition tRet + | Tarrow (Optional l, tArg, tRet, _) -> + (Optional l, tArg) :: getArgsLoop ~full ~env ~currentArgumentPosition tRet + | Tarrow (Nolabel, tArg, tRet, _) -> + (Unlabelled {argumentPosition = currentArgumentPosition}, tArg) + :: getArgsLoop ~full ~env + ~currentArgumentPosition:(currentArgumentPosition + 1) + tRet + | Tconstr (path, typeArgs, _) -> ( + match References.digConstructor ~env ~package:full.package path with + | Some + ( env, + { + item = {decl = {type_manifest = Some t1; type_params = typeParams}}; + } ) -> + let t1 = t1 |> instantiateType ~typeParams ~typeArgs in + getArgsLoop ~full ~env ~currentArgumentPosition t1 + | _ -> []) + | _ -> [] + in + t |> getArgsLoop ~env ~full ~currentArgumentPosition:0 + let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env ~exact ~scope (contextPath : Completable.contextPath) = let package = full.package in @@ -1326,7 +1477,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env else None) | None -> []) | None -> []) - | CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc} -> ( + | CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc; inJsx} -> ( match cp |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env @@ -1433,7 +1584,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env | None -> None in match completionPath with - | Some completionPath -> + | Some completionPath -> ( let completionPathMinusOpens = completionPath |> removeRawOpens package.opens @@ -1448,14 +1599,45 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env |> getCompletionsForPath ~completionContext:Value ~exact:false ~package ~opens ~allFiles ~pos ~env ~scope in - completions - |> List.map (fun (completion : Completion.t) -> - { - completion with - name = completionName completion.name; - env - (* Restore original env for the completion after x->foo()... *); - }) + let completions = + completions + |> List.map (fun (completion : Completion.t) -> + { + completion with + name = completionName completion.name; + env + (* Restore original env for the completion after x->foo()... *); + }) + in + (* We add React element functions to the completion if we're in a JSX context *) + let forJsxCompletion = + if inJsx then + match getTypePath typ with + | Some (Path.Pident id) when Ident.name id = "int" -> Some "int" + | Some (Path.Pident id) when Ident.name id = "float" -> Some "float" + | Some (Path.Pident id) when Ident.name id = "string" -> + Some "string" + | Some (Path.Pident id) when Ident.name id = "array" -> Some "array" + | _ -> None + else None + in + match forJsxCompletion with + | Some builtinNameToComplete + when checkName builtinNameToComplete ~prefix:funNamePrefix + ~exact:false -> + [ + Completion.createWithSnippet + ~name:("React." ^ builtinNameToComplete) + ~kind:(Value typ) ~env ~sortText:"A" + ~docstring: + [ + "Turns `" ^ builtinNameToComplete + ^ "` into `React.element` so it can be used inside of JSX."; + ] + (); + ] + @ completions + | _ -> completions) | None -> []) | None -> []) | CTuple ctxPaths -> @@ -1477,6 +1659,59 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env ~kind:(Completion.Value (Ctype.newty (Ttuple typeExrps))); ] else [] + | CJsxPropValue {pathToComponent; propName} -> ( + let findTypeOfValue path = + path + |> getCompletionsForPath ~completionContext:Value ~exact:true ~package + ~opens ~allFiles ~pos ~env ~scope + |> completionsGetTypeEnv + in + let targetLabel = + getJsxLabels ~componentPath:pathToComponent ~findTypeOfValue ~package + |> List.find_opt (fun (label, _, _) -> label = propName) + in + match targetLabel with + | None -> [] + | Some (_, typ, env) -> + [ + Completion.create ~name:"dummy" ~env + ~kind:(Completion.Value (Utils.unwrapIfOption typ)); + ]) + | CArgument {functionContextPath; argumentLabel} -> ( + let labels, env = + match + functionContextPath + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos + ~env ~exact:true ~scope + |> completionsGetTypeEnv + with + | Some (typ, env) -> (typ |> getArgs ~full ~env, env) + | None -> ([], env) + in + let targetLabel = + labels + |> List.find_opt (fun (label, _) -> + match argumentLabel with + | Unlabelled _ -> label = argumentLabel + | Labelled name | Optional name -> ( + match label with + | (Labelled n | Optional n) when name = n -> true + | _ -> false)) + in + let expandOption = + match targetLabel with + | None | Some ((Unlabelled _ | Labelled _), _) -> false + | Some (Optional _, _) -> true + in + match targetLabel with + | None -> [] + | Some (_, typ) -> + [ + Completion.create ~name:"dummy" ~env + ~kind: + (Completion.Value + (if expandOption then Utils.unwrapIfOption typ else typ)); + ]) let getOpens ~debug ~rawOpens ~package ~env = if debug && rawOpens <> [] then @@ -1504,36 +1739,6 @@ let getOpens ~debug ~rawOpens ~package ~env = (* Last open takes priority *) List.rev resolvedOpens -let getArgs ~env (t : Types.type_expr) ~full = - let rec getArgsLoop ~env (t : Types.type_expr) ~full ~currentArgumentPosition - = - match t.desc with - | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> - getArgsLoop ~full ~env ~currentArgumentPosition t1 - | Tarrow (Labelled l, tArg, tRet, _) -> - (SharedTypes.Completable.Labelled l, tArg) - :: getArgsLoop ~full ~env ~currentArgumentPosition tRet - | Tarrow (Optional l, tArg, tRet, _) -> - (Optional l, tArg) :: getArgsLoop ~full ~env ~currentArgumentPosition tRet - | Tarrow (Nolabel, tArg, tRet, _) -> - (Unlabelled {argumentPosition = currentArgumentPosition}, tArg) - :: getArgsLoop ~full ~env - ~currentArgumentPosition:(currentArgumentPosition + 1) - tRet - | Tconstr (path, typeArgs, _) -> ( - match References.digConstructor ~env ~package:full.package path with - | Some - ( env, - { - item = {decl = {type_manifest = Some t1; type_params = typeParams}}; - } ) -> - let t1 = t1 |> instantiateType ~typeParams ~typeArgs in - getArgsLoop ~full ~env ~currentArgumentPosition t1 - | _ -> []) - | _ -> [] - in - t |> getArgsLoop ~env ~full ~currentArgumentPosition:0 - (** Pulls out a type we can complete from a type expr. *) let rec extractType ~env ~package (t : Types.type_expr) = match t.desc with @@ -1543,6 +1748,7 @@ let rec extractType ~env ~package (t : Types.type_expr) = | Tconstr (Path.Pident {name = "array"}, [payloadTypeExpr], _) -> Some (Tarray (env, payloadTypeExpr)) | Tconstr (Path.Pident {name = "bool"}, [], _) -> Some (Tbool env) + | Tconstr (Path.Pident {name = "string"}, [], _) -> Some (Tstring env) | Tconstr (path, _, _) -> ( match References.digConstructor ~env ~package path with | Some (env, {item = {decl = {type_manifest = Some t1}}}) -> @@ -1590,219 +1796,160 @@ let printConstructorArgs argsLen ~asSnippet = if List.length !args > 0 then "(" ^ (!args |> String.concat ", ") ^ ")" else "" -let completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix - ~expandOption ~includeLocalValues ~completionContext = - let namesUsed = Hashtbl.create 10 in - let rec completeTypedValueInner t ~env ~full ~prefix ~expandOption = - let items = - match t |> extractType ~env ~package:full.package with - | Some (Toption (env, typ)) when expandOption -> - typ |> completeTypedValueInner ~env ~full ~prefix ~expandOption:false - | Some (Tbool env) -> - [ - Completion.create ~name:"true" - ~kind:(Label (t |> Shared.typeToString)) - ~env; - Completion.create ~name:"false" - ~kind:(Label (t |> Shared.typeToString)) - ~env; - ] - |> filterItems ~prefix - | Some (Tvariant {env; constructors; variantDecl; variantName}) -> - constructors - |> List.map (fun (constructor : Constructor.t) -> - Completion.createWithSnippet - ~name: - (constructor.cname.txt - ^ printConstructorArgs - (List.length constructor.args) - ~asSnippet:false) - ~insertText: - (constructor.cname.txt - ^ printConstructorArgs - (List.length constructor.args) - ~asSnippet:true) - ~kind: - (Constructor - ( constructor, - variantDecl |> Shared.declToString variantName )) - ~env ()) - |> filterItems ~prefix - | Some (Tpolyvariant {env; constructors; typeExpr}) -> - constructors - |> List.map (fun (constructor : polyVariantConstructor) -> - Completion.createWithSnippet - ~name: - ("#" ^ constructor.name - ^ printConstructorArgs - (List.length constructor.args) - ~asSnippet:false) - ~insertText: - ((if Utils.startsWith prefix "#" then "" else "#") - ^ constructor.name - ^ printConstructorArgs - (List.length constructor.args) - ~asSnippet:true) - ~kind: - (PolyvariantConstructor - (constructor, typeExpr |> Shared.typeToString)) - ~env ()) - |> filterItems ~prefix - | Some (Toption (env, t)) -> - [ - Completion.create ~name:"None" - ~kind:(Label (t |> Shared.typeToString)) - ~env; - Completion.createWithSnippet ~name:"Some(_)" - ~kind:(Label (t |> Shared.typeToString)) - ~env ~insertText:"Some(${1:_})" (); - ] - |> filterItems ~prefix - | Some (Tuple (env, exprs, typ)) -> - let numExprs = List.length exprs in - [ - Completion.createWithSnippet - ~name:(printConstructorArgs numExprs ~asSnippet:false) - ~insertText:(printConstructorArgs numExprs ~asSnippet:true) - ~kind:(Value typ) ~env (); - ] - | Some (Trecord {env; fields; typeExpr}) -> ( - (* As we're completing for a record, we'll need a hint (completionContext) - here to figure out whether we should complete for a record field, or - the record body itself. *) - match completionContext with - | Some (Completable.RecordField {seenFields}) -> - fields - |> List.filter (fun (field : field) -> - List.mem field.fname.txt seenFields = false) - |> List.map (fun (field : field) -> - Completion.create ~name:field.fname.txt - ~kind:(Field (field, typeExpr |> Shared.typeToString)) - ~env) - |> filterItems ~prefix - | None -> - [ - Completion.createWithSnippet ~name:"{}" - ~insertText:(if !Cfg.supportsSnippets then "{$0}" else "{}") - ~sortText:"a" ~kind:(Value typeExpr) ~env (); - ]) - | Some (Tarray (env, typeExpr)) -> +let rec completeTypedValue (t : Types.type_expr) ~env ~full ~prefix + ~completionContext = + match t |> extractType ~env ~package:full.package with + | Some (Tbool env) -> + [ + Completion.create ~name:"true" + ~kind:(Label (t |> Shared.typeToString)) + ~env; + Completion.create ~name:"false" + ~kind:(Label (t |> Shared.typeToString)) + ~env; + ] + |> filterItems ~prefix + | Some (Tvariant {env; constructors; variantDecl; variantName}) -> + constructors + |> List.map (fun (constructor : Constructor.t) -> + Completion.createWithSnippet + ~name: + (constructor.cname.txt + ^ printConstructorArgs + (List.length constructor.args) + ~asSnippet:false) + ~insertText: + (constructor.cname.txt + ^ printConstructorArgs + (List.length constructor.args) + ~asSnippet:true) + ~kind: + (Constructor + (constructor, variantDecl |> Shared.declToString variantName)) + ~env ()) + |> filterItems ~prefix + | Some (Tpolyvariant {env; constructors; typeExpr}) -> + constructors + |> List.map (fun (constructor : polyVariantConstructor) -> + Completion.createWithSnippet + ~name: + ("#" ^ constructor.name + ^ printConstructorArgs + (List.length constructor.args) + ~asSnippet:false) + ~insertText: + ((if Utils.startsWith prefix "#" then "" else "#") + ^ constructor.name + ^ printConstructorArgs + (List.length constructor.args) + ~asSnippet:true) + ~kind: + (PolyvariantConstructor + (constructor, typeExpr |> Shared.typeToString)) + ~env ()) + |> filterItems ~prefix + | Some (Toption (env, t)) -> + let innerType = Utils.unwrapIfOption t in + let expandedCompletions = + innerType + |> completeTypedValue ~env ~full ~prefix ~completionContext + |> List.map (fun (c : Completion.t) -> + { + c with + name = "Some(" ^ c.name ^ ")"; + sortText = None; + insertText = + (match c.insertText with + | None -> None + | Some insertText -> Some ("Some(" ^ insertText ^ ")")); + }) + in + [ + Completion.create ~name:"None" + ~kind:(Label (t |> Shared.typeToString)) + ~env; + Completion.createWithSnippet ~name:"Some(_)" + ~kind:(Label (t |> Shared.typeToString)) + ~env ~insertText:"Some(${1:_})" (); + ] + @ expandedCompletions + |> filterItems ~prefix + | Some (Tuple (env, exprs, typ)) -> + let numExprs = List.length exprs in + [ + Completion.createWithSnippet + ~name:(printConstructorArgs numExprs ~asSnippet:false) + ~insertText:(printConstructorArgs numExprs ~asSnippet:true) + ~kind:(Value typ) ~env (); + ] + | Some (Trecord {env; fields; typeExpr}) -> ( + (* As we're completing for a record, we'll need a hint (completionContext) + here to figure out whether we should complete for a record field, or + the record body itself. *) + match completionContext with + | Some (Completable.RecordField {seenFields}) -> + fields + |> List.filter (fun (field : field) -> + List.mem field.fname.txt seenFields = false) + |> List.map (fun (field : field) -> + Completion.create ~name:field.fname.txt + ~kind:(Field (field, typeExpr |> Shared.typeToString)) + ~env) + |> filterItems ~prefix + | None -> + if prefix = "" then [ - Completion.createWithSnippet ~name:"[]" - ~insertText:(if !Cfg.supportsSnippets then "[$0]" else "[]") - ~sortText:"a" ~kind:(Value typeExpr) ~env (); + Completion.createWithSnippet ~name:"{}" + ~insertText:(if !Cfg.supportsSnippets then "{$0}" else "{}") + ~sortText:"A" ~kind:(Value typeExpr) ~env (); ] - | _ -> [] - in - (* Include all values and modules in completion if there's a prefix, not otherwise *) - if prefix = "" || includeLocalValues = false then items - else - items - @ completionForExportedValues ~env:envWhereCompletionStarted ~prefix - ~exact:false ~namesUsed - @ completionForExportedModules ~env:envWhereCompletionStarted ~prefix - ~exact:false ~namesUsed - in - completeTypedValueInner ~env ~full ~prefix ~expandOption - -let getJsxLabels ~componentPath ~findTypeOfValue ~package = - match componentPath @ ["make"] |> findTypeOfValue with - | Some (typ, make_env) -> - let rec getFieldsV3 (texp : Types.type_expr) = - match texp.desc with - | Tfield (name, _, t1, t2) -> - let fields = t2 |> getFieldsV3 in - if name = "children" then fields else (name, t1, make_env) :: fields - | Tlink te | Tsubst te | Tpoly (te, []) -> te |> getFieldsV3 - | Tvar None -> [] - | _ -> [] - in - let getFieldsV4 ~path ~typeArgs = - match References.digConstructor ~env:make_env ~package path with - | Some - ( env, - { - item = - { - decl = - { - type_kind = Type_record (labelDecls, _repr); - type_params = typeParams; - }; - }; - } ) -> - labelDecls - |> List.map (fun (ld : Types.label_declaration) -> - let name = Ident.name ld.ld_id in - let t = ld.ld_type |> instantiateType ~typeParams ~typeArgs in - (name, t, env)) - | _ -> [] - in - let rec getLabels (t : Types.type_expr) = - match t.desc with - | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> getLabels t1 - | Tarrow - ( Nolabel, - { - desc = - ( Tconstr (* Js.t *) (_, [{desc = Tobject (tObj, _)}], _) - | Tobject (tObj, _) ); - }, - _, - _ ) -> - (* JSX V3 *) - getFieldsV3 tObj - | Tarrow (Nolabel, {desc = Tconstr (path, typeArgs, _)}, _, _) - when Path.last path = "props" -> - (* JSX V4 *) - getFieldsV4 ~path ~typeArgs - | Tconstr - ( clPath, - [ - { - desc = - ( Tconstr (* Js.t *) (_, [{desc = Tobject (tObj, _)}], _) - | Tobject (tObj, _) ); - }; - _; - ], - _ ) - when Path.name clPath = "React.componentLike" -> - (* JSX V3 external or interface *) - getFieldsV3 tObj - | Tconstr (clPath, [{desc = Tconstr (path, typeArgs, _)}; _], _) - when Path.name clPath = "React.componentLike" - && Path.last path = "props" -> - (* JSX V4 external or interface *) - getFieldsV4 ~path ~typeArgs - | _ -> [] - in - typ |> getLabels - | None -> [] + else []) + | Some (Tarray (env, typeExpr)) -> + if prefix = "" then + [ + Completion.createWithSnippet ~name:"[]" + ~insertText:(if !Cfg.supportsSnippets then "[$0]" else "[]") + ~sortText:"A" ~kind:(Value typeExpr) ~env (); + ] + else [] + | Some (Tstring env) -> + if prefix = "" then + [ + Completion.createWithSnippet ~name:"\"\"" + ~insertText:(if !Cfg.supportsSnippets then "\"$0\"" else "\"\"") + ~sortText:"A" + ~kind: + (Value (Ctype.newconstr (Path.Pident (Ident.create "string")) [])) + ~env (); + ] + else [] + | _ -> [] -(** This moves through a pattern via a set of instructions, trying to resolve the type at the end of the pattern. *) -let rec resolveNestedPattern typ ~env ~package ~nested = +(** This moves through a nested path via a set of instructions, trying to resolve the type at the end of the path. *) +let rec resolveNested typ ~env ~package ~nested = match nested with | [] -> Some (typ, env, None) | patternPath :: nested -> ( match (patternPath, typ |> extractType ~env ~package) with - | Completable.PTupleItem {itemNum}, Some (Tuple (env, tupleItems, _)) -> ( + | Completable.NTupleItem {itemNum}, Some (Tuple (env, tupleItems, _)) -> ( match List.nth_opt tupleItems itemNum with | None -> None - | Some typ -> typ |> resolveNestedPattern ~env ~package ~nested) - | PFollowRecordField {fieldName}, Some (Trecord {env; fields}) -> ( + | Some typ -> typ |> resolveNested ~env ~package ~nested) + | NFollowRecordField {fieldName}, Some (Trecord {env; fields}) -> ( match fields |> List.find_opt (fun (field : field) -> field.fname.txt = fieldName) with | None -> None - | Some {typ} -> typ |> resolveNestedPattern ~env ~package ~nested) - | PRecordBody {seenFields}, Some (Trecord {env; typeExpr}) -> + | Some {typ; optional} -> + let typ = if optional then Utils.unwrapIfOption typ else typ in + typ |> resolveNested ~env ~package ~nested) + | NRecordBody {seenFields}, Some (Trecord {env; typeExpr}) -> Some (typeExpr, env, Some (Completable.RecordField {seenFields})) - | ( PVariantPayload {constructorName = "Some"; itemNum = 0}, + | ( NVariantPayload {constructorName = "Some"; itemNum = 0}, Some (Toption (env, typ)) ) -> - typ |> resolveNestedPattern ~env ~package ~nested - | ( PVariantPayload {constructorName; itemNum}, + typ |> resolveNested ~env ~package ~nested + | ( NVariantPayload {constructorName; itemNum}, Some (Tvariant {env; constructors}) ) -> ( match constructors @@ -1813,8 +1960,8 @@ let rec resolveNestedPattern typ ~env ~package ~nested = | Some constructor -> ( match List.nth_opt constructor.args itemNum with | None -> None - | Some (typ, _) -> typ |> resolveNestedPattern ~env ~package ~nested)) - | ( PPolyvariantPayload {constructorName; itemNum}, + | Some (typ, _) -> typ |> resolveNested ~env ~package ~nested)) + | ( NPolyvariantPayload {constructorName; itemNum}, Some (Tpolyvariant {env; constructors}) ) -> ( match constructors @@ -1825,9 +1972,9 @@ let rec resolveNestedPattern typ ~env ~package ~nested = | Some constructor -> ( match List.nth_opt constructor.args itemNum with | None -> None - | Some typ -> typ |> resolveNestedPattern ~env ~package ~nested)) - | PArray, Some (Tarray (env, typ)) -> - typ |> resolveNestedPattern ~env ~package ~nested + | Some typ -> typ |> resolveNested ~env ~package ~nested)) + | NArray, Some (Tarray (env, typ)) -> + typ |> resolveNested ~env ~package ~nested | _ -> None) let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover @@ -1882,18 +2029,6 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover && (forHover || not (List.mem name identsSeen))) |> List.map mkLabel) @ keyLabels - | CjsxPropValue {pathToComponent; prefix; propName} -> ( - let targetLabel = - getJsxLabels ~componentPath:pathToComponent ~findTypeOfValue ~package - |> List.find_opt (fun (label, _, _) -> label = propName) - in - let envWhereCompletionStarted = env in - match targetLabel with - | None -> [] - | Some (_, typ, env) -> - typ - |> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix - ~expandOption:true ~includeLocalValues:true ~completionContext:None) | Cdecorator prefix -> let mkDecorator (name, docstring) = {(Completion.create ~name ~kind:(Label "") ~env) with docstring} @@ -2131,38 +2266,6 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i in (dec2, doc)) |> List.map mkDecorator - | Cargument {functionContextPath; argumentLabel; prefix} -> ( - let envWhereCompletionStarted = env in - let labels = - match - functionContextPath - |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:true ~scope - |> completionsGetTypeEnv - with - | Some (typ, _env) -> typ |> getArgs ~full ~env - | None -> [] - in - let targetLabel = - labels - |> List.find_opt (fun (label, _) -> - match argumentLabel with - | Unlabelled _ -> label = argumentLabel - | Labelled name | Optional name -> ( - match label with - | (Labelled n | Optional n) when name = n -> true - | _ -> false)) - in - match targetLabel with - | None -> [] - | Some (Optional _, typ) -> - typ - |> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix - ~expandOption:true ~includeLocalValues:true ~completionContext:None - | Some ((Unlabelled _ | Labelled _), typ) -> - typ - |> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix - ~expandOption:false ~includeLocalValues:true ~completionContext:None) | CnamedArg (cp, prefix, identsSeen) -> let labels = match @@ -2192,7 +2295,7 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i Utils.startsWith name prefix && (forHover || not (List.mem name identsSeen))) |> List.map mkLabel - | Cpattern {typ; prefix; nested; fallback} -> ( + | Cpattern {contextPath; prefix; nested; fallback} -> ( let fallbackOrEmpty ?items () = match (fallback, items) with | Some fallback, (None | Some []) -> @@ -2200,21 +2303,54 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i | _, Some items -> items | None, None -> [] in - let envWhereCompletionStarted = env in match - typ + contextPath |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env ~exact:true ~scope |> completionsGetTypeEnv with | Some (typ, env) -> ( - match typ |> resolveNestedPattern ~env ~package:full.package ~nested with + match typ |> resolveNested ~env ~package:full.package ~nested with | None -> fallbackOrEmpty () | Some (typ, env, completionContext) -> let items = - typ - |> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix - ~expandOption:false ~includeLocalValues:false ~completionContext + typ |> completeTypedValue ~env ~full ~prefix ~completionContext in fallbackOrEmpty ~items ()) | None -> fallbackOrEmpty ()) + | Cexpression {contextPath; prefix; nested} -> ( + match + contextPath + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:true ~scope + |> completionsGetTypeEnv + with + | None -> [] + | Some (typ, env) -> ( + match typ |> resolveNested ~env ~package:full.package ~nested with + | None -> [] + | Some (typ, env, completionContext) -> ( + let items = + typ |> completeTypedValue ~env ~full ~prefix ~completionContext + in + match (prefix, completionContext) with + | "", _ -> items + | _, None -> + (* Completions for local things like variables in scope, modules in the project, etc. *) + let regularCompletions = + prefix + |> getComplementaryCompletionsForTypedValue ~opens ~allFiles ~env + ~scope + in + let items = + if List.length regularCompletions > 0 then + (* The client will occasionally sort the list of completions alphabetically, disregarding the order + in which we send it. This fixes that by providing a sort text making the typed completions + guaranteed to end up on top. *) + items + |> List.map (fun (c : Completion.t) -> + {c with sortText = Some ("A" ^ " " ^ c.name)}) + else items + in + items @ regularCompletions + | _ -> items))) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 386518224..d42f8f448 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1,13 +1,5 @@ open SharedTypes -let extractCompletableArgValueInfo exp = - match exp.Parsetree.pexp_desc with - | Pexp_ident {txt = Lident txt} -> Some txt - | Pexp_construct ({txt = Lident "()"}, _) -> Some "" - | Pexp_construct ({txt = Lident txt}, None) -> Some txt - | Pexp_variant (label, None) -> Some ("#" ^ label) - | _ -> None - let isExprHole exp = match exp.Parsetree.pexp_desc with | Pexp_extension ({txt = "rescript.exprhole"}, _) -> true @@ -23,6 +15,221 @@ let isPatternTuple pat = | Ppat_tuple _ -> true | _ -> false +let isExprTuple expr = + match expr.Parsetree.pexp_desc with + | Pexp_tuple _ -> true + | _ -> false + +let rec getUnqualifiedName txt = + match txt with + | Longident.Lident fieldName -> fieldName + | Ldot (t, _) -> getUnqualifiedName t + | _ -> "" + +let rec traverseExpr (exp : Parsetree.expression) ~exprPath ~pos + ~firstCharBeforeCursorNoWhite = + let locHasCursor loc = loc |> CursorPosition.locHasCursor ~pos in + let someIfHasCursor v = if locHasCursor exp.pexp_loc then Some v else None in + match exp.pexp_desc with + | Pexp_ident {txt = Lident txt} when Utils.hasBraces exp.pexp_attributes -> + (* An ident with braces attribute corresponds to for example `{n}`. + Looks like a record but is parsed as an ident with braces. *) + someIfHasCursor (txt, [Completable.NRecordBody {seenFields = []}] @ exprPath) + | Pexp_ident {txt = Lident txt} -> someIfHasCursor (txt, exprPath) + | Pexp_construct ({txt = Lident "()"}, _) -> someIfHasCursor ("", exprPath) + | Pexp_construct ({txt = Lident txt}, None) -> someIfHasCursor (txt, exprPath) + | Pexp_variant (label, None) -> someIfHasCursor ("#" ^ label, exprPath) + | Pexp_array arrayPatterns -> ( + let nextExprPath = [Completable.NArray] @ exprPath in + (* No fields but still has cursor = empty completion *) + if List.length arrayPatterns = 0 && locHasCursor exp.pexp_loc then + Some ("", nextExprPath) + else + let arrayItemWithCursor = + arrayPatterns + |> List.find_map (fun e -> + e + |> traverseExpr ~exprPath:nextExprPath + ~firstCharBeforeCursorNoWhite ~pos) + in + + match (arrayItemWithCursor, locHasCursor exp.pexp_loc) with + | Some arrayItemWithCursor, _ -> Some arrayItemWithCursor + | None, true when firstCharBeforeCursorNoWhite = Some ',' -> + (* No item had the cursor, but the entire expr still has the cursor (so + the cursor is in the array somewhere), and the first char before the + cursor is a comma = interpret as compleing for a new value (example: + `[None, , None]`) *) + Some ("", nextExprPath) + | _ -> None) + | Pexp_tuple tupleItems when locHasCursor exp.pexp_loc -> + tupleItems + |> traverseExprTupleItems ~firstCharBeforeCursorNoWhite ~pos + ~nextExprPath:(fun itemNum -> + [Completable.NTupleItem {itemNum}] @ exprPath) + ~resultFromFoundItemNum:(fun itemNum -> + [Completable.NTupleItem {itemNum = itemNum + 1}] @ exprPath) + | Pexp_record ([], _) -> + (* Empty fields means we're in a record body `{}`. Complete for the fields. *) + someIfHasCursor ("", [Completable.NRecordBody {seenFields = []}] @ exprPath) + | Pexp_record (fields, _) -> ( + let fieldWithCursor = ref None in + let fieldWithExprHole = ref None in + fields + |> List.iter (fun (fname, exp) -> + match + ( fname.Location.txt, + exp.Parsetree.pexp_loc |> CursorPosition.classifyLoc ~pos ) + with + | Longident.Lident fname, HasCursor -> + fieldWithCursor := Some (fname, exp) + | Lident fname, _ when isExprHole exp -> + fieldWithExprHole := Some (fname, exp) + | _ -> ()); + let seenFields = + fields + |> List.filter_map (fun (fieldName, _f) -> + match fieldName with + | {Location.txt = Longident.Lident fieldName} -> Some fieldName + | _ -> None) + in + match (!fieldWithCursor, !fieldWithExprHole) with + | Some (fname, f), _ | None, Some (fname, f) -> ( + match f.pexp_desc with + | Pexp_extension ({txt = "rescript.exprhole"}, _) -> + (* An expression hole means for example `{someField: }`. We want to complete for the type of `someField`. *) + someIfHasCursor + ("", [Completable.NFollowRecordField {fieldName = fname}] @ exprPath) + | Pexp_ident {txt = Lident txt} -> + (* A var means `{someField: s}` or similar. Complete for identifiers or values. *) + someIfHasCursor (txt, exprPath) + | _ -> + f + |> traverseExpr ~firstCharBeforeCursorNoWhite ~pos + ~exprPath: + ([Completable.NFollowRecordField {fieldName = fname}] @ exprPath) + ) + | None, None -> ( + (* Figure out if we're completing for a new field. + If the cursor is inside of the record body, but no field has the cursor, + and there's no pattern hole. Check the first char to the left of the cursor, + ignoring white space. If that's a comma, we assume you're completing for a new field. *) + match firstCharBeforeCursorNoWhite with + | Some ',' -> + someIfHasCursor ("", [Completable.NRecordBody {seenFields}] @ exprPath) + | _ -> None)) + | Pexp_construct + ( {txt}, + Some {pexp_loc; pexp_desc = Pexp_construct ({txt = Lident "()"}, _)} ) + when locHasCursor pexp_loc -> + (* Empty payload with cursor, like: Test() *) + Some + ( "", + [ + Completable.NVariantPayload + {constructorName = getUnqualifiedName txt; itemNum = 0}; + ] + @ exprPath ) + | Pexp_construct ({txt}, Some e) + when pos >= (e.pexp_loc |> Loc.end_) + && firstCharBeforeCursorNoWhite = Some ',' + && isExprTuple e = false -> + (* Empty payload with trailing ',', like: Test(true, ) *) + Some + ( "", + [ + Completable.NVariantPayload + {constructorName = getUnqualifiedName txt; itemNum = 1}; + ] + @ exprPath ) + | Pexp_construct ({txt}, Some {pexp_loc; pexp_desc = Pexp_tuple tupleItems}) + when locHasCursor pexp_loc -> + tupleItems + |> traverseExprTupleItems ~firstCharBeforeCursorNoWhite ~pos + ~nextExprPath:(fun itemNum -> + [ + Completable.NVariantPayload + {constructorName = getUnqualifiedName txt; itemNum}; + ] + @ exprPath) + ~resultFromFoundItemNum:(fun itemNum -> + [ + Completable.NVariantPayload + {constructorName = getUnqualifiedName txt; itemNum = itemNum + 1}; + ] + @ exprPath) + | Pexp_construct ({txt}, Some p) when locHasCursor exp.pexp_loc -> + p + |> traverseExpr ~firstCharBeforeCursorNoWhite ~pos + ~exprPath: + ([ + Completable.NVariantPayload + {constructorName = getUnqualifiedName txt; itemNum = 0}; + ] + @ exprPath) + | Pexp_variant + (txt, Some {pexp_loc; pexp_desc = Pexp_construct ({txt = Lident "()"}, _)}) + when locHasCursor pexp_loc -> + (* Empty payload with cursor, like: #test() *) + Some + ( "", + [Completable.NPolyvariantPayload {constructorName = txt; itemNum = 0}] + @ exprPath ) + | Pexp_variant (txt, Some e) + when pos >= (e.pexp_loc |> Loc.end_) + && firstCharBeforeCursorNoWhite = Some ',' + && isExprTuple e = false -> + (* Empty payload with trailing ',', like: #test(true, ) *) + Some + ( "", + [Completable.NPolyvariantPayload {constructorName = txt; itemNum = 1}] + @ exprPath ) + | Pexp_variant (txt, Some {pexp_loc; pexp_desc = Pexp_tuple tupleItems}) + when locHasCursor pexp_loc -> + tupleItems + |> traverseExprTupleItems ~firstCharBeforeCursorNoWhite ~pos + ~nextExprPath:(fun itemNum -> + [Completable.NPolyvariantPayload {constructorName = txt; itemNum}] + @ exprPath) + ~resultFromFoundItemNum:(fun itemNum -> + [ + Completable.NPolyvariantPayload + {constructorName = txt; itemNum = itemNum + 1}; + ] + @ exprPath) + | Pexp_variant (txt, Some p) when locHasCursor exp.pexp_loc -> + p + |> traverseExpr ~firstCharBeforeCursorNoWhite ~pos + ~exprPath: + ([ + Completable.NPolyvariantPayload + {constructorName = txt; itemNum = 0}; + ] + @ exprPath) + | _ -> None + +and traverseExprTupleItems tupleItems ~nextExprPath ~resultFromFoundItemNum ~pos + ~firstCharBeforeCursorNoWhite = + let itemNum = ref (-1) in + let itemWithCursor = + tupleItems + |> List.find_map (fun e -> + itemNum := !itemNum + 1; + e + |> traverseExpr ~exprPath:(nextExprPath !itemNum) + ~firstCharBeforeCursorNoWhite ~pos) + in + match (itemWithCursor, firstCharBeforeCursorNoWhite) with + | None, Some ',' -> + (* No tuple item has the cursor, but there's a comma before the cursor. + Figure out what arg we're trying to complete. Example: (true, , None) *) + let posNum = ref (-1) in + tupleItems + |> List.iteri (fun index e -> + if pos >= Loc.start e.Parsetree.pexp_loc then posNum := index); + if !posNum > -1 then Some ("", resultFromFoundItemNum !posNum) else None + | v, _ -> v + type prop = { name: string; posStart: int * int; @@ -36,8 +243,8 @@ type jsxProps = { childrenStart: (int * int) option; } -let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor ~posAfterCompName - = +let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor + ~firstCharBeforeCursorNoWhite ~posAfterCompName = let allLabels = List.fold_right (fun prop allLabels -> prop.name :: allLabels) @@ -60,26 +267,39 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor ~posAfterCompName None else if prop.exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then (* Cursor on expr assigned *) - match extractCompletableArgValueInfo prop.exp with - | Some prefix -> + match + traverseExpr prop.exp ~exprPath:[] ~pos:posBeforeCursor + ~firstCharBeforeCursorNoWhite + with + | Some (prefix, nested) -> Some - (CjsxPropValue + (Cexpression { - pathToComponent = - Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt; + contextPath = + CJsxPropValue + { + pathToComponent = + Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt; + propName = prop.name; + }; + nested = List.rev nested; prefix; - propName = prop.name; }) | _ -> None else if prop.exp.pexp_loc |> Loc.end_ = (Location.none |> Loc.end_) then if isExprHole prop.exp then Some - (CjsxPropValue + (Cexpression { - pathToComponent = - Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt; + contextPath = + CJsxPropValue + { + pathToComponent = + Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt; + propName = prop.name; + }; prefix = ""; - propName = prop.name; + nested = []; }) else None else loop rest @@ -140,8 +360,8 @@ let extractJsxProps ~(compName : Longident.t Location.loc) ~args = args |> processProps ~acc:[] let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor - ~(contextPath : Completable.contextPath) ~posAfterFunExpr ~charBeforeCursor - ~isPipedExpr = + ~(contextPath : Completable.contextPath) ~posAfterFunExpr + ~firstCharBeforeCursorNoWhite ~charBeforeCursor ~isPipedExpr = let fnHasCursor = posAfterFunExpr <= posBeforeCursor && posBeforeCursor < endPos in @@ -163,47 +383,74 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor then Some (Completable.CnamedArg (contextPath, labelled.name, allNames)) else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then (* Completing in the assignment of labelled argument *) - match extractCompletableArgValueInfo exp with + match + traverseExpr exp ~exprPath:[] ~pos:posBeforeCursor + ~firstCharBeforeCursorNoWhite + with | None -> None - | Some prefix -> + | Some (prefix, nested) -> Some - (Cargument + (Cexpression { - functionContextPath = contextPath; - argumentLabel = Labelled labelled.name; + contextPath = + CArgument + { + functionContextPath = contextPath; + argumentLabel = Labelled labelled.name; + }; prefix; + nested = List.rev nested; }) else if isExprHole exp then Some - (Cargument + (Cexpression { - functionContextPath = contextPath; - argumentLabel = Labelled labelled.name; + contextPath = + CArgument + { + functionContextPath = contextPath; + argumentLabel = Labelled labelled.name; + }; prefix = ""; + nested = []; }) else loop rest | {label = None; exp} :: rest -> if Res_parsetree_viewer.isTemplateLiteral exp then None else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then (* Completing in an unlabelled argument *) - match extractCompletableArgValueInfo exp with + match + traverseExpr exp ~pos:posBeforeCursor ~firstCharBeforeCursorNoWhite + ~exprPath:[] + with | None -> None - | Some prefix -> + | Some (prefix, nested) -> Some - (Cargument + (Cexpression { - functionContextPath = contextPath; - argumentLabel = - Unlabelled {argumentPosition = !unlabelledCount}; + contextPath = + CArgument + { + functionContextPath = contextPath; + argumentLabel = + Unlabelled {argumentPosition = !unlabelledCount}; + }; prefix; + nested = List.rev nested; }) else if isExprHole exp then Some - (Cargument + (Cexpression { - functionContextPath = contextPath; - argumentLabel = Unlabelled {argumentPosition = !unlabelledCount}; + contextPath = + CArgument + { + functionContextPath = contextPath; + argumentLabel = + Unlabelled {argumentPosition = !unlabelledCount}; + }; prefix = ""; + nested = []; }) else ( unlabelledCount := !unlabelledCount + 1; @@ -214,12 +461,17 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor | Some '~' -> Some (Completable.CnamedArg (contextPath, "", allNames)) | _ -> Some - (Cargument + (Cexpression { - functionContextPath = contextPath; - argumentLabel = - Unlabelled {argumentPosition = !unlabelledCount}; + contextPath = + CArgument + { + functionContextPath = contextPath; + argumentLabel = + Unlabelled {argumentPosition = !unlabelledCount}; + }; prefix = ""; + nested = []; }) else None in @@ -230,11 +482,16 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor ] when fnHasCursor -> Some - (Completable.Cargument + (Completable.Cexpression { - functionContextPath = contextPath; - argumentLabel = Unlabelled {argumentPosition = 0}; + contextPath = + CArgument + { + functionContextPath = contextPath; + argumentLabel = Unlabelled {argumentPosition = 0}; + }; prefix = ""; + nested = []; }) | _ -> loop args @@ -267,12 +524,6 @@ let rec exprToContextPath (e : Parsetree.expression) = else None | _ -> None -let rec getUnqualifiedName txt = - match txt with - | Longident.Lident fieldName -> fieldName - | Ldot (t, _) -> getUnqualifiedName t - | _ -> "" - let completePipeChain ~(lhs : Parsetree.expression) = (* Complete the end of pipe chains by reconstructing the pipe chain as a single pipe, so it can be completed. @@ -447,12 +698,12 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = | Ppat_var {txt} -> someIfHasCursor (txt, patternPath) | Ppat_construct ({txt = Lident "()"}, None) -> (* switch s { | () }*) - someIfHasCursor ("", patternPath @ [Completable.PTupleItem {itemNum = 0}]) + someIfHasCursor ("", patternPath @ [Completable.NTupleItem {itemNum = 0}]) | Ppat_construct ({txt = Lident prefix}, None) -> someIfHasCursor (prefix, patternPath) | Ppat_variant (prefix, None) -> someIfHasCursor ("#" ^ prefix, patternPath) | Ppat_array arrayPatterns -> - let nextPatternPath = [Completable.PArray] @ patternPath in + let nextPatternPath = [Completable.NArray] @ patternPath in if List.length arrayPatterns = 0 && locHasCursor pat.ppat_loc then Some ("", nextPatternPath) else @@ -463,13 +714,13 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = tupleItems |> traverseTupleItems ~nextPatternPath:(fun itemNum -> - [Completable.PTupleItem {itemNum}] @ patternPath) + [Completable.NTupleItem {itemNum}] @ patternPath) ~resultFromFoundItemNum:(fun itemNum -> - [Completable.PTupleItem {itemNum = itemNum + 1}] @ patternPath) + [Completable.NTupleItem {itemNum = itemNum + 1}] @ patternPath) | Ppat_record ([], _) -> (* Empty fields means we're in a record body `{}`. Complete for the fields. *) someIfHasCursor - ("", [Completable.PRecordBody {seenFields = []}] @ patternPath) + ("", [Completable.NRecordBody {seenFields = []}] @ patternPath) | Ppat_record (fields, _) -> ( let fieldWithCursor = ref None in let fieldWithPatHole = ref None in @@ -499,17 +750,17 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = (* A pattern hole means for example `{someField: }`. We want to complete for the type of `someField`. *) someIfHasCursor ( "", - [Completable.PFollowRecordField {fieldName = fname}] @ patternPath + [Completable.NFollowRecordField {fieldName = fname}] @ patternPath ) | Ppat_var {txt} -> (* A var means `{s}` or similar. Complete for fields. *) someIfHasCursor - (txt, [Completable.PRecordBody {seenFields}] @ patternPath) + (txt, [Completable.NRecordBody {seenFields}] @ patternPath) | _ -> f |> traversePattern ~patternPath: - ([Completable.PFollowRecordField {fieldName = fname}] + ([Completable.NFollowRecordField {fieldName = fname}] @ patternPath)) | None, None -> ( (* Figure out if we're completing for a new field. @@ -519,7 +770,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = match firstCharBeforeCursorNoWhite with | Some ',' -> someIfHasCursor - ("", [Completable.PRecordBody {seenFields}] @ patternPath) + ("", [Completable.NRecordBody {seenFields}] @ patternPath) | _ -> None)) | Ppat_construct ( {txt}, @@ -530,7 +781,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = Some ( "", [ - Completable.PVariantPayload + Completable.NVariantPayload {constructorName = getUnqualifiedName txt; itemNum = 0}; ] @ patternPath ) @@ -542,7 +793,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = Some ( "", [ - Completable.PVariantPayload + Completable.NVariantPayload {constructorName = getUnqualifiedName txt; itemNum = 1}; ] @ patternPath ) @@ -552,13 +803,13 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = |> traverseTupleItems ~nextPatternPath:(fun itemNum -> [ - Completable.PVariantPayload + Completable.NVariantPayload {constructorName = getUnqualifiedName txt; itemNum}; ] @ patternPath) ~resultFromFoundItemNum:(fun itemNum -> [ - Completable.PVariantPayload + Completable.NVariantPayload { constructorName = getUnqualifiedName txt; itemNum = itemNum + 1; @@ -570,7 +821,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = |> traversePattern ~patternPath: ([ - Completable.PVariantPayload + Completable.NVariantPayload {constructorName = getUnqualifiedName txt; itemNum = 0}; ] @ patternPath) @@ -582,7 +833,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = (* Empty payload with cursor, like: #test() *) Some ( "", - [Completable.PPolyvariantPayload {constructorName = txt; itemNum = 0}] + [Completable.NPolyvariantPayload {constructorName = txt; itemNum = 0}] @ patternPath ) | Ppat_variant (txt, Some pat) when posBeforeCursor >= (pat.ppat_loc |> Loc.end_) @@ -591,18 +842,18 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = (* Empty payload with trailing ',', like: #test(true, ) *) Some ( "", - [Completable.PPolyvariantPayload {constructorName = txt; itemNum = 1}] + [Completable.NPolyvariantPayload {constructorName = txt; itemNum = 1}] @ patternPath ) | Ppat_variant (txt, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems}) when locHasCursor ppat_loc -> tupleItems |> traverseTupleItems ~nextPatternPath:(fun itemNum -> - [Completable.PPolyvariantPayload {constructorName = txt; itemNum}] + [Completable.NPolyvariantPayload {constructorName = txt; itemNum}] @ patternPath) ~resultFromFoundItemNum:(fun itemNum -> [ - Completable.PPolyvariantPayload + Completable.NPolyvariantPayload {constructorName = txt; itemNum = itemNum + 1}; ] @ patternPath) @@ -611,7 +862,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = |> traversePattern ~patternPath: ([ - Completable.PPolyvariantPayload + Completable.NPolyvariantPayload {constructorName = txt; itemNum = 0}; ] @ patternPath) @@ -623,7 +874,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = setResult (Completable.Cpattern { - typ = ctxPath; + contextPath = ctxPath; prefix; nested = List.rev nestedPattern; fallback = None; @@ -663,6 +914,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = !scope |> Scope.addModule ~name:md.pmd_name.txt ~loc:md.pmd_name.loc in let setLookingForPat ctxPath = lookingForPat := Some ctxPath in + let inJsxContext = ref false in let unsetLookingForPat () = lookingForPat := None in (* Identifies expressions where we can do typed pattern or expr completion. *) @@ -692,7 +944,12 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = | Some ctxPath -> setResult (Completable.Cpattern - {typ = ctxPath; nested = []; prefix = ""; fallback = None})) + { + contextPath = ctxPath; + nested = []; + prefix = ""; + fallback = None; + })) | Pexp_match (exp, cases) -> ( (* If there's more than one case, or the case isn't a pattern hole, figure out if we're completing another broken parser case (`switch x { | true => () | }` for example). *) @@ -719,7 +976,12 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = (* If there's no case with the cursor, but a broken parser case, complete for the top level. *) setResult (Completable.Cpattern - {typ = ctxPath; nested = []; prefix = ""; fallback = None}) + { + contextPath = ctxPath; + nested = []; + prefix = ""; + fallback = None; + }) | false, false -> ())) | _ -> unsetLookingForPat () in @@ -774,6 +1036,13 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = if not !processed then Ast_iterator.default_iterator.structure_item iterator item in + let value_binding (iterator : Ast_iterator.iterator) + (value_binding : Parsetree.value_binding) = + let oldInJsxContext = !inJsxContext in + if Utils.isReactComponent value_binding then inJsxContext := true; + Ast_iterator.default_iterator.value_binding iterator value_binding; + inJsxContext := oldInJsxContext + in let signature (iterator : Ast_iterator.iterator) (signature : Parsetree.signature) = let oldScope = !scope in @@ -850,6 +1119,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = Ast_iterator.default_iterator.attribute iterator (id, payload) in let expr (iterator : Ast_iterator.iterator) (expr : Parsetree.expression) = + let oldInJsxContext = !inJsxContext in let processed = ref false in let setFound () = found := true; @@ -864,11 +1134,20 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = match exprToContextPath lhs with | Some pipe -> setResult - (Cpath (CPPipe {contextPath = pipe; id; lhsLoc = lhs.pexp_loc})); + (Cpath + (CPPipe + { + contextPath = pipe; + id; + lhsLoc = lhs.pexp_loc; + inJsx = !inJsxContext; + })); true | None -> false) | Some (pipe, lhsLoc) -> - setResult (Cpath (CPPipe {contextPath = pipe; id; lhsLoc})); + setResult + (Cpath + (CPPipe {contextPath = pipe; id; lhsLoc; inJsx = !inJsxContext})); true in typedCompletionExpr expr; @@ -940,6 +1219,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = | None -> ()) | Pexp_apply ({pexp_desc = Pexp_ident compName}, args) when Res_parsetree_viewer.isJsxExpression expr -> + inJsxContext := true; let jsxProps = extractJsxProps ~compName ~args in let compNamePath = flattenLidCheckDot ~jsx:true compName in if debug then @@ -958,6 +1238,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = let jsxCompletable = findJsxPropsCompletable ~jsxProps ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor ~posAfterCompName:(Loc.end_ compName.loc) + ~firstCharBeforeCursorNoWhite in if jsxCompletable <> None then setResultOpt jsxCompletable else if compName.loc |> Loc.hasPos ~pos:posBeforeCursor then @@ -995,6 +1276,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor ~posAfterFunExpr:(Loc.end_ funExpr.pexp_loc) ~charBeforeCursor ~isPipedExpr:true + ~firstCharBeforeCursorNoWhite | None -> None in @@ -1030,6 +1312,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor ~posAfterFunExpr:(Loc.end_ funExpr.pexp_loc) ~charBeforeCursor ~isPipedExpr:false + ~firstCharBeforeCursorNoWhite | None -> None in @@ -1095,7 +1378,8 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = scope := oldScope; processed := true | _ -> ()); - if not !processed then Ast_iterator.default_iterator.expr iterator expr + if not !processed then Ast_iterator.default_iterator.expr iterator expr; + inJsxContext := oldInJsxContext in let typ (iterator : Ast_iterator.iterator) (core_type : Parsetree.core_type) = if core_type.ptyp_loc |> Loc.hasPos ~pos:posNoWhite then ( @@ -1204,6 +1488,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = structure_item; typ; type_kind; + value_binding; } in diff --git a/analysis/src/ProcessCmt.ml b/analysis/src/ProcessCmt.ml index dab690758..901c19a12 100644 --- a/analysis/src/ProcessCmt.ml +++ b/analysis/src/ProcessCmt.ml @@ -89,13 +89,16 @@ let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t) | Type_record (fields, _) -> Record (fields - |> List.map (fun {Types.ld_id; ld_type} -> + |> List.map (fun {Types.ld_id; ld_type; ld_attributes} -> let astamp = Ident.binding_time ld_id in let name = Ident.name ld_id in { stamp = astamp; fname = Location.mknoloc name; typ = ld_type; + optional = + Res_parsetree_viewer.hasOptionalAttribute + ld_attributes; }))); } ~name ~stamp:(Ident.binding_time ident) ~env type_attributes @@ -212,10 +215,22 @@ let forTypeDeclaration ~env ~(exported : Exported.t) (fields |> List.map (fun - {Typedtree.ld_id; ld_name = fname; ld_type = {ctyp_type}} + { + Typedtree.ld_id; + ld_name = fname; + ld_type = {ctyp_type}; + ld_attributes; + } -> let fstamp = Ident.binding_time ld_id in - {stamp = fstamp; fname; typ = ctyp_type}))); + { + stamp = fstamp; + fname; + typ = ctyp_type; + optional = + Res_parsetree_viewer.hasOptionalAttribute + ld_attributes; + }))); } ~name ~stamp ~env typ_attributes (Exported.add exported Exported.Type) diff --git a/analysis/src/Protocol.ml b/analysis/src/Protocol.ml index 3dc07b564..bd464b91d 100644 --- a/analysis/src/Protocol.ml +++ b/analysis/src/Protocol.ml @@ -109,7 +109,7 @@ let stringifyObject properties = |> String.concat ",\n") ^ "\n }" -let wrapInQuotes s = "\"" ^ s ^ "\"" +let wrapInQuotes s = "\"" ^ Json.escape s ^ "\"" let optWrapInQuotes s = match s with @@ -119,10 +119,10 @@ let optWrapInQuotes s = let stringifyCompletionItem c = stringifyObject [ - ("label", Some (wrapInQuotes (Json.escape c.label))); + ("label", Some (wrapInQuotes c.label)); ("kind", Some (string_of_int c.kind)); ("tags", Some (c.tags |> List.map string_of_int |> array)); - ("detail", Some (wrapInQuotes (Json.escape c.detail))); + ("detail", Some (wrapInQuotes c.detail)); ( "documentation", Some (match c.documentation with diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 5a06ce9d9..768a7bb7e 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -24,7 +24,12 @@ module ModulePath = struct loop modulePath [tipName] end -type field = {stamp: int; fname: string Location.loc; typ: Types.type_expr} +type field = { + stamp: int; + fname: string Location.loc; + typ: Types.type_expr; + optional: bool; +} module Constructor = struct type t = { @@ -316,12 +321,13 @@ module Completion = struct insertTextFormat = None; } - let createWithSnippet ~name ?insertText ~kind ~env ?sortText () = + let createWithSnippet ~name ?insertText ~kind ~env ?sortText ?(docstring = []) + () = { name; env; deprecated = None; - docstring = []; + docstring; kind; sortText; insertText; @@ -546,33 +552,39 @@ module Completable = struct | CPPipe of { contextPath: contextPath; id: string; + inJsx: bool; (** Whether this pipe was found in a JSX context. *) lhsLoc: Location.t; (** The loc item for the left hand side of the pipe. *) } | CTuple of contextPath list + | CArgument of { + functionContextPath: contextPath; + argumentLabel: argumentLabel; + } + | CJsxPropValue of {pathToComponent: string list; propName: string} - (** Additional context for a pattern completion where needed. *) - type patternContext = RecordField of {seenFields: string list} + (** Additional context for nested completion where needed. *) + type nestedContext = RecordField of {seenFields: string list} - type patternPath = - | PTupleItem of {itemNum: int} - | PFollowRecordField of {fieldName: string} - | PRecordBody of {seenFields: string list} - | PVariantPayload of {constructorName: string; itemNum: int} - | PPolyvariantPayload of {constructorName: string; itemNum: int} - | PArray + type nestedPath = + | NTupleItem of {itemNum: int} + | NFollowRecordField of {fieldName: string} + | NRecordBody of {seenFields: string list} + | NVariantPayload of {constructorName: string; itemNum: int} + | NPolyvariantPayload of {constructorName: string; itemNum: int} + | NArray - let patternPathToString p = + let nestedPathToString p = match p with - | PTupleItem {itemNum} -> "tuple($" ^ string_of_int itemNum ^ ")" - | PFollowRecordField {fieldName} -> "recordField(" ^ fieldName ^ ")" - | PRecordBody _ -> "recordBody" - | PVariantPayload {constructorName; itemNum} -> + | NTupleItem {itemNum} -> "tuple($" ^ string_of_int itemNum ^ ")" + | NFollowRecordField {fieldName} -> "recordField(" ^ fieldName ^ ")" + | NRecordBody _ -> "recordBody" + | NVariantPayload {constructorName; itemNum} -> "variantPayload::" ^ constructorName ^ "($" ^ string_of_int itemNum ^ ")" - | PPolyvariantPayload {constructorName; itemNum} -> + | NPolyvariantPayload {constructorName; itemNum} -> "polyvariantPayload::" ^ constructorName ^ "($" ^ string_of_int itemNum ^ ")" - | PArray -> "array" + | NArray -> "array" type t = | Cdecorator of string (** e.g. @module *) @@ -582,20 +594,14 @@ module Completable = struct | Cpath of contextPath | Cjsx of string list * string * string list (** E.g. (["M", "Comp"], "id", ["id1", "id2"]) for ), complete for the value of `someBoolArg` (true or false). *) - | CjsxPropValue of { - pathToComponent: string list; - propName: string; + | Cexpression of { + contextPath: contextPath; + nested: nestedPath list; prefix: string; } | Cpattern of { - typ: contextPath; - nested: patternPath list; + contextPath: contextPath; + nested: nestedPath list; prefix: string; fallback: t option; } @@ -606,6 +612,7 @@ module Completable = struct | Toption of QueryEnv.t * Types.type_expr | Tbool of QueryEnv.t | Tarray of QueryEnv.t * Types.type_expr + | Tstring of QueryEnv.t | Tvariant of { env: QueryEnv.t; constructors: Constructor.t list; @@ -648,11 +655,26 @@ module Completable = struct completionContextToString completionContext ^ list sl | CPField (cp, s) -> contextPathToString cp ^ "." ^ str s | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]" - | CPPipe {contextPath; id} -> contextPathToString contextPath ^ "->" ^ id + | CPPipe {contextPath; id; inJsx} -> + contextPathToString contextPath + ^ "->" ^ id + ^ if inJsx then " <>" else "" | CTuple ctxPaths -> "CTuple(" ^ (ctxPaths |> List.map contextPathToString |> String.concat ", ") ^ ")" + | CArgument {functionContextPath; argumentLabel} -> + "CArgument " + ^ contextPathToString functionContextPath + ^ "(" + ^ (match argumentLabel with + | Unlabelled {argumentPosition} -> + "$" ^ string_of_int argumentPosition + | Labelled name -> "~" ^ name + | Optional name -> "~" ^ name ^ "=?") + ^ ")" + | CJsxPropValue {pathToComponent; propName} -> + "CJsxPropValue " ^ (pathToComponent |> list) ^ " " ^ propName in function @@ -665,29 +687,29 @@ module Completable = struct | Cnone -> "Cnone" | Cjsx (sl1, s, sl2) -> "Cjsx(" ^ (sl1 |> list) ^ ", " ^ str s ^ ", " ^ (sl2 |> list) ^ ")" - | Cargument {functionContextPath; argumentLabel; prefix} -> - "Cargument " - ^ contextPathToString functionContextPath - ^ "(" - ^ (match argumentLabel with - | Unlabelled {argumentPosition} -> "$" ^ string_of_int argumentPosition - | Labelled name -> "~" ^ name - | Optional name -> "~" ^ name ^ "=?") - ^ (if prefix <> "" then "=" ^ prefix else "") - ^ ")" - | CjsxPropValue {prefix; pathToComponent; propName} -> - "CjsxPropValue " ^ (pathToComponent |> list) ^ " " ^ propName ^ "=" - ^ prefix - | Cpattern {typ; nested; prefix} -> ( - "Cpattern " ^ contextPathToString typ + | Cpattern {contextPath; nested; prefix} -> ( + "Cpattern " + ^ contextPathToString contextPath + ^ (if prefix = "" then "" else "=" ^ prefix) + ^ + match nested with + | [] -> "" + | nestedPaths -> + "->" + ^ (nestedPaths + |> List.map (fun nestedPath -> nestedPathToString nestedPath) + |> String.concat ", ")) + | Cexpression {contextPath; nested; prefix} -> ( + "Cexpression " + ^ contextPathToString contextPath ^ (if prefix = "" then "" else "=" ^ prefix) ^ match nested with | [] -> "" - | patternPaths -> + | nestedPaths -> "->" - ^ (patternPaths - |> List.map (fun patternPath -> patternPathToString patternPath) + ^ (nestedPaths + |> List.map (fun nestedPath -> nestedPathToString nestedPath) |> String.concat ", ")) end diff --git a/analysis/src/SignatureHelp.ml b/analysis/src/SignatureHelp.ml index 5b6390a20..a31140320 100644 --- a/analysis/src/SignatureHelp.ml +++ b/analysis/src/SignatureHelp.ml @@ -179,10 +179,19 @@ let signatureHelp ~path ~pos ~currentFile ~debug = let supportsMarkdownLinks = true in let foundFunctionApplicationExpr = ref None in let setFound r = - if !foundFunctionApplicationExpr = None then + (* Because we want to handle both piped and regular function calls, and in + the case of piped calls the iterator will process both the pipe and the + regular call (even though it's piped), we need to ensure that we don't + re-save the same expression (but unpiped, even though it's actually piped). *) + match (!foundFunctionApplicationExpr, r) with + | Some (_, alreadyFoundExp, _), (_, newExp, _) + when alreadyFoundExp.Parsetree.pexp_loc <> newExp.Parsetree.pexp_loc + -> foundFunctionApplicationExpr := Some r + | None, _ -> foundFunctionApplicationExpr := Some r + | Some _, _ -> () in - let searchForArgWithCursor ~isPipeExpr ~args ~exp = + let searchForArgWithCursor ~isPipeExpr ~args = let extractedArgs = extractExpApplyArgs ~args in let argAtCursor = let firstArgIndex = if isPipeExpr then 1 else 0 in @@ -246,7 +255,7 @@ let signatureHelp ~path ~pos ~currentFile ~debug = else 0)) | v -> v in - setFound (argAtCursor, exp, extractedArgs) + (argAtCursor, extractedArgs) in let expr (iterator : Ast_iterator.iterator) (expr : Parsetree.expression) = @@ -269,7 +278,10 @@ let signatureHelp ~path ~pos ~currentFile ~debug = when pexp_loc |> CursorPosition.classifyLoc ~pos:posBeforeCursor == HasCursor -> - searchForArgWithCursor ~isPipeExpr:true ~args ~exp + let argAtCursor, extractedArgs = + searchForArgWithCursor ~isPipeExpr:true ~args + in + setFound (argAtCursor, exp, extractedArgs) (* Look for applying idents, like someIdent(...) *) | { pexp_desc = Pexp_apply (({pexp_desc = Pexp_ident _} as exp), args); @@ -278,7 +290,10 @@ let signatureHelp ~path ~pos ~currentFile ~debug = when pexp_loc |> CursorPosition.classifyLoc ~pos:posBeforeCursor == HasCursor -> - searchForArgWithCursor ~isPipeExpr:false ~args ~exp + let argAtCursor, extractedArgs = + searchForArgWithCursor ~isPipeExpr:false ~args + in + setFound (argAtCursor, exp, extractedArgs) | _ -> ()); Ast_iterator.default_iterator.expr iterator expr in diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml index 5bca598c8..d62334bc7 100644 --- a/analysis/src/Utils.ml +++ b/analysis/src/Utils.ml @@ -159,3 +159,17 @@ let rec skipWhite text i = match text.[i] with | ' ' | '\n' | '\r' | '\t' -> skipWhite text (i - 1) | _ -> i + +let hasBraces attributes = + attributes |> List.exists (fun (loc, _) -> loc.Location.txt = "ns.braces") + +let rec unwrapIfOption (t : Types.type_expr) = + match t.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> unwrapIfOption t1 + | Tconstr (Path.Pident {name = "option"}, [unwrappedType], _) -> unwrappedType + | _ -> t +let isReactComponent (vb : Parsetree.value_binding) = + vb.pvb_attributes + |> List.exists (function + | {Location.txt = "react.component"}, _payload -> true + | _ -> false) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index 198580cb9..dc8fa8cb9 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -212,13 +212,8 @@ module AddTypeAnnotation = struct match si.pstr_desc with | Pstr_value (_recFlag, bindings) -> let processBinding (vb : Parsetree.value_binding) = - let isReactComponent = - (* Can't add a type annotation to a react component, or the compiler crashes *) - vb.pvb_attributes - |> List.exists (function - | {Location.txt = "react.component"}, _payload -> true - | _ -> false) - in + (* Can't add a type annotation to a react component, or the compiler crashes *) + let isReactComponent = Utils.isReactComponent vb in if not isReactComponent then processPattern vb.pvb_pat; processFunction vb.pvb_expr in diff --git a/analysis/tests/src/CompletionExpressions.res b/analysis/tests/src/CompletionExpressions.res index 87eca05dd..4e806fbe7 100644 --- a/analysis/tests/src/CompletionExpressions.res +++ b/analysis/tests/src/CompletionExpressions.res @@ -3,3 +3,116 @@ let f = Some([false]) // switch (s, f) { | } // ^com + +type otherRecord = { + someField: int, + otherField: string, +} + +type rec someRecord = { + age: int, + offline: bool, + online: option, + variant: someVariant, + polyvariant: somePolyVariant, + nested: option, +} +and someVariant = One | Two | Three(int, string) +and somePolyVariant = [#one | #two(bool) | #three(someRecord, bool)] + +let fnTakingRecord = (r: someRecord) => { + ignore(r) +} + +// let _ = fnTakingRecord({}) +// ^com + +// let _ = fnTakingRecord({n}) +// ^com + +// let _ = fnTakingRecord({offline: }) +// ^com + +// let _ = fnTakingRecord({age: 123, }) +// ^com + +// let _ = fnTakingRecord({age: 123, offline: true}) +// ^com + +// let _ = fnTakingRecord({age: 123, nested: }) +// ^com + +// let _ = fnTakingRecord({age: 123, nested: {}}) +// ^com + +// let _ = fnTakingRecord({age: 123, nested: Some({})}) +// ^com + +// let _ = fnTakingRecord({age: 123, variant: }) +// ^com + +// let _ = fnTakingRecord({age: 123, variant: O }) +// ^com + +// let _ = fnTakingRecord({age: 123, polyvariant: #three() }) +// ^com + +// let _ = fnTakingRecord({age: 123, polyvariant: #three({}, ) }) +// ^com + +// let _ = fnTakingRecord({age: 123, polyvariant: #three({}, t) }) +// ^com + +let fnTakingArray = (arr: array>) => { + ignore(arr) +} + +// let _ = fnTakingArray() +// ^com + +// let _ = fnTakingArray([]) +// ^com + +// let _ = fnTakingArray(s) +// ^com + +// let _ = fnTakingArray([Some()]) +// ^com + +// let _ = fnTakingArray([None, ]) +// ^com + +// let _ = fnTakingArray([None, , None]) +// ^com + +let someBoolVar = true + +// let _ = fnTakingRecord({offline: so }) +// ^com + +let fnTakingOtherRecord = (r: otherRecord) => { + ignore(r) +} + +// let _ = fnTakingOtherRecord({otherField: }) +// ^com + +type recordWithOptionalField = { + someField: int, + someOptField?: bool, +} + +let fnTakingRecordWithOptionalField = (r: recordWithOptionalField) => { + ignore(r) +} + +// let _ = fnTakingRecordWithOptionalField({someOptField: }) +// ^com +type recordWithOptVariant = {someVariant: option} + +let fnTakingRecordWithOptVariant = (r: recordWithOptVariant) => { + ignore(r) +} + +// let _ = fnTakingRecordWithOptVariant({someVariant: }) +// ^com diff --git a/analysis/tests/src/CompletionFunctionArguments.res b/analysis/tests/src/CompletionFunctionArguments.res index b6961351c..39e9079ba 100644 --- a/analysis/tests/src/CompletionFunctionArguments.res +++ b/analysis/tests/src/CompletionFunctionArguments.res @@ -55,8 +55,8 @@ let someFnTakingVariant = ( // let _ = someFnTakingVariant(~config=O) // ^com -// let _ = someFnTakingVariant(S) -// ^com +// let _ = someFnTakingVariant(So) +// ^com // let _ = someFnTakingVariant(~configOpt2=O) // ^com @@ -76,3 +76,16 @@ let fnTakingTuple = (arg: (int, int, float)) => { // let _ = fnTakingTuple() // ^com + +type someRecord = { + age: int, + offline: bool, + online: option, +} + +let fnTakingRecord = (r: someRecord) => { + ignore(r) +} + +// let _ = fnTakingRecord({}) +// ^com diff --git a/analysis/tests/src/CompletionJsx.res b/analysis/tests/src/CompletionJsx.res new file mode 100644 index 000000000..c08889d8b --- /dev/null +++ b/analysis/tests/src/CompletionJsx.res @@ -0,0 +1,33 @@ +let someString = "hello" +ignore(someString) + +// someString->st +// ^com + +module SomeComponent = { + @react.component + let make = (~someProp) => { + let someInt = 12 + let someArr = [React.null] + ignore(someInt) + ignore(someArr) + // someString->st + // ^com +
+ {React.string(someProp)} +
{React.null}
+ // {someString->st} + // ^com + // {"Some string"->st} + // ^com + // {"Some string"->Js.String2.trim->st} + // ^com + // {someInt->} + // ^com + // {12->} + // ^com + // {someArr->a} + // ^com +
+ } +} diff --git a/analysis/tests/src/SignatureHelp.res b/analysis/tests/src/SignatureHelp.res index 8fa6c4020..41d2b8cfb 100644 --- a/analysis/tests/src/SignatureHelp.res +++ b/analysis/tests/src/SignatureHelp.res @@ -68,3 +68,9 @@ let fn = (age: int, name: string, year: int) => { // let _ = fn(12, "hello", ) // ^she + +// let _ = fn({ iAmSoSpecial() }) +// ^she + +// let _ = fn({ iAmSoSpecial({ someFunc() }) }) +// ^she diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index 8c95be01b..7907834ab 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -463,7 +463,7 @@ Completable: Cpath Value[Js, Dict, u] Complete src/Completion.res 59:30 posCursor:[59:30] posNoWhite:[59:29] Found expr:[59:15->59:30] JSX 59:21] second[59:22->59:28]=...[59:29->59:30]> _children:None -Completable: CjsxPropValue [O, Comp] second=z +Completable: Cexpression CJsxPropValue [O, Comp] second=z [{ "label": "zzz", "kind": 12, diff --git a/analysis/tests/src/expected/CompletionExpressions.res.txt b/analysis/tests/src/expected/CompletionExpressions.res.txt index 7793379c7..6531231ae 100644 --- a/analysis/tests/src/expected/CompletionExpressions.res.txt +++ b/analysis/tests/src/expected/CompletionExpressions.res.txt @@ -11,3 +11,525 @@ Completable: Cpattern CTuple(Value[s], Value[f]) "insertTextFormat": 2 }] +Complete src/CompletionExpressions.res 26:27 +posCursor:[26:27] posNoWhite:[26:26] Found expr:[26:11->26:29] +Pexp_apply ...[26:11->26:25] (...[26:26->26:28]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordBody +[{ + "label": "age", + "kind": 5, + "tags": [], + "detail": "age: int\n\nsomeRecord", + "documentation": null + }, { + "label": "offline", + "kind": 5, + "tags": [], + "detail": "offline: bool\n\nsomeRecord", + "documentation": null + }, { + "label": "online", + "kind": 5, + "tags": [], + "detail": "online: option\n\nsomeRecord", + "documentation": null + }, { + "label": "variant", + "kind": 5, + "tags": [], + "detail": "variant: someVariant\n\nsomeRecord", + "documentation": null + }, { + "label": "polyvariant", + "kind": 5, + "tags": [], + "detail": "polyvariant: somePolyVariant\n\nsomeRecord", + "documentation": null + }, { + "label": "nested", + "kind": 5, + "tags": [], + "detail": "nested: option\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionExpressions.res 29:28 +posCursor:[29:28] posNoWhite:[29:27] Found expr:[29:11->29:30] +Pexp_apply ...[29:11->29:25] (...[29:27->29:28]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)=n->recordBody +[{ + "label": "nested", + "kind": 5, + "tags": [], + "detail": "nested: option\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionExpressions.res 32:35 +posCursor:[32:35] posNoWhite:[32:34] Found expr:[32:11->32:38] +Pexp_apply ...[32:11->32:25] (...[32:26->32:38]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(offline) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionExpressions.res 35:36 +posCursor:[35:36] posNoWhite:[35:35] Found expr:[35:11->35:39] +Pexp_apply ...[35:11->35:25] (...[35:26->35:38]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordBody +[{ + "label": "offline", + "kind": 5, + "tags": [], + "detail": "offline: bool\n\nsomeRecord", + "documentation": null + }, { + "label": "online", + "kind": 5, + "tags": [], + "detail": "online: option\n\nsomeRecord", + "documentation": null + }, { + "label": "variant", + "kind": 5, + "tags": [], + "detail": "variant: someVariant\n\nsomeRecord", + "documentation": null + }, { + "label": "polyvariant", + "kind": 5, + "tags": [], + "detail": "polyvariant: somePolyVariant\n\nsomeRecord", + "documentation": null + }, { + "label": "nested", + "kind": 5, + "tags": [], + "detail": "nested: option\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionExpressions.res 38:37 +posCursor:[38:37] posNoWhite:[38:35] Found expr:[38:11->38:53] +Pexp_apply ...[38:11->38:25] (...[38:26->38:52]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordBody +[{ + "label": "online", + "kind": 5, + "tags": [], + "detail": "online: option\n\nsomeRecord", + "documentation": null + }, { + "label": "variant", + "kind": 5, + "tags": [], + "detail": "variant: someVariant\n\nsomeRecord", + "documentation": null + }, { + "label": "polyvariant", + "kind": 5, + "tags": [], + "detail": "polyvariant: somePolyVariant\n\nsomeRecord", + "documentation": null + }, { + "label": "nested", + "kind": 5, + "tags": [], + "detail": "nested: option\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionExpressions.res 41:44 +posCursor:[41:44] posNoWhite:[41:43] Found expr:[41:11->41:47] +Pexp_apply ...[41:11->41:25] (...[41:26->41:47]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(nested) +[{ + "label": "None", + "kind": 4, + "tags": [], + "detail": "otherRecord", + "documentation": null + }, { + "label": "Some(_)", + "kind": 4, + "tags": [], + "detail": "otherRecord", + "documentation": null, + "insertText": "Some(${1:_})", + "insertTextFormat": 2 + }, { + "label": "Some({})", + "kind": 12, + "tags": [], + "detail": "otherRecord", + "documentation": null, + "insertText": "Some({$0})", + "insertTextFormat": 2 + }] + +Complete src/CompletionExpressions.res 44:46 +posCursor:[44:46] posNoWhite:[44:45] Found expr:[44:11->44:49] +Pexp_apply ...[44:11->44:25] (...[44:26->44:48]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(nested), recordBody +[] + +Complete src/CompletionExpressions.res 47:51 +posCursor:[47:51] posNoWhite:[47:50] Found expr:[47:11->47:55] +Pexp_apply ...[47:11->47:25] (...[47:26->47:54]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(nested), variantPayload::Some($0), recordBody +[{ + "label": "someField", + "kind": 5, + "tags": [], + "detail": "someField: int\n\notherRecord", + "documentation": null + }, { + "label": "otherField", + "kind": 5, + "tags": [], + "detail": "otherField: string\n\notherRecord", + "documentation": null + }] + +Complete src/CompletionExpressions.res 50:45 +posCursor:[50:45] posNoWhite:[50:44] Found expr:[50:11->50:48] +Pexp_apply ...[50:11->50:25] (...[50:26->50:48]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(variant) +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null, + "insertText": "One", + "insertTextFormat": 2 + }, { + "label": "Two", + "kind": 4, + "tags": [], + "detail": "Two\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null, + "insertText": "Two", + "insertTextFormat": 2 + }, { + "label": "Three(_, _)", + "kind": 4, + "tags": [], + "detail": "Three(int, string)\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null, + "insertText": "Three(${1:_}, ${2:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionExpressions.res 53:47 +posCursor:[53:47] posNoWhite:[53:46] Found expr:[53:11->53:50] +Pexp_apply ...[53:11->53:25] (...[53:26->53:49]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)=O->recordField(variant) +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null, + "sortText": "A One", + "insertText": "One", + "insertTextFormat": 2 + }, { + "label": "Obj", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }, { + "label": "Object", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }] + +Complete src/CompletionExpressions.res 56:57 +posCursor:[56:57] posNoWhite:[56:56] Found expr:[56:11->56:61] +Pexp_apply ...[56:11->56:25] (...[56:26->56:60]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(polyvariant), polyvariantPayload::three($0) +[{ + "label": "{}", + "kind": 12, + "tags": [], + "detail": "someRecord", + "documentation": null, + "sortText": "A", + "insertText": "{$0}", + "insertTextFormat": 2 + }] + +Complete src/CompletionExpressions.res 59:60 +posCursor:[59:60] posNoWhite:[59:59] Found expr:[59:11->59:65] +Pexp_apply ...[59:11->59:25] (...[59:26->59:64]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(polyvariant), polyvariantPayload::three($1) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionExpressions.res 62:62 +posCursor:[62:62] posNoWhite:[62:61] Found expr:[62:11->62:66] +Pexp_apply ...[62:11->62:25] (...[62:26->62:65]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)=t->recordField(polyvariant), polyvariantPayload::three($1) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionExpressions.res 69:25 +posCursor:[69:25] posNoWhite:[69:24] Found expr:[69:11->69:26] +Pexp_apply ...[69:11->69:24] (...[69:25->69:26]) +Completable: Cexpression CArgument Value[fnTakingArray]($0) +[{ + "label": "[]", + "kind": 12, + "tags": [], + "detail": "option", + "documentation": null, + "sortText": "A", + "insertText": "[$0]", + "insertTextFormat": 2 + }] + +Complete src/CompletionExpressions.res 72:26 +posCursor:[72:26] posNoWhite:[72:25] Found expr:[72:11->72:28] +Pexp_apply ...[72:11->72:24] (...[72:25->72:27]) +Completable: Cexpression CArgument Value[fnTakingArray]($0)->array +[{ + "label": "None", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(_)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null, + "insertText": "Some(${1:_})", + "insertTextFormat": 2 + }, { + "label": "Some(true)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(false)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionExpressions.res 75:26 +posCursor:[75:26] posNoWhite:[75:25] Found expr:[75:11->75:27] +Pexp_apply ...[75:11->75:24] (...[75:25->75:26]) +Completable: Cexpression CArgument Value[fnTakingArray]($0)=s +[{ + "label": "s", + "kind": 12, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionExpressions.res 78:31 +posCursor:[78:31] posNoWhite:[78:30] Found expr:[78:11->78:34] +Pexp_apply ...[78:11->78:24] (...[78:25->78:33]) +Completable: Cexpression CArgument Value[fnTakingArray]($0)->array, variantPayload::Some($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionExpressions.res 81:31 +posCursor:[81:31] posNoWhite:[81:30] Found expr:[81:11->81:34] +Pexp_apply ...[81:11->81:24] (...[81:25->81:33]) +Completable: Cexpression CArgument Value[fnTakingArray]($0)->array +[{ + "label": "None", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(_)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null, + "insertText": "Some(${1:_})", + "insertTextFormat": 2 + }, { + "label": "Some(true)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(false)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionExpressions.res 84:31 +posCursor:[84:31] posNoWhite:[84:30] Found expr:[84:11->84:40] +Pexp_apply ...[84:11->84:24] (...[84:25->84:39]) +Completable: Cexpression CArgument Value[fnTakingArray]($0)->array +[{ + "label": "None", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(_)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null, + "insertText": "Some(${1:_})", + "insertTextFormat": 2 + }, { + "label": "Some(true)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(false)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionExpressions.res 89:38 +posCursor:[89:38] posNoWhite:[89:37] Found expr:[89:11->89:41] +Pexp_apply ...[89:11->89:25] (...[89:26->89:40]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)=so +[{ + "label": "someBoolVar", + "kind": 12, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionExpressions.res 96:43 +posCursor:[96:43] posNoWhite:[96:42] Found expr:[96:11->96:46] +Pexp_apply ...[96:11->96:30] (...[96:31->96:46]) +Completable: Cexpression CArgument Value[fnTakingOtherRecord]($0)->recordField(otherField) +[{ + "label": "\"\"", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": null, + "sortText": "A", + "insertText": "\"$0\"", + "insertTextFormat": 2 + }] + +Complete src/CompletionExpressions.res 108:57 +posCursor:[108:57] posNoWhite:[108:56] Found expr:[108:11->108:60] +Pexp_apply ...[108:11->108:42] (...[108:43->108:60]) +Completable: Cexpression CArgument Value[fnTakingRecordWithOptionalField]($0)->recordField(someOptField) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionExpressions.res 116:53 +posCursor:[116:53] posNoWhite:[116:52] Found expr:[116:11->116:56] +Pexp_apply ...[116:11->116:39] (...[116:40->116:56]) +Completable: Cexpression CArgument Value[fnTakingRecordWithOptVariant]($0)->recordField(someVariant) +[{ + "label": "None", + "kind": 4, + "tags": [], + "detail": "someVariant", + "documentation": null + }, { + "label": "Some(_)", + "kind": 4, + "tags": [], + "detail": "someVariant", + "documentation": null, + "insertText": "Some(${1:_})", + "insertTextFormat": 2 + }, { + "label": "Some(One)", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null, + "insertText": "Some(One)", + "insertTextFormat": 2 + }, { + "label": "Some(Two)", + "kind": 4, + "tags": [], + "detail": "Two\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null, + "insertText": "Some(Two)", + "insertTextFormat": 2 + }, { + "label": "Some(Three(_, _))", + "kind": 4, + "tags": [], + "detail": "Three(int, string)\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null, + "insertText": "Some(Three(${1:_}, ${2:_}))", + "insertTextFormat": 2 + }] + diff --git a/analysis/tests/src/expected/CompletionFunctionArguments.res.txt b/analysis/tests/src/expected/CompletionFunctionArguments.res.txt index 1d265ed4a..a52989fa8 100644 --- a/analysis/tests/src/expected/CompletionFunctionArguments.res.txt +++ b/analysis/tests/src/expected/CompletionFunctionArguments.res.txt @@ -1,7 +1,7 @@ Complete src/CompletionFunctionArguments.res 10:24 posCursor:[10:24] posNoWhite:[10:23] Found expr:[10:11->10:25] Pexp_apply ...[10:11->10:17] (~isOn10:19->10:23=...__ghost__[0:-1->0:-1]) -Completable: Cargument Value[someFn](~isOn) +Completable: Cexpression CArgument Value[someFn](~isOn) [{ "label": "true", "kind": 4, @@ -19,13 +19,14 @@ Completable: Cargument Value[someFn](~isOn) Complete src/CompletionFunctionArguments.res 13:25 posCursor:[13:25] posNoWhite:[13:24] Found expr:[13:11->13:26] Pexp_apply ...[13:11->13:17] (~isOn13:19->13:23=...[13:24->13:25]) -Completable: Cargument Value[someFn](~isOn=t) +Completable: Cexpression CArgument Value[someFn](~isOn)=t [{ "label": "true", "kind": 4, "tags": [], "detail": "bool", - "documentation": null + "documentation": null, + "sortText": "A true" }, { "label": "tLocalVar", "kind": 12, @@ -37,7 +38,7 @@ Completable: Cargument Value[someFn](~isOn=t) Complete src/CompletionFunctionArguments.res 16:25 posCursor:[16:25] posNoWhite:[16:24] Found expr:[16:11->16:26] Pexp_apply ...[16:11->16:17] (~isOff16:19->16:24=...__ghost__[0:-1->0:-1]) -Completable: Cargument Value[someFn](~isOff) +Completable: Cexpression CArgument Value[someFn](~isOff) [{ "label": "true", "kind": 4, @@ -59,7 +60,7 @@ posCursor:[21:27] posNoWhite:[21:26] Found expr:[21:7->23:8] posCursor:[21:27] posNoWhite:[21:26] Found expr:[21:7->21:28] posCursor:[21:27] posNoWhite:[21:26] Found expr:[21:14->21:28] Pexp_apply ...[21:14->21:20] (~isOn21:22->21:26=...__ghost__[0:-1->0:-1]) -Completable: Cargument Value[someFn](~isOn) +Completable: Cexpression CArgument Value[someFn](~isOn) [{ "label": "true", "kind": 4, @@ -77,25 +78,19 @@ Completable: Cargument Value[someFn](~isOn) Complete src/CompletionFunctionArguments.res 34:24 posCursor:[34:24] posNoWhite:[34:23] Found expr:[34:11->34:25] Pexp_apply ...[34:11->34:22] (...[34:23->34:24]) -Completable: Cargument Value[someOtherFn]($0=f) +Completable: Cexpression CArgument Value[someOtherFn]($0)=f [{ "label": "false", "kind": 4, "tags": [], "detail": "bool", "documentation": null - }, { - "label": "fnTakingTuple", - "kind": 12, - "tags": [], - "detail": "((int, int, float)) => unit", - "documentation": null }] Complete src/CompletionFunctionArguments.res 51:39 posCursor:[51:39] posNoWhite:[51:38] Found expr:[51:11->51:40] Pexp_apply ...[51:11->51:30] (~config51:32->51:38=...__ghost__[0:-1->0:-1]) -Completable: Cargument Value[someFnTakingVariant](~config) +Completable: Cexpression CArgument Value[someFnTakingVariant](~config) [{ "label": "One", "kind": 4, @@ -125,13 +120,14 @@ Completable: Cargument Value[someFnTakingVariant](~config) Complete src/CompletionFunctionArguments.res 54:40 posCursor:[54:40] posNoWhite:[54:39] Found expr:[54:11->54:41] Pexp_apply ...[54:11->54:30] (~config54:32->54:38=...[54:39->54:40]) -Completable: Cargument Value[someFnTakingVariant](~config=O) +Completable: Cexpression CArgument Value[someFnTakingVariant](~config)=O [{ "label": "One", "kind": 4, "tags": [], "detail": "One\n\ntype someVariant = One | Two | Three(int, string)", "documentation": null, + "sortText": "A One", "insertText": "One", "insertTextFormat": 2 }, { @@ -140,32 +136,52 @@ Completable: Cargument Value[someFnTakingVariant](~config=O) "tags": [], "detail": "module", "documentation": null + }, { + "label": "Obj", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }, { + "label": "Object", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null }] -Complete src/CompletionFunctionArguments.res 57:32 -posCursor:[57:32] posNoWhite:[57:31] Found expr:[57:11->57:33] -Pexp_apply ...[57:11->57:30] (...[57:31->57:32]) -Completable: Cargument Value[someFnTakingVariant]($0=S) +Complete src/CompletionFunctionArguments.res 57:33 +posCursor:[57:33] posNoWhite:[57:32] Found expr:[57:11->57:34] +Pexp_apply ...[57:11->57:30] (...[57:31->57:33]) +Completable: Cexpression CArgument Value[someFnTakingVariant]($0)=So [{ "label": "Some(_)", "kind": 4, "tags": [], "detail": "someVariant", "documentation": null, + "sortText": "A Some(_)", "insertText": "Some(${1:_})", "insertTextFormat": 2 + }, { + "label": "Sort", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null }] Complete src/CompletionFunctionArguments.res 60:44 posCursor:[60:44] posNoWhite:[60:43] Found expr:[60:11->60:45] Pexp_apply ...[60:11->60:30] (~configOpt260:32->60:42=...[60:43->60:44]) -Completable: Cargument Value[someFnTakingVariant](~configOpt2=O) +Completable: Cexpression CArgument Value[someFnTakingVariant](~configOpt2)=O [{ "label": "One", "kind": 4, "tags": [], "detail": "One\n\ntype someVariant = One | Two | Three(int, string)", "documentation": null, + "sortText": "A One", "insertText": "One", "insertTextFormat": 2 }, { @@ -174,12 +190,24 @@ Completable: Cargument Value[someFnTakingVariant](~configOpt2=O) "tags": [], "detail": "module", "documentation": null + }, { + "label": "Obj", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }, { + "label": "Object", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null }] Complete src/CompletionFunctionArguments.res 63:23 posCursor:[63:23] posNoWhite:[63:22] Found expr:[63:11->63:24] Pexp_apply ...[63:11->63:22] (...[63:23->63:24]) -Completable: Cargument Value[someOtherFn]($0) +Completable: Cexpression CArgument Value[someOtherFn]($0) [{ "label": "true", "kind": 4, @@ -197,7 +225,7 @@ Completable: Cargument Value[someOtherFn]($0) Complete src/CompletionFunctionArguments.res 66:28 posCursor:[66:28] posNoWhite:[66:27] Found expr:[66:11->66:30] Pexp_apply ...[66:11->66:22] (...[66:23->66:24], ...[66:26->66:27]) -Completable: Cargument Value[someOtherFn]($2) +Completable: Cexpression CArgument Value[someOtherFn]($2) [{ "label": "true", "kind": 4, @@ -214,13 +242,14 @@ Completable: Cargument Value[someOtherFn]($2) Complete src/CompletionFunctionArguments.res 69:30 posCursor:[69:30] posNoWhite:[69:29] Found expr:[69:11->69:31] -Completable: Cargument Value[someOtherFn]($2=t) +Completable: Cexpression CArgument Value[someOtherFn]($2)=t [{ "label": "true", "kind": 4, "tags": [], "detail": "bool", - "documentation": null + "documentation": null, + "sortText": "A true" }, { "label": "tLocalVar", "kind": 12, @@ -232,7 +261,7 @@ Completable: Cargument Value[someOtherFn]($2=t) Complete src/CompletionFunctionArguments.res 76:25 posCursor:[76:25] posNoWhite:[76:24] Found expr:[76:11->76:26] Pexp_apply ...[76:11->76:24] (...[76:25->76:26]) -Completable: Cargument Value[fnTakingTuple]($0) +Completable: Cexpression CArgument Value[fnTakingTuple]($0) [{ "label": "(_, _, _)", "kind": 12, @@ -243,3 +272,27 @@ Completable: Cargument Value[fnTakingTuple]($0) "insertTextFormat": 2 }] +Complete src/CompletionFunctionArguments.res 89:27 +posCursor:[89:27] posNoWhite:[89:26] Found expr:[89:11->89:29] +Pexp_apply ...[89:11->89:25] (...[89:26->89:28]) +Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordBody +[{ + "label": "age", + "kind": 5, + "tags": [], + "detail": "age: int\n\nsomeRecord", + "documentation": null + }, { + "label": "offline", + "kind": 5, + "tags": [], + "detail": "offline: bool\n\nsomeRecord", + "documentation": null + }, { + "label": "online", + "kind": 5, + "tags": [], + "detail": "online: option\n\nsomeRecord", + "documentation": null + }] + diff --git a/analysis/tests/src/expected/CompletionJsx.res.txt b/analysis/tests/src/expected/CompletionJsx.res.txt new file mode 100644 index 000000000..133aeaec9 --- /dev/null +++ b/analysis/tests/src/expected/CompletionJsx.res.txt @@ -0,0 +1,360 @@ +Complete src/CompletionJsx.res 3:17 +posCursor:[3:17] posNoWhite:[3:16] Found expr:[3:3->3:17] +Completable: Cpath Value[someString]->st +[{ + "label": "Js.String2.startsWith", + "kind": 12, + "tags": [], + "detail": "(t, t) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n```res example\nJs.String2.startsWith(\"BuckleScript\", \"Buckle\") == true\nJs.String2.startsWith(\"BuckleScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Buckle\") == false\n```\n"} + }, { + "label": "Js.String2.startsWithFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n```res example\nJs.String2.startsWithFrom(\"BuckleScript\", \"kle\", 3) == true\nJs.String2.startsWithFrom(\"BuckleScript\", \"\", 3) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Buckle\", 2) == false\n```\n"} + }] + +Complete src/CompletionJsx.res 13:21 +posCursor:[13:21] posNoWhite:[13:20] Found expr:[8:13->31:3] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[9:4->30:10] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[10:4->30:10] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[11:4->30:10] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[12:4->30:10] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[13:7->30:10] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[13:7->13:21] +Completable: Cpath Value[someString]->st <> +[{ + "label": "React.string", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "Turns `string` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "label": "Js.String2.startsWith", + "kind": 12, + "tags": [], + "detail": "(t, t) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n```res example\nJs.String2.startsWith(\"BuckleScript\", \"Buckle\") == true\nJs.String2.startsWith(\"BuckleScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Buckle\") == false\n```\n"} + }, { + "label": "Js.String2.startsWithFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n```res example\nJs.String2.startsWithFrom(\"BuckleScript\", \"kle\", 3) == true\nJs.String2.startsWithFrom(\"BuckleScript\", \"\", 3) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Buckle\", 2) == false\n```\n"} + }] + +Complete src/CompletionJsx.res 18:24 +posCursor:[18:24] posNoWhite:[18:23] Found expr:[8:13->31:3] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[9:4->30:10] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[10:4->30:10] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[11:4->30:10] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[12:4->30:10] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[18:24] posNoWhite:[18:23] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[16:7->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[17:7->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->30:4] +Pexp_construct :::[18:10->30:4] [18:10->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->18:24] +Completable: Cpath Value[someString]->st <> +[{ + "label": "React.string", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "Turns `string` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "label": "Js.String2.startsWith", + "kind": 12, + "tags": [], + "detail": "(t, t) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n```res example\nJs.String2.startsWith(\"BuckleScript\", \"Buckle\") == true\nJs.String2.startsWith(\"BuckleScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Buckle\") == false\n```\n"} + }, { + "label": "Js.String2.startsWithFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n```res example\nJs.String2.startsWithFrom(\"BuckleScript\", \"kle\", 3) == true\nJs.String2.startsWithFrom(\"BuckleScript\", \"\", 3) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Buckle\", 2) == false\n```\n"} + }] + +Complete src/CompletionJsx.res 20:27 +posCursor:[20:27] posNoWhite:[20:26] Found expr:[8:13->31:3] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[9:4->30:10] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[10:4->30:10] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[11:4->30:10] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[12:4->30:10] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[20:27] posNoWhite:[20:26] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[16:7->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[17:7->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->30:4] +Pexp_construct :::[20:10->30:4] [20:10->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->20:27] +Completable: Cpath string->st <> +[{ + "label": "React.string", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "Turns `string` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "label": "Js.String2.startsWith", + "kind": 12, + "tags": [], + "detail": "(t, t) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n```res example\nJs.String2.startsWith(\"BuckleScript\", \"Buckle\") == true\nJs.String2.startsWith(\"BuckleScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Buckle\") == false\n```\n"} + }, { + "label": "Js.String2.startsWithFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n```res example\nJs.String2.startsWithFrom(\"BuckleScript\", \"kle\", 3) == true\nJs.String2.startsWithFrom(\"BuckleScript\", \"\", 3) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Buckle\", 2) == false\n```\n"} + }] + +Complete src/CompletionJsx.res 22:44 +posCursor:[22:44] posNoWhite:[22:43] Found expr:[8:13->31:3] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[9:4->30:10] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[10:4->30:10] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[11:4->30:10] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[12:4->30:10] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[22:44] posNoWhite:[22:43] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[16:7->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[17:7->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[22:10->30:4] +Pexp_construct :::[22:10->30:4] [22:10->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[22:10->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[22:10->22:44] +Completable: Cpath Value[Js, String2, trim](Nolabel)->st <> +[{ + "label": "React.string", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "Turns `string` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "label": "Js.String2.startsWith", + "kind": 12, + "tags": [], + "detail": "(t, t) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n```res example\nJs.String2.startsWith(\"BuckleScript\", \"Buckle\") == true\nJs.String2.startsWith(\"BuckleScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Buckle\") == false\n```\n"} + }, { + "label": "Js.String2.startsWithFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n```res example\nJs.String2.startsWithFrom(\"BuckleScript\", \"kle\", 3) == true\nJs.String2.startsWithFrom(\"BuckleScript\", \"\", 3) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Buckle\", 2) == false\n```\n"} + }] + +Complete src/CompletionJsx.res 24:19 +posCursor:[24:19] posNoWhite:[24:18] Found expr:[8:13->31:3] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[9:4->30:10] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[10:4->30:10] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[11:4->30:10] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[12:4->30:10] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[24:19] posNoWhite:[24:18] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[16:7->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[17:7->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->30:4] +Pexp_construct :::[24:10->30:4] [24:10->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->0:-1] +Completable: Cpath Value[someInt]-> <> +[{ + "label": "React.int", + "kind": 12, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "Turns `int` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "label": "Belt.Int.fromString", + "kind": 12, + "tags": [], + "detail": "string => option", + "documentation": {"kind": "markdown", "value": "\n Converts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n ```res example\n Js.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n ```\n"} + }, { + "label": "Belt.Int.*", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Multiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 * 2 === 4) /* true */\n ```\n"} + }, { + "label": "Belt.Int./", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Division of two `int` values. Same as the division from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(4 / 2 === 2); /* true */\n ```\n"} + }, { + "label": "Belt.Int.toString", + "kind": 12, + "tags": [], + "detail": "int => string", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n ```res example\n Js.log(Belt.Int.toString(1) === \"1\") /* true */\n ```\n"} + }, { + "label": "Belt.Int.toFloat", + "kind": 12, + "tags": [], + "detail": "int => float", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `float`.\n\n ```res example\n Js.log(Belt.Int.toFloat(1) === 1.0) /* true */\n ```\n"} + }, { + "label": "Belt.Int.fromFloat", + "kind": 12, + "tags": [], + "detail": "float => int", + "documentation": {"kind": "markdown", "value": "\n Converts a given `float` to an `int`.\n\n ```res example\n Js.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.-", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Subtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 - 1 === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.+", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Addition of two `int` values. Same as the addition from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 + 2 === 4) /* true */\n ```\n"} + }] + +Complete src/CompletionJsx.res 26:14 +posCursor:[26:14] posNoWhite:[26:13] Found expr:[8:13->31:3] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[9:4->30:10] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[10:4->30:10] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[11:4->30:10] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[12:4->30:10] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[26:14] posNoWhite:[26:13] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[16:7->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[17:7->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->30:4] +Pexp_construct :::[26:10->30:4] [26:10->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->0:-1] +Completable: Cpath int-> <> +[{ + "label": "React.int", + "kind": 12, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "Turns `int` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "label": "Belt.Int.fromString", + "kind": 12, + "tags": [], + "detail": "string => option", + "documentation": {"kind": "markdown", "value": "\n Converts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n ```res example\n Js.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n ```\n"} + }, { + "label": "Belt.Int.*", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Multiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 * 2 === 4) /* true */\n ```\n"} + }, { + "label": "Belt.Int./", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Division of two `int` values. Same as the division from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(4 / 2 === 2); /* true */\n ```\n"} + }, { + "label": "Belt.Int.toString", + "kind": 12, + "tags": [], + "detail": "int => string", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n ```res example\n Js.log(Belt.Int.toString(1) === \"1\") /* true */\n ```\n"} + }, { + "label": "Belt.Int.toFloat", + "kind": 12, + "tags": [], + "detail": "int => float", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `float`.\n\n ```res example\n Js.log(Belt.Int.toFloat(1) === 1.0) /* true */\n ```\n"} + }, { + "label": "Belt.Int.fromFloat", + "kind": 12, + "tags": [], + "detail": "float => int", + "documentation": {"kind": "markdown", "value": "\n Converts a given `float` to an `int`.\n\n ```res example\n Js.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.-", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Subtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 - 1 === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.+", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Addition of two `int` values. Same as the addition from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 + 2 === 4) /* true */\n ```\n"} + }] + +Complete src/CompletionJsx.res 28:20 +posCursor:[28:20] posNoWhite:[28:19] Found expr:[8:13->31:3] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[9:4->30:10] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[10:4->30:10] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[11:4->30:10] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[12:4->30:10] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[28:20] posNoWhite:[28:19] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[16:7->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[17:7->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->30:4] +Pexp_construct :::[28:10->30:4] [28:10->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->28:20] +Completable: Cpath Value[someArr]->a <> +[{ + "label": "React.array", + "kind": 12, + "tags": [], + "detail": "array", + "documentation": {"kind": "markdown", "value": "Turns `array` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "label": "Js.Array2.append", + "kind": 12, + "tags": [1], + "detail": "(t<'a>, 'a) => t<'a>", + "documentation": {"kind": "markdown", "value": "Deprecated: `append` is not type-safe. Use `concat` instead.\n\n"} + }] + diff --git a/analysis/tests/src/expected/CompletionJsxProps.res.txt b/analysis/tests/src/expected/CompletionJsxProps.res.txt index 7656ead99..261fa6328 100644 --- a/analysis/tests/src/expected/CompletionJsxProps.res.txt +++ b/analysis/tests/src/expected/CompletionJsxProps.res.txt @@ -1,7 +1,7 @@ Complete src/CompletionJsxProps.res 0:47 posCursor:[0:47] posNoWhite:[0:46] Found expr:[0:12->0:47] JSX 0:43] on[0:44->0:46]=...__ghost__[0:-1->0:-1]> _children:None -Completable: CjsxPropValue [CompletionSupport, TestComponent] on= +Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] on [{ "label": "true", "kind": 4, @@ -19,7 +19,7 @@ Completable: CjsxPropValue [CompletionSupport, TestComponent] on= Complete src/CompletionJsxProps.res 3:48 posCursor:[3:48] posNoWhite:[3:47] Found expr:[3:12->3:48] JSX 3:43] on[3:44->3:46]=...[3:47->3:48]> _children:None -Completable: CjsxPropValue [CompletionSupport, TestComponent] on=t +Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] on=t [{ "label": "true", "kind": 4, @@ -31,13 +31,14 @@ Completable: CjsxPropValue [CompletionSupport, TestComponent] on=t Complete src/CompletionJsxProps.res 6:50 posCursor:[6:50] posNoWhite:[6:49] Found expr:[6:12->6:50] JSX 6:43] test[6:44->6:48]=...[6:49->6:50]> _children:None -Completable: CjsxPropValue [CompletionSupport, TestComponent] test=T +Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] test=T [{ "label": "Two", "kind": 4, "tags": [], "detail": "Two\n\ntype testVariant = One | Two | Three(int)", "documentation": null, + "sortText": "A Two", "insertText": "Two", "insertTextFormat": 2 }, { @@ -46,14 +47,27 @@ Completable: CjsxPropValue [CompletionSupport, TestComponent] test=T "tags": [], "detail": "Three(int)\n\ntype testVariant = One | Two | Three(int)", "documentation": null, + "sortText": "A Three(_)", "insertText": "Three(${1:_})", "insertTextFormat": 2 + }, { + "label": "TableclothMap", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }, { + "label": "TypeDefinition", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null }] Complete src/CompletionJsxProps.res 9:52 posCursor:[9:52] posNoWhite:[9:51] Found expr:[9:12->9:52] JSX 9:43] polyArg[9:44->9:51]=...__ghost__[0:-1->0:-1]> _children:None -Completable: CjsxPropValue [CompletionSupport, TestComponent] polyArg= +Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyArg [{ "label": "#one", "kind": 4, @@ -91,7 +105,7 @@ Completable: CjsxPropValue [CompletionSupport, TestComponent] polyArg= Complete src/CompletionJsxProps.res 12:54 posCursor:[12:54] posNoWhite:[12:53] Found expr:[12:12->12:54] JSX 12:43] polyArg[12:44->12:51]=...[12:52->12:54]> _children:None -Completable: CjsxPropValue [CompletionSupport, TestComponent] polyArg=#t +Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyArg=#t [{ "label": "#three(_, _)", "kind": 4, diff --git a/analysis/tests/src/expected/CompletionPattern.res.txt b/analysis/tests/src/expected/CompletionPattern.res.txt index 5b97cb151..a1e346ef3 100644 --- a/analysis/tests/src/expected/CompletionPattern.res.txt +++ b/analysis/tests/src/expected/CompletionPattern.res.txt @@ -80,7 +80,7 @@ Completable: Cpattern Value[f] "tags": [], "detail": "someRecord", "documentation": null, - "sortText": "a", + "sortText": "A", "insertText": "{$0}", "insertTextFormat": 2 }] @@ -170,7 +170,7 @@ Completable: Cpattern Value[f]->recordField(nest) "tags": [], "detail": "nestedRecord", "documentation": null, - "sortText": "a", + "sortText": "A", "insertText": "{$0}", "insertTextFormat": 2 }] @@ -414,7 +414,7 @@ Completable: Cpattern Value[c] "tags": [], "detail": "bool", "documentation": null, - "sortText": "a", + "sortText": "A", "insertText": "[$0]", "insertTextFormat": 2 }] @@ -497,6 +497,18 @@ Completable: Cpattern Value[p]->variantPayload::Test($2) "documentation": null, "insertText": "Some(${1:_})", "insertTextFormat": 2 + }, { + "label": "Some(true)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(false)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null }] Complete src/CompletionPattern.res 140:23 @@ -531,7 +543,7 @@ Completable: Cpattern Value[p]->variantPayload::Test($3) "tags": [], "detail": "bool", "documentation": null, - "sortText": "a", + "sortText": "A", "insertText": "[$0]", "insertTextFormat": 2 }] @@ -573,6 +585,18 @@ Completable: Cpattern Value[v]->polyvariantPayload::test($2) "documentation": null, "insertText": "Some(${1:_})", "insertTextFormat": 2 + }, { + "label": "Some(true)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(false)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null }] Complete src/CompletionPattern.res 156:24 @@ -605,7 +629,7 @@ Completable: Cpattern Value[v]->polyvariantPayload::test($3) "tags": [], "detail": "bool", "documentation": null, - "sortText": "a", + "sortText": "A", "insertText": "[$0]", "insertTextFormat": 2 }] @@ -647,6 +671,18 @@ Completable: Cpattern Value[s]->tuple($1) "documentation": null, "insertText": "Some(${1:_})", "insertTextFormat": 2 + }, { + "label": "Some(true)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(false)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null }] Complete src/CompletionPattern.res 170:22 @@ -667,6 +703,18 @@ Completable: Cpattern Value[s]->tuple($1) "documentation": null, "insertText": "Some(${1:_})", "insertTextFormat": 2 + }, { + "label": "Some(true)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(false)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null }] Complete src/CompletionPattern.res 173:35 @@ -700,6 +748,18 @@ Completable: Cpattern Value[s]->tuple($1) "documentation": null, "insertText": "Some(${1:_})", "insertTextFormat": 2 + }, { + "label": "Some(true)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(false)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null }] Complete src/CompletionPattern.res 179:21 diff --git a/analysis/tests/src/expected/Jsx2.res.txt b/analysis/tests/src/expected/Jsx2.res.txt index 00d97ddb4..5cd54d307 100644 --- a/analysis/tests/src/expected/Jsx2.res.txt +++ b/analysis/tests/src/expected/Jsx2.res.txt @@ -6,7 +6,7 @@ the type is not great but jump to definition works Complete src/Jsx2.res 8:15 posCursor:[8:15] posNoWhite:[8:14] Found expr:[8:4->8:15] JSX 8:5] second[8:6->8:12]=...[8:13->8:15]> _children:None -Completable: CjsxPropValue [M] second=fi +Completable: Cexpression CJsxPropValue [M] second=fi [] Complete src/Jsx2.res 11:20 diff --git a/analysis/tests/src/expected/SignatureHelp.res.txt b/analysis/tests/src/expected/SignatureHelp.res.txt index f6b960ff5..802874b2f 100644 --- a/analysis/tests/src/expected/SignatureHelp.res.txt +++ b/analysis/tests/src/expected/SignatureHelp.res.txt @@ -266,3 +266,45 @@ extracted params: "activeParameter": 2 } +Signature help src/SignatureHelp.res 71:29 +posCursor:[71:28] posNoWhite:[71:27] Found expr:[71:11->71:33] +Pexp_apply ...[71:11->71:13] (...[71:16->71:30]) +posCursor:[71:28] posNoWhite:[71:27] Found expr:[71:16->71:30] +Pexp_apply ...[71:16->71:28] (...[71:29->71:30]) +posCursor:[71:28] posNoWhite:[71:27] Found expr:[71:16->71:28] +Pexp_ident iAmSoSpecial:[71:16->71:28] +argAtCursor: unlabelled<0> +extracted params: +[string] +{ + "signatures": [{ + "label": "string => unit", + "parameters": [{"label": [0, 6], "documentation": {"kind": "markdown", "value": "```rescript\nstring\n```"}}] + }], + "activeSignature": 0, + "activeParameter": 0 +} + +Signature help src/SignatureHelp.res 74:40 +posCursor:[74:39] posNoWhite:[74:38] Found expr:[74:11->74:47] +Pexp_apply ...[74:11->74:13] (...[74:16->74:44]) +posCursor:[74:39] posNoWhite:[74:38] Found expr:[74:16->74:44] +Pexp_apply ...[74:16->74:28] (...[74:31->74:41]) +posCursor:[74:39] posNoWhite:[74:38] Found expr:[74:31->74:41] +Pexp_apply ...[74:31->74:39] (...[74:40->74:41]) +posCursor:[74:39] posNoWhite:[74:38] Found expr:[74:31->74:39] +Pexp_ident someFunc:[74:31->74:39] +argAtCursor: unlabelled<0> +extracted params: +[( + int, ~two: string=?, ~three: unit => unit, ~four: someVariant, unit] +{ + "signatures": [{ + "label": "(\n int,\n ~two: string=?,\n ~three: unit => unit,\n ~four: someVariant,\n unit,\n) => unit", + "parameters": [{"label": [0, 7], "documentation": {"kind": "markdown", "value": "```rescript\nint\n```"}}, {"label": [11, 25], "documentation": {"kind": "markdown", "value": "```rescript\noption\n```"}}, {"label": [29, 49], "documentation": {"kind": "markdown", "value": ""}}, {"label": [53, 71], "documentation": {"kind": "markdown", "value": "```rescript\nsomeVariant\n```\n```rescript\ntype someVariant = One | Two | Three\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C0%2C0%5D)"}}, {"label": [75, 79], "documentation": {"kind": "markdown", "value": "```rescript\nint\n```"}}], + "documentation": {"kind": "markdown", "value": " Does stuff. "} + }], + "activeSignature": 0, + "activeParameter": 0 +} + diff --git a/server/src/server.ts b/server/src/server.ts index 696247abf..9bf74d1e6 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1135,7 +1135,7 @@ function onMessage(msg: p.Message) { ], tokenModifiers: [], }, - documentSelector: null, + documentSelector: [{ scheme: "file", language: "rescript" }], // TODO: Support range for full, and add delta support full: true, },