diff --git a/CHANGELOG.md b/CHANGELOG.md index 31525ffea..98d114866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Add support for completion in patterns. https://github.com/rescript-lang/rescript-vscode/pull/670 - Add support for pattern completion of unsaved tuples. https://github.com/rescript-lang/rescript-vscode/pull/679 - Add support for completion in typed expressions. https://github.com/rescript-lang/rescript-vscode/pull/682 +- Complete for `React.element` creator functions (`React.string` etc) when in JSX context. https://github.com/rescript-lang/rescript-vscode/pull/681 #### :nail_care: Polish diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index f59cf191b..bf9bf65f0 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1477,7 +1477,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env else None) | None -> []) | None -> []) - | CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc} -> ( + | CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc; inJsx} -> ( match cp |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env @@ -1584,7 +1584,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env | None -> None in match completionPath with - | Some completionPath -> + | Some completionPath -> ( let completionPathMinusOpens = completionPath |> removeRawOpens package.opens @@ -1599,14 +1599,45 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env |> getCompletionsForPath ~completionContext:Value ~exact:false ~package ~opens ~allFiles ~pos ~env ~scope in - completions - |> List.map (fun (completion : Completion.t) -> - { - completion with - name = completionName completion.name; - env - (* Restore original env for the completion after x->foo()... *); - }) + let completions = + completions + |> List.map (fun (completion : Completion.t) -> + { + completion with + name = completionName completion.name; + env + (* Restore original env for the completion after x->foo()... *); + }) + in + (* We add React element functions to the completion if we're in a JSX context *) + let forJsxCompletion = + if inJsx then + match getTypePath typ with + | Some (Path.Pident id) when Ident.name id = "int" -> Some "int" + | Some (Path.Pident id) when Ident.name id = "float" -> Some "float" + | Some (Path.Pident id) when Ident.name id = "string" -> + Some "string" + | Some (Path.Pident id) when Ident.name id = "array" -> Some "array" + | _ -> None + else None + in + match forJsxCompletion with + | Some builtinNameToComplete + when checkName builtinNameToComplete ~prefix:funNamePrefix + ~exact:false -> + [ + Completion.createWithSnippet + ~name:("React." ^ builtinNameToComplete) + ~kind:(Value typ) ~env ~sortText:"A" + ~docstring: + [ + "Turns `" ^ builtinNameToComplete + ^ "` into `React.element` so it can be used inside of JSX."; + ] + (); + ] + @ completions + | _ -> completions) | None -> []) | None -> []) | CTuple ctxPaths -> diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index c54bbc4df..d42f8f448 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -914,6 +914,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = !scope |> Scope.addModule ~name:md.pmd_name.txt ~loc:md.pmd_name.loc in let setLookingForPat ctxPath = lookingForPat := Some ctxPath in + let inJsxContext = ref false in let unsetLookingForPat () = lookingForPat := None in (* Identifies expressions where we can do typed pattern or expr completion. *) @@ -1035,6 +1036,13 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = if not !processed then Ast_iterator.default_iterator.structure_item iterator item in + let value_binding (iterator : Ast_iterator.iterator) + (value_binding : Parsetree.value_binding) = + let oldInJsxContext = !inJsxContext in + if Utils.isReactComponent value_binding then inJsxContext := true; + Ast_iterator.default_iterator.value_binding iterator value_binding; + inJsxContext := oldInJsxContext + in let signature (iterator : Ast_iterator.iterator) (signature : Parsetree.signature) = let oldScope = !scope in @@ -1111,6 +1119,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = Ast_iterator.default_iterator.attribute iterator (id, payload) in let expr (iterator : Ast_iterator.iterator) (expr : Parsetree.expression) = + let oldInJsxContext = !inJsxContext in let processed = ref false in let setFound () = found := true; @@ -1125,11 +1134,20 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = match exprToContextPath lhs with | Some pipe -> setResult - (Cpath (CPPipe {contextPath = pipe; id; lhsLoc = lhs.pexp_loc})); + (Cpath + (CPPipe + { + contextPath = pipe; + id; + lhsLoc = lhs.pexp_loc; + inJsx = !inJsxContext; + })); true | None -> false) | Some (pipe, lhsLoc) -> - setResult (Cpath (CPPipe {contextPath = pipe; id; lhsLoc})); + setResult + (Cpath + (CPPipe {contextPath = pipe; id; lhsLoc; inJsx = !inJsxContext})); true in typedCompletionExpr expr; @@ -1201,6 +1219,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = | None -> ()) | Pexp_apply ({pexp_desc = Pexp_ident compName}, args) when Res_parsetree_viewer.isJsxExpression expr -> + inJsxContext := true; let jsxProps = extractJsxProps ~compName ~args in let compNamePath = flattenLidCheckDot ~jsx:true compName in if debug then @@ -1359,7 +1378,8 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = scope := oldScope; processed := true | _ -> ()); - if not !processed then Ast_iterator.default_iterator.expr iterator expr + if not !processed then Ast_iterator.default_iterator.expr iterator expr; + inJsxContext := oldInJsxContext in let typ (iterator : Ast_iterator.iterator) (core_type : Parsetree.core_type) = if core_type.ptyp_loc |> Loc.hasPos ~pos:posNoWhite then ( @@ -1468,6 +1488,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = structure_item; typ; type_kind; + value_binding; } in diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 38f6a550a..a9962c62f 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -316,12 +316,13 @@ module Completion = struct insertTextFormat = None; } - let createWithSnippet ~name ?insertText ~kind ~env ?sortText () = + let createWithSnippet ~name ?insertText ~kind ~env ?sortText ?(docstring = []) + () = { name; env; deprecated = None; - docstring = []; + docstring; kind; sortText; insertText; @@ -546,6 +547,7 @@ module Completable = struct | CPPipe of { contextPath: contextPath; id: string; + inJsx: bool; (** Whether this pipe was found in a JSX context. *) lhsLoc: Location.t; (** The loc item for the left hand side of the pipe. *) } @@ -648,7 +650,10 @@ module Completable = struct completionContextToString completionContext ^ list sl | CPField (cp, s) -> contextPathToString cp ^ "." ^ str s | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]" - | CPPipe {contextPath; id} -> contextPathToString contextPath ^ "->" ^ id + | CPPipe {contextPath; id; inJsx} -> + contextPathToString contextPath + ^ "->" ^ id + ^ if inJsx then " <>" else "" | CTuple ctxPaths -> "CTuple(" ^ (ctxPaths |> List.map contextPathToString |> String.concat ", ") diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml index e51a301f1..d62334bc7 100644 --- a/analysis/src/Utils.ml +++ b/analysis/src/Utils.ml @@ -168,3 +168,8 @@ let rec unwrapIfOption (t : Types.type_expr) = | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> unwrapIfOption t1 | Tconstr (Path.Pident {name = "option"}, [unwrappedType], _) -> unwrappedType | _ -> t +let isReactComponent (vb : Parsetree.value_binding) = + vb.pvb_attributes + |> List.exists (function + | {Location.txt = "react.component"}, _payload -> true + | _ -> false) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index 198580cb9..dc8fa8cb9 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -212,13 +212,8 @@ module AddTypeAnnotation = struct match si.pstr_desc with | Pstr_value (_recFlag, bindings) -> let processBinding (vb : Parsetree.value_binding) = - let isReactComponent = - (* Can't add a type annotation to a react component, or the compiler crashes *) - vb.pvb_attributes - |> List.exists (function - | {Location.txt = "react.component"}, _payload -> true - | _ -> false) - in + (* Can't add a type annotation to a react component, or the compiler crashes *) + let isReactComponent = Utils.isReactComponent vb in if not isReactComponent then processPattern vb.pvb_pat; processFunction vb.pvb_expr in diff --git a/analysis/tests/src/CompletionJsx.res b/analysis/tests/src/CompletionJsx.res new file mode 100644 index 000000000..c08889d8b --- /dev/null +++ b/analysis/tests/src/CompletionJsx.res @@ -0,0 +1,33 @@ +let someString = "hello" +ignore(someString) + +// someString->st +// ^com + +module SomeComponent = { + @react.component + let make = (~someProp) => { + let someInt = 12 + let someArr = [React.null] + ignore(someInt) + ignore(someArr) + // someString->st + // ^com +
+ {React.string(someProp)} +
{React.null}
+ // {someString->st} + // ^com + // {"Some string"->st} + // ^com + // {"Some string"->Js.String2.trim->st} + // ^com + // {someInt->} + // ^com + // {12->} + // ^com + // {someArr->a} + // ^com +
+ } +} diff --git a/analysis/tests/src/expected/CompletionJsx.res.txt b/analysis/tests/src/expected/CompletionJsx.res.txt new file mode 100644 index 000000000..133aeaec9 --- /dev/null +++ b/analysis/tests/src/expected/CompletionJsx.res.txt @@ -0,0 +1,360 @@ +Complete src/CompletionJsx.res 3:17 +posCursor:[3:17] posNoWhite:[3:16] Found expr:[3:3->3:17] +Completable: Cpath Value[someString]->st +[{ + "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```res example\nJs.String2.startsWith(\"BuckleScript\", \"Buckle\") == true\nJs.String2.startsWith(\"BuckleScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Buckle\") == false\n```\n"} + }, { + "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```res example\nJs.String2.startsWithFrom(\"BuckleScript\", \"kle\", 3) == true\nJs.String2.startsWithFrom(\"BuckleScript\", \"\", 3) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Buckle\", 2) == false\n```\n"} + }] + +Complete src/CompletionJsx.res 13:21 +posCursor:[13:21] posNoWhite:[13:20] Found expr:[8:13->31:3] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[9:4->30:10] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[10:4->30:10] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[11:4->30:10] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[12:4->30:10] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[13:7->30:10] +posCursor:[13:21] posNoWhite:[13:20] Found expr:[13:7->13:21] +Completable: Cpath Value[someString]->st <> +[{ + "label": "React.string", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "Turns `string` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "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```res example\nJs.String2.startsWith(\"BuckleScript\", \"Buckle\") == true\nJs.String2.startsWith(\"BuckleScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Buckle\") == false\n```\n"} + }, { + "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```res example\nJs.String2.startsWithFrom(\"BuckleScript\", \"kle\", 3) == true\nJs.String2.startsWithFrom(\"BuckleScript\", \"\", 3) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Buckle\", 2) == false\n```\n"} + }] + +Complete src/CompletionJsx.res 18:24 +posCursor:[18:24] posNoWhite:[18:23] Found expr:[8:13->31:3] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[9:4->30:10] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[10:4->30:10] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[11:4->30:10] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[12:4->30:10] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[18:24] posNoWhite:[18:23] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[16:7->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[17:7->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->30:4] +Pexp_construct :::[18:10->30:4] [18:10->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->30:4] +posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->18:24] +Completable: Cpath Value[someString]->st <> +[{ + "label": "React.string", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "Turns `string` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "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```res example\nJs.String2.startsWith(\"BuckleScript\", \"Buckle\") == true\nJs.String2.startsWith(\"BuckleScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Buckle\") == false\n```\n"} + }, { + "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```res example\nJs.String2.startsWithFrom(\"BuckleScript\", \"kle\", 3) == true\nJs.String2.startsWithFrom(\"BuckleScript\", \"\", 3) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Buckle\", 2) == false\n```\n"} + }] + +Complete src/CompletionJsx.res 20:27 +posCursor:[20:27] posNoWhite:[20:26] Found expr:[8:13->31:3] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[9:4->30:10] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[10:4->30:10] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[11:4->30:10] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[12:4->30:10] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[20:27] posNoWhite:[20:26] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[16:7->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[17:7->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->30:4] +Pexp_construct :::[20:10->30:4] [20:10->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->30:4] +posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->20:27] +Completable: Cpath string->st <> +[{ + "label": "React.string", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "Turns `string` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "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```res example\nJs.String2.startsWith(\"BuckleScript\", \"Buckle\") == true\nJs.String2.startsWith(\"BuckleScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Buckle\") == false\n```\n"} + }, { + "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```res example\nJs.String2.startsWithFrom(\"BuckleScript\", \"kle\", 3) == true\nJs.String2.startsWithFrom(\"BuckleScript\", \"\", 3) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Buckle\", 2) == false\n```\n"} + }] + +Complete src/CompletionJsx.res 22:44 +posCursor:[22:44] posNoWhite:[22:43] Found expr:[8:13->31:3] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[9:4->30:10] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[10:4->30:10] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[11:4->30:10] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[12:4->30:10] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[22:44] posNoWhite:[22:43] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[16:7->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[17:7->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[22:10->30:4] +Pexp_construct :::[22:10->30:4] [22:10->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[22:10->30:4] +posCursor:[22:44] posNoWhite:[22:43] Found expr:[22:10->22:44] +Completable: Cpath Value[Js, String2, trim](Nolabel)->st <> +[{ + "label": "React.string", + "kind": 12, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "Turns `string` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "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```res example\nJs.String2.startsWith(\"BuckleScript\", \"Buckle\") == true\nJs.String2.startsWith(\"BuckleScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Buckle\") == false\n```\n"} + }, { + "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```res example\nJs.String2.startsWithFrom(\"BuckleScript\", \"kle\", 3) == true\nJs.String2.startsWithFrom(\"BuckleScript\", \"\", 3) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Buckle\", 2) == false\n```\n"} + }] + +Complete src/CompletionJsx.res 24:19 +posCursor:[24:19] posNoWhite:[24:18] Found expr:[8:13->31:3] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[9:4->30:10] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[10:4->30:10] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[11:4->30:10] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[12:4->30:10] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[24:19] posNoWhite:[24:18] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[16:7->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[17:7->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->30:4] +Pexp_construct :::[24:10->30:4] [24:10->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->30:4] +posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->0:-1] +Completable: Cpath Value[someInt]-> <> +[{ + "label": "React.int", + "kind": 12, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "Turns `int` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "label": "Belt.Int.fromString", + "kind": 12, + "tags": [], + "detail": "string => option", + "documentation": {"kind": "markdown", "value": "\n Converts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n ```res example\n Js.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n ```\n"} + }, { + "label": "Belt.Int.*", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Multiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 * 2 === 4) /* true */\n ```\n"} + }, { + "label": "Belt.Int./", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Division of two `int` values. Same as the division from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(4 / 2 === 2); /* true */\n ```\n"} + }, { + "label": "Belt.Int.toString", + "kind": 12, + "tags": [], + "detail": "int => string", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n ```res example\n Js.log(Belt.Int.toString(1) === \"1\") /* true */\n ```\n"} + }, { + "label": "Belt.Int.toFloat", + "kind": 12, + "tags": [], + "detail": "int => float", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `float`.\n\n ```res example\n Js.log(Belt.Int.toFloat(1) === 1.0) /* true */\n ```\n"} + }, { + "label": "Belt.Int.fromFloat", + "kind": 12, + "tags": [], + "detail": "float => int", + "documentation": {"kind": "markdown", "value": "\n Converts a given `float` to an `int`.\n\n ```res example\n Js.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.-", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Subtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 - 1 === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.+", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Addition of two `int` values. Same as the addition from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 + 2 === 4) /* true */\n ```\n"} + }] + +Complete src/CompletionJsx.res 26:14 +posCursor:[26:14] posNoWhite:[26:13] Found expr:[8:13->31:3] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[9:4->30:10] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[10:4->30:10] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[11:4->30:10] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[12:4->30:10] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[26:14] posNoWhite:[26:13] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[16:7->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[17:7->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->30:4] +Pexp_construct :::[26:10->30:4] [26:10->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->30:4] +posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->0:-1] +Completable: Cpath int-> <> +[{ + "label": "React.int", + "kind": 12, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "Turns `int` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "label": "Belt.Int.fromString", + "kind": 12, + "tags": [], + "detail": "string => option", + "documentation": {"kind": "markdown", "value": "\n Converts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n ```res example\n Js.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n ```\n"} + }, { + "label": "Belt.Int.*", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Multiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 * 2 === 4) /* true */\n ```\n"} + }, { + "label": "Belt.Int./", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Division of two `int` values. Same as the division from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(4 / 2 === 2); /* true */\n ```\n"} + }, { + "label": "Belt.Int.toString", + "kind": 12, + "tags": [], + "detail": "int => string", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n ```res example\n Js.log(Belt.Int.toString(1) === \"1\") /* true */\n ```\n"} + }, { + "label": "Belt.Int.toFloat", + "kind": 12, + "tags": [], + "detail": "int => float", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `float`.\n\n ```res example\n Js.log(Belt.Int.toFloat(1) === 1.0) /* true */\n ```\n"} + }, { + "label": "Belt.Int.fromFloat", + "kind": 12, + "tags": [], + "detail": "float => int", + "documentation": {"kind": "markdown", "value": "\n Converts a given `float` to an `int`.\n\n ```res example\n Js.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.-", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Subtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 - 1 === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.+", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Addition of two `int` values. Same as the addition from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 + 2 === 4) /* true */\n ```\n"} + }] + +Complete src/CompletionJsx.res 28:20 +posCursor:[28:20] posNoWhite:[28:19] Found expr:[8:13->31:3] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[9:4->30:10] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[10:4->30:10] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[11:4->30:10] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[12:4->30:10] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[15:5->30:10] +JSX 15:8] > _children:15:8 +posCursor:[28:20] posNoWhite:[28:19] Found expr:[15:8->30:4] +Pexp_construct :::[16:7->30:4] [16:7->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[16:7->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[17:7->30:4] +Pexp_construct :::[17:7->30:4] [17:7->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[17:7->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->30:4] +Pexp_construct :::[28:10->30:4] [28:10->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->30:4] +posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->28:20] +Completable: Cpath Value[someArr]->a <> +[{ + "label": "React.array", + "kind": 12, + "tags": [], + "detail": "array", + "documentation": {"kind": "markdown", "value": "Turns `array` into `React.element` so it can be used inside of JSX."}, + "sortText": "A", + "insertTextFormat": 2 + }, { + "label": "Js.Array2.append", + "kind": 12, + "tags": [1], + "detail": "(t<'a>, 'a) => t<'a>", + "documentation": {"kind": "markdown", "value": "Deprecated: `append` is not type-safe. Use `concat` instead.\n\n"} + }] +