diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 4b225a0ad0e99..82015d6148e04 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6613,7 +6613,7 @@ namespace ts { includeFilePattern: getRegularExpressionForWildcard(includes, absolutePath, "files"), includeDirectoryPattern: getRegularExpressionForWildcard(includes, absolutePath, "directories"), excludePattern: getRegularExpressionForWildcard(excludes, absolutePath, "exclude"), - basePaths: getBasePaths(path, includes, useCaseSensitiveFileNames) + basePaths: getBasePaths(absolutePath, includes, useCaseSensitiveFileNames) }; } @@ -6637,22 +6637,22 @@ namespace ts { const results: string[][] = includeFileRegexes ? includeFileRegexes.map(() => []) : [[]]; const visited = new Map(); const toCanonical = createGetCanonicalFileName(useCaseSensitiveFileNames); - for (const basePath of patterns.basePaths) { - if (directoryExists(basePath)) { - visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth); + for (const absoluteBasePath of patterns.basePaths) { + if (directoryExists(absoluteBasePath)) { + visitDirectory(absoluteBasePath, depth); } } return flatten(results); - function visitDirectory(path: string, absolutePath: string, depth: number | undefined) { + function visitDirectory(absolutePath: string, depth: number | undefined) { const canonicalPath = toCanonical(realpath(absolutePath)); if (visited.has(canonicalPath)) return; visited.set(canonicalPath, true); - const { files, directories } = getFileSystemEntries(path); + const { files, directories } = getFileSystemEntries(absolutePath); for (const current of sort(files, compareStringsCaseSensitive)) { - const name = combinePaths(path, current); + const name = combinePaths(absolutePath, current); const absoluteName = combinePaths(absolutePath, current); if (extensions && !fileExtensionIsOneOf(name, extensions)) continue; if (excludeRegex && excludeRegex.test(absoluteName)) continue; @@ -6675,11 +6675,10 @@ namespace ts { } for (const current of sort(directories, compareStringsCaseSensitive)) { - const name = combinePaths(path, current); const absoluteName = combinePaths(absolutePath, current); if ((!includeDirectoryRegex || includeDirectoryRegex.test(absoluteName)) && (!excludeRegex || !excludeRegex.test(absoluteName))) { - visitDirectory(name, absoluteName, depth); + visitDirectory(absoluteName, depth); } } } @@ -6687,10 +6686,11 @@ namespace ts { /** * Computes the unique non-wildcard base paths amongst the provided include patterns. + * @returns Absolute directory paths */ - function getBasePaths(path: string, includes: readonly string[] | undefined, useCaseSensitiveFileNames: boolean): string[] { + function getBasePaths(absoluteTsconfigPath: string, includes: readonly string[] | undefined, useCaseSensitiveFileNames: boolean): string[] { // Storage for our results in the form of literal paths (e.g. the paths as written by the user). - const basePaths: string[] = [path]; + const basePaths: string[] = [absoluteTsconfigPath]; if (includes) { // Storage for literal base paths amongst the include patterns. @@ -6698,9 +6698,9 @@ namespace ts { for (const include of includes) { // We also need to check the relative paths by converting them to absolute and normalizing // in case they escape the base path (e.g "..\somedirectory") - const absolute: string = isRootedDiskPath(include) ? include : normalizePath(combinePaths(path, include)); + const absoluteIncludePath: string = isRootedDiskPath(include) ? include : normalizePath(combinePaths(absoluteTsconfigPath, include)); // Append the literal and canonical candidate base paths. - includeBasePaths.push(getIncludeBasePath(absolute)); + includeBasePaths.push(getIncludeBasePath(absoluteIncludePath)); } // Sort the offsets array using either the literal or canonical path representations. @@ -6709,7 +6709,7 @@ namespace ts { // Iterate over each include base path and include unique base paths that are not a // subpath of an existing base path for (const includeBasePath of includeBasePaths) { - if (every(basePaths, basePath => !containsPath(basePath, includeBasePath, path, !useCaseSensitiveFileNames))) { + if (every(basePaths, basePath => !containsPath(basePath, includeBasePath, absoluteTsconfigPath, !useCaseSensitiveFileNames))) { basePaths.push(includeBasePath); } } diff --git a/src/testRunner/unittests/publicApi.ts b/src/testRunner/unittests/publicApi.ts index 883b979f9c72e..e741e902b4e31 100644 --- a/src/testRunner/unittests/publicApi.ts +++ b/src/testRunner/unittests/publicApi.ts @@ -182,3 +182,34 @@ describe("unittests:: Public APIs:: getChild* methods on EndOfFileToken with JSD assert.equal(endOfFileToken.getChildCount(), 1); assert.notEqual(endOfFileToken.getChildAt(0), /*expected*/ undefined); }); + +describe("unittests:: Public APIs:: sys", () => { + it("readDirectory", () => { + // #45990, testing passing a non-absolute path + // `sys.readDirectory` is just `matchFiles` plugged into the real FS + const read = ts.matchFiles( + /*path*/ "", + /*extensions*/ [".ts", ".tsx"], + /*excludes*/ ["node_modules", "dist"], + /*includes*/ ["**/*"], + /*useCaseSensitiveFileNames*/ true, + /*currentDirectory*/ "/", + /*depth*/ undefined, + /*getFileSystemEntries*/ path => { + switch (path) { + case "/": return { directories: [], files: ["file.ts"] }; + default: return { directories: [], files: [] }; + } + }, + /*realpath*/ ts.identity, + /*directoryExists*/ path => { + switch (path) { + case "/": return true; + default: return false; + } + } + ); + + assert.deepEqual(read, ["/file.ts"]); + }); +});