diff --git a/analysis/src/Cli.ml b/analysis/src/Cli.ml index 84ef96d0e..3a9a5fbb7 100644 --- a/analysis/src/Cli.ml +++ b/analysis/src/Cli.ml @@ -68,8 +68,9 @@ Options: let main () = match Array.to_list Sys.argv with | [_; "completion"; path; line; col; currentFile] -> - Commands.completion ~path ~line:(int_of_string line) - ~col:(int_of_string col) ~currentFile + Commands.completion ~debug:false ~path + ~pos:(int_of_string line, int_of_string col) + ~currentFile | [_; "definition"; path; line; col] -> Commands.definition ~path ~line:(int_of_string line) ~col:(int_of_string col) diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index e53a2bc5f..dc6de6cb4 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -1,24 +1,32 @@ -open SharedTypes - -let completion ~path ~line ~col ~currentFile = - let pos = (line, col) in +let completion ~debug ~path ~pos ~currentFile = let result = let textOpt = Files.readFile currentFile in - let completionItems = - match NewCompletions.getCompletable ~textOpt ~pos with - | None -> [] - | Some (completable, rawOpens) -> ( - (* Only perform expensive ast operations if there are completables *) - match Cmt.fromPath ~path with + match textOpt with + | None | Some "" -> [] + | Some text -> + let completionItems = + match + Completion.completionWithParser ~debug ~path ~posCursor:pos + ~currentFile ~text + with | None -> [] - | Some full -> - NewCompletions.computeCompletions ~completable ~full ~pos ~rawOpens) - in - completionItems - |> List.map Protocol.stringifyCompletionItem - |> Protocol.array + | Some (completable, scope) -> ( + if debug then + Printf.printf "Completable: %s\n" + (SharedTypes.Completable.toString completable); + (* Only perform expensive ast operations if there are completables *) + match Cmt.fromPath ~path with + | None -> [] + | Some full -> + let env = SharedTypes.QueryEnv.fromFile full.file in + let package = full.package in + NewCompletions.computeCompletions ~completable ~package ~pos ~scope + ~env) + in + completionItems in - print_endline result + print_endline + (result |> List.map Protocol.stringifyCompletionItem |> Protocol.array) let hover ~path ~line ~col = let result = @@ -291,7 +299,7 @@ let test ~path = let line = line + 1 in let col = len - mlen - 3 in close_out cout; - completion ~path ~line ~col ~currentFile; + completion ~debug:true ~path ~pos:(line, col) ~currentFile; Sys.remove currentFile | "hig" -> print_endline ("Highlight " ^ path); diff --git a/analysis/src/Completion.ml b/analysis/src/Completion.ml new file mode 100644 index 000000000..a3083fb21 --- /dev/null +++ b/analysis/src/Completion.ml @@ -0,0 +1,744 @@ +open SharedTypes + +let rec skipWhite text i = + if i < 0 then 0 + else + match text.[i] with + | ' ' | '\n' | '\r' | '\t' -> skipWhite text (i - 1) + | _ -> i + +let offsetOfLine text line = + let ln = String.length text in + let rec loop i lno = + if i >= ln then None + else + match text.[i] with + | '\n' -> if lno = line - 1 then Some (i + 1) else loop (i + 1) (lno + 1) + | _ -> loop (i + 1) lno + in + match line with 0 -> Some 0 | _ -> loop 0 0 + +let positionToOffset text (line, character) = + match offsetOfLine text line with + | None -> None + | Some bol -> Some (bol + character) + +type prop = { + name : string; + posStart : int * int; + posEnd : int * int; + exp : Parsetree.expression; +} + +type jsxProps = { + componentPath : string list; + props : prop list; + childrenStart : (int * int) option; +} + +let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor ~posAfterCompName + = + let allLabels = + List.fold_right + (fun prop allLabels -> prop.name :: allLabels) + jsxProps.props [] + in + let rec loop props = + match props with + | prop :: rest -> + if prop.posStart <= posBeforeCursor && posBeforeCursor < prop.posEnd then + Some (Completable.Cjsx (jsxProps.componentPath, prop.name, allLabels)) + else if + prop.posEnd <= posBeforeCursor + && posBeforeCursor < Loc.start prop.exp.pexp_loc + then None + else if prop.exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then None + else loop rest + | [] -> + let beforeChildrenStart = + match jsxProps.childrenStart with + | Some childrenPos -> posBeforeCursor < childrenPos + | None -> posBeforeCursor <= endPos + in + let afterCompName = posBeforeCursor >= posAfterCompName in + if afterCompName && beforeChildrenStart then + Some (Cjsx (jsxProps.componentPath, "", allLabels)) + else None + in + loop jsxProps.props + +let rec skipLineComment ~pos ~i str = + if i < String.length str then + match str.[i] with + | '\n' -> Some ((fst pos + 1, 0), i + 1) + | _ -> skipLineComment ~pos:(fst pos, snd pos + 1) ~i:(i + 1) str + else None + +let rec skipComment ~pos ~i ~depth str = + if i < String.length str then + match str.[i] with + | '\n' -> skipComment ~depth ~pos:(fst pos + 1, 0) ~i:(i + 1) str + | '/' when i + 1 < String.length str && str.[i + 1] = '*' -> + skipComment ~depth:(depth + 1) ~pos:(fst pos, snd pos + 2) ~i:(i + 2) str + | '*' when i + 1 < String.length str && str.[i + 1] = '/' -> + if depth > 1 then + skipComment ~depth:(depth - 1) + ~pos:(fst pos, snd pos + 2) + ~i:(i + 2) str + else Some ((fst pos, snd pos + 2), i + 2) + | _ -> skipComment ~depth ~pos:(fst pos, snd pos + 1) ~i:(i + 1) str + else None + +let extractJsxProps ~text ~(compName : Longident.t Location.loc) ~args = + let rec extractLabelPos ~pos ~i str = + if i < String.length str then + match str.[i] with + | '/' when i + 1 < String.length str && str.[i + 1] = '/' -> ( + match skipLineComment ~pos ~i str with + | Some (pos, i) -> extractLabelPos ~pos ~i str + | None -> None) + | '/' when i + 1 < String.length str && str.[i + 1] = '*' -> ( + match skipComment ~depth:0 ~pos ~i str with + | Some (pos, i) -> extractLabelPos ~pos ~i str + | None -> None) + | ' ' | '\r' | '\t' -> + extractLabelPos ~pos:(fst pos, snd pos + 1) ~i:(i + 1) str + | '\n' -> extractLabelPos ~pos:(fst pos + 1, 0) ~i:(i + 1) str + | 'a' .. 'z' | 'A' .. 'Z' | '_' | '0' .. '9' -> Some pos + | _ -> None + else None + in + let thisCaseShouldNotHappen = + {componentPath = []; props = []; childrenStart = None} + in + let rec processProps ~lastOffset ~lastPos ~acc args = + match args with + | (Asttypes.Labelled "children", {Parsetree.pexp_loc}) :: _ -> + { + componentPath = Utils.flattenLongIdent ~jsx:true compName.txt; + props = List.rev acc; + childrenStart = + (if pexp_loc.loc_ghost then None else Some (Loc.start pexp_loc)); + } + | ((Labelled s | Optional s), (eProp : Parsetree.expression)) :: rest -> ( + let ePosStart, ePosEnd = Loc.range eProp.pexp_loc in + match + (positionToOffset text ePosStart, positionToOffset text ePosEnd) + with + | Some offsetStart, Some offsetEnd when not eProp.pexp_loc.loc_ghost -> + let label = String.sub text lastOffset (offsetStart - lastOffset) in + let labelPos = + match extractLabelPos ~pos:lastPos ~i:0 label with + | Some pos -> pos + | None -> (* Must be punned *) ePosStart + in + processProps + ~acc: + ({ + name = s; + posStart = labelPos; + posEnd = (fst labelPos, snd labelPos + String.length s); + exp = eProp; + } + :: acc) + ~lastOffset:offsetEnd ~lastPos:ePosEnd rest + | _ -> thisCaseShouldNotHappen) + | _ -> thisCaseShouldNotHappen + in + let posAfterCompName = Loc.end_ compName.loc in + let offsetAfterCompName = + match positionToOffset text posAfterCompName with + | None -> assert false + | Some offset -> offset + in + args + |> processProps ~lastOffset:offsetAfterCompName ~lastPos:posAfterCompName + ~acc:[] + +type labelled = { + name : string; + opt : bool; + posStart : int * int; + posEnd : int * int; +} + +type label = labelled option +type arg = {label : label; exp : Parsetree.expression} + +let findExpApplyCompletable ~(args : arg list) ~endPos ~posBeforeCursor + ~(funName : Longident.t Location.loc) = + let funPath = Utils.flattenLongIdent funName.txt in + let posAfterFunName = Loc.end_ funName.loc in + let allNames = + List.fold_right + (fun arg allLabels -> + match arg with + | {label = Some labelled} -> labelled.name :: allLabels + | {label = None} -> allLabels) + args [] + in + let rec loop args = + match args with + | {label = Some labelled; exp} :: rest -> + if + labelled.posStart <= posBeforeCursor + && posBeforeCursor < labelled.posEnd + then Some (Completable.Clabel (funPath, labelled.name, allNames)) + else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then None + else loop rest + | {label = None; exp} :: rest -> + if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then None + else loop rest + | [] -> + if posAfterFunName <= posBeforeCursor && posBeforeCursor < endPos then + Some (Clabel (funPath, "", allNames)) + else None + in + loop args + +let extractExpApplyArgs ~text ~(funName : Longident.t Location.loc) ~args = + let rec extractLabelPos ~pos ~i str = + if i < String.length str then + match str.[i] with + | '/' when i + 1 < String.length str && str.[i + 1] = '/' -> ( + match skipLineComment ~pos ~i str with + | Some (pos, i) -> extractLabelPos ~pos ~i str + | None -> None) + | '/' when i + 1 < String.length str && str.[i + 1] = '*' -> ( + match skipComment ~depth:0 ~pos ~i str with + | Some (pos, i) -> extractLabelPos ~pos ~i str + | None -> None) + | ' ' | '\r' | '\t' | ',' | '(' | '~' -> + extractLabelPos ~pos:(fst pos, snd pos + 1) ~i:(i + 1) str + | '\n' -> extractLabelPos ~pos:(fst pos + 1, 0) ~i:(i + 1) str + | 'a' .. 'z' | 'A' .. 'Z' | '_' | '0' .. '9' -> Some pos + | _ -> None + else None + in + let rec processArgs ~lastOffset ~lastPos ~acc args = + match args with + | (((Asttypes.Labelled s | Optional s) as label), (e : Parsetree.expression)) + :: rest -> ( + let ePosStart, ePosEnd = Loc.range e.pexp_loc in + match + (positionToOffset text ePosStart, positionToOffset text ePosEnd) + with + | Some offsetStart, Some offsetEnd -> + let labelText = String.sub text lastOffset (offsetStart - lastOffset) in + let labelPos = + match extractLabelPos ~pos:lastPos ~i:0 labelText with + | Some pos -> pos + | None -> (* Must be punned *) ePosStart + in + let labelled = + { + name = s; + opt = (match label with Optional _ -> true | _ -> false); + posStart = labelPos; + posEnd = (fst labelPos, snd labelPos + String.length s); + } + in + processArgs + ~acc:({label = Some labelled; exp = e} :: acc) + ~lastOffset:offsetEnd ~lastPos:ePosEnd rest + | _ -> assert false) + | (Asttypes.Nolabel, (e : Parsetree.expression)) :: rest -> ( + if e.pexp_loc.loc_ghost then processArgs ~acc ~lastOffset ~lastPos rest + else + let ePosEnd = Loc.end_ e.pexp_loc in + match positionToOffset text ePosEnd with + | Some offsetEnd -> + processArgs + ~acc:({label = None; exp = e} :: acc) + ~lastOffset:offsetEnd ~lastPos:ePosEnd rest + | _ -> + (* should not happen *) + assert false) + | [] -> List.rev acc + in + let lastPos = Loc.end_ funName.loc in + let lastOffset = + match positionToOffset text lastPos with + | Some offset -> offset + | None -> assert false + in + args |> processArgs ~lastOffset ~lastPos ~acc:[] + +let rec exprToContextPath (e : Parsetree.expression) = + match e.pexp_desc with + | Pexp_constant (Pconst_string _) -> Some Completable.CPString + | Pexp_array _ -> Some CPArray + | Pexp_ident {txt} -> Some (CPId (Utils.flattenLongIdent txt, Value)) + | Pexp_field (e1, {txt = Lident name}) -> ( + match exprToContextPath e1 with + | Some contextPath -> Some (CPField (contextPath, name)) + | _ -> None) + | Pexp_field (_, {txt = Ldot (lid, name)}) -> + (* Case x.M.field ignore the x part *) + Some (CPField (CPId (Utils.flattenLongIdent lid, Module), name)) + | Pexp_send (e1, {txt}) -> ( + match exprToContextPath e1 with + | None -> None + | Some contexPath -> Some (CPObj (contexPath, txt))) + | _ -> None + +let completionWithParser ~debug ~path ~posCursor ~currentFile ~text = + let offset = + match positionToOffset text posCursor with + | Some offset -> offset + | None -> assert false + in + let offsetNoWhite = skipWhite text (offset - 1) in + let posNoWhite = + let line, col = posCursor in + (line, max 0 col - offset + offsetNoWhite) + in + let posBeforeCursor = (fst posCursor, max 0 (snd posCursor - 1)) in + let blankAfterCursor = + match positionToOffset text posCursor with + | Some offset when offset > 0 -> ( + let charBeforeCursor = text.[offset - 1] in + let charAtCursor = + if offset < String.length text then text.[offset] else '\n' + in + match charAtCursor with + | ' ' | '\t' | '\r' | '\n' -> Some charBeforeCursor + | _ -> None) + | _ -> None + in + + let found = ref false in + let result = ref None in + let scope = ref (Scope.create ()) in + let setResultOpt x = + if !result = None then + match x with None -> () | Some x -> result := Some (x, !scope) + in + let setResult x = setResultOpt (Some x) in + let scopeValueDescription (vd : Parsetree.value_description) = + scope := + !scope |> Scope.addValue ~name:vd.pval_name.txt ~loc:vd.pval_name.loc + in + let scopeValueBinding (vb : Parsetree.value_binding) = + match vb.pvb_pat.ppat_desc with + | Ppat_var {txt; loc} + | Ppat_constraint ({ppat_desc = Ppat_var {txt; loc}}, _) -> + scope := !scope |> Scope.addValue ~name:txt ~loc + | _ -> () + in + let scopeTypeKind (tk : Parsetree.type_kind) = + match tk with + | Ptype_variant constrDecls -> + constrDecls + |> List.iter (fun (cd : Parsetree.constructor_declaration) -> + scope := + !scope + |> Scope.addConstructor ~name:cd.pcd_name.txt ~loc:cd.pcd_loc) + | Ptype_record labelDecls -> + labelDecls + |> List.iter (fun (ld : Parsetree.label_declaration) -> + scope := + !scope |> Scope.addField ~name:ld.pld_name.txt ~loc:ld.pld_loc) + | _ -> () + in + let scopeTypeDeclaration (td : Parsetree.type_declaration) = + scope := + !scope |> Scope.addType ~name:td.ptype_name.txt ~loc:td.ptype_name.loc; + scopeTypeKind td.ptype_kind + in + let scopeModuleBinding (mb : Parsetree.module_binding) = + scope := + !scope |> Scope.addModule ~name:mb.pmb_name.txt ~loc:mb.pmb_name.loc + in + let scopeModuleDeclaration (md : Parsetree.module_declaration) = + scope := + !scope |> Scope.addModule ~name:md.pmd_name.txt ~loc:md.pmd_name.loc + in + + let structure (iterator : Ast_iterator.iterator) + (structure : Parsetree.structure) = + let oldScope = !scope in + Ast_iterator.default_iterator.structure iterator structure; + scope := oldScope + in + let structure_item (iterator : Ast_iterator.iterator) + (item : Parsetree.structure_item) = + let processed = ref false in + (match item.pstr_desc with + | Pstr_open {popen_lid} -> + scope := !scope |> Scope.addOpen ~lid:popen_lid.txt + | Pstr_primitive vd -> scopeValueDescription vd + | Pstr_value (recFlag, bindings) -> + if recFlag = Recursive then bindings |> List.iter scopeValueBinding; + bindings |> List.iter (fun vb -> iterator.value_binding iterator vb); + if recFlag = Nonrecursive then bindings |> List.iter scopeValueBinding; + processed := true + | Pstr_type (recFlag, decls) -> + if recFlag = Recursive then decls |> List.iter scopeTypeDeclaration; + decls |> List.iter (fun td -> iterator.type_declaration iterator td); + if recFlag = Nonrecursive then decls |> List.iter scopeTypeDeclaration; + processed := true + | Pstr_module mb -> + iterator.module_binding iterator mb; + scopeModuleBinding mb; + processed := true + | Pstr_recmodule mbs -> + mbs |> List.iter scopeModuleBinding; + mbs |> List.iter (fun b -> iterator.module_binding iterator b); + processed := true + | _ -> ()); + if not !processed then + Ast_iterator.default_iterator.structure_item iterator item + in + let signature (iterator : Ast_iterator.iterator) + (signature : Parsetree.signature) = + let oldScope = !scope in + Ast_iterator.default_iterator.signature iterator signature; + scope := oldScope + in + let signature_item (iterator : Ast_iterator.iterator) + (item : Parsetree.signature_item) = + let processed = ref false in + (match item.psig_desc with + | Psig_open {popen_lid} -> + scope := !scope |> Scope.addOpen ~lid:popen_lid.txt + | Psig_value vd -> scopeValueDescription vd + | Psig_type (recFlag, decls) -> + if recFlag = Recursive then decls |> List.iter scopeTypeDeclaration; + decls |> List.iter (fun td -> iterator.type_declaration iterator td); + if recFlag = Nonrecursive then decls |> List.iter scopeTypeDeclaration; + processed := true + | Psig_module md -> + iterator.module_declaration iterator md; + scopeModuleDeclaration md; + processed := true + | Psig_recmodule mds -> + mds |> List.iter scopeModuleDeclaration; + mds |> List.iter (fun d -> iterator.module_declaration iterator d); + processed := true + | _ -> ()); + if not !processed then + Ast_iterator.default_iterator.signature_item iterator item + in + let attribute (iterator : Ast_iterator.iterator) + ((id, payload) : Parsetree.attribute) = + (if String.length id.txt >= 3 && String.sub id.txt 0 3 = "ns." then + (* skip: internal parser attribute *) () + else if id.loc.loc_ghost then () + else if id.loc |> Loc.hasPos ~pos:posBeforeCursor then + let posStart, posEnd = Loc.range id.loc in + match (positionToOffset text posStart, positionToOffset text posEnd) with + | Some offsetStart, Some offsetEnd -> + (* Can't trust the parser's location + E.g. @foo. let x... gives as label @foo.let *) + let label = + let rawLabel = + String.sub text offsetStart (offsetEnd - offsetStart) + in + let ( ++ ) x y = + match (x, y) with + | Some i1, Some i2 -> Some (min i1 i2) + | Some _, None -> x + | None, _ -> y + in + let label = + match + String.index_opt rawLabel ' ' + ++ String.index_opt rawLabel '\t' + ++ String.index_opt rawLabel '\r' + ++ String.index_opt rawLabel '\n' + with + | None -> rawLabel + | Some i -> String.sub rawLabel 0 i + in + if label <> "" && label.[0] = '@' then + String.sub label 1 (String.length label - 1) + else label + in + found := true; + if debug then + Printf.printf "Attribute id:%s:%s label:%s\n" id.txt + (Loc.toString id.loc) label; + setResult (Completable.Cdecorator label) + | _ -> ()); + Ast_iterator.default_iterator.attribute iterator (id, payload) + in + let expr (iterator : Ast_iterator.iterator) (expr : Parsetree.expression) = + let processed = ref false in + let setFound () = + found := true; + if debug then + Printf.printf "posCursor:[%s] posNoWhite:[%s] Found expr:%s\n" + (Pos.toString posCursor) (Pos.toString posNoWhite) + (Loc.toString expr.pexp_loc) + in + let setPipeResult ~(lhs : Parsetree.expression) ~id = + match exprToContextPath lhs with + | Some pipe -> + setResult (Cpath (CPPipe (pipe, id))); + true + | None -> false + in + match expr.pexp_desc with + | Pexp_apply + ( {pexp_desc = Pexp_ident {txt = Lident "|."; loc = opLoc}}, + [ + (_, lhs); + (_, {pexp_desc = Pexp_extension _; pexp_loc = {loc_ghost = true}}); + ] ) + when opLoc |> Loc.hasPos ~pos:posBeforeCursor -> + (* Case foo-> when the parser adds a ghost expression to the rhs + so the apply expression does not include the cursor *) + if setPipeResult ~lhs ~id:"" then setFound () + | _ -> + if expr.pexp_loc |> Loc.hasPos ~pos:posNoWhite then ( + setFound (); + match expr.pexp_desc with + | Pexp_ident id -> + if debug then + Printf.printf "Pexp_ident %s:%s\n" + (Utils.flattenLongIdent id.txt |> String.concat ".") + (Loc.toString id.loc); + if id.loc |> Loc.hasPos ~pos:posBeforeCursor then + let path_ = id.txt |> Utils.flattenLongIdent in + let path = + if blankAfterCursor = Some '.' then ( + (* Sometimes "Foo. " is followed by "bar" and the parser's + behaviour is to parse as "Foo.bar". + This gets back the intended path "Foo." *) + let path = + match path_ |> List.rev with + | _last :: pathRev -> List.rev ("" :: pathRev) + | path -> path + in + if debug then + Printf.printf "Id breaks up. New path:%s\n" + (path |> String.concat "."); + path) + else path_ + in + setResult (Cpath (CPId (path, Value))) + | Pexp_construct (id, eOpt) -> + if debug then + Printf.printf "Pexp_construct %s:%s %s\n" + (Utils.flattenLongIdent id.txt |> String.concat "\n") + (Loc.toString id.loc) + (match eOpt with + | None -> "None" + | Some e -> Loc.toString e.pexp_loc); + if + eOpt = None && (not id.loc.loc_ghost) + && id.loc |> Loc.hasPos ~pos:posBeforeCursor + then setResult (Cpath (CPId (Utils.flattenLongIdent id.txt, Value))) + | Pexp_field (e, fieldName) -> ( + if debug then + Printf.printf "Pexp_field %s %s:%s\n" (Loc.toString e.pexp_loc) + (Utils.flattenLongIdent fieldName.txt |> String.concat ".") + (Loc.toString fieldName.loc); + if fieldName.loc |> Loc.hasPos ~pos:posBeforeCursor then + match fieldName.txt with + | Lident name -> ( + match exprToContextPath e with + | Some contextPath -> + let contextPath = Completable.CPField (contextPath, name) in + setResult (Cpath contextPath) + | None -> ()) + | Ldot (id, name) -> + (* Case x.M.field ignore the x part *) + let contextPath = + Completable.CPField + ( CPId (Utils.flattenLongIdent id, Module), + if name = "_" then "" else name ) + in + setResult (Cpath contextPath) + | Lapply _ -> () + else if Loc.end_ e.pexp_loc = posBeforeCursor then + match exprToContextPath e with + | Some contextPath -> setResult (Cpath (CPField (contextPath, ""))) + | None -> ()) + | Pexp_apply ({pexp_desc = Pexp_ident compName}, args) + when Res_parsetree_viewer.isJsxExpression expr -> + let jsxProps = extractJsxProps ~text ~compName ~args in + if debug then + Printf.printf "JSX <%s:%s %s> _children:%s\n" + (jsxProps.componentPath |> String.concat ",") + (Loc.toString compName.loc) + (jsxProps.props + |> List.map (fun {name; posStart; posEnd; exp} -> + Printf.sprintf "%s[%s->%s]=...%s" name + (Pos.toString posStart) (Pos.toString posEnd) + (Loc.toString exp.pexp_loc)) + |> String.concat " ") + (match jsxProps.childrenStart with + | None -> "None" + | Some childrenPosStart -> Pos.toString childrenPosStart); + let jsxCompletable = + findJsxPropsCompletable ~jsxProps ~endPos:(Loc.end_ expr.pexp_loc) + ~posBeforeCursor ~posAfterCompName:(Loc.end_ compName.loc) + in + if jsxCompletable <> None then setResultOpt jsxCompletable + else if compName.loc |> Loc.hasPos ~pos:posBeforeCursor then + setResult + (Cpath + (CPId (Utils.flattenLongIdent ~jsx:true compName.txt, Module))) + | Pexp_apply + ( {pexp_desc = Pexp_ident {txt = Lident "|."}}, + [ + (_, lhs); + (_, {pexp_desc = Pexp_ident {txt = Longident.Lident id; loc}}); + ] ) + when loc |> Loc.hasPos ~pos:posBeforeCursor -> + (* Case foo->id *) + setPipeResult ~lhs ~id |> ignore + | Pexp_apply + ( {pexp_desc = Pexp_ident {txt = Lident "|."; loc = opLoc}}, + [(_, lhs); _] ) + when Loc.end_ opLoc = posCursor -> + (* Case foo-> *) + setPipeResult ~lhs ~id:"" |> ignore + | Pexp_apply ({pexp_desc = Pexp_ident {txt = Lident "|."}}, [_; _]) -> + () + | Pexp_apply ({pexp_desc = Pexp_ident funName}, args) -> + let args = extractExpApplyArgs ~text ~funName ~args in + if debug then + Printf.printf "Pexp_apply ...%s (%s)\n" (Loc.toString funName.loc) + (args + |> List.map (fun {label; exp} -> + Printf.sprintf "%s...%s" + (match label with + | None -> "" + | Some {name; opt; posStart; posEnd} -> + "~" ^ name ^ Pos.toString posStart ^ "->" + ^ Pos.toString posEnd ^ "=" + ^ if opt then "?" else "") + (Loc.toString exp.pexp_loc)) + |> String.concat ", "); + let expApplyCompletable = + findExpApplyCompletable ~funName ~args + ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor + in + setResultOpt expApplyCompletable + | Pexp_send (lhs, {txt; loc}) -> ( + (* e["txt"] + If the string for txt is not closed, it could go over several lines. + Only take the first like to represent the label *) + let txtLines = txt |> String.split_on_char '\n' in + let label = List.hd txtLines in + let label = + if label <> "" && label.[String.length label - 1] = '\r' then + String.sub label 0 (String.length label - 1) + else label + in + let labelRange = + let l, c = Loc.start loc in + ((l, c + 1), (l, c + 1 + String.length label)) + in + if debug then + Printf.printf "Pexp_send %s%s e:%s\n" label + (Range.toString labelRange) + (Loc.toString lhs.pexp_loc); + if + labelRange |> Range.hasPos ~pos:posBeforeCursor + || (label = "" && posCursor = fst labelRange) + then + match exprToContextPath lhs with + | Some contextPath -> setResult (Cpath (CPObj (contextPath, label))) + | None -> ()) + | Pexp_let (recFlag, bindings, e) -> + let oldScope = !scope in + if recFlag = Recursive then bindings |> List.iter scopeValueBinding; + bindings |> List.iter (fun vb -> iterator.value_binding iterator vb); + if recFlag = Nonrecursive then bindings |> List.iter scopeValueBinding; + iterator.expr iterator e; + scope := oldScope; + processed := true + | Pexp_letmodule (name, modExpr, modBody) -> + let oldScope = !scope in + iterator.location iterator name.loc; + iterator.module_expr iterator modExpr; + scope := !scope |> Scope.addModule ~name:name.txt ~loc:name.loc; + iterator.expr iterator modBody; + scope := oldScope; + processed := true + | _ -> ()); + if not !processed then Ast_iterator.default_iterator.expr iterator expr + in + let typ (iterator : Ast_iterator.iterator) (core_type : Parsetree.core_type) = + if core_type.ptyp_loc |> Loc.hasPos ~pos:posNoWhite then ( + found := true; + if debug then + Printf.printf "posCursor:[%s] posNoWhite:[%s] Found type:%s\n" + (Pos.toString posCursor) (Pos.toString posNoWhite) + (Loc.toString core_type.ptyp_loc); + match core_type.ptyp_desc with + | Ptyp_constr (id, _args) -> + if debug then + Printf.printf "Ptyp_constr %s:%s\n" + (Utils.flattenLongIdent id.txt |> String.concat ".") + (Loc.toString id.loc); + if id.loc |> Loc.hasPos ~pos:posBeforeCursor then + setResult (Cpath (CPId (Utils.flattenLongIdent id.txt, Type))) + | _ -> ()); + Ast_iterator.default_iterator.typ iterator core_type + in + let module_expr (iterator : Ast_iterator.iterator) + (me : Parsetree.module_expr) = + (match me.pmod_desc with + | Pmod_ident id when id.loc |> Loc.hasPos ~pos:posBeforeCursor -> + if debug then + Printf.printf "Pmod_ident %s:%s\n" + (Utils.flattenLongIdent id.txt |> String.concat ".") + (Loc.toString id.loc); + found := true; + setResult (Cpath (CPId (Utils.flattenLongIdent id.txt, Module))) + | _ -> ()); + Ast_iterator.default_iterator.module_expr iterator me + in + let module_type (iterator : Ast_iterator.iterator) + (mt : Parsetree.module_type) = + (match mt.pmty_desc with + | Pmty_ident id when id.loc |> Loc.hasPos ~pos:posBeforeCursor -> + if debug then + Printf.printf "Pmty_ident %s:%s\n" + (Utils.flattenLongIdent id.txt |> String.concat ".") + (Loc.toString id.loc); + found := true; + setResult (Cpath (CPId (Utils.flattenLongIdent id.txt, Module))) + | _ -> ()); + Ast_iterator.default_iterator.module_type iterator mt + in + + let iterator = + { + Ast_iterator.default_iterator with + attribute; + expr; + module_expr; + module_type; + signature; + signature_item; + structure; + structure_item; + typ; + } + in + + if Filename.check_suffix path ".res" then ( + let parser = + Res_driver.parsingEngine.parseImplementation ~forPrinter:false + in + let {Res_driver.parsetree = str} = parser ~filename:currentFile in + iterator.structure iterator str |> ignore; + if blankAfterCursor = Some ' ' || blankAfterCursor = Some '\n' then + setResult (Cpath (CPId ([""], Value))); + if !found = false then if debug then Printf.printf "XXX Not found!\n"; + !result) + else if Filename.check_suffix path ".resi" then ( + let parser = Res_driver.parsingEngine.parseInterface ~forPrinter:false in + let {Res_driver.parsetree = signature} = parser ~filename:currentFile in + iterator.signature iterator signature |> ignore; + if blankAfterCursor = Some ' ' || blankAfterCursor = Some '\n' then + setResult (Cpath (CPId ([""], Type))); + if !found = false then if debug then Printf.printf "XXX Not found!\n"; + !result) + else None diff --git a/analysis/src/CreateInterface.ml b/analysis/src/CreateInterface.ml index 04fc1b358..fd168bcbc 100644 --- a/analysis/src/CreateInterface.ml +++ b/analysis/src/CreateInterface.ml @@ -113,7 +113,7 @@ let printSignature ~extractor ~signature = :: Sig_value (makeId (* make *), makeValueDesc) :: rest when Ident.name makePropsId = Ident.name makeId ^ "Props" && ((* from implementation *) makePropsLoc.loc_ghost - || (* from interface *) makePropsLoc = makeValueDesc.val_loc) + || (* from interface *) makePropsLoc = makeValueDesc.val_loc) && getComponentType makeValueDesc.val_type <> None -> (* {"name": string} => retType ~~> (~name:string) => retType @@ -164,8 +164,7 @@ let printSignature ~extractor ~signature = (* Rescript primitive name, e.g. @val external ... Copy the external declaration verbatim from the implementation file *) let lines = - let posStart = val_loc.loc_start |> Utils.tupleOfLexing in - let posEnd = val_loc.loc_end |> Utils.tupleOfLexing in + let posStart, posEnd = Loc.range val_loc in extractor |> SourceFileExtractor.extract ~posStart ~posEnd in Buffer.add_string buf ((lines |> String.concat "\n") ^ "\n"); diff --git a/analysis/src/DocumentSymbol.ml b/analysis/src/DocumentSymbol.ml index 79cfb271c..65e38b555 100644 --- a/analysis/src/DocumentSymbol.ml +++ b/analysis/src/DocumentSymbol.ml @@ -91,7 +91,7 @@ let command ~path = | Pstr_module mb -> processModuleBinding mb | Pstr_recmodule mbs -> mbs |> List.iter processModuleBinding | Pstr_exception ec -> processExtensionConstructor ec - | _ -> Ast_iterator.default_iterator.structure_item iterator item); + | _ -> ()); Ast_iterator.default_iterator.structure_item iterator item in let signature_item (iterator : Ast_iterator.iterator) diff --git a/analysis/src/Loc.ml b/analysis/src/Loc.ml new file mode 100644 index 000000000..8e0c72bd6 --- /dev/null +++ b/analysis/src/Loc.ml @@ -0,0 +1,10 @@ +type t = Location.t + +let start (loc : t) = Pos.ofLexing loc.loc_start +let end_ (loc : t) = Pos.ofLexing loc.loc_end +let range loc : Range.t = (start loc, end_ loc) + +let toString (loc : t) = + (if loc.loc_ghost then "__ghost__" else "") ^ (loc |> range |> Range.toString) + +let hasPos ~pos loc = start loc <= pos && pos < end_ loc diff --git a/analysis/src/NewCompletions.ml b/analysis/src/NewCompletions.ml index 67e64f853..35e0e75c1 100644 --- a/analysis/src/NewCompletions.ml +++ b/analysis/src/NewCompletions.ml @@ -496,73 +496,48 @@ let resolveOpens ~env ~previous opens ~package = (* loop(previous) *) previous opens -let completionForDeclareds ~pos iter stamps prefix transformContents = - (* Log.log("completion for declares " ++ prefix); *) - let res = ref [] in - iter - (fun _stamp (declared : _ Declared.t) -> - if - Utils.startsWith declared.name.txt prefix - && Utils.locationContainsFuzzy declared.scopeLoc pos - then - res := - { - (Completion.create ~name:declared.name.txt - ~kind:(transformContents declared.item)) - with - extentLoc = declared.extentLoc; - deprecated = declared.deprecated; - docstring = declared.docstring; - } - :: !res) - stamps; - !res - -let completionForDeclaredModules ~pos ~env ~suffix = - completionForDeclareds ~pos Stamps.iterModules env.QueryEnv.file.stamps suffix - (fun m -> Completion.Module m) - -let completionForDeclaredValues ~pos ~env ~suffix = - completionForDeclareds ~pos Stamps.iterValues env.QueryEnv.file.stamps suffix - (fun m -> Completion.Value m) - -let completionForDeclaredTypes ~pos ~env ~suffix = - completionForDeclareds ~pos Stamps.iterTypes env.QueryEnv.file.stamps suffix - (fun m -> Completion.Type m) +let checkName name ~prefix ~exact = + if exact then name = prefix else Utils.startsWith name prefix -let completionForExporteds iterExported getDeclared prefix transformContents = +let completionForExporteds iterExported getDeclared ~prefix ~exact ~env + ~namesUsed transformContents = let res = ref [] in iterExported (fun name stamp -> (* Log.log("checking exported: " ++ name); *) - if Utils.startsWith name prefix then + if checkName name ~prefix ~exact then match getDeclared stamp with - | Some (declared : _ Declared.t) -> + | Some (declared : _ Declared.t) + when not (Hashtbl.mem namesUsed declared.name.txt) -> + Hashtbl.add namesUsed declared.name.txt (); res := { - (Completion.create ~name:declared.name.txt + (Completion.create ~name:declared.name.txt ~env ~kind:(transformContents declared.item)) with - extentLoc = declared.extentLoc; deprecated = declared.deprecated; docstring = declared.docstring; } :: !res - | None -> ()); + | _ -> ()); !res -let completionForExportedModules ~env ~suffix = +let completionForExportedModules ~env ~prefix ~exact ~namesUsed = completionForExporteds (Exported.iter env.QueryEnv.exported Exported.Module) - (Stamps.findModule env.file.stamps) suffix (fun m -> Completion.Module m) + (Stamps.findModule env.file.stamps) ~prefix ~exact ~env ~namesUsed (fun m -> + Completion.Module m) -let completionForExportedValues ~env ~suffix = +let completionForExportedValues ~env ~prefix ~exact ~namesUsed = completionForExporteds (Exported.iter env.QueryEnv.exported Exported.Value) - (Stamps.findValue env.file.stamps) suffix (fun v -> Completion.Value v) + (Stamps.findValue env.file.stamps) ~prefix ~exact ~env ~namesUsed (fun v -> + Completion.Value v) -let completionForExportedTypes ~env ~suffix = +let completionForExportedTypes ~env ~prefix ~exact ~namesUsed = completionForExporteds (Exported.iter env.QueryEnv.exported Exported.Type) - (Stamps.findType env.file.stamps) suffix (fun t -> Completion.Type t) + (Stamps.findType env.file.stamps) ~prefix ~exact ~env ~namesUsed (fun t -> + Completion.Type t) -let completionForConstructors ~(env : QueryEnv.t) ~suffix = +let completionsForExportedConstructors ~(env : QueryEnv.t) ~prefix ~exact + ~namesUsed = let res = ref [] in Exported.iter env.exported Exported.Type (fun _name stamp -> match Stamps.findType env.file.stamps stamp with @@ -570,179 +545,322 @@ let completionForConstructors ~(env : QueryEnv.t) ~suffix = res := (constructors |> List.filter (fun c -> - Utils.startsWith c.Constructor.cname.txt suffix) - |> List.map (fun c -> - Completion.create ~name:c.Constructor.cname.txt - ~kind: - (Completion.Constructor - (c, t.item.decl |> Shared.declToString t.name.txt)))) + checkName c.Constructor.cname.txt ~prefix ~exact) + |> Utils.filterMap (fun c -> + let name = c.Constructor.cname.txt in + if not (Hashtbl.mem namesUsed name) then + let () = Hashtbl.add namesUsed name () in + Some + (Completion.create ~name ~env + ~kind: + (Completion.Constructor + (c, t.item.decl |> Shared.declToString t.name.txt))) + else None)) @ !res | _ -> ()); !res -let completionForFields ~(env : QueryEnv.t) ~suffix = +let completionForExportedFields ~(env : QueryEnv.t) ~prefix ~exact ~namesUsed = let res = ref [] in Exported.iter env.exported Exported.Type (fun _name stamp -> match Stamps.findType env.file.stamps stamp with | Some ({item = {kind = Record fields}} as t) -> res := (fields - |> List.filter (fun f -> Utils.startsWith f.fname.txt suffix) - |> List.map (fun f -> - Completion.create ~name:f.fname.txt - ~kind: - (Completion.Field - (f, t.item.decl |> Shared.declToString t.name.txt)))) + |> List.filter (fun f -> checkName f.fname.txt ~prefix ~exact) + |> Utils.filterMap (fun f -> + let name = f.fname.txt in + if not (Hashtbl.mem namesUsed name) then + let () = Hashtbl.add namesUsed name () in + Some + (Completion.create ~name ~env + ~kind: + (Completion.Field + (f, t.item.decl |> Shared.declToString t.name.txt))) + else None)) @ !res | _ -> ()); !res -let isCapitalized name = - if name = "" then false - else - let c = name.[0] in - match c with 'A' .. 'Z' -> true | _ -> false +let findModuleInScope ~env ~moduleName ~scope = + let modulesTable = Hashtbl.create 10 in + env.QueryEnv.file.stamps + |> Stamps.iterModules (fun _ declared -> + Hashtbl.replace modulesTable + (declared.name.txt, declared.extentLoc |> Loc.start) + declared); + let result = ref None in + let processModule name loc = + if name = moduleName && !result = None then + match Hashtbl.find_opt modulesTable (name, Loc.start loc) with + | Some declared -> result := Some declared + | None -> + Log.log + (Printf.sprintf "Module Not Found %s loc:%s\n" name (Loc.toString loc)) + in + scope |> Scope.iterModulesBeforeFirstOpen processModule; + scope |> Scope.iterModulesAfterFirstOpen processModule; + !result -type completion = - | QualifiedRecordAccess of path (* e.g. _.A.B.field where _ indicates a path ending in a lowercase id *) - | RecordAccess of path * path * string (* e.g. A.B.var .f1.f2 .f3 *) - | Path of path -(* e.g. A.B.var or A.B *) +let resolvePathFromStamps ~(env : QueryEnv.t) ~package ~scope ~moduleName ~path + = + (* Log.log("Finding from stamps " ++ name); *) + match findModuleInScope ~env ~moduleName ~scope with + | None -> None + | Some declared -> ( + (* Log.log("found it"); *) + match ProcessCmt.findInModule ~env declared.item path with + | None -> None + | Some res -> ( + match res with + | `Local (env, name) -> Some (env, name) + | `Global (moduleName, fullPath) -> ( + match ProcessCmt.fileForModule ~package moduleName with + | None -> None + | Some file -> + ProcessCmt.resolvePath ~env:(QueryEnv.fromFile file) ~path:fullPath + ~package))) -let determineCompletion (dotpath : path) = - let rec loop dotpath = - match dotpath with - | [] -> assert false - | [one] -> Path [one] - | [one; two] -> - if isCapitalized one then Path [one; two] - else RecordAccess ([one], [], two) - | one :: rest -> ( - if isCapitalized one then - match loop rest with - | Path path -> Path (one :: path) - | RecordAccess (valuePath, middleFields, lastField) -> - RecordAccess (one :: valuePath, middleFields, lastField) - | QualifiedRecordAccess _ as completion -> - (* A. _.B.field -> _.B.field *) - completion - else - match loop rest with - | Path path -> - (* x. B.field -> _.B.field *) - QualifiedRecordAccess path - | RecordAccess ([name], middleFields, lastField) -> - RecordAccess ([one], name :: middleFields, lastField) - | RecordAccess (valuePath, middleFields, lastField) -> - (* x.A.B.v.f1.f2.f3 --> .A.B.v.f1.f2.f3 *) - QualifiedRecordAccess (valuePath @ middleFields @ [lastField]) - | QualifiedRecordAccess _ as completion -> - (* x. _.A.f -> _.A.f *) - completion) +let resolveModuleWithOpens ~opens ~package ~moduleName = + let rec loop opens = + match opens with + | (env : QueryEnv.t) :: rest -> ( + Log.log ("Looking for env in " ^ Uri2.toString env.file.uri); + match ProcessCmt.resolvePath ~env ~package ~path:[moduleName; ""] with + | Some (env, _) -> Some env + | None -> loop rest) + | [] -> None in - loop dotpath + loop opens -(* Note: This is a hack. It will be wrong some times if you have a local thing - that overrides an open. +let resolveFileModule ~moduleName ~package = + Log.log ("Getting module " ^ moduleName); + match ProcessCmt.fileForModule ~package moduleName with + | None -> None + | Some file -> + Log.log "got it"; + let env = QueryEnv.fromFile file in + Some env - Maybe the way to fix it is to make note of what things in an open override - locally defined things... -*) -let getEnvWithOpens ~pos ~(env : QueryEnv.t) ~package ~(opens : QueryEnv.t list) - (path : string list) = - match ProcessCmt.resolveFromStamps ~env ~path ~package ~pos with +let getEnvWithOpens ~scope ~(env : QueryEnv.t) ~package + ~(opens : QueryEnv.t list) ~moduleName (path : string list) = + (* TODO: handle interleaving of opens and local modules correctly *) + match resolvePathFromStamps ~env ~scope ~moduleName ~path ~package with | Some x -> Some x - | None -> - let rec loop opens = - match opens with - | (env : QueryEnv.t) :: rest -> ( - Log.log ("Looking for env in " ^ Uri2.toString env.file.uri); - match ProcessCmt.resolvePath ~env ~package ~path with - | Some x -> Some x - | None -> loop rest) - | [] -> ( - match path with - | [] | [_] -> None - | top :: path -> ( - Log.log ("Getting module " ^ top); - match ProcessCmt.fileForModule ~package top with - | None -> None - | Some file -> - Log.log "got it"; - let env = QueryEnv.fromFile file in - ProcessCmt.resolvePath ~env ~package ~path)) - in - loop opens + | None -> ( + match resolveModuleWithOpens ~opens ~package ~moduleName with + | Some env -> ProcessCmt.resolvePath ~env ~package ~path + | None -> ( + match resolveFileModule ~moduleName ~package with + | None -> None + | Some env -> ProcessCmt.resolvePath ~env ~package ~path)) let detail name (kind : Completion.kind) = match kind with | Type {decl} -> decl |> Shared.declToString name | Value typ -> typ |> Shared.typeToString + | ObjLabel typ -> typ |> Shared.typeToString | Module _ -> "module" | FileModule _ -> "file module" | Field ({typ}, s) -> name ^ ": " ^ (typ |> Shared.typeToString) ^ "\n\n" ^ s | Constructor (c, s) -> showConstructor c ^ "\n\n" ^ s -let localValueCompletions ~pos ~(env : QueryEnv.t) suffix = - let results = [] in - Log.log "---------------- LOCAL VAL"; - let results = - if suffix = "" || isCapitalized suffix then - results - @ completionForDeclaredModules ~pos ~env ~suffix - @ completionForConstructors ~env ~suffix - else results +let findAllCompletions ~(env : QueryEnv.t) ~prefix ~exact ~namesUsed + ~(completionContext : Completable.completionContext) = + Log.log ("findAllCompletions uri:" ^ Uri2.toString env.file.uri); + match completionContext with + | Value -> + completionForExportedValues ~env ~prefix ~exact ~namesUsed + @ completionsForExportedConstructors ~env ~prefix ~exact ~namesUsed + @ completionForExportedModules ~env ~prefix ~exact ~namesUsed + | Type -> + completionForExportedTypes ~env ~prefix ~exact ~namesUsed + @ completionForExportedModules ~env ~prefix ~exact ~namesUsed + | Module -> completionForExportedModules ~env ~prefix ~exact ~namesUsed + | Field -> + completionForExportedFields ~env ~prefix ~exact ~namesUsed + @ completionForExportedModules ~env ~prefix ~exact ~namesUsed + +let findLocalCompletionsForValuesAndConstructors ~env ~prefix ~exact ~opens + ~scope = + let valueTable = Hashtbl.create 10 in + env.QueryEnv.file.stamps + |> Stamps.iterValues (fun _ declared -> + Hashtbl.replace valueTable + (declared.name.txt, declared.extentLoc |> Loc.start) + declared); + let constructorTable = Hashtbl.create 10 in + env.QueryEnv.file.stamps + |> Stamps.iterConstructors (fun _ declared -> + Hashtbl.replace constructorTable + (declared.name.txt, declared.extentLoc |> Loc.start) + declared); + let namesUsed = Hashtbl.create 10 in + let resultRev = ref [] in + let processValue name loc = + if checkName name ~prefix ~exact then + match Hashtbl.find_opt valueTable (name, Loc.start loc) with + | Some declared -> + if not (Hashtbl.mem namesUsed name) then ( + Hashtbl.add namesUsed name (); + resultRev := + { + (Completion.create ~name:declared.name.txt ~env + ~kind:(Value declared.item)) + with + deprecated = declared.deprecated; + docstring = declared.docstring; + } + :: !resultRev) + | None -> + Log.log + (Printf.sprintf "Completion Value Not Found %s loc:%s\n" name + (Loc.toString loc)) in - let results = - if suffix = "" || not (isCapitalized suffix) then - results - @ completionForDeclaredValues ~pos ~env ~suffix - @ completionForDeclaredTypes ~pos ~env ~suffix - @ completionForFields ~env ~suffix - else results + let processConstructor name loc = + if checkName name ~prefix ~exact then + match Hashtbl.find_opt constructorTable (name, Loc.start loc) with + | Some declared -> + if not (Hashtbl.mem namesUsed name) then ( + Hashtbl.add namesUsed name (); + resultRev := + { + (Completion.create ~name:declared.name.txt ~env + ~kind: + (Constructor + ( declared.item, + snd declared.item.typeDecl + |> Shared.declToString (fst declared.item.typeDecl) ))) + with + deprecated = declared.deprecated; + docstring = declared.docstring; + } + :: !resultRev) + | None -> + Log.log + (Printf.sprintf "Completion Constructor Not Found %s loc:%s\n" name + (Loc.toString loc)) in - results |> List.map (fun r -> (r, env)) + scope |> Scope.iterValuesBeforeFirstOpen processValue; + scope |> Scope.iterConstructorsBeforeFirstOpen processConstructor; + let valuesFromOpens = + opens + |> List.fold_left + (fun results env -> + let completionsFromThisOpen = + findAllCompletions ~env ~prefix ~exact ~namesUsed + ~completionContext:Value + in + completionsFromThisOpen @ results) + [] + in + scope |> Scope.iterValuesAfterFirstOpen processValue; + scope |> Scope.iterConstructorsAfterFirstOpen processConstructor; + List.rev_append !resultRev valuesFromOpens -let valueCompletions ~(env : QueryEnv.t) suffix = - Log.log (" - Completing in " ^ Uri2.toString env.file.uri); - let results = [] in - let results = - if suffix = "" || isCapitalized suffix then ( - (* Get rid of lowercase modules (#417) *) - Exported.iter env.exported Exported.Module (fun name _ -> - if not (isCapitalized name) then - Exported.removeModule env.exported name); - results - @ completionForExportedModules ~env ~suffix - @ completionForConstructors ~env ~suffix) - else results +let findLocalCompletionsForTypes ~env ~prefix ~exact ~opens ~scope = + let typesTable = Hashtbl.create 10 in + env.QueryEnv.file.stamps + |> Stamps.iterTypes (fun _ declared -> + Hashtbl.replace typesTable + (declared.name.txt, declared.extentLoc |> Loc.start) + declared); + let namesUsed = Hashtbl.create 10 in + let resultRev = ref [] in + let processType name loc = + if checkName name ~prefix ~exact then + match Hashtbl.find_opt typesTable (name, Loc.start loc) with + | Some declared -> + if not (Hashtbl.mem namesUsed name) then ( + Hashtbl.add namesUsed name (); + resultRev := + { + (Completion.create ~name:declared.name.txt ~env + ~kind:(Type declared.item)) + with + deprecated = declared.deprecated; + docstring = declared.docstring; + } + :: !resultRev) + | None -> + Log.log + (Printf.sprintf "Completion Type Not Found %s loc:%s\n" name + (Loc.toString loc)) in - let results = - if suffix = "" || not (isCapitalized suffix) then ( - Log.log " -- not capitalized"; - results - @ completionForExportedValues ~env ~suffix - @ completionForExportedTypes ~env ~suffix - @ completionForFields ~env ~suffix) - else results + scope |> Scope.iterTypesBeforeFirstOpen processType; + let valuesFromOpens = + opens + |> List.fold_left + (fun results env -> + let completionsFromThisOpen = + findAllCompletions ~env ~prefix ~exact ~namesUsed + ~completionContext:Type + in + completionsFromThisOpen @ results) + [] in - results |> List.map (fun r -> (r, env)) + scope |> Scope.iterTypesAfterFirstOpen processType; + List.rev_append !resultRev valuesFromOpens -let attributeCompletions ~(env : QueryEnv.t) ~suffix = - let results = [] in - let results = - if suffix = "" || isCapitalized suffix then - results @ completionForExportedModules ~env ~suffix - else results +let findLocalCompletionsForModules ~env ~prefix ~exact ~opens ~scope = + let modulesTable = Hashtbl.create 10 in + env.QueryEnv.file.stamps + |> Stamps.iterModules (fun _ declared -> + Hashtbl.replace modulesTable + (declared.name.txt, declared.extentLoc |> Loc.start) + declared); + let namesUsed = Hashtbl.create 10 in + let resultRev = ref [] in + let processModule name loc = + if checkName name ~prefix ~exact then + match Hashtbl.find_opt modulesTable (name, Loc.start loc) with + | Some declared -> + if not (Hashtbl.mem namesUsed name) then ( + Hashtbl.add namesUsed name (); + resultRev := + { + (Completion.create ~name:declared.name.txt ~env + ~kind:(Module declared.item)) + with + deprecated = declared.deprecated; + docstring = declared.docstring; + } + :: !resultRev) + | None -> + Log.log + (Printf.sprintf "Completion Module Not Found %s loc:%s\n" name + (Loc.toString loc)) in - let results = - if suffix = "" || not (isCapitalized suffix) then - results - @ completionForExportedValues ~env ~suffix - @ completionForFields ~env ~suffix - else results + scope |> Scope.iterModulesBeforeFirstOpen processModule; + let valuesFromOpens = + opens + |> List.fold_left + (fun results env -> + let completionsFromThisOpen = + findAllCompletions ~env ~prefix ~exact ~namesUsed + ~completionContext:Module + in + completionsFromThisOpen @ results) + [] in - results |> List.map (fun r -> (r, env)) + scope |> Scope.iterModulesAfterFirstOpen processModule; + List.rev_append !resultRev valuesFromOpens + +let findLocalCompletionsWithOpens ~pos ~(env : QueryEnv.t) ~prefix ~exact ~opens + ~scope ~(completionContext : Completable.completionContext) = + (* TODO: handle arbitrary interleaving of opens and local bindings correctly *) + Log.log + ("findLocalCompletionsWithOpens uri:" ^ Uri2.toString env.file.uri ^ " pos:" + ^ Pos.toString pos); + match completionContext with + | Value -> + findLocalCompletionsForValuesAndConstructors ~env ~prefix ~exact ~opens + ~scope + | Type -> findLocalCompletionsForTypes ~env ~prefix ~exact ~opens ~scope + | Module -> findLocalCompletionsForModules ~env ~prefix ~exact ~opens ~scope + | Field -> + (* There's no local completion for fields *) + [] (* TODO filter out things that are defined after the current position *) let resolveRawOpens ~env ~rawOpens ~package = @@ -754,7 +872,7 @@ let resolveRawOpens ~env ~rawOpens ~package = ~previous: (List.map QueryEnv.fromFile (packageOpens |> Utils.filterMap (ProcessCmt.fileForModule ~package))) - rawOpens ~package + (List.rev rawOpens) ~package in opens @@ -781,120 +899,38 @@ let rec extractObjectType ~env ~package (t : Types.type_expr) = | _ -> None) | _ -> None -let getCompletions ~full ~rawOpens ~allFiles ~pos ~dotpath = - Log.log - ("Opens folkz > " - ^ string_of_int (List.length rawOpens) - ^ " " - ^ String.concat " ... " (rawOpens |> List.map pathToString)); - let env = QueryEnv.fromFile full.file in - let package = full.package in - let packageOpens = "Pervasives" :: package.opens in - Log.log ("Package opens " ^ String.concat " " packageOpens); - let resolvedOpens = resolveRawOpens ~env ~rawOpens ~package in - Log.log - ("Opens nows " - ^ string_of_int (List.length resolvedOpens) - ^ " " - ^ String.concat " " - (resolvedOpens - |> List.map (fun (e : QueryEnv.t) -> Uri2.toString e.file.uri))); - (* Last open takes priority *) - let opens = List.rev resolvedOpens in - match dotpath with +let getCompletionsForPath ~package ~opens ~allFiles ~pos ~exact ~scope + ~completionContext ~env path = + match path with | [] -> [] - | [suffix] -> - let locallyDefinedValues = localValueCompletions ~pos ~env suffix in - let alreadyUsedIdentifiers = Hashtbl.create 10 in - let valuesFromOpens = - opens - |> List.fold_left - (fun results env -> - let completionsFromThisOpen = valueCompletions ~env suffix in - List.filter - (fun ((declared : Completion.t), _env) -> - if Hashtbl.mem alreadyUsedIdentifiers declared.name then false - else ( - Hashtbl.add alreadyUsedIdentifiers declared.name true; - true)) - completionsFromThisOpen - @ results) - [] + | [prefix] -> + let localCompletionsWithOpens = + findLocalCompletionsWithOpens ~pos ~env ~prefix ~exact ~opens ~scope + ~completionContext in - (* TODO complete the namespaced name too *) - let localModuleNames = + let fileModules = allFiles |> FileSet.elements |> Utils.filterMap (fun name -> - if Utils.startsWith name suffix && not (String.contains name '-') + if + checkName name ~prefix ~exact + && not + (* TODO complete the namespaced name too *) + (String.contains name '-') then Some - ( Completion.create ~name ~kind:(Completion.FileModule name), - env ) + (Completion.create ~name ~env + ~kind:(Completion.FileModule name)) else None) in - locallyDefinedValues @ valuesFromOpens @ localModuleNames - | _ -> ( - Log.log ("Completing for " ^ String.concat "<.>" dotpath); - match determineCompletion dotpath with - | Path path -> ( - Log.log ("Path " ^ pathToString path); - match getEnvWithOpens ~pos ~env ~package ~opens path with - | Some (env, suffix) -> - Log.log "Got the env"; - valueCompletions ~env suffix - | None -> []) - | RecordAccess (valuePath, middleFields, lastField) -> ( - Log.log ("lastField :" ^ lastField); - Log.log ("-------------- Looking for " ^ (valuePath |> pathToString)); - match getEnvWithOpens ~pos ~env ~package ~opens valuePath with - | Some (env, name) -> ( - match - ProcessCmt.findInScope pos name Stamps.iterValues env.file.stamps - with - | None -> [] - | Some declared -> ( - Log.log ("Found it! " ^ declared.name.txt); - match declared.item |> extractRecordType ~env ~package with - | None -> [] - | Some (env, fields, typDecl) -> ( - match - middleFields - |> List.fold_left - (fun current name -> - match current with - | None -> None - | Some (env, fields, _) -> ( - match - fields |> List.find_opt (fun f -> f.fname.txt = name) - with - | None -> None - | Some attr -> - Log.log ("Found attr " ^ name); - attr.typ |> extractRecordType ~env ~package)) - (Some (env, fields, typDecl)) - with - | None -> [] - | Some (env, fields, typDecl) -> - fields - |> Utils.filterMap (fun field -> - if Utils.startsWith field.fname.txt lastField then - Some - ( Completion.create ~name:field.fname.txt - ~kind: - (Completion.Field - ( field, - typDecl.item.decl - |> Shared.declToString typDecl.name.txt )), - env ) - else None)))) - | None -> []) - | QualifiedRecordAccess path -> ( - match getEnvWithOpens ~pos ~env ~package ~opens path with - | None -> [] - | Some (env, suffix) -> - attributeCompletions ~env ~suffix - @ List.concat - (opens |> List.map (fun env -> attributeCompletions ~env ~suffix)))) + localCompletionsWithOpens @ fileModules + | moduleName :: path -> ( + Log.log ("Path " ^ pathToString path); + match getEnvWithOpens ~scope ~env ~package ~opens ~moduleName path with + | Some (env, prefix) -> + Log.log "Got the env"; + let namesUsed = Hashtbl.create 10 in + findAllCompletions ~env ~prefix ~exact ~namesUsed ~completionContext + | None -> []) let mkItem ~name ~kind ~detail ~deprecated ~docstring = let docContent = @@ -916,10 +952,220 @@ let mkItem ~name ~kind ~detail ~deprecated ~docstring = else Some {kind = "markdown"; value = docContent}); } -let processCompletable ~processDotPath ~full ~package ~rawOpens - (completable : PartialParser.completable) = +let completionToItem {Completion.name; deprecated; docstring; kind} = + mkItem ~name + ~kind:(Completion.kindToInt kind) + ~deprecated ~detail:(detail name kind) ~docstring + +let completionsGetTypeEnv = function + | {Completion.kind = Value typ; env} :: _ -> Some (typ, env) + | {Completion.kind = ObjLabel typ; env} :: _ -> Some (typ, env) + | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env) + | _ -> None + +let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos + ~env ~exact ~scope (contextPath : Completable.contextPath) = + match contextPath with + | CPString -> + [ + Completion.create ~name:"string" ~env + ~kind: + (Completion.Value + (Ctype.newconstr (Path.Pident (Ident.create "string")) [])); + ] + | CPArray -> + [ + Completion.create ~name:"array" ~env + ~kind: + (Completion.Value + (Ctype.newconstr (Path.Pident (Ident.create "array")) [])); + ] + | CPId (path, completionContext) -> + path + |> getCompletionsForPath ~package ~opens ~allFiles ~pos ~exact + ~completionContext ~env ~scope + | CPField (CPId (path, Module), fieldName) -> + (* M.field *) + path @ [fieldName] + |> getCompletionsForPath ~package ~opens ~allFiles ~pos ~exact + ~completionContext:Field ~env ~scope + | CPField (cp, fieldName) -> ( + match + cp + |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos + ~env ~exact:true ~scope + |> completionsGetTypeEnv + with + | Some (typ, env) -> ( + match typ |> extractRecordType ~env ~package with + | Some (env, fields, typDecl) -> + fields + |> Utils.filterMap (fun field -> + if checkName field.fname.txt ~prefix:fieldName ~exact then + Some + (Completion.create ~name:field.fname.txt ~env + ~kind: + (Completion.Field + ( field, + typDecl.item.decl + |> Shared.declToString typDecl.name.txt ))) + else None) + | None -> []) + | None -> []) + | CPObj (cp, label) -> ( + match + cp + |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos + ~env ~exact:true ~scope + |> completionsGetTypeEnv + with + | Some (typ, env) -> ( + match typ |> extractObjectType ~env ~package with + | Some (env, tObj) -> + let rec getFields (texp : Types.type_expr) = + match texp.desc with + | Tfield (name, _, t1, t2) -> + let fields = t2 |> getFields in + (name, t1) :: fields + | Tlink te -> te |> getFields + | Tvar None -> [] + | _ -> [] + in + tObj |> getFields + |> Utils.filterMap (fun (field, typ) -> + if checkName field ~prefix:label ~exact then + Some + (Completion.create ~name:field ~env + ~kind:(Completion.ObjLabel typ)) + else None) + | None -> []) + | None -> []) + | CPPipe (cp, funNamePrefix) -> ( + match + cp + |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos + ~env ~exact:true ~scope + |> completionsGetTypeEnv + with + | Some (typ, _envNotUsed) -> ( + let arrayModulePath = ["Js"; "Array2"] in + let listModulePath = ["Belt"; "List"] in + let optionModulePath = ["Belt"; "Option"] in + let stringModulePath = ["Js"; "String2"] in + let getModulePath path = + let rec loop (path : Path.t) = + match path with + | Pident id -> [Ident.name id] + | Pdot (p, s, _) -> s :: loop p + | Papply _ -> [] + in + match path with + | Path.Pident id when Ident.name id = "array" -> arrayModulePath + | Path.Pident id when Ident.name id = "list" -> listModulePath + | Path.Pident id when Ident.name id = "option" -> optionModulePath + | Path.Pident id when Ident.name id = "string" -> stringModulePath + | _ -> ( match loop path with _ :: rest -> List.rev rest | [] -> []) + in + let getConstr typ = + match typ.Types.desc with + | Tconstr (path, _, _) + | Tlink {desc = Tconstr (path, _, _)} + | Tpoly ({desc = Tconstr (path, _, _)}, []) -> + Some path + | _ -> None + in + let fromType typ = + match getConstr typ with + | None -> None + | Some path -> Some (getModulePath path) + in + let lhsPath = fromType typ in + let removePackageOpens modulePath = + match modulePath with + | toplevel :: rest when package.opens |> List.mem toplevel -> rest + | _ -> modulePath + in + let rec removeRawOpen rawOpen modulePath = + match (rawOpen, modulePath) with + | [_], _ -> Some modulePath + | s :: inner, first :: restPath when s = first -> + removeRawOpen inner restPath + | _ -> None + in + let rec removeRawOpens rawOpens modulePath = + match rawOpens with + | rawOpen :: restOpens -> ( + let newModulePath = removeRawOpens restOpens modulePath in + match removeRawOpen rawOpen newModulePath with + | None -> newModulePath + | Some mp -> mp) + | [] -> modulePath + in + match lhsPath with + | Some modulePath -> ( + match modulePath with + | _ :: _ -> + let modulePathMinusOpens = + modulePath |> removePackageOpens |> removeRawOpens rawOpens + |> String.concat "." + in + let completionName name = + if modulePathMinusOpens = "" then name + else modulePathMinusOpens ^ "." ^ name + in + let completions = + modulePath @ [funNamePrefix] + |> 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()... *); + }) + | [] -> []) + | None -> []) + | None -> []) + +let getOpens ~rawOpens ~package ~env = + Log.log + ("Opens folkz > " + ^ string_of_int (List.length rawOpens) + ^ " " + ^ String.concat " ... " (rawOpens |> List.map pathToString)); + let packageOpens = "Pervasives" :: package.opens in + Log.log ("Package opens " ^ String.concat " " packageOpens); + let resolvedOpens = resolveRawOpens ~env ~rawOpens ~package in + Log.log + ("Opens nows " + ^ string_of_int (List.length resolvedOpens) + ^ " " + ^ String.concat " " + (resolvedOpens + |> List.map (fun (e : QueryEnv.t) -> Uri2.toString e.file.uri))); + (* Last open takes priority *) + List.rev resolvedOpens + +let processCompletable ~package ~scope ~env ~pos (completable : Completable.t) = + let rawOpens = Scope.getRawOpens scope in + let opens = getOpens ~rawOpens ~package ~env in + let allFiles = FileSet.union package.projectFiles package.dependenciesFiles in + let findTypeOfValue path = + path + |> getCompletionsForPath ~completionContext:Value ~exact:true ~package + ~opens ~allFiles ~pos ~env ~scope + |> completionsGetTypeEnv + in match completable with - | Cjsx ([id], prefix, identsSeen) when String.lowercase_ascii id = id -> + | Cpath contextPath -> + contextPath + |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos + ~env ~exact:false ~scope + |> List.map completionToItem + | Cjsx ([id], prefix, identsSeen) when String.uncapitalize_ascii id = id -> let mkLabel_ name typString = mkItem ~name ~kind:4 ~deprecated:None ~detail:typString ~docstring:[] in @@ -935,15 +1181,14 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens |> List.map mkLabel) @ keyLabels | Cjsx (componentPath, prefix, identsSeen) -> - let completions = processDotPath ~exact:true (componentPath @ ["make"]) in let labels = - match completions with - | ({Completion.kind = Completion.Value typ}, _env) :: _ -> + match componentPath @ ["make"] |> findTypeOfValue with + | Some (typ, _env) -> let rec getFields (texp : Types.type_expr) = match texp.desc with | Tfield (name, _, t1, t2) -> let fields = t2 |> getFields in - (name, t1) :: fields + if name = "children" then fields else (name, t1) :: fields | Tlink te -> te |> getFields | Tvar None -> [] | _ -> [] @@ -977,7 +1222,7 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens | _ -> [] in typ |> getLabels - | _ -> [] + | None -> [] in let mkLabel_ name typString = mkItem ~name ~kind:4 ~deprecated:None ~detail:typString ~docstring:[] @@ -993,129 +1238,6 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens Utils.startsWith name prefix && not (List.mem name identsSeen)) |> List.map mkLabel) @ keyLabels - | Cdotpath dotpath -> - let completions = dotpath |> processDotPath ~exact:false in - (* TODO(#107): figure out why we're getting duplicates. *) - completions |> Utils.dedup - |> List.map (fun ({Completion.name; deprecated; docstring; kind}, _env) -> - mkItem ~name - ~kind:(Completion.kindToInt kind) - ~deprecated ~detail:(detail name kind) ~docstring) - | Cpipe (pipe, partialName) -> ( - let arrayModulePath = ["Js"; "Array2"] in - let listModulePath = ["Belt"; "List"] in - let optionModulePath = ["Belt"; "Option"] in - let stringModulePath = ["Js"; "String2"] in - let getModulePath path = - let rec loop (path : Path.t) = - match path with - | Pident id -> [Ident.name id] - | Pdot (p, s, _) -> s :: loop p - | Papply _ -> [] - in - match path with - | Path.Pident id when Ident.name id = "array" -> arrayModulePath - | Path.Pident id when Ident.name id = "list" -> listModulePath - | Path.Pident id when Ident.name id = "option" -> optionModulePath - | Path.Pident id when Ident.name id = "string" -> stringModulePath - | _ -> ( match loop path with _ :: rest -> List.rev rest | [] -> []) - in - let getLhsPath ~pipeId ~partialName = - let getConstr typ = - match typ.Types.desc with - | Tconstr (path, _, _) - | Tlink {desc = Tconstr (path, _, _)} - | Tpoly ({desc = Tconstr (path, _, _)}, []) -> - Some path - | _ -> None - in - let fromType typ = - match getConstr typ with - | None -> None - | Some path -> - let modulePath = getModulePath path in - Some (modulePath, partialName) - in - let getField ~env ~typ fieldName = - match extractRecordType typ ~env ~package with - | Some (env1, fields, _) -> ( - match - fields |> List.find_opt (fun field -> field.fname.txt = fieldName) - with - | None -> None - | Some field -> Some (field.typ, env1)) - | None -> None - in - let rec getFields ~env ~typ = function - | [] -> Some (typ, env) - | fieldName :: rest -> ( - match getField ~env ~typ fieldName with - | None -> None - | Some (typ1, env1) -> getFields ~env:env1 ~typ:typ1 rest) - in - match String.split_on_char '.' pipeId with - | x :: fieldNames -> ( - match [x] |> processDotPath ~exact:true with - | ({Completion.kind = Value typ}, env) :: _ -> ( - match getFields ~env ~typ fieldNames with - | None -> None - | Some (typ1, _env1) -> fromType typ1) - | _ -> None) - | [] -> None - in - let lhsPath = - match pipe with - | PipeId pipeId -> getLhsPath ~pipeId ~partialName - | PipeString -> Some (stringModulePath, partialName) - | PipeArray -> Some (arrayModulePath, partialName) - in - let removePackageOpens modulePath = - match modulePath with - | toplevel :: rest when package.opens |> List.mem toplevel -> rest - | _ -> modulePath - in - let rec removeRawOpen rawOpen modulePath = - match (rawOpen, modulePath) with - | [_], _ -> Some modulePath - | s :: inner, first :: restPath when s = first -> - removeRawOpen inner restPath - | _ -> None - in - let rec removeRawOpens rawOpens modulePath = - match rawOpens with - | rawOpen :: restOpens -> - let newModulePath = - match removeRawOpen rawOpen modulePath with - | None -> modulePath - | Some newModulePath -> newModulePath - in - removeRawOpens restOpens newModulePath - | [] -> modulePath - in - match lhsPath with - | Some (modulePath, partialName) -> ( - match modulePath with - | _ :: _ -> - let modulePathMinusOpens = - modulePath |> removePackageOpens |> removeRawOpens rawOpens - |> String.concat "." - in - let completionName name = - if modulePathMinusOpens = "" then name - else modulePathMinusOpens ^ "." ^ name - in - let dotpath = modulePath @ [partialName] in - let declareds = dotpath |> processDotPath ~exact:false in - declareds - |> List.filter (fun ({Completion.kind}, _env) -> - match kind with Completion.Value _ -> true | _ -> false) - |> List.map - (fun ({Completion.name; deprecated; docstring; kind}, _env) -> - mkItem ~name:(completionName name) - ~kind:(Completion.kindToInt kind) - ~detail:(detail name kind) ~deprecated ~docstring) - | _ -> []) - | None -> []) | Cdecorator prefix -> let mkDecorator name = mkItem ~name ~kind:4 ~deprecated:None ~detail:"" ~docstring:[] @@ -1162,8 +1284,8 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens |> List.map mkDecorator | Clabel (funPath, prefix, identsSeen) -> let labels = - match funPath |> processDotPath ~exact:true with - | ({Completion.kind = Value typ}, _env) :: _ -> + match funPath |> findTypeOfValue with + | Some (typ, _env) -> let rec getLabels (t : Types.type_expr) = match t.desc with | Tlink t1 | Tsubst t1 -> getLabels t1 @@ -1173,7 +1295,7 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens | _ -> [] in typ |> getLabels - | _ -> [] + | None -> [] in let mkLabel (name, typ) = mkItem ~name ~kind:4 ~deprecated:None @@ -1184,86 +1306,7 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens |> List.filter (fun (name, _t) -> Utils.startsWith name prefix && not (List.mem name identsSeen)) |> List.map mkLabel - | Cobj (lhs, path, prefix) -> - let rec getFields (texp : Types.type_expr) = - match texp.desc with - | Tfield (name, _, t1, t2) -> - let fields = t2 |> getFields in - (name, t1) :: fields - | Tlink te -> te |> getFields - | Tvar None -> [] - | _ -> [] - in - let getObjectFields ~env (t : Types.type_expr) = - match t |> extractObjectType ~env ~package with - | Some (env, tObj) -> (env, getFields tObj) - | None -> (env, []) - in - let rec resolvePath ~env fields path = - match path with - | name :: restPath -> ( - match fields |> List.find_opt (fun (n, _) -> n = name) with - | Some (_, fieldType) -> - let env, innerFields = getObjectFields ~env fieldType in - resolvePath ~env innerFields restPath - | None -> []) - | [] -> fields - in - let env0 = QueryEnv.fromFile full.file in - let env, fields = - match lhs |> processDotPath ~exact:true with - | ({Completion.kind = Value typ}, env) :: _ -> getObjectFields ~env typ - | _ -> (env0, []) - in - let labels = resolvePath ~env fields path in - let mkLabel_ name typString = - mkItem ~name ~kind:4 ~deprecated:None ~detail:typString ~docstring:[] - in - let mkLabel (name, typ) = mkLabel_ name (typ |> Shared.typeToString) in - if labels = [] then [] - else - labels - |> List.filter (fun (name, _t) -> Utils.startsWith name prefix) - |> List.map mkLabel - -let getCompletable ~textOpt ~pos = - match textOpt with - | None -> None - | Some text -> ( - match PartialParser.positionToOffset text pos with - | None -> None - | Some offset -> ( - match PartialParser.findCompletable text offset with - | None -> None - | Some completable -> - let offsetFromLineStart = offset - snd pos in - (* try to avoid confusion e.g. unclosed quotes at current position *) - let rawOpens = PartialParser.findOpens text offsetFromLineStart in - Some (completable, rawOpens))) -let computeCompletions ~completable ~full ~pos ~rawOpens = - let package = full.package in - let allFiles = FileSet.union package.projectFiles package.dependenciesFiles in - let processDotPath ~exact dotpath = - let completions = getCompletions ~full ~rawOpens ~allFiles ~pos ~dotpath in - match dotpath |> List.rev with - | last :: _ when exact -> - (* Heuristic to approximate scope. - Take the last position before pos if any, or just return the first element. *) - let rec prioritize decls = - match decls with - | (d1, e1) :: (d2, e2) :: rest -> - let pos2 = d2.Completion.extentLoc.loc_start |> Utils.tupleOfLexing in - if pos2 >= pos then prioritize ((d1, e1) :: rest) - else - let pos1 = d1.extentLoc.loc_start |> Utils.tupleOfLexing in - if pos1 <= pos2 then prioritize ((d2, e2) :: rest) - else prioritize ((d1, e1) :: rest) - | [] | [_] -> decls - in - completions - |> List.filter (fun ({Completion.name}, _env) -> name = last) - |> prioritize - | _ -> completions - in - completable |> processCompletable ~processDotPath ~full ~package ~rawOpens +let computeCompletions ~(completable : Completable.t) ~package ~pos ~scope ~env + = + completable |> processCompletable ~package ~scope ~env ~pos diff --git a/analysis/src/PartialParser.ml b/analysis/src/PartialParser.ml index 446dcbd7f..e69de29bb 100644 --- a/analysis/src/PartialParser.ml +++ b/analysis/src/PartialParser.ml @@ -1,373 +0,0 @@ -let rec findBack text char i = - if i < 0 then i - else if text.[i] = char && (i = 0 || text.[i - 1] <> '/') then i - 1 - else findBack text char (i - 1) - -let rec findOpenComment text i = - if i < 1 then 0 - else if text.[i] = '*' && text.[i - 1] = '/' then i - 2 - else findOpenComment text (i - 1) - -let rec findBackSkippingCommentsAndStrings text char pair i level = - let loop = findBackSkippingCommentsAndStrings text char pair in - if i < 0 then 0 - else if text.[i] = char then - if level = 0 then i - 1 else loop (i - 1) (level - 1) - else if text.[i] = pair then loop (i - 1) (level + 1) - else - match text.[i] with - | '"' -> loop (findBack text '"' (i - 1)) level - | '/' when i >= 1 && text.[i - 1] = '*' -> - loop (findOpenComment text (i - 2)) level - | _ -> loop (i - 1) level - -let rec findLineComment text offset = - if offset <= 0 || text.[offset] = '\n' then None - else if offset > 0 && text.[offset] = '/' && text.[offset - 1] = '/' then - Some (offset - 1) - else findLineComment text (offset - 1) - -(* Check if the position is inside a `//` comment *) -let insideLineComment text offset = findLineComment text offset <> None - -let skipLineComment text offset = - match findLineComment text offset with None -> offset | Some n -> n - 1 - -let rec skipWhite text i = - if i < 0 then 0 - else - match text.[i] with - | ' ' | '\n' | '\r' | '\t' -> skipWhite text (i - 1) - | _ -> i - -let rec startOfLident text i = - if i < 0 then 0 - else - match text.[i] with - | 'a' .. 'z' | 'A' .. 'Z' | '.' | '_' | '0' .. '9' -> - startOfLident text (i - 1) - | _ -> i + 1 - -(* foo(... ~arg) from ~arg find foo *) -let findCallFromArgument text offset = - let none = ([], []) in - let rec loop identsSeen i = - let i = skipLineComment text i in - let i = skipWhite text i in - if i > 0 then - match text.[i] with - | '}' -> - let i1 = findBackSkippingCommentsAndStrings text '{' '}' (i - 1) 0 in - if i1 > 0 then loop identsSeen i1 else none - | ')' -> - let i1 = findBackSkippingCommentsAndStrings text '(' ')' (i - 1) 0 in - if i1 > 0 then loop identsSeen i1 else none - | ']' -> - let i1 = findBackSkippingCommentsAndStrings text '[' ']' (i - 1) 0 in - if i1 > 0 then loop identsSeen i1 else none - | '"' -> - let i1 = findBack text '"' (i - 1) in - if i1 > 0 then loop identsSeen i1 else none - | '\'' -> - let i1 = findBack text '\'' (i - 1) in - if i1 > 0 then loop identsSeen i1 else none - | '`' -> - let i1 = findBack text '`' (i - 1) in - if i1 > 0 then loop identsSeen i1 else none - | '(' -> - let i1 = skipWhite text (i - 1) in - let i0 = startOfLident text i1 in - let funLident = String.sub text i0 (i1 - i0 + 1) in - (Str.split (Str.regexp_string ".") funLident, identsSeen) - | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '.' | '_' -> - let i1 = startOfLident text i in - let ident = String.sub text i1 (i - i1 + 1) in - if i1 - 1 > 0 then - match text.[i1 - 1] with - | '~' -> loop (ident :: identsSeen) (i1 - 2) - | _ -> loop identsSeen (i1 - 1) - else none - | _ -> loop identsSeen (i - 1) - else none - in - loop [] offset - -(* skip A or #A or %A if present *) -let skipOptVariantExtension text i = - if i > 0 then - match text.[i] with - | 'a' .. 'z' | 'A' .. 'Z' | '_' | '0' .. '9' -> - let i = startOfLident text i - 1 in - let i = - if i > 0 then match text.[i] with '#' | '%' -> i - 1 | _ -> i else i - in - i - | _ -> i - else i - -(* Figure out whether id should be autocompleted as component prop. - Find JSX context ctx for component M to autocomplete id (already parsed) as a prop. - ctx ::= | [...] - optVariant ::= id | #id | %id | _nothing_ -*) -let findJsxContext text offset = - let rec loop identsSeen i = - let i = skipWhite text i in - if i > 0 then - match text.[i] with - | '}' -> - let i1 = findBackSkippingCommentsAndStrings text '{' '}' (i - 1) 0 in - if i1 > 0 then beforeParen identsSeen i1 else None - | ')' -> - let i1 = findBackSkippingCommentsAndStrings text '(' ')' (i - 1) 0 in - if i1 > 0 then beforeParen identsSeen i1 else None - | '>' -> - let i1 = findBackSkippingCommentsAndStrings text '<' '>' (i - 1) 0 in - if i1 > 0 then beforeValue identsSeen i1 else None - | ']' -> - let i1 = findBackSkippingCommentsAndStrings text '[' ']' (i - 1) 0 in - if i1 > 0 then beforeValue identsSeen i1 else None - | '"' -> - let i1 = findBack text '"' (i - 1) in - if i1 > 0 then beforeValue identsSeen i1 else None - | '\'' -> - let i1 = findBack text '\'' (i - 1) in - if i1 > 0 then beforeValue identsSeen i1 else None - | '`' -> - let i1 = findBack text '`' (i - 1) in - if i1 > 0 then beforeValue identsSeen i1 else None - | _ -> - let i1 = startOfLident text i in - let ident = String.sub text i1 (i - i1 + 1) in - if i1 >= 1 && ident <> "" then - match ident.[0] with - | ('a' .. 'z' | 'A' .. 'Z') when i1 >= 1 && text.[i1 - 1] = '<' -> - Some (ident, identsSeen) - | _ -> - if i1 >= 1 && text.[i1 - 1] = '#' then - beforeValue identsSeen (i1 - 2) - else beforeIdent ~ident identsSeen (i1 - 1) - else None - else None - and beforeIdent ~ident identsSeen i = - let i = skipWhite text i in - if i > 0 then - match text.[i] with - | '?' -> fromEquals identsSeen (i - 1) - | '=' -> fromEquals identsSeen i - | _ -> (* punning *) loop (ident :: identsSeen) i - else None - and beforeParen identsSeen i = - let i = skipWhite text i in - beforeValue identsSeen (skipOptVariantExtension text i) - and beforeValue identsSeen i = - let i = skipWhite text i in - if i > 0 then - match text.[i] with - | '?' -> fromEquals identsSeen (i - 1) - | _ -> fromEquals identsSeen i - else None - and fromEquals identsSeen i = - let i = skipWhite text i in - if i > 0 then - match text.[i] with - | '=' -> ( - let i = skipWhite text (i - 1) in - let i1 = startOfLident text i in - let ident = String.sub text i1 (i - i1 + 1) in - match ident with "" -> None | _ -> loop (ident :: identsSeen) (i1 - 1)) - | _ -> None - else None - in - loop [] offset - -type pipe = PipeId of string | PipeArray | PipeString - -type completable = - | Cdecorator of string (** e.g. @module *) - | Clabel of string list * string * string list - (** e.g. (["M", "foo"], "label", ["l1", "l2"]) for M.foo(...~l1...~l2...~label...) *) - | Cdotpath of string list (** e.g. ["M", "foo"] for M.foo *) - | Cjsx of string list * string * string list - (** E.g. (["M", "Comp"], "id", ["id1", "id2"]) for foo" *) - -let isLowercaseIdent id = - let rec loop i = - if i < 0 then true - else - match id.[i] with - | ('a' .. 'z' | '_') when i = 0 -> true - | ('a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_') when i > 0 -> loop (i - 1) - | _ -> false - in - loop (String.length id - 1) - -let findCompletable text offset = - let mkPath s = - let len = String.length s in - let dotpath = Str.split (Str.regexp_string ".") s in - let dotpath = - match s.[len - 1] with '.' -> dotpath @ [""] | _ -> dotpath - in - match dotpath with - | [id] when String.lowercase_ascii id = id -> ( - match findJsxContext text (offset - len - 1) with - | None -> Cdotpath dotpath - | Some (componentName, identsSeen) -> - Cjsx (Str.split (Str.regexp_string ".") componentName, id, identsSeen)) - | _ -> Cdotpath dotpath - in - let mkPipe off partialName = - let off = skipWhite text off in - let rec loop i = - if i < 0 then Some (PipeId (String.sub text 0 (i - 1))) - else - match text.[i] with - | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '.' | '_' -> loop (i - 1) - | '"' when i == off -> Some PipeString - | ']' when i == off -> Some PipeArray - | _ -> Some (PipeId (String.sub text (i + 1) (off - i))) - in - match loop off with - | None -> None - | Some lhs -> Some (Cpipe (lhs, partialName)) - in - let mkObj ~off ~partialName = - let off = skipWhite text off in - let rec loop off path i = - if i < 0 then - let id = String.sub text 0 (i - 1) in - Some ([], [id]) - else - match text.[i] with - | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' | '.' -> - loop off path (i - 1) - | ']' when i > 1 && text.[i - 1] = '"' -> - let i0 = i - 2 in - let i1 = startOfLident text i0 in - let ident = String.sub text i1 (i0 - i1 + 1) in - if ident <> "" && i1 > 1 && text.[i1 - 1] = '"' && text.[i1 - 2] = '[' - then loop (off - i + i1 - 3) (ident :: path) (i1 - 3) - else None - | _ -> - let id = String.sub text (i + 1) (off - i) in - Some (path, Str.split (Str.regexp_string ".") id) - in - match loop off [] off with - | None -> None - | Some (path, lhs) -> Some (Cobj (lhs, path, partialName)) - in - - let suffix i = String.sub text (i + 1) (offset - (i + 1)) in - let rec loop i = - if i < 0 then Some (mkPath (suffix i)) - else - match text.[i] with - | '>' when i > 0 && text.[i - 1] = '-' -> - let rest = suffix i in - if isLowercaseIdent rest then mkPipe (i - 2) rest - else Some (mkPath rest) - | '~' -> - let labelPrefix = suffix i in - let funPath, identsSeen = findCallFromArgument text (i - 1) in - Some (Clabel (funPath, labelPrefix, identsSeen)) - | '@' -> Some (Cdecorator (suffix i)) - | '"' when i > 0 && text.[i - 1] = '[' -> - let partialName = suffix i in - mkObj ~off:(i - 2) ~partialName - | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '.' | '_' -> loop (i - 1) - | ' ' when i = offset - 1 -> ( - (* autocomplete with no id: check if inside JSX *) - match findJsxContext text (offset - 1) with - | None -> None - | Some (componentName, identsSeen) -> - Some - (Cjsx - (Str.split (Str.regexp_string ".") componentName, "", identsSeen)) - ) - | _ -> if i = offset - 1 then None else Some (mkPath (suffix i)) - in - if offset > String.length text || offset = 0 then None else loop (offset - 1) - -let findOpens text offset = - let opens = ref [] in - let pathOfModuleOpen o = - let rec loop items = - match items with - | [] -> ["place holder"] - | one :: rest -> one :: loop rest - in - loop (o |> Str.split (Str.regexp_string ".")) - in - let add o = opens := (o |> pathOfModuleOpen) :: !opens in - let maybeOpen i0 = - let rec loop i = - if i < 4 then 0 - else - match text.[i] with - | 'a' .. 'z' | 'A' .. 'Z' | '.' | '_' | '0' .. '9' -> loop (i - 1) - | ' ' | '!' -> - let at = skipWhite text (i - 1) in - let at = - if at >= 0 && text.[at] = '!' then - (* handle open! *) - skipWhite text (at - 1) - else at - in - if - at >= 3 - && text.[at - 3] = 'o' - && text.[at - 2] = 'p' - && text.[at - 1] = 'e' - && text.[at] = 'n' - && not (insideLineComment text (at - 4)) - then ( - add (String.sub text (i + 1) (i0 + 1 - (i + 1))); - at - 4) - else at - | _ -> i - in - loop (i0 - 1) - in - let rec loop i = - if i > 1 then - match text.[i] with - | '}' -> loop (findBackSkippingCommentsAndStrings text '{' '}' (i - 1) 0) - | ']' -> loop (findBackSkippingCommentsAndStrings text '[' ']' (i - 1) 0) - | ')' -> loop (findBackSkippingCommentsAndStrings text '(' ')' (i - 1) 0) - | '"' -> loop (findBack text '"' (i - 1)) - | 'a' .. 'z' | 'A' .. 'Z' | '_' | '0' .. '9' -> loop (maybeOpen i) - | '(' when text.[i - 1] = '.' -> ( - match text.[i - 2] with - | 'a' .. 'z' | 'A' .. 'Z' | '_' | '0' .. '9' -> - let i0 = startOfLident text (i - 3) in - add (String.sub text i0 (i - i0 - 1)) - | _ -> loop (i - 1)) - | _ -> - if i > 1 && text.[i] = '/' && text.[i - 1] = '*' then - loop (findOpenComment text (i - 2)) - else loop (i - 1) - in - loop (offset - 1) |> ignore; - !opens - -let offsetOfLine text line = - let ln = String.length text in - let rec loop i lno = - if i >= ln then None - else - match text.[i] with - | '\n' -> if lno = line - 1 then Some (i + 1) else loop (i + 1) (lno + 1) - | _ -> loop (i + 1) lno - in - match line with 0 -> Some 0 | _ -> loop 0 0 - -let positionToOffset text (line, character) = - match offsetOfLine text line with - | None -> None - | Some bol -> Some (bol + character) diff --git a/analysis/src/Pos.ml b/analysis/src/Pos.ml new file mode 100644 index 000000000..cfcf6b7b6 --- /dev/null +++ b/analysis/src/Pos.ml @@ -0,0 +1,6 @@ +type t = int * int + +let ofLexing {Lexing.pos_lnum; pos_cnum; pos_bol} = + (pos_lnum - 1, pos_cnum - pos_bol) + +let toString (loc, col) = Printf.sprintf "%d:%d" loc col diff --git a/analysis/src/ProcessAttributes.ml b/analysis/src/ProcessAttributes.ml index 0836b7596..ac0835c87 100644 --- a/analysis/src/ProcessAttributes.ml +++ b/analysis/src/ProcessAttributes.ml @@ -34,13 +34,11 @@ let rec findDeprecatedAttribute attributes = | ({Asttypes.txt = "deprecated"}, _) :: _ -> Some "" | _ :: rest -> findDeprecatedAttribute rest -let newDeclared ~item ~scope ~extent ~name ~stamp ~modulePath isExported - attributes = +let newDeclared ~item ~extent ~name ~stamp ~modulePath isExported attributes = { Declared.name; stamp; extentLoc = extent; - scopeLoc = scope; isExported; modulePath; deprecated = findDeprecatedAttribute attributes; diff --git a/analysis/src/ProcessCmt.ml b/analysis/src/ProcessCmt.ml index a01c15765..e9ba99a04 100644 --- a/analysis/src/ProcessCmt.ml +++ b/analysis/src/ProcessCmt.ml @@ -1,38 +1,12 @@ open Typedtree open SharedTypes -let locsExtent locs = - let locs = locs |> List.filter (fun loc -> not loc.Location.loc_ghost) in - (* This filters out ghost locs, but still assumes positions are ordered. - Perhaps compute min/max. *) - match locs with - | [] -> Location.none - | first :: _ -> - let last = List.nth locs (List.length locs - 1) in - let first, last = - if first.loc_start.pos_cnum < last.loc_start.pos_cnum then (first, last) - else (last, first) - in - {loc_ghost = true; loc_start = first.loc_start; loc_end = last.loc_end} - -let impItemsExtent items = - items |> List.map (fun item -> item.Typedtree.str_loc) |> locsExtent - -let sigItemsExtent items = - items |> List.map (fun item -> item.Typedtree.sig_loc) |> locsExtent - let addItem ~(name : string Location.loc) ~extent ~stamp ~(env : Env.t) ~item attributes addExported addStamp = let isExported = addExported name.txt stamp in let declared = - ProcessAttributes.newDeclared ~item - ~scope: - { - Location.loc_start = extent.Location.loc_end; - loc_end = env.scope.loc_end; - loc_ghost = false; - } - ~extent ~name ~stamp ~modulePath:env.modulePath isExported attributes + ProcessAttributes.newDeclared ~item ~extent ~name ~stamp + ~modulePath:env.modulePath isExported attributes in addStamp env.stamps stamp declared; declared @@ -49,17 +23,13 @@ let rec forTypeSignatureItem ~env ~(exported : Exported.t) (Exported.add exported Exported.Value) Stamps.addValue in - [ - { - Module.kind = Module.Value declared.item; - name = declared.name.txt; - }; - ] + [{Module.kind = Module.Value declared.item; name = declared.name.txt}] | Sig_type ( ident, ({type_loc; type_kind; type_manifest; type_attributes} as decl), recStatus ) -> let declared = + let name = Location.mknoloc (Ident.name ident) in addItem ~extent:type_loc ~item: { @@ -94,16 +64,11 @@ let rec forTypeSignatureItem ~env ~(exported : Exported.t) (* TODO(406): constructor record args support *) | Cstr_record _ -> []); res = cd_res; + typeDecl = (name, decl); } in let declared = ProcessAttributes.newDeclared ~item ~extent:cd_loc - ~scope: - { - Location.loc_start = type_loc.Location.loc_end; - loc_end = env.scope.loc_end; - loc_ghost = false; - } ~name:(Location.mknoloc name) ~stamp (* TODO maybe this needs another child *) ~modulePath:env.modulePath true cd_attributes @@ -122,17 +87,11 @@ let rec forTypeSignatureItem ~env ~(exported : Exported.t) typ = ld_type; }))); } - ~name:(Location.mknoloc (Ident.name ident)) - ~stamp:(Ident.binding_time ident) ~env type_attributes + ~name ~stamp:(Ident.binding_time ident) ~env type_attributes (Exported.add exported Exported.Type) Stamps.addType in - [ - { - Module.kind = Type (declared.item, recStatus); - name = declared.name.txt; - }; - ] + [{Module.kind = Type (declared.item, recStatus); name = declared.name.txt}] | Sig_module (ident, {md_type; md_attributes; md_loc}, _) -> let declared = addItem ~extent:md_loc @@ -142,12 +101,7 @@ let rec forTypeSignatureItem ~env ~(exported : Exported.t) (Exported.add exported Exported.Module) Stamps.addModule in - [ - { - Module.kind = Module declared.item; - name = declared.name.txt; - }; - ] + [{Module.kind = Module declared.item; name = declared.name.txt}] | _ -> [] and forTypeSignature env signature = @@ -202,23 +156,43 @@ let forTypeDeclaration ~env ~(exported : Exported.t) | Ttype_variant constructors -> Variant (constructors - |> List.map (fun {cd_id; cd_name = cname; cd_args; cd_res} -> - let stamp = Ident.binding_time cd_id in + |> List.map + (fun { - Constructor.stamp; - cname; - args = - (match cd_args with - | Cstr_tuple args -> - args - |> List.map (fun t -> (t.ctyp_type, t.ctyp_loc)) - (* TODO(406) *) - | Cstr_record _ -> []); - res = - (match cd_res with - | None -> None - | Some t -> Some t.ctyp_type); - })) + cd_id; + cd_name = cname; + cd_args; + cd_res; + cd_attributes; + cd_loc; + } + -> + let stamp = Ident.binding_time cd_id in + let item = + { + Constructor.stamp; + cname; + args = + (match cd_args with + | Cstr_tuple args -> + args + |> List.map (fun t -> (t.ctyp_type, t.ctyp_loc)) + (* TODO(406) *) + | Cstr_record _ -> []); + res = + (match cd_res with + | None -> None + | Some t -> Some t.ctyp_type); + typeDecl = (name.txt, typ_type); + } + in + let declared = + ProcessAttributes.newDeclared ~item ~extent:cd_loc + ~name:cname ~stamp ~modulePath:env.modulePath true + cd_attributes + in + Stamps.addConstructor env.stamps stamp declared; + item)) | Ttype_record fields -> Record (fields @@ -247,12 +221,7 @@ let rec forSignatureItem ~env ~(exported : Exported.t) (Exported.add exported Exported.Value) Stamps.addValue in - [ - { - Module.kind = Module.Value declared.item; - name = declared.name.txt; - }; - ] + [{Module.kind = Module.Value declared.item; name = declared.name.txt}] | Tsig_type (recFlag, decls) -> decls |> List.mapi (fun i decl -> @@ -272,12 +241,7 @@ let rec forSignatureItem ~env ~(exported : Exported.t) (Exported.add exported Exported.Module) Stamps.addModule in - [ - { - Module.kind = Module declared.item; - name = declared.name.txt; - }; - ] + [{Module.kind = Module declared.item; name = declared.name.txt}] | Tsig_recmodule modDecls -> modDecls |> List.map (fun modDecl -> @@ -367,7 +331,9 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = bindings; !items | Tstr_module - {mb_id; mb_attributes; mb_loc; mb_name = name; mb_expr = {mod_desc}} -> + {mb_id; mb_attributes; mb_loc; mb_name = name; mb_expr = {mod_desc}} + when not (String.length name.txt >= 6 && String.sub name.txt 0 6 = "local_") + (* %%private generates a dummy module called local_... *) -> let item = forModule env mod_desc name.txt in let declared = addItem ~item ~name ~extent:mb_loc ~stamp:(Ident.binding_time mb_id) ~env @@ -375,12 +341,7 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = (Exported.add exported Exported.Module) Stamps.addModule in - [ - { - Module.kind = Module declared.item; - name = declared.name.txt; - }; - ] + [{Module.kind = Module declared.item; name = declared.name.txt}] | Tstr_recmodule modDecls -> modDecls |> List.map (fun modDecl -> @@ -406,12 +367,7 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = (Exported.add exported Exported.Module) Stamps.addModule in - [ - { - Module.kind = Module modTypeItem; - name = declared.name.txt; - }; - ] + [{Module.kind = Module modTypeItem; name = declared.name.txt}] | Tstr_include {incl_mod; incl_type} -> let env = match getModulePath incl_mod.mod_desc with @@ -435,12 +391,7 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = (Exported.add exported Exported.Value) Stamps.addValue in - [ - { - Module.kind = Value declared.item; - name = declared.name.txt; - }; - ] + [{Module.kind = Value declared.item; name = declared.name.txt}] | Tstr_type (recFlag, decls) -> decls |> List.mapi (fun i decl -> @@ -458,11 +409,7 @@ and forModule env mod_desc moduleName = | Tmod_ident (path, _lident) -> Ident path | Tmod_structure structure -> let env = - { - env with - scope = impItemsExtent structure.str_items; - modulePath = ExportedModule (moduleName, env.modulePath); - } + {env with modulePath = ExportedModule (moduleName, env.modulePath)} in let contents = forStructure ~env structure.str_items in Structure contents @@ -476,12 +423,6 @@ and forModule env mod_desc moduleName = let stamp = Ident.binding_time ident in let declared = ProcessAttributes.newDeclared ~item:kind ~name:argName - ~scope: - { - Location.loc_start = t.mty_loc.loc_end; - loc_end = env.scope.loc_end; - loc_ghost = false; - } ~extent:t.Typedtree.mty_loc ~stamp ~modulePath:NotVisible false [] in Stamps.addModule env.stamps stamp declared)); @@ -533,24 +474,8 @@ let forCmt ~moduleName ~uri ({cmt_modname; cmt_annots} : Cmt_format.cmt_infos) = | _ -> None) |> List.concat in - let extent = impItemsExtent items in - let extent = - { - extent with - loc_end = - { - extent.loc_end with - pos_lnum = extent.loc_end.pos_lnum + 1000000; - pos_cnum = extent.loc_end.pos_cnum + 100000000; - }; - } - in let env = - { - Env.scope = extent; - stamps = Stamps.init (); - modulePath = File (uri, moduleName); - } + {Env.stamps = Stamps.init (); modulePath = File (uri, moduleName)} in let structure = forStructure ~env items in {File.uri; moduleName = cmt_modname; stamps = env.stamps; structure} @@ -565,31 +490,19 @@ let forCmt ~moduleName ~uri ({cmt_modname; cmt_annots} : Cmt_format.cmt_infos) = |> List.concat in let env = - { - Env.scope = sigItemsExtent items; - stamps = Stamps.init (); - modulePath = File (uri, moduleName); - } + {Env.stamps = Stamps.init (); modulePath = File (uri, moduleName)} in let structure = forSignature ~env items in {uri; moduleName = cmt_modname; stamps = env.stamps; structure} | Implementation structure -> let env = - { - Env.scope = impItemsExtent structure.str_items; - stamps = Stamps.init (); - modulePath = File (uri, moduleName); - } + {Env.stamps = Stamps.init (); modulePath = File (uri, moduleName)} in let structure = forStructure ~env structure.str_items in {uri; moduleName = cmt_modname; stamps = env.stamps; structure} | Interface signature -> let env = - { - Env.scope = sigItemsExtent signature.sig_items; - stamps = Stamps.init (); - modulePath = File (uri, moduleName); - } + {Env.stamps = Stamps.init (); modulePath = File (uri, moduleName)} in let structure = forSignature ~env signature.sig_items in {uri; moduleName = cmt_modname; stamps = env.stamps; structure} @@ -683,19 +596,19 @@ let rec resolvePathInner ~(env : QueryEnv.t) ~path = | Some stamp -> ( match Stamps.findModule env.file.stamps stamp with | None -> None - | Some {item = kind} -> findInModule ~env kind subPath)) + | Some {item} -> findInModule ~env item subPath)) -and findInModule ~env kind path = - match kind with +and findInModule ~env module_ path = + match module_ with | Structure {exported} -> resolvePathInner ~env:{env with exported} ~path - | Constraint (_, moduleTypeKind) -> findInModule ~env moduleTypeKind path + | Constraint (_, module1) -> findInModule ~env module1 path | Ident modulePath -> ( let stamp, moduleName, fullPath = joinPaths modulePath path in if stamp = 0 then Some (`Global (moduleName, fullPath)) else match Stamps.findModule env.file.stamps stamp with | None -> None - | Some {item = kind} -> findInModule ~env kind fullPath) + | Some {item} -> findInModule ~env item fullPath) let fromCompilerPath ~(env : QueryEnv.t) path = match makePath path with @@ -716,7 +629,6 @@ let fromCompilerPath ~(env : QueryEnv.t) path = module F (Collector : sig val extra : extra val file : File.t - val scopeExtent : Location.t list ref end) = struct let extra = Collector.extra @@ -895,17 +807,6 @@ struct addLocItem extra nameLoc (Typed (name, constructorType, locType)) | _ -> () - let currentScopeExtent () = - if !Collector.scopeExtent = [] then Location.none - else List.hd !Collector.scopeExtent - - let addScopeExtent loc = - Collector.scopeExtent := loc :: !Collector.scopeExtent - - let popScopeExtent () = - if List.length !Collector.scopeExtent > 1 then - Collector.scopeExtent := List.tl !Collector.scopeExtent - let rec lidIsComplex (lid : Longident.t) = match lid with | Lapply _ -> true @@ -961,21 +862,6 @@ struct Hashtbl.replace Collector.extra.opens loc () | _ -> () - let enter_structure {str_items} = - if str_items <> [] then - let first = List.hd str_items in - let last = List.nth str_items (List.length str_items - 1) in - let extent = - { - Location.loc_ghost = true; - loc_start = first.str_loc.loc_start; - loc_end = last.str_loc.loc_end; - } - in - addScopeExtent extent - - let leave_structure str = if str.str_items <> [] then popScopeExtent () - let enter_signature_item item = match item.sig_desc with | Tsig_value {val_id; val_loc; val_name = name; val_desc; val_attributes} -> @@ -983,12 +869,6 @@ struct if Stamps.findValue Collector.file.stamps stamp = None then ( let declared = ProcessAttributes.newDeclared ~name ~stamp ~extent:val_loc - ~scope: - { - loc_ghost = true; - loc_start = val_loc.loc_end; - loc_end = (currentScopeExtent ()).loc_end; - } ~modulePath:NotVisible ~item:val_desc.ctyp_type false val_attributes in Stamps.addValue Collector.file.stamps stamp declared; @@ -1007,15 +887,8 @@ struct let addForPattern stamp name = if Stamps.findValue Collector.file.stamps stamp = None then ( let declared = - ProcessAttributes.newDeclared ~name ~stamp - ~scope: - { - loc_ghost = true; - loc_start = pat_loc.loc_end; - loc_end = (currentScopeExtent ()).loc_end; - } - ~modulePath:NotVisible ~extent:pat_loc ~item:pat_type false - pat_attributes + ProcessAttributes.newDeclared ~name ~stamp ~modulePath:NotVisible + ~extent:pat_loc ~item:pat_type false pat_attributes in Stamps.addValue Collector.file.stamps stamp declared; addReference stamp name.loc; @@ -1062,42 +935,14 @@ struct addForConstructor expression.exp_type lident constructor | Texp_field (inner, lident, _label_description) -> addForField inner.exp_type expression.exp_type lident - | Texp_let (_, _, _) -> - (* TODO this scope tracking won't work for recursive *) - addScopeExtent expression.exp_loc - | Texp_function {cases} -> ( - match cases with - | [{c_lhs = {pat_desc = Tpat_var _}; c_rhs}] -> - addScopeExtent c_rhs.exp_loc - | _ -> ()) - | _ -> () - - let leave_expression expression = - match expression.exp_desc with - | Texp_let (_isrec, _bindings, _expr) -> popScopeExtent () - | Texp_function {cases} -> ( - match cases with [_] -> popScopeExtent () | _ -> ()) | _ -> () end let extraForStructureItems ~(file : File.t) (items : Typedtree.structure_item list) parts = let extra = extraForFile ~file in - let extent = impItemsExtent items in - let extent = - { - extent with - loc_end = - { - extent.loc_end with - pos_lnum = extent.loc_end.pos_lnum + 1000000; - pos_cnum = extent.loc_end.pos_cnum + 100000000; - }; - } - in (* TODO look through parts and extend the extent *) let module Iter = TypedtreeIter.MakeIterator (F (struct - let scopeExtent = ref [extent] let extra = extra let file = file end)) in @@ -1118,21 +963,8 @@ let extraForStructureItems ~(file : File.t) let extraForSignatureItems ~(file : File.t) (items : Typedtree.signature_item list) parts = let extra = extraForFile ~file in - let extent = sigItemsExtent items in - let extent = - { - extent with - loc_end = - { - extent.loc_end with - pos_lnum = extent.loc_end.pos_lnum + 1000000; - pos_cnum = extent.loc_end.pos_cnum + 100000000; - }; - } - in (* TODO look through parts and extend the extent *) let module Iter = TypedtreeIter.MakeIterator (F (struct - let scopeExtent = ref [extent] let extra = extra let file = file end)) in @@ -1248,49 +1080,6 @@ let rec resolvePath ~env ~path ~package = | Some file -> resolvePath ~env:(QueryEnv.fromFile file) ~path:fullPath ~package)) -let locationIsBefore {Location.loc_start} pos = - Utils.tupleOfLexing loc_start <= pos - -let findInScope pos name iter stamps = - (* Log.log("Find " ++ name ++ " with " ++ string_of_int(Hashtbl.length(stamps)) ++ " stamps"); *) - let res = ref None in - iter - (fun _stamp (declared : _ Declared.t) -> - if declared.name.txt = name then - (* Log.log("a stamp " ++ Utils.showLocation(declared.scopeLoc) ++ " " ++ string_of_int(l) ++ "," ++ string_of_int(c)); *) - if locationIsBefore declared.scopeLoc pos then - match !res with - | None -> res := Some declared - | Some current -> - if - current.name.loc.loc_start.pos_cnum - < declared.name.loc.loc_start.pos_cnum - then res := Some declared) - stamps; - !res - -let resolveFromStamps ~(env : QueryEnv.t) ~path ~package ~pos = - match path with - | [] -> None - | [name] -> Some (env, name) - | name :: inner -> ( - (* Log.log("Finding from stamps " ++ name); *) - match findInScope pos name Stamps.iterModules env.file.stamps with - | None -> None - | Some declared -> ( - (* Log.log("found it"); *) - match findInModule ~env declared.item inner with - | None -> None - | Some res -> ( - match res with - | `Local (env, name) -> Some (env, name) - | `Global (moduleName, fullPath) -> ( - match fileForModule ~package moduleName with - | None -> None - | Some file -> - resolvePath ~env:(QueryEnv.fromFile file) ~path:fullPath ~package))) - ) - let resolveModuleFromCompilerPath ~env ~package path = match fromCompilerPath ~env path with | `Global (moduleName, path) -> ( diff --git a/analysis/src/Range.ml b/analysis/src/Range.ml new file mode 100644 index 000000000..a74349075 --- /dev/null +++ b/analysis/src/Range.ml @@ -0,0 +1,6 @@ +type t = Pos.t * Pos.t + +let toString ((posStart, posEnd) : t) = + Printf.sprintf "[%s->%s]" (Pos.toString posStart) (Pos.toString posEnd) + +let hasPos ~pos ((posStart, posEnd) : t) = posStart <= pos && pos < posEnd diff --git a/analysis/src/Scope.ml b/analysis/src/Scope.ml new file mode 100644 index 000000000..1f458db3b --- /dev/null +++ b/analysis/src/Scope.ml @@ -0,0 +1,127 @@ +type item = + | Constructor of string * Location.t + | Field of string * Location.t + | Module of string * Location.t + | Open of string list + | Type of string * Location.t + | Value of string * Location.t + +type t = item list + +let itemToString item = + let str s = if s = "" then "\"\"" else s in + let list l = "[" ^ (l |> List.map str |> String.concat ", ") ^ "]" in + match item with + | Constructor (s, loc) -> "Constructor " ^ s ^ " " ^ Loc.toString loc + | Field (s, loc) -> "Field " ^ s ^ " " ^ Loc.toString loc + | Open sl -> "Open " ^ list sl + | Module (s, loc) -> "Module " ^ s ^ " " ^ Loc.toString loc + | Value (s, loc) -> "Value " ^ s ^ " " ^ Loc.toString loc + | Type (s, loc) -> "Type " ^ s ^ " " ^ Loc.toString loc + +let create () : t = [] +let addConstructor ~name ~loc x = Constructor (name, loc) :: x +let addField ~name ~loc x = Field (name, loc) :: x +let addModule ~name ~loc x = Module (name, loc) :: x +let addOpen ~lid x = Open (Utils.flattenLongIdent lid @ ["place holder"]) :: x +let addValue ~name ~loc x = Value (name, loc) :: x +let addType ~name ~loc x = Type (name, loc) :: x + +let iterValuesBeforeFirstOpen f x = + let rec loop items = + match items with + | Value (s, loc) :: rest -> + f s loc; + loop rest + | Open _ :: _ -> () + | _ :: rest -> loop rest + | [] -> () + in + loop x + +let iterValuesAfterFirstOpen f x = + let rec loop foundOpen items = + match items with + | Value (s, loc) :: rest -> + if foundOpen then f s loc; + loop foundOpen rest + | Open _ :: rest -> loop true rest + | _ :: rest -> loop foundOpen rest + | [] -> () + in + loop false x + +let iterConstructorsBeforeFirstOpen f x = + let rec loop items = + match items with + | Constructor (s, loc) :: rest -> + f s loc; + loop rest + | Open _ :: _ -> () + | _ :: rest -> loop rest + | [] -> () + in + loop x + +let iterConstructorsAfterFirstOpen f x = + let rec loop foundOpen items = + match items with + | Constructor (s, loc) :: rest -> + if foundOpen then f s loc; + loop foundOpen rest + | Open _ :: rest -> loop true rest + | _ :: rest -> loop foundOpen rest + | [] -> () + in + loop false x + +let iterTypesBeforeFirstOpen f x = + let rec loop items = + match items with + | Type (s, loc) :: rest -> + f s loc; + loop rest + | Open _ :: _ -> () + | _ :: rest -> loop rest + | [] -> () + in + loop x + +let iterTypesAfterFirstOpen f x = + let rec loop foundOpen items = + match items with + | Type (s, loc) :: rest -> + if foundOpen then f s loc; + loop foundOpen rest + | Open _ :: rest -> loop true rest + | _ :: rest -> loop foundOpen rest + | [] -> () + in + loop false x + +let iterModulesBeforeFirstOpen f x = + let rec loop items = + match items with + | Module (s, loc) :: rest -> + f s loc; + loop rest + | Open _ :: _ -> () + | _ :: rest -> loop rest + | [] -> () + in + loop x + +let iterModulesAfterFirstOpen f x = + let rec loop foundOpen items = + match items with + | Module (s, loc) :: rest -> + if foundOpen then f s loc; + loop foundOpen rest + | Open _ :: rest -> loop true rest + | _ :: rest -> loop foundOpen rest + | [] -> () + in + loop false x + +let getRawOpens x = + x |> Utils.filterMap (function Open path -> Some path | _ -> None) diff --git a/analysis/src/SemanticTokens.ml b/analysis/src/SemanticTokens.ml index 12fc2e583..3ee86c7ef 100644 --- a/analysis/src/SemanticTokens.ml +++ b/analysis/src/SemanticTokens.ml @@ -89,15 +89,6 @@ module Token = struct Buffer.contents buf end -let locToPositions (loc : Location.t) = - (Utils.tupleOfLexing loc.loc_start, Utils.tupleOfLexing loc.loc_end) - -let posToString (loc, col) = Printf.sprintf "(%d,%d)" loc col - -let locToString (loc : Location.t) = - let posStart, posEnd = locToPositions loc in - Printf.sprintf "%s->%s" (posToString posStart) (posToString posEnd) - let isLowercaseId id = id <> "" && @@ -110,7 +101,7 @@ let isUppercaseId id = let c = id.[0] in c >= 'A' && c <= 'Z' -let emitFromPos posStart posEnd ~type_ emitter = +let emitFromRange (posStart, posEnd) ~type_ emitter = let length = if fst posStart = fst posEnd then snd posEnd - snd posStart else 0 in @@ -119,8 +110,7 @@ let emitFromPos posStart posEnd ~type_ emitter = |> Token.add ~line:(fst posStart) ~char:(snd posStart) ~length ~type_ let emitFromLoc ~loc ~type_ emitter = - let posStart, posEnd = locToPositions loc in - emitter |> emitFromPos posStart posEnd ~type_ + emitter |> emitFromRange (Loc.range loc) ~type_ let emitLongident ?(backwards = false) ?(jsx = false) ?(lowerCaseToken = if jsx then Token.JsxLowercase else Token.Variable) @@ -151,17 +141,17 @@ let emitLongident ?(backwards = false) ?(jsx = false) | None -> (posAfter, false) in if debug then - Printf.printf "Lident: %s %s%s %s\n" id (posToString pos) - (if lenMismatch then "->" ^ posToString posEnd else "") + Printf.printf "Lident: %s %s%s %s\n" id (Pos.toString pos) + (if lenMismatch then "->" ^ Pos.toString posEnd else "") (Token.tokenTypeDebug type_); - emitter |> emitFromPos pos posEnd ~type_ + emitter |> emitFromRange (pos, posEnd) ~type_ | id :: segments when isUppercaseId id || isLowercaseId id -> let type_ = if isUppercaseId id then upperCaseToken else lowerCaseToken in if debug then - Printf.printf "Ldot: %s %s %s\n" id (posToString pos) + Printf.printf "Ldot: %s %s %s\n" id (Pos.toString pos) (Token.tokenTypeDebug type_); let length = String.length id in - emitter |> emitFromPos pos (fst pos, snd pos + length) ~type_; + emitter |> emitFromRange (pos, (fst pos, snd pos + length)) ~type_; loop (fst pos, snd pos + length + 1) segments | _ -> () in @@ -173,44 +163,44 @@ let emitLongident ?(backwards = false) ?(jsx = false) else loop pos segments let emitVariable ~id ~debug ~loc emitter = - if debug then Printf.printf "Variable: %s %s\n" id (locToString loc); + if debug then Printf.printf "Variable: %s %s\n" id (Loc.toString loc); emitter |> emitFromLoc ~loc ~type_:Variable let emitJsxOpen ~lid ~debug ~loc emitter = emitter |> emitLongident - ~pos:(Utils.tupleOfLexing loc.Location.loc_start) + ~pos:(Pos.ofLexing loc.Location.loc_start) ~lid ~jsx:true ~debug let emitJsxClose ~lid ~debug ~pos emitter = emitter |> emitLongident ~backwards:true ~pos ~lid ~jsx:true ~debug let emitJsxTag ~debug ~name ~pos emitter = - if debug then Printf.printf "JsxTag %s: %s\n" name (posToString pos); - emitter |> emitFromPos pos (fst pos, snd pos + 1) ~type_:Token.JsxTag + if debug then Printf.printf "JsxTag %s: %s\n" name (Pos.toString pos); + emitter |> emitFromRange (pos, (fst pos, snd pos + 1)) ~type_:Token.JsxTag let emitType ~lid ~debug ~loc emitter = emitter |> emitLongident ~lowerCaseToken:Token.Type - ~pos:(Utils.tupleOfLexing loc.Location.loc_start) + ~pos:(Pos.ofLexing loc.Location.loc_start) ~lid ~debug let emitRecordLabel ~(label : Longident.t Location.loc) ~debug emitter = emitter |> emitLongident ~lowerCaseToken:Token.Property - ~pos:(Utils.tupleOfLexing label.loc.loc_start) - ~posEnd:(Some (Utils.tupleOfLexing label.loc.loc_end)) + ~pos:(Pos.ofLexing label.loc.loc_start) + ~posEnd:(Some (Pos.ofLexing label.loc.loc_end)) ~lid:label.txt ~debug let emitVariant ~(name : Longident.t Location.loc) ~debug emitter = emitter |> emitLongident ~lastToken:(Some Token.EnumMember) - ~pos:(Utils.tupleOfLexing name.loc.loc_start) + ~pos:(Pos.ofLexing name.loc.loc_start) ~lid:name.txt ~debug let command ~debug ~emitter ~path = let processTypeArg (coreType : Parsetree.core_type) = - if debug then Printf.printf "TypeArg: %s\n" (locToString coreType.ptyp_loc) + if debug then Printf.printf "TypeArg: %s\n" (Loc.toString coreType.ptyp_loc) in let typ (iterator : Ast_iterator.iterator) (coreType : Parsetree.core_type) = match coreType.ptyp_desc with @@ -254,8 +244,8 @@ let command ~debug ~emitter ~path = if lid <> Lident "not" then emitter |> emitLongident - ~pos:(Utils.tupleOfLexing loc.loc_start) - ~posEnd:(Some (Utils.tupleOfLexing loc.loc_end)) + ~pos:(Pos.ofLexing loc.loc_start) + ~posEnd:(Some (Pos.ofLexing loc.loc_end)) ~lid ~debug; Ast_iterator.default_iterator.expr iterator e | Pexp_apply ({pexp_desc = Pexp_ident lident; pexp_loc}, args) @@ -271,7 +261,7 @@ let command ~debug ~emitter ~path = emitter (* --> emitJsxTag ~debug ~name:"<" ~pos: - (let pos = Utils.tupleOfLexing e.pexp_loc.loc_start in + (let pos = Pos.ofLexing e.pexp_loc.loc_start in (fst pos, snd pos - 1 (* the AST skips the loc of < somehow *))); emitter |> emitJsxOpen ~lid:lident.txt ~debug ~loc:pexp_loc; @@ -279,7 +269,7 @@ let command ~debug ~emitter ~path = let rec loop = function | (Asttypes.Labelled "children", {Parsetree.pexp_loc = {loc_start}}) :: _ -> - Utils.tupleOfLexing loc_start + Pos.ofLexing loc_start | _ :: args -> loop args | [] -> (* should not happen *) (-1, -1) in @@ -287,7 +277,7 @@ let command ~debug ~emitter ~path = loop args in let posOfFinalGreatherthan = - let pos = Utils.tupleOfLexing e.pexp_loc.loc_end in + let pos = Pos.ofLexing e.pexp_loc.loc_end in (fst pos, snd pos - 1) in let selfClosing = @@ -296,10 +286,10 @@ let command ~debug ~emitter ~path = (* there's an off-by one somehow in the AST *) in (if not selfClosing then - let lineStart, colStart = Utils.tupleOfLexing pexp_loc.loc_start in - let lineEnd, colEnd = Utils.tupleOfLexing pexp_loc.loc_end in + let lineStart, colStart = Pos.ofLexing pexp_loc.loc_start in + let lineEnd, colEnd = Pos.ofLexing pexp_loc.loc_end in let length = if lineStart = lineEnd then colEnd - colStart else 0 in - let lineEndWhole, colEndWhole = Utils.tupleOfLexing e.pexp_loc.loc_end in + let lineEndWhole, colEndWhole = Pos.ofLexing e.pexp_loc.loc_end in if length > 0 && colEndWhole > length then ( emitter |> emitJsxClose ~debug ~lid:lident.txt @@ -316,7 +306,8 @@ let command ~debug ~emitter ~path = Pexp_ident {txt = Longident.Lident (("<" | ">") as op); loc}; }, [_; _] ) -> - if debug then Printf.printf "Binary operator %s %s\n" op (locToString loc); + if debug then + Printf.printf "Binary operator %s %s\n" op (Loc.toString loc); emitter |> emitFromLoc ~loc ~type_:Operator; Ast_iterator.default_iterator.expr iterator e | Pexp_record (cases, _) -> @@ -338,8 +329,7 @@ let command ~debug ~emitter ~path = (me : Parsetree.module_expr) = match me.pmod_desc with | Pmod_ident {txt = lid; loc} -> - emitter - |> emitLongident ~pos:(Utils.tupleOfLexing loc.loc_start) ~lid ~debug; + emitter |> emitLongident ~pos:(Pos.ofLexing loc.loc_start) ~lid ~debug; Ast_iterator.default_iterator.module_expr iterator me | _ -> Ast_iterator.default_iterator.module_expr iterator me in @@ -347,7 +337,7 @@ let command ~debug ~emitter ~path = (mb : Parsetree.module_binding) = emitter |> emitLongident - ~pos:(Utils.tupleOfLexing mb.pmb_name.loc.loc_start) + ~pos:(Pos.ofLexing mb.pmb_name.loc.loc_start) ~lid:(Longident.Lident mb.pmb_name.txt) ~debug; Ast_iterator.default_iterator.module_binding iterator mb in @@ -355,7 +345,7 @@ let command ~debug ~emitter ~path = (md : Parsetree.module_declaration) = emitter |> emitLongident - ~pos:(Utils.tupleOfLexing md.pmd_name.loc.loc_start) + ~pos:(Pos.ofLexing md.pmd_name.loc.loc_start) ~lid:(Longident.Lident md.pmd_name.txt) ~debug; Ast_iterator.default_iterator.module_declaration iterator md in @@ -365,7 +355,7 @@ let command ~debug ~emitter ~path = | Pmty_ident {txt = lid; loc} -> emitter |> emitLongident ~upperCaseToken:Token.Type - ~pos:(Utils.tupleOfLexing loc.loc_start) + ~pos:(Pos.ofLexing loc.loc_start) ~lid ~debug; Ast_iterator.default_iterator.module_type iterator mt | _ -> Ast_iterator.default_iterator.module_type iterator mt @@ -374,7 +364,7 @@ let command ~debug ~emitter ~path = (mtd : Parsetree.module_type_declaration) = emitter |> emitLongident ~upperCaseToken:Token.Type - ~pos:(Utils.tupleOfLexing mtd.pmtd_name.loc.loc_start) + ~pos:(Pos.ofLexing mtd.pmtd_name.loc.loc_start) ~lid:(Longident.Lident mtd.pmtd_name.txt) ~debug; Ast_iterator.default_iterator.module_type_declaration iterator mtd in @@ -382,7 +372,7 @@ let command ~debug ~emitter ~path = (od : Parsetree.open_description) = emitter |> emitLongident - ~pos:(Utils.tupleOfLexing od.popen_lid.loc.loc_start) + ~pos:(Pos.ofLexing od.popen_lid.loc.loc_start) ~lid:od.popen_lid.txt ~debug; Ast_iterator.default_iterator.open_description iterator od in diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index be64933ab..aaa2ca282 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -12,6 +12,7 @@ module Constructor = struct cname : string Location.loc; args : (Types.type_expr * Location.t) list; res : Types.type_expr option; + typeDecl : string * Types.type_declaration; } end @@ -73,8 +74,6 @@ module Exported = struct | Module -> t.modules_ in Hashtbl.iter f tbl - - let removeModule {modules_} name = Hashtbl.remove modules_ name end module Module = struct @@ -94,41 +93,10 @@ module Module = struct and t = Ident of Path.t | Structure of structure | Constraint of t * t end -module Completion = struct - type kind = - | Module of Module.t - | Value of Types.type_expr - | Type of Type.t - | Constructor of Constructor.t * string - | Field of field * string - | FileModule of string - - type t = { - name : string; - extentLoc : Location.t; - deprecated : string option; - docstring : string list; - kind : kind; - } - - let create ~name ~kind = - {name; extentLoc = Location.none; deprecated = None; docstring = []; kind} - - let kindToInt kind = - match kind with - | Module _ -> 9 - | FileModule _ -> 9 - | Constructor (_, _) -> 4 - | Field (_, _) -> 5 - | Type _ -> 22 - | Value _ -> 12 -end - module Declared = struct type 'item t = { name : string Location.loc; extentLoc : Location.t; - scopeLoc : Location.t; stamp : int; modulePath : modulePath; isExported : bool; @@ -149,6 +117,7 @@ module Stamps : sig val findType : t -> int -> Type.t Declared.t option val findValue : t -> int -> Types.type_expr Declared.t option val init : unit -> t + val iterConstructors : (int -> Constructor.t Declared.t -> unit) -> t -> unit val iterModules : (int -> Module.t Declared.t -> unit) -> t -> unit val iterTypes : (int -> Type.t Declared.t -> unit) -> t -> unit val iterValues : (int -> Types.type_expr Declared.t -> unit) -> t -> unit @@ -205,10 +174,11 @@ end = struct Hashtbl.iter (fun stamp d -> match d with KValue d -> f stamp d | _ -> ()) stamps -end -module Env = struct - type t = {stamps : Stamps.t; modulePath : modulePath; scope : Location.t} + let iterConstructors f stamps = + Hashtbl.iter + (fun stamp d -> match d with KConstructor d -> f stamp d | _ -> ()) + stamps end module File = struct @@ -234,6 +204,42 @@ module QueryEnv = struct let fromFile file = {file; exported = file.structure.exported} end +module Completion = struct + type kind = + | Module of Module.t + | Value of Types.type_expr + | ObjLabel of Types.type_expr + | Type of Type.t + | Constructor of Constructor.t * string + | Field of field * string + | FileModule of string + + type t = { + name : string; + env : QueryEnv.t; + deprecated : string option; + docstring : string list; + kind : kind; + } + + let create ~name ~kind ~env = + {name; env; deprecated = None; docstring = []; kind} + + let kindToInt kind = + match kind with + | Module _ -> 9 + | FileModule _ -> 9 + | Constructor (_, _) -> 4 + | ObjLabel _ -> 4 + | Field (_, _) -> 5 + | Type _ -> 22 + | Value _ -> 12 +end + +module Env = struct + type t = {stamps : Stamps.t; modulePath : modulePath} +end + type filePath = string type paths = @@ -389,3 +395,50 @@ let locItemToString {loc = {Location.loc_start; loc_end}; locType} = (* needed for debugging *) let _ = locItemToString + +module Completable = struct + (* Completion context *) + type completionContext = Type | Value | Module | Field + + type contextPath = + | CPString + | CPArray + | CPId of string list * completionContext + | CPField of contextPath * string + | CPObj of contextPath * string + | CPPipe of contextPath * string + + type t = + | Cdecorator of string (** e.g. @module *) + | Clabel of string list * string * string list + (** e.g. (["M", "foo"], "label", ["l1", "l2"]) for M.foo(...~l1...~l2...~label...) *) + | Cpath of contextPath + | Cjsx of string list * string * string list + (** E.g. (["M", "Comp"], "id", ["id1", "id2"]) for List.map str |> String.concat ", ") ^ "]" in + let completionContextToString = function + | Value -> "Value" + | Type -> "Type" + | Module -> "Module" + | Field -> "Field" + in + let rec contextPathToString = function + | CPString -> "string" + | CPArray -> "array" + | CPId (sl, completionContext) -> + completionContextToString completionContext ^ list sl + | CPField (cp, s) -> contextPathToString cp ^ "." ^ str s + | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]" + | CPPipe (cp, s) -> contextPathToString cp ^ "->" ^ s + in + function + | Cpath cp -> "Cpath " ^ contextPathToString cp + | Cdecorator s -> "Cdecorator(" ^ str s ^ ")" + | Clabel (sl1, s, sl2) -> + "Clabel(" ^ (sl1 |> list) ^ ", " ^ str s ^ ", " ^ (sl2 |> list) ^ ")" + | Cjsx (sl1, s, sl2) -> + "Cjsx(" ^ (sl1 |> list) ^ ", " ^ str s ^ ", " ^ (sl2 |> list) ^ ")" +end diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml index 5434ae43d..cb053c523 100644 --- a/analysis/src/Utils.ml +++ b/analysis/src/Utils.ml @@ -42,25 +42,6 @@ let rec find fn items = | one :: rest -> ( match fn one with None -> find fn rest | Some x -> Some x) -let dedup items = - let m = Hashtbl.create (List.length items) in - items - |> List.filter (fun a -> - if Hashtbl.mem m a then false - else ( - Hashtbl.add m a (); - true)) - -let tupleOfLexing {Lexing.pos_lnum; pos_cnum; pos_bol} = - (pos_lnum - 1, pos_cnum - pos_bol) - -(** - Check if pos is within the location, but be fuzzy about when the location ends. - If it's within 5 lines, go with it. -*) -let locationContainsFuzzy {Location.loc_start; loc_end} (l, c) = - tupleOfLexing loc_start <= (l, c) && tupleOfLexing loc_end >= (l - 5, c) - let filterMap f = let rec aux accu = function | [] -> List.rev accu @@ -70,5 +51,19 @@ let filterMap f = aux [] let dumpPath path = Str.global_replace (Str.regexp_string "\\") "/" path - let isUncurriedInternal path = startsWith (Path.name path) "Js.Fn.arity" + +let flattenLongIdent ?(jsx = false) lid = + let rec loop acc lid = + match lid with + | Longident.Lident txt -> txt :: acc + | Ldot (lid, txt) -> + let acc = + if jsx && txt = "createElement" then acc + else if txt = "_" then "" :: acc + else txt :: acc + in + loop acc lid + | Lapply _ -> acc + in + loop [] lid diff --git a/analysis/src/vendor/res_outcome_printer/res_core.ml b/analysis/src/vendor/res_outcome_printer/res_core.ml index 940223486..c400dc9e4 100644 --- a/analysis/src/vendor/res_outcome_printer/res_core.ml +++ b/analysis/src/vendor/res_outcome_printer/res_core.ml @@ -651,18 +651,28 @@ let parseValuePath p = | Lident ident -> Longident.Ldot(path, ident) | Uident uident -> Parser.next p; - Parser.expect Dot p; - aux p (Ldot (path, uident)) + if p.Parser.token = Dot then ( + Parser.expect Dot p; + aux p (Ldot (path, uident)) + ) else ( + Parser.err p (Diagnostics.unexpected p.Parser.token p.breadcrumbs); + path + ) | token -> Parser.err p (Diagnostics.unexpected token p.breadcrumbs); - Longident.Lident "_" + Longident.Ldot (path, "_") in let ident = match p.Parser.token with | Lident ident -> Longident.Lident ident | Uident ident -> Parser.next p; - Parser.expect Dot p; - aux p (Lident ident) + if p.Parser.token = Dot then ( + Parser.expect Dot p; + aux p (Lident ident) + ) else ( + Parser.err p (Diagnostics.unexpected p.Parser.token p.breadcrumbs); + Longident.Lident ident + ) | token -> Parser.err p (Diagnostics.unexpected token p.breadcrumbs); Longident.Lident "_" @@ -682,7 +692,7 @@ let parseValuePathTail p startPos ident = loop p (Longident.Ldot (path, ident)) | token -> Parser.err p (Diagnostics.unexpected token p.breadcrumbs); - Location.mknoloc path + Location.mkloc (Longident.Ldot (path, "_")) (mkLoc startPos p.prevEndPos) in loop p ident @@ -705,7 +715,7 @@ let parseModuleLongIdentTail ~lowercase p startPos ident = end | t -> Parser.err p (Diagnostics.uident t); - Location.mkloc acc (mkLoc startPos p.prevEndPos) + Location.mkloc (Longident.Ldot (acc, "_")) (mkLoc startPos p.prevEndPos) in loop p ident @@ -1921,7 +1931,7 @@ and parseBracketAccess p expr startPos = let rbracket = p.prevEndPos in let e = let identLoc = mkLoc stringStart stringEnd in - let loc = mkLoc lbracket rbracket in + let loc = mkLoc startPos rbracket in Ast_helper.Exp.send ~loc expr (Location.mkloc s identLoc) in let e = parsePrimaryExpr ~operand:e p in @@ -3527,8 +3537,10 @@ and parseValueOrConstructor p = Ast_helper.Exp.ident ~loc (Location.mkloc lident loc) | token -> Parser.next p; + let loc = mkLoc startPos p.prevEndPos in Parser.err p (Diagnostics.unexpected token p.breadcrumbs); - Recover.defaultExpr() + let lident = buildLongident ("_"::acc) in + Ast_helper.Exp.ident ~loc (Location.mkloc lident loc) in aux p [] diff --git a/analysis/tests/bsconfig.json b/analysis/tests/bsconfig.json index 38fa295dd..812c8f61e 100644 --- a/analysis/tests/bsconfig.json +++ b/analysis/tests/bsconfig.json @@ -6,6 +6,7 @@ "subdirs": true } ], + "bsc-flags": ["-w -33-44"], "bs-dependencies": ["@rescript/react"], "reason": { "react-jsx": 3 } } diff --git a/analysis/tests/src/CompletePrioritize2.res b/analysis/tests/src/CompletePrioritize2.res index ddeaf3038..d262bb62a 100644 --- a/analysis/tests/src/CompletePrioritize2.res +++ b/analysis/tests/src/CompletePrioritize2.res @@ -1,10 +1,12 @@ -let a = 4 -let _ = a -let a = "" -let _ = a +let ax = 4 +let _ = ax +let ax = "" +let _ = ax module Test = { type t = {name: int} - let add = (a: t) => a.name + 1 + let add = (ax: t) => ax.name + 1 } -let a: Test.t = {name: 4} -//^com a-> \ No newline at end of file +let ax: Test.t = {name: 4} +//^com ax-> + +//^com ax \ No newline at end of file diff --git a/analysis/tests/src/Completion.res b/analysis/tests/src/Completion.res index db879a365..d5271ca7e 100644 --- a/analysis/tests/src/Completion.res +++ b/analysis/tests/src/Completion.res @@ -122,4 +122,66 @@ let foo = { add((x: Inner.z), Inner.v + y) } -exception MyOtherException \ No newline at end of file +exception MyOtherException + +// ^com children +} +// ^com } + let forAutoRecord: forAutoRecord = assert false +} + +module FAO = { + let forAutoObject = {"forAutoLabel": FAR.forAutoRecord, "age": 32} +} + +// ^com FAO.forAutoObject[" +// ^com FAO.forAutoObject["forAutoLabel"]. +// ^com FAO.forAutoObject["forAutoLabel"].forAuto-> +// ^com FAO.forAutoObject["forAutoLabel"].forAuto->ForAuto.a diff --git a/analysis/tests/src/Debug.res b/analysis/tests/src/Debug.res index 22c432032..00159acdc 100644 --- a/analysis/tests/src/Debug.res +++ b/analysis/tests/src/Debug.res @@ -4,4 +4,15 @@ let _ = Belt.List.map let _ = List.map // ^def -// ^db- \ No newline at end of file + +open Js +module Before = { + open Belt + let _ = Id.getCmpInternal +} +module Inner = { + // ^com eqN + open List + let _ = map +} +// ^db- diff --git a/analysis/tests/src/Jsx.res b/analysis/tests/src/Jsx.res index da2a1293b..26a71ed1c 100644 --- a/analysis/tests/src/Jsx.res +++ b/analysis/tests/src/Jsx.res @@ -6,11 +6,11 @@ module M = { let _ = // ^def -//^com React.string(first) @@ -42,7 +42,7 @@ let y = 44 //^com -// ^def +// ^def module Ext = { @react.component @module("@material-ui/core") @@ -53,8 +53,60 @@ let _ = (Ext.make, Ext.makeProps) //^com + +module WithChildren = { + @react.component + let make = (~name as _: string, ~children) => children +} + +let _ =
+//^com x.DefineSomeFields.thisField + DefineSomeFields.thisValue + +module Outer = { + module Inner = { + let hello = 3 + } +} +let _ = Outer.Inner.hello + +let _ = +
+ +let _ = +
+ + +let _ = +
+ diff --git a/analysis/tests/src/Jsx.resi b/analysis/tests/src/Jsx.resi index 4c00a5530..27ec5f157 100644 --- a/analysis/tests/src/Jsx.resi +++ b/analysis/tests/src/Jsx.resi @@ -2,5 +2,9 @@ let make: (~first: string) => React.element // ^hov -let y : int -// ^hov \ No newline at end of file +let y: int +// ^hov + +// ^com type t = React.e + +// ^com let x : React.e diff --git a/analysis/tests/src/RecordCompletion.res b/analysis/tests/src/RecordCompletion.res index b9a2b1c76..2dcc7dabf 100644 --- a/analysis/tests/src/RecordCompletion.res +++ b/analysis/tests/src/RecordCompletion.res @@ -9,3 +9,10 @@ let t2 = {n2: t} // ^com t.n->m // ^com t2.n2.n->m + +module R = { + type t = {name: string} +} + +let n = {R.name: ""} +// ^com n.R. diff --git a/analysis/tests/src/expected/CompletePrioritize1.res.txt b/analysis/tests/src/expected/CompletePrioritize1.res.txt index 7642c950e..fc6375ade 100644 --- a/analysis/tests/src/expected/CompletePrioritize1.res.txt +++ b/analysis/tests/src/expected/CompletePrioritize1.res.txt @@ -1,4 +1,6 @@ Complete tests/src/CompletePrioritize1.res 4:2 +posCursor:[5:4] posNoWhite:[5:3] Found expr:[5:1->0:-1] +Completable: Cpath Value[a]-> [{ "label": "Test.add", "kind": 12, diff --git a/analysis/tests/src/expected/CompletePrioritize2.res.txt b/analysis/tests/src/expected/CompletePrioritize2.res.txt index e024878a3..d8f653e32 100644 --- a/analysis/tests/src/expected/CompletePrioritize2.res.txt +++ b/analysis/tests/src/expected/CompletePrioritize2.res.txt @@ -1,4 +1,6 @@ Complete tests/src/CompletePrioritize2.res 8:2 +posCursor:[9:5] posNoWhite:[9:4] Found expr:[9:1->0:-1] +Completable: Cpath Value[ax]-> [{ "label": "Test.add", "kind": 12, @@ -7,3 +9,15 @@ Complete tests/src/CompletePrioritize2.res 8:2 "documentation": null }] +Complete tests/src/CompletePrioritize2.res 10:2 +posCursor:[11:3] posNoWhite:[11:2] Found expr:[11:1->11:3] +Pexp_ident ax:[11:1->11:3] +Completable: Cpath Value[ax] +[{ + "label": "ax", + "kind": 12, + "tags": [], + "detail": "Test.t", + "documentation": null + }] + diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index 946922dd0..a0d94f13c 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -1,4 +1,7 @@ Complete tests/src/Completion.res 0:2 +posCursor:[1:9] posNoWhite:[1:8] Found expr:[1:1->1:9] +Pexp_ident MyList.m:[1:1->1:9] +Completable: Cpath Value[MyList, m] [{ "label": "mapReverse", "kind": 12, @@ -68,13 +71,11 @@ Complete tests/src/Completion.res 0:2 }] Complete tests/src/Completion.res 1:2 +posCursor:[2:7] posNoWhite:[2:6] Found expr:[2:1->6:6] +Pexp_ident Array.:[2:1->6:6] +Id breaks up. New path:Array. +Completable: Cpath Value[Array, ""] [{ - "label": "Floatarray", - "kind": 9, - "tags": [], - "detail": "module", - "documentation": null - }, { "label": "fold_left", "kind": 12, "tags": [], @@ -284,9 +285,18 @@ Complete tests/src/Completion.res 1:2 "tags": [], "detail": "(array<'a>, int, array<'a>, int, int) => unit", "documentation": {"kind": "markdown", "value": " [Array.blit v1 o1 v2 o2 len] copies [len] elements\n from array [v1], starting at element number [o1], to array [v2],\n starting at element number [o2]. It works correctly even if\n [v1] and [v2] are the same array, and the source and\n destination chunks overlap.\n\n Raise [Invalid_argument \"Array.blit\"] if [o1] and [len] do not\n designate a valid subarray of [v1], or if [o2] and [len] do not\n designate a valid subarray of [v2]. "} + }, { + "label": "Floatarray", + "kind": 9, + "tags": [], + "detail": "module", + "documentation": null }] Complete tests/src/Completion.res 2:2 +posCursor:[3:8] posNoWhite:[3:7] Found expr:[3:1->3:8] +Pexp_ident Array.m:[3:1->3:8] +Completable: Cpath Value[Array, m] [{ "label": "mapi", "kind": 12, @@ -338,6 +348,9 @@ Complete tests/src/Completion.res 2:2 }] Complete tests/src/Completion.res 12:2 +posCursor:[13:15] posNoWhite:[13:14] Found expr:[13:10->13:15] +Pexp_ident Dep.c:[13:10->13:15] +Completable: Cpath Value[Dep, c] [{ "label": "customDouble", "kind": 12, @@ -347,6 +360,9 @@ Complete tests/src/Completion.res 12:2 }] Complete tests/src/Completion.res 19:2 +posCursor:[20:18] posNoWhite:[20:17] Found expr:[20:9->20:18] +Pexp_apply ...[20:9->20:16] () +Completable: Clabel([Lib, foo], "", []) [{ "label": "age", "kind": 4, @@ -362,6 +378,10 @@ Complete tests/src/Completion.res 19:2 }] Complete tests/src/Completion.res 21:2 +posCursor:[22:11] posNoWhite:[22:10] Found expr:[22:1->22:11] +posCursor:[22:11] posNoWhite:[22:10] Found expr:[22:10->22:11] +Pexp_ident m:[22:10->22:11] +Completable: Cpath array->m [{ "label": "Js.Array2.mapi", "kind": 12, @@ -377,6 +397,10 @@ Complete tests/src/Completion.res 21:2 }] Complete tests/src/Completion.res 23:2 +posCursor:[24:11] posNoWhite:[24:10] Found expr:[24:1->24:11] +posCursor:[24:11] posNoWhite:[24:10] Found expr:[24:8->24:11] +Pexp_ident toU:[24:8->24:11] +Completable: Cpath string->toU [{ "label": "Js.String2.toUpperCase", "kind": 12, @@ -386,6 +410,10 @@ Complete tests/src/Completion.res 23:2 }] Complete tests/src/Completion.res 27:2 +posCursor:[28:6] posNoWhite:[28:5] Found expr:[28:1->28:6] +posCursor:[28:6] posNoWhite:[28:5] Found expr:[28:5->28:6] +Pexp_ident e:[28:5->28:6] +Completable: Cpath Value[op]->e [{ "label": "Belt.Option.eqU", "kind": 12, @@ -401,6 +429,12 @@ Complete tests/src/Completion.res 27:2 }] Complete tests/src/Completion.res 36:2 +posCursor:[37:5] posNoWhite:[37:4] Found expr:[37:1->46:3] +Pexp_apply ...[41:9->41:10] (...[37:1->41:8], ...[42:2->46:3]) +posCursor:[37:5] posNoWhite:[37:4] Found expr:[37:1->41:8] +posCursor:[37:5] posNoWhite:[37:4] Found expr:[37:3->37:5] +Pexp_ident |.:[37:3->37:5] +Completable: Cpath Value[fa]-> [{ "label": "ForAuto.abc", "kind": 12, @@ -416,6 +450,10 @@ Complete tests/src/Completion.res 36:2 }] Complete tests/src/Completion.res 38:2 +posCursor:[39:19] posNoWhite:[39:18] Found expr:[39:1->39:19] +posCursor:[39:19] posNoWhite:[39:18] Found expr:[39:10->39:19] +Pexp_ident Js.Dict.u:[39:10->39:19] +Completable: Cpath Value[Js, Dict, u] [{ "label": "unsafeGet", "kind": 12, @@ -431,6 +469,11 @@ Complete tests/src/Completion.res 38:2 }] Complete tests/src/Completion.res 50:2 +posCursor:[51:28] posNoWhite:[51:27] Found expr:[51:13->51:28] +JSX 51:19] second[51:20->51:26]=...[51:27->51:28]> _children:None +posCursor:[51:28] posNoWhite:[51:27] Found expr:[51:27->51:28] +Pexp_ident z:[51:27->51:28] +Completable: Cpath Value[z] [{ "label": "zzz", "kind": 12, @@ -440,6 +483,11 @@ Complete tests/src/Completion.res 50:2 }] Complete tests/src/Completion.res 52:2 +posCursor:[53:21] posNoWhite:[53:20] Found expr:[53:13->53:21] +JSX 53:19] z[53:20->53:21]=...[53:20->53:21]> _children:None +posCursor:[53:21] posNoWhite:[53:20] Found expr:[53:20->53:21] +Pexp_ident z:[53:20->53:21] +Completable: Cjsx([O, Comp], z, [z]) [{ "label": "zoo", "kind": 4, @@ -639,10 +687,152 @@ DocumentSymbol tests/src/Completion.res "name": "MyOtherException", "kind": 9, "location": {"uri": "Completion.res", "range": {"start": {"line": 124, "character": 0}, "end": {"line": 124, "character": 26}}} +}, +{ + "name": "aa", + "kind": 26, + "location": {"uri": "Completion.res", "range": {"start": {"line": 128, "character": 0}, "end": {"line": 128, "character": 29}}} +}, +{ + "name": "x", + "kind": 7, + "location": {"uri": "Completion.res", "range": {"start": {"line": 128, "character": 10}, "end": {"line": 128, "character": 15}}} +}, +{ + "name": "name", + "kind": 7, + "location": {"uri": "Completion.res", "range": {"start": {"line": 128, "character": 17}, "end": {"line": 128, "character": 28}}} +}, +{ + "name": "bb", + "kind": 26, + "location": {"uri": "Completion.res", "range": {"start": {"line": 129, "character": 0}, "end": {"line": 129, "character": 24}}} +}, +{ + "name": "aa", + "kind": 7, + "location": {"uri": "Completion.res", "range": {"start": {"line": 129, "character": 11}, "end": {"line": 129, "character": 16}}} +}, +{ + "name": "w", + "kind": 7, + "location": {"uri": "Completion.res", "range": {"start": {"line": 129, "character": 18}, "end": {"line": 129, "character": 23}}} +}, +{ + "name": "q", + "kind": 13, + "location": {"uri": "Completion.res", "range": {"start": {"line": 130, "character": 0}, "end": {"line": 130, "character": 23}}} +}, +{ + "name": "WithChildren", + "kind": 2, + "location": {"uri": "Completion.res", "range": {"start": {"line": 136, "character": 7}, "end": {"line": 139, "character": 1}}} +}, +{ + "name": "make", + "kind": 12, + "location": {"uri": "Completion.res", "range": {"start": {"line": 138, "character": 2}, "end": {"line": 138, "character": 69}}} +}, +{ + "name": "z", + "kind": 26, + "location": {"uri": "Completion.res", "range": {"start": {"line": 145, "character": 0}, "end": {"line": 145, "character": 32}}} +}, +{ + "name": "Allo", + "kind": 22, + "location": {"uri": "Completion.res", "range": {"start": {"line": 145, "character": 9}, "end": {"line": 145, "character": 15}}} +}, +{ + "name": "Asterix", + "kind": 22, + "location": {"uri": "Completion.res", "range": {"start": {"line": 145, "character": 16}, "end": {"line": 145, "character": 25}}} +}, +{ + "name": "Baba", + "kind": 22, + "location": {"uri": "Completion.res", "range": {"start": {"line": 145, "character": 26}, "end": {"line": 145, "character": 32}}} +}, +{ + "name": "Private", + "kind": 2, + "location": {"uri": "Completion.res", "range": {"start": {"line": 151, "character": 7}, "end": {"line": 154, "character": 1}}} +}, +{ + "name": "awr", + "kind": 16, + "location": {"uri": "Completion.res", "range": {"start": {"line": 152, "character": 12}, "end": {"line": 152, "character": 23}}} +}, +{ + "name": "b", + "kind": 13, + "location": {"uri": "Completion.res", "range": {"start": {"line": 153, "character": 2}, "end": {"line": 153, "character": 13}}} +}, +{ + "name": "Shadow", + "kind": 2, + "location": {"uri": "Completion.res", "range": {"start": {"line": 158, "character": 7}, "end": {"line": 165, "character": 1}}} +}, +{ + "name": "A", + "kind": 2, + "location": {"uri": "Completion.res", "range": {"start": {"line": 159, "character": 9}, "end": {"line": 161, "character": 3}}} +}, +{ + "name": "shadowed", + "kind": 16, + "location": {"uri": "Completion.res", "range": {"start": {"line": 160, "character": 4}, "end": {"line": 160, "character": 20}}} +}, +{ + "name": "B", + "kind": 2, + "location": {"uri": "Completion.res", "range": {"start": {"line": 162, "character": 9}, "end": {"line": 164, "character": 3}}} +}, +{ + "name": "shadowed", + "kind": 15, + "location": {"uri": "Completion.res", "range": {"start": {"line": 163, "character": 4}, "end": {"line": 163, "character": 21}}} +}, +{ + "name": "FAR", + "kind": 2, + "location": {"uri": "Completion.res", "range": {"start": {"line": 174, "character": 7}, "end": {"line": 177, "character": 1}}} +}, +{ + "name": "forAutoRecord", + "kind": 26, + "location": {"uri": "Completion.res", "range": {"start": {"line": 175, "character": 2}, "end": {"line": 175, "character": 67}}} +}, +{ + "name": "forAuto", + "kind": 7, + "location": {"uri": "Completion.res", "range": {"start": {"line": 175, "character": 24}, "end": {"line": 175, "character": 42}}} +}, +{ + "name": "something", + "kind": 7, + "location": {"uri": "Completion.res", "range": {"start": {"line": 175, "character": 44}, "end": {"line": 175, "character": 66}}} +}, +{ + "name": "forAutoRecord", + "kind": 13, + "location": {"uri": "Completion.res", "range": {"start": {"line": 176, "character": 2}, "end": {"line": 176, "character": 49}}} +}, +{ + "name": "FAO", + "kind": 2, + "location": {"uri": "Completion.res", "range": {"start": {"line": 179, "character": 7}, "end": {"line": 181, "character": 1}}} +}, +{ + "name": "forAutoObject", + "kind": 13, + "location": {"uri": "Completion.res", "range": {"start": {"line": 180, "character": 2}, "end": {"line": 180, "character": 68}}} } ] Complete tests/src/Completion.res 56:2 +Attribute id:reac:[57:1->57:6] label:reac +Completable: Cdecorator(reac) [{ "label": "react.component", "kind": 4, @@ -652,6 +842,10 @@ Complete tests/src/Completion.res 56:2 }] Complete tests/src/Completion.res 58:2 +posCursor:[59:8] posNoWhite:[59:7] Found expr:[0:-1->69:17] +Pexp_apply ...[67:6->67:7] (...[67:8->69:17]) +Attribute id:react.let:[59:1->67:3] label:react. +Completable: Cdecorator(react.) [{ "label": "component", "kind": 4, @@ -661,6 +855,9 @@ Complete tests/src/Completion.res 58:2 }] Complete tests/src/Completion.res 60:2 +posCursor:[61:25] posNoWhite:[61:24] Found expr:[61:9->61:25] +Pexp_apply ...[61:9->61:16] (~name61:18->61:22=...[61:18->61:22]) +Completable: Clabel([Lib, foo], "", [name]) [{ "label": "age", "kind": 4, @@ -670,6 +867,9 @@ Complete tests/src/Completion.res 60:2 }] Complete tests/src/Completion.res 62:2 +posCursor:[63:24] posNoWhite:[63:23] Found expr:[63:9->63:24] +Pexp_apply ...[63:9->63:16] (~age63:18->63:21=...[63:18->63:21]) +Completable: Clabel([Lib, foo], "", [age]) [{ "label": "name", "kind": 4, @@ -679,6 +879,9 @@ Complete tests/src/Completion.res 62:2 }] Complete tests/src/Completion.res 64:2 +posCursor:[65:30] posNoWhite:[65:29] Found expr:[65:9->65:30] +Pexp_apply ...[65:9->65:16] (~age65:18->65:21=...[65:23->65:26]) +Completable: Clabel([Lib, foo], "", [age]) [{ "label": "name", "kind": 4, @@ -688,21 +891,15 @@ Complete tests/src/Completion.res 64:2 }] Complete tests/src/Completion.res 67:2 -[{ - "label": "age", - "kind": 4, - "tags": [], - "detail": "int", - "documentation": null - }, { - "label": "name", - "kind": 4, - "tags": [], - "detail": "string", - "documentation": null - }] +posCursor:[68:2] posNoWhite:[68:1] Found expr:[67:8->69:17] +Pexp_apply ...[67:8->67:15] (~age69:1->69:4=...[69:5->69:6], ~name69:9->69:13=...[69:14->69:16]) +Completable: Clabel([Lib, foo], "", [age, name]) +[] Complete tests/src/Completion.res 72:2 +posCursor:[73:11] posNoWhite:[73:10] Found expr:[73:1->75:18] +Pexp_send a[73:10->73:11] e:[73:1->73:8] +Completable: Cpath Value[someObj]["a"] [{ "label": "age", "kind": 4, @@ -712,6 +909,9 @@ Complete tests/src/Completion.res 72:2 }] Complete tests/src/Completion.res 76:2 +posCursor:[77:22] posNoWhite:[77:21] Found expr:[77:1->80:10] +Pexp_send [77:22->77:22] e:[77:1->77:20] +Completable: Cpath Value[nestedObj]["x"]["y"][""] [{ "label": "age", "kind": 4, @@ -727,6 +927,9 @@ Complete tests/src/Completion.res 76:2 }] Complete tests/src/Completion.res 79:2 +posCursor:[80:5] posNoWhite:[80:4] Found expr:[80:1->82:20] +Pexp_send a[80:4->80:5] e:[80:1->80:2] +Completable: Cpath Value[o]["a"] [{ "label": "age", "kind": 4, @@ -736,6 +939,9 @@ Complete tests/src/Completion.res 79:2 }] Complete tests/src/Completion.res 83:2 +posCursor:[84:15] posNoWhite:[84:14] Found expr:[84:1->101:20] +Pexp_send [84:15->84:15] e:[84:1->84:13] +Completable: Cpath Value[no]["x"]["y"][""] [{ "label": "name", "kind": 4, @@ -751,6 +957,9 @@ Complete tests/src/Completion.res 83:2 }] Complete tests/src/Completion.res 88:3 +posCursor:[89:3] posNoWhite:[89:2] Found expr:[89:1->93:3] +Pexp_field [89:1->89:2] _:[93:0->93:3] +Completable: Cpath Value[r]."" [{ "label": "x", "kind": 5, @@ -766,6 +975,9 @@ Complete tests/src/Completion.res 88:3 }] Complete tests/src/Completion.res 90:3 +posCursor:[91:19] posNoWhite:[91:18] Found expr:[91:1->93:3] +Pexp_field [91:1->91:18] _:[93:0->93:3] +Completable: Cpath Value[Obj, Rec, recordVal]."" [{ "label": "xx", "kind": 5, @@ -781,6 +993,11 @@ Complete tests/src/Completion.res 90:3 }] Complete tests/src/Completion.res 96:3 +posCursor:[97:3] posNoWhite:[97:2] Found expr:[96:11->99:1] +posCursor:[97:3] posNoWhite:[97:2] Found expr:[97:1->98:5] +posCursor:[97:3] posNoWhite:[97:2] Found expr:[97:1->97:3] +Pexp_ident my:[97:1->97:3] +Completable: Cpath Value[my] [{ "label": "myAmazingFunction", "kind": 12, @@ -790,6 +1007,92 @@ Complete tests/src/Completion.res 96:3 }] Complete tests/src/Completion.res 100:3 +posCursor:[101:13] posNoWhite:[101:12] Found expr:[101:1->120:32] +Pexp_send [101:13->101:13] e:[101:1->101:11] +Completable: Cpath Value[Obj, object][""] +[{ + "label": "name", + "kind": 4, + "tags": [], + "detail": "string", + "documentation": null + }, { + "label": "age", + "kind": 4, + "tags": [], + "detail": "int", + "documentation": null + }] + +Complete tests/src/Completion.res 125:3 +posCursor:[126:4] posNoWhite:[126:3] Found expr:[126:2->126:4] +JSX 126:4] > _children:None +posCursor:[126:4] posNoWhite:[126:3] Found expr:[126:2->126:4] +Pexp_ident O..createElement:[126:2->126:4] +Id breaks up. New path:O.. +Completable: Cpath Module[O, ""] +[{ + "label": "Comp", + "kind": 9, + "tags": [], + "detail": "module", + "documentation": null + }] + +Complete tests/src/Completion.res 130:3 +posCursor:[131:6] posNoWhite:[131:5] Found expr:[131:1->136:6] +Pexp_field [131:1->131:5] _:[136:0->136:6] +Completable: Cpath Value[q].aa."" +[{ + "label": "x", + "kind": 5, + "tags": [], + "detail": "x: int\n\ntype aa = {x: int, name: string}", + "documentation": null + }, { + "label": "name", + "kind": 5, + "tags": [], + "detail": "name: string\n\ntype aa = {x: int, name: string}", + "documentation": null + }] + +Complete tests/src/Completion.res 131:3 +posCursor:[132:7] posNoWhite:[132:6] Found expr:[132:1->132:7] +Pexp_field [132:1->132:5] n:[132:6->132:7] +Completable: Cpath Value[q].aa.n +[{ + "label": "name", + "kind": 5, + "tags": [], + "detail": "name: string\n\ntype aa = {x: int, name: string}", + "documentation": null + }] + +Complete tests/src/Completion.res 133:3 +posCursor:[134:4] posNoWhite:[134:3] Found expr:[134:1->134:4] +Pexp_construct Lis:[134:1->134:4] None +Completable: Cpath Value[Lis] +[{ + "label": "List", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }, { + "label": "ListLabels", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }] + +Complete tests/src/Completion.res 139:3 +posCursor:[140:15] posNoWhite:[140:13] Found expr:[140:2->140:14] +JSX 140:14] > _children:None +posCursor:[140:15] posNoWhite:[140:13] Found expr:[140:2->140:14] +Pexp_ident WithChildren.createElement:[140:2->140:14] +Completable: Cjsx([WithChildren], "", []) [{ "label": "name", "kind": 4, @@ -797,10 +1100,190 @@ Complete tests/src/Completion.res 100:3 "detail": "string", "documentation": null }, { + "label": "key", + "kind": 4, + "tags": [], + "detail": "string", + "documentation": null + }] + +Complete tests/src/Completion.res 141:3 +posCursor:[142:14] posNoWhite:[142:13] Found type:[142:10->142:14] +Ptyp_constr Js.n:[142:10->142:14] +Completable: Cpath Type[Js, n] +[{ + "label": "null_undefined", + "kind": 22, + "tags": [], + "detail": "type null_undefined<'a> = nullable<'a>", + "documentation": null + }, { + "label": "nullable", + "kind": 22, + "tags": [], + "detail": "type nullable<+'a>", + "documentation": {"kind": "markdown", "value": " value of this type can be [undefined], [null] or ['a]\n this type is the same as type [t] n {!Null_undefined} "} + }, { + "label": "null", + "kind": 22, + "tags": [], + "detail": "type null<+'a>", + "documentation": {"kind": "markdown", "value": " nullable, value of this type can be either [null] or ['a]\n this type is the same as type [t] in {!Null}\n"} + }] + +Complete tests/src/Completion.res 142:3 +posCursor:[143:18] posNoWhite:[143:17] Found type:[143:10->143:18] +Ptyp_constr ForAuto.:[143:10->143:18] +Completable: Cpath Type[ForAuto, ""] +[{ + "label": "t", + "kind": 22, + "tags": [], + "detail": "type t = int", + "documentation": null + }] + +Complete tests/src/Completion.res 146:3 +posCursor:[147:11] posNoWhite:[147:10] Found expr:[147:9->147:11] +Pexp_construct As:[147:9->147:11] None +Completable: Cpath Value[As] +[{ + "label": "Asterix", + "kind": 4, + "tags": [], + "detail": "Asterix\n\ntype z = Allo | Asterix | Baba", + "documentation": null + }] + +Complete tests/src/Completion.res 148:3 +Pmod_ident For:[149:12->149:15] +Completable: Cpath Module[For] +[{ + "label": "ForAuto", + "kind": 9, + "tags": [], + "detail": "module", + "documentation": null + }, { + "label": "Format", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }] + +Complete tests/src/Completion.res 155:3 +posCursor:[156:9] posNoWhite:[156:8] Found expr:[156:1->158:6] +Pexp_ident Private.:[156:1->158:6] +Id breaks up. New path:Private. +Completable: Cpath Value[Private, ""] +[{ + "label": "b", + "kind": 12, + "tags": [], + "detail": "int", + "documentation": null + }] + +Complete tests/src/Completion.res 166:3 +posCursor:[167:4] posNoWhite:[167:3] Found expr:[167:1->167:4] +Pexp_ident sha:[167:1->167:4] +Completable: Cpath Value[sha] +[] + +Complete tests/src/Completion.res 168:3 +posCursor:[169:4] posNoWhite:[169:3] Found expr:[169:1->169:4] +Pexp_ident sha:[169:1->169:4] +Completable: Cpath Value[sha] +[{ + "label": "shadowed", + "kind": 12, + "tags": [], + "detail": "int", + "documentation": null + }] + +Complete tests/src/Completion.res 170:3 +posCursor:[171:4] posNoWhite:[171:3] Found expr:[171:1->171:4] +Pexp_ident sha:[171:1->171:4] +Completable: Cpath Value[sha] +[{ + "label": "shadowed", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": null + }] + +Complete tests/src/Completion.res 182:3 +posCursor:[183:20] posNoWhite:[183:19] Found expr:[183:1->184:27] +Pexp_send [183:20->183:20] e:[183:1->183:18] +Completable: Cpath Value[FAO, forAutoObject][""] +[{ "label": "age", "kind": 4, "tags": [], "detail": "int", "documentation": null + }, { + "label": "forAutoLabel", + "kind": 4, + "tags": [], + "detail": "FAR.forAutoRecord", + "documentation": null + }] + +Complete tests/src/Completion.res 183:3 +posCursor:[184:35] posNoWhite:[184:34] Found expr:[184:1->188:0] +Pexp_field [184:1->184:34] _:[188:0->188:0] +Completable: Cpath Value[FAO, forAutoObject]["forAutoLabel"]."" +[{ + "label": "forAuto", + "kind": 5, + "tags": [], + "detail": "forAuto: ForAuto.t\n\ntype forAutoRecord = {\n forAuto: ForAuto.t,\n something: option,\n}", + "documentation": null + }, { + "label": "something", + "kind": 5, + "tags": [], + "detail": "something: option\n\ntype forAutoRecord = {\n forAuto: ForAuto.t,\n something: option,\n}", + "documentation": null + }] + +Complete tests/src/Completion.res 184:3 +posCursor:[185:44] posNoWhite:[185:43] Found expr:[185:1->0:-1] +Completable: Cpath Value[FAO, forAutoObject]["forAutoLabel"].forAuto-> +[{ + "label": "ForAuto.abc", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": null + }, { + "label": "ForAuto.abd", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": null + }] + +Complete tests/src/Completion.res 185:3 +posCursor:[186:53] posNoWhite:[186:52] Found expr:[186:1->186:53] +posCursor:[186:53] posNoWhite:[186:52] Found expr:[186:44->186:53] +Pexp_ident ForAuto.a:[186:44->186:53] +Completable: Cpath Value[ForAuto, a] +[{ + "label": "abc", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": null + }, { + "label": "abd", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": null }] diff --git a/analysis/tests/src/expected/Cross.res.txt b/analysis/tests/src/expected/Cross.res.txt index aebbfa5a2..5076fff01 100644 --- a/analysis/tests/src/expected/Cross.res.txt +++ b/analysis/tests/src/expected/Cross.res.txt @@ -94,5 +94,8 @@ TypeDefinition tests/src/Cross.res 37:37 {"uri": "DefinitionWithInterface.resi", "range": {"start": {"line": 3, "character": 0}, "end": {"line": 3, "character": 6}}} Complete tests/src/Cross.res 39:2 +posCursor:[40:26] posNoWhite:[40:25] Found expr:[40:1->40:26] +Pexp_ident DefinitionWithInterface.a:[40:1->40:26] +Completable: Cpath Value[DefinitionWithInterface, a] [] diff --git a/analysis/tests/src/expected/Debug.res.txt b/analysis/tests/src/expected/Debug.res.txt index fecfa8798..980e47212 100644 --- a/analysis/tests/src/expected/Debug.res.txt +++ b/analysis/tests/src/expected/Debug.res.txt @@ -59,4 +59,36 @@ resolvePath path:map [ref] Inner uri list.mli {"uri": "list.mli", "range": {"start": {"line": 116, "character": 4}, "end": {"line": 116, "character": 7}}} +Complete tests/src/Debug.res 12:5 +posCursor:[13:4] posNoWhite:[13:3] Found expr:[13:1->13:4] +Pexp_ident eqN:[13:1->13:4] +Completable: Cpath Value[eqN] +Opens folkz > 1 Js.place holder +Package opens Pervasives +Package opens Pervasives +fileForModule Impl cmt:tests/node_modules/rescript/lib/ocaml/pervasives.cmti res:tests/node_modules/rescript/lib/ocaml/pervasives.mli +resolving open Js.place holder +resolvePath path:Js.place holder +Not local +resolvePath path:Js.place holder +fileForModule Impl cmt:tests/node_modules/rescript/lib/ocaml/js.cmt res:tests/node_modules/rescript/lib/ocaml/js.ml +resolvePath path:place holder +Opens nows 2 pervasives.mli js.ml +findLocalCompletionsWithOpens uri:Debug.res pos:13:4 +findAllCompletions uri:js.ml +findAllCompletions uri:pervasives.mli +[{ + "label": "eqNullable", + "kind": 12, + "tags": [], + "detail": "('a, nullable<'a>) => bool", + "documentation": null + }, { + "label": "eqNull", + "kind": 12, + "tags": [], + "detail": "('a, null<'a>) => bool", + "documentation": null + }] + diff --git a/analysis/tests/src/expected/Div.res.txt b/analysis/tests/src/expected/Div.res.txt index 2d9a106ab..cde6ff78f 100644 --- a/analysis/tests/src/expected/Div.res.txt +++ b/analysis/tests/src/expected/Div.res.txt @@ -2,6 +2,11 @@ Hover tests/src/Div.res 1:10 {"contents": "```rescript\n(\n string,\n ~props: ReactDOMRe.domProps=?,\n array,\n) => React.element\n```"} Complete tests/src/Div.res 3:3 +posCursor:[4:15] posNoWhite:[4:14] Found expr:[4:2->4:15] +JSX 4:5] dangerous[4:6->4:15]=...[4:6->4:15]> _children:None +posCursor:[4:15] posNoWhite:[4:14] Found expr:[4:6->4:15] +Pexp_ident dangerous:[4:6->4:15] +Completable: Cjsx([div], dangerous, [dangerous]) [{ "label": "dangerouslySetInnerHTML", "kind": 4, diff --git a/analysis/tests/src/expected/Highlight.res.txt b/analysis/tests/src/expected/Highlight.res.txt index b14f2499e..ed6a39136 100644 --- a/analysis/tests/src/expected/Highlight.res.txt +++ b/analysis/tests/src/expected/Highlight.res.txt @@ -1,143 +1,143 @@ Highlight tests/src/Highlight.res structure items:38 diagnostics:0 -Lident: M (0,7) Class -Lident: C (1,9) Class -Lident: Component (1,13) Class -Variable: _c (4,4)->(4,6) -JsxTag <: (4,9) -Lident: Component (4,10) Class -Variable: _mc (6,4)->(6,7) -JsxTag <: (6,10) -Ldot: M (6,11) Class -Lident: C (6,13) Class -Variable: _d (8,4)->(8,6) -JsxTag <: (8,9) -Lident: div (8,10) JsxLowercase -Variable: _d2 (10,4)->(10,7) -JsxTag <: (11,2) -Lident: div (11,3) JsxLowercase -Lident: div (16,4) JsxLowercase -JsxTag >: (11,6) -JsxTag >: (16,7) -Ldot: React (12,5) Class -Lident: string (12,11) Variable -JsxTag <: (13,4) -Lident: div (13,5) JsxLowercase -Lident: div (13,34) JsxLowercase -JsxTag >: (13,8) -JsxTag >: (13,37) -Ldot: React (13,11) Class -Lident: string (13,17) Variable -Ldot: React (14,5) Class -Lident: string (14,11) Variable -Ldot: React (15,5) Class -Lident: string (15,11) Variable -Lident: pair (18,5) Type -Lident: looooooooooooooooooooooooooooooooooooooong_int (20,5) Type -Lident: int (20,54) Type -Lident: looooooooooooooooooooooooooooooooooooooong_string (22,5) Type -Lident: string (22,57) Type -Lident: pairIntString (24,5) Type -Lident: list (24,21) Type -TypeArg: (25,2)->(28,3) -Lident: pair (25,2) Type -TypeArg: (26,4)->(26,50) -TypeArg: (27,4)->(27,53) -Lident: looooooooooooooooooooooooooooooooooooooong_int (26,4) Type -Lident: looooooooooooooooooooooooooooooooooooooong_string (27,4) Type -Binary operator < (31,12)->(31,13) -Binary operator > (31,22)->(31,23) -Lident: MT (33,12) Type -Lident: DDF (34,9) Class -Lident: DDF (39,7) Class -Lident: DDF (40,9) Class -Lident: MT (39,12) Type -Lident: XX (45,7) Class -Lident: YY (46,9) Class -Lident: t (47,9) Type -Lident: int (47,13) Type -Ldot: XX (51,5) Class -Lident: YY (51,8) Class -Lident: tt (53,5) Type -Lident: t (53,10) Type -Lident: T (57,7) Class -Lident: someRecord (58,7) Type -Lident: someField (59,4) Property -Lident: int (59,15) Type -Lident: someOtherField (60,4) Property -Lident: string (60,20) Type -Lident: theParam (61,4) Property -Lident: someEnum (64,7) Type -Lident: A (64,18) EnumMember -Lident: B (64,22) EnumMember -Lident: C (64,26) EnumMember -Variable: foo (67,4)->(67,7) -Variable: x (67,10)->(67,11) -Ldot: T (67,17) Class -Lident: someField (67,19) Property -Lident: x (67,15) Variable -Variable: add (69,4)->(69,7) -Variable: x (69,21)->(69,22) -Variable: world (69,24)->(69,30) -Lident: x (69,35) Variable -Lident: world (69,39) Variable -Lident: add (71,8) Variable -JsxTag <: (73,8) -Lident: div (73,9) JsxLowercase -Lident: div (73,36) JsxLowercase -JsxTag >: (73,24) -JsxTag >: (73,39) -JsxTag <: (73,26) -Lident: div (73,27) JsxLowercase -Lident: SomeComponent (75,7) Class -Lident: Nested (76,9) Class -Variable: make (78,8)->(78,12) -Variable: children (78,16)->(78,25) -Lident: children (79,10) Variable -JsxTag <: (84,8) -Ldot: SomeComponent (84,9) Class -Lident: Nested (84,23) Class -Ldot: SomeComponent (84,41) Class -Lident: Nested (84,55) Class -JsxTag >: (84,29) -JsxTag >: (84,61) -JsxTag <: (84,31) -Lident: div (84,32) JsxLowercase -Variable: toAs (90,4)->(90,8) -Variable: x (90,19)->(90,20) -Lident: x (90,25) Variable -Variable: _toEquals (91,4)->(91,13) -Lident: toAs (91,16) Variable -Variable: to (93,4)->(93,6) -Lident: to (94,9) Variable -Lident: to (94,14) Variable -Lident: to (94,20) Variable -Lident: to (94,25) Variable -Lident: ToAsProp (98,7) Class -Variable: make (100,6)->(100,10) -Variable: to (100,14)->(100,17) -Ldot: React (101,8) Class -Lident: int (101,14) Variable -Lident: to (101,18) Variable -JsxTag <: (104,8) -Lident: ToAsProp (104,9) Class -Variable: true (107,4)->(107,11) -Lident: true (108,8)->(108,15) Variable -Variable: enumInModule (110,4)->(110,16) -Ldot: T (110,19) Class -Lident: A (110,21) EnumMember -Lident: typeInModule (112,5) Type -Ldot: XX (112,20) Class -Ldot: YY (112,23) Class -Lident: t (112,26) Type -Lident: QQ (114,7) Class -Lident: somePolyEnumType (115,7) Type -Lident: list (118,29) Type -TypeArg: (118,34)->(118,37) -Lident: int (118,34) Type -Variable: x (123,8)->(123,9) -Lident: x (124,9) Variable -Ldot: QQ (126,8) Class -Lident: somePolyEnumType (126,11) Type -Lident: abc (133,9)->(133,14) Property +Lident: M 0:7 Class +Lident: C 1:9 Class +Lident: Component 1:13 Class +Variable: _c [4:4->4:6] +JsxTag <: 4:9 +Lident: Component 4:10 Class +Variable: _mc [6:4->6:7] +JsxTag <: 6:10 +Ldot: M 6:11 Class +Lident: C 6:13 Class +Variable: _d [8:4->8:6] +JsxTag <: 8:9 +Lident: div 8:10 JsxLowercase +Variable: _d2 [10:4->10:7] +JsxTag <: 11:2 +Lident: div 11:3 JsxLowercase +Lident: div 16:4 JsxLowercase +JsxTag >: 11:6 +JsxTag >: 16:7 +Ldot: React 12:5 Class +Lident: string 12:11 Variable +JsxTag <: 13:4 +Lident: div 13:5 JsxLowercase +Lident: div 13:34 JsxLowercase +JsxTag >: 13:8 +JsxTag >: 13:37 +Ldot: React 13:11 Class +Lident: string 13:17 Variable +Ldot: React 14:5 Class +Lident: string 14:11 Variable +Ldot: React 15:5 Class +Lident: string 15:11 Variable +Lident: pair 18:5 Type +Lident: looooooooooooooooooooooooooooooooooooooong_int 20:5 Type +Lident: int 20:54 Type +Lident: looooooooooooooooooooooooooooooooooooooong_string 22:5 Type +Lident: string 22:57 Type +Lident: pairIntString 24:5 Type +Lident: list 24:21 Type +TypeArg: [25:2->28:3] +Lident: pair 25:2 Type +TypeArg: [26:4->26:50] +TypeArg: [27:4->27:53] +Lident: looooooooooooooooooooooooooooooooooooooong_int 26:4 Type +Lident: looooooooooooooooooooooooooooooooooooooong_string 27:4 Type +Binary operator < [31:12->31:13] +Binary operator > [31:22->31:23] +Lident: MT 33:12 Type +Lident: DDF 34:9 Class +Lident: DDF 39:7 Class +Lident: DDF 40:9 Class +Lident: MT 39:12 Type +Lident: XX 45:7 Class +Lident: YY 46:9 Class +Lident: t 47:9 Type +Lident: int 47:13 Type +Ldot: XX 51:5 Class +Lident: YY 51:8 Class +Lident: tt 53:5 Type +Lident: t 53:10 Type +Lident: T 57:7 Class +Lident: someRecord 58:7 Type +Lident: someField 59:4 Property +Lident: int 59:15 Type +Lident: someOtherField 60:4 Property +Lident: string 60:20 Type +Lident: theParam 61:4 Property +Lident: someEnum 64:7 Type +Lident: A 64:18 EnumMember +Lident: B 64:22 EnumMember +Lident: C 64:26 EnumMember +Variable: foo [67:4->67:7] +Variable: x [67:10->67:11] +Ldot: T 67:17 Class +Lident: someField 67:19 Property +Lident: x 67:15 Variable +Variable: add [69:4->69:7] +Variable: x [69:21->69:22] +Variable: world [69:24->69:30] +Lident: x 69:35 Variable +Lident: world 69:39 Variable +Lident: add 71:8 Variable +JsxTag <: 73:8 +Lident: div 73:9 JsxLowercase +Lident: div 73:36 JsxLowercase +JsxTag >: 73:24 +JsxTag >: 73:39 +JsxTag <: 73:26 +Lident: div 73:27 JsxLowercase +Lident: SomeComponent 75:7 Class +Lident: Nested 76:9 Class +Variable: make [78:8->78:12] +Variable: children [78:16->78:25] +Lident: children 79:10 Variable +JsxTag <: 84:8 +Ldot: SomeComponent 84:9 Class +Lident: Nested 84:23 Class +Ldot: SomeComponent 84:41 Class +Lident: Nested 84:55 Class +JsxTag >: 84:29 +JsxTag >: 84:61 +JsxTag <: 84:31 +Lident: div 84:32 JsxLowercase +Variable: toAs [90:4->90:8] +Variable: x [90:19->90:20] +Lident: x 90:25 Variable +Variable: _toEquals [91:4->91:13] +Lident: toAs 91:16 Variable +Variable: to [93:4->93:6] +Lident: to 94:9 Variable +Lident: to 94:14 Variable +Lident: to 94:20 Variable +Lident: to 94:25 Variable +Lident: ToAsProp 98:7 Class +Variable: make [100:6->100:10] +Variable: to [100:14->100:17] +Ldot: React 101:8 Class +Lident: int 101:14 Variable +Lident: to 101:18 Variable +JsxTag <: 104:8 +Lident: ToAsProp 104:9 Class +Variable: true [107:4->107:11] +Lident: true 108:8->108:15 Variable +Variable: enumInModule [110:4->110:16] +Ldot: T 110:19 Class +Lident: A 110:21 EnumMember +Lident: typeInModule 112:5 Type +Ldot: XX 112:20 Class +Ldot: YY 112:23 Class +Lident: t 112:26 Type +Lident: QQ 114:7 Class +Lident: somePolyEnumType 115:7 Type +Lident: list 118:29 Type +TypeArg: [118:34->118:37] +Lident: int 118:34 Type +Variable: x [123:8->123:9] +Lident: x 124:9 Variable +Ldot: QQ 126:8 Class +Lident: somePolyEnumType 126:11 Type +Lident: abc 133:9->133:14 Property diff --git a/analysis/tests/src/expected/Jsx.res.txt b/analysis/tests/src/expected/Jsx.res.txt index 6c6ff43d3..b14c9ac4b 100644 --- a/analysis/tests/src/expected/Jsx.res.txt +++ b/analysis/tests/src/expected/Jsx.res.txt @@ -2,27 +2,19 @@ Definition tests/src/Jsx.res 5:9 {"uri": "Jsx.res", "range": {"start": {"line": 2, "character": 6}, "end": {"line": 2, "character": 10}}} Complete tests/src/Jsx.res 7:2 -[{ - "label": "first", - "kind": 4, - "tags": [], - "detail": "string", - "documentation": null - }, { - "label": "fun", - "kind": 4, - "tags": [], - "detail": "option", - "documentation": null - }, { - "label": "key", - "kind": 4, - "tags": [], - "detail": "string", - "documentation": null - }] +posCursor:[8:13] posNoWhite:[8:12] Found expr:[8:2->8:13] +JSX 8:3] second[8:4->8:10]=...[8:11->8:13]> _children:None +posCursor:[8:13] posNoWhite:[8:12] Found expr:[8:11->8:13] +Pexp_ident fi:[8:11->8:13] +Completable: Cpath Value[fi] +[] Complete tests/src/Jsx.res 9:2 +posCursor:[10:18] posNoWhite:[10:17] Found expr:[10:2->10:18] +JSX 10:3] second[10:4->10:10]=...[10:11->10:16] f[10:17->10:18]=...[10:17->10:18]> _children:None +posCursor:[10:18] posNoWhite:[10:17] Found expr:[10:17->10:18] +Pexp_ident f:[10:17->10:18] +Completable: Cjsx([M], f, [second, f]) [{ "label": "first", "kind": 4, @@ -38,33 +30,37 @@ Complete tests/src/Jsx.res 9:2 }] Complete tests/src/Jsx.res 11:2 +posCursor:[12:11] posNoWhite:[12:10] Found expr:[12:10->12:11] +JSX 12:11] > _children:None +posCursor:[12:11] posNoWhite:[12:10] Found expr:[12:10->12:11] +Pexp_ident M.createElement:[12:10->12:11] +Completable: Cpath Module[M] [{ - "label": "second", - "kind": 4, - "tags": [], - "detail": "option", - "documentation": null - }, { - "label": "first", - "kind": 4, + "label": "M", + "kind": 9, "tags": [], - "detail": "string", + "detail": "module", "documentation": null }, { - "label": "fun", - "kind": 4, + "label": "Map", + "kind": 9, "tags": [], - "detail": "option", + "detail": "file module", "documentation": null }, { - "label": "key", - "kind": 4, + "label": "MoreLabels", + "kind": 9, "tags": [], - "detail": "string", + "detail": "file module", "documentation": null }] Complete tests/src/Jsx.res 18:2 +posCursor:[19:17] posNoWhite:[19:16] Found expr:[19:2->19:17] +JSX 19:3] prop[19:4->19:8]=...[19:10->19:14] k[19:16->19:17]=...[19:16->19:17]> _children:None +posCursor:[19:17] posNoWhite:[19:16] Found expr:[19:16->19:17] +Pexp_ident k:[19:16->19:17] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -74,6 +70,11 @@ Complete tests/src/Jsx.res 18:2 }] Complete tests/src/Jsx.res 20:2 +posCursor:[21:15] posNoWhite:[21:14] Found expr:[21:2->21:15] +JSX 21:3] prop[21:4->21:8]=...[21:9->21:13] k[21:14->21:15]=...[21:14->21:15]> _children:None +posCursor:[21:15] posNoWhite:[21:14] Found expr:[21:14->21:15] +Pexp_ident k:[21:14->21:15] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -83,6 +84,11 @@ Complete tests/src/Jsx.res 20:2 }] Complete tests/src/Jsx.res 22:2 +posCursor:[23:19] posNoWhite:[23:18] Found expr:[23:2->23:19] +JSX 23:3] prop[23:4->23:8]=...[23:9->23:17] k[23:18->23:19]=...[23:18->23:19]> _children:None +posCursor:[23:19] posNoWhite:[23:18] Found expr:[23:18->23:19] +Pexp_ident k:[23:18->23:19] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -92,6 +98,11 @@ Complete tests/src/Jsx.res 22:2 }] Complete tests/src/Jsx.res 24:2 +posCursor:[25:22] posNoWhite:[25:21] Found expr:[25:2->25:22] +JSX 25:3] prop[25:4->25:8]=...[25:9->25:20] k[25:21->25:22]=...[25:21->25:22]> _children:None +posCursor:[25:22] posNoWhite:[25:21] Found expr:[25:21->25:22] +Pexp_ident k:[25:21->25:22] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -101,6 +112,11 @@ Complete tests/src/Jsx.res 24:2 }] Complete tests/src/Jsx.res 26:2 +posCursor:[27:16] posNoWhite:[27:15] Found expr:[27:2->27:16] +JSX 27:3] prop[27:4->27:8]=...[27:10->27:14] k[27:15->27:16]=...[27:15->27:16]> _children:None +posCursor:[27:16] posNoWhite:[27:15] Found expr:[27:15->27:16] +Pexp_ident k:[27:15->27:16] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -110,6 +126,11 @@ Complete tests/src/Jsx.res 26:2 }] Complete tests/src/Jsx.res 28:2 +posCursor:[29:14] posNoWhite:[29:13] Found expr:[29:2->29:14] +JSX 29:3] prop[29:4->29:8]=...[29:9->29:12] k[29:13->29:14]=...[29:13->29:14]> _children:None +posCursor:[29:14] posNoWhite:[29:13] Found expr:[29:13->29:14] +Pexp_ident k:[29:13->29:14] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -119,6 +140,11 @@ Complete tests/src/Jsx.res 28:2 }] Complete tests/src/Jsx.res 30:2 +posCursor:[31:15] posNoWhite:[31:14] Found expr:[31:2->31:15] +JSX 31:3] prop[31:4->31:8]=...[31:9->31:13] k[31:14->31:15]=...[31:14->31:15]> _children:None +posCursor:[31:15] posNoWhite:[31:14] Found expr:[31:14->31:15] +Pexp_ident k:[31:14->31:15] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -128,9 +154,25 @@ Complete tests/src/Jsx.res 30:2 }] Complete tests/src/Jsx.res 32:2 -[] +posCursor:[33:16] posNoWhite:[33:15] Found expr:[33:2->33:16] +JSX 33:3] prop[33:4->33:8]=...[33:9->33:14] k[33:15->33:16]=...[33:15->33:16]> _children:None +posCursor:[33:16] posNoWhite:[33:15] Found expr:[33:15->33:16] +Pexp_ident k:[33:15->33:16] +Completable: Cjsx([M], k, [prop, k]) +[{ + "label": "key", + "kind": 4, + "tags": [], + "detail": "string", + "documentation": null + }] Complete tests/src/Jsx.res 34:2 +posCursor:[35:14] posNoWhite:[35:13] Found expr:[35:2->35:14] +JSX 35:3] prop[35:4->35:8]=...[35:9->35:12] k[35:13->35:14]=...[35:13->35:14]> _children:None +posCursor:[35:14] posNoWhite:[35:13] Found expr:[35:13->35:14] +Pexp_ident k:[35:13->35:14] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -140,6 +182,11 @@ Complete tests/src/Jsx.res 34:2 }] Complete tests/src/Jsx.res 36:2 +posCursor:[37:25] posNoWhite:[37:24] Found expr:[37:2->37:25] +JSX 37:3] prop[37:4->37:8]=...[37:9->37:23] k[37:24->37:25]=...[37:24->37:25]> _children:None +posCursor:[37:25] posNoWhite:[37:24] Found expr:[37:24->37:25] +Pexp_ident k:[37:24->37:25] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -149,6 +196,11 @@ Complete tests/src/Jsx.res 36:2 }] Complete tests/src/Jsx.res 38:2 +posCursor:[39:36] posNoWhite:[39:35] Found expr:[39:2->39:36] +JSX 39:3] prop[39:4->39:8]=...[39:9->39:34] k[39:35->39:36]=...[39:35->39:36]> _children:None +posCursor:[39:36] posNoWhite:[39:35] Found expr:[39:35->39:36] +Pexp_ident k:[39:35->39:36] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -158,6 +210,11 @@ Complete tests/src/Jsx.res 38:2 }] Complete tests/src/Jsx.res 40:2 +posCursor:[41:23] posNoWhite:[41:22] Found expr:[41:2->41:23] +JSX 41:3] prop[41:4->41:8]=...[41:9->41:21] k[41:22->41:23]=...[41:22->41:23]> _children:None +posCursor:[41:23] posNoWhite:[41:22] Found expr:[41:22->41:23] +Pexp_ident k:[41:22->41:23] +Completable: Cjsx([M], k, [prop, k]) [{ "label": "key", "kind": 4, @@ -170,6 +227,11 @@ Definition tests/src/Jsx.res 43:11 {"uri": "Component.res", "range": {"start": {"line": 1, "character": 4}, "end": {"line": 1, "character": 8}}} Complete tests/src/Jsx.res 52:2 +posCursor:[53:8] posNoWhite:[53:7] Found expr:[53:2->53:8] +JSX 53:5] al[53:6->53:8]=...[53:6->53:8]> _children:None +posCursor:[53:8] posNoWhite:[53:7] Found expr:[53:6->53:8] +Pexp_ident al:[53:6->53:8] +Completable: Cjsx([Ext], al, [al]) [{ "label": "align", "kind": 4, @@ -179,19 +241,20 @@ Complete tests/src/Jsx.res 52:2 }] Complete tests/src/Jsx.res 54:2 +posCursor:[55:9] posNoWhite:[55:8] Found expr:[55:2->55:9] +JSX 55:3] first[55:4->55:9]=...[55:4->55:9]> _children:None +posCursor:[55:9] posNoWhite:[55:8] Found expr:[55:4->55:9] +Pexp_ident first:[55:4->55:9] +Completable: Cjsx([M], first, [first]) +[] + +Complete tests/src/Jsx.res 56:2 +posCursor:[57:14] posNoWhite:[57:13] Found expr:[57:2->57:14] +JSX 57:3] first[57:4->57:9]=...[57:10->57:12] k[57:13->57:14]=...[57:13->57:14]> _children:None +posCursor:[57:14] posNoWhite:[57:13] Found expr:[57:13->57:14] +Pexp_ident k:[57:13->57:14] +Completable: Cjsx([M], k, [first, k]) [{ - "label": "second", - "kind": 4, - "tags": [], - "detail": "option", - "documentation": null - }, { - "label": "fun", - "kind": 4, - "tags": [], - "detail": "option", - "documentation": null - }, { "label": "key", "kind": 4, "tags": [], @@ -199,7 +262,12 @@ Complete tests/src/Jsx.res 54:2 "documentation": null }] -Complete tests/src/Jsx.res 56:2 +Complete tests/src/Jsx.res 58:2 +posCursor:[59:21] posNoWhite:[59:20] Found expr:[59:2->59:21] +JSX 59:3] first[59:4->59:9]=...[59:17->59:19] k[59:20->59:21]=...[59:20->59:21]> _children:None +posCursor:[59:21] posNoWhite:[59:20] Found expr:[59:20->59:21] +Pexp_ident k:[59:20->59:21] +Completable: Cjsx([M], k, [first, k]) [{ "label": "key", "kind": 4, @@ -208,12 +276,143 @@ Complete tests/src/Jsx.res 56:2 "documentation": null }] -Complete tests/src/Jsx.res 58:2 +Complete tests/src/Jsx.res 60:2 +posCursor:[61:4] posNoWhite:[61:3] Found expr:[61:2->65:69] +Pexp_apply ...[63:20->63:21] (...[61:2->63:19], ...[64:2->65:69]) +posCursor:[61:4] posNoWhite:[61:3] Found expr:[61:2->63:19] +JSX 61:3] > _children:61:3 +posCursor:[61:4] posNoWhite:[61:3] Found expr:[61:3->63:20] +Pexp_construct :::[63:0->63:20] [63:0->63:20] +posCursor:[61:4] posNoWhite:[61:3] Found expr:__ghost__[61:3->63:20] +Pexp_construct []:__ghost__[61:3->63:20] None +[] + +Complete tests/src/Jsx.res 68:2 +posCursor:[69:14] posNoWhite:[69:13] Found expr:[69:2->69:14] +JSX 69:14] > _children:None +posCursor:[69:14] posNoWhite:[69:13] Found expr:[69:2->69:14] +Pexp_ident WithChildren.createElement:[69:2->69:14] +Completable: Cpath Module[WithChildren] [{ - "label": "key", + "label": "WithChildren", + "kind": 9, + "tags": [], + "detail": "module", + "documentation": null + }] + +Complete tests/src/Jsx.res 69:2 +posCursor:[70:16] posNoWhite:[70:15] Found expr:[70:2->70:16] +JSX 70:14] n[70:15->70:16]=...[70:15->70:16]> _children:None +posCursor:[70:16] posNoWhite:[70:15] Found expr:[70:15->70:16] +Pexp_ident n:[70:15->70:16] +Completable: Cjsx([WithChildren], n, [n]) +[{ + "label": "name", "kind": 4, "tags": [], "detail": "string", "documentation": null }] +Complete tests/src/Jsx.res 71:2 +posCursor:[72:16] posNoWhite:[72:15] Found type:[72:9->72:16] +Ptyp_constr React.e:[72:9->72:16] +Completable: Cpath Type[React, e] +[{ + "label": "element", + "kind": 22, + "tags": [], + "detail": "type element", + "documentation": null + }] + +Complete tests/src/Jsx.res 72:2 +posCursor:[73:18] posNoWhite:[73:17] Found type:[73:9->75:6] +Ptyp_constr ReactDOMR:[73:9->75:6] +Completable: Cpath Type[ReactDOMR] +[{ + "label": "ReactDOMRe", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }] + +Complete tests/src/Jsx.res 77:5 +posCursor:[78:17] posNoWhite:[78:16] Found expr:[78:9->78:17] +Pexp_apply ...[78:11->78:12] (...[78:9->78:10], ...[78:13->78:17]) +posCursor:[78:17] posNoWhite:[78:16] Found expr:[78:13->78:17] +Pexp_field [78:13->78:14] th:[78:15->78:17] +Completable: Cpath Value[x].th +[] + +Complete tests/src/Jsx.res 80:3 +posCursor:[81:26] posNoWhite:[81:25] Found expr:[81:9->85:3] +Pexp_ident DefineSomeFields.:[81:9->85:3] +Id breaks up. New path:DefineSomeFields. +posCursor:[81:26] posNoWhite:[81:25] Found expr:[0:-1->85:70] +Pexp_apply ...[85:6->85:7] (...[85:8->85:70]) +Completable: Cpath Value[DefineSomeFields, ""] +[{ + "label": "thisValue", + "kind": 12, + "tags": [], + "detail": "int", + "documentation": null + }] + +Complete tests/src/Jsx.res 82:3 +posCursor:[83:34] posNoWhite:[83:33] Found expr:[83:9->83:34] +Pexp_apply ...[83:11->83:12] (...[83:9->83:10], ...[83:13->83:34]) +posCursor:[83:34] posNoWhite:[83:33] Found expr:[83:13->83:34] +Pexp_field [83:13->83:14] DefineSomeFields.th:[83:15->83:34] +Completable: Cpath Module[DefineSomeFields].th +[{ + "label": "thisField", + "kind": 5, + "tags": [], + "detail": "thisField: int\n\ntype r = {thisField: int, thatField: string}", + "documentation": null + }, { + "label": "thatField", + "kind": 5, + "tags": [], + "detail": "thatField: string\n\ntype r = {thisField: int, thatField: string}", + "documentation": null + }] + +Complete tests/src/Jsx.res 95:5 +posCursor:[96:16] posNoWhite:[96:15] Found expr:[95:3->98:4] +JSX 95:6] x[96:1->96:2]=...[96:3->96:16] name[97:4->97:8]=...[97:9->97:11]> _children:98:2 +posCursor:[96:16] posNoWhite:[96:15] Found expr:[96:3->96:16] +Pexp_ident Outer.Inner.h:[96:3->96:16] +Completable: Cpath Value[Outer, Inner, h] +[{ + "label": "hello", + "kind": 12, + "tags": [], + "detail": "int", + "documentation": null + }] + +Complete tests/src/Jsx.res 101:5 +posCursor:[102:15] posNoWhite:[102:14] Found expr:[101:3->103:9] +JSX 101:6] x[102:1->102:2]=...[102:3->103:8]> _children:None +posCursor:[102:15] posNoWhite:[102:14] Found expr:[102:3->103:8] +Pexp_ident Outer.Inner.name:[102:3->103:8] +Id breaks up. New path:Outer.Inner. +Completable: Cpath Value[Outer, Inner, ""] +[{ + "label": "hello", + "kind": 12, + "tags": [], + "detail": "int", + "documentation": null + }] + +Complete tests/src/Jsx.res 107:5 +posCursor:[108:3] posNoWhite:[108:2] Found expr:[107:3->109:9] +JSX 107:6] x[108:1->108:2]=...[109:4->109:8]> _children:None +[] + diff --git a/analysis/tests/src/expected/Jsx.resi.txt b/analysis/tests/src/expected/Jsx.resi.txt index f82257ca3..72bb22f47 100644 --- a/analysis/tests/src/expected/Jsx.resi.txt +++ b/analysis/tests/src/expected/Jsx.resi.txt @@ -4,3 +4,27 @@ Hover tests/src/Jsx.resi 1:4 Hover tests/src/Jsx.resi 4:4 {"contents": "```rescript\nint\n```"} +Complete tests/src/Jsx.resi 6:3 +posCursor:[7:17] posNoWhite:[7:16] Found type:[7:10->7:17] +Ptyp_constr React.e:[7:10->7:17] +Completable: Cpath Type[React, e] +[{ + "label": "element", + "kind": 22, + "tags": [], + "detail": "type element", + "documentation": null + }] + +Complete tests/src/Jsx.resi 8:3 +posCursor:[9:16] posNoWhite:[9:15] Found type:[9:9->9:16] +Ptyp_constr React.e:[9:9->9:16] +Completable: Cpath Type[React, e] +[{ + "label": "element", + "kind": 22, + "tags": [], + "detail": "type element", + "documentation": null + }] + diff --git a/analysis/tests/src/expected/RecordCompletion.res.txt b/analysis/tests/src/expected/RecordCompletion.res.txt index eca2e8d1c..c88f872eb 100644 --- a/analysis/tests/src/expected/RecordCompletion.res.txt +++ b/analysis/tests/src/expected/RecordCompletion.res.txt @@ -1,4 +1,8 @@ Complete tests/src/RecordCompletion.res 7:3 +posCursor:[8:7] posNoWhite:[8:6] Found expr:[8:1->8:7] +posCursor:[8:7] posNoWhite:[8:6] Found expr:[8:6->8:7] +Pexp_ident m:[8:6->8:7] +Completable: Cpath Value[t].n->m [{ "label": "Js.Array2.mapi", "kind": 12, @@ -14,6 +18,10 @@ Complete tests/src/RecordCompletion.res 7:3 }] Complete tests/src/RecordCompletion.res 9:3 +posCursor:[10:11] posNoWhite:[10:10] Found expr:[10:1->10:11] +posCursor:[10:11] posNoWhite:[10:10] Found expr:[10:10->10:11] +Pexp_ident m:[10:10->10:11] +Completable: Cpath Value[t2].n2.n->m [{ "label": "Js.Array2.mapi", "kind": 12, @@ -28,3 +36,15 @@ Complete tests/src/RecordCompletion.res 9:3 "documentation": null }] +Complete tests/src/RecordCompletion.res 16:3 +posCursor:[17:5] posNoWhite:[17:4] Found expr:[17:1->19:0] +Pexp_field [17:1->17:2] R.:[17:3->19:0] +Completable: Cpath Module[R]."" +[{ + "label": "name", + "kind": 5, + "tags": [], + "detail": "name: string\n\ntype t = {name: string}", + "documentation": null + }] +