From c51701d78ac77674c76078f26f6e8a17254b1f55 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 20 Nov 2022 10:15:54 +0100 Subject: [PATCH] Route aware tools (#115) * code lens in ReScript files detailing how many routes the file is referenced in * changeset * support custom LSP command for returning routes for a file * fix wording * fix error in code lens path order --- .changeset/quick-rings-shake.md | 5 + .changeset/silver-lemons-cry.md | 5 + .../cli/RescriptRelayRouterCli__Bindings.res | 38 ++++ .../cli/RescriptRelayRouterCli__Bindings.resi | 30 +++ .../cli/RescriptRelayRouterCli__Commands.res | 8 +- .../cli/RescriptRelayRouterCli__Parser.res | 2 - .../cli/RescriptRelayRouterCli__Types.res | 14 +- .../cli/RescriptRelayRouterCli__Utils.res | 11 + .../rescript-relay-router/cli/lsp/Lsp.res | 203 +++++++++++++++++- .../cli/lsp/LspProtocol.res | 16 ++ .../RescriptRelayRouterLsp__DepsReader.res | 110 ++++++++++ .../lsp/RescriptRelayRouterLsp__Resolvers.res | 40 +--- .../cli/lsp/RescriptRelayRouterLsp__Utils.res | 25 +++ 13 files changed, 458 insertions(+), 49 deletions(-) create mode 100644 .changeset/quick-rings-shake.md create mode 100644 .changeset/silver-lemons-cry.md create mode 100644 packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__DepsReader.res create mode 100644 packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__Utils.res diff --git a/.changeset/quick-rings-shake.md b/.changeset/quick-rings-shake.md new file mode 100644 index 0000000..9938dc6 --- /dev/null +++ b/.changeset/quick-rings-shake.md @@ -0,0 +1,5 @@ +--- +"rescript-relay-router": patch +--- + +LSP: Code lens in ReScript files detailing how many routes the file is referenced in. diff --git a/.changeset/silver-lemons-cry.md b/.changeset/silver-lemons-cry.md new file mode 100644 index 0000000..1e8b946 --- /dev/null +++ b/.changeset/silver-lemons-cry.md @@ -0,0 +1,5 @@ +--- +"rescript-relay-router": patch +--- + +LSP: Support custom LSP command for returning routes for a file. diff --git a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Bindings.res b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Bindings.res index 8d2ee55..5c5d885 100644 --- a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Bindings.res +++ b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Bindings.res @@ -12,6 +12,9 @@ module Chokidar = { @send external onUnlink: (t, @as(json`"unlink"`) _, string => unit) => t = "on" + @send + external onAdd: (t, @as(json`"add"`) _, string => unit) => t = "on" + @send external close: t => Promise.t = "close" } @@ -38,6 +41,12 @@ module Path = { @module("path") external extname: string => string = "extname" + + @live + type parsed = {name: string} + + @module("path") + external parse: string => parsed = "parse" } module Node = { @@ -115,6 +124,33 @@ module Fs = { } } +module ChildProcess = { + module Spawn = { + type stdout + type stderr + @live + type t = {stdout: stdout, stderr: stderr} + + @live + type opts = { + shell?: bool, + cwd?: string, + stdio?: [#inherit | #pipe], + } + + type buffer + @send external bufferToString: buffer => string = "toString" + + @module("child_process") + external make: (string, array, opts) => t = "spawn" + + @send external onStdoutData: (stdout, @as(json`"data"`) _, buffer => unit) => unit = "on" + @send external onClose: (t, @as(json`"close"`) _, int => unit) => unit = "on" + + let onData = (t, cb) => t.stdout->onStdoutData(buffer => buffer->bufferToString->cb) + } +} + module URL = { type t @@ -169,3 +205,5 @@ module LinesAndColumns = { @send external offsetForLocation: (t, loc) => int = "indexForLocation" } + +@val @module("os") external osEOL: string = "EOL" diff --git a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Bindings.resi b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Bindings.resi index 5b15cb6..c3f5fd4 100644 --- a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Bindings.resi +++ b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Bindings.resi @@ -11,6 +11,9 @@ module Chokidar: { @send external onUnlink: (t, @as(json`"unlink"`) _, string => unit) => t = "on" + @send + external onAdd: (t, @as(json`"add"`) _, string => unit) => t = "on" + @send external close: t => Js.Promise.t = "close" } @@ -36,6 +39,11 @@ module Path: { @module("path") external extname: string => string = "extname" + + type parsed = {name: string} + + @module("path") + external parse: string => parsed = "parse" } module Node: { @@ -88,6 +96,26 @@ module Fs: { let writeFileIfChanged: (string, string) => unit } +module ChildProcess: { + module Spawn: { + type t + + @live + type opts = { + shell?: bool, + cwd?: string, + stdio?: [#inherit | #pipe], + } + + @module("child_process") + external make: (string, array, opts) => t = "spawn" + + @send external onClose: (t, @as(json`"close"`) _, int => unit) => unit = "on" + + let onData: (t, string => unit) => unit + } +} + module URL: { type t @@ -139,3 +167,5 @@ module LinesAndColumns: { @send external offsetForLocation: (t, loc) => int = "indexForLocation" } + +@val @module("os") external osEOL: string = "EOL" diff --git a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Commands.res b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Commands.res index 613acf0..10097a3 100644 --- a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Commands.res +++ b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Commands.res @@ -299,7 +299,7 @@ let init = () => { Console.log("[init] Done! You can now run the `generate -scaffold-renderers` command.") } -type cliResult = Done | Watcher({watcher: Chokidar.Watcher.t}) +type cliResult = Done | Watcher({watchers: array}) let runCli = args => { switch args->List.fromArray { @@ -379,7 +379,7 @@ let runCli = args => { ->Watcher.onUnlink(_ => { generateRoutesSafe() }) - Watcher({watcher: theWatcher}) + Watcher({watchers: [theWatcher]}) } else { Done } @@ -400,8 +400,8 @@ let runCli = args => { } else { NodeRpc } - let watcher = Lsp.start(~config, ~mode) - Watcher({watcher: watcher}) + let watchers = Lsp.start(~config, ~mode) + Watcher({watchers: watchers}) | _ => Console.log("Unknown command. Use -help or -h to see all available commands.") Done diff --git a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Parser.res b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Parser.res index 969a970..0fd3a02 100644 --- a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Parser.res +++ b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Parser.res @@ -528,8 +528,6 @@ module Path = { } } -let f = Belt.Array.concatMany - module Validators = { open! JsoncParser diff --git a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Types.res b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Types.res index 3d89292..4bcb104 100644 --- a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Types.res +++ b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Types.res @@ -93,7 +93,8 @@ module RoutePath: { } let getPathSegment = t => t.pathSegment - let getFullRoutePath = t => "/" ++ t.currentRoutePath->List.toArray->Array.joinWith("/") + let getFullRoutePath = t => + "/" ++ t.currentRoutePath->List.toArray->Array.reverse->Array.joinWith("/") let toPattern = t => "/" ++ @@ -207,4 +208,15 @@ type rec routeForCliMatching = { type config = { generatedPath: string, routesFolderPath: string, + rescriptLibFolderPath: string, +} + +type dependencyDeclaration = { + dependsOn: Set.t, + dependents: Set.t, +} + +type moduleDepsCache = { + mutable cache: Dict.t, + mutable compilerLastRebuilt: float, } diff --git a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Utils.res b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Utils.res index 6f3ec36..5fe1a2c 100644 --- a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Utils.res +++ b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Utils.res @@ -34,6 +34,10 @@ module Config: { ->Option.getWithDefault(Path.join([routesFolderPath, "__generated__"])) ->resolveFullPath, routesFolderPath, + // TODO: This won't work for monorepo setups etc where the ReScript + // lib dir isn't at the same level as the router config file. Fix + // eventually. + rescriptLibFolderPath: Path.join([Path.dirname(filepath), "lib", "bs"]), } } @@ -294,3 +298,10 @@ let queryParamToQueryParamDecoder = (param, ~key) => { )}),\n` } } + +let maybePluralize = (text, ~count) => + text ++ if count == 1 { + "" + } else { + "s" + } diff --git a/packages/rescript-relay-router/cli/lsp/Lsp.res b/packages/rescript-relay-router/cli/lsp/Lsp.res index ec08bbc..1013f39 100644 --- a/packages/rescript-relay-router/cli/lsp/Lsp.res +++ b/packages/rescript-relay-router/cli/lsp/Lsp.res @@ -6,12 +6,13 @@ let dummyPos: LspProtocol.loc = { character: -1, } -@val -external log: 'any => unit = "console.error" +let log = Console.error module Bindings = RescriptRelayRouterCli__Bindings module Utils = RescriptRelayRouterCli__Utils module Resolvers = RescriptRelayRouterLsp__Resolvers +module DepsReader = RescriptRelayRouterLsp__DepsReader +module LspUtils = RescriptRelayRouterLsp__Utils open RescriptRelayRouterCli__Types @@ -32,6 +33,7 @@ module Message = { | #"textDocument/documentLink" | #"textDocument/completion" | #"textDocument/codeAction" + | #"textDocument/rescriptRelayRouterRoutes" ] as 'a let jsonrpcVersion = "2.0" @@ -91,6 +93,7 @@ module Message = { | DocumentLinks(documentLinkParams) | Completion(completionParams) | CodeAction(codeActionParams) + | RescriptRelayRouterRoutes(string) | UnmappedMessage let decodeLspMessage = (msg: msg): t => { @@ -103,6 +106,7 @@ module Message = { | #"textDocument/documentLink" => DocumentLinks(msg->unsafeGetParams) | #"textDocument/completion" => Completion(msg->unsafeGetParams) | #"textDocument/codeAction" => CodeAction(msg->unsafeGetParams) + | #"textDocument/rescriptRelayRouterRoutes" => RescriptRelayRouterRoutes(msg->unsafeGetParams) | _ => UnmappedMessage } } @@ -231,6 +235,7 @@ module Message = { external fromDocumentLinks: array => t = "%identity" external fromCompletionItems: array => t = "%identity" external fromCodeActions: array => t = "%identity" + external fromRoutesForFile: array => t = "%identity" let null: unit => t } = { type t @@ -241,6 +246,7 @@ module Message = { external fromDocumentLinks: array => t = "%identity" external fromCompletionItems: array => t = "%identity" external fromCodeActions: array => t = "%identity" + external fromRoutesForFile: array => t = "%identity" let null = () => Nullable.null->fromAny } @@ -370,6 +376,12 @@ let start = (~mode, ~config: config) => { let routeFilesCaches: Dict.t = Dict.empty() let routeRenderersCache: Dict.t = Dict.empty() + // Holds the module graph for ReScript modules + let moduleDepsCache = { + cache: Dict.empty(), + compilerLastRebuilt: 0., + } + let getRouteFileContents = fileName => { switch routeFilesCaches->Dict.get(fileName) { | Some(contents) => Ok(contents) @@ -435,6 +447,59 @@ let start = (~mode, ~config: config) => { publishDiagnostics(lspResolveContext) } + let rebuildingDepsCachePromise = ref(None) + + let doRebuildDepsCacheIfNeeded = async () => { + let config = getCurrentLspContext()->CurrentContext.getConfig + let currentLastBuiltAt = moduleDepsCache.compilerLastRebuilt + let lastBuiltAt = DepsReader.getLastBuiltFromCompilerLog(~config)->Option.getWithDefault(0.) + + if lastBuiltAt > currentLastBuiltAt { + switch await DepsReader.readDeps(~config) { + | Ok(depsByModuleNames) => + moduleDepsCache.cache = depsByModuleNames + moduleDepsCache.compilerLastRebuilt = lastBuiltAt + Ok(moduleDepsCache) + | Error(err) => Error(err) + } + } else { + Ok(moduleDepsCache) + } + } + + let getFreshModuleDepsCache = () => { + switch rebuildingDepsCachePromise.contents { + | None => + rebuildingDepsCachePromise.contents = Some( + doRebuildDepsCacheIfNeeded()->Promise.thenResolve(res => { + rebuildingDepsCachePromise.contents = None + res + }), + ) + | Some(_) => () + } + rebuildingDepsCachePromise.contents + } + + let isRouteRenderer = moduleName => moduleName->String.endsWith("_route_renderer") + + let rec findRoutesForFile = (moduleName, ~moduleDepsCache, ~foundRoutes) => { + switch moduleDepsCache.cache->Dict.get(moduleName) { + | None => () + | Some({dependents}) => + dependents->Set.forEach(mName => { + if isRouteRenderer(mName) { + let _: Set.t<_> = + foundRoutes->Set.add( + mName->String.slice(~start=0, ~end="_route_renderer"->String.length * -1), + ) + } else { + findRoutesForFile(mName, ~moduleDepsCache, ~foundRoutes) + } + }) + } + } + let openedFile = (uri, text) => { let key = uri->Bindings.Path.basename @@ -473,7 +538,7 @@ let start = (~mode, ~config: config) => { rebuildLspResolveContext() } - let theWatcher = + let routeFilesWatcher = Bindings.Chokidar.watcher ->Bindings.Chokidar.watch(Utils.pathInRoutesFolder(~config, ~fileName="*.json", ())) ->Bindings.Chokidar.Watcher.onChange(_ => { @@ -567,7 +632,7 @@ let start = (~mode, ~config: config) => { ->send } else { shutdownRequestAlreadyReceived := true - theWatcher->Bindings.Chokidar.Watcher.close->Promise.done + routeFilesWatcher->Bindings.Chokidar.Watcher.close->Promise.done Message.Response.make(~id=msg->Message.getId, ~result=Message.Result.null(), ()) ->Message.Response.asMessage ->send @@ -631,7 +696,82 @@ let start = (~mode, ~config: config) => { ->Message.Response.asMessage ->send } else { - () + switch getFreshModuleDepsCache() { + | None => () + | Some(promise) => + promise + ->Promise.thenResolve(res => { + switch res { + | Error(_) => () + | Ok(moduleDepsCache) => + let thisModuleName = (fileName->Bindings.Path.parse).name + let foundRoutes = Set.make() + findRoutesForFile(thisModuleName, ~foundRoutes, ~moduleDepsCache) + if foundRoutes->Set.size > 0 { + let result = [ + LspProtocol.makeCodeLensItem( + ~range={ + start: {line: 0, character: 0}, + end_: {line: 0, character: 0}, + }, + ~command=LspProtocol.Command.makeTextOnlyCommand( + `RescriptRelayRouter: Referenced in ${foundRoutes + ->Set.size + ->Int.toString} ${Utils.maybePluralize( + "route", + ~count=foundRoutes->Set.size, + )}`, + ), + ), + LspProtocol.makeCodeLensItem( + ~range={ + start: {line: 0, character: 0}, + end_: {line: 0, character: 0}, + }, + ~command=LspProtocol.Command.makeOpenRouteDefinitionsCommand( + ~title=`Open definition for ${if foundRoutes->Set.size > 1 { + "routes" + } else { + "route " ++ + foundRoutes->Set.values->Array.fromIterator->Array.getUnsafe(0) + }}`, + ~routes=foundRoutes + ->Set.values + ->Array.fromIterator + ->Array.filterMap(routeName => + switch routeName->LspUtils.findRouteWithName( + ~routeChildren=( + ctx->CurrentContext.getCurrentRouteStructure + ).result, + ) { + | None => None + | Some(routeEntry) => + Some({ + LspProtocol.Command.sourceFilePath: Utils.pathInRoutesFolder( + ~fileName=routeEntry.sourceFile, + ~config=ctx->CurrentContext.getConfig, + (), + ), + routeName, + loc: { + line: routeEntry.loc.start.line, + character: routeEntry.loc.start.column, + }, + }) + } + ), + ), + ), + ]->Message.Result.fromCodeLenses + + Message.Response.make(~id=msg->Message.getId, ~result, ()) + ->Message.Response.asMessage + ->send + } + } + }) + ->Promise.done + } } | ".json" => @@ -654,6 +794,53 @@ let start = (~mode, ~config: config) => { ->send | _ => () } + + | RescriptRelayRouterRoutes(thisModuleName) => + switch getFreshModuleDepsCache() { + | None => () + | Some(promise) => + promise + ->Promise.thenResolve(res => { + switch res { + | Error(_) => () + | Ok(moduleDepsCache) => + let foundRoutes = Set.make() + findRoutesForFile(thisModuleName, ~foundRoutes, ~moduleDepsCache) + if foundRoutes->Set.size > 0 { + let result = + foundRoutes + ->Set.values + ->Array.fromIterator + ->Array.filterMap(routeName => + switch routeName->LspUtils.findRouteWithName( + ~routeChildren=(ctx->CurrentContext.getCurrentRouteStructure).result, + ) { + | None => None + | Some(routeEntry) => + Some({ + LspProtocol.Command.sourceFilePath: Utils.pathInRoutesFolder( + ~fileName=routeEntry.sourceFile, + ~config=ctx->CurrentContext.getConfig, + (), + ), + routeName, + loc: { + line: routeEntry.loc.start.line, + character: routeEntry.loc.start.column, + }, + }) + } + ) + ->Message.Result.fromRoutesForFile + + Message.Response.make(~id=msg->Message.getId, ~result, ()) + ->Message.Response.asMessage + ->send + } + } + }) + ->Promise.done + } | DocumentLinks(params) => if params.textDocument.uri->Bindings.Path.extname == ".json" { let result = switch ctx @@ -712,9 +899,7 @@ let start = (~mode, ~config: config) => { }, ) { | None => Message.Result.null() - | Some(codeActions) => - log(codeActions) - Message.Result.fromCodeActions(codeActions) + | Some(codeActions) => Message.Result.fromCodeActions(codeActions) } Message.Response.make(~id=msg->Message.getId, ~result, ()) @@ -769,5 +954,5 @@ let start = (~mode, ~config: config) => { log(`Starting LSP in Node RPC.`) } - theWatcher + [routeFilesWatcher] } diff --git a/packages/rescript-relay-router/cli/lsp/LspProtocol.res b/packages/rescript-relay-router/cli/lsp/LspProtocol.res index ce56bff..45f9d90 100644 --- a/packages/rescript-relay-router/cli/lsp/LspProtocol.res +++ b/packages/rescript-relay-router/cli/lsp/LspProtocol.res @@ -214,6 +214,22 @@ module Command = { arguments: Some([fileUri, pos.line->Int.toString, pos.character->Int.toString]), } + type routeRendererReference = { + sourceFilePath: string, + routeName: string, + loc: loc, + } + + let makeOpenRouteDefinitionsCommand = (~title, ~routes) => { + title, + command: `vscode-rescript-relay.open-route-definitions`, + arguments: Some( + routes->Array.map(r => + `${r.sourceFilePath};${r.routeName};${r.loc.line->Int.toString};${r.loc.character->Int.toString}` + ), + ), + } + let makeTextOnlyCommand = title => { title, command: "", diff --git a/packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__DepsReader.res b/packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__DepsReader.res new file mode 100644 index 0000000..a9b744c --- /dev/null +++ b/packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__DepsReader.res @@ -0,0 +1,110 @@ +module Bindings = RescriptRelayRouterCli__Bindings +module Utils = RescriptRelayRouterCli__Utils +module Types = RescriptRelayRouterCli__Types + +let getLastBuiltFromCompilerLog = (~config: Utils.Config.t): option => { + let compilerLogConents = + Bindings.Fs.readFileSync( + Bindings.Path.resolve([config.rescriptLibFolderPath, ".compiler.log"]), + )->String.split(Bindings.osEOL) + + // The "Done" marker is on the second line from the bottom, if it exists. + let statusLine = + compilerLogConents[compilerLogConents->Array.length - 2]->Option.getWithDefault("") + + if statusLine->String.startsWith("#Done(") { + statusLine + ->String.split("#Done(") + ->Array.getUnsafe(1) + ->String.split(")") + ->Array.getUnsafe(0) + ->Float.fromString + } else { + None + } +} + +let readDeps = (~config: Utils.Config.t) => { + open Bindings.ChildProcess.Spawn + + Promise.make((resolve, _) => { + let byModuleNames = Dict.empty() + + let t = make( + "find", + [".", "-name", `"*.d"`, "-type", "f", "|", "xargs", "cat"], + {shell: true, cwd: config.rescriptLibFolderPath}, + ) + + t->onData(lines => { + lines + ->String.split(Bindings.osEOL) + ->Array.forEach( + line => { + let lineContents = line->String.split(" : ") + switch lineContents { + | [filenameRaw, depsLine] => + let currentTargetModule = (filenameRaw->String.trim->Bindings.Path.parse).name + + // Extract deps for line + let dependsOnTheseModules = + depsLine + ->String.split(" ") + ->Array.map(s => (s->String.trim->Bindings.Path.parse).name) + ->Array.reduce( + [], + (acc, curr) => { + if !(acc->Array.includes(curr) && curr != currentTargetModule) { + acc->Array.push(curr) + } + acc + }, + ) + + // Let's first add the files this depends on + switch byModuleNames->Dict.get(currentTargetModule) { + | None => + let entry = { + Types.dependents: Set.make(), + dependsOn: dependsOnTheseModules->Set.fromArray, + } + byModuleNames->Dict.set(currentTargetModule, entry) + | Some(existingEntry) => + dependsOnTheseModules->Array.forEach( + m => { + let _: Set.t<_> = existingEntry.dependsOn->Set.add(m) + }, + ) + } + + // Dependents + dependsOnTheseModules->Array.forEach( + m => { + switch byModuleNames->Dict.get(m) { + | None => + byModuleNames->Dict.set( + m, + {dependents: [currentTargetModule]->Set.fromArray, dependsOn: Set.make()}, + ) + | Some(existingEntry) => + let _: Set.t<_> = existingEntry.dependents->Set.add(currentTargetModule) + } + }, + ) + | _ => () + } + }, + ) + }) + + t->onClose(exitCode => { + resolve(. + if exitCode === 0 { + Ok(byModuleNames) + } else { + Error(`Failed with exit code: ${Int.toString(exitCode)}`) + }, + ) + }) + }) +} diff --git a/packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__Resolvers.res b/packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__Resolvers.res index 8606c4f..011aac3 100644 --- a/packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__Resolvers.res +++ b/packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__Resolvers.res @@ -1,9 +1,7 @@ open RescriptRelayRouterCli__Types module Bindings = RescriptRelayRouterCli__Bindings module Utils = RescriptRelayRouterCli__Utils - -@val -external log: 'any => unit = "console.error" +module LspUtils = RescriptRelayRouterLsp__Utils type lspResolveContext = { fileUri: string, @@ -278,11 +276,8 @@ let codeActions = (routeStructure: routeStructure, ~ctx: lspResolveContext): opt array, > => { switch routeStructure->findRequestContext(~ctx) { - | None => - log("oops, none") - None + | None => None | Some(astLocation) => - log(astLocation) switch astLocation { | RouteEntry({routeEntry: {parentRouteLoc: Some({childrenArray})}}) => Some([ @@ -308,9 +303,7 @@ let codeActions = (routeStructure: routeStructure, ~ctx: lspResolveContext): opt }), }, ]) - | _ => - log("no match") - None + | _ => None } } } @@ -394,30 +387,11 @@ let routeRendererCodeLens = ( // Now let's find the target route so we can open that file via the code // lens. - let routeName = - routeRendererFileName - ->String.split("_route_renderer.res") - ->Array.get(0) - ->Option.getWithDefault("") - - let matchingRouteEntry = ref(None) - - let rec findRouteWithName = (name, ~routeChildren) => { - routeChildren->Array.forEach(routeEntry => { - switch routeEntry { - | RouteEntry(routeEntry) if routeEntry.name->RouteName.getFullRouteName == routeName => - matchingRouteEntry := Some(routeEntry) - | RouteEntry({children: Some(children)}) - | Include({content: children}) => - name->findRouteWithName(~routeChildren=children) - | _ => () - } - }) - } - - routeName->findRouteWithName(~routeChildren=routeStructure.result) + let routeName = LspUtils.routeNameFromRouteRendererFileName(routeRendererFileName) - switch matchingRouteEntry.contents { + switch routeName->Belt.Option.flatMap(routeName => + routeName->LspUtils.findRouteWithName(~routeChildren=routeStructure.result) + ) { | None => () | Some(routeEntry) => foundRenderer := diff --git a/packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__Utils.res b/packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__Utils.res new file mode 100644 index 0000000..6a717d8 --- /dev/null +++ b/packages/rescript-relay-router/cli/lsp/RescriptRelayRouterLsp__Utils.res @@ -0,0 +1,25 @@ +open RescriptRelayRouterCli__Types + +let findRouteWithName = (routeName, ~routeChildren) => { + let res = ref(None) + + let rec searchForRouteWithName = (routeName, ~routeChildren) => { + routeChildren->Array.forEach(routeEntry => { + switch routeEntry { + | RouteEntry(routeEntry) if routeEntry.name->RouteName.getFullRouteName == routeName => + res := Some(routeEntry) + | RouteEntry({children: Some(children)}) + | Include({content: children}) => + routeName->searchForRouteWithName(~routeChildren=children) + | _ => () + } + }) + } + + routeName->searchForRouteWithName(~routeChildren) + + res.contents +} + +let routeNameFromRouteRendererFileName = routeRendererFileName => + routeRendererFileName->String.split("_route_renderer.res")->Array.get(0)