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 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..a5662cf14 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,71 @@ 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 { + 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. @@ -500,7 +553,7 @@ export function computeWorkspaceRootPathFromLockfile( // 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; }