@@ -17,6 +17,9 @@ import * as lookup from "./lookup";
1717import { reportError } from "./errorReporter" ;
1818import config from "./config" ;
1919import { filesDiagnostics , projectsFiles } from "./projectFiles" ;
20+ import { workspaceFolders } from "./server" ;
21+ import { rewatchLockPartialPath , rescriptLockPartialPath } from "./constants" ;
22+ import { findRescriptRuntimesInProject } from "./find-runtime" ;
2023
2124let tempFilePrefix = "rescript_format_file_" + process . pid + "_" ;
2225let tempFileId = 0 ;
@@ -301,6 +304,12 @@ export let runAnalysisAfterSanityCheck = async (
301304 binaryPath = builtinBinaryPath ;
302305 }
303306
307+ let runtime : string | undefined = undefined ;
308+ if ( semver . gt ( rescriptVersion as string , "12.0.0-rc.1" ) ) {
309+ const runtimePath = await getRuntimePathFromProjectRoot ( projectRootPath ) ;
310+ runtime = runtimePath ?? undefined ;
311+ }
312+
304313 let options : childProcess . ExecFileSyncOptions = {
305314 cwd : projectRootPath || undefined ,
306315 maxBuffer : Infinity ,
@@ -315,6 +324,7 @@ export let runAnalysisAfterSanityCheck = async (
315324 config . extensionConfiguration . cache ?. projectConfig ?. enable === true
316325 ? "true"
317326 : undefined ,
327+ RESCRIPT_RUNTIME : runtime ,
318328 } ,
319329 } ;
320330
@@ -372,6 +382,110 @@ export const toCamelCase = (text: string): string => {
372382 . replace ( / ( \s | - ) + / g, "" ) ;
373383} ;
374384
385+ /**
386+ * Computes the workspace root path from a project root path by checking for rewatch/rescript lockfiles.
387+ * In a monorepo, this finds the parent project root that contains the workspace.
388+ * If a rewatch/rescript lockfile is found in the project root, it's a local package
389+ * in the workspace, so we return null (which will default to projectRootPath).
390+ */
391+ export function computeWorkspaceRootPathFromLockfile (
392+ projectRootPath : string | null ,
393+ ) : string | null {
394+ if ( projectRootPath == null ) {
395+ return null ;
396+ }
397+
398+ const projectRewatchLockfiles = [
399+ ...Array . from ( workspaceFolders ) . map ( ( w ) =>
400+ path . resolve ( w , rewatchLockPartialPath ) ,
401+ ) ,
402+ ...Array . from ( workspaceFolders ) . map ( ( w ) =>
403+ path . resolve ( w , rescriptLockPartialPath ) ,
404+ ) ,
405+ path . resolve ( projectRootPath , rewatchLockPartialPath ) ,
406+ path . resolve ( projectRootPath , rescriptLockPartialPath ) ,
407+ ] ;
408+
409+ const foundRewatchLockfileInProjectRoot = projectRewatchLockfiles . some (
410+ ( lockFile ) => fs . existsSync ( lockFile ) ,
411+ ) ;
412+
413+ // if we find a rewatch.lock in the project root, it's a compilation of a local package
414+ // in the workspace.
415+ return ! foundRewatchLockfileInProjectRoot
416+ ? findProjectRootOfFile ( projectRootPath , true )
417+ : null ;
418+ }
419+
420+ // Shared cache: key is either workspace root path or project root path
421+ const runtimePathCache = new Map < string , string | null > ( ) ;
422+
423+ /**
424+ * Gets the runtime path from a workspace root path.
425+ * This function is cached per workspace root path.
426+ */
427+ export async function getRuntimePathFromWorkspaceRoot (
428+ workspaceRootPath : string ,
429+ ) : Promise < string | null > {
430+ // Check cache first
431+ if ( runtimePathCache . has ( workspaceRootPath ) ) {
432+ return runtimePathCache . get ( workspaceRootPath ) ! ;
433+ }
434+
435+ // Compute and cache
436+ let rescriptRuntime : string | null =
437+ config . extensionConfiguration . runtimePath ?? null ;
438+
439+ if ( rescriptRuntime !== null ) {
440+ runtimePathCache . set ( workspaceRootPath , rescriptRuntime ) ;
441+ return rescriptRuntime ;
442+ }
443+
444+ const rescriptRuntimes =
445+ await findRescriptRuntimesInProject ( workspaceRootPath ) ;
446+
447+ const result = rescriptRuntimes . at ( 0 ) ?? null ;
448+ runtimePathCache . set ( workspaceRootPath , result ) ;
449+ return result ;
450+ }
451+
452+ /**
453+ * Gets the runtime path from a project root path.
454+ * Computes the workspace root path and then resolves the runtime.
455+ * This function is cached per project root path.
456+ */
457+ export async function getRuntimePathFromProjectRoot (
458+ projectRootPath : string | null ,
459+ ) : Promise < string | null > {
460+ if ( projectRootPath == null ) {
461+ return null ;
462+ }
463+
464+ // Check cache first (keyed by projectRootPath)
465+ if ( runtimePathCache . has ( projectRootPath ) ) {
466+ return runtimePathCache . get ( projectRootPath ) ! ;
467+ }
468+
469+ // Compute workspace root and resolve runtime
470+ const workspaceRootPath =
471+ computeWorkspaceRootPathFromLockfile ( projectRootPath ) ?? projectRootPath ;
472+
473+ // Check cache again with workspace root (might have been cached from a previous call)
474+ if ( runtimePathCache . has ( workspaceRootPath ) ) {
475+ const result = runtimePathCache . get ( workspaceRootPath ) ! ;
476+ // Cache it under projectRootPath too for faster lookup next time
477+ runtimePathCache . set ( projectRootPath , result ) ;
478+ return result ;
479+ }
480+
481+ // Compute and cache
482+ const result = await getRuntimePathFromWorkspaceRoot ( workspaceRootPath ) ;
483+ // Cache it under both keys
484+ runtimePathCache . set ( workspaceRootPath , result ) ;
485+ runtimePathCache . set ( projectRootPath , result ) ;
486+ return result ;
487+ }
488+
375489export const getNamespaceNameFromConfigFile = (
376490 projDir : p . DocumentUri ,
377491) : execResult => {
0 commit comments