From 0f3b8df66caf9c7aa66fa97c66f1b9fe0dcc39f7 Mon Sep 17 00:00:00 2001 From: nojaf Date: Mon, 3 Feb 2025 13:25:55 +0100 Subject: [PATCH 01/14] WIP tagged template completion --- analysis/src/CompletionBackEnd.ml | 140 +++++++++++++++++++++++++++-- analysis/src/CompletionFrontEnd.ml | 34 +++++++ analysis/src/SharedTypes.ml | 25 +++++- analysis/src/TypeUtils.ml | 5 +- 4 files changed, 192 insertions(+), 12 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 3becd1cecf..4ed5476435 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -600,8 +600,11 @@ let getComplementaryCompletionsForTypedValue ~opens ~allFiles ~scope ~env prefix in localCompletionsWithOpens @ fileModules -let getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope - ~completionContext ~env path = +let getCompletionsForPath ~(debug : bool) ~(opens : QueryEnv.t list) + ~(full : full) ~(pos : int * int) ~(exact : bool) + ~(scope : ScopeTypes.item list) + ~(completionContext : Completable.completionContext) ~(env : QueryEnv.t) + (path : filePath list) : Completion.t list = if debug then Printf.printf "Path %s\n" (path |> String.concat "."); let allFiles = allFilesInPackage full.package in match path with @@ -775,6 +778,9 @@ let completionsGetCompletionType ~full completions = let rec completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos completions = + Printf.printf "Enter completionsGetCompletionType2 with %d \n" + (List.length completions); + completions |> List.iter (fun c -> c |> Completion.toString |> print_endline); let firstNonSyntheticCompletion = List.find_opt (fun c -> not c.Completion.synthetic) completions in @@ -816,9 +822,8 @@ and completionsGetTypeEnv2 ~debug (completions : Completion.t list) ~full ~opens and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact ~scope ?(mode = Regular) contextPath = let envCompletionIsMadeFrom = env in - if debug then - Printf.printf "ContextPath %s\n" - (Completable.contextPathToString contextPath); + if debug then print_endline "Enter getCompletionsForContextPath:"; + Printf.printf "ContextPath %s\n" (Completable.contextPathToString contextPath); let package = full.package in match contextPath with | CPString -> @@ -972,6 +977,121 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact [Completion.create "dummy" ~env ~kind:(Completion.Value retType)] | _ -> []) | _ -> []) + (* sh`meh` *) + | CPField + { + contextPath = CPApply ((CPId {path; completionContext} as cp), _); + fieldName; + posOfDot; + exprLoc; + } -> ( + Format.printf "YAP '%s'\n" fieldName; + Location.print_loc Format.std_formatter exprLoc; + let completionsFromCtxPath = + getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope + ~completionContext ~env:envCompletionIsMadeFrom path + in + let mainTypeCompletionEnv = + completionsFromCtxPath + |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos + in + match mainTypeCompletionEnv with + | None -> [] + | Some (typ, env) -> ( + print_endline "FOOOOOO"; + let _, rt, typeArgsOpt = + TypeUtils.extractFunctionType2 ~env:envCompletionIsMadeFrom + ~package:full.package typ + in + Printtyp.raw_type_expr Format.std_formatter rt; + print_endline "__"; + let mainTypeId = TypeUtils.findRootTypeId ~full ~env rt in + let typePath = TypeUtils.pathFromTypeExpr rt in + match mainTypeId with + | None -> + if Debug.verbose () then + Printf.printf + "[pipe_completion] Could not find mainTypeId. Aborting pipe \ + completions.\n"; + [] + | Some mainTypeId -> + if Debug.verbose () then + Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; + let pipeCompletions = + (* We now need a completion path from where to look up the module for our dot completion type. + This is from where we pull all of the functions we want to complete for the pipe. + + A completion path here could be one of two things: + 1. A module path to the main module for the type we've found + 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` + + The below code will deliberately _not_ dig into type aliases for the main type when we're looking + for what _module_ to complete from. This is because you should be able to control where completions + come from even if your type is an alias. + *) + let completeAsBuiltin = + match typePath with + | Some t -> + TypeUtils.completionPathFromMaybeBuiltin t ~package:full.package + | None -> None + in + let completionPath = + match (completeAsBuiltin, typePath) with + | Some completionPathForBuiltin, _ -> + Some (false, completionPathForBuiltin) + | _, Some p -> ( + (* If this isn't a builtin, but we have a path, we try to resolve the + module path relative to the env we're completing from. This ensures that + what we get here is a module path we can find completions for regardless of + of the current scope for the position we're at.*) + match + TypeUtils.getModulePathRelativeToEnv ~debug + ~env:envCompletionIsMadeFrom ~envFromItem:env + (Utils.expandPath p) + with + | None -> Some (true, [env.file.moduleName]) + | Some p -> Some (false, p)) + | _ -> None + in + match completionPath with + | None -> [] + | Some (isFromCurrentModule, completionPath) -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens + ~pos ~scope ~debug ~prefix:"" ~env ~rawOpens ~full completionPath + |> fun cps -> + Format.printf "Before filtering (%d)" (List.length cps); + cps + |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic:false + ~targetTypeId:mainTypeId + |> List.filter (fun (c : Completion.t) -> + (* If we're completing from the current module then we need to care about scope. + This is automatically taken care of in other cases. *) + if isFromCurrentModule then + match c.kind with + | Value _ -> + scope + |> List.find_opt (fun (item : ScopeTypes.item) -> + match item with + | Value (scopeItemName, _, _, _) -> + scopeItemName = c.name + | _ -> false) + |> Option.is_some + | _ -> false + else true) + in + (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we + find and add those completions as well. *) + let extraCompletions = + TypeUtils.getExtraModulesToCompleteFromForType ~env ~full typ + |> List.map (fun completionPath -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom + ~opens ~pos ~scope ~debug ~prefix:"" ~env ~rawOpens ~full + completionPath) + |> List.flatten + |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full + ~targetTypeId:mainTypeId + in + pipeCompletions @ extraCompletions)) | CPField {contextPath = CPId {path; completionContext = Module}; fieldName} -> if Debug.verbose () then print_endline "[ctx_path]--> CPField: M.field"; @@ -980,6 +1100,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext:Field ~env ~scope | CPField {contextPath = cp; fieldName; posOfDot; exprLoc} -> ( + Completable.contextPathToString cp |> print_endline; if Debug.verbose () then print_endline "[dot_completion]--> Triggered"; let completionsFromCtxPath = cp @@ -1064,7 +1185,9 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact |> TypeUtils.resolveTypeForPipeCompletion ~env ~package:full.package ~full ~lhsLoc in + (* Here we search the root type, I'd expect that is not always the case? *) let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in + Printtyp.raw_type_expr Format.std_formatter typ; let typePath = TypeUtils.pathFromTypeExpr typ in match mainTypeId with | None -> @@ -1117,6 +1240,9 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | Some (isFromCurrentModule, completionPath) -> completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath + |> fun cps -> + Format.printf "Before filtering (%d)" (List.length cps); + cps |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic ~targetTypeId:mainTypeId |> List.filter (fun (c : Completion.t) -> @@ -1819,8 +1945,8 @@ let rec completeTypedValue ?(typeArgContext : typeArgContext option) ~rawOpens module StringSet = Set.Make (String) let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = - if debug then - Printf.printf "Completable: %s\n" (Completable.toString completable); + if debug then print_endline "Enter processCompletable:"; + Printf.printf "Completable: %s\n" (Completable.toString completable); let package = full.package in let rawOpens = Scope.getRawOpens scope in let opens = getOpens ~debug ~rawOpens ~package ~env in diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 4ca35778a8..c7cd3b733e 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1127,6 +1127,40 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor (* Case foo-> when the parser adds a ghost expression to the rhs so the apply expression does not include the cursor *) if setPipeResult ~lhs ~id:"" then setFound () + (* sh`echo "meh"`. *) + | Pexp_apply + { + funct = {pexp_desc = Pexp_ident {txt = Lident "."; loc = dotLoc}}; + args = [(_, ({pexp_desc = Pexp_apply _} as innerExpr)); _ghostThing]; + } + when dotLoc |> Loc.hasPos ~pos:posBeforeCursor -> + Printast.expression 4 Format.std_formatter expr; + print_endline "yow_yow"; + exprToContextPath innerExpr + |> Option.iter (fun cpath -> + setResult + (Cpath + (CPField + { + contextPath = cpath; + fieldName = ""; + posOfDot; + exprLoc = expr.pexp_loc; + })); + setFound ()) + (* TODO: further extend on all the ghost stuff, make this clear that this targetting tag templates *) + | Pexp_field (e, {txt = Longident.Lident "_"}) -> ( + print_endline "field yozora:_"; + Printast.expression 4 Format.std_formatter expr; + match exprToContextPath e with + | None -> () + | Some contextPath -> + print_endline (Completable.contextPathToString contextPath); + setFound (); + setResult + (Cpath + (CPField + {contextPath; fieldName = ""; posOfDot; exprLoc = e.pexp_loc}))) | _ -> ( if expr.pexp_loc |> Loc.hasPos ~pos:posNoWhite && !result = None then ( setFound (); diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 600c39a867..d7a4098c39 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -689,20 +689,20 @@ module Completable = struct | CPAwait ctxPath -> "await " ^ contextPathToString ctxPath | CPOption ctxPath -> "option<" ^ contextPathToString ctxPath ^ ">" | CPApply (cp, labels) -> - contextPathToString cp ^ "(" + "CPApply(" ^ contextPathToString cp ^ "(" ^ (labels |> List.map (function | Asttypes.Noloc.Nolabel -> "Nolabel" | Labelled s -> "~" ^ s | Optional s -> "?" ^ s) |> String.concat ", ") - ^ ")" + ^ "))" | CPArray (Some ctxPath) -> "array<" ^ contextPathToString ctxPath ^ ">" | CPArray None -> "array" | CPId {path; completionContext} -> - completionContextToString completionContext ^ list path + "CPID(" ^ completionContextToString completionContext ^ list path ^ ")" | CPField {contextPath = cp; fieldName = s} -> - contextPathToString cp ^ "." ^ str s + "CPField(" ^ contextPathToString cp ^ "." ^ str s ^ ")" | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]" | CPPipe {contextPath; id; inJsx} -> contextPathToString contextPath @@ -801,6 +801,19 @@ module Completion = struct | ExtractedType of completionType * [`Value | `Type] | FollowContextPath of Completable.contextPath * ScopeTypes.item list + let kind_to_string = function + | Module _ -> "Module" + | Value _ -> "Value" + | ObjLabel _ -> "ObjLabel" + | Label _ -> "Label" + | Type _ -> "Type" + | Constructor _ -> "Constructor" + | PolyvariantConstructor _ -> "PolyvariantConstructor" + | Field _ -> "Field" + | FileModule _ -> "FileModule" + | Snippet _ -> "Snippet" + | ExtractedType _ -> "ExtractedType" + | FollowContextPath _ -> "FollowContextPath" type t = { name: string; sortText: string option; @@ -819,6 +832,10 @@ module Completion = struct (** Whether this item is an made up, synthetic item or not. *) } + let toString (t : t) : string = + Format.sprintf "Completion: %s %s %b" t.name (kind_to_string t.kind) + t.synthetic + let create ?(synthetic = false) ?additionalTextEdits ?data ?typeArgContext ?(includesSnippets = false) ?insertText ~kind ~env ?sortText ?deprecated ?filterText ?detail ?(docstring = []) name = diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index ffeca9ae40..37643b398e 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -508,6 +508,7 @@ let rec digToRelevantTemplateNameType ~env ~package ?(suffix = "") let rec resolveTypeForPipeCompletion ~env ~package ~lhsLoc ~full (t : Types.type_expr) = + print_endline "Enter resolveTypeForPipeCompletion:"; (* If the type we're completing on is a type parameter, we won't be able to do completion unless we know what that type parameter is compiled as. This attempts to look up the compiled type for that type parameter by @@ -1167,7 +1168,7 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ?posOfDot of the project. Example: type x in module SomeModule in file SomeFile would get the globally unique id `SomeFile.SomeModule.x`.*) let rec findRootTypeId ~full ~env (t : Types.type_expr) = - let debug = false in + let debug = true in match t.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> findRootTypeId ~full ~env t1 | Tconstr (path, _, _) -> ( @@ -1208,8 +1209,10 @@ let filterPipeableFunctions ~env ~full ?synthetic ?targetTypeId ?posOfDot match targetTypeId with | None -> completions | Some targetTypeId -> + Format.printf "targetTypeId: %s\n" targetTypeId; completions |> List.filter_map (fun (completion : Completion.t) -> + print_endline (Completion.toString completion); let thisCompletionItemTypeId = match completion.kind with | Value t -> ( From 6b45ce042b4a83cf580b43a45c7d3022035ee688 Mon Sep 17 00:00:00 2001 From: nojaf Date: Mon, 3 Feb 2025 17:05:22 +0100 Subject: [PATCH 02/14] Ensure debug logging is behind flag --- analysis/src/CompletionBackEnd.ml | 43 ++++++++++++++++++------------ analysis/src/CompletionFrontEnd.ml | 10 ++++--- analysis/src/TypeUtils.ml | 7 ++--- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 4ed5476435..d6871711c1 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -778,9 +778,11 @@ let completionsGetCompletionType ~full completions = let rec completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos completions = - Printf.printf "Enter completionsGetCompletionType2 with %d \n" - (List.length completions); - completions |> List.iter (fun c -> c |> Completion.toString |> print_endline); + if Debug.verbose () then ( + Printf.printf "Enter completionsGetCompletionType2 with %d \n" + (List.length completions); + completions + |> List.iter (fun c -> c |> Completion.toString |> print_endline)); let firstNonSyntheticCompletion = List.find_opt (fun c -> not c.Completion.synthetic) completions in @@ -822,8 +824,10 @@ and completionsGetTypeEnv2 ~debug (completions : Completion.t list) ~full ~opens and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact ~scope ?(mode = Regular) contextPath = let envCompletionIsMadeFrom = env in - if debug then print_endline "Enter getCompletionsForContextPath:"; - Printf.printf "ContextPath %s\n" (Completable.contextPathToString contextPath); + if debug then ( + print_endline "Enter getCompletionsForContextPath:"; + Printf.printf "ContextPath %s\n" + (Completable.contextPathToString contextPath)); let package = full.package in match contextPath with | CPString -> @@ -982,11 +986,12 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact { contextPath = CPApply ((CPId {path; completionContext} as cp), _); fieldName; - posOfDot; + posOfDot = _; exprLoc; } -> ( - Format.printf "YAP '%s'\n" fieldName; - Location.print_loc Format.std_formatter exprLoc; + if Debug.verbose () then ( + Format.printf "YAP '%s'\n" fieldName; + Location.print_loc Format.std_formatter exprLoc); let completionsFromCtxPath = getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope ~completionContext ~env:envCompletionIsMadeFrom path @@ -998,13 +1003,14 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact match mainTypeCompletionEnv with | None -> [] | Some (typ, env) -> ( - print_endline "FOOOOOO"; - let _, rt, typeArgsOpt = + if Debug.verbose () then print_endline "FOOOOOO"; + let _, rt, _typeArgsOpt = TypeUtils.extractFunctionType2 ~env:envCompletionIsMadeFrom ~package:full.package typ in - Printtyp.raw_type_expr Format.std_formatter rt; - print_endline "__"; + if Debug.verbose () then ( + Printtyp.raw_type_expr Format.std_formatter rt; + print_endline "__"); let mainTypeId = TypeUtils.findRootTypeId ~full ~env rt in let typePath = TypeUtils.pathFromTypeExpr rt in match mainTypeId with @@ -1059,7 +1065,8 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix:"" ~env ~rawOpens ~full completionPath |> fun cps -> - Format.printf "Before filtering (%d)" (List.length cps); + if Debug.verbose () then + Format.printf "Before filtering (%d)" (List.length cps); cps |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic:false ~targetTypeId:mainTypeId @@ -1100,7 +1107,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext:Field ~env ~scope | CPField {contextPath = cp; fieldName; posOfDot; exprLoc} -> ( - Completable.contextPathToString cp |> print_endline; + if Debug.verbose () then Completable.contextPathToString cp |> print_endline; if Debug.verbose () then print_endline "[dot_completion]--> Triggered"; let completionsFromCtxPath = cp @@ -1187,7 +1194,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact in (* Here we search the root type, I'd expect that is not always the case? *) let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in - Printtyp.raw_type_expr Format.std_formatter typ; + if Debug.verbose () then Printtyp.raw_type_expr Format.std_formatter typ; let typePath = TypeUtils.pathFromTypeExpr typ in match mainTypeId with | None -> @@ -1241,7 +1248,8 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath |> fun cps -> - Format.printf "Before filtering (%d)" (List.length cps); + if Debug.verbose () then + Format.printf "Before filtering (%d)" (List.length cps); cps |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic ~targetTypeId:mainTypeId @@ -1946,7 +1954,8 @@ module StringSet = Set.Make (String) let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = if debug then print_endline "Enter processCompletable:"; - Printf.printf "Completable: %s\n" (Completable.toString completable); + if Debug.verbose () then + Printf.printf "Completable: %s\n" (Completable.toString completable); let package = full.package in let rawOpens = Scope.getRawOpens scope in let opens = getOpens ~debug ~rawOpens ~package ~env in diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index c7cd3b733e..8434bed586 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1134,8 +1134,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor args = [(_, ({pexp_desc = Pexp_apply _} as innerExpr)); _ghostThing]; } when dotLoc |> Loc.hasPos ~pos:posBeforeCursor -> - Printast.expression 4 Format.std_formatter expr; - print_endline "yow_yow"; + if Debug.verbose () then ( + Printast.expression 4 Format.std_formatter expr; + print_endline "yow_yow"); exprToContextPath innerExpr |> Option.iter (fun cpath -> setResult @@ -1150,8 +1151,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor setFound ()) (* TODO: further extend on all the ghost stuff, make this clear that this targetting tag templates *) | Pexp_field (e, {txt = Longident.Lident "_"}) -> ( - print_endline "field yozora:_"; - Printast.expression 4 Format.std_formatter expr; + if Debug.verbose () then ( + print_endline "field yozora:_"; + Printast.expression 4 Format.std_formatter expr); match exprToContextPath e with | None -> () | Some contextPath -> diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 37643b398e..8837671031 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1168,7 +1168,7 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ?posOfDot of the project. Example: type x in module SomeModule in file SomeFile would get the globally unique id `SomeFile.SomeModule.x`.*) let rec findRootTypeId ~full ~env (t : Types.type_expr) = - let debug = true in + let debug = Debug.verbose () in match t.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> findRootTypeId ~full ~env t1 | Tconstr (path, _, _) -> ( @@ -1209,10 +1209,11 @@ let filterPipeableFunctions ~env ~full ?synthetic ?targetTypeId ?posOfDot match targetTypeId with | None -> completions | Some targetTypeId -> - Format.printf "targetTypeId: %s\n" targetTypeId; + if Debug.verbose () then Format.printf "targetTypeId: %s\n" targetTypeId; completions |> List.filter_map (fun (completion : Completion.t) -> - print_endline (Completion.toString completion); + if Debug.verbose () then + print_endline (Completion.toString completion); let thisCompletionItemTypeId = match completion.kind with | Value t -> ( From d42587e51e7e22024cc3c3eb27f1d4a22ace010c Mon Sep 17 00:00:00 2001 From: nojaf Date: Tue, 4 Feb 2025 08:44:29 +0100 Subject: [PATCH 03/14] Include completions of alias --- analysis/src/CompletionBackEnd.ml | 260 +++++++++++------- analysis/src/TypeUtils.ml | 9 +- .../tests/src/CompletionTaggedTemplate.res | 24 ++ .../expected/CompletionTaggedTemplate.res.txt | 97 +++++++ 4 files changed, 289 insertions(+), 101 deletions(-) create mode 100644 tests/analysis_tests/tests/src/CompletionTaggedTemplate.res create mode 100644 tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index d6871711c1..6a417a1643 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -560,9 +560,12 @@ let findLocalCompletionsForModules ~(localTables : LocalTables.t) ~env ~prefix let findLocalCompletionsWithOpens ~pos ~(env : QueryEnv.t) ~prefix ~exact ~opens ~scope ~(completionContext : Completable.completionContext) = (* TODO: handle arbitrary interleaving of opens and local bindings correctly *) - Log.log - ("findLocalCompletionsWithOpens uri:" ^ Uri.toString env.file.uri ^ " pos:" - ^ Pos.toString pos); + if Debug.verbose () then + Format.printf + "findLocalCompletionsWithOpens uri: %s pos: %s completionContext: %s\n" + (Uri.toString env.file.uri) + (Pos.toString pos) + (Completable.completionContextToString completionContext); let localTables = LocalTables.create () in match completionContext with | Value | ValueOrField -> @@ -605,7 +608,9 @@ let getCompletionsForPath ~(debug : bool) ~(opens : QueryEnv.t list) ~(scope : ScopeTypes.item list) ~(completionContext : Completable.completionContext) ~(env : QueryEnv.t) (path : filePath list) : Completion.t list = - if debug then Printf.printf "Path %s\n" (path |> String.concat "."); + if debug then + Printf.printf "Enter getCompletionsForPath:\nPath %s\n" + (path |> String.concat "."); let allFiles = allFilesInPackage full.package in match path with | [] -> [] @@ -627,6 +632,11 @@ let getCompletionsForPath ~(debug : bool) ~(opens : QueryEnv.t list) (Completion.create name ~env ~kind:(Completion.FileModule name)) else None) in + if Debug.verbose () then + Format.printf "localCompletionsWithOpens (%d) fileModules (%d)\n" + (List.length localCompletionsWithOpens) + (List.length fileModules); + localCompletionsWithOpens @ fileModules | moduleName :: path -> ( Log.log ("Path " ^ pathToString path); @@ -940,6 +950,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact result | CPApply (cp, labels) -> ( if Debug.verbose () then print_endline "[ctx_path]--> CPApply"; + match cp |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env @@ -974,6 +985,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | [], [(Nolabel | Labelled _ | Optional _)] -> (* should not happen, but just ignore extra arguments *) [] in + match TypeUtils.extractFunctionType ~env ~package typ with | args, tRet when args <> [] -> let args = processApply args labels in @@ -988,7 +1000,8 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact fieldName; posOfDot = _; exprLoc; - } -> ( + } + when false -> ( if Debug.verbose () then ( Format.printf "YAP '%s'\n" fieldName; Location.print_loc Format.std_formatter exprLoc); @@ -1129,6 +1142,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact DotCompletionUtils.fieldCompletionsForDotCompletion typ ~env ~package ~prefix:fieldName ?posOfDot ~exact in + (* typ comes from a hashtable and might already be a translation from *) (* Get additional completions acting as if this field completion was actually a pipe completion. *) let cpAsPipeCompletion = Completable.CPPipe @@ -1149,11 +1163,18 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact cpAsPipeCompletion |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env:envCompletionIsMadeFrom ~exact ~scope + |> fun cs -> + if Debug.verbose () then + Format.printf "Completions? %d\n" (List.length cs); + cs |> List.filter_map (fun c -> + if Debug.verbose () then print_endline (Completion.toString c); TypeUtils.transformCompletionToPipeCompletion ~synthetic:true ~env ?posOfDot c) in - fieldCompletions @ pipeCompletions) + let evenMoreCompletions = [] in + + fieldCompletions @ pipeCompletions @ evenMoreCompletions) | CPObj (cp, label) -> ( (* TODO: Also needs to support ExtractedType *) if Debug.verbose () then print_endline "[ctx_path]--> CPObj"; @@ -1186,109 +1207,54 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact if Debug.verbose () then print_endline "[CPPipe]--> Could not resolve type env"; [] - | Some (typ, env) -> ( + | Some (typ, env) -> + if Debug.verbose () then print_endline "Resolved type in CPPipe"; let env, typ = typ |> TypeUtils.resolveTypeForPipeCompletion ~env ~package:full.package ~full ~lhsLoc in - (* Here we search the root type, I'd expect that is not always the case? *) let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in - if Debug.verbose () then Printtyp.raw_type_expr Format.std_formatter typ; - let typePath = TypeUtils.pathFromTypeExpr typ in - match mainTypeId with - | None -> - if Debug.verbose () then - Printf.printf - "[pipe_completion] Could not find mainTypeId. Aborting pipe \ - completions.\n"; - [] - | Some mainTypeId -> - if Debug.verbose () then - Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; - let pipeCompletions = - (* We now need a completion path from where to look up the module for our dot completion type. - This is from where we pull all of the functions we want to complete for the pipe. - - A completion path here could be one of two things: - 1. A module path to the main module for the type we've found - 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` - - The below code will deliberately _not_ dig into type aliases for the main type when we're looking - for what _module_ to complete from. This is because you should be able to control where completions - come from even if your type is an alias. - *) - let completeAsBuiltin = - match typePath with - | Some t -> - TypeUtils.completionPathFromMaybeBuiltin t ~package:full.package - | None -> None + (* Here we search the root type, I'd expect that is not always the case? *) + let pipeCompletionsAndExtraCompletions = + nojaf_extracted ~debug ~full ~opens ~rawOpens ~prefix ~scope ~pos + ~synthetic ~env ~envCompletionIsMadeFrom ~mainTypeId ~typ + in + (* Add JSX completion items if we're in a JSX context. *) + let jsxCompletions = + match mainTypeId with + | Some mainTypeId when inJsx -> + PipeCompletionUtils.addJsxCompletionItems ~env ~mainTypeId ~prefix + ~full ~rawOpens typ + | _ -> [] + in + let evenMoreCompletions = + match cp with + | CPApply (CPId {path; completionContext; loc}, _) -> ( + let completionsFromCtxPath = + getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope + ~completionContext ~env:envCompletionIsMadeFrom path in - let completionPath = - match (completeAsBuiltin, typePath) with - | Some completionPathForBuiltin, _ -> - Some (false, completionPathForBuiltin) - | _, Some p -> ( - (* If this isn't a builtin, but we have a path, we try to resolve the - module path relative to the env we're completing from. This ensures that - what we get here is a module path we can find completions for regardless of - of the current scope for the position we're at.*) - match - TypeUtils.getModulePathRelativeToEnv ~debug - ~env:envCompletionIsMadeFrom ~envFromItem:env - (Utils.expandPath p) - with - | None -> Some (true, [env.file.moduleName]) - | Some p -> Some (false, p)) - | _ -> None + let mainTypeCompletionEnv = + completionsFromCtxPath + |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos in - match completionPath with + match mainTypeCompletionEnv with | None -> [] - | Some (isFromCurrentModule, completionPath) -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens - ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath - |> fun cps -> - if Debug.verbose () then - Format.printf "Before filtering (%d)" (List.length cps); - cps - |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic - ~targetTypeId:mainTypeId - |> List.filter (fun (c : Completion.t) -> - (* If we're completing from the current module then we need to care about scope. - This is automatically taken care of in other cases. *) - if isFromCurrentModule then - match c.kind with - | Value _ -> - scope - |> List.find_opt (fun (item : ScopeTypes.item) -> - match item with - | Value (scopeItemName, _, _, _) -> - scopeItemName = c.name - | _ -> false) - |> Option.is_some - | _ -> false - else true) - in - (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we - find and add those completions as well. *) - let extraCompletions = - TypeUtils.getExtraModulesToCompleteFromForType ~env ~full typ - |> List.map (fun completionPath -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom - ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full - completionPath) - |> List.flatten - |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full - ~targetTypeId:mainTypeId - in - (* Add JSX completion items if we're in a JSX context. *) - let jsxCompletions = - if inJsx then - PipeCompletionUtils.addJsxCompletionItems ~env ~mainTypeId ~prefix - ~full ~rawOpens typ - else [] - in - jsxCompletions @ pipeCompletions @ extraCompletions)) + | Some (typ, env) -> ( + match TypeUtils.extracReturnTypeButDontDoAnyAliasLookup typ with + | retType -> + if Debug.verbose () then ( + print_endline "hoi\n"; + Printtyp.type_expr Format.std_formatter retType; + print_endline "\nendhoi"); + let mainTypeId = TypeUtils.findRootTypeId ~full ~env retType in + nojaf_extracted ~debug ~full ~opens ~rawOpens ~prefix ~scope ~pos + ~synthetic ~env ~envCompletionIsMadeFrom ~mainTypeId + ~typ:retType)) + | _ -> [] + in + jsxCompletions @ pipeCompletionsAndExtraCompletions @ evenMoreCompletions) | CTuple ctxPaths -> if Debug.verbose () then print_endline "[ctx_path]--> CTuple"; (* Turn a list of context paths into a list of type expressions. *) @@ -1452,6 +1418,100 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | None -> [] | Some typExpr -> [Completion.create "dummy" ~env ~kind:(Value typExpr)]) +and nojaf_extracted ~(debug : bool) ~(full : full) ~(opens : QueryEnv.t list) + ~(rawOpens : string list list) ~(prefix : string) + ~(scope : ScopeTypes.item list) ~(pos : int * int) ~(synthetic : bool) + ~(env : QueryEnv.t) ~(envCompletionIsMadeFrom : QueryEnv.t) + ~(mainTypeId : file option) ~(typ : Types.type_expr) : Completion.t list = + if Debug.verbose () then ( + print_endline "Enter nojaf_extracted"; + Printtyp.raw_type_expr Format.std_formatter typ); + let typePath = TypeUtils.pathFromTypeExpr typ in + match mainTypeId with + | None -> + if Debug.verbose () then + Printf.printf + "[pipe_completion] Could not find mainTypeId. Aborting pipe completions.\n"; + [] + | Some mainTypeId -> + if Debug.verbose () then + Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; + let pipeCompletions = + (* We now need a completion path from where to look up the module for our dot completion type. + This is from where we pull all of the functions we want to complete for the pipe. + + A completion path here could be one of two things: + 1. A module path to the main module for the type we've found + 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` + + The below code will deliberately _not_ dig into type aliases for the main type when we're looking + for what _module_ to complete from. This is because you should be able to control where completions + come from even if your type is an alias. + *) + let completeAsBuiltin = + match typePath with + | Some t -> + TypeUtils.completionPathFromMaybeBuiltin t ~package:full.package + | None -> None + in + let completionPath = + match (completeAsBuiltin, typePath) with + | Some completionPathForBuiltin, _ -> + Some (false, completionPathForBuiltin) + | _, Some p -> ( + (* If this isn't a builtin, but we have a path, we try to resolve the + module path relative to the env we're completing from. This ensures that + what we get here is a module path we can find completions for regardless of + of the current scope for the position we're at.*) + match + TypeUtils.getModulePathRelativeToEnv ~debug + ~env:envCompletionIsMadeFrom ~envFromItem:env (Utils.expandPath p) + with + | None -> Some (true, [env.file.moduleName]) + | Some p -> Some (false, p)) + | _ -> None + in + match completionPath with + | None -> [] + | Some (isFromCurrentModule, completionPath) -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens + ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath + |> fun cps -> + if Debug.verbose () then + Format.printf "Before filtering (%d)" (List.length cps); + cps + |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic + ~targetTypeId:mainTypeId + |> List.filter (fun (c : Completion.t) -> + (* If we're completing from the current module then we need to care about scope. + This is automatically taken care of in other cases. *) + if isFromCurrentModule then + match c.kind with + | Value _ -> + scope + |> List.find_opt (fun (item : ScopeTypes.item) -> + match item with + | Value (scopeItemName, _, _, _) -> + scopeItemName = c.name + | _ -> false) + |> Option.is_some + | _ -> false + else true) + in + (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we + find and add those completions as well. *) + let extraCompletions = + TypeUtils.getExtraModulesToCompleteFromForType ~env ~full typ + |> List.map (fun completionPath -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom + ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full + completionPath) + |> List.flatten + |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full + ~targetTypeId:mainTypeId + in + pipeCompletions @ extraCompletions + let getOpens ~debug ~rawOpens ~package ~env = if debug && rawOpens <> [] then Printf.printf "%s\n" diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 8837671031..54f8ed7490 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -262,6 +262,13 @@ let extractFunctionType ~env ~package typ = in loop ~env [] typ +let rec extracReturnTypeButDontDoAnyAliasLookup (typ : Types.type_expr) : + Types.type_expr = + match typ.desc with + | Types.Tarrow (_, _t1, t2, _, _) -> + extracReturnTypeButDontDoAnyAliasLookup t2 + | _ -> typ + let extractFunctionTypeWithEnv ~env ~package typ = let rec loop ~env acc (t : Types.type_expr) = match t.desc with @@ -508,7 +515,7 @@ let rec digToRelevantTemplateNameType ~env ~package ?(suffix = "") let rec resolveTypeForPipeCompletion ~env ~package ~lhsLoc ~full (t : Types.type_expr) = - print_endline "Enter resolveTypeForPipeCompletion:"; + if Debug.verbose () then print_endline "Enter resolveTypeForPipeCompletion:"; (* If the type we're completing on is a type parameter, we won't be able to do completion unless we know what that type parameter is compiled as. This attempts to look up the compiled type for that type parameter by diff --git a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res new file mode 100644 index 0000000000..668c6685c2 --- /dev/null +++ b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res @@ -0,0 +1,24 @@ +module X = { + type t = int + + let minus_two = (t:t) => t - 2 + let minus_three = (t:t) => t -3 +} + +@module("meh") @taggedTemplate +external meh: (array, array) => X.t = "default" + +// let x = meh`foo`. +// ^com + +/* + +dune exec -- rescript-editor-analysis debug-dump verbose test /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res > /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt + +dune exec -- rescript-editor-analysis completion \ + /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res \ + 10 17 \ + /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res \ + > /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt + + */ \ No newline at end of file diff --git a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt new file mode 100644 index 0000000000..df1b454f22 --- /dev/null +++ b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt @@ -0,0 +1,97 @@ +[{ + "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": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "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": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "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": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "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": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "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": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "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": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "newText": "" + }] + }, { + "label": "->X.minus_two", + "kind": 12, + "tags": [], + "detail": "t => int", + "documentation": null, + "sortText": "minus_two", + "insertText": "->X.minus_two", + "additionalTextEdits": [{ + "range": {"start": {"line": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "newText": "" + }] + }, { + "label": "->X.minus_three", + "kind": 12, + "tags": [], + "detail": "t => int", + "documentation": null, + "sortText": "minus_three", + "insertText": "->X.minus_three", + "additionalTextEdits": [{ + "range": {"start": {"line": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "newText": "" + }] + }] From 7b013cf99cc5db4ffcd381b1420151cfcb604338 Mon Sep 17 00:00:00 2001 From: nojaf Date: Tue, 4 Feb 2025 12:36:20 +0100 Subject: [PATCH 04/14] Turn off evenMoreCompletions --- analysis/src/CompletionBackEnd.ml | 6 ++---- analysis/src/CompletionFrontEnd.ml | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 6a417a1643..f63fe7a6c5 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1172,9 +1172,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact TypeUtils.transformCompletionToPipeCompletion ~synthetic:true ~env ?posOfDot c) in - let evenMoreCompletions = [] in - - fieldCompletions @ pipeCompletions @ evenMoreCompletions) + fieldCompletions @ pipeCompletions) | CPObj (cp, label) -> ( (* TODO: Also needs to support ExtractedType *) if Debug.verbose () then print_endline "[ctx_path]--> CPObj"; @@ -1230,7 +1228,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact in let evenMoreCompletions = match cp with - | CPApply (CPId {path; completionContext; loc}, _) -> ( + | CPApply (CPId {path; completionContext; loc = _}, _) when false -> ( let completionsFromCtxPath = getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope ~completionContext ~env:envCompletionIsMadeFrom path diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 8434bed586..b962e41dd9 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1150,7 +1150,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor })); setFound ()) (* TODO: further extend on all the ghost stuff, make this clear that this targetting tag templates *) - | Pexp_field (e, {txt = Longident.Lident "_"}) -> ( + | Pexp_field (e, {txt = Longident.Lident "_"}) when false -> ( if Debug.verbose () then ( print_endline "field yozora:_"; Printast.expression 4 Format.std_formatter expr); From f42e7d7ff9057790e7299419d442b75c4b334101 Mon Sep 17 00:00:00 2001 From: nojaf Date: Wed, 5 Feb 2025 09:31:39 +0100 Subject: [PATCH 05/14] Add support for tagged template + dot in FrontEnd. --- analysis/src/CompletionBackEnd.ml | 387 +++++------------- analysis/src/CompletionFrontEnd.ml | 57 ++- .../tests/src/CompletionTaggedTemplate.res | 12 +- 3 files changed, 136 insertions(+), 320 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index f63fe7a6c5..3becd1cecf 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -560,12 +560,9 @@ let findLocalCompletionsForModules ~(localTables : LocalTables.t) ~env ~prefix let findLocalCompletionsWithOpens ~pos ~(env : QueryEnv.t) ~prefix ~exact ~opens ~scope ~(completionContext : Completable.completionContext) = (* TODO: handle arbitrary interleaving of opens and local bindings correctly *) - if Debug.verbose () then - Format.printf - "findLocalCompletionsWithOpens uri: %s pos: %s completionContext: %s\n" - (Uri.toString env.file.uri) - (Pos.toString pos) - (Completable.completionContextToString completionContext); + Log.log + ("findLocalCompletionsWithOpens uri:" ^ Uri.toString env.file.uri ^ " pos:" + ^ Pos.toString pos); let localTables = LocalTables.create () in match completionContext with | Value | ValueOrField -> @@ -603,14 +600,9 @@ let getComplementaryCompletionsForTypedValue ~opens ~allFiles ~scope ~env prefix in localCompletionsWithOpens @ fileModules -let getCompletionsForPath ~(debug : bool) ~(opens : QueryEnv.t list) - ~(full : full) ~(pos : int * int) ~(exact : bool) - ~(scope : ScopeTypes.item list) - ~(completionContext : Completable.completionContext) ~(env : QueryEnv.t) - (path : filePath list) : Completion.t list = - if debug then - Printf.printf "Enter getCompletionsForPath:\nPath %s\n" - (path |> String.concat "."); +let getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope + ~completionContext ~env path = + if debug then Printf.printf "Path %s\n" (path |> String.concat "."); let allFiles = allFilesInPackage full.package in match path with | [] -> [] @@ -632,11 +624,6 @@ let getCompletionsForPath ~(debug : bool) ~(opens : QueryEnv.t list) (Completion.create name ~env ~kind:(Completion.FileModule name)) else None) in - if Debug.verbose () then - Format.printf "localCompletionsWithOpens (%d) fileModules (%d)\n" - (List.length localCompletionsWithOpens) - (List.length fileModules); - localCompletionsWithOpens @ fileModules | moduleName :: path -> ( Log.log ("Path " ^ pathToString path); @@ -788,11 +775,6 @@ let completionsGetCompletionType ~full completions = let rec completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos completions = - if Debug.verbose () then ( - Printf.printf "Enter completionsGetCompletionType2 with %d \n" - (List.length completions); - completions - |> List.iter (fun c -> c |> Completion.toString |> print_endline)); let firstNonSyntheticCompletion = List.find_opt (fun c -> not c.Completion.synthetic) completions in @@ -834,10 +816,9 @@ and completionsGetTypeEnv2 ~debug (completions : Completion.t list) ~full ~opens and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact ~scope ?(mode = Regular) contextPath = let envCompletionIsMadeFrom = env in - if debug then ( - print_endline "Enter getCompletionsForContextPath:"; + if debug then Printf.printf "ContextPath %s\n" - (Completable.contextPathToString contextPath)); + (Completable.contextPathToString contextPath); let package = full.package in match contextPath with | CPString -> @@ -950,7 +931,6 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact result | CPApply (cp, labels) -> ( if Debug.verbose () then print_endline "[ctx_path]--> CPApply"; - match cp |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env @@ -985,7 +965,6 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | [], [(Nolabel | Labelled _ | Optional _)] -> (* should not happen, but just ignore extra arguments *) [] in - match TypeUtils.extractFunctionType ~env ~package typ with | args, tRet when args <> [] -> let args = processApply args labels in @@ -993,125 +972,6 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact [Completion.create "dummy" ~env ~kind:(Completion.Value retType)] | _ -> []) | _ -> []) - (* sh`meh` *) - | CPField - { - contextPath = CPApply ((CPId {path; completionContext} as cp), _); - fieldName; - posOfDot = _; - exprLoc; - } - when false -> ( - if Debug.verbose () then ( - Format.printf "YAP '%s'\n" fieldName; - Location.print_loc Format.std_formatter exprLoc); - let completionsFromCtxPath = - getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope - ~completionContext ~env:envCompletionIsMadeFrom path - in - let mainTypeCompletionEnv = - completionsFromCtxPath - |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos - in - match mainTypeCompletionEnv with - | None -> [] - | Some (typ, env) -> ( - if Debug.verbose () then print_endline "FOOOOOO"; - let _, rt, _typeArgsOpt = - TypeUtils.extractFunctionType2 ~env:envCompletionIsMadeFrom - ~package:full.package typ - in - if Debug.verbose () then ( - Printtyp.raw_type_expr Format.std_formatter rt; - print_endline "__"); - let mainTypeId = TypeUtils.findRootTypeId ~full ~env rt in - let typePath = TypeUtils.pathFromTypeExpr rt in - match mainTypeId with - | None -> - if Debug.verbose () then - Printf.printf - "[pipe_completion] Could not find mainTypeId. Aborting pipe \ - completions.\n"; - [] - | Some mainTypeId -> - if Debug.verbose () then - Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; - let pipeCompletions = - (* We now need a completion path from where to look up the module for our dot completion type. - This is from where we pull all of the functions we want to complete for the pipe. - - A completion path here could be one of two things: - 1. A module path to the main module for the type we've found - 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` - - The below code will deliberately _not_ dig into type aliases for the main type when we're looking - for what _module_ to complete from. This is because you should be able to control where completions - come from even if your type is an alias. - *) - let completeAsBuiltin = - match typePath with - | Some t -> - TypeUtils.completionPathFromMaybeBuiltin t ~package:full.package - | None -> None - in - let completionPath = - match (completeAsBuiltin, typePath) with - | Some completionPathForBuiltin, _ -> - Some (false, completionPathForBuiltin) - | _, Some p -> ( - (* If this isn't a builtin, but we have a path, we try to resolve the - module path relative to the env we're completing from. This ensures that - what we get here is a module path we can find completions for regardless of - of the current scope for the position we're at.*) - match - TypeUtils.getModulePathRelativeToEnv ~debug - ~env:envCompletionIsMadeFrom ~envFromItem:env - (Utils.expandPath p) - with - | None -> Some (true, [env.file.moduleName]) - | Some p -> Some (false, p)) - | _ -> None - in - match completionPath with - | None -> [] - | Some (isFromCurrentModule, completionPath) -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens - ~pos ~scope ~debug ~prefix:"" ~env ~rawOpens ~full completionPath - |> fun cps -> - if Debug.verbose () then - Format.printf "Before filtering (%d)" (List.length cps); - cps - |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic:false - ~targetTypeId:mainTypeId - |> List.filter (fun (c : Completion.t) -> - (* If we're completing from the current module then we need to care about scope. - This is automatically taken care of in other cases. *) - if isFromCurrentModule then - match c.kind with - | Value _ -> - scope - |> List.find_opt (fun (item : ScopeTypes.item) -> - match item with - | Value (scopeItemName, _, _, _) -> - scopeItemName = c.name - | _ -> false) - |> Option.is_some - | _ -> false - else true) - in - (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we - find and add those completions as well. *) - let extraCompletions = - TypeUtils.getExtraModulesToCompleteFromForType ~env ~full typ - |> List.map (fun completionPath -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom - ~opens ~pos ~scope ~debug ~prefix:"" ~env ~rawOpens ~full - completionPath) - |> List.flatten - |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full - ~targetTypeId:mainTypeId - in - pipeCompletions @ extraCompletions)) | CPField {contextPath = CPId {path; completionContext = Module}; fieldName} -> if Debug.verbose () then print_endline "[ctx_path]--> CPField: M.field"; @@ -1120,7 +980,6 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext:Field ~env ~scope | CPField {contextPath = cp; fieldName; posOfDot; exprLoc} -> ( - if Debug.verbose () then Completable.contextPathToString cp |> print_endline; if Debug.verbose () then print_endline "[dot_completion]--> Triggered"; let completionsFromCtxPath = cp @@ -1142,7 +1001,6 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact DotCompletionUtils.fieldCompletionsForDotCompletion typ ~env ~package ~prefix:fieldName ?posOfDot ~exact in - (* typ comes from a hashtable and might already be a translation from *) (* Get additional completions acting as if this field completion was actually a pipe completion. *) let cpAsPipeCompletion = Completable.CPPipe @@ -1163,12 +1021,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact cpAsPipeCompletion |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env:envCompletionIsMadeFrom ~exact ~scope - |> fun cs -> - if Debug.verbose () then - Format.printf "Completions? %d\n" (List.length cs); - cs |> List.filter_map (fun c -> - if Debug.verbose () then print_endline (Completion.toString c); TypeUtils.transformCompletionToPipeCompletion ~synthetic:true ~env ?posOfDot c) in @@ -1205,54 +1058,103 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact if Debug.verbose () then print_endline "[CPPipe]--> Could not resolve type env"; [] - | Some (typ, env) -> - if Debug.verbose () then print_endline "Resolved type in CPPipe"; + | Some (typ, env) -> ( let env, typ = typ |> TypeUtils.resolveTypeForPipeCompletion ~env ~package:full.package ~full ~lhsLoc in let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in - (* Here we search the root type, I'd expect that is not always the case? *) - let pipeCompletionsAndExtraCompletions = - nojaf_extracted ~debug ~full ~opens ~rawOpens ~prefix ~scope ~pos - ~synthetic ~env ~envCompletionIsMadeFrom ~mainTypeId ~typ - in - (* Add JSX completion items if we're in a JSX context. *) - let jsxCompletions = - match mainTypeId with - | Some mainTypeId when inJsx -> - PipeCompletionUtils.addJsxCompletionItems ~env ~mainTypeId ~prefix - ~full ~rawOpens typ - | _ -> [] - in - let evenMoreCompletions = - match cp with - | CPApply (CPId {path; completionContext; loc = _}, _) when false -> ( - let completionsFromCtxPath = - getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope - ~completionContext ~env:envCompletionIsMadeFrom path + let typePath = TypeUtils.pathFromTypeExpr typ in + match mainTypeId with + | None -> + if Debug.verbose () then + Printf.printf + "[pipe_completion] Could not find mainTypeId. Aborting pipe \ + completions.\n"; + [] + | Some mainTypeId -> + if Debug.verbose () then + Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; + let pipeCompletions = + (* We now need a completion path from where to look up the module for our dot completion type. + This is from where we pull all of the functions we want to complete for the pipe. + + A completion path here could be one of two things: + 1. A module path to the main module for the type we've found + 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` + + The below code will deliberately _not_ dig into type aliases for the main type when we're looking + for what _module_ to complete from. This is because you should be able to control where completions + come from even if your type is an alias. + *) + let completeAsBuiltin = + match typePath with + | Some t -> + TypeUtils.completionPathFromMaybeBuiltin t ~package:full.package + | None -> None in - let mainTypeCompletionEnv = - completionsFromCtxPath - |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos + let completionPath = + match (completeAsBuiltin, typePath) with + | Some completionPathForBuiltin, _ -> + Some (false, completionPathForBuiltin) + | _, Some p -> ( + (* If this isn't a builtin, but we have a path, we try to resolve the + module path relative to the env we're completing from. This ensures that + what we get here is a module path we can find completions for regardless of + of the current scope for the position we're at.*) + match + TypeUtils.getModulePathRelativeToEnv ~debug + ~env:envCompletionIsMadeFrom ~envFromItem:env + (Utils.expandPath p) + with + | None -> Some (true, [env.file.moduleName]) + | Some p -> Some (false, p)) + | _ -> None in - match mainTypeCompletionEnv with + match completionPath with | None -> [] - | Some (typ, env) -> ( - match TypeUtils.extracReturnTypeButDontDoAnyAliasLookup typ with - | retType -> - if Debug.verbose () then ( - print_endline "hoi\n"; - Printtyp.type_expr Format.std_formatter retType; - print_endline "\nendhoi"); - let mainTypeId = TypeUtils.findRootTypeId ~full ~env retType in - nojaf_extracted ~debug ~full ~opens ~rawOpens ~prefix ~scope ~pos - ~synthetic ~env ~envCompletionIsMadeFrom ~mainTypeId - ~typ:retType)) - | _ -> [] - in - jsxCompletions @ pipeCompletionsAndExtraCompletions @ evenMoreCompletions) + | Some (isFromCurrentModule, completionPath) -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens + ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath + |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic + ~targetTypeId:mainTypeId + |> List.filter (fun (c : Completion.t) -> + (* If we're completing from the current module then we need to care about scope. + This is automatically taken care of in other cases. *) + if isFromCurrentModule then + match c.kind with + | Value _ -> + scope + |> List.find_opt (fun (item : ScopeTypes.item) -> + match item with + | Value (scopeItemName, _, _, _) -> + scopeItemName = c.name + | _ -> false) + |> Option.is_some + | _ -> false + else true) + in + (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we + find and add those completions as well. *) + let extraCompletions = + TypeUtils.getExtraModulesToCompleteFromForType ~env ~full typ + |> List.map (fun completionPath -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom + ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full + completionPath) + |> List.flatten + |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full + ~targetTypeId:mainTypeId + in + (* Add JSX completion items if we're in a JSX context. *) + let jsxCompletions = + if inJsx then + PipeCompletionUtils.addJsxCompletionItems ~env ~mainTypeId ~prefix + ~full ~rawOpens typ + else [] + in + jsxCompletions @ pipeCompletions @ extraCompletions)) | CTuple ctxPaths -> if Debug.verbose () then print_endline "[ctx_path]--> CTuple"; (* Turn a list of context paths into a list of type expressions. *) @@ -1416,100 +1318,6 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | None -> [] | Some typExpr -> [Completion.create "dummy" ~env ~kind:(Value typExpr)]) -and nojaf_extracted ~(debug : bool) ~(full : full) ~(opens : QueryEnv.t list) - ~(rawOpens : string list list) ~(prefix : string) - ~(scope : ScopeTypes.item list) ~(pos : int * int) ~(synthetic : bool) - ~(env : QueryEnv.t) ~(envCompletionIsMadeFrom : QueryEnv.t) - ~(mainTypeId : file option) ~(typ : Types.type_expr) : Completion.t list = - if Debug.verbose () then ( - print_endline "Enter nojaf_extracted"; - Printtyp.raw_type_expr Format.std_formatter typ); - let typePath = TypeUtils.pathFromTypeExpr typ in - match mainTypeId with - | None -> - if Debug.verbose () then - Printf.printf - "[pipe_completion] Could not find mainTypeId. Aborting pipe completions.\n"; - [] - | Some mainTypeId -> - if Debug.verbose () then - Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; - let pipeCompletions = - (* We now need a completion path from where to look up the module for our dot completion type. - This is from where we pull all of the functions we want to complete for the pipe. - - A completion path here could be one of two things: - 1. A module path to the main module for the type we've found - 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` - - The below code will deliberately _not_ dig into type aliases for the main type when we're looking - for what _module_ to complete from. This is because you should be able to control where completions - come from even if your type is an alias. - *) - let completeAsBuiltin = - match typePath with - | Some t -> - TypeUtils.completionPathFromMaybeBuiltin t ~package:full.package - | None -> None - in - let completionPath = - match (completeAsBuiltin, typePath) with - | Some completionPathForBuiltin, _ -> - Some (false, completionPathForBuiltin) - | _, Some p -> ( - (* If this isn't a builtin, but we have a path, we try to resolve the - module path relative to the env we're completing from. This ensures that - what we get here is a module path we can find completions for regardless of - of the current scope for the position we're at.*) - match - TypeUtils.getModulePathRelativeToEnv ~debug - ~env:envCompletionIsMadeFrom ~envFromItem:env (Utils.expandPath p) - with - | None -> Some (true, [env.file.moduleName]) - | Some p -> Some (false, p)) - | _ -> None - in - match completionPath with - | None -> [] - | Some (isFromCurrentModule, completionPath) -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens - ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath - |> fun cps -> - if Debug.verbose () then - Format.printf "Before filtering (%d)" (List.length cps); - cps - |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic - ~targetTypeId:mainTypeId - |> List.filter (fun (c : Completion.t) -> - (* If we're completing from the current module then we need to care about scope. - This is automatically taken care of in other cases. *) - if isFromCurrentModule then - match c.kind with - | Value _ -> - scope - |> List.find_opt (fun (item : ScopeTypes.item) -> - match item with - | Value (scopeItemName, _, _, _) -> - scopeItemName = c.name - | _ -> false) - |> Option.is_some - | _ -> false - else true) - in - (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we - find and add those completions as well. *) - let extraCompletions = - TypeUtils.getExtraModulesToCompleteFromForType ~env ~full typ - |> List.map (fun completionPath -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom - ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full - completionPath) - |> List.flatten - |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full - ~targetTypeId:mainTypeId - in - pipeCompletions @ extraCompletions - let getOpens ~debug ~rawOpens ~package ~env = if debug && rawOpens <> [] then Printf.printf "%s\n" @@ -2011,8 +1819,7 @@ let rec completeTypedValue ?(typeArgContext : typeArgContext option) ~rawOpens module StringSet = Set.Make (String) let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = - if debug then print_endline "Enter processCompletable:"; - if Debug.verbose () then + if debug then Printf.printf "Completable: %s\n" (Completable.toString completable); let package = full.package in let rawOpens = Scope.getRawOpens scope in diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index b962e41dd9..9a0c7b5606 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1116,53 +1116,66 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor resetCurrentCtxPath oldCtxPath | Pexp_apply { - funct = {pexp_desc = Pexp_ident {txt = Lident "->"; loc = opLoc}}; + funct = {pexp_desc = Pexp_ident {txt = Lident "->"; loc = _}}; args = [ (_, lhs); (_, {pexp_desc = Pexp_extension _; pexp_loc = {loc_ghost = true}}); ]; } - when opLoc |> Loc.hasPos ~pos:posBeforeCursor -> + when expr.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor -> (* Case foo-> when the parser adds a ghost expression to the rhs so the apply expression does not include the cursor *) if setPipeResult ~lhs ~id:"" then setFound () - (* sh`echo "meh"`. *) + (* + A dot completion for a tagged templated application. + Example: + sh`echo "meh"`. + or + sh`bar`.len + *) | Pexp_apply { - funct = {pexp_desc = Pexp_ident {txt = Lident "."; loc = dotLoc}}; - args = [(_, ({pexp_desc = Pexp_apply _} as innerExpr)); _ghostThing]; + funct = {pexp_desc = Pexp_ident {txt = Lident "."; loc = _}}; + args = + [(_, ({pexp_desc = Pexp_apply _} as innerExpr)); (_, fieldExpr)]; } - when dotLoc |> Loc.hasPos ~pos:posBeforeCursor -> + when Res_parsetree_viewer.is_tagged_template_literal innerExpr + && expr.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor + || CompletionExpressions.isExprHole fieldExpr -> if Debug.verbose () then ( + let print_loc (loc : Location.t) : string = + Format.sprintf "(%d,%d:%d,%d)" loc.loc_start.pos_lnum + loc.loc_start.pos_cnum loc.loc_end.pos_lnum loc.loc_end.pos_cnum + in Printast.expression 4 Format.std_formatter expr; - print_endline "yow_yow"); + Format.printf "posBeforeCursor: %s, expr.pexp_loc: %s" + (Pos.toString posBeforeCursor) + (print_loc expr.pexp_loc); + print_endline "\nyow_yow"); exprToContextPath innerExpr |> Option.iter (fun cpath -> + (* Determine the field name if present *) + let fieldName = + match fieldExpr.pexp_desc with + | Pexp_ident {txt = Lident fieldName} + when Res_parsetree_viewer.has_tagged_template_literal_attr + innerExpr.pexp_attributes -> + fieldName + (* This is likely to be an exprhole *) + | _ -> "" + in + setResult (Cpath (CPField { contextPath = cpath; - fieldName = ""; + fieldName; posOfDot; exprLoc = expr.pexp_loc; })); setFound ()) - (* TODO: further extend on all the ghost stuff, make this clear that this targetting tag templates *) - | Pexp_field (e, {txt = Longident.Lident "_"}) when false -> ( - if Debug.verbose () then ( - print_endline "field yozora:_"; - Printast.expression 4 Format.std_formatter expr); - match exprToContextPath e with - | None -> () - | Some contextPath -> - print_endline (Completable.contextPathToString contextPath); - setFound (); - setResult - (Cpath - (CPField - {contextPath; fieldName = ""; posOfDot; exprLoc = e.pexp_loc}))) | _ -> ( if expr.pexp_loc |> Loc.hasPos ~pos:posNoWhite && !result = None then ( setFound (); diff --git a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res index 668c6685c2..93285f3ae1 100644 --- a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res +++ b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res @@ -1,16 +1,12 @@ -module X = { - type t = int - - let minus_two = (t:t) => t - 2 - let minus_three = (t:t) => t -3 -} - @module("meh") @taggedTemplate -external meh: (array, array) => X.t = "default" +external meh: (array, array) => string = "default" // let x = meh`foo`. // ^com +// let y = meh`bar`.len +// ^com + /* dune exec -- rescript-editor-analysis debug-dump verbose test /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res > /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt From cb04a890549fb7a524d3e87ca3820222e7299a85 Mon Sep 17 00:00:00 2001 From: nojaf Date: Wed, 5 Feb 2025 09:37:16 +0100 Subject: [PATCH 06/14] Revert change to pipe --- analysis/src/CompletionFrontEnd.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 9a0c7b5606..0977c2347b 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1159,8 +1159,8 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor let fieldName = match fieldExpr.pexp_desc with | Pexp_ident {txt = Lident fieldName} - when Res_parsetree_viewer.has_tagged_template_literal_attr - innerExpr.pexp_attributes -> + when Res_parsetree_viewer.is_tagged_template_literal innerExpr + -> fieldName (* This is likely to be an exprhole *) | _ -> "" From 854db1da27c0b38e1121ed6f0b79db1f4a67c90f Mon Sep 17 00:00:00 2001 From: nojaf Date: Wed, 5 Feb 2025 09:38:48 +0100 Subject: [PATCH 07/14] Yeah this is the revert pipe thing --- analysis/src/CompletionFrontEnd.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 0977c2347b..f2768df342 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1116,14 +1116,14 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor resetCurrentCtxPath oldCtxPath | Pexp_apply { - funct = {pexp_desc = Pexp_ident {txt = Lident "->"; loc = _}}; + funct = {pexp_desc = Pexp_ident {txt = Lident "->"; loc = opLoc}}; args = [ (_, lhs); (_, {pexp_desc = Pexp_extension _; pexp_loc = {loc_ghost = true}}); ]; } - when expr.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor -> + when opLoc |> Loc.hasPos ~pos:posBeforeCursor -> (* Case foo-> when the parser adds a ghost expression to the rhs so the apply expression does not include the cursor *) if setPipeResult ~lhs ~id:"" then setFound () From 39a1db56496f0fce7e606376e520771e32ef6de4 Mon Sep 17 00:00:00 2001 From: nojaf Date: Wed, 5 Feb 2025 09:42:09 +0100 Subject: [PATCH 08/14] Revert other changes and commit test result --- analysis/src/SharedTypes.ml | 25 +- analysis/src/TypeUtils.ml | 13 +- .../expected/CompletionTaggedTemplate.res.txt | 601 ++++++++++++++++-- 3 files changed, 560 insertions(+), 79 deletions(-) diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index d7a4098c39..600c39a867 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -689,20 +689,20 @@ module Completable = struct | CPAwait ctxPath -> "await " ^ contextPathToString ctxPath | CPOption ctxPath -> "option<" ^ contextPathToString ctxPath ^ ">" | CPApply (cp, labels) -> - "CPApply(" ^ contextPathToString cp ^ "(" + contextPathToString cp ^ "(" ^ (labels |> List.map (function | Asttypes.Noloc.Nolabel -> "Nolabel" | Labelled s -> "~" ^ s | Optional s -> "?" ^ s) |> String.concat ", ") - ^ "))" + ^ ")" | CPArray (Some ctxPath) -> "array<" ^ contextPathToString ctxPath ^ ">" | CPArray None -> "array" | CPId {path; completionContext} -> - "CPID(" ^ completionContextToString completionContext ^ list path ^ ")" + completionContextToString completionContext ^ list path | CPField {contextPath = cp; fieldName = s} -> - "CPField(" ^ contextPathToString cp ^ "." ^ str s ^ ")" + contextPathToString cp ^ "." ^ str s | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]" | CPPipe {contextPath; id; inJsx} -> contextPathToString contextPath @@ -801,19 +801,6 @@ module Completion = struct | ExtractedType of completionType * [`Value | `Type] | FollowContextPath of Completable.contextPath * ScopeTypes.item list - let kind_to_string = function - | Module _ -> "Module" - | Value _ -> "Value" - | ObjLabel _ -> "ObjLabel" - | Label _ -> "Label" - | Type _ -> "Type" - | Constructor _ -> "Constructor" - | PolyvariantConstructor _ -> "PolyvariantConstructor" - | Field _ -> "Field" - | FileModule _ -> "FileModule" - | Snippet _ -> "Snippet" - | ExtractedType _ -> "ExtractedType" - | FollowContextPath _ -> "FollowContextPath" type t = { name: string; sortText: string option; @@ -832,10 +819,6 @@ module Completion = struct (** Whether this item is an made up, synthetic item or not. *) } - let toString (t : t) : string = - Format.sprintf "Completion: %s %s %b" t.name (kind_to_string t.kind) - t.synthetic - let create ?(synthetic = false) ?additionalTextEdits ?data ?typeArgContext ?(includesSnippets = false) ?insertText ~kind ~env ?sortText ?deprecated ?filterText ?detail ?(docstring = []) name = diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 54f8ed7490..ffeca9ae40 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -262,13 +262,6 @@ let extractFunctionType ~env ~package typ = in loop ~env [] typ -let rec extracReturnTypeButDontDoAnyAliasLookup (typ : Types.type_expr) : - Types.type_expr = - match typ.desc with - | Types.Tarrow (_, _t1, t2, _, _) -> - extracReturnTypeButDontDoAnyAliasLookup t2 - | _ -> typ - let extractFunctionTypeWithEnv ~env ~package typ = let rec loop ~env acc (t : Types.type_expr) = match t.desc with @@ -515,7 +508,6 @@ let rec digToRelevantTemplateNameType ~env ~package ?(suffix = "") let rec resolveTypeForPipeCompletion ~env ~package ~lhsLoc ~full (t : Types.type_expr) = - if Debug.verbose () then print_endline "Enter resolveTypeForPipeCompletion:"; (* If the type we're completing on is a type parameter, we won't be able to do completion unless we know what that type parameter is compiled as. This attempts to look up the compiled type for that type parameter by @@ -1175,7 +1167,7 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ?posOfDot of the project. Example: type x in module SomeModule in file SomeFile would get the globally unique id `SomeFile.SomeModule.x`.*) let rec findRootTypeId ~full ~env (t : Types.type_expr) = - let debug = Debug.verbose () in + let debug = false in match t.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> findRootTypeId ~full ~env t1 | Tconstr (path, _, _) -> ( @@ -1216,11 +1208,8 @@ let filterPipeableFunctions ~env ~full ?synthetic ?targetTypeId ?posOfDot match targetTypeId with | None -> completions | Some targetTypeId -> - if Debug.verbose () then Format.printf "targetTypeId: %s\n" targetTypeId; completions |> List.filter_map (fun (completion : Completion.t) -> - if Debug.verbose () then - print_endline (Completion.toString completion); let thisCompletionItemTypeId = match completion.kind with | Value t -> ( diff --git a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt index df1b454f22..851a98e643 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt @@ -1,97 +1,606 @@ +Complete src/CompletionTaggedTemplate.res 3:20 +posCursor:[3:20] posNoWhite:[3:19] Found expr:[3:11->0:-1] +Completable: Cpath Value[meh](Nolabel, Nolabel)."" +Package opens Pervasives.JsxModules.place holder +ContextPath Value[meh](Nolabel, Nolabel)."" +ContextPath Value[meh](Nolabel, Nolabel) +ContextPath Value[meh] +Path meh +ContextPath Value[meh](Nolabel, Nolabel, Nolabel)-> +ContextPath Value[meh](Nolabel, Nolabel, Nolabel) +ContextPath Value[meh] +Path meh +Path Js.String2. [{ - "label": "->Belt.Int.*", + "label": "->Js.String2.normalizeByForm", "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.*", + "detail": "(t, t) => t", + "documentation": {"kind": "markdown", "value": "\nES2015: `normalize(str, form)` returns the normalized Unicode string using the\nspecified form of normalization, which may be one of:\n- \"NFC\" — Normalization Form Canonical Composition.\n- \"NFD\" — Normalization Form Canonical Decomposition.\n- \"NFKC\" — Normalization Form Compatibility Composition.\n- \"NFKD\" — Normalization Form Compatibility Decomposition.\n\nSee [`String.normalize`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize) on MDN.\nSee also [Unicode technical report #15](https://unicode.org/reports/tr15/) for details.\n"}, + "sortText": "normalizeByForm", + "insertText": "->Js.String2.normalizeByForm", "additionalTextEdits": [{ - "range": {"start": {"line": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, "newText": "" }] }, { - "label": "->Belt.Int./", + "label": "->Js.String2.toLowerCase", "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./", + "detail": "t => t", + "documentation": {"kind": "markdown", "value": "\n`toLowerCase(str)` converts `str` to lower case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\ngive different results depending upon context, for example with the Greek\nletter sigma, which has two different lower case forms; one when it is the last\ncharacter in a string and another when it is not.\n\nSee [`String.toLowerCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toLowerCase(\"ABC\") == \"abc\"\nJs.String2.toLowerCase(`ΣΠ`) == `σπ`\nJs.String2.toLowerCase(`ΠΣ`) == `πς`\n```\n"}, + "sortText": "toLowerCase", + "insertText": "->Js.String2.toLowerCase", "additionalTextEdits": [{ - "range": {"start": {"line": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, "newText": "" }] }, { - "label": "->Belt.Int.toString", + "label": "->Js.String2.startsWith", "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", + "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": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, "newText": "" }] }, { - "label": "->Belt.Int.toFloat", + "label": "->Js.String2.lastIndexOf", "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", + "detail": "(t, t) => int", + "documentation": {"kind": "markdown", "value": "\n`lastIndexOf(str, searchValue)` returns the position of the last occurrence of\n`searchValue` within `str`, searching backwards from the end of the string.\nReturns -1 if `searchValue` is not in `str`. The return value is always\nrelative to the beginning of the string.\n\nSee [`String.lastIndexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.lastIndexOf(\"bookseller\", \"ok\") == 2\nJs.String2.lastIndexOf(\"beekeeper\", \"ee\") == 4\nJs.String2.lastIndexOf(\"abcdefg\", \"xyz\") == -1\n```\n"}, + "sortText": "lastIndexOf", + "insertText": "->Js.String2.lastIndexOf", "additionalTextEdits": [{ - "range": {"start": {"line": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, "newText": "" }] }, { - "label": "->Belt.Int.-", + "label": "->Js.String2.concat", "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.-", + "detail": "(t, t) => t", + "documentation": {"kind": "markdown", "value": "\n`concat(original, append)` returns a new `string` with `append` added after\n`original`.\n\nSee [`String.concat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.concat(\"cow\", \"bell\") == \"cowbell\"\n```\n"}, + "sortText": "concat", + "insertText": "->Js.String2.concat", "additionalTextEdits": [{ - "range": {"start": {"line": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, "newText": "" }] }, { - "label": "->Belt.Int.+", + "label": "->Js.String2.splitAtMost", "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.+", + "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": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, "newText": "" }] }, { - "label": "->X.minus_two", + "label": "->Js.String2.unsafeReplaceBy1", + "kind": 12, + "tags": [], + "detail": "(t, Js_re.t, (t, t, int, t) => t) => t", + "documentation": {"kind": "markdown", "value": "\nReturns a new `string` with some or all matches of a pattern with one set of\ncapturing parentheses replaced by the value returned from the given function.\nThe function receives as its parameters the matched string, the captured\nstring, the offset at which the match begins, and the whole string being\nmatched.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n\n## Examples\n\n```rescript\nlet str = \"Jony is 40\"\nlet re = /(Jony is )\\d+/g\nlet matchFn = (_match, part1, _offset, _wholeString) => {\n part1 ++ \"41\"\n}\n\nJs.String2.unsafeReplaceBy1(str, re, matchFn) == \"Jony is 41\"\n```\n"}, + "sortText": "unsafeReplaceBy1", + "insertText": "->Js.String2.unsafeReplaceBy1", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.replaceByRe", + "kind": 12, + "tags": [], + "detail": "(t, Js_re.t, t) => t", + "documentation": {"kind": "markdown", "value": "\n`replaceByRe(str, regex, replacement)` returns a new `string` where occurrences\nmatching regex have been replaced by `replacement`.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.replaceByRe(\"vowels be gone\", /[aeiou]/g, \"x\") == \"vxwxls bx gxnx\"\nJs.String2.replaceByRe(\"Juan Fulano\", /(\\w+) (\\w+)/, \"$2, $1\") == \"Fulano, Juan\"\n```\n"}, + "sortText": "replaceByRe", + "insertText": "->Js.String2.replaceByRe", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.trim", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": {"kind": "markdown", "value": "\n`trim(str)` returns a string that is `str` with whitespace stripped from both\nends. Internal whitespace is not removed.\n\nSee [`String.trim`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.trim(\" abc def \") == \"abc def\"\nJs.String2.trim(\"\\n\\r\\t abc def \\n\\n\\t\\r \") == \"abc def\"\n```\n"}, + "sortText": "trim", + "insertText": "->Js.String2.trim", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.includesFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `includes(str, searchValue start)` returns `true` if `searchValue` is\nfound anywhere within `str` starting at character number `start` (where 0 is\nthe first character), `false` otherwise.\n\nSee [`String.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.includesFrom(\"programmer\", \"gram\", 1) == true\nJs.String2.includesFrom(\"programmer\", \"gram\", 4) == false\nJs.String2.includesFrom(`대한민국`, `한`, 1) == true\n```\n"}, + "sortText": "includesFrom", + "insertText": "->Js.String2.includesFrom", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.concatMany", + "kind": 12, + "tags": [], + "detail": "(t, array) => t", + "documentation": {"kind": "markdown", "value": "\n`concatMany(original, arr)` returns a new `string` consisting of each item of an\narray of strings added to the `original` string.\n\nSee [`String.concat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.concatMany(\"1st\", [\"2nd\", \"3rd\", \"4th\"]) == \"1st2nd3rd4th\"\n```\n"}, + "sortText": "concatMany", + "insertText": "->Js.String2.concatMany", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.repeat", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": {"kind": "markdown", "value": "\n`repeat(str, n)` returns a `string` that consists of `n` repetitions of `str`.\nRaises `RangeError` if `n` is negative.\n\nSee [`String.repeat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.repeat(\"ha\", 3) == \"hahaha\"\nJs.String2.repeat(\"empty\", 0) == \"\"\n```\n"}, + "sortText": "repeat", + "insertText": "->Js.String2.repeat", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.toLocaleLowerCase", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": {"kind": "markdown", "value": "\n`toLocaleLowerCase(str)` converts `str` to lower case using the current locale.\nSee [`String.toLocaleLowerCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLocaleLowerCase)\non MDN.\n"}, + "sortText": "toLocaleLowerCase", + "insertText": "->Js.String2.toLocaleLowerCase", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.unsafeReplaceBy0", + "kind": 12, + "tags": [], + "detail": "(t, Js_re.t, (t, int, t) => t) => t", + "documentation": {"kind": "markdown", "value": "\nReturns a new `string` with some or all matches of a pattern with no capturing\nparentheses replaced by the value returned from the given function. The\nfunction receives as its parameters the matched string, the offset at which the\nmatch begins, and the whole string being matched.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n\n## Examples\n\n```rescript\nlet str = \"beautiful vowels\"\nlet re = /[aeiou]/g\nlet matchFn = (matchPart, _offset, _wholeString) => Js.String2.toUpperCase(matchPart)\n\nJs.String2.unsafeReplaceBy0(str, re, matchFn) == \"bEAUtIfUl vOwEls\"\n```\n"}, + "sortText": "unsafeReplaceBy0", + "insertText": "->Js.String2.unsafeReplaceBy0", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.charCodeAt", + "kind": 12, + "tags": [], + "detail": "(t, int) => float", + "documentation": {"kind": "markdown", "value": "\n`charCodeAt(s, n)` returns the character code at position `n` in string `s`;\nthe result is in the range 0-65535, unlke `codePointAt`, so it will not work\ncorrectly for characters with code points greater than or equal to 0x10000. The\nreturn type is `float` because this function returns NaN if `n` is less than\nzero or greater than the length of the string.\n\nSee [`String.charCodeAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.charCodeAt(`😺`, 0) == 0xd83d->Belt.Int.toFloat\nJs.String2.codePointAt(`😺`, 0) == Some(0x1f63a)\n```\n"}, + "sortText": "charCodeAt", + "insertText": "->Js.String2.charCodeAt", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.link", + "kind": 12, + "tags": [], + "detail": "(t, t) => t", + "documentation": {"kind": "markdown", "value": "\nES2015: `link(linkText, urlText)` creates a string with an HTML `` element\nwith href attribute of `urlText` and `linkText` as its content. Please do not\nuse this method, as it has been removed from the relevant web standards. See\n[`String.link`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/link)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.link(\"Go to page two\", \"page2.html\") == \"Go to page two\"\n```\n"}, + "sortText": "link", + "insertText": "->Js.String2.link", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.charAt", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": {"kind": "markdown", "value": "\n`charAt(s, n)` gets the character at index `n` within string `s`. If `n` is\nnegative or greater than the length of `s`, it returns the empty string. If the\nstring contains characters outside the range \\u0000-\\uffff, it will return the\nfirst 16-bit value at that position in the string.\n\nSee [`String.charAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.charAt(\"Reason\", 0) == \"R\"\nJs.String2.charAt(\"Reason\", 12) == \"\"\nJs.String2.charAt(`Rẽasöń`, 5) == `ń`\n```\n"}, + "sortText": "charAt", + "insertText": "->Js.String2.charAt", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.includes", + "kind": 12, + "tags": [], + "detail": "(t, t) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `includes(str, searchValue)` returns `true` if `searchValue` is found\nanywhere within `str`, false otherwise.\n\nSee [`String.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.includes(\"programmer\", \"gram\") == true\nJs.String2.includes(\"programmer\", \"er\") == true\nJs.String2.includes(\"programmer\", \"pro\") == true\nJs.String2.includes(\"programmer.dat\", \"xyz\") == false\n```\n"}, + "sortText": "includes", + "insertText": "->Js.String2.includes", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.anchor", + "kind": 12, + "tags": [], + "detail": "(t, t) => t", + "documentation": {"kind": "markdown", "value": "\n`anchor(anchorText, anchorName)` creates a string with an HTML `` element\nwith name attribute of `anchorName` and `anchorText` as its content. Please do\nnot use this method, as it has been removed from the relevant web standards.\n\nSee [`String.anchor`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/anchor)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.anchor(\"Page One\", \"page1\") == \"Page One\"\n```\n"}, + "sortText": "anchor", + "insertText": "->Js.String2.anchor", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.match_", + "kind": 12, + "tags": [], + "detail": "(t, Js_re.t) => option>>", + "documentation": {"kind": "markdown", "value": "\n`match(str, regexp)` matches a `string` against the given `regexp`. If there is\nno match, it returns `None`. For regular expressions without the g modifier, if\n there is a match, the return value is `Some(array)` where the array contains:\n- The entire matched string\n- Any capture groups if the regexp had parentheses\nFor regular expressions with the g modifier, a matched expression returns\n`Some(array)` with all the matched substrings and no capture groups.\n\nSee [`String.match`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.match_(\"The better bats\", /b[aeiou]t/) == Some([\"bet\"])\nJs.String2.match_(\"The better bats\", /b[aeiou]t/g) == Some([\"bet\", \"bat\"])\nJs.String2.match_(\"Today is 2018-04-05.\", /(\\d+)-(\\d+)-(\\d+)/) ==\n Some([\"2018-04-05\", \"2018\", \"04\", \"05\"])\nJs.String2.match_(\"The large container.\", /b[aeiou]g/) == None\n```\n"}, + "sortText": "match_", + "insertText": "->Js.String2.match_", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.lastIndexOfFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => int", + "documentation": {"kind": "markdown", "value": "\n`lastIndexOfFrom(str, searchValue, start)` returns the position of the last\noccurrence of `searchValue` within `str`, searching backwards from the given\nstart position. Returns -1 if `searchValue` is not in `str`. The return value\nis always relative to the beginning of the string.\n\nSee [`String.lastIndexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.lastIndexOfFrom(\"bookseller\", \"ok\", 6) == 2\nJs.String2.lastIndexOfFrom(\"beekeeper\", \"ee\", 8) == 4\nJs.String2.lastIndexOfFrom(\"beekeeper\", \"ee\", 3) == 1\nJs.String2.lastIndexOfFrom(\"abcdefg\", \"xyz\", 4) == -1\n```\n"}, + "sortText": "lastIndexOfFrom", + "insertText": "->Js.String2.lastIndexOfFrom", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.replace", + "kind": 12, + "tags": [], + "detail": "(t, t, t) => t", + "documentation": {"kind": "markdown", "value": "\nES2015: `replace(str, substr, newSubstr)` returns a new `string` which is\nidentical to `str` except with the first matching instance of `substr` replaced\nby `newSubstr`. `substr` is treated as a verbatim string to match, not a\nregular expression.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.replace(\"old string\", \"old\", \"new\") == \"new string\"\nJs.String2.replace(\"the cat and the dog\", \"the\", \"this\") == \"this cat and the dog\"\n```\n"}, + "sortText": "replace", + "insertText": "->Js.String2.replace", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.codePointAt", + "kind": 12, + "tags": [], + "detail": "(t, int) => option", + "documentation": {"kind": "markdown", "value": "\n`codePointAt(s, n)` returns the code point at position `n` within string `s` as\na `Some(value)`. The return value handles code points greater than or equal to\n0x10000. If there is no code point at the given position, the function returns\n`None`.\n\nSee [`String.codePointAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.codePointAt(`¿😺?`, 1) == Some(0x1f63a)\nJs.String2.codePointAt(\"abc\", 5) == None\n```\n"}, + "sortText": "codePointAt", + "insertText": "->Js.String2.codePointAt", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.length", "kind": 12, "tags": [], "detail": "t => int", - "documentation": null, - "sortText": "minus_two", - "insertText": "->X.minus_two", + "documentation": {"kind": "markdown", "value": "\n`length(s)` returns the length of the given `string`.\n\nSee [`String.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.length(\"abcd\") == 4\n```\n"}, + "sortText": "length", + "insertText": "->Js.String2.length", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.localeCompare", + "kind": 12, + "tags": [], + "detail": "(t, t) => float", + "documentation": {"kind": "markdown", "value": "\n`localeCompare(reference, comparison)` returns\n- a negative value if reference comes before comparison in sort order\n- zero if reference and comparison have the same sort order\n- a positive value if reference comes after comparison in sort order\n\nSee [`String.localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.localeCompare(\"zebra\", \"ant\") > 0.0\nJs.String2.localeCompare(\"ant\", \"zebra\") < 0.0\nJs.String2.localeCompare(\"cat\", \"cat\") == 0.0\nJs.String2.localeCompare(\"CAT\", \"cat\") > 0.0\n```\n"}, + "sortText": "localeCompare", + "insertText": "->Js.String2.localeCompare", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.indexOf", + "kind": 12, + "tags": [], + "detail": "(t, t) => int", + "documentation": {"kind": "markdown", "value": "\nES2015: `indexOf(str, searchValue)` returns the position at which `searchValue`\nwas first found within `str`, or -1 if `searchValue` is not in `str`.\n\nSee [`String.indexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.indexOf(\"bookseller\", \"ok\") == 2\nJs.String2.indexOf(\"bookseller\", \"sell\") == 4\nJs.String2.indexOf(\"beekeeper\", \"ee\") == 1\nJs.String2.indexOf(\"bookseller\", \"xyz\") == -1\n```\n"}, + "sortText": "indexOf", + "insertText": "->Js.String2.indexOf", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.unsafeReplaceBy3", + "kind": 12, + "tags": [], + "detail": "(t, Js_re.t, (t, t, t, t, int, t) => t) => t", + "documentation": {"kind": "markdown", "value": "\nReturns a new `string` with some or all matches of a pattern with three sets of\ncapturing parentheses replaced by the value returned from the given function.\nThe function receives as its parameters the matched string, the captured\nstrings, the offset at which the match begins, and the whole string being\nmatched.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n"}, + "sortText": "unsafeReplaceBy3", + "insertText": "->Js.String2.unsafeReplaceBy3", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.endsWithFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => bool", + "documentation": {"kind": "markdown", "value": "\n`endsWithFrom(str, ending, len)` returns `true` if the first len characters of\n`str` end with `ending`, `false` otherwise. If `len` is greater than or equal\nto the length of `str`, then it works like `endsWith`. (Honestly, this should\nhave been named endsWithAt, but oh well).\n\nSee [`String.endsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.endsWithFrom(\"abcd\", \"cd\", 4) == true\nJs.String2.endsWithFrom(\"abcde\", \"cd\", 3) == false\nJs.String2.endsWithFrom(\"abcde\", \"cde\", 99) == true\nJs.String2.endsWithFrom(\"example.dat\", \"ple\", 7) == true\n```\n"}, + "sortText": "endsWithFrom", + "insertText": "->Js.String2.endsWithFrom", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.endsWith", + "kind": 12, + "tags": [], + "detail": "(t, t) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `endsWith(str, substr)` returns `true` if the `str` ends with `substr`,\n`false` otherwise.\n\nSee [`String.endsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.endsWith(\"ReScript\", \"Script\") == true\nJs.String2.endsWith(\"C++\", \"Script\") == false\n```\n"}, + "sortText": "endsWith", + "insertText": "->Js.String2.endsWith", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.normalize", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": {"kind": "markdown", "value": "\n`normalize(str)` returns the normalized Unicode string using Normalization Form\nCanonical (NFC) Composition. Consider the character ã, which can be represented\nas the single codepoint \\u00e3 or the combination of a lower case letter A\n\\u0061 and a combining tilde \\u0303. Normalization ensures that both can be\nstored in an equivalent binary representation.\n\nSee [`String.normalize`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)\non MDN. See also [Unicode technical report\n#15](https://unicode.org/reports/tr15/) for details.\n"}, + "sortText": "normalize", + "insertText": "->Js.String2.normalize", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.get", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": {"kind": "markdown", "value": "\n`get(s, n)` returns as a `string` the character at the given index number. If\n`n` is out of range, this function returns `undefined`,so at some point this\nfunction may be modified to return `option`.\n\n## Examples\n\n```rescript\nJs.String2.get(\"Reason\", 0) == \"R\"\nJs.String2.get(\"Reason\", 4) == \"o\"\nJs.String2.get(`Rẽasöń`, 5) == `ń`\n```\n"}, + "sortText": "get", + "insertText": "->Js.String2.get", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.toUpperCase", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": {"kind": "markdown", "value": "\n`toUpperCase(str)` converts `str` to upper case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\nexpand the number of letters in the result; for example the German ß\ncapitalizes to two Ses in a row.\n\nSee [`String.toUpperCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toUpperCase(\"abc\") == \"ABC\"\nJs.String2.toUpperCase(`Straße`) == `STRASSE`\nJs.String2.toUpperCase(`πς`) == `ΠΣ`\n```\n"}, + "sortText": "toUpperCase", + "insertText": "->Js.String2.toUpperCase", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.castToArrayLike", + "kind": 12, + "tags": [], + "detail": "t => Js_array2.array_like", + "documentation": {"kind": "markdown", "value": "\nCasts its argument to an `array_like` entity that can be processed by functions\nsuch as `Js.Array2.fromMap()`\n\n## Examples\n\n```rescript\nlet s = \"abcde\"\nlet arr = Js.Array2.fromMap(Js.String2.castToArrayLike(s), x => x)\narr == [\"a\", \"b\", \"c\", \"d\", \"e\"]\n```\n"}, + "sortText": "castToArrayLike", + "insertText": "->Js.String2.castToArrayLike", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.unsafeReplaceBy2", + "kind": 12, + "tags": [], + "detail": "(t, Js_re.t, (t, t, t, int, t) => t) => t", + "documentation": {"kind": "markdown", "value": "\nReturns a new `string` with some or all matches of a pattern with two sets of\ncapturing parentheses replaced by the value returned from the given function.\nThe function receives as its parameters the matched string, the captured\nstrings, the offset at which the match begins, and the whole string being\nmatched.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n\n## Examples\n\n```rescript\nlet str = \"7 times 6\"\nlet re = /(\\d+) times (\\d+)/\nlet matchFn = (_match, p1, p2, _offset, _wholeString) => {\n switch (Belt.Int.fromString(p1), Belt.Int.fromString(p2)) {\n | (Some(x), Some(y)) => Belt.Int.toString(x * y)\n | _ => \"???\"\n }\n}\n\nJs.String2.unsafeReplaceBy2(str, re, matchFn) == \"42\"\n```\n"}, + "sortText": "unsafeReplaceBy2", + "insertText": "->Js.String2.unsafeReplaceBy2", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }, { + "label": "->Js.String2.toLocaleUpperCase", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": {"kind": "markdown", "value": "\n`toLocaleUpperCase(str)` converts `str` to upper case using the current locale.\nSee [`String.to:LocaleUpperCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLocaleUpperCase)\non MDN.\n"}, + "sortText": "toLocaleUpperCase", + "insertText": "->Js.String2.toLocaleUpperCase", "additionalTextEdits": [{ - "range": {"start": {"line": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, "newText": "" }] }, { - "label": "->X.minus_three", + "label": "->Js.String2.indexOfFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => int", + "documentation": {"kind": "markdown", "value": "\n`indexOfFrom(str, searchValue, start)` returns the position at which\n`searchValue` was found within `str` starting at character position `start`, or\n-1 if `searchValue` is not found in that portion of `str`. The return value is\nrelative to the beginning of the string, no matter where the search started\nfrom.\n\nSee [`String.indexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.indexOfFrom(\"bookseller\", \"ok\", 1) == 2\nJs.String2.indexOfFrom(\"bookseller\", \"sell\", 2) == 4\nJs.String2.indexOfFrom(\"bookseller\", \"sell\", 5) == -1\n```\n"}, + "sortText": "indexOfFrom", + "insertText": "->Js.String2.indexOfFrom", + "additionalTextEdits": [{ + "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "newText": "" + }] + }] + +Complete src/CompletionTaggedTemplate.res 6:23 +posCursor:[6:23] posNoWhite:[6:22] Found expr:[6:11->6:23] +Completable: Cpath Value[meh](Nolabel, Nolabel).len +Package opens Pervasives.JsxModules.place holder +ContextPath Value[meh](Nolabel, Nolabel).len +ContextPath Value[meh](Nolabel, Nolabel) +ContextPath Value[meh] +Path meh +ContextPath Value[meh](Nolabel, Nolabel, Nolabel)->len +ContextPath Value[meh](Nolabel, Nolabel, Nolabel) +ContextPath Value[meh] +Path meh +Path Js.String2.len +[{ + "label": "->Js.String2.length", "kind": 12, "tags": [], "detail": "t => int", - "documentation": null, - "sortText": "minus_three", - "insertText": "->X.minus_three", + "documentation": {"kind": "markdown", "value": "\n`length(s)` returns the length of the given `string`.\n\nSee [`String.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.length(\"abcd\") == 4\n```\n"}, + "sortText": "length", + "insertText": "->Js.String2.length", "additionalTextEdits": [{ - "range": {"start": {"line": 10, "character": 16}, "end": {"line": 10, "character": 17}}, + "range": {"start": {"line": 6, "character": 19}, "end": {"line": 6, "character": 20}}, "newText": "" }] }] + From e56fa3fa632ecc6bc94f94b4e1c5fb443e130c3f Mon Sep 17 00:00:00 2001 From: nojaf Date: Wed, 5 Feb 2025 09:44:33 +0100 Subject: [PATCH 09/14] Remove local execution commands --- .../tests/src/CompletionTaggedTemplate.res | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res index 93285f3ae1..3e336a5bfa 100644 --- a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res +++ b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res @@ -6,15 +6,3 @@ external meh: (array, array) => string = "default" // let y = meh`bar`.len // ^com - -/* - -dune exec -- rescript-editor-analysis debug-dump verbose test /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res > /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt - -dune exec -- rescript-editor-analysis completion \ - /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res \ - 10 17 \ - /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res \ - > /Users/nojaf/Projects/rescript/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt - - */ \ No newline at end of file From 9098b74a9543c451c6478520eb7d3fed99fc76a9 Mon Sep 17 00:00:00 2001 From: nojaf Date: Wed, 5 Feb 2025 09:53:02 +0100 Subject: [PATCH 10/14] Remove debug log --- analysis/src/CompletionFrontEnd.ml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index f2768df342..7fe85c7a4c 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1143,16 +1143,6 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor when Res_parsetree_viewer.is_tagged_template_literal innerExpr && expr.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor || CompletionExpressions.isExprHole fieldExpr -> - if Debug.verbose () then ( - let print_loc (loc : Location.t) : string = - Format.sprintf "(%d,%d:%d,%d)" loc.loc_start.pos_lnum - loc.loc_start.pos_cnum loc.loc_end.pos_lnum loc.loc_end.pos_cnum - in - Printast.expression 4 Format.std_formatter expr; - Format.printf "posBeforeCursor: %s, expr.pexp_loc: %s" - (Pos.toString posBeforeCursor) - (print_loc expr.pexp_loc); - print_endline "\nyow_yow"); exprToContextPath innerExpr |> Option.iter (fun cpath -> (* Determine the field name if present *) From 0ea3bab762b5e75dbed89e08d10b3588d7aede7f Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 6 Feb 2025 09:23:22 +0100 Subject: [PATCH 11/14] Change sample --- analysis/src/CompletionFrontEnd.ml | 5 +- .../tests/src/CompletionTaggedTemplate.res | 14 +- .../expected/CompletionTaggedTemplate.res.txt | 594 +----------------- 3 files changed, 46 insertions(+), 567 deletions(-) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 7fe85c7a4c..e00ffd3e14 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1148,10 +1148,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor (* Determine the field name if present *) let fieldName = match fieldExpr.pexp_desc with - | Pexp_ident {txt = Lident fieldName} - when Res_parsetree_viewer.is_tagged_template_literal innerExpr - -> - fieldName + | Pexp_ident {txt = Lident fieldName} -> fieldName (* This is likely to be an exprhole *) | _ -> "" in diff --git a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res index 3e336a5bfa..dce1a0cbcd 100644 --- a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res +++ b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res @@ -1,8 +1,16 @@ +module M = { + type t + + let a = (_t:t) => 4 + let b = (_:t) => "c" + let xyz = (_:t, p:int) => p + 1 +} + @module("meh") @taggedTemplate -external meh: (array, array) => string = "default" +external meh: (array, array) => M.t = "default" // let x = meh`foo`. // ^com -// let y = meh`bar`.len -// ^com +// let y = meh`bar`.x +// ^com diff --git a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt index 851a98e643..73f0090148 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt @@ -1,5 +1,5 @@ -Complete src/CompletionTaggedTemplate.res 3:20 -posCursor:[3:20] posNoWhite:[3:19] Found expr:[3:11->0:-1] +Complete src/CompletionTaggedTemplate.res 11:20 +posCursor:[11:20] posNoWhite:[11:19] Found expr:[11:11->0:-1] Completable: Cpath Value[meh](Nolabel, Nolabel)."" Package opens Pervasives.JsxModules.place holder ContextPath Value[meh](Nolabel, Nolabel)."" @@ -10,596 +10,70 @@ ContextPath Value[meh](Nolabel, Nolabel, Nolabel)-> ContextPath Value[meh](Nolabel, Nolabel, Nolabel) ContextPath Value[meh] Path meh -Path Js.String2. +CPPipe pathFromEnv:M found:true +Path M. [{ - "label": "->Js.String2.normalizeByForm", + "label": "->M.xyz", "kind": 12, "tags": [], - "detail": "(t, t) => t", - "documentation": {"kind": "markdown", "value": "\nES2015: `normalize(str, form)` returns the normalized Unicode string using the\nspecified form of normalization, which may be one of:\n- \"NFC\" — Normalization Form Canonical Composition.\n- \"NFD\" — Normalization Form Canonical Decomposition.\n- \"NFKC\" — Normalization Form Compatibility Composition.\n- \"NFKD\" — Normalization Form Compatibility Decomposition.\n\nSee [`String.normalize`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize) on MDN.\nSee also [Unicode technical report #15](https://unicode.org/reports/tr15/) for details.\n"}, - "sortText": "normalizeByForm", - "insertText": "->Js.String2.normalizeByForm", + "detail": "(t, int) => int", + "documentation": null, + "sortText": "xyz", + "insertText": "->M.xyz", "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "range": {"start": {"line": 11, "character": 19}, "end": {"line": 11, "character": 20}}, "newText": "" }] }, { - "label": "->Js.String2.toLowerCase", + "label": "->M.b", "kind": 12, "tags": [], - "detail": "t => t", - "documentation": {"kind": "markdown", "value": "\n`toLowerCase(str)` converts `str` to lower case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\ngive different results depending upon context, for example with the Greek\nletter sigma, which has two different lower case forms; one when it is the last\ncharacter in a string and another when it is not.\n\nSee [`String.toLowerCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toLowerCase(\"ABC\") == \"abc\"\nJs.String2.toLowerCase(`ΣΠ`) == `σπ`\nJs.String2.toLowerCase(`ΠΣ`) == `πς`\n```\n"}, - "sortText": "toLowerCase", - "insertText": "->Js.String2.toLowerCase", + "detail": "t => string", + "documentation": null, + "sortText": "b", + "insertText": "->M.b", "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "range": {"start": {"line": 11, "character": 19}, "end": {"line": 11, "character": 20}}, "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.lastIndexOf", - "kind": 12, - "tags": [], - "detail": "(t, t) => int", - "documentation": {"kind": "markdown", "value": "\n`lastIndexOf(str, searchValue)` returns the position of the last occurrence of\n`searchValue` within `str`, searching backwards from the end of the string.\nReturns -1 if `searchValue` is not in `str`. The return value is always\nrelative to the beginning of the string.\n\nSee [`String.lastIndexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.lastIndexOf(\"bookseller\", \"ok\") == 2\nJs.String2.lastIndexOf(\"beekeeper\", \"ee\") == 4\nJs.String2.lastIndexOf(\"abcdefg\", \"xyz\") == -1\n```\n"}, - "sortText": "lastIndexOf", - "insertText": "->Js.String2.lastIndexOf", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.concat", - "kind": 12, - "tags": [], - "detail": "(t, t) => t", - "documentation": {"kind": "markdown", "value": "\n`concat(original, append)` returns a new `string` with `append` added after\n`original`.\n\nSee [`String.concat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.concat(\"cow\", \"bell\") == \"cowbell\"\n```\n"}, - "sortText": "concat", - "insertText": "->Js.String2.concat", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.unsafeReplaceBy1", - "kind": 12, - "tags": [], - "detail": "(t, Js_re.t, (t, t, int, t) => t) => t", - "documentation": {"kind": "markdown", "value": "\nReturns a new `string` with some or all matches of a pattern with one set of\ncapturing parentheses replaced by the value returned from the given function.\nThe function receives as its parameters the matched string, the captured\nstring, the offset at which the match begins, and the whole string being\nmatched.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n\n## Examples\n\n```rescript\nlet str = \"Jony is 40\"\nlet re = /(Jony is )\\d+/g\nlet matchFn = (_match, part1, _offset, _wholeString) => {\n part1 ++ \"41\"\n}\n\nJs.String2.unsafeReplaceBy1(str, re, matchFn) == \"Jony is 41\"\n```\n"}, - "sortText": "unsafeReplaceBy1", - "insertText": "->Js.String2.unsafeReplaceBy1", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.replaceByRe", - "kind": 12, - "tags": [], - "detail": "(t, Js_re.t, t) => t", - "documentation": {"kind": "markdown", "value": "\n`replaceByRe(str, regex, replacement)` returns a new `string` where occurrences\nmatching regex have been replaced by `replacement`.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.replaceByRe(\"vowels be gone\", /[aeiou]/g, \"x\") == \"vxwxls bx gxnx\"\nJs.String2.replaceByRe(\"Juan Fulano\", /(\\w+) (\\w+)/, \"$2, $1\") == \"Fulano, Juan\"\n```\n"}, - "sortText": "replaceByRe", - "insertText": "->Js.String2.replaceByRe", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.trim", - "kind": 12, - "tags": [], - "detail": "t => t", - "documentation": {"kind": "markdown", "value": "\n`trim(str)` returns a string that is `str` with whitespace stripped from both\nends. Internal whitespace is not removed.\n\nSee [`String.trim`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.trim(\" abc def \") == \"abc def\"\nJs.String2.trim(\"\\n\\r\\t abc def \\n\\n\\t\\r \") == \"abc def\"\n```\n"}, - "sortText": "trim", - "insertText": "->Js.String2.trim", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.includesFrom", - "kind": 12, - "tags": [], - "detail": "(t, t, int) => bool", - "documentation": {"kind": "markdown", "value": "\nES2015: `includes(str, searchValue start)` returns `true` if `searchValue` is\nfound anywhere within `str` starting at character number `start` (where 0 is\nthe first character), `false` otherwise.\n\nSee [`String.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.includesFrom(\"programmer\", \"gram\", 1) == true\nJs.String2.includesFrom(\"programmer\", \"gram\", 4) == false\nJs.String2.includesFrom(`대한민국`, `한`, 1) == true\n```\n"}, - "sortText": "includesFrom", - "insertText": "->Js.String2.includesFrom", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.concatMany", - "kind": 12, - "tags": [], - "detail": "(t, array) => t", - "documentation": {"kind": "markdown", "value": "\n`concatMany(original, arr)` returns a new `string` consisting of each item of an\narray of strings added to the `original` string.\n\nSee [`String.concat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.concatMany(\"1st\", [\"2nd\", \"3rd\", \"4th\"]) == \"1st2nd3rd4th\"\n```\n"}, - "sortText": "concatMany", - "insertText": "->Js.String2.concatMany", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.repeat", - "kind": 12, - "tags": [], - "detail": "(t, int) => t", - "documentation": {"kind": "markdown", "value": "\n`repeat(str, n)` returns a `string` that consists of `n` repetitions of `str`.\nRaises `RangeError` if `n` is negative.\n\nSee [`String.repeat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.repeat(\"ha\", 3) == \"hahaha\"\nJs.String2.repeat(\"empty\", 0) == \"\"\n```\n"}, - "sortText": "repeat", - "insertText": "->Js.String2.repeat", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.toLocaleLowerCase", - "kind": 12, - "tags": [], - "detail": "t => t", - "documentation": {"kind": "markdown", "value": "\n`toLocaleLowerCase(str)` converts `str` to lower case using the current locale.\nSee [`String.toLocaleLowerCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLocaleLowerCase)\non MDN.\n"}, - "sortText": "toLocaleLowerCase", - "insertText": "->Js.String2.toLocaleLowerCase", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.unsafeReplaceBy0", - "kind": 12, - "tags": [], - "detail": "(t, Js_re.t, (t, int, t) => t) => t", - "documentation": {"kind": "markdown", "value": "\nReturns a new `string` with some or all matches of a pattern with no capturing\nparentheses replaced by the value returned from the given function. The\nfunction receives as its parameters the matched string, the offset at which the\nmatch begins, and the whole string being matched.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n\n## Examples\n\n```rescript\nlet str = \"beautiful vowels\"\nlet re = /[aeiou]/g\nlet matchFn = (matchPart, _offset, _wholeString) => Js.String2.toUpperCase(matchPart)\n\nJs.String2.unsafeReplaceBy0(str, re, matchFn) == \"bEAUtIfUl vOwEls\"\n```\n"}, - "sortText": "unsafeReplaceBy0", - "insertText": "->Js.String2.unsafeReplaceBy0", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.charCodeAt", - "kind": 12, - "tags": [], - "detail": "(t, int) => float", - "documentation": {"kind": "markdown", "value": "\n`charCodeAt(s, n)` returns the character code at position `n` in string `s`;\nthe result is in the range 0-65535, unlke `codePointAt`, so it will not work\ncorrectly for characters with code points greater than or equal to 0x10000. The\nreturn type is `float` because this function returns NaN if `n` is less than\nzero or greater than the length of the string.\n\nSee [`String.charCodeAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.charCodeAt(`😺`, 0) == 0xd83d->Belt.Int.toFloat\nJs.String2.codePointAt(`😺`, 0) == Some(0x1f63a)\n```\n"}, - "sortText": "charCodeAt", - "insertText": "->Js.String2.charCodeAt", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.link", - "kind": 12, - "tags": [], - "detail": "(t, t) => t", - "documentation": {"kind": "markdown", "value": "\nES2015: `link(linkText, urlText)` creates a string with an HTML `` element\nwith href attribute of `urlText` and `linkText` as its content. Please do not\nuse this method, as it has been removed from the relevant web standards. See\n[`String.link`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/link)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.link(\"Go to page two\", \"page2.html\") == \"Go to page two\"\n```\n"}, - "sortText": "link", - "insertText": "->Js.String2.link", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.charAt", - "kind": 12, - "tags": [], - "detail": "(t, int) => t", - "documentation": {"kind": "markdown", "value": "\n`charAt(s, n)` gets the character at index `n` within string `s`. If `n` is\nnegative or greater than the length of `s`, it returns the empty string. If the\nstring contains characters outside the range \\u0000-\\uffff, it will return the\nfirst 16-bit value at that position in the string.\n\nSee [`String.charAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.charAt(\"Reason\", 0) == \"R\"\nJs.String2.charAt(\"Reason\", 12) == \"\"\nJs.String2.charAt(`Rẽasöń`, 5) == `ń`\n```\n"}, - "sortText": "charAt", - "insertText": "->Js.String2.charAt", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.includes", - "kind": 12, - "tags": [], - "detail": "(t, t) => bool", - "documentation": {"kind": "markdown", "value": "\nES2015: `includes(str, searchValue)` returns `true` if `searchValue` is found\nanywhere within `str`, false otherwise.\n\nSee [`String.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.includes(\"programmer\", \"gram\") == true\nJs.String2.includes(\"programmer\", \"er\") == true\nJs.String2.includes(\"programmer\", \"pro\") == true\nJs.String2.includes(\"programmer.dat\", \"xyz\") == false\n```\n"}, - "sortText": "includes", - "insertText": "->Js.String2.includes", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.anchor", - "kind": 12, - "tags": [], - "detail": "(t, t) => t", - "documentation": {"kind": "markdown", "value": "\n`anchor(anchorText, anchorName)` creates a string with an HTML `` element\nwith name attribute of `anchorName` and `anchorText` as its content. Please do\nnot use this method, as it has been removed from the relevant web standards.\n\nSee [`String.anchor`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/anchor)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.anchor(\"Page One\", \"page1\") == \"Page One\"\n```\n"}, - "sortText": "anchor", - "insertText": "->Js.String2.anchor", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.match_", - "kind": 12, - "tags": [], - "detail": "(t, Js_re.t) => option>>", - "documentation": {"kind": "markdown", "value": "\n`match(str, regexp)` matches a `string` against the given `regexp`. If there is\nno match, it returns `None`. For regular expressions without the g modifier, if\n there is a match, the return value is `Some(array)` where the array contains:\n- The entire matched string\n- Any capture groups if the regexp had parentheses\nFor regular expressions with the g modifier, a matched expression returns\n`Some(array)` with all the matched substrings and no capture groups.\n\nSee [`String.match`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.match_(\"The better bats\", /b[aeiou]t/) == Some([\"bet\"])\nJs.String2.match_(\"The better bats\", /b[aeiou]t/g) == Some([\"bet\", \"bat\"])\nJs.String2.match_(\"Today is 2018-04-05.\", /(\\d+)-(\\d+)-(\\d+)/) ==\n Some([\"2018-04-05\", \"2018\", \"04\", \"05\"])\nJs.String2.match_(\"The large container.\", /b[aeiou]g/) == None\n```\n"}, - "sortText": "match_", - "insertText": "->Js.String2.match_", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.lastIndexOfFrom", - "kind": 12, - "tags": [], - "detail": "(t, t, int) => int", - "documentation": {"kind": "markdown", "value": "\n`lastIndexOfFrom(str, searchValue, start)` returns the position of the last\noccurrence of `searchValue` within `str`, searching backwards from the given\nstart position. Returns -1 if `searchValue` is not in `str`. The return value\nis always relative to the beginning of the string.\n\nSee [`String.lastIndexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.lastIndexOfFrom(\"bookseller\", \"ok\", 6) == 2\nJs.String2.lastIndexOfFrom(\"beekeeper\", \"ee\", 8) == 4\nJs.String2.lastIndexOfFrom(\"beekeeper\", \"ee\", 3) == 1\nJs.String2.lastIndexOfFrom(\"abcdefg\", \"xyz\", 4) == -1\n```\n"}, - "sortText": "lastIndexOfFrom", - "insertText": "->Js.String2.lastIndexOfFrom", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.replace", - "kind": 12, - "tags": [], - "detail": "(t, t, t) => t", - "documentation": {"kind": "markdown", "value": "\nES2015: `replace(str, substr, newSubstr)` returns a new `string` which is\nidentical to `str` except with the first matching instance of `substr` replaced\nby `newSubstr`. `substr` is treated as a verbatim string to match, not a\nregular expression.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.replace(\"old string\", \"old\", \"new\") == \"new string\"\nJs.String2.replace(\"the cat and the dog\", \"the\", \"this\") == \"this cat and the dog\"\n```\n"}, - "sortText": "replace", - "insertText": "->Js.String2.replace", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.codePointAt", - "kind": 12, - "tags": [], - "detail": "(t, int) => option", - "documentation": {"kind": "markdown", "value": "\n`codePointAt(s, n)` returns the code point at position `n` within string `s` as\na `Some(value)`. The return value handles code points greater than or equal to\n0x10000. If there is no code point at the given position, the function returns\n`None`.\n\nSee [`String.codePointAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.codePointAt(`¿😺?`, 1) == Some(0x1f63a)\nJs.String2.codePointAt(\"abc\", 5) == None\n```\n"}, - "sortText": "codePointAt", - "insertText": "->Js.String2.codePointAt", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.length", + "label": "->M.a", "kind": 12, "tags": [], "detail": "t => int", - "documentation": {"kind": "markdown", "value": "\n`length(s)` returns the length of the given `string`.\n\nSee [`String.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.length(\"abcd\") == 4\n```\n"}, - "sortText": "length", - "insertText": "->Js.String2.length", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.localeCompare", - "kind": 12, - "tags": [], - "detail": "(t, t) => float", - "documentation": {"kind": "markdown", "value": "\n`localeCompare(reference, comparison)` returns\n- a negative value if reference comes before comparison in sort order\n- zero if reference and comparison have the same sort order\n- a positive value if reference comes after comparison in sort order\n\nSee [`String.localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.localeCompare(\"zebra\", \"ant\") > 0.0\nJs.String2.localeCompare(\"ant\", \"zebra\") < 0.0\nJs.String2.localeCompare(\"cat\", \"cat\") == 0.0\nJs.String2.localeCompare(\"CAT\", \"cat\") > 0.0\n```\n"}, - "sortText": "localeCompare", - "insertText": "->Js.String2.localeCompare", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.indexOf", - "kind": 12, - "tags": [], - "detail": "(t, t) => int", - "documentation": {"kind": "markdown", "value": "\nES2015: `indexOf(str, searchValue)` returns the position at which `searchValue`\nwas first found within `str`, or -1 if `searchValue` is not in `str`.\n\nSee [`String.indexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.indexOf(\"bookseller\", \"ok\") == 2\nJs.String2.indexOf(\"bookseller\", \"sell\") == 4\nJs.String2.indexOf(\"beekeeper\", \"ee\") == 1\nJs.String2.indexOf(\"bookseller\", \"xyz\") == -1\n```\n"}, - "sortText": "indexOf", - "insertText": "->Js.String2.indexOf", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.unsafeReplaceBy3", - "kind": 12, - "tags": [], - "detail": "(t, Js_re.t, (t, t, t, t, int, t) => t) => t", - "documentation": {"kind": "markdown", "value": "\nReturns a new `string` with some or all matches of a pattern with three sets of\ncapturing parentheses replaced by the value returned from the given function.\nThe function receives as its parameters the matched string, the captured\nstrings, the offset at which the match begins, and the whole string being\nmatched.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n"}, - "sortText": "unsafeReplaceBy3", - "insertText": "->Js.String2.unsafeReplaceBy3", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.endsWithFrom", - "kind": 12, - "tags": [], - "detail": "(t, t, int) => bool", - "documentation": {"kind": "markdown", "value": "\n`endsWithFrom(str, ending, len)` returns `true` if the first len characters of\n`str` end with `ending`, `false` otherwise. If `len` is greater than or equal\nto the length of `str`, then it works like `endsWith`. (Honestly, this should\nhave been named endsWithAt, but oh well).\n\nSee [`String.endsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.endsWithFrom(\"abcd\", \"cd\", 4) == true\nJs.String2.endsWithFrom(\"abcde\", \"cd\", 3) == false\nJs.String2.endsWithFrom(\"abcde\", \"cde\", 99) == true\nJs.String2.endsWithFrom(\"example.dat\", \"ple\", 7) == true\n```\n"}, - "sortText": "endsWithFrom", - "insertText": "->Js.String2.endsWithFrom", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.endsWith", - "kind": 12, - "tags": [], - "detail": "(t, t) => bool", - "documentation": {"kind": "markdown", "value": "\nES2015: `endsWith(str, substr)` returns `true` if the `str` ends with `substr`,\n`false` otherwise.\n\nSee [`String.endsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.endsWith(\"ReScript\", \"Script\") == true\nJs.String2.endsWith(\"C++\", \"Script\") == false\n```\n"}, - "sortText": "endsWith", - "insertText": "->Js.String2.endsWith", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.normalize", - "kind": 12, - "tags": [], - "detail": "t => t", - "documentation": {"kind": "markdown", "value": "\n`normalize(str)` returns the normalized Unicode string using Normalization Form\nCanonical (NFC) Composition. Consider the character ã, which can be represented\nas the single codepoint \\u00e3 or the combination of a lower case letter A\n\\u0061 and a combining tilde \\u0303. Normalization ensures that both can be\nstored in an equivalent binary representation.\n\nSee [`String.normalize`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)\non MDN. See also [Unicode technical report\n#15](https://unicode.org/reports/tr15/) for details.\n"}, - "sortText": "normalize", - "insertText": "->Js.String2.normalize", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.get", - "kind": 12, - "tags": [], - "detail": "(t, int) => t", - "documentation": {"kind": "markdown", "value": "\n`get(s, n)` returns as a `string` the character at the given index number. If\n`n` is out of range, this function returns `undefined`,so at some point this\nfunction may be modified to return `option`.\n\n## Examples\n\n```rescript\nJs.String2.get(\"Reason\", 0) == \"R\"\nJs.String2.get(\"Reason\", 4) == \"o\"\nJs.String2.get(`Rẽasöń`, 5) == `ń`\n```\n"}, - "sortText": "get", - "insertText": "->Js.String2.get", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "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", + "documentation": null, + "sortText": "a", + "insertText": "->M.a", "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.toUpperCase", - "kind": 12, - "tags": [], - "detail": "t => t", - "documentation": {"kind": "markdown", "value": "\n`toUpperCase(str)` converts `str` to upper case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\nexpand the number of letters in the result; for example the German ß\ncapitalizes to two Ses in a row.\n\nSee [`String.toUpperCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toUpperCase(\"abc\") == \"ABC\"\nJs.String2.toUpperCase(`Straße`) == `STRASSE`\nJs.String2.toUpperCase(`πς`) == `ΠΣ`\n```\n"}, - "sortText": "toUpperCase", - "insertText": "->Js.String2.toUpperCase", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.castToArrayLike", - "kind": 12, - "tags": [], - "detail": "t => Js_array2.array_like", - "documentation": {"kind": "markdown", "value": "\nCasts its argument to an `array_like` entity that can be processed by functions\nsuch as `Js.Array2.fromMap()`\n\n## Examples\n\n```rescript\nlet s = \"abcde\"\nlet arr = Js.Array2.fromMap(Js.String2.castToArrayLike(s), x => x)\narr == [\"a\", \"b\", \"c\", \"d\", \"e\"]\n```\n"}, - "sortText": "castToArrayLike", - "insertText": "->Js.String2.castToArrayLike", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.unsafeReplaceBy2", - "kind": 12, - "tags": [], - "detail": "(t, Js_re.t, (t, t, t, int, t) => t) => t", - "documentation": {"kind": "markdown", "value": "\nReturns a new `string` with some or all matches of a pattern with two sets of\ncapturing parentheses replaced by the value returned from the given function.\nThe function receives as its parameters the matched string, the captured\nstrings, the offset at which the match begins, and the whole string being\nmatched.\n\nSee [`String.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)\non MDN.\n\n## Examples\n\n```rescript\nlet str = \"7 times 6\"\nlet re = /(\\d+) times (\\d+)/\nlet matchFn = (_match, p1, p2, _offset, _wholeString) => {\n switch (Belt.Int.fromString(p1), Belt.Int.fromString(p2)) {\n | (Some(x), Some(y)) => Belt.Int.toString(x * y)\n | _ => \"???\"\n }\n}\n\nJs.String2.unsafeReplaceBy2(str, re, matchFn) == \"42\"\n```\n"}, - "sortText": "unsafeReplaceBy2", - "insertText": "->Js.String2.unsafeReplaceBy2", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.toLocaleUpperCase", - "kind": 12, - "tags": [], - "detail": "t => t", - "documentation": {"kind": "markdown", "value": "\n`toLocaleUpperCase(str)` converts `str` to upper case using the current locale.\nSee [`String.to:LocaleUpperCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLocaleUpperCase)\non MDN.\n"}, - "sortText": "toLocaleUpperCase", - "insertText": "->Js.String2.toLocaleUpperCase", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, - "newText": "" - }] - }, { - "label": "->Js.String2.indexOfFrom", - "kind": 12, - "tags": [], - "detail": "(t, t, int) => int", - "documentation": {"kind": "markdown", "value": "\n`indexOfFrom(str, searchValue, start)` returns the position at which\n`searchValue` was found within `str` starting at character position `start`, or\n-1 if `searchValue` is not found in that portion of `str`. The return value is\nrelative to the beginning of the string, no matter where the search started\nfrom.\n\nSee [`String.indexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.indexOfFrom(\"bookseller\", \"ok\", 1) == 2\nJs.String2.indexOfFrom(\"bookseller\", \"sell\", 2) == 4\nJs.String2.indexOfFrom(\"bookseller\", \"sell\", 5) == -1\n```\n"}, - "sortText": "indexOfFrom", - "insertText": "->Js.String2.indexOfFrom", - "additionalTextEdits": [{ - "range": {"start": {"line": 3, "character": 19}, "end": {"line": 3, "character": 20}}, + "range": {"start": {"line": 11, "character": 19}, "end": {"line": 11, "character": 20}}, "newText": "" }] }] -Complete src/CompletionTaggedTemplate.res 6:23 -posCursor:[6:23] posNoWhite:[6:22] Found expr:[6:11->6:23] -Completable: Cpath Value[meh](Nolabel, Nolabel).len +Complete src/CompletionTaggedTemplate.res 14:21 +posCursor:[14:21] posNoWhite:[14:20] Found expr:[14:11->14:21] +Completable: Cpath Value[meh](Nolabel, Nolabel).x Package opens Pervasives.JsxModules.place holder -ContextPath Value[meh](Nolabel, Nolabel).len +ContextPath Value[meh](Nolabel, Nolabel).x ContextPath Value[meh](Nolabel, Nolabel) ContextPath Value[meh] Path meh -ContextPath Value[meh](Nolabel, Nolabel, Nolabel)->len +ContextPath Value[meh](Nolabel, Nolabel, Nolabel)->x ContextPath Value[meh](Nolabel, Nolabel, Nolabel) ContextPath Value[meh] Path meh -Path Js.String2.len +CPPipe pathFromEnv:M found:true +Path M.x [{ - "label": "->Js.String2.length", + "label": "->M.xyz", "kind": 12, "tags": [], - "detail": "t => int", - "documentation": {"kind": "markdown", "value": "\n`length(s)` returns the length of the given `string`.\n\nSee [`String.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.length(\"abcd\") == 4\n```\n"}, - "sortText": "length", - "insertText": "->Js.String2.length", + "detail": "(t, int) => int", + "documentation": null, + "sortText": "xyz", + "insertText": "->M.xyz", "additionalTextEdits": [{ - "range": {"start": {"line": 6, "character": 19}, "end": {"line": 6, "character": 20}}, + "range": {"start": {"line": 14, "character": 19}, "end": {"line": 14, "character": 20}}, "newText": "" }] }] From 500b9b782dae6b27bdfd6d916176d77fb3e7fbfc Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 6 Feb 2025 09:24:11 +0100 Subject: [PATCH 12/14] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ebdd91a9..182ac813ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Fix async context checking for module await. https://github.com/rescript-lang/rescript/pull/7271 - Fix `%external` extension. https://github.com/rescript-lang/rescript/pull/7272 +- Fix completion for application with tagged template. https://github.com/rescript-lang/rescript/pull/7278 # 12.0.0-alpha.8 From f145cb4dba6ad25bdc4bb95f6f9a4ce26af62094 Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 6 Feb 2025 10:05:33 +0100 Subject: [PATCH 13/14] Split up cases in frontend. --- analysis/src/CompletionFrontEnd.ml | 49 +++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index e00ffd3e14..b6ee3c7164 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1128,31 +1128,52 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor so the apply expression does not include the cursor *) if setPipeResult ~lhs ~id:"" then setFound () (* - A dot completion for a tagged templated application. + A dot completion for a tagged templated application with an expr hole. Example: sh`echo "meh"`. - or - sh`bar`.len *) | Pexp_apply { funct = {pexp_desc = Pexp_ident {txt = Lident "."; loc = _}}; args = - [(_, ({pexp_desc = Pexp_apply _} as innerExpr)); (_, fieldExpr)]; + [ + (* sh`echo "meh"` *) + (_, ({pexp_desc = Pexp_apply _} as innerExpr)); + (* recovery inserted node *) + (_, {pexp_desc = Pexp_extension ({txt = "rescript.exprhole"}, _)}); + ]; + } + when Res_parsetree_viewer.is_tagged_template_literal innerExpr -> + exprToContextPath innerExpr + |> Option.iter (fun cpath -> + setResult + (Cpath + (CPField + { + contextPath = cpath; + fieldName = ""; + posOfDot; + exprLoc = expr.pexp_loc; + })); + setFound ()) + (* + A dot completion for a tagged templated application with an ident. + Example: + sh`echo "meh"`.foo + *) + | Pexp_apply + { + funct = {pexp_desc = Pexp_ident {txt = Lident "."; loc = _}}; + args = + [ + (_, ({pexp_desc = Pexp_apply _} as innerExpr)); + (_, {pexp_desc = Pexp_ident {txt = Lident fieldName}}); + ]; } when Res_parsetree_viewer.is_tagged_template_literal innerExpr - && expr.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor - || CompletionExpressions.isExprHole fieldExpr -> + && expr.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor -> exprToContextPath innerExpr |> Option.iter (fun cpath -> - (* Determine the field name if present *) - let fieldName = - match fieldExpr.pexp_desc with - | Pexp_ident {txt = Lident fieldName} -> fieldName - (* This is likely to be an exprhole *) - | _ -> "" - in - setResult (Cpath (CPField From 375dffb2db5dca2b26a0581c0f07f8d63bf132b6 Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 6 Feb 2025 10:06:34 +0100 Subject: [PATCH 14/14] Add example --- analysis/src/CompletionFrontEnd.ml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index b6ee3c7164..661af008d3 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1156,7 +1156,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor exprLoc = expr.pexp_loc; })); setFound ()) - (* + (* A dot completion for a tagged templated application with an ident. Example: sh`echo "meh"`.foo @@ -1166,7 +1166,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor funct = {pexp_desc = Pexp_ident {txt = Lident "."; loc = _}}; args = [ + (* sh`echo "meh"` *) (_, ({pexp_desc = Pexp_apply _} as innerExpr)); + (* foo *) (_, {pexp_desc = Pexp_ident {txt = Lident fieldName}}); ]; }