diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b23c91d8..3fa6610d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ #### :rocket: New Feature +- Port [7292](https://github.com/rescript-lang/rescript/pull/7292): Fix dot completion issue with React primitives. https://github.com/rescript-lang/rescript-vscode/pull/1074 + - Add support for "dot completion everywhere". In addition to record fields, dots will now complete for object fields, and pipe completions applicable to the type the dot is on. You can also configure where the editor draws extra pipe completions from via the `@editor.completeFrom` attribute. https://github.com/rescript-lang/rescript-vscode/pull/1054 #### :bug: Bug fix diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 7589e45f9..2c5218da9 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -990,7 +990,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 @@ -1024,7 +1024,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact CPApply (cp, [Asttypes.Nolabel]) | _ -> cp); id = fieldName; - inJsx = false; + inJsx; lhsLoc = exprLoc; } in diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index ec3b5566f..73f91ae09 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 ("|." | "|.u")} -> 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 @@ -261,7 +264,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) = (_, {pexp_desc = Pexp_apply (d, args); pexp_loc; pexp_attributes}); ] ) -> (* Transform away pipe with apply call *) - exprToContextPath + exprToContextPath ~inJsxContext { pexp_desc = Pexp_apply (d, (Nolabel, lhs) :: args); pexp_loc; @@ -272,7 +275,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) = [(_, lhs); (_, {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes})] ) -> (* Transform away pipe with identifier *) - exprToContextPath + exprToContextPath ~inJsxContext { pexp_desc = Pexp_apply @@ -282,26 +285,28 @@ let rec exprToContextPathInner (e : Parsetree.expression) = pexp_attributes; } | Pexp_apply (e1, args) -> ( - match exprToContextPath e1 with + match exprToContextPath ~inJsxContext e1 with | None -> None | Some contexPath -> Some (CPApply (contexPath, args |> List.map fst))) | 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: @@ -315,13 +320,15 @@ let completePipeChain (exp : Parsetree.expression) = | Pexp_apply ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}}, [_; (_, {pexp_desc = Pexp_apply (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 ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}}, [_; (_, {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 @@ -408,6 +415,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 := @@ -542,9 +550,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 @@ -576,7 +584,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 @@ -593,7 +601,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})) @@ -623,7 +631,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 @@ -640,7 +648,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" @@ -781,7 +789,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 @@ -1037,14 +1045,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 @@ -1079,7 +1087,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) -> @@ -1178,7 +1186,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 @@ -1187,6 +1195,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor fieldName = name; posOfDot; exprLoc = e.pexp_loc; + inJsx = !inJsxContext; } in setResult (Cpath contextPath) @@ -1210,12 +1219,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 @@ -1225,6 +1235,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor fieldName = ""; posOfDot; exprLoc = e.pexp_loc; + inJsx = !inJsxContext; })) | None -> ()) | Pexp_apply ({pexp_desc = Pexp_ident compName}, args) @@ -1295,7 +1306,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 -> @@ -1343,7 +1356,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 -> @@ -1387,7 +1402,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 (lbl, defaultExpOpt, pat, e) -> diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 59675f181..307352728 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -616,6 +616,8 @@ module Completable = struct fieldName: string; posOfDot: (int * int) option; exprLoc: Location.t; + inJsx: bool; + (** Whether this field access 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 5c33f0f7f..5967edebc 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1169,7 +1169,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/analysis/tests/src/CompletionJsx.res b/analysis/tests/src/CompletionJsx.res index a52597496..c71ee7e30 100644 --- a/analysis/tests/src/CompletionJsx.res +++ b/analysis/tests/src/CompletionJsx.res @@ -89,3 +89,6 @@ module Info = { // // ^com + +// let _ =

{"".s}

+// ^com diff --git a/analysis/tests/src/expected/CompletionJsx.res.txt b/analysis/tests/src/expected/CompletionJsx.res.txt index 31041216d..2a37cc79b 100644 --- a/analysis/tests/src/expected/CompletionJsx.res.txt +++ b/analysis/tests/src/expected/CompletionJsx.res.txt @@ -583,3 +583,189 @@ Path Info.make "documentation": null }] +Complete src/CompletionJsx.res 92:19 +posCursor:[92:19] posNoWhite:[92:18] Found expr:[92:12->92:24] +JSX 92:13] > _children:92:13 +posCursor:[92:19] posNoWhite:[92:18] Found expr:[92:13->92:20] +posCursor:[92:19] posNoWhite:[92:18] Found expr:[92:15->92:20] +posCursor:[92:19] posNoWhite:[92:18] Found expr:[92:15->92:19] +Pexp_field [92:15->92:17] s:[92:18->92:19] +Completable: Cpath string.s +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath string.s +ContextPath string +ContextPath string->s <> +ContextPath string +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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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\", %re(\"/\\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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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\", %re(\"/\\s*:\\s*TODO/\"), ~limit=3) == [\n Some(\"one\"),\n Some(\"two\"),\n Some(\"three\"),\n ]\n\nJs.String2.splitByReAtMost(\"one: two: three: four\", %re(\"/\\s*:\\s*TODO/\"), ~limit=0) == []\n\nJs.String2.splitByReAtMost(\"one: two: three: four\", %re(\"/\\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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "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\", %re(\"/\\d+/\")) == 8\nJs.String2.search(\"no numbers\", %re(\"/\\d+/\")) == -1\n```\n"}, + "sortText": "search", + "insertText": "->Js.String2.search", + "additionalTextEdits": [{ + "range": {"start": {"line": 92, "character": 17}, "end": {"line": 92, "character": 18}}, + "newText": "" + }] + }] +