Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion server/src/incrementalCompilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
97 changes: 75 additions & 22 deletions server/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 ||
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}

Expand Down