Skip to content
121 changes: 69 additions & 52 deletions analysis/src/Completion.ml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type prop = {
}

type jsxProps = {
componentPath : string list;
compName : Longident.t Location.loc;
props : prop list;
childrenStart : (int * int) option;
}
Expand All @@ -47,7 +47,11 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor ~posAfterCompName
match props with
| prop :: rest ->
if prop.posStart <= posBeforeCursor && posBeforeCursor < prop.posEnd then
Some (Completable.Cjsx (jsxProps.componentPath, prop.name, allLabels))
Some
(Completable.Cjsx
( Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt,
prop.name,
allLabels ))
else if
prop.posEnd <= posBeforeCursor
&& posBeforeCursor < Loc.start prop.exp.pexp_loc
Expand All @@ -62,7 +66,11 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor ~posAfterCompName
in
let afterCompName = posBeforeCursor >= posAfterCompName in
if afterCompName && beforeChildrenStart then
Some (Cjsx (jsxProps.componentPath, "", allLabels))
Some
(Cjsx
( Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt,
"",
allLabels ))
else None
in
loop jsxProps.props
Expand Down Expand Up @@ -91,13 +99,17 @@ let rec skipComment ~pos ~i ~depth str =

let extractJsxProps ~(compName : Longident.t Location.loc) ~args =
let thisCaseShouldNotHappen =
{componentPath = []; props = []; childrenStart = None}
{
compName = Location.mknoloc (Longident.Lident "");
props = [];
childrenStart = None;
}
in
let rec processProps ~acc args =
match args with
| (Asttypes.Labelled "children", {Parsetree.pexp_loc}) :: _ ->
{
componentPath = Utils.flattenLongIdent ~jsx:true compName.txt;
compName;
props = List.rev acc;
childrenStart =
(if pexp_loc.loc_ghost then None else Some (Loc.start pexp_loc));
Expand Down Expand Up @@ -235,6 +247,21 @@ let completionWithParser ~debug ~path ~posCursor ~currentFile ~text =
| _ -> None)
| _ -> None
in
let flattenLidCheckDot ?(jsx = true) (lid : Longident.t Location.loc) =
(* Flatten an identifier keeping track of whether the current cursor
is after a "." in the id followed by a blank character.
In that case, cut the path after ".". *)
let cutAtOffset =
let idStart = Loc.start lid.loc in
match blankAfterCursor with
| Some '.' ->
if fst posBeforeCursor = fst idStart then
Some (snd posBeforeCursor - snd idStart)
else None
| _ -> None
in
Utils.flattenLongIdent ~cutAtOffset ~jsx lid.txt
in

