From e5794c06bea69ad870ad9a971d8d93916c5b7ba5 Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 13 Feb 2025 09:26:18 +0100 Subject: [PATCH 1/6] During call --- analysis/src/CompletionBackEnd.ml | 4 +- analysis/src/CompletionFrontEnd.ml | 79 ++++--- analysis/src/Packages.ml | 1 + analysis/src/SharedTypes.ml | 1 + analysis/src/TypeUtils.ml | 5 +- .../tests/src/CompletionJsx.res | 11 + .../tests/src/expected/CompletionJsx.res.txt | 194 +++++++++++++++++- 7 files changed, 260 insertions(+), 35 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 3becd1cecf..fb5fcdc252 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -979,7 +979,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact path @ [fieldName] |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext:Field ~env ~scope - | CPField {contextPath = cp; fieldName; posOfDot; exprLoc} -> ( + | CPField {contextPath = cp; fieldName; posOfDot; exprLoc; inJsx} -> ( if Debug.verbose () then print_endline "[dot_completion]--> Triggered"; let completionsFromCtxPath = cp @@ -1013,7 +1013,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact CPApply (cp, [Asttypes.Noloc.Nolabel]) | _ -> cp); id = fieldName; - inJsx = false; + inJsx; lhsLoc = exprLoc; } in diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 661af008d3..aea7a6141d 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -206,7 +206,8 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor }) | _ -> loop args -let rec exprToContextPathInner (e : Parsetree.expression) = +let rec exprToContextPathInner ~(inJsxContext : bool) (e : Parsetree.expression) + = match e.pexp_desc with | Pexp_constant (Pconst_string _) -> Some Completable.CPString | Pexp_constant (Pconst_integer _) -> Some CPInt @@ -217,13 +218,13 @@ let rec exprToContextPathInner (e : Parsetree.expression) = (CPArray (match exprs with | [] -> None - | exp :: _ -> exprToContextPath exp)) + | exp :: _ -> exprToContextPath ~inJsxContext exp)) | Pexp_ident {txt = Lident "->"} -> None | Pexp_ident {txt; loc} -> Some (CPId {path = Utils.flattenLongIdent txt; completionContext = Value; loc}) | Pexp_field (e1, {txt = Lident name}) -> ( - match exprToContextPath e1 with + match exprToContextPath ~inJsxContext e1 with | Some contextPath -> Some (CPField @@ -232,6 +233,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) = fieldName = name; posOfDot = None; exprLoc = e1.pexp_loc; + inJsx = inJsxContext; }) | _ -> None) | Pexp_field (e1, {loc; txt = Ldot (lid, name)}) -> @@ -249,9 +251,10 @@ let rec exprToContextPathInner (e : Parsetree.expression) = fieldName = name; posOfDot = None; exprLoc = e1.pexp_loc; + inJsx = inJsxContext; }) | Pexp_send (e1, {txt}) -> ( - match exprToContextPath e1 with + match exprToContextPath ~inJsxContext e1 with | None -> None | Some contexPath -> Some (CPObj (contexPath, txt))) | Pexp_apply @@ -266,7 +269,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) = [(_, lhs); (_, {pexp_desc = Pexp_apply {funct = d; args; partial}})]; } -> (* Transform away pipe with apply call *) - exprToContextPath + exprToContextPath ~inJsxContext { pexp_desc = Pexp_apply {funct = d; args = (Nolabel, lhs) :: args; partial}; @@ -283,7 +286,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) = partial; } -> (* Transform away pipe with identifier *) - exprToContextPath + exprToContextPath ~inJsxContext { pexp_desc = Pexp_apply @@ -296,29 +299,31 @@ let rec exprToContextPathInner (e : Parsetree.expression) = pexp_attributes; } | Pexp_apply {funct = e1; args} -> ( - match exprToContextPath e1 with + match exprToContextPath ~inJsxContext e1 with | None -> None | Some contexPath -> Some (CPApply (contexPath, args |> List.map fst |> List.map Asttypes.to_noloc)) ) | Pexp_tuple exprs -> - let exprsAsContextPaths = exprs |> List.filter_map exprToContextPath in + let exprsAsContextPaths = + exprs |> List.filter_map (exprToContextPath ~inJsxContext) + in if List.length exprs = List.length exprsAsContextPaths then Some (CTuple exprsAsContextPaths) else None | _ -> None -and exprToContextPath (e : Parsetree.expression) = +and exprToContextPath ~(inJsxContext : bool) (e : Parsetree.expression) = match ( Res_parsetree_viewer.has_await_attribute e.pexp_attributes, - exprToContextPathInner e ) + exprToContextPathInner ~inJsxContext e ) with | true, Some ctxPath -> Some (CPAwait ctxPath) | false, Some ctxPath -> Some ctxPath | _, None -> None -let completePipeChain (exp : Parsetree.expression) = +let completePipeChain ~(inJsxContext : bool) (exp : Parsetree.expression) = (* Complete the end of pipe chains by reconstructing the pipe chain as a single pipe, so it can be completed. Example: @@ -334,7 +339,8 @@ let completePipeChain (exp : Parsetree.expression) = funct = {pexp_desc = Pexp_ident {txt = Lident "->"}}; args = [_; (_, {pexp_desc = Pexp_apply {funct = d}})]; } -> - exprToContextPath exp |> Option.map (fun ctxPath -> (ctxPath, d.pexp_loc)) + exprToContextPath ~inJsxContext exp + |> Option.map (fun ctxPath -> (ctxPath, d.pexp_loc)) (* When the left side of the pipe we're completing is an identifier application. Example: someArray->filterAllTheGoodStuff-> *) | Pexp_apply @@ -342,7 +348,8 @@ let completePipeChain (exp : Parsetree.expression) = funct = {pexp_desc = Pexp_ident {txt = Lident "->"}}; args = [_; (_, {pexp_desc = Pexp_ident _; pexp_loc})]; } -> - exprToContextPath exp |> Option.map (fun ctxPath -> (ctxPath, pexp_loc)) + exprToContextPath ~inJsxContext exp + |> Option.map (fun ctxPath -> (ctxPath, pexp_loc)) | _ -> None let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor @@ -429,6 +436,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor (Completable.toString x); result := Some (x, !scope) in + let inJsxContext = ref false in let setResult x = setResultOpt (Some x) in let scopeValueDescription (vd : Parsetree.value_description) = scope := @@ -563,9 +571,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor (* Pipe chains get special treatment here, because when assigning values we want the return of the entire pipe chain as a function call, rather than as a pipe completion call. *) - match completePipeChain vb.pvb_expr with + match completePipeChain ~inJsxContext:!inJsxContext vb.pvb_expr with | Some (ctxPath, _) -> Some ctxPath - | None -> exprToContextPath vb.pvb_expr + | None -> exprToContextPath ~inJsxContext:!inJsxContext vb.pvb_expr in scopePattern ?contextPath vb.pvb_pat in @@ -597,7 +605,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor scope := !scope |> Scope.addModule ~name:md.pmd_name.txt ~loc:md.pmd_name.loc in - let inJsxContext = ref false in + (* Identifies expressions where we can do typed pattern or expr completion. *) let typedCompletionExpr (exp : Parsetree.expression) = let debugTypedCompletionExpr = false in @@ -614,7 +622,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor print_endline "[typedCompletionExpr] No cases - has cursor"; (* We can do exhaustive switch completion if this is an ident we can complete from. *) - match exprToContextPath expr with + match exprToContextPath ~inJsxContext:!inJsxContext expr with | None -> () | Some contextPath -> setResult (CexhaustiveSwitch {contextPath; exprLoc = exp.pexp_loc})) @@ -644,7 +652,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor }; ] ) -> ( (* A single case that's a pattern hole typically means `switch x { | }`. Complete as the pattern itself with nothing nested. *) - match exprToContextPath exp with + match exprToContextPath ~inJsxContext:!inJsxContext exp with | None -> () | Some ctxPath -> setResult @@ -661,7 +669,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor print_endline "[typedCompletionExpr] Has cases"; (* If there's more than one case, or the case isn't a pattern hole, figure out if we're completing another broken parser case (`switch x { | true => () | }` for example). *) - match exp |> exprToContextPath with + match exp |> exprToContextPath ~inJsxContext:!inJsxContext with | None -> if Debug.verbose () && debugTypedCompletionExpr then print_endline "[typedCompletionExpr] Has cases - no ctx path" @@ -802,7 +810,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ( pvb_pat |> CompletionPatterns.traversePattern ~patternPath:[] ~locHasCursor ~firstCharBeforeCursorNoWhite ~posBeforeCursor, - exprToContextPath pvb_expr ) + exprToContextPath ~inJsxContext:!inJsxContext pvb_expr ) with | Some (prefix, nested), Some ctxPath -> setResult @@ -1059,14 +1067,14 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor in (match findThisExprLoc with | Some loc when expr.pexp_loc = loc -> ( - match exprToContextPath expr with + match exprToContextPath ~inJsxContext:!inJsxContext expr with | None -> () | Some ctxPath -> setResult (Cpath ctxPath)) | _ -> ()); let setPipeResult ~(lhs : Parsetree.expression) ~id = - match completePipeChain lhs with + match completePipeChain ~inJsxContext:!inJsxContext lhs with | None -> ( - match exprToContextPath lhs with + match exprToContextPath ~inJsxContext:!inJsxContext lhs with | Some pipe -> setResult (Cpath @@ -1101,7 +1109,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor && Option.is_none findThisExprLoc -> if Debug.verbose () then print_endline "[completionFrontend] Checking each case"; - let ctxPath = exprToContextPath expr in + let ctxPath = exprToContextPath ~inJsxContext:!inJsxContext expr in let oldCtxPath = !currentCtxPath in cases |> List.iter (fun (case : Parsetree.case) -> @@ -1144,7 +1152,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ]; } when Res_parsetree_viewer.is_tagged_template_literal innerExpr -> - exprToContextPath innerExpr + exprToContextPath ~inJsxContext:!inJsxContext innerExpr |> Option.iter (fun cpath -> setResult (Cpath @@ -1154,6 +1162,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor fieldName = ""; posOfDot; exprLoc = expr.pexp_loc; + inJsx = !inJsxContext; })); setFound ()) (* @@ -1174,7 +1183,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor } when Res_parsetree_viewer.is_tagged_template_literal innerExpr && expr.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor -> - exprToContextPath innerExpr + exprToContextPath ~inJsxContext:!inJsxContext innerExpr |> Option.iter (fun cpath -> setResult (Cpath @@ -1184,6 +1193,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor fieldName; posOfDot; exprLoc = expr.pexp_loc; + inJsx = !inJsxContext; })); setFound ()) | _ -> ( @@ -1262,7 +1272,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor if fieldName.loc |> Loc.hasPos ~pos:posBeforeCursor then match fieldName.txt with | Lident name -> ( - match exprToContextPath e with + match exprToContextPath ~inJsxContext:!inJsxContext e with | Some contextPath -> let contextPath = Completable.CPField @@ -1271,6 +1281,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor fieldName = name; posOfDot; exprLoc = e.pexp_loc; + inJsx = !inJsxContext; } in setResult (Cpath contextPath) @@ -1294,12 +1305,13 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor else name); posOfDot; exprLoc = e.pexp_loc; + inJsx = !inJsxContext; } in setResult (Cpath contextPath) | Lapply _ -> () else if Loc.end_ e.pexp_loc = posBeforeCursor then - match exprToContextPath e with + match exprToContextPath ~inJsxContext:!inJsxContext e with | Some contextPath -> setResult (Cpath @@ -1309,6 +1321,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor fieldName = ""; posOfDot; exprLoc = e.pexp_loc; + inJsx = !inJsxContext; })) | None -> ()) | Pexp_apply {funct = {pexp_desc = Pexp_ident compName}; args} @@ -1386,7 +1399,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor if Debug.verbose () then print_endline "[expr_iter] Complete fn arguments (piped)"; let args = extractExpApplyArgs ~args in - let funCtxPath = exprToContextPath funExpr in + let funCtxPath = + exprToContextPath ~inJsxContext:!inJsxContext funExpr + in let argCompletable = match funCtxPath with | Some contextPath -> @@ -1437,7 +1452,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor (Loc.toString exp.pexp_loc)) |> String.concat ", "); - let funCtxPath = exprToContextPath funExpr in + let funCtxPath = + exprToContextPath ~inJsxContext:!inJsxContext funExpr + in let argCompletable = match funCtxPath with | Some contextPath -> @@ -1481,7 +1498,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor labelRange |> Range.hasPos ~pos:posBeforeCursor || (label = "" && posCursor = fst labelRange) then - match exprToContextPath lhs with + match exprToContextPath ~inJsxContext:!inJsxContext lhs with | Some contextPath -> setResult (Cpath (CPObj (contextPath, label))) | None -> ()) | Pexp_fun diff --git a/analysis/src/Packages.ml b/analysis/src/Packages.ml index 675093636d..7fe58117ab 100644 --- a/analysis/src/Packages.ml +++ b/analysis/src/Packages.ml @@ -156,6 +156,7 @@ let newBsPackage ~rootPath = | ["RescriptCore"] -> true | _ -> false) |> Option.is_some + || fst rescriptVersion >= 12 then { arrayModulePath = ["Array"]; diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 600c39a867..d13757df95 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -616,6 +616,7 @@ module Completable = struct fieldName: string; posOfDot: (int * int) option; exprLoc: Location.t; + inJsx: bool; (** Whether this pipe was found in a JSX context. *) } | CPObj of contextPath * string | CPAwait of contextPath diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index ffeca9ae40..4b2939f970 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1152,7 +1152,10 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ?posOfDot { completion with name = nameWithPipe; - sortText = Some (name |> String.split_on_char '.' |> List.rev |> List.hd); + sortText = + (match completion.sortText with + | Some _ -> completion.sortText + | None -> Some (name |> String.split_on_char '.' |> List.rev |> List.hd)); insertText = Some nameWithPipe; env; synthetic; diff --git a/tests/analysis_tests/tests/src/CompletionJsx.res b/tests/analysis_tests/tests/src/CompletionJsx.res index a525974966..a3735f41ab 100644 --- a/tests/analysis_tests/tests/src/CompletionJsx.res +++ b/tests/analysis_tests/tests/src/CompletionJsx.res @@ -89,3 +89,14 @@ module Info = { // // ^com + + +module Foo = { + @react.component + let make = () => { + let content = "" + React.null + //

{content.s}

+ // ^com + } +} \ No newline at end of file diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt index 2b8726fa6f..160e72a2ba 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt @@ -545,7 +545,9 @@ Path MultiPropComp.make }] Complete src/CompletionJsx.res 89:26 -posCursor:[89:26] posNoWhite:[89:24] Found expr:[89:4->89:27] +posCursor:[89:26] posNoWhite:[89:24] Found expr:[89:4->100:3] +Pexp_apply ...[93:11->93:12] (...[89:4->93:10], ...[94:2->100:3]) +posCursor:[89:26] posNoWhite:[89:24] Found expr:[89:4->93:10] JSX 89:8] _type[89:9->89:14]=...[89:16->89:24]> _children:89:26 Completable: Cjsx([Info], "", [_type]) Package opens Pervasives.JsxModules.place holder @@ -558,3 +560,193 @@ Path Info.make "documentation": null }] +Complete src/CompletionJsx.res 98:20 +posCursor:[98:20] posNoWhite:[98:19] Found expr:[95:13->100:3] +posCursor:[98:20] posNoWhite:[98:19] Found expr:[96:4->98:25] +posCursor:[98:20] posNoWhite:[98:19] Found expr:[97:4->98:25] +posCursor:[98:20] posNoWhite:[98:19] Found expr:[98:8->98:25] +JSX 98:9] > _children:98:9 +posCursor:[98:20] posNoWhite:[98:19] Found expr:[98:9->98:21] +posCursor:[98:20] posNoWhite:[98:19] Found expr:[98:11->98:21] +posCursor:[98:20] posNoWhite:[98:19] Found expr:[98:11->98:20] +Pexp_field [98:11->98:18] s:[98:19->98:20] +Completable: Cpath Value[content].s +Package opens Pervasives.JsxModules.place holder +ContextPath Value[content].s +ContextPath Value[content] +Path content +ContextPath Value[content]->s <> +ContextPath Value[content] +Path content +Path Js.String2.s +[{ + "label": "->React.string", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."}, + "sortText": "A", + "insertText": "->React.string", + "insertTextFormat": 2, + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.startsWith", + "kind": 12, + "tags": [], + "detail": "(t, t) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"}, + "sortText": "startsWith", + "insertText": "->Js.String2.startsWith", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.splitAtMost", + "kind": 12, + "tags": [], + "detail": "(t, t, ~limit: int) => array", + "documentation": {"kind": "markdown", "value": "\n`splitAtMost delimiter ~limit: n str` splits the given `str` at every occurrence of `delimiter` and returns an array of the first `n` resulting substrings. If `n` is negative or greater than the number of substrings, the array will contain all the substrings.\n\n```\nsplitAtMost \"ant/bee/cat/dog/elk\" \"/\" ~limit: 3 = [|\"ant\"; \"bee\"; \"cat\"|];;\nsplitAtMost \"ant/bee/cat/dog/elk\" \"/\" ~limit: 0 = [| |];;\nsplitAtMost \"ant/bee/cat/dog/elk\" \"/\" ~limit: 9 = [|\"ant\"; \"bee\"; \"cat\"; \"dog\"; \"elk\"|];;\n```\n"}, + "sortText": "splitAtMost", + "insertText": "->Js.String2.splitAtMost", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.substrAtMost", + "kind": 12, + "tags": [], + "detail": "(t, ~from: int, ~length: int) => t", + "documentation": {"kind": "markdown", "value": "\n`substrAtMost(str, ~from: pos, ~length: n)` returns the substring of `str` of\nlength `n` starting at position `pos`.\n- If `pos` is less than zero, the starting position is the length of `str - pos`.\n- If `pos` is greater than or equal to the length of `str`, returns the empty string.\n- If `n` is less than or equal to zero, returns the empty string.\n\nJavaScript’s `String.substr()` is a legacy function. When possible, use\n`substring()` instead.\n\nSee [`String.substr`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.substrAtMost(\"abcdefghij\", ~from=3, ~length=4) == \"defg\"\nJs.String2.substrAtMost(\"abcdefghij\", ~from=-3, ~length=4) == \"hij\"\nJs.String2.substrAtMost(\"abcdefghij\", ~from=12, ~length=2) == \"\"\n```\n"}, + "sortText": "substrAtMost", + "insertText": "->Js.String2.substrAtMost", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.sliceToEnd", + "kind": 12, + "tags": [], + "detail": "(t, ~from: int) => t", + "documentation": {"kind": "markdown", "value": "\n`sliceToEnd(str, from:n)` returns the substring of `str` starting at character\n`n` to the end of the string.\n- If `n` is negative, then it is evaluated as `length(str - n)`.\n- If `n` is greater than the length of `str`, then sliceToEnd returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.sliceToEnd(\"abcdefg\", ~from=4) == \"efg\"\nJs.String2.sliceToEnd(\"abcdefg\", ~from=-2) == \"fg\"\nJs.String2.sliceToEnd(\"abcdefg\", ~from=7) == \"\"\n```\n"}, + "sortText": "sliceToEnd", + "insertText": "->Js.String2.sliceToEnd", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.slice", + "kind": 12, + "tags": [], + "detail": "(t, ~from: int, ~to_: int) => t", + "documentation": {"kind": "markdown", "value": "\n`slice(str, from:n1, to_:n2)` returns the substring of `str` starting at\ncharacter `n1` up to but not including `n2`.\n- If either `n1` or `n2` is negative, then it is evaluated as `length(str - n1)` or `length(str - n2)`.\n- If `n2` is greater than the length of `str`, then it is treated as `length(str)`.\n- If `n1` is greater than `n2`, slice returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=5) == \"cde\"\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=9) == \"cdefg\"\nJs.String2.slice(\"abcdefg\", ~from=-4, ~to_=-2) == \"de\"\nJs.String2.slice(\"abcdefg\", ~from=5, ~to_=1) == \"\"\n```\n"}, + "sortText": "slice", + "insertText": "->Js.String2.slice", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.splitByRe", + "kind": 12, + "tags": [], + "detail": "(t, Js_re.t) => array>", + "documentation": {"kind": "markdown", "value": "\n`splitByRe(str, regex)` splits the given `str` at every occurrence of `regex`\nand returns an array of the resulting substrings.\n\nSee [`String.split`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.splitByRe(\"art; bed , cog ;dad\", /\\s*[,;]\\s*TODO/) == [\n Some(\"art\"),\n Some(\"bed\"),\n Some(\"cog\"),\n Some(\"dad\"),\n ]\n```\n"}, + "sortText": "splitByRe", + "insertText": "->Js.String2.splitByRe", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.startsWithFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"}, + "sortText": "startsWithFrom", + "insertText": "->Js.String2.startsWithFrom", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.split", + "kind": 12, + "tags": [], + "detail": "(t, t) => array", + "documentation": {"kind": "markdown", "value": "\n`split(str, delimiter)` splits the given `str` at every occurrence of\n`delimiter` and returns an array of the resulting substrings.\n\nSee [`String.split`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.split(\"2018-01-02\", \"-\") == [\"2018\", \"01\", \"02\"]\nJs.String2.split(\"a,b,,c\", \",\") == [\"a\", \"b\", \"\", \"c\"]\nJs.String2.split(\"good::bad as great::awful\", \"::\") == [\"good\", \"bad as great\", \"awful\"]\nJs.String2.split(\"has-no-delimiter\", \";\") == [\"has-no-delimiter\"]\n```\n"}, + "sortText": "split", + "insertText": "->Js.String2.split", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.splitByReAtMost", + "kind": 12, + "tags": [], + "detail": "(t, Js_re.t, ~limit: int) => array>", + "documentation": {"kind": "markdown", "value": "\n`splitByReAtMost(str, regex, ~limit:n)` splits the given `str` at every\noccurrence of `regex` and returns an array of the first `n` resulting\nsubstrings. If `n` is negative or greater than the number of substrings, the\narray will contain all the substrings.\n\nSee [`String.split`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.splitByReAtMost(\"one: two: three: four\", /\\s*:\\s*TODO/, ~limit=3) == [\n Some(\"one\"),\n Some(\"two\"),\n Some(\"three\"),\n ]\n\nJs.String2.splitByReAtMost(\"one: two: three: four\", /\\s*:\\s*TODO/, ~limit=0) == []\n\nJs.String2.splitByReAtMost(\"one: two: three: four\", /\\s*:\\s*TODO/, ~limit=8) == [\n Some(\"one\"),\n Some(\"two\"),\n Some(\"three\"),\n Some(\"four\"),\n ]\n```\n"}, + "sortText": "splitByReAtMost", + "insertText": "->Js.String2.splitByReAtMost", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.substring", + "kind": 12, + "tags": [], + "detail": "(t, ~from: int, ~to_: int) => t", + "documentation": {"kind": "markdown", "value": "\n`substring(str, ~from: start, ~to_: finish)` returns characters `start` up to\nbut not including finish from `str`.\n- If `start` is less than zero, it is treated as zero.\n- If `finish` is zero or negative, the empty string is returned.\n- If `start` is greater than `finish`, the `start` and `finish` points are swapped.\n\nSee [`String.substring`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.substring(\"playground\", ~from=3, ~to_=6) == \"ygr\"\nJs.String2.substring(\"playground\", ~from=6, ~to_=3) == \"ygr\"\nJs.String2.substring(\"playground\", ~from=4, ~to_=12) == \"ground\"\n```\n"}, + "sortText": "substring", + "insertText": "->Js.String2.substring", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.substr", + "kind": 12, + "tags": [], + "detail": "(t, ~from: int) => t", + "documentation": {"kind": "markdown", "value": "\n`substr(str, ~from:n)` returns the substring of `str` from position `n` to the\nend of the string.\n- If `n` is less than zero, the starting position is the length of `str - n`.\n- If `n` is greater than or equal to the length of `str`, returns the empty string.\n\nJavaScript’s `String.substr()` is a legacy function. When possible, use\n`substring()` instead.\n\nSee [`String.substr`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.substr(\"abcdefghij\", ~from=3) == \"defghij\"\nJs.String2.substr(\"abcdefghij\", ~from=-3) == \"hij\"\nJs.String2.substr(\"abcdefghij\", ~from=12) == \"\"\n```\n"}, + "sortText": "substr", + "insertText": "->Js.String2.substr", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.substringToEnd", + "kind": 12, + "tags": [], + "detail": "(t, ~from: int) => t", + "documentation": {"kind": "markdown", "value": "\n`substringToEnd(str, ~from: start)` returns the substring of `str` from\nposition `start` to the end.\n- If `start` is less than or equal to zero, the entire string is returned.\n- If `start` is greater than or equal to the length of `str`, the empty string is returned.\n\nSee [`String.substring`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.substringToEnd(\"playground\", ~from=4) == \"ground\"\nJs.String2.substringToEnd(\"playground\", ~from=-3) == \"playground\"\nJs.String2.substringToEnd(\"playground\", ~from=12) == \"\"\n```\n"}, + "sortText": "substringToEnd", + "insertText": "->Js.String2.substringToEnd", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }, { + "label": "->Js.String2.search", + "kind": 12, + "tags": [], + "detail": "(t, Js_re.t) => int", + "documentation": {"kind": "markdown", "value": "\n`search(str, regexp)` returns the starting position of the first match of\n`regexp` in the given `str`, or -1 if there is no match.\n\nSee [`String.search`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.search(\"testing 1 2 3\", /\\d+/) == 8\nJs.String2.search(\"no numbers\", /\\d+/) == -1\n```\n"}, + "sortText": "search", + "insertText": "->Js.String2.search", + "additionalTextEdits": [{ + "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "newText": "" + }] + }] + From 6bd6e8f71964c40418bd088167dbfd5253c71708 Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 13 Feb 2025 13:35:28 +0100 Subject: [PATCH 2/6] Simplify example --- .../tests/src/CompletionJsx.res | 11 +--- .../tests/src/expected/CompletionJsx.res.txt | 61 ++++++++----------- 2 files changed, 29 insertions(+), 43 deletions(-) diff --git a/tests/analysis_tests/tests/src/CompletionJsx.res b/tests/analysis_tests/tests/src/CompletionJsx.res index a3735f41ab..a67ab15926 100644 --- a/tests/analysis_tests/tests/src/CompletionJsx.res +++ b/tests/analysis_tests/tests/src/CompletionJsx.res @@ -91,12 +91,5 @@ module Info = { // ^com -module Foo = { - @react.component - let make = () => { - let content = "" - React.null - //

{content.s}

- // ^com - } -} \ No newline at end of file +// let _ =

{"".s}

+// ^com diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt index 160e72a2ba..b4de290007 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt @@ -545,9 +545,7 @@ Path MultiPropComp.make }] Complete src/CompletionJsx.res 89:26 -posCursor:[89:26] posNoWhite:[89:24] Found expr:[89:4->100:3] -Pexp_apply ...[93:11->93:12] (...[89:4->93:10], ...[94:2->100:3]) -posCursor:[89:26] posNoWhite:[89:24] Found expr:[89:4->93:10] +posCursor:[89:26] posNoWhite:[89:24] Found expr:[89:4->89:27] JSX 89:8] _type[89:9->89:14]=...[89:16->89:24]> _children:89:26 Completable: Cjsx([Info], "", [_type]) Package opens Pervasives.JsxModules.place holder @@ -560,24 +558,19 @@ Path Info.make "documentation": null }] -Complete src/CompletionJsx.res 98:20 -posCursor:[98:20] posNoWhite:[98:19] Found expr:[95:13->100:3] -posCursor:[98:20] posNoWhite:[98:19] Found expr:[96:4->98:25] -posCursor:[98:20] posNoWhite:[98:19] Found expr:[97:4->98:25] -posCursor:[98:20] posNoWhite:[98:19] Found expr:[98:8->98:25] -JSX 98:9] > _children:98:9 -posCursor:[98:20] posNoWhite:[98:19] Found expr:[98:9->98:21] -posCursor:[98:20] posNoWhite:[98:19] Found expr:[98:11->98:21] -posCursor:[98:20] posNoWhite:[98:19] Found expr:[98:11->98:20] -Pexp_field [98:11->98:18] s:[98:19->98:20] -Completable: Cpath Value[content].s +Complete src/CompletionJsx.res 93:19 +posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:12->93:24] +JSX 93:13] > _children:93:13 +posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:13->93:20] +posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:15->93:20] +posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:15->93:19] +Pexp_field [93:15->93:17] s:[93:18->93:19] +Completable: Cpath string.s Package opens Pervasives.JsxModules.place holder -ContextPath Value[content].s -ContextPath Value[content] -Path content -ContextPath Value[content]->s <> -ContextPath Value[content] -Path content +ContextPath string.s +ContextPath string +ContextPath string->s <> +ContextPath string Path Js.String2.s [{ "label": "->React.string", @@ -589,7 +582,7 @@ Path Js.String2.s "insertText": "->React.string", "insertTextFormat": 2, "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -601,7 +594,7 @@ Path Js.String2.s "sortText": "startsWith", "insertText": "->Js.String2.startsWith", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -613,7 +606,7 @@ Path Js.String2.s "sortText": "splitAtMost", "insertText": "->Js.String2.splitAtMost", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -625,7 +618,7 @@ Path Js.String2.s "sortText": "substrAtMost", "insertText": "->Js.String2.substrAtMost", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -637,7 +630,7 @@ Path Js.String2.s "sortText": "sliceToEnd", "insertText": "->Js.String2.sliceToEnd", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -649,7 +642,7 @@ Path Js.String2.s "sortText": "slice", "insertText": "->Js.String2.slice", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -661,7 +654,7 @@ Path Js.String2.s "sortText": "splitByRe", "insertText": "->Js.String2.splitByRe", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -673,7 +666,7 @@ Path Js.String2.s "sortText": "startsWithFrom", "insertText": "->Js.String2.startsWithFrom", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -685,7 +678,7 @@ Path Js.String2.s "sortText": "split", "insertText": "->Js.String2.split", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -697,7 +690,7 @@ Path Js.String2.s "sortText": "splitByReAtMost", "insertText": "->Js.String2.splitByReAtMost", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -709,7 +702,7 @@ Path Js.String2.s "sortText": "substring", "insertText": "->Js.String2.substring", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -721,7 +714,7 @@ Path Js.String2.s "sortText": "substr", "insertText": "->Js.String2.substr", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -733,7 +726,7 @@ Path Js.String2.s "sortText": "substringToEnd", "insertText": "->Js.String2.substringToEnd", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }, { @@ -745,7 +738,7 @@ Path Js.String2.s "sortText": "search", "insertText": "->Js.String2.search", "additionalTextEdits": [{ - "range": {"start": {"line": 98, "character": 18}, "end": {"line": 98, "character": 19}}, + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, "newText": "" }] }] From 0d5e69b1a83efb0716995a770719df946295574f Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 13 Feb 2025 13:56:30 +0100 Subject: [PATCH 3/6] Add completion for integers inside braces. --- analysis/src/CompletionFrontEnd.ml | 27 +++++ .../tests/src/CompletionJsx.res | 3 + .../tests/src/expected/CompletionJsx.res.txt | 100 ++++++++++++++++++ 3 files changed, 130 insertions(+) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index aea7a6141d..e1e474ecdc 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1196,6 +1196,33 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor inJsx = !inJsxContext; })); setFound ()) + (* A dot completion of an integer + Example: {42.} + *) + | Pexp_constant (Pconst_float (float, _)) + when String.ends_with ~suffix:"." float + && Utils.hasBraces expr.pexp_attributes -> + let length = String.length float - 1 in + let integerExpr = + { + expr with + pexp_desc = + Pexp_constant (Pconst_integer (String.sub float 0 length, None)); + } + in + exprToContextPath ~inJsxContext:!inJsxContext integerExpr + |> Option.iter (fun cpath -> + setResult + (Cpath + (CPField + { + contextPath = cpath; + fieldName = ""; + posOfDot; + exprLoc = expr.pexp_loc; + inJsx = !inJsxContext; + })); + setFound ()) | _ -> ( if expr.pexp_loc |> Loc.hasPos ~pos:posNoWhite && !result = None then ( setFound (); diff --git a/tests/analysis_tests/tests/src/CompletionJsx.res b/tests/analysis_tests/tests/src/CompletionJsx.res index a67ab15926..de1ffd8623 100644 --- a/tests/analysis_tests/tests/src/CompletionJsx.res +++ b/tests/analysis_tests/tests/src/CompletionJsx.res @@ -93,3 +93,6 @@ module Info = { // let _ =

{"".s}

// ^com + +// let _ = {42.} +// ^com \ No newline at end of file diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt index b4de290007..0d5fcfc9a0 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt @@ -743,3 +743,103 @@ Path Js.String2.s }] }] +Complete src/CompletionJsx.res 96:18 +posCursor:[96:18] posNoWhite:[96:17] Found expr:[96:12->96:23] +JSX 96:13] > _children:96:13 +posCursor:[96:18] posNoWhite:[96:17] Found expr:[96:13->96:19] +posCursor:[96:18] posNoWhite:[96:17] Found expr:[96:15->96:19] +posCursor:[96:18] posNoWhite:[96:17] Found expr:[96:15->96:18] +Completable: Cpath int."" +Package opens Pervasives.JsxModules.place holder +ContextPath int."" +ContextPath int +ContextPath int-> <> +ContextPath int +Path Belt.Int. +[{ + "label": "->React.int", + "kind": 12, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "Turns `int` into a JSX element so it can be used inside of JSX."}, + "sortText": "A", + "insertText": "->React.int", + "insertTextFormat": 2, + "additionalTextEdits": [{ + "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, + "newText": "" + }] + }, { + "label": "->Belt.Int.*", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\nMultiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nassertEqual(2 * 2, 4)\n```\n"}, + "sortText": "*", + "insertText": "->Belt.Int.*", + "additionalTextEdits": [{ + "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, + "newText": "" + }] + }, { + "label": "->Belt.Int./", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\nDivision of two `int` values. Same as the division from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nassertEqual(4 / 2, 2)\n```\n"}, + "sortText": "/", + "insertText": "->Belt.Int./", + "additionalTextEdits": [{ + "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, + "newText": "" + }] + }, { + "label": "->Belt.Int.toString", + "kind": 12, + "tags": [], + "detail": "int => string", + "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nBelt.Int.toString(1)->assertEqual(\"1\")\n```\n"}, + "sortText": "toString", + "insertText": "->Belt.Int.toString", + "additionalTextEdits": [{ + "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, + "newText": "" + }] + }, { + "label": "->Belt.Int.toFloat", + "kind": 12, + "tags": [], + "detail": "int => float", + "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `float`.\n\n## Examples\n\n```rescript\nBelt.Int.toFloat(1)->assertEqual(1.0)\n```\n"}, + "sortText": "toFloat", + "insertText": "->Belt.Int.toFloat", + "additionalTextEdits": [{ + "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, + "newText": "" + }] + }, { + "label": "->Belt.Int.-", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\nSubtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nassertEqual(2 - 1, 1)\n```\n"}, + "sortText": "-", + "insertText": "->Belt.Int.-", + "additionalTextEdits": [{ + "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, + "newText": "" + }] + }, { + "label": "->Belt.Int.+", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\nAddition of two `int` values. Same as the addition from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nassertEqual(2 + 2, 4)\n```\n"}, + "sortText": "+", + "insertText": "->Belt.Int.+", + "additionalTextEdits": [{ + "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, + "newText": "" + }] + }] + From 78314d4db95d6492709c38a01896ffabd2e2d846 Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 13 Feb 2025 13:57:32 +0100 Subject: [PATCH 4/6] Add changelog entry --- CHANGELOG.md | 1 + analysis/src/CompletionFrontEnd.ml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ffe324cae..3c57a48105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Fix completion for application with tagged template. https://github.com/rescript-lang/rescript/pull/7278 - Fix error message for arity in the presence of optional arguments. https://github.com/rescript-lang/rescript/pull/7284 - Fix issue in functors with more than one argument (which are curried): emit nested function always. https://github.com/rescript-lang/rescript/pull/7273 +- Fix dot completion issue with React primitives. https://github.com/rescript-lang/rescript/pull/7292 #### :house: Internal diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index e1e474ecdc..3de6081745 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1196,7 +1196,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor inJsx = !inJsxContext; })); setFound ()) - (* A dot completion of an integer + (* A dot completion of an integer inside braces. Useful for ->React.int completion. Example: {42.} *) | Pexp_constant (Pconst_float (float, _)) From 806e9758dafa0810e9da7b4c72a3c3844287e0cc Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 13 Feb 2025 16:29:15 +0100 Subject: [PATCH 5/6] Remove completion of integer in braces --- analysis/src/CompletionFrontEnd.ml | 27 ----- .../tests/src/CompletionJsx.res | 3 - .../tests/src/expected/CompletionJsx.res.txt | 100 ------------------ 3 files changed, 130 deletions(-) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 3de6081745..aea7a6141d 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1196,33 +1196,6 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor inJsx = !inJsxContext; })); setFound ()) - (* A dot completion of an integer inside braces. Useful for ->React.int completion. - Example: {42.} - *) - | Pexp_constant (Pconst_float (float, _)) - when String.ends_with ~suffix:"." float - && Utils.hasBraces expr.pexp_attributes -> - let length = String.length float - 1 in - let integerExpr = - { - expr with - pexp_desc = - Pexp_constant (Pconst_integer (String.sub float 0 length, None)); - } - in - exprToContextPath ~inJsxContext:!inJsxContext integerExpr - |> Option.iter (fun cpath -> - setResult - (Cpath - (CPField - { - contextPath = cpath; - fieldName = ""; - posOfDot; - exprLoc = expr.pexp_loc; - inJsx = !inJsxContext; - })); - setFound ()) | _ -> ( if expr.pexp_loc |> Loc.hasPos ~pos:posNoWhite && !result = None then ( setFound (); diff --git a/tests/analysis_tests/tests/src/CompletionJsx.res b/tests/analysis_tests/tests/src/CompletionJsx.res index de1ffd8623..a67ab15926 100644 --- a/tests/analysis_tests/tests/src/CompletionJsx.res +++ b/tests/analysis_tests/tests/src/CompletionJsx.res @@ -93,6 +93,3 @@ module Info = { // let _ =

{"".s}

// ^com - -// let _ = {42.} -// ^com \ No newline at end of file diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt index 0d5fcfc9a0..b4de290007 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt @@ -743,103 +743,3 @@ Path Js.String2.s }] }] -Complete src/CompletionJsx.res 96:18 -posCursor:[96:18] posNoWhite:[96:17] Found expr:[96:12->96:23] -JSX 96:13] > _children:96:13 -posCursor:[96:18] posNoWhite:[96:17] Found expr:[96:13->96:19] -posCursor:[96:18] posNoWhite:[96:17] Found expr:[96:15->96:19] -posCursor:[96:18] posNoWhite:[96:17] Found expr:[96:15->96:18] -Completable: Cpath int."" -Package opens Pervasives.JsxModules.place holder -ContextPath int."" -ContextPath int -ContextPath int-> <> -ContextPath int -Path Belt.Int. -[{ - "label": "->React.int", - "kind": 12, - "tags": [], - "detail": "int", - "documentation": {"kind": "markdown", "value": "Turns `int` into a JSX element so it can be used inside of JSX."}, - "sortText": "A", - "insertText": "->React.int", - "insertTextFormat": 2, - "additionalTextEdits": [{ - "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, - "newText": "" - }] - }, { - "label": "->Belt.Int.*", - "kind": 12, - "tags": [], - "detail": "(int, int) => int", - "documentation": {"kind": "markdown", "value": "\nMultiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nassertEqual(2 * 2, 4)\n```\n"}, - "sortText": "*", - "insertText": "->Belt.Int.*", - "additionalTextEdits": [{ - "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, - "newText": "" - }] - }, { - "label": "->Belt.Int./", - "kind": 12, - "tags": [], - "detail": "(int, int) => int", - "documentation": {"kind": "markdown", "value": "\nDivision of two `int` values. Same as the division from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nassertEqual(4 / 2, 2)\n```\n"}, - "sortText": "/", - "insertText": "->Belt.Int./", - "additionalTextEdits": [{ - "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, - "newText": "" - }] - }, { - "label": "->Belt.Int.toString", - "kind": 12, - "tags": [], - "detail": "int => string", - "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nBelt.Int.toString(1)->assertEqual(\"1\")\n```\n"}, - "sortText": "toString", - "insertText": "->Belt.Int.toString", - "additionalTextEdits": [{ - "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, - "newText": "" - }] - }, { - "label": "->Belt.Int.toFloat", - "kind": 12, - "tags": [], - "detail": "int => float", - "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `float`.\n\n## Examples\n\n```rescript\nBelt.Int.toFloat(1)->assertEqual(1.0)\n```\n"}, - "sortText": "toFloat", - "insertText": "->Belt.Int.toFloat", - "additionalTextEdits": [{ - "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, - "newText": "" - }] - }, { - "label": "->Belt.Int.-", - "kind": 12, - "tags": [], - "detail": "(int, int) => int", - "documentation": {"kind": "markdown", "value": "\nSubtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nassertEqual(2 - 1, 1)\n```\n"}, - "sortText": "-", - "insertText": "->Belt.Int.-", - "additionalTextEdits": [{ - "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, - "newText": "" - }] - }, { - "label": "->Belt.Int.+", - "kind": 12, - "tags": [], - "detail": "(int, int) => int", - "documentation": {"kind": "markdown", "value": "\nAddition of two `int` values. Same as the addition from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nassertEqual(2 + 2, 4)\n```\n"}, - "sortText": "+", - "insertText": "->Belt.Int.+", - "additionalTextEdits": [{ - "range": {"start": {"line": 96, "character": 17}, "end": {"line": 96, "character": 18}}, - "newText": "" - }] - }] - From cd00216126511b08d45d68c3228ab7348540a99d Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 13 Feb 2025 16:29:46 +0100 Subject: [PATCH 6/6] Fix typo --- analysis/src/SharedTypes.ml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index d13757df95..97a44550ce 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -616,7 +616,8 @@ module Completable = struct fieldName: string; posOfDot: (int * int) option; exprLoc: Location.t; - inJsx: bool; (** Whether this pipe was found in a JSX context. *) + inJsx: bool; + (** Whether this field access was found in a JSX context. *) } | CPObj of contextPath * string | CPAwait of contextPath