diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 0bea9468432..178578ec452 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1992,7 +1992,7 @@ namespace ts { } if (include && include.length > 0) { - for (const file of host.readDirectory(basePath, supportedExtensions, exclude, include)) { + for (const file of host.readDirectory(basePath, supportedExtensions, exclude, include, /*depth*/ undefined)) { // If we have already included a literal or wildcard path with a // higher priority extension, we should skip this file. // diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 86ad483ccc3..48e16ab16b4 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2,8 +2,9 @@ /// namespace ts { + export const versionMajorMinor = "2.5"; /** The version of the TypeScript compiler release */ - export const version = "2.5.0"; + export const version = `${versionMajorMinor}.0`; } /* @internal */ @@ -2016,7 +2017,7 @@ namespace ts { }; } - export function matchFiles(path: string, extensions: string[], excludes: string[], includes: string[], useCaseSensitiveFileNames: boolean, currentDirectory: string, getFileSystemEntries: (path: string) => FileSystemEntries): string[] { + export function matchFiles(path: string, extensions: string[], excludes: string[], includes: string[], useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries): string[] { path = normalizePath(path); currentDirectory = normalizePath(currentDirectory); @@ -2033,15 +2034,14 @@ namespace ts { const comparer = useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive; for (const basePath of patterns.basePaths) { - visitDirectory(basePath, combinePaths(currentDirectory, basePath)); + visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth); } return flatten(results); - function visitDirectory(path: string, absolutePath: string) { + function visitDirectory(path: string, absolutePath: string, depth: number | undefined) { let { files, directories } = getFileSystemEntries(path); files = files.slice().sort(comparer); - directories = directories.slice().sort(comparer); for (const current of files) { const name = combinePaths(path, current); @@ -2059,12 +2059,20 @@ namespace ts { } } + if (depth !== undefined) { + depth--; + if (depth === 0) { + return; + } + } + + directories = directories.slice().sort(comparer); for (const current of directories) { const name = combinePaths(path, current); const absoluteName = combinePaths(absolutePath, current); if ((!includeDirectoryRegex || includeDirectoryRegex.test(absoluteName)) && (!excludeRegex || !excludeRegex.test(absoluteName))) { - visitDirectory(name, absoluteName); + visitDirectory(name, absoluteName, depth); } } } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index cd14f3b2801..18ddba9c917 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -33,7 +33,7 @@ namespace ts { getExecutingFilePath(): string; getCurrentDirectory(): string; getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[]; + readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -281,8 +281,8 @@ namespace ts { } } - function readDirectory(path: string, extensions?: string[], excludes?: string[], includes?: string[]): string[] { - return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), getAccessibleFileSystemEntries); + function readDirectory(path: string, extensions?: string[], excludes?: string[], includes?: string[], depth?: number): string[] { + return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries); } const enum FileSystemEntryKind { @@ -475,7 +475,7 @@ namespace ts { getCurrentDirectory: () => ChakraHost.currentDirectory, getDirectories: ChakraHost.getDirectories, getEnvironmentVariable: ChakraHost.getEnvironmentVariable || (() => ""), - readDirectory: (path: string, extensions?: string[], excludes?: string[], includes?: string[]) => { + readDirectory(path, extensions, excludes, includes, _depth) { const pattern = getFileMatcherPatterns(path, excludes, includes, !!ChakraHost.useCaseSensitiveFileNames, ChakraHost.currentDirectory); return ChakraHost.readDirectory(path, extensions, pattern.basePaths, pattern.excludePattern, pattern.includeFilePattern, pattern.includeDirectoryPattern); }, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 85ab8a65d5d..4b79c4e99ed 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2390,7 +2390,7 @@ namespace ts { export interface ParseConfigHost { useCaseSensitiveFileNames: boolean; - readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[]): string[]; + readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[]; /** * Gets a value indicating whether the specified path exists and is a file. diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 97d49ca134d..026ff2de85f 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -493,7 +493,7 @@ namespace Harness { args(): string[]; getExecutingFilePath(): string; exit(exitCode?: number): void; - readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[]): string[]; + readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[], depth?: number): string[]; tryEnableSourceMapsForHost?(): void; getEnvironmentVariable?(name: string): string; } @@ -537,7 +537,7 @@ namespace Harness { ts.sys.tryEnableSourceMapsForHost(); } } - export const readDirectory: typeof IO.readDirectory = (path, extension, exclude, include) => ts.sys.readDirectory(path, extension, exclude, include); + export const readDirectory: typeof IO.readDirectory = (path, extension, exclude, include, depth) => ts.sys.readDirectory(path, extension, exclude, include, depth); export function createDirectory(path: string) { if (!directoryExists(path)) { @@ -733,12 +733,12 @@ namespace Harness { Http.writeToServerSync(serverRoot + path, "WRITE", contents); } - export function readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[]) { + export function readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[], depth?: number) { const fs = new Utils.VirtualFileSystem(path, useCaseSensitiveFileNames()); for (const file of listFiles(path)) { fs.addFile(file); } - return ts.matchFiles(path, extension, exclude, include, useCaseSensitiveFileNames(), getCurrentDirectory(), path => { + return ts.matchFiles(path, extension, exclude, include, useCaseSensitiveFileNames(), getCurrentDirectory(), depth, path => { const entry = fs.traversePath(path); if (entry && entry.isDirectory()) { const directory = entry; diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 774c43556ca..82f0b87be94 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -208,10 +208,11 @@ namespace Harness.LanguageService { const script = this.getScriptSnapshot(fileName); return script !== undefined; } - readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] { + readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[] { return ts.matchFiles(path, extensions, exclude, include, /*useCaseSensitiveFileNames*/ false, this.getCurrentDirectory(), + depth, (p) => this.virtualFileSystem.getAccessibleFileSystemEntries(p)); } readFile(path: string): string { @@ -312,9 +313,7 @@ namespace Harness.LanguageService { getScriptVersion(fileName: string): string { return this.nativeHost.getScriptVersion(fileName); } getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); } - readDirectory(_rootDir: string, _extension: string): string { - return ts.notImplemented(); - } + readDirectory = ts.notImplemented; readDirectoryNames = ts.notImplemented; readFileNames = ts.notImplemented; fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; } @@ -668,9 +667,7 @@ namespace Harness.LanguageService { return ts.sys.getEnvironmentVariable(name); } - readDirectory(_path: string, _extension?: string[], _exclude?: string[], _include?: string[]): string[] { - return ts.notImplemented(); - } + readDirectory() { return ts.notImplemented(); } watchFile(): ts.FileWatcher { return { close: ts.noop }; diff --git a/src/harness/loggedIO.ts b/src/harness/loggedIO.ts index 76f585b623a..d4a1e2e2bd0 100644 --- a/src/harness/loggedIO.ts +++ b/src/harness/loggedIO.ts @@ -67,6 +67,7 @@ interface IOLog { extensions: string[], exclude: string[], include: string[], + depth: number, result: string[] }[]; } @@ -220,10 +221,9 @@ namespace Playback { memoize(path => findFileByPath(replayLog.filesRead, path, /*throwFileNotFoundError*/ true).contents)); wrapper.readDirectory = recordReplay(wrapper.readDirectory, underlying)( - (path, extensions, exclude, include) => { - const result = (underlying).readDirectory(path, extensions, exclude, include); - const logEntry = { path, extensions, exclude, include, result }; - recordLog.directoriesRead.push(logEntry); + (path, extensions, exclude, include, depth) => { + const result = (underlying).readDirectory(path, extensions, exclude, include, depth); + recordLog.directoriesRead.push({ path, extensions, exclude, include, depth, result }); return result; }, path => { diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index b02164034fa..169d2ef539b 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -275,8 +275,8 @@ class ProjectRunner extends RunnerBase { : ts.normalizeSlashes(testCase.projectRoot) + "/" + ts.normalizeSlashes(fileName); } - function readDirectory(rootDir: string, extension: string[], exclude: string[], include: string[]): string[] { - const harnessReadDirectoryResult = Harness.IO.readDirectory(getFileNameInTheProjectTest(rootDir), extension, exclude, include); + function readDirectory(rootDir: string, extension: string[], exclude: string[], include: string[], depth: number): string[] { + const harnessReadDirectoryResult = Harness.IO.readDirectory(getFileNameInTheProjectTest(rootDir), extension, exclude, include, depth); const result: string[] = []; for (let i = 0; i < harnessReadDirectoryResult.length; i++) { result[i] = ts.getRelativePathToDirectoryOrUrl(testCase.projectRoot, harnessReadDirectoryResult[i], diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index 067cd351ec7..e8e54a0d6d0 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -19,7 +19,7 @@ namespace ts.server { getExecutingFilePath(): string { return void 0; }, getCurrentDirectory(): string { return void 0; }, getEnvironmentVariable(): string { return ""; }, - readDirectory(): string[] { return []; }, + readDirectory() { return []; }, exit: noop, setTimeout() { return 0; }, clearTimeout: noop, diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 2f6ba7aa3bb..c0a0d21f854 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -438,14 +438,13 @@ namespace ts.projectSystem { } } - readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] { - const that = this; - return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), (dir) => { + readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[] { + return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { const result: FileSystemEntries = { directories: [], files: [] }; - const dirEntry = that.fs.get(that.toPath(dir)); + const dirEntry = this.fs.get(this.toPath(dir)); if (isFolder(dirEntry)) { dirEntry.entries.forEach((entry) => { if (isFolder(entry)) { diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index 9483308287f..7479c532c08 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -663,16 +663,22 @@ namespace ts.projectSystem { path: "/node_modules/jquery/package.json", content: JSON.stringify({ name: "jquery" }) }; + // Should not search deeply in node_modules. + const nestedPackage = { + path: "/node_modules/jquery/nested/package.json", + content: JSON.stringify({ name: "nested" }), + }; const jqueryDTS = { path: "/tmp/node_modules/@types/jquery/index.d.ts", content: "" }; - const host = createServerHost([app, jsconfig, jquery, jqueryPackage]); + const host = createServerHost([app, jsconfig, jquery, jqueryPackage, nestedPackage]); const installer = new (class extends Installer { constructor() { - super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") }); + super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery", "nested") }); } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { + installWorker(_requestId: number, args: string[], _cwd: string, cb: TI.RequestCompletedAction) { + assert.deepEqual(args, [`@types/jquery@ts${versionMajorMinor}`]); const installedTypings = ["@types/jquery"]; const typingFiles = [jqueryDTS]; executeCommand(this, host, installedTypings, typingFiles, cb); @@ -1058,6 +1064,29 @@ namespace ts.projectSystem { assert.deepEqual(result.cachedTypingPaths, [node.path]); assert.deepEqual(result.newTypingNames, ["bar"]); }); + + it("should search only 2 levels deep", () => { + const app = { + path: "/app.js", + content: "", + }; + const a = { + path: "/node_modules/a/package.json", + content: JSON.stringify({ name: "a" }), + }; + const b = { + path: "/node_modules/a/b/package.json", + content: JSON.stringify({ name: "b" }), + }; + const host = createServerHost([app, a, b]); + const cache = createMap(); + const result = JsTyping.discoverTypings(host, [app.path], getDirectoryPath(app.path), /*safeListPath*/ undefined, cache, { enable: true }, /*unresolvedImports*/ []); + assert.deepEqual(result, { + cachedTypingPaths: [], + newTypingNames: ["a"], // But not "b" + filesToWatch: ["/bower_components", "/node_modules"], + }); + }); }); describe("telemetry events", () => { diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index b89f4c7232d..3904a27bb1c 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -216,8 +216,8 @@ namespace Utils { } } - readDirectory(path: string, extensions: string[], excludes: string[], includes: string[]) { - return ts.matchFiles(path, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, (path: string) => this.getAccessibleFileSystemEntries(path)); + readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number) { + return ts.matchFiles(path, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, (path: string) => this.getAccessibleFileSystemEntries(path)); } } } \ No newline at end of file diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index b8f9807139f..fa972a0c930 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -222,8 +222,8 @@ namespace ts.server { return this.host.directoryExists(path); } - readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] { - return this.host.readDirectory(path, extensions, exclude, include); + readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[] { + return this.host.readDirectory(path, extensions, exclude, include, depth); } getDirectories(path: string): string[] { diff --git a/src/server/server.ts b/src/server/server.ts index 5f2b7054755..a7063fb1ad3 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -35,7 +35,6 @@ namespace ts.server { } = require("os"); function getGlobalTypingsCacheLocation() { - const versionMajorMinor = ts.version.match(/\d+\.\d+/)[0]; switch (process.platform) { case "win32": { const basePath = process.env.LOCALAPPDATA || diff --git a/src/server/typingsInstaller/typingsInstaller.ts b/src/server/typingsInstaller/typingsInstaller.ts index fa533450b26..cf1e1a836a0 100644 --- a/src/server/typingsInstaller/typingsInstaller.ts +++ b/src/server/typingsInstaller/typingsInstaller.ts @@ -434,5 +434,4 @@ namespace ts.server.typingsInstaller { export function typingsName(packageName: string): string { return `@types/${packageName}@ts${versionMajorMinor}`; } - const versionMajorMinor = version.split(".").slice(0, 2).join("."); } \ No newline at end of file diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 008fe357ec5..f3ee1e007cc 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -208,6 +208,7 @@ namespace ts.JsTyping { return; } + // depth of 2, so we access `node_modules/foo` but not `node_modules/foo/bar` const fileNames = host.readDirectory(packagesFolderPath, [".json"], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2); for (const fileName of fileNames) { const normalizedFileName = normalizePath(fileName); diff --git a/src/services/shims.ts b/src/services/shims.ts index 1c37f598021..0a0b2c82c5b 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -467,7 +467,7 @@ namespace ts { } } - export class CoreServicesShimHostAdapter implements ParseConfigHost, ModuleResolutionHost { + export class CoreServicesShimHostAdapter implements ParseConfigHost, ModuleResolutionHost, JsTyping.TypingResolutionHost { public directoryExists: (directoryName: string) => boolean; public realpath: (path: string) => string; @@ -484,33 +484,17 @@ namespace ts { } public readDirectory(rootDir: string, extensions: string[], exclude: string[], include: string[], depth?: number): string[] { - // Wrap the API changes for 2.0 release. This try/catch - // should be removed once TypeScript 2.0 has shipped. - try { - const pattern = getFileMatcherPatterns(rootDir, exclude, include, - this.shimHost.useCaseSensitiveFileNames(), this.shimHost.getCurrentDirectory()); - return JSON.parse(this.shimHost.readDirectory( - rootDir, - JSON.stringify(extensions), - JSON.stringify(pattern.basePaths), - pattern.excludePattern, - pattern.includeFilePattern, - pattern.includeDirectoryPattern, - depth - )); - } - catch (e) { - const results: string[] = []; - for (const extension of extensions) { - for (const file of this.readDirectoryFallback(rootDir, extension, exclude)) - { - if (!contains(results, file)) { - results.push(file); - } - } - } - return results; - } + const pattern = getFileMatcherPatterns(rootDir, exclude, include, + this.shimHost.useCaseSensitiveFileNames(), this.shimHost.getCurrentDirectory()); + return JSON.parse(this.shimHost.readDirectory( + rootDir, + JSON.stringify(extensions), + JSON.stringify(pattern.basePaths), + pattern.excludePattern, + pattern.includeFilePattern, + pattern.includeDirectoryPattern, + depth + )); } public fileExists(fileName: string): boolean { @@ -521,10 +505,6 @@ namespace ts { return this.shimHost.readFile(fileName); } - private readDirectoryFallback(rootDir: string, extension: string, exclude: string[]) { - return JSON.parse(this.shimHost.readDirectory(rootDir, extension, JSON.stringify(exclude))); - } - public getDirectories(path: string): string[] { return JSON.parse(this.shimHost.getDirectories(path)); } diff --git a/src/services/types.ts b/src/services/types.ts index 2d47da2fd1d..447029a2f66 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -163,7 +163,7 @@ namespace ts { * LS host can optionally implement these methods to support completions for module specifiers. * Without these methods, only completions for ambient modules will be provided. */ - readDirectory?(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[]; + readDirectory?(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[]; readFile?(path: string, encoding?: string): string; fileExists?(path: string): boolean;