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/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..97a44550ce 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 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..a67ab15926 100644 --- a/tests/analysis_tests/tests/src/CompletionJsx.res +++ b/tests/analysis_tests/tests/src/CompletionJsx.res @@ -89,3 +89,7 @@ module Info = { // // ^com + + +// 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 2b8726fa6f..b4de290007 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt @@ -558,3 +558,188 @@ Path Info.make "documentation": null }] +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 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": 93, "character": 17}, "end": {"line": 93, "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": 93, "character": 17}, "end": {"line": 93, "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": 93, "character": 17}, "end": {"line": 93, "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": 93, "character": 17}, "end": {"line": 93, "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": 93, "character": 17}, "end": {"line": 93, "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": 93, "character": 17}, "end": {"line": 93, "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\", /\\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": 93, "character": 17}, "end": {"line": 93, "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": 93, "character": 17}, "end": {"line": 93, "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": 93, "character": 17}, "end": {"line": 93, "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\", /\\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": 93, "character": 17}, "end": {"line": 93, "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": 93, "character": 17}, "end": {"line": 93, "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": 93, "character": 17}, "end": {"line": 93, "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": 93, "character": 17}, "end": {"line": 93, "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\", /\\d+/) == 8\nJs.String2.search(\"no numbers\", /\\d+/) == -1\n```\n"}, + "sortText": "search", + "insertText": "->Js.String2.search", + "additionalTextEdits": [{ + "range": {"start": {"line": 93, "character": 17}, "end": {"line": 93, "character": 18}}, + "newText": "" + }] + }] +