Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cut an identifier at blank/space after dot. #400

Merged
merged 12 commits into from
Apr 28, 2022
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