diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index ccde539fafa..7a73949cf7a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -727,22 +727,10 @@ namespace ts { const supportedExtensions = getSupportedExtensions(options); Debug.assert(indexOf(supportedExtensions, ".ts") < indexOf(supportedExtensions, ".d.ts"), "Changed priority of extensions to pick"); - const potentialFiles: string[] = []; - if (host.readDirectoryWithMultipleExtensions) { - addRange(potentialFiles, host.readDirectoryWithMultipleExtensions(basePath, supportedExtensions, exclude)); - } - else { - for (const extension of supportedExtensions) { - addRange(potentialFiles, host.readDirectory(basePath, extension, exclude)); - } - } // Get files of supported extensions in their order of resolution for (const extension of supportedExtensions) { - for (const fileName of potentialFiles) { - if (!fileExtensionIs(fileName, extension)) { - continue; - } - + const filesInDirWithExtension = host.readDirectory(basePath, extension, exclude); + for (const fileName of filesInDirWithExtension) { // .ts extension would read the .d.ts extension files too but since .d.ts is lower priority extension, // lets pick them when its turn comes up if (extension === ".ts" && fileExtensionIs(fileName, ".d.ts")) { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 659c021c6be..db4270ad271 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -27,7 +27,6 @@ namespace ts { getCurrentDirectory(): string; getDirectories(path: string): string[]; readDirectory(path: string, extension?: string, exclude?: string[]): string[]; - readDirectoryWithMultipleExtensions?(path: string, extensions: string[], exclude?: string[]): string[]; getModifiedTime?(path: string): Date; createHash?(data: string): string; getMemoryUsage?(): number; @@ -416,23 +415,25 @@ namespace ts { return filter(_fs.readdirSync(path), p => fileSystemEntryExists(combinePaths(path, p), FileSystemEntryKind.Directory)); } - function visitDirectory(path: string, result: string[], extension: string | string[], exclude: string[]) { - const files = _fs.readdirSync(path || ".").sort(); - const directories: string[] = []; - for (const current of files) { + function readDirectory(path: string, extension?: string, exclude?: string[]): string[] { + const result: string[] = []; + exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); + visitDirectory(path); + return result; + function visitDirectory(path: string) { + const files = _fs.readdirSync(path || ".").sort(); + const directories: string[] = []; + for (const current of files) { // This is necessary because on some file system node fails to exclude // "." and "..". See https://github.com/nodejs/node/issues/4002 if (current === "." || current === "..") { continue; } - const name = combinePaths(path, current); - if (!contains(exclude, getCanonicalPath(name))) { - // fs.statSync would throw an exception if the file is a symlink - // whose linked file doesn't exist. - try { + const name = combinePaths(path, current); + if (!contains(exclude, getCanonicalPath(name))) { const stat = _fs.statSync(name); if (stat.isFile()) { - if (checkExtension(name)) { + if (!extension || fileExtensionIs(name, extension)) { result.push(name); } } @@ -440,38 +441,11 @@ namespace ts { directories.push(name); } } - catch (e) { } + } + for (const current of directories) { + visitDirectory(current); } } - for (const current of directories) { - visitDirectory(current, result, extension, exclude); - } - - function checkExtension(name: string) { - if (!extension) { - return true; - } - if (typeof extension === "string") { - return fileExtensionIs(name, extension); - } - else { - return forEach(extension, ext => fileExtensionIs(name, ext)); - } - } - } - - function readDirectoryWithMultipleExtensions(path: string, extensions: string[], exclude?: string[]): string[] { - const result: string[] = []; - exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); - visitDirectory(path, result, extensions, exclude); - return result; - } - - function readDirectory(path: string, extension?: string, exclude?: string[]): string[] { - const result: string[] = []; - exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); - visitDirectory(path, result, extension, exclude); - return result; } return { @@ -548,7 +522,6 @@ namespace ts { }, getDirectories, readDirectory, - readDirectoryWithMultipleExtensions, getModifiedTime(path) { try { return _fs.statSync(path).mtime; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a386b3c1ff2..d43cd9f910d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1670,7 +1670,6 @@ namespace ts { export interface ParseConfigHost { readDirectory(rootDir: string, extension: string, exclude: string[]): string[]; - readDirectoryWithMultipleExtensions?(rootDir: string, extensions: string[], exclude: string[]): string[]; } export interface WriteFileCallback { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5af2989a717..572acc61a18 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1184,12 +1184,10 @@ namespace ts.server { * @param fileContent is a known version of the file content that is more up to date than the one on disk */ openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind): { configFileName?: string, configFileErrors?: Diagnostic[] } { - this.log("start openClientFile: " + new Date().getTime()); const { configFileName, configFileErrors } = this.openOrUpdateConfiguredProjectForFile(fileName); const info = this.openFile(fileName, /*openedByClient*/ true, fileContent, scriptKind); this.addOpenFile(info); this.printProjects(); - this.log("end openClientFile: " + new Date().getTime()); return { configFileName, configFileErrors }; } @@ -1199,7 +1197,6 @@ namespace ts.server { * the tsconfig file content and update the project; otherwise we create a new one. */ openOrUpdateConfiguredProjectForFile(fileName: string): { configFileName?: string, configFileErrors?: Diagnostic[] } { - this.log("start openOrUpdateConfiguredProjectForFile: " + new Date().getTime()); const searchPath = ts.normalizePath(getDirectoryPath(fileName)); this.log("Search path: " + searchPath, "Info"); const configFileName = this.findConfigFile(searchPath); @@ -1228,7 +1225,6 @@ namespace ts.server { else { this.log("No config files found."); } - this.log("end openOrUpdateConfiguredProjectForFile: " + new Date().getTime()); return configFileName ? { configFileName } : {}; } @@ -1368,11 +1364,8 @@ namespace ts.server { } openConfigFile(configFilename: string, clientFileName?: string): { success: boolean, project?: Project, errors?: Diagnostic[] } { - this.log("start openConfigFile: " + new Date().getTime()); const { succeeded, projectOptions, errors } = this.configFileToProjectOptions(configFilename); - this.log("finish reading config file: " + new Date().getTime()); if (!succeeded) { - this.log("finish openConfigFile: " + new Date().getTime()); return { success: false, errors }; } else { @@ -1408,7 +1401,6 @@ namespace ts.server { path => this.directoryWatchedForSourceFilesChanged(project, path), /*recursive*/ true ); - this.log("finish openConfigFile: " + new Date().getTime()); return { success: true, project: project, errors }; } } diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 4ebf1aff5d6..4cdd331f1d9 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -124,8 +124,8 @@ declare namespace ts.server.protocol { */ fileNames?: string[]; /** - * Indicates if the project has a active language service instance - */ + * Indicates if the project has a active language service instance + */ languageServiceDisabled?: boolean; } diff --git a/src/server/session.ts b/src/server/session.ts index 17fe4b758f2..fa5fd378a0b 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -421,7 +421,6 @@ namespace ts.server { } private getProjectInfo(fileName: string, needFileNameList: boolean): protocol.ProjectInfo { - this.logger.info("start getProjectInfo:" + new Date().getTime()); fileName = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(fileName); if (!project) { @@ -436,7 +435,6 @@ namespace ts.server { if (needFileNameList) { projectInfo.fileNames = project.getFileNames(); } - this.logger.info("end getProjectInfo:" + new Date().getTime()); return projectInfo; } diff --git a/tests/cases/unittests/tsserverProjectSystem.ts b/tests/cases/unittests/tsserverProjectSystem.ts index cf382d09463..d7b808f79b7 100644 --- a/tests/cases/unittests/tsserverProjectSystem.ts +++ b/tests/cases/unittests/tsserverProjectSystem.ts @@ -25,6 +25,7 @@ namespace ts { interface FileOrFolder { path: string; content?: string; + fileSize?: number; } interface FSEntry { @@ -34,6 +35,7 @@ namespace ts { interface File extends FSEntry { content: string; + fileSize?: number; } interface Folder extends FSEntry { @@ -157,7 +159,7 @@ namespace ts { const path = this.toPath(fileOrFolder.path); const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); if (typeof fileOrFolder.content === "string") { - const entry = { path, content: fileOrFolder.content, fullPath }; + const entry = { path, content: fileOrFolder.content, fullPath, fileSize: fileOrFolder.fileSize }; this.fs.set(path, entry); addFolder(getDirectoryPath(fullPath), this.toPath, this.fs).entries.push(entry); } @@ -172,6 +174,17 @@ namespace ts { return this.fs.contains(path) && isFile(this.fs.get(path)); }; + getFileSize(s: string) { + const path = this.toPath(s); + if (this.fs.contains(path)) { + const entry = this.fs.get(path); + if (isFile(entry)) { + return entry.fileSize ? entry.fileSize : entry.content.length; + } + } + return undefined; + } + directoryExists(s: string) { const path = this.toPath(s); return this.fs.contains(path) && isFolder(this.fs.get(path)); @@ -567,5 +580,38 @@ namespace ts { checkConfiguredProjectActualFiles(project, [file1.path, classicModuleFile.path]); checkNumberOfInferredProjects(projectService, 1); }); + + it("should disable language service for project with too many non-ts files", () => { + const jsFiles: FileOrFolder[] = []; + const configFile: FileOrFolder = { + path: `/a/b/jsconfig.json`, + content: "{}" + }; + jsFiles.push(configFile); + for (let i = 0; i < 1000; i++) { + jsFiles.push({ + path: `/a/b/file${i}.js`, + content: "", + fileSize: 50000 + }); + } + + const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", jsFiles); + const projectService = new server.ProjectService(host, nullLogger); + projectService.openClientFile(jsFiles[1].path); + projectService.openClientFile(jsFiles[2].path); + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + + const project = projectService.configuredProjects[0]; + assert(project.languageServiceDiabled, "the project's language service is expected to be disabled"); + + configFile.content = `{ + "files": ["/a/b/file1.js", "/a/b/file2.js", "/a/b/file3.js"] + }`; + host.reloadFS(jsFiles); + host.triggerFileWatcherCallback(configFile.path); + assert(!project.languageServiceDiabled, "after the config file change, the project's language service is expected to be enabled."); + }); }); } \ No newline at end of file