Skip to content

Commit 4e0514d

Browse files
authored
Merge pull request #30376 from Microsoft/recursiveSymLinks
Handle recursive symlinks when matching file names
2 parents b762d62 + b6d520a commit 4e0514d

File tree

6 files changed

+42
-7
lines changed

6 files changed

+42
-7
lines changed

src/compiler/sys.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1148,7 +1148,7 @@ namespace ts {
11481148
}
11491149

11501150
function readDirectory(path: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] {
1151-
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries);
1151+
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
11521152
}
11531153

11541154
function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean {

src/compiler/utilities.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8101,7 +8101,7 @@ namespace ts {
81018101
}
81028102

81038103
/** @param path directory of the tsconfig.json */
8104-
export function matchFiles(path: string, extensions: ReadonlyArray<string> | undefined, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string> | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries): string[] {
8104+
export function matchFiles(path: string, extensions: ReadonlyArray<string> | undefined, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string> | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries, realpath: (path: string) => string): string[] {
81058105
path = normalizePath(path);
81068106
currentDirectory = normalizePath(currentDirectory);
81078107

@@ -8114,14 +8114,18 @@ namespace ts {
81148114
// Associate an array of results with each include regex. This keeps results in order of the "include" order.
81158115
// If there are no "includes", then just put everything in results[0].
81168116
const results: string[][] = includeFileRegexes ? includeFileRegexes.map(() => []) : [[]];
8117-
8117+
const visited = createMap<true>();
8118+
const toCanonical = createGetCanonicalFileName(useCaseSensitiveFileNames);
81188119
for (const basePath of patterns.basePaths) {
81198120
visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth);
81208121
}
81218122

81228123
return flatten<string>(results);
81238124

81248125
function visitDirectory(path: string, absolutePath: string, depth: number | undefined) {
8126+
const canonicalPath = toCanonical(realpath(absolutePath));
8127+
if (visited.has(canonicalPath)) return;
8128+
visited.set(canonicalPath, true);
81258129
const { files, directories } = getFileSystemEntries(path);
81268130

81278131
for (const current of sort<string>(files, compareStringsCaseSensitive)) {

src/compiler/watchUtilities.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace ts {
1111
directoryExists?(path: string): boolean;
1212
getDirectories?(path: string): string[];
1313
readDirectory?(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
14+
realpath?(path: string): string;
1415

1516
createDirectory?(path: string): void;
1617
writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void;
@@ -56,7 +57,8 @@ namespace ts {
5657
writeFile: host.writeFile && writeFile,
5758
addOrDeleteFileOrDirectory,
5859
addOrDeleteFile,
59-
clearCache
60+
clearCache,
61+
realpath: host.realpath && realpath
6062
};
6163

6264
function toPath(fileName: string) {
@@ -170,7 +172,7 @@ namespace ts {
170172
const rootDirPath = toPath(rootDir);
171173
const result = tryReadDirectory(rootDir, rootDirPath);
172174
if (result) {
173-
return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames, currentDirectory, depth, getFileSystemEntries);
175+
return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames, currentDirectory, depth, getFileSystemEntries, realpath);
174176
}
175177
return host.readDirectory!(rootDir, extensions, excludes, includes, depth);
176178

@@ -183,6 +185,10 @@ namespace ts {
183185
}
184186
}
185187

188+
function realpath(s: string) {
189+
return host.realpath ? host.realpath(s) : s;
190+
}
191+
186192
function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) {
187193
const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath);
188194
if (existingResult) {

src/harness/fakes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ namespace fakes {
8787
}
8888

8989
public readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
90-
return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, path => this.getAccessibleFileSystemEntries(path));
90+
return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, path => this.getAccessibleFileSystemEntries(path), path => this.realpath(path));
9191
}
9292

9393
public getAccessibleFileSystemEntries(path: string): ts.FileSystemEntries {

src/harness/virtualFileSystemWithWatch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ interface Array<T> {}`
826826
});
827827
}
828828
return { directories, files };
829-
});
829+
}, path => this.realpath(path));
830830
}
831831

832832
watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): FileWatcher {

src/testRunner/unittests/config/matchFiles.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,5 +1512,30 @@ namespace ts {
15121512
validateMatches(getExpected(caseSensitiveBasePath), json, caseSensitiveOrderingDiffersWithCaseHost, caseSensitiveBasePath);
15131513
validateMatches(getExpected(caseInsensitiveBasePath), json, caseInsensitiveOrderingDiffersWithCaseHost, caseInsensitiveBasePath);
15141514
});
1515+
1516+
it("when recursive symlinked directories are present", () => {
1517+
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
1518+
cwd: caseInsensitiveBasePath, files: {
1519+
"c:/dev/index.ts": ""
1520+
}
1521+
});
1522+
fs.mkdirpSync("c:/dev/a/b/c");
1523+
fs.symlinkSync("c:/dev/A", "c:/dev/a/self");
1524+
fs.symlinkSync("c:/dev/a", "c:/dev/a/b/parent");
1525+
fs.symlinkSync("c:/dev/a", "c:/dev/a/b/c/grandparent");
1526+
const host = new fakes.ParseConfigHost(fs);
1527+
const json = {};
1528+
const expected: ParsedCommandLine = {
1529+
options: {},
1530+
errors: [],
1531+
fileNames: [
1532+
"c:/dev/index.ts"
1533+
],
1534+
wildcardDirectories: {
1535+
"c:/dev": WatchDirectoryFlags.Recursive
1536+
},
1537+
};
1538+
validateMatches(expected, json, host, caseInsensitiveBasePath);
1539+
});
15151540
});
15161541
}

0 commit comments

Comments
 (0)