let found = ref false in
let result = ref None in
Expand Down Expand Up @@ -449,46 +476,31 @@ let completionWithParser ~debug ~path ~posCursor ~currentFile ~text =
so the apply expression does not include the cursor *)
if setPipeResult ~lhs ~id:"" then setFound ()
| _ ->
if expr.pexp_loc |> Loc.hasPos ~pos:posNoWhite then (
if expr.pexp_loc |> Loc.hasPos ~pos:posNoWhite && !result = None then (
setFound ();
match expr.pexp_desc with
| Pexp_constant _ -> setResult Cnone
| Pexp_ident id ->
| Pexp_ident lid ->
let lidPath = flattenLidCheckDot lid in
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) ->
(lidPath |> String.concat ".")
(Loc.toString lid.loc);
if lid.loc |> Loc.hasPos ~pos:posBeforeCursor then
setResult (Cpath (CPId (lidPath, Value)))
| Pexp_construct (lid, eOpt) ->
let lidPath = flattenLidCheckDot lid in
if debug then
Printf.printf "Pexp_construct %s:%s %s\n"
(Utils.flattenLongIdent id.txt |> String.concat "\n")
(Loc.toString id.loc)
(lidPath |> String.concat "\n")
(Loc.toString lid.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)))
eOpt = None && (not lid.loc.loc_ghost)
&& lid.loc |> Loc.hasPos ~pos:posBeforeCursor
then setResult (Cpath (CPId (lidPath, Value)))
| Pexp_field (e, fieldName) -> (
if debug then
Printf.printf "Pexp_field %s %s:%s\n" (Loc.toString e.pexp_loc)
Expand All @@ -507,7 +519,10 @@ let completionWithParser ~debug ~path ~posCursor ~currentFile ~text =
let contextPath =
Completable.CPField
( CPId (Utils.flattenLongIdent id, Module),
if name = "_" then "" else name )
if blankAfterCursor = Some '.' then
(* x.M. field ---> M. *) ""
else if name = "_" then ""
else name )
in
setResult (Cpath contextPath)
| Lapply _ -> ()
Expand All @@ -518,9 +533,10 @@ let completionWithParser ~debug ~path ~posCursor ~currentFile ~text =
| Pexp_apply ({pexp_desc = Pexp_ident compName}, args)
when Res_parsetree_viewer.isJsxExpression expr ->
let jsxProps = extractJsxProps ~compName ~args in
let compNamePath = flattenLidCheckDot ~jsx:true compName in
if debug then
Printf.printf "JSX <%s:%s %s> _children:%s\n"
(jsxProps.componentPath |> String.concat ",")
(compNamePath |> String.concat ".")
(Loc.toString compName.loc)
(jsxProps.props
|> List.map (fun {name; posStart; posEnd; exp} ->
Expand All @@ -537,9 +553,7 @@ let completionWithParser ~debug ~path ~posCursor ~currentFile ~text =
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)))
setResult (Cpath (CPId (compNamePath, Module)))
| Pexp_apply
( {pexp_desc = Pexp_ident {txt = Lident "|."}},
[
Expand Down Expand Up @@ -630,39 +644,42 @@ let completionWithParser ~debug ~path ~posCursor ~currentFile ~text =
(Pos.toString posCursor) (Pos.toString posNoWhite)
(Loc.toString core_type.ptyp_loc);
match core_type.ptyp_desc with
| Ptyp_constr (id, _args) ->
| Ptyp_constr (lid, _args) ->
let lidPath = flattenLidCheckDot lid in
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)))
(lidPath |> String.concat ".")
(Loc.toString lid.loc);
if lid.loc |> Loc.hasPos ~pos:posBeforeCursor then
setResult (Cpath (CPId (lidPath, 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 ->
| Pmod_ident lid when lid.loc |> Loc.hasPos ~pos:posBeforeCursor ->
let lidPath = flattenLidCheckDot lid in
if debug then
Printf.printf "Pmod_ident %s:%s\n"
(Utils.flattenLongIdent id.txt |> String.concat ".")
(Loc.toString id.loc);
(lidPath |> String.concat ".")
(Loc.toString lid.loc);
found := true;
setResult (Cpath (CPId (Utils.flattenLongIdent id.txt, Module)))
setResult (Cpath (CPId (lidPath, 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 ->
| Pmty_ident lid when lid.loc |> Loc.hasPos ~pos:posBeforeCursor ->
let lidPath = flattenLidCheckDot lid in
if debug then
Printf.printf "Pmty_ident %s:%s\n"
(Utils.flattenLongIdent id.txt |> String.concat ".")
(Loc.toString id.loc);
(lidPath |> String.concat ".")
(Loc.toString lid.loc);
found := true;
setResult (Cpath (CPId (Utils.flattenLongIdent id.txt, Module)))
setResult (Cpath (CPId (lidPath, Module)))
| _ -> ());
Ast_iterator.default_iterator.module_type iterator mt
in
Expand Down
22 changes: 11 additions & 11 deletions analysis/src/Utils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,17 @@ let filterMap f =
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 =
let flattenLongIdent ?(jsx = false) ?(cutAtOffset = None) lid =
let rec loop lid =
match lid with
| Longident.Lident txt -> txt :: acc
| Longident.Lident txt -> ([txt], String.length txt)
| 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
let path, offset = loop lid in
if Some offset = cutAtOffset then ("" :: path, offset + 1)
else if jsx && txt = "createElement" then (path, offset)
else if txt = "_" then ("" :: path, offset + 1)
else (txt :: path, offset + 1 + String.length txt)
| Lapply _ -> ([], 0)
in
loop [] lid
let path, _ = loop lid in
List.rev path
7 changes: 7 additions & 0 deletions analysis/tests/src/Completion.res
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,10 @@ module SomeLocalModule = {
// ^com
// type zz = SomeLocalModule.
// ^com

type record = {
someProp: string,
// otherProp: SomeLocalModule.
// ^com
thirdProp: string,
}
17 changes: 15 additions & 2 deletions analysis/tests/src/Jsx.res
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ let _ = <M first="abc" />
// <M second=fi
// ^com


// <M second="abc" f
// ^com


// let e = <M
// ^com

Expand Down Expand Up @@ -140,3 +138,18 @@ let _ =
// ^com
name=""
/>

module Nested = {
module Comp = {
@react.component
let make = (~name) => React.string(name)
}
}

let _ = <Nested.Comp name="" />

// let _ = <Nested.Co name="" />
// ^com

// let _ = <Nested. name="" />
// ^com
3 changes: 3 additions & 0 deletions analysis/tests/src/RecordCompletion.res
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ module R = {
let n = {R.name: ""}
// n.R.
// ^com

// n.R. xx
// ^com
Loading