Handle recursive symlinks when matching file names

Fixes #28842
This commit is contained in:
Sheetal Nandi 2019-03-13 12:23:18 -07:00
parent b762d6205e
commit b6d520a7a5
6 changed files with 42 additions and 7 deletions

View file

@ -1148,7 +1148,7 @@ namespace ts {
}
function readDirectory(path: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] {
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries);
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
}
function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean {

View file

@ -8101,7 +8101,7 @@ namespace ts {
}
/** @param path directory of the tsconfig.json */
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[] {
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[] {
path = normalizePath(path);
currentDirectory = normalizePath(currentDirectory);
@ -8114,7 +8114,8 @@ namespace ts {
// Associate an array of results with each include regex. This keeps results in order of the "include" order.
// If there are no "includes", then just put everything in results[0].
const results: string[][] = includeFileRegexes ? includeFileRegexes.map(() => []) : [[]];
const visited = createMap<true>();
const toCanonical = createGetCanonicalFileName(useCaseSensitiveFileNames);
for (const basePath of patterns.basePaths) {
visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth);
}
@ -8122,6 +8123,9 @@ namespace ts {
return flatten<string>(results);
function visitDirectory(path: string, absolutePath: string, depth: number | undefined) {
const canonicalPath = toCanonical(realpath(absolutePath));
if (visited.has(canonicalPath)) return;
visited.set(canonicalPath, true);
const { files, directories } = getFileSystemEntries(path);
for (const current of sort<string>(files, compareStringsCaseSensitive)) {

View file

@ -11,6 +11,7 @@ namespace ts {
directoryExists?(path: string): boolean;
getDirectories?(path: string): string[];
readDirectory?(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
realpath?(path: string): string;
createDirectory?(path: string): void;
writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void;
@ -56,7 +57,8 @@ namespace ts {
writeFile: host.writeFile && writeFile,
addOrDeleteFileOrDirectory,
addOrDeleteFile,
clearCache
clearCache,
realpath: host.realpath && realpath
};
function toPath(fileName: string) {
@ -170,7 +172,7 @@ namespace ts {
const rootDirPath = toPath(rootDir);
const result = tryReadDirectory(rootDir, rootDirPath);
if (result) {
return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames, currentDirectory, depth, getFileSystemEntries);
return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames, currentDirectory, depth, getFileSystemEntries, realpath);
}
return host.readDirectory!(rootDir, extensions, excludes, includes, depth);
@ -183,6 +185,10 @@ namespace ts {
}
}
function realpath(s: string) {
return host.realpath ? host.realpath(s) : s;
}
function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) {
const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath);
if (existingResult) {

View file

@ -87,7 +87,7 @@ namespace fakes {
}
public readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, path => this.getAccessibleFileSystemEntries(path));
return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, path => this.getAccessibleFileSystemEntries(path), path => this.realpath(path));
}
public getAccessibleFileSystemEntries(path: string): ts.FileSystemEntries {

View file

@ -826,7 +826,7 @@ interface Array<T> {}`
});
}
return { directories, files };
});
}, path => this.realpath(path));
}
watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): FileWatcher {

View file

@ -1512,5 +1512,30 @@ namespace ts {
validateMatches(getExpected(caseSensitiveBasePath), json, caseSensitiveOrderingDiffersWithCaseHost, caseSensitiveBasePath);
validateMatches(getExpected(caseInsensitiveBasePath), json, caseInsensitiveOrderingDiffersWithCaseHost, caseInsensitiveBasePath);
});
it("when recursive symlinked directories are present", () => {
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
cwd: caseInsensitiveBasePath, files: {
"c:/dev/index.ts": ""
}
});
fs.mkdirpSync("c:/dev/a/b/c");
fs.symlinkSync("c:/dev/A", "c:/dev/a/self");
fs.symlinkSync("c:/dev/a", "c:/dev/a/b/parent");
fs.symlinkSync("c:/dev/a", "c:/dev/a/b/c/grandparent");
const host = new fakes.ParseConfigHost(fs);
const json = {};
const expected: ParsedCommandLine = {
options: {},
errors: [],
fileNames: [
"c:/dev/index.ts"
],
wildcardDirectories: {
"c:/dev": WatchDirectoryFlags.Recursive
},
};
validateMatches(expected, json, host, caseInsensitiveBasePath);
});
});
}