From 3883f4e8e86aa929f6367c38116d5589769eb22a Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 20 Nov 2025 14:12:45 +0100 Subject: [PATCH 1/2] Pass RESCRIPT_RUNTIME env to analysis process --- server/src/bsc-args/rewatch.ts | 36 +-------- server/src/incrementalCompilation.ts | 43 +++++----- server/src/utils.ts | 114 +++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 58 deletions(-) diff --git a/server/src/bsc-args/rewatch.ts b/server/src/bsc-args/rewatch.ts index c4ac3528d..e4dc4ab60 100644 --- a/server/src/bsc-args/rewatch.ts +++ b/server/src/bsc-args/rewatch.ts @@ -8,8 +8,6 @@ import { IncrementallyCompiledFileInfo, } from "../incrementalCompilation"; import type { projectFiles } from "../projectFiles"; -import config from "../config"; -import { findRescriptRuntimesInProject } from "../find-runtime"; import { jsonrpcVersion } from "../constants"; export type RewatchCompilerArgs = { @@ -20,39 +18,7 @@ export type RewatchCompilerArgs = { async function getRuntimePath( entry: IncrementallyCompiledFileInfo, ): Promise { - let rescriptRuntime: string | null = - config.extensionConfiguration.runtimePath ?? null; - - if (rescriptRuntime !== null) { - if (debug()) { - console.log( - `Using configured runtime path as RESCRIPT_RUNTIME: ${rescriptRuntime}`, - ); - } - return rescriptRuntime; - } - - const rescriptRuntimes = await findRescriptRuntimesInProject( - entry.project.workspaceRootPath, - ); - - if (debug()) { - if (rescriptRuntimes.length === 0) { - console.log( - `Did not find @rescript/runtime directory for ${entry.project.workspaceRootPath}`, - ); - } else if (rescriptRuntimes.length > 1) { - console.warn( - `Found multiple @rescript/runtime directories, using the first one as RESCRIPT_RUNTIME: ${rescriptRuntimes.join(", ")}`, - ); - } else { - console.log( - `Found @rescript/runtime directory: ${rescriptRuntimes.join(", ")}`, - ); - } - } - - return rescriptRuntimes.at(0) ?? null; + return utils.getRuntimePathFromWorkspaceRoot(entry.project.workspaceRootPath); } export async function getRewatchBscArgs( diff --git a/server/src/incrementalCompilation.ts b/server/src/incrementalCompilation.ts index 4b90178f4..ee1b82ba3 100644 --- a/server/src/incrementalCompilation.ts +++ b/server/src/incrementalCompilation.ts @@ -262,33 +262,30 @@ function triggerIncrementalCompilationOfFile( return; } - const projectRewatchLockfiles = [ - ...Array.from(workspaceFolders).map((w) => - path.resolve(w, c.rewatchLockPartialPath), - ), - ...Array.from(workspaceFolders).map((w) => - path.resolve(w, c.rescriptLockPartialPath), - ), - path.resolve(projectRootPath, c.rewatchLockPartialPath), - path.resolve(projectRootPath, c.rescriptLockPartialPath), - ]; - - let foundRewatchLockfileInProjectRoot = false; - if (projectRewatchLockfiles.some((lockFile) => fs.existsSync(lockFile))) { - foundRewatchLockfileInProjectRoot = true; - } else if (debug()) { + // computeWorkspaceRootPathFromLockfile returns null if lockfile found (local package) or if no parent found + const computedWorkspaceRoot = + utils.computeWorkspaceRootPathFromLockfile(projectRootPath); + // If null, it means either a lockfile was found (local package) or no parent project root exists + // In both cases, we default to projectRootPath + const workspaceRootPath = computedWorkspaceRoot ?? projectRootPath; + + // Determine if lockfile was found for debug logging + // If computedWorkspaceRoot is null and projectRootPath is not null, check if parent exists + const foundRewatchLockfileInProjectRoot = + computedWorkspaceRoot == null && + projectRootPath != null && + utils.findProjectRootOfFile(projectRootPath, true) != null; + + if (foundRewatchLockfileInProjectRoot && debug()) { console.log( - `Did not find ${projectRewatchLockfiles.join(" or ")} in project root, assuming bsb`, + `Found rewatch/rescript lockfile in project root, treating as local package in workspace`, + ); + } else if (!foundRewatchLockfileInProjectRoot && debug()) { + console.log( + `Did not find rewatch/rescript lockfile in project root, assuming bsb`, ); } - // if we find a rewatch.lock in the project root, it's a compilation of a local package - // in the workspace. - const workspaceRootPath = - projectRootPath && !foundRewatchLockfileInProjectRoot - ? utils.findProjectRootOfFile(projectRootPath, true) - : null; - const bscBinaryLocation = project.bscBinaryLocation; if (bscBinaryLocation == null) { if (debug()) diff --git a/server/src/utils.ts b/server/src/utils.ts index c64dbcc57..ac0d346a7 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -17,6 +17,9 @@ import * as lookup from "./lookup"; import { reportError } from "./errorReporter"; import config from "./config"; import { filesDiagnostics, projectsFiles } from "./projectFiles"; +import { workspaceFolders } from "./server"; +import { rewatchLockPartialPath, rescriptLockPartialPath } from "./constants"; +import { findRescriptRuntimesInProject } from "./find-runtime"; let tempFilePrefix = "rescript_format_file_" + process.pid + "_"; let tempFileId = 0; @@ -301,6 +304,12 @@ export let runAnalysisAfterSanityCheck = async ( binaryPath = builtinBinaryPath; } + let runtime: string | undefined = undefined; + if (semver.gt(rescriptVersion as string, "12.0.0-rc.1")) { + const runtimePath = await getRuntimePathFromProjectRoot(projectRootPath); + runtime = runtimePath ?? undefined; + } + let options: childProcess.ExecFileSyncOptions = { cwd: projectRootPath || undefined, maxBuffer: Infinity, @@ -315,6 +324,7 @@ export let runAnalysisAfterSanityCheck = async ( config.extensionConfiguration.cache?.projectConfig?.enable === true ? "true" : undefined, + RESCRIPT_RUNTIME: runtime, }, }; @@ -372,6 +382,110 @@ export const toCamelCase = (text: string): string => { .replace(/(\s|-)+/g, ""); }; +/** + * Computes the workspace root path from a project root path by checking for rewatch/rescript lockfiles. + * In a monorepo, this finds the parent project root that contains the workspace. + * If a rewatch/rescript lockfile is found in the project root, it's a local package + * in the workspace, so we return null (which will default to projectRootPath). + */ +export function computeWorkspaceRootPathFromLockfile( + projectRootPath: string | null, +): string | null { + if (projectRootPath == null) { + return null; + } + + const projectRewatchLockfiles = [ + ...Array.from(workspaceFolders).map((w) => + path.resolve(w, rewatchLockPartialPath), + ), + ...Array.from(workspaceFolders).map((w) => + path.resolve(w, rescriptLockPartialPath), + ), + path.resolve(projectRootPath, rewatchLockPartialPath), + path.resolve(projectRootPath, rescriptLockPartialPath), + ]; + + const foundRewatchLockfileInProjectRoot = projectRewatchLockfiles.some( + (lockFile) => fs.existsSync(lockFile), + ); + + // if we find a rewatch.lock in the project root, it's a compilation of a local package + // in the workspace. + return !foundRewatchLockfileInProjectRoot + ? findProjectRootOfFile(projectRootPath, true) + : null; +} + +// Shared cache: key is either workspace root path or project root path +const runtimePathCache = new Map(); + +/** + * Gets the runtime path from a workspace root path. + * This function is cached per workspace root path. + */ +export async function getRuntimePathFromWorkspaceRoot( + workspaceRootPath: string, +): Promise { + // Check cache first + if (runtimePathCache.has(workspaceRootPath)) { + return runtimePathCache.get(workspaceRootPath)!; + } + + // Compute and cache + let rescriptRuntime: string | null = + config.extensionConfiguration.runtimePath ?? null; + + if (rescriptRuntime !== null) { + runtimePathCache.set(workspaceRootPath, rescriptRuntime); + return rescriptRuntime; + } + + const rescriptRuntimes = + await findRescriptRuntimesInProject(workspaceRootPath); + + const result = rescriptRuntimes.at(0) ?? null; + runtimePathCache.set(workspaceRootPath, result); + return result; +} + +/** + * Gets the runtime path from a project root path. + * Computes the workspace root path and then resolves the runtime. + * This function is cached per project root path. + */ +export async function getRuntimePathFromProjectRoot( + projectRootPath: string | null, +): Promise { + if (projectRootPath == null) { + return null; + } + + // Check cache first (keyed by projectRootPath) + if (runtimePathCache.has(projectRootPath)) { + return runtimePathCache.get(projectRootPath)!; + } + + // Compute workspace root and resolve runtime + const workspaceRootPath = + computeWorkspaceRootPathFromLockfile(projectRootPath) ?? projectRootPath; + + // Check cache again with workspace root (might have been cached from a previous call) + if (runtimePathCache.has(workspaceRootPath)) { + const result = runtimePathCache.get(workspaceRootPath)!; + // Cache it under projectRootPath too for faster lookup next time + runtimePathCache.set(projectRootPath, result); + return result; + } + + // Compute and cache + const result = await getRuntimePathFromWorkspaceRoot(workspaceRootPath); + // Cache it under both keys + runtimePathCache.set(workspaceRootPath, result); + runtimePathCache.set(projectRootPath, result); + return result; +} + export const getNamespaceNameFromConfigFile = ( projDir: p.DocumentUri, ): execResult => { From 1271e0d69c1adc35c2c71890dc4e10cd5c28d87f Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 20 Nov 2025 14:32:15 +0100 Subject: [PATCH 2/2] change log --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aba093d1..1d9c0007e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ - Paste as JSON.t or ReScript JSX in VSCode. https://github.com/rescript-lang/rescript-vscode/pull/1141 +#### :bug: Bug fix + +- Pass RESCRIPT_RUNTIME to analysis process. https://github.com/rescript-lang/rescript-vscode/pull/1145 + ## 1.66.0 #### :bug: Bug fix