From af371e15e783420f073f473d3a3ebc09372b9d1a Mon Sep 17 00:00:00 2001 From: nojaf Date: Sat, 13 Dec 2025 21:03:57 +0100 Subject: [PATCH 1/4] Fix rewatch lockfile detection on Windows by correcting project root lookup logic Refactor findProjectRootOfFile to use separate functions for file vs directory lookups, eliminating confusing boolean parameter and fixing the case where exact directory matches were incorrectly excluded. --- server/src/incrementalCompilation.ts | 2 +- server/src/utils.ts | 110 +++++++++++++++++++++------ 2 files changed, 89 insertions(+), 23 deletions(-) diff --git a/server/src/incrementalCompilation.ts b/server/src/incrementalCompilation.ts index b7e328978..624b3d47e 100644 --- a/server/src/incrementalCompilation.ts +++ b/server/src/incrementalCompilation.ts @@ -277,7 +277,7 @@ function triggerIncrementalCompilationOfFile( const foundRewatchLockfileInProjectRoot = computedWorkspaceRoot == null && projectRootPath != null && - utils.findProjectRootOfFile(projectRootPath, true) != null; + utils.findProjectRootOfDir(projectRootPath) != null; if (foundRewatchLockfileInProjectRoot && debug()) { console.log( diff --git a/server/src/utils.ts b/server/src/utils.ts index ac47a2c58..e3d36d028 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -82,9 +82,9 @@ export let createFileInTempDir = (extension = ""): NormalizedPath => { return path.join(os.tmpdir(), tempFileName) as NormalizedPath; }; -let findProjectRootOfFileInDir = ( +function findProjectRootOfFileInDir( source: NormalizedPath, -): NormalizedPath | null => { +): NormalizedPath | null { const dir = normalizePath(path.dirname(source)); if (dir == null) { return null; @@ -102,18 +102,20 @@ let findProjectRootOfFileInDir = ( return findProjectRootOfFileInDir(dir); } } -}; +} -// TODO: races here? -export let findProjectRootOfFile = ( +/** + * Searches for a project root path in projectsFiles that contains the given file path. + * Excludes exact matches - the source must be a file inside a project, not the project root itself. + */ +function findProjectRootContainingFile( source: NormalizedPath, - allowDir?: boolean, -): NormalizedPath | null => { - // First look in project files (keys are already normalized) +): NormalizedPath | null { let foundRootFromProjectFiles: NormalizedPath | null = null; for (const rootPath of projectsFiles.keys()) { // Both are normalized, so direct comparison works - if (source.startsWith(rootPath) && (!allowDir || source !== rootPath)) { + // For files, we exclude exact matches (source !== rootPath) + if (source.startsWith(rootPath) && source !== rootPath) { // Prefer the longest path (most nested) if ( foundRootFromProjectFiles == null || @@ -124,20 +126,77 @@ export let findProjectRootOfFile = ( } } - if (foundRootFromProjectFiles != null) { - return foundRootFromProjectFiles; - } else { - const isDir = path.extname(source) === ""; - const searchPath = - isDir && !allowDir ? path.join(source, "dummy.res") : source; - const normalizedSearchPath = normalizePath(searchPath); - if (normalizedSearchPath == null) { - return null; + return foundRootFromProjectFiles; +} + +/** + * Searches for a project root path in projectsFiles that matches the given directory path. + * Allows exact matches - the source can be the project root directory itself. + */ +function findProjectRootMatchingDir( + source: NormalizedPath, +): NormalizedPath | null { + console.log({ + source, + searchType: "directory", + projectsFilesKeys: Array.from(projectsFiles.keys()), + }); + + let foundRootFromProjectFiles: NormalizedPath | null = null; + for (const rootPath of projectsFiles.keys()) { + // Both are normalized, so direct comparison works + // For directories, we allow exact matches + if (source.startsWith(rootPath)) { + // Prefer the longest path (most nested) + if ( + foundRootFromProjectFiles == null || + rootPath.length > foundRootFromProjectFiles.length + ) { + foundRootFromProjectFiles = rootPath; + } } - const foundPath = findProjectRootOfFileInDir(normalizedSearchPath); - return foundPath; } -}; + + return foundRootFromProjectFiles; +} + +/** + * Finds the project root for a given file path. + * This is the main function used throughout the codebase for finding project roots. + */ +export function findProjectRootOfFile( + source: NormalizedPath, +): NormalizedPath | null { + // First look in project files - exclude exact matches since we're looking for a file + let foundRoot = findProjectRootContainingFile(source); + + if (foundRoot != null) { + return foundRoot; + } + + // Fallback: search the filesystem + const foundPath = findProjectRootOfFileInDir(source); + return foundPath; +} + +/** + * Finds the project root for a given directory path. + * This allows exact matches and is used for workspace/lockfile detection. + */ +export function findProjectRootOfDir( + source: NormalizedPath, +): NormalizedPath | null { + // First look in project files - allow exact matches since we're looking for a directory + let foundRoot = findProjectRootMatchingDir(source); + + if (foundRoot != null) { + return foundRoot; + } + + // Fallback: search the filesystem + const foundPath = findProjectRootOfFileInDir(source); + return foundPath; +} /** * Gets the project file for a given project root path. @@ -493,14 +552,21 @@ export function computeWorkspaceRootPathFromLockfile( path.resolve(projectRootPath, rescriptLockPartialPath), ]; + console.log(projectRewatchLockfiles); + const foundRewatchLockfileInProjectRoot = projectRewatchLockfiles.some( (lockFile) => fs.existsSync(lockFile), ); + console.log( + "foundRewatchLockfileInProjectRoot", + foundRewatchLockfileInProjectRoot, + ); + // 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) + ? findProjectRootOfDir(projectRootPath) : null; } From aa2c6cf041f7c5deb359fadb6505ca3a9b8138b5 Mon Sep 17 00:00:00 2001 From: nojaf Date: Sat, 13 Dec 2025 21:11:15 +0100 Subject: [PATCH 2/4] Remove logs --- server/src/utils.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/server/src/utils.ts b/server/src/utils.ts index e3d36d028..fb95ae605 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -136,12 +136,6 @@ function findProjectRootContainingFile( function findProjectRootMatchingDir( source: NormalizedPath, ): NormalizedPath | null { - console.log({ - source, - searchType: "directory", - projectsFilesKeys: Array.from(projectsFiles.keys()), - }); - let foundRootFromProjectFiles: NormalizedPath | null = null; for (const rootPath of projectsFiles.keys()) { // Both are normalized, so direct comparison works @@ -552,8 +546,6 @@ export function computeWorkspaceRootPathFromLockfile( path.resolve(projectRootPath, rescriptLockPartialPath), ]; - console.log(projectRewatchLockfiles); - const foundRewatchLockfileInProjectRoot = projectRewatchLockfiles.some( (lockFile) => fs.existsSync(lockFile), ); From 801f81309c59976b454b4b031364f8f9d36019c2 Mon Sep 17 00:00:00 2001 From: nojaf Date: Sat, 13 Dec 2025 21:11:45 +0100 Subject: [PATCH 3/4] Remove logs --- server/src/utils.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/server/src/utils.ts b/server/src/utils.ts index fb95ae605..a5662cf14 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -550,11 +550,6 @@ export function computeWorkspaceRootPathFromLockfile( (lockFile) => fs.existsSync(lockFile), ); - console.log( - "foundRewatchLockfileInProjectRoot", - foundRewatchLockfileInProjectRoot, - ); - // if we find a rewatch.lock in the project root, it's a compilation of a local package // in the workspace. return !foundRewatchLockfileInProjectRoot From 894205f33559843fb08f675a5a81e11fe63acd13 Mon Sep 17 00:00:00 2001 From: nojaf Date: Sat, 13 Dec 2025 22:26:28 +0100 Subject: [PATCH 4/4] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11efe49d5..758a5fd36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ ## [Unreleased] +#### :bug: Bug fix + +- Fix rewatch lockfile detection on Windows. https://github.com/rescript-lang/rescript-vscode/pull/1160 + #### :nail_care: Polish - Resolve symlinks when finding platform binaries. https://github.com/rescript-lang/rescript-vscode/pull/1154