From 4d3cff1e5a6f34e967bcda1ee1cf002295b8f9e3 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 11 Mar 2016 15:56:36 -0800 Subject: [PATCH 01/13] Add upper limit for the program size, fix readDirectory for the symlink files --- src/compiler/commandLineParser.ts | 2 +- src/compiler/diagnosticMessages.json | 4 ++++ src/compiler/program.ts | 16 ++++++++++++- src/compiler/sys.ts | 12 +++++----- src/compiler/utilities.ts | 6 +++++ src/server/editorServices.ts | 36 ++++++++++++++++++++++++---- 6 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index cf55f030c3382..a11d06a644585 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -566,7 +566,7 @@ namespace ts { } else { // by default exclude node_modules, and any specificied output directory - exclude = ["node_modules"]; + exclude = ["node_modules", "bower_components"]; const outDir = json["compilerOptions"] && json["compilerOptions"]["outDir"]; if (outDir) { exclude.push(outDir); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 15a3a4e5ef474..b5208e3aad4dc 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2824,5 +2824,9 @@ "Unknown typing option '{0}'.": { "category": "Error", "code": 17010 + }, + "Too many javascript files in the project. Consider add to the `exclude` list in the config file.": { + "category": "Error", + "code": 17012 } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 495fbde5a9992..406eeb6cd9112 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -748,7 +748,21 @@ namespace ts { } if (!tryReuseStructureFromOldProgram()) { - forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false)); + let programSize = 0; + for (const name of rootNames) { + const path = toPath(name, currentDirectory, getCanonicalFileName); + if (programSize <= maxProgramSize) { + processRootFile(name, /*isDefaultLib*/ false); + if (!hasTypeScriptFileExtension(name) && filesByName.get(path)) { + programSize += filesByName.get(path).text.length; + } + } + else { + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Too_many_javascript_files_in_the_project_Consider_add_to_the_exclude_list_in_the_config_file)); + break; + } + } + // Do not process the default library if: // - The '--noLib' flag is used. // - A 'no-default-lib' reference comment is encountered in diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 4cf94655b354f..6d7847ab588a6 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -74,7 +74,7 @@ namespace ts { watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; }; - export var sys: System = (function () { + export var sys: System = (function() { function getWScriptSystem(): System { @@ -404,8 +404,8 @@ namespace ts { const watchedFileSet = createWatchedFileSet(); function isNode4OrLater(): boolean { - return parseInt(process.version.charAt(1)) >= 4; - } + return parseInt(process.version.charAt(1)) >= 4; + } const platform: string = _os.platform(); // win32\win64 are case insensitive platforms, MacOS (darwin) by default is also case insensitive @@ -500,7 +500,7 @@ namespace ts { for (const current of files) { const name = combinePaths(path, current); if (!contains(exclude, getCanonicalPath(name))) { - const stat = _fs.statSync(name); + const stat = _fs.lstatSync(name); if (stat.isFile()) { if (!extension || fileExtensionIs(name, extension)) { result.push(name); @@ -532,7 +532,7 @@ namespace ts { // and https://github.com/Microsoft/TypeScript/issues/4643), therefore // if the current node.js version is newer than 4, use `fs.watch` instead. const watchSet = isNode4OrLater() ? watchedFileSet : pollingWatchedFileSet; - const watchedFile = watchSet.addFile(filePath, callback); + const watchedFile = watchSet.addFile(filePath, callback); return { close: () => watchSet.removeFile(watchedFile) }; @@ -562,7 +562,7 @@ namespace ts { } ); }, - resolvePath: function (path: string): string { + resolvePath: function(path: string): string { return _path.resolve(path); }, fileExists, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index de10d469c87fa..72ad016f5964d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2484,6 +2484,10 @@ namespace ts { return forEach(supportedJavascriptExtensions, extension => fileExtensionIs(fileName, extension)); } + export function hasTypeScriptFileExtension(fileName: string) { + return forEach(supportedTypeScriptExtensions, extension => fileExtensionIs(fileName, extension)); + } + /** * Replace each instance of non-ascii characters by one, two, three, or four escape sequences * representing the UTF-8 encoding of the character, and return the expanded char code list. @@ -2866,4 +2870,6 @@ namespace ts { export function isParameterPropertyDeclaration(node: ParameterDeclaration): boolean { return node.flags & NodeFlags.AccessibilityModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent); } + + export const maxProgramSize = 35 * 1024 * 1024; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 71907735b9125..26b40b3d908d9 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1217,13 +1217,35 @@ namespace ts.server { } else { const project = this.createProject(configFilename, projectOptions); + let programSize = 0; + + // As the project openning might not be complete if there are too many files, + // therefore to surface the diagnostics we need to make sure the given client file is opened. + if (clientFileName) { + const currentClientFileInfo = this.openFile(clientFileName, /*openedByClient*/ true); + project.addRoot(currentClientFileInfo); + programSize += currentClientFileInfo.content.length; + } + for (const rootFilename of projectOptions.files) { - if (this.host.fileExists(rootFilename)) { - const info = this.openFile(rootFilename, /*openedByClient*/ clientFileName == rootFilename); - project.addRoot(info); + if (rootFilename === clientFileName) { + continue; + } + + if (programSize <= maxProgramSize) { + if (this.host.fileExists(rootFilename)) { + const info = this.openFile(rootFilename, /*openedByClient*/ false); + project.addRoot(info); + if (!hasTypeScriptFileExtension(rootFilename)) { + programSize += info.content.length; + } + } + else { + return { errorMsg: "specified file " + rootFilename + " not found" }; + } } else { - return { errorMsg: "specified file " + rootFilename + " not found" }; + break; } } project.finishGraph(); @@ -1251,11 +1273,15 @@ namespace ts.server { return error; } else { - const oldFileNames = project.compilerService.host.roots.map(info => info.fileName); + const oldFileNames = project.projectOptions ? project.projectOptions.files : project.compilerService.host.roots.map(info => info.fileName); const newFileNames = projectOptions.files; const fileNamesToRemove = oldFileNames.filter(f => newFileNames.indexOf(f) < 0); const fileNamesToAdd = newFileNames.filter(f => oldFileNames.indexOf(f) < 0); + if (fileNamesToAdd.length === 0 && fileNamesToRemove.length === 0) { + return; + } + for (const fileName of fileNamesToRemove) { const info = this.getScriptInfo(fileName); if (info) { From b155fa847a6dd9d069bf6cb2e8e7dcebb16cda08 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 11 Mar 2016 16:18:40 -0800 Subject: [PATCH 02/13] Add comments --- src/compiler/sys.ts | 3 +++ src/server/editorServices.ts | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 6d7847ab588a6..c70985cd018a4 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -500,6 +500,9 @@ namespace ts { for (const current of files) { 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. fs.lstatSync would return a stat + // object for the symlink file itself in this case const stat = _fs.lstatSync(name); if (stat.isFile()) { if (!extension || fileExtensionIs(name, extension)) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 26b40b3d908d9..4b49c2944cd3f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1273,15 +1273,14 @@ namespace ts.server { return error; } else { + // if the project is too large, the root files might not have been all loaded if the total + // program size reached the upper limit. In that case project.projectOptions.files should + // be more precise. However this would only happen for configured project. const oldFileNames = project.projectOptions ? project.projectOptions.files : project.compilerService.host.roots.map(info => info.fileName); const newFileNames = projectOptions.files; const fileNamesToRemove = oldFileNames.filter(f => newFileNames.indexOf(f) < 0); const fileNamesToAdd = newFileNames.filter(f => oldFileNames.indexOf(f) < 0); - if (fileNamesToAdd.length === 0 && fileNamesToRemove.length === 0) { - return; - } - for (const fileName of fileNamesToRemove) { const info = this.getScriptInfo(fileName); if (info) { From a3aa0002a3a88b3225ef97f0ff4aacf748b53827 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Mon, 14 Mar 2016 14:51:24 -0700 Subject: [PATCH 03/13] CR feedback / Change upper limit / Add disableSizeLimit compiler option --- src/compiler/commandLineParser.ts | 5 ++++ src/compiler/diagnosticMessages.json | 7 +++-- src/compiler/program.ts | 38 +++++++++++++++++++--------- src/compiler/sys.ts | 20 ++++++++------- src/compiler/types.ts | 1 + src/compiler/utilities.ts | 2 +- src/server/editorServices.ts | 23 ++++++++++++----- 7 files changed, 65 insertions(+), 31 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index a11d06a644585..72deb16f0c13f 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -332,6 +332,11 @@ namespace ts { name: "noImplicitUseStrict", type: "boolean", description: Diagnostics.Do_not_emit_use_strict_directives_in_module_output + }, + { + name: "disableSizeLimit", + type: "boolean", + description: Diagnostics.Disable_the_upper_limit_for_the_total_file_size_of_a_project } ]; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b5208e3aad4dc..d96b10f61ade0 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2620,7 +2620,10 @@ "category": "Message", "code": 6112 }, - + "Disable the upper limit for the total file size of a project.": { + "category": "Message", + "code": 6113 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 @@ -2825,7 +2828,7 @@ "category": "Error", "code": 17010 }, - "Too many javascript files in the project. Consider add to the `exclude` list in the config file.": { + "Too many JavaScript files in the project. Use an exact 'files' list, or use the 'exclude' setting in project configuration to limit included source folders. The likely folder to exclude is '{0}'. To disable the project size limit, set the 'disableSizeLimit' compiler option to 'true'": { "category": "Error", "code": 17012 } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 406eeb6cd9112..cdb4d3b417cc3 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -742,24 +742,38 @@ namespace ts { (oldOptions.target !== options.target) || (oldOptions.noLib !== options.noLib) || (oldOptions.jsx !== options.jsx) || - (oldOptions.allowJs !== options.allowJs)) { + (oldOptions.allowJs !== options.allowJs) || + (oldOptions.disableSizeLimit !== options.disableSizeLimit)) { oldProgram = undefined; } } if (!tryReuseStructureFromOldProgram()) { - let programSize = 0; - for (const name of rootNames) { - const path = toPath(name, currentDirectory, getCanonicalFileName); - if (programSize <= maxProgramSize) { - processRootFile(name, /*isDefaultLib*/ false); - if (!hasTypeScriptFileExtension(name) && filesByName.get(path)) { - programSize += filesByName.get(path).text.length; + if (options.disableSizeLimit === true) { + forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false)); + } + else { + let programSize = 0; + for (const name of rootNames) { + const path = toPath(name, currentDirectory, getCanonicalFileName); + if (programSize <= maxProgramSize) { + processRootFile(name, /*isDefaultLib*/ false); + const file = filesByName.get(path); + if (!hasTypeScriptFileExtension(name) && file && file.text) { + programSize += file.text.length; + } + } + else { + // If the program size limit was reached when processing a file, this file is + // likely in the problematic folder than contains too many files + const commonSourceDirectory = getCommonSourceDirectory(); + let rootLevelDirectory = path.substring(0, Math.max(commonSourceDirectory.length, path.indexOf(directorySeparator, commonSourceDirectory.length))); + if (rootLevelDirectory[rootLevelDirectory.length - 1] !== directorySeparator) { + rootLevelDirectory += directorySeparator; + } + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Too_many_JavaScript_files_in_the_project_Use_an_exact_files_list_or_use_the_exclude_setting_in_project_configuration_to_limit_included_source_folders_The_likely_folder_to_exclude_is_0_To_disable_the_project_size_limit_set_the_disableSizeLimit_compiler_option_to_true, rootLevelDirectory)); + break; } - } - else { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Too_many_javascript_files_in_the_project_Consider_add_to_the_exclude_list_in_the_config_file)); - break; } } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index c70985cd018a4..1c7164aeb01a5 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -501,17 +501,19 @@ namespace ts { 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. fs.lstatSync would return a stat - // object for the symlink file itself in this case - const stat = _fs.lstatSync(name); - if (stat.isFile()) { - if (!extension || fileExtensionIs(name, extension)) { - result.push(name); + // whose linked file doesn't exist. + try { + const stat = _fs.statSync(name); + if (stat.isFile()) { + if (!extension || fileExtensionIs(name, extension)) { + result.push(name); + } + } + else if (stat.isDirectory()) { + directories.push(name); } } - else if (stat.isDirectory()) { - directories.push(name); - } + catch (e) { } } } for (const current of directories) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 155f5c1a77fb4..73dc959e16385 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2437,6 +2437,7 @@ namespace ts { allowSyntheticDefaultImports?: boolean; allowJs?: boolean; noImplicitUseStrict?: boolean; + disableSizeLimit?: boolean; /* @internal */ stripInternal?: boolean; // Skip checking lib.d.ts to help speed up tests. diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 72ad016f5964d..9073e481729a1 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2871,5 +2871,5 @@ namespace ts { return node.flags & NodeFlags.AccessibilityModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent); } - export const maxProgramSize = 35 * 1024 * 1024; + export const maxProgramSize = 20 * 1024 * 1024; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4b49c2944cd3f..9cbb038f04e75 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1222,9 +1222,14 @@ namespace ts.server { // As the project openning might not be complete if there are too many files, // therefore to surface the diagnostics we need to make sure the given client file is opened. if (clientFileName) { - const currentClientFileInfo = this.openFile(clientFileName, /*openedByClient*/ true); - project.addRoot(currentClientFileInfo); - programSize += currentClientFileInfo.content.length; + if (this.host.fileExists(clientFileName)) { + const currentClientFileInfo = this.openFile(clientFileName, /*openedByClient*/ true); + project.addRoot(currentClientFileInfo); + programSize += currentClientFileInfo.content.length; + } + else { + return { errorMsg: "specified file " + clientFileName + " not found" }; + } } for (const rootFilename of projectOptions.files) { @@ -1232,8 +1237,12 @@ namespace ts.server { continue; } - if (programSize <= maxProgramSize) { - if (this.host.fileExists(rootFilename)) { + if (this.host.fileExists(rootFilename)) { + if (projectOptions.compilerOptions.disableSizeLimit === true) { + const info = this.openFile(rootFilename, /*openedByClient*/ false); + project.addRoot(info); + } + else if (programSize <= maxProgramSize) { const info = this.openFile(rootFilename, /*openedByClient*/ false); project.addRoot(info); if (!hasTypeScriptFileExtension(rootFilename)) { @@ -1241,11 +1250,11 @@ namespace ts.server { } } else { - return { errorMsg: "specified file " + rootFilename + " not found" }; + break; } } else { - break; + return { errorMsg: "specified file " + rootFilename + " not found" }; } } project.finishGraph(); From a6a466c752298446ccde7894cf62a4365d6eee25 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Mon, 14 Mar 2016 20:14:17 -0700 Subject: [PATCH 04/13] online and offline CR feedback --- src/compiler/commandLineParser.ts | 3 +- src/compiler/diagnosticMessages.json | 6 +-- src/compiler/program.ts | 55 ++++++++++++++-------------- src/compiler/utilities.ts | 2 +- src/server/editorServices.ts | 10 ++--- src/services/services.ts | 3 +- 6 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 72deb16f0c13f..3b7f423bf860a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -335,8 +335,7 @@ namespace ts { }, { name: "disableSizeLimit", - type: "boolean", - description: Diagnostics.Disable_the_upper_limit_for_the_total_file_size_of_a_project + type: "boolean" } ]; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d96b10f61ade0..f8a7b627a7cf7 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2620,10 +2620,6 @@ "category": "Message", "code": 6112 }, - "Disable the upper limit for the total file size of a project.": { - "category": "Message", - "code": 6113 - }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 @@ -2828,7 +2824,7 @@ "category": "Error", "code": 17010 }, - "Too many JavaScript files in the project. Use an exact 'files' list, or use the 'exclude' setting in project configuration to limit included source folders. The likely folder to exclude is '{0}'. To disable the project size limit, set the 'disableSizeLimit' compiler option to 'true'": { + "Too many JavaScript files in the project. Consider specifying the 'exclude' setting in project configuration to limit included source folders. The likely folder to exclude is '{0}'. To disable the project size limit, set the 'disableSizeLimit' compiler option to 'true'.": { "category": "Error", "code": 17012 } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index cdb4d3b417cc3..8f2f95fa4fe6b 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -696,6 +696,7 @@ namespace ts { let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; let classifiableNames: Map; + let programSizeForNonTsFiles = 0; let skipDefaultLib = options.noLib; const supportedExtensions = getSupportedExtensions(options); @@ -749,34 +750,7 @@ namespace ts { } if (!tryReuseStructureFromOldProgram()) { - if (options.disableSizeLimit === true) { - forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false)); - } - else { - let programSize = 0; - for (const name of rootNames) { - const path = toPath(name, currentDirectory, getCanonicalFileName); - if (programSize <= maxProgramSize) { - processRootFile(name, /*isDefaultLib*/ false); - const file = filesByName.get(path); - if (!hasTypeScriptFileExtension(name) && file && file.text) { - programSize += file.text.length; - } - } - else { - // If the program size limit was reached when processing a file, this file is - // likely in the problematic folder than contains too many files - const commonSourceDirectory = getCommonSourceDirectory(); - let rootLevelDirectory = path.substring(0, Math.max(commonSourceDirectory.length, path.indexOf(directorySeparator, commonSourceDirectory.length))); - if (rootLevelDirectory[rootLevelDirectory.length - 1] !== directorySeparator) { - rootLevelDirectory += directorySeparator; - } - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Too_many_JavaScript_files_in_the_project_Use_an_exact_files_list_or_use_the_exclude_setting_in_project_configuration_to_limit_included_source_folders_The_likely_folder_to_exclude_is_0_To_disable_the_project_size_limit_set_the_disableSizeLimit_compiler_option_to_true, rootLevelDirectory)); - break; - } - } - } - + forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false)); // Do not process the default library if: // - The '--noLib' flag is used. // - A 'no-default-lib' reference comment is encountered in @@ -1480,6 +1454,27 @@ namespace ts { return file; } + if (!options.disableSizeLimit) { + if (programSizeForNonTsFiles === -1) { + return; + } + if (programSizeForNonTsFiles > maxProgramSizeForNonTsFiles) { + // If the program size limit was reached when processing a file, this file is + // likely in the problematic folder than contains too many files. + // Normally the folder is one level down from the commonSourceDirectory, for example, + // if the commonSourceDirectory is "/src/", and the last processed path was "/src/node_modules/a/b.js", + // we should show in the error message "/src/node_modules/". + const commonSourceDirectory = getCommonSourceDirectory(); + let rootLevelDirectory = path.substring(0, Math.max(commonSourceDirectory.length, path.indexOf(directorySeparator, commonSourceDirectory.length))); + if (rootLevelDirectory[rootLevelDirectory.length - 1] !== directorySeparator) { + rootLevelDirectory += directorySeparator; + } + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Too_many_JavaScript_files_in_the_project_Consider_specifying_the_exclude_setting_in_project_configuration_to_limit_included_source_folders_The_likely_folder_to_exclude_is_0_To_disable_the_project_size_limit_set_the_disableSizeLimit_compiler_option_to_true, rootLevelDirectory)); + programSizeForNonTsFiles = -1; + return; + } + } + // We haven't looked for this file, do so now and cache result const file = host.getSourceFile(fileName, options.target, hostErrorMessage => { if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) { @@ -1491,6 +1486,10 @@ namespace ts { } }); + if (!options.disableSizeLimit && file && file.text && !hasTypeScriptFileExtension(file.fileName)) { + programSizeForNonTsFiles += file.text.length; + } + filesByName.set(path, file); if (file) { file.wasReferenced = file.wasReferenced || isReference; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 9073e481729a1..7974fb66cd601 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2871,5 +2871,5 @@ namespace ts { return node.flags & NodeFlags.AccessibilityModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent); } - export const maxProgramSize = 20 * 1024 * 1024; + export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9cbb038f04e75..456ff0ed26a1f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1217,7 +1217,7 @@ namespace ts.server { } else { const project = this.createProject(configFilename, projectOptions); - let programSize = 0; + let programSizeForNonTsFiles = 0; // As the project openning might not be complete if there are too many files, // therefore to surface the diagnostics we need to make sure the given client file is opened. @@ -1225,7 +1225,7 @@ namespace ts.server { if (this.host.fileExists(clientFileName)) { const currentClientFileInfo = this.openFile(clientFileName, /*openedByClient*/ true); project.addRoot(currentClientFileInfo); - programSize += currentClientFileInfo.content.length; + programSizeForNonTsFiles += currentClientFileInfo.content.length; } else { return { errorMsg: "specified file " + clientFileName + " not found" }; @@ -1238,15 +1238,15 @@ namespace ts.server { } if (this.host.fileExists(rootFilename)) { - if (projectOptions.compilerOptions.disableSizeLimit === true) { + if (projectOptions.compilerOptions.disableSizeLimit) { const info = this.openFile(rootFilename, /*openedByClient*/ false); project.addRoot(info); } - else if (programSize <= maxProgramSize) { + else if (programSizeForNonTsFiles <= maxProgramSizeForNonTsFiles) { const info = this.openFile(rootFilename, /*openedByClient*/ false); project.addRoot(info); if (!hasTypeScriptFileExtension(rootFilename)) { - programSize += info.content.length; + programSizeForNonTsFiles += info.content.length; } } else { diff --git a/src/services/services.ts b/src/services/services.ts index a562ecf18cf13..e72c647db1bb4 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2760,7 +2760,8 @@ namespace ts { oldSettings.module !== newSettings.module || oldSettings.noResolve !== newSettings.noResolve || oldSettings.jsx !== newSettings.jsx || - oldSettings.allowJs !== newSettings.allowJs); + oldSettings.allowJs !== newSettings.allowJs || + oldSettings.disableSizeLimit !== oldSettings.disableSizeLimit); // Now create a new compiler const compilerHost: CompilerHost = { From d4eb3b8d12420494d64d42b40e203c86c58daa3c Mon Sep 17 00:00:00 2001 From: zhengbli Date: Mon, 14 Mar 2016 20:43:22 -0700 Subject: [PATCH 05/13] Don't count current opened client file if it's TS file --- src/server/editorServices.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 456ff0ed26a1f..6f8006986ed6c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1225,7 +1225,9 @@ namespace ts.server { if (this.host.fileExists(clientFileName)) { const currentClientFileInfo = this.openFile(clientFileName, /*openedByClient*/ true); project.addRoot(currentClientFileInfo); - programSizeForNonTsFiles += currentClientFileInfo.content.length; + if (!hasTypeScriptFileExtension(currentClientFileInfo.fileName) && currentClientFileInfo.content) { + programSizeForNonTsFiles += currentClientFileInfo.content.length; + } } else { return { errorMsg: "specified file " + clientFileName + " not found" }; From 225e3b4f45e0c300b7f8dda3df1c1886887ed95e Mon Sep 17 00:00:00 2001 From: zhengbli Date: Wed, 16 Mar 2016 23:01:24 -0700 Subject: [PATCH 06/13] Speed up file searching --- src/compiler/commandLineParser.ts | 4 ++-- src/compiler/sys.ts | 17 +++++++++++++++-- src/compiler/types.ts | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 34a84ac793f8b..f535140eeaf66 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -637,10 +637,10 @@ namespace ts { const supportedExtensions = getSupportedExtensions(options); Debug.assert(indexOf(supportedExtensions, ".ts") < indexOf(supportedExtensions, ".d.ts"), "Changed priority of extensions to pick"); + const potentialFiles = host.readDirectory(basePath, supportedExtensions, exclude); // Get files of supported extensions in their order of resolution for (const extension of supportedExtensions) { - const filesInDirWithExtension = host.readDirectory(basePath, extension, exclude); - for (const fileName of filesInDirWithExtension) { + for (const fileName of potentialFiles) { // .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 1c7164aeb01a5..924fe92990b5c 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -489,11 +489,24 @@ namespace ts { return fileSystemEntryExists(path, FileSystemEntryKind.Directory); } - function readDirectory(path: string, extension?: string, exclude?: string[]): string[] { + function readDirectory(path: string, extension?: string | string[], exclude?: string[]): string[] { const result: string[] = []; exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); visitDirectory(path); return result; + + function checkExtension(name: string) { + if (!extension) { + return true; + } + if (typeof extension === "string") { + return fileExtensionIs(name, extension); + } + if (typeof extension === "string[]") { + return forEach(extension, ext => fileExtensionIs(name, ext)); + } + } + function visitDirectory(path: string) { const files = _fs.readdirSync(path || ".").sort(); const directories: string[] = []; @@ -505,7 +518,7 @@ namespace ts { try { const stat = _fs.statSync(name); if (stat.isFile()) { - if (!extension || fileExtensionIs(name, extension)) { + if (checkExtension(name)) { result.push(name); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 95a1e32c401dd..af765eb17b0cb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1580,7 +1580,7 @@ namespace ts { } export interface ParseConfigHost { - readDirectory(rootDir: string, extension: string, exclude: string[]): string[]; + readDirectory(rootDir: string, extension: string | string[], exclude: string[]): string[]; } export interface WriteFileCallback { From c8e0b000070bb5bc1568e99fb46b53b3492a3c50 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Thu, 17 Mar 2016 15:55:53 -0700 Subject: [PATCH 07/13] Make language service optional for a project --- src/compiler/commandLineParser.ts | 10 +- src/compiler/program.ts | 46 ++++---- src/compiler/sys.ts | 78 ++++++++------ src/compiler/types.ts | 5 +- src/compiler/utilities.ts | 2 - src/server/editorServices.ts | 170 +++++++++++++++++++++++------- src/server/protocol.d.ts | 1 + src/server/session.ts | 53 ++++++---- 8 files changed, 250 insertions(+), 115 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index f535140eeaf66..d7e58b20ca5cd 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -637,7 +637,15 @@ namespace ts { const supportedExtensions = getSupportedExtensions(options); Debug.assert(indexOf(supportedExtensions, ".ts") < indexOf(supportedExtensions, ".d.ts"), "Changed priority of extensions to pick"); - const potentialFiles = host.readDirectory(basePath, supportedExtensions, exclude); + 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) { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 2da1fffdb8b67..64a487baad2ec 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -7,6 +7,7 @@ namespace ts { /* @internal */ export let emitTime = 0; /* @internal */ export let ioReadTime = 0; /* @internal */ export let ioWriteTime = 0; + /* @internal */ export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; /** The version of the TypeScript compiler release */ @@ -696,6 +697,7 @@ namespace ts { let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; let classifiableNames: Map; + let programSizeLimitExceeded = false; let programSizeForNonTsFiles = 0; let skipDefaultLib = options.noLib; @@ -792,6 +794,11 @@ namespace ts { return program; + function exceedProgramSizeLimit() { + return !options.disableSizeLimit && programSizeLimitExceeded; + } + + function getCommonSourceDirectory() { if (typeof commonSourceDirectory === "undefined") { if (options.rootDir && checkSourceFilesBelongToPath(files, options.rootDir)) { @@ -1417,7 +1424,7 @@ namespace ts { } } - if (diagnostic) { + if (diagnostic && !exceedProgramSizeLimit()) { if (refFile !== undefined && refEnd !== undefined && refPos !== undefined) { fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, diagnostic, ...diagnosticArgument)); } @@ -1454,25 +1461,9 @@ namespace ts { return file; } - if (!options.disableSizeLimit) { - if (programSizeForNonTsFiles === -1) { - return; - } - if (programSizeForNonTsFiles > maxProgramSizeForNonTsFiles) { - // If the program size limit was reached when processing a file, this file is - // likely in the problematic folder than contains too many files. - // Normally the folder is one level down from the commonSourceDirectory, for example, - // if the commonSourceDirectory is "/src/", and the last processed path was "/src/node_modules/a/b.js", - // we should show in the error message "/src/node_modules/". - const commonSourceDirectory = getCommonSourceDirectory(); - let rootLevelDirectory = path.substring(0, Math.max(commonSourceDirectory.length, path.indexOf(directorySeparator, commonSourceDirectory.length))); - if (rootLevelDirectory[rootLevelDirectory.length - 1] !== directorySeparator) { - rootLevelDirectory += directorySeparator; - } - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Too_many_JavaScript_files_in_the_project_Consider_specifying_the_exclude_setting_in_project_configuration_to_limit_included_source_folders_The_likely_folder_to_exclude_is_0_To_disable_the_project_size_limit_set_the_disableSizeLimit_compiler_option_to_true, rootLevelDirectory)); - programSizeForNonTsFiles = -1; - return; - } + const isNonTsFile = !hasTypeScriptFileExtension(fileName); + if (isNonTsFile && exceedProgramSizeLimit()) { + return undefined; } // We haven't looked for this file, do so now and cache result @@ -1488,6 +1479,21 @@ namespace ts { if (!options.disableSizeLimit && file && file.text && !hasTypeScriptFileExtension(file.fileName)) { programSizeForNonTsFiles += file.text.length; + if (programSizeForNonTsFiles > maxProgramSizeForNonTsFiles) { + // If the program size limit was reached when processing a file, this file is + // likely in the problematic folder than contains too many files. + // Normally the folder is one level down from the commonSourceDirectory, for example, + // if the commonSourceDirectory is "/src/", and the last processed path was "/src/node_modules/a/b.js", + // we should show in the error message "/src/node_modules/". + const commonSourceDirectory = getCommonSourceDirectory(); + let rootLevelDirectory = path.substring(0, Math.max(commonSourceDirectory.length, path.indexOf(directorySeparator, commonSourceDirectory.length))); + if (rootLevelDirectory[rootLevelDirectory.length - 1] !== directorySeparator) { + rootLevelDirectory += directorySeparator; + } + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Too_many_JavaScript_files_in_the_project_Consider_specifying_the_exclude_setting_in_project_configuration_to_limit_included_source_folders_The_likely_folder_to_exclude_is_0_To_disable_the_project_size_limit_set_the_disableSizeLimit_compiler_option_to_true, rootLevelDirectory)); + programSizeLimitExceeded = true; + return undefined; + } } filesByName.set(path, file); diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 924fe92990b5c..3b8b9b3f39f95 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -10,6 +10,7 @@ namespace ts { useCaseSensitiveFileNames: boolean; write(s: string): void; readFile(path: string, encoding?: string): string; + getFileSize?(path: string): number; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; watchFile?(path: Path, callback: FileWatcherCallback): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; @@ -20,6 +21,7 @@ namespace ts { getExecutingFilePath(): string; getCurrentDirectory(): string; readDirectory(path: string, extension?: string, exclude?: string[]): string[]; + readDirectoryWithMultipleExtensions?(path: string, extensions: string[], exclude?: string[]): string[]; getMemoryUsage?(): number; exit(exitCode?: number): void; } @@ -489,10 +491,32 @@ namespace ts { return fileSystemEntryExists(path, FileSystemEntryKind.Directory); } - function readDirectory(path: string, extension?: string | string[], exclude?: string[]): string[] { + function visitDirectory(path: string, extension: string | string[], exclude: string[]) { const result: string[] = []; - exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); - visitDirectory(path); + const files = _fs.readdirSync(path || ".").sort(); + const directories: string[] = []; + for (const current of files) { + 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 stat = _fs.statSync(name); + if (stat.isFile()) { + if (checkExtension(name)) { + result.push(name); + } + } + else if (stat.isDirectory()) { + directories.push(name); + } + } + catch (e) { } + } + } + for (const current of directories) { + visitDirectory(current, extension, exclude); + } return result; function checkExtension(name: string) { @@ -502,37 +526,20 @@ namespace ts { if (typeof extension === "string") { return fileExtensionIs(name, extension); } - if (typeof extension === "string[]") { + else { return forEach(extension, ext => fileExtensionIs(name, ext)); } } + } - function visitDirectory(path: string) { - const files = _fs.readdirSync(path || ".").sort(); - const directories: string[] = []; - for (const current of files) { - 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 stat = _fs.statSync(name); - if (stat.isFile()) { - if (checkExtension(name)) { - result.push(name); - } - } - else if (stat.isDirectory()) { - directories.push(name); - } - } - catch (e) { } - } - } - for (const current of directories) { - visitDirectory(current); - } - } + function readDirectoryWithMultipleExtensions(path: string, extensions: string[], exclude?: string[]): string[] { + exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); + return visitDirectory(path, extensions, exclude); + } + + function readDirectory(path: string, extension?: string, exclude?: string[]): string[] { + exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); + return visitDirectory(path, extension, exclude); } return { @@ -597,12 +604,23 @@ namespace ts { return process.cwd(); }, readDirectory, + readDirectoryWithMultipleExtensions, getMemoryUsage() { if (global.gc) { global.gc(); } return process.memoryUsage().heapUsed; }, + getFileSize(path) { + try { + const stat = _fs.statSync(path); + if (stat.isFile()) { + return stat.size; + } + } + catch (e) { } + return 0; + }, exit(exitCode?: number): void { process.exit(exitCode); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index af765eb17b0cb..3638cc3a92f23 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1580,7 +1580,8 @@ namespace ts { } export interface ParseConfigHost { - readDirectory(rootDir: string, extension: string | string[], exclude: string[]): string[]; + readDirectory(rootDir: string, extension: string, exclude: string[]): string[]; + readDirectoryWithMultipleExtensions?(rootDir: string, extensions: string[], exclude: string[]): string[]; } export interface WriteFileCallback { @@ -2440,7 +2441,7 @@ namespace ts { allowJs?: boolean; noImplicitUseStrict?: boolean; disableSizeLimit?: boolean; - lib?: string[]; + lib?: string[]; /* @internal */ stripInternal?: boolean; // Skip checking lib.d.ts to help speed up tests. diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 68db246cb9a3f..6520e339ac909 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2870,6 +2870,4 @@ namespace ts { export function isParameterPropertyDeclaration(node: ParameterDeclaration): boolean { return node.flags & NodeFlags.AccessibilityModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent); } - - export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 8c3429ac69890..2eef384df5c54 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -347,12 +347,29 @@ namespace ts.server { /** Used for configured projects which may have multiple open roots */ openRefCount = 0; - constructor(public projectService: ProjectService, public projectOptions?: ProjectOptions) { + constructor( + public projectService: ProjectService, + public projectOptions?: ProjectOptions, + public languageServiceDiabled?: boolean) { if (projectOptions && projectOptions.files) { // If files are listed explicitly, allow all extensions projectOptions.compilerOptions.allowNonTsExtensions = true; } - this.compilerService = new CompilerService(this, projectOptions && projectOptions.compilerOptions); + if (!languageServiceDiabled) { + this.compilerService = new CompilerService(this, projectOptions && projectOptions.compilerOptions); + } + } + + enableLanguageService() { + // if the language service was disabled, we should re-initiate the compiler service + if (this.languageServiceDiabled) { + this.compilerService = new CompilerService(this, this.projectOptions && this.projectOptions.compilerOptions); + } + this.languageServiceDiabled = false; + } + + disableLanguageService() { + this.languageServiceDiabled = true; } addOpenRef() { @@ -369,19 +386,36 @@ namespace ts.server { } getRootFiles() { + if (this.languageServiceDiabled) { + // When the languageService was disabled, only return file list if it is a configured project + return this.projectOptions ? this.projectOptions.files : undefined; + } + return this.compilerService.host.roots.map(info => info.fileName); } getFileNames() { + if (this.languageServiceDiabled) { + return this.projectOptions ? this.projectOptions.files : undefined; + } + const sourceFiles = this.program.getSourceFiles(); return sourceFiles.map(sourceFile => sourceFile.fileName); } getSourceFile(info: ScriptInfo) { + if (this.languageServiceDiabled) { + return undefined; + } + return this.filenameToSourceFile[info.fileName]; } getSourceFileFromName(filename: string, requireOpen?: boolean) { + if (this.languageServiceDiabled) { + return undefined; + } + const info = this.projectService.getScriptInfo(filename); if (info) { if ((!requireOpen) || info.isOpen) { @@ -391,15 +425,30 @@ namespace ts.server { } isRoot(info: ScriptInfo) { + if (this.languageServiceDiabled) { + if (!this.projectOptions) { + return undefined; + } + return forEach(this.projectOptions.files, file => toPath(file, file, createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames)) === info.path); + } + return this.compilerService.host.roots.some(root => root === info); } removeReferencedFile(info: ScriptInfo) { + if (this.languageServiceDiabled) { + return; + } + this.compilerService.host.removeReferencedFile(info); this.updateGraph(); } updateFileMap() { + if (this.languageServiceDiabled) { + return; + } + this.filenameToSourceFile = {}; const sourceFiles = this.program.getSourceFiles(); for (let i = 0, len = sourceFiles.length; i < len; i++) { @@ -409,11 +458,19 @@ namespace ts.server { } finishGraph() { + if (this.languageServiceDiabled) { + return; + } + this.updateGraph(); this.compilerService.languageService.getNavigateToItems(".*"); } updateGraph() { + if (this.languageServiceDiabled) { + return; + } + this.program = this.compilerService.languageService.getProgram(); this.updateFileMap(); } @@ -424,15 +481,32 @@ namespace ts.server { // add a root file to project addRoot(info: ScriptInfo) { + if (this.languageServiceDiabled) { + return; + } + this.compilerService.host.addRoot(info); } // remove a root file from project removeRoot(info: ScriptInfo) { + if (this.languageServiceDiabled) { + return; + } + this.compilerService.host.removeRoot(info); } filesToString() { + if (this.languageServiceDiabled) { + if (this.projectOptions) { + let strBuilder = ""; + ts.forEach(this.projectOptions.files, + file => { strBuilder += file + "\n"; }); + return strBuilder; + } + } + let strBuilder = ""; ts.forEachValue(this.filenameToSourceFile, sourceFile => { strBuilder += sourceFile.fileName + "\n"; }); @@ -443,7 +517,9 @@ namespace ts.server { this.projectOptions = projectOptions; if (projectOptions.compilerOptions) { projectOptions.compilerOptions.allowNonTsExtensions = true; - this.compilerService.setCompilerOptions(projectOptions.compilerOptions); + if (!this.languageServiceDiabled) { + this.compilerService.setCompilerOptions(projectOptions.compilerOptions); + } } } } @@ -1055,10 +1131,12 @@ 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) { + this.log("start openClientFile: " + new Date().getTime()); this.openOrUpdateConfiguredProjectForFile(fileName); const info = this.openFile(fileName, /*openedByClient*/ true, fileContent); this.addOpenFile(info); this.printProjects(); + this.log("end openClientFile: " + new Date().getTime()); return info; } @@ -1068,6 +1146,7 @@ namespace ts.server { * the tsconfig file content and update the project; otherwise we create a new one. */ openOrUpdateConfiguredProjectForFile(fileName: string) { + this.log("start openOrUpdateConfiguredProjectForFile: " + new Date().getTime()); const searchPath = ts.normalizePath(getDirectoryPath(fileName)); this.log("Search path: " + searchPath, "Info"); const configFileName = this.findConfigFile(searchPath); @@ -1091,6 +1170,7 @@ namespace ts.server { else { this.log("No config files found."); } + this.log("end openOrUpdateConfiguredProjectForFile: " + new Date().getTime()); } /** @@ -1207,53 +1287,49 @@ namespace ts.server { return { succeeded: true, projectOptions }; } } + } + private exceedTotalNonTsFileSizeLimit(fileNames: string[]) { + let totalNonTsFileSize = 0; + for (const fileName of fileNames) { + if (hasTypeScriptFileExtension(fileName)) { + continue; + } + totalNonTsFileSize += this.host.getFileSize(fileName); + if (totalNonTsFileSize > maxProgramSizeForNonTsFiles) { + return true; + } + } + return false; } openConfigFile(configFilename: string, clientFileName?: string): ProjectOpenResult { + this.log("start openConfigFile: " + new Date().getTime()); const { succeeded, projectOptions, error } = this.configFileToProjectOptions(configFilename); + this.log("finish reading config file: " + new Date().getTime()); if (!succeeded) { + this.log("finish openConfigFile: " + new Date().getTime()); return error; } else { - const project = this.createProject(configFilename, projectOptions); - let programSizeForNonTsFiles = 0; - - // As the project openning might not be complete if there are too many files, - // therefore to surface the diagnostics we need to make sure the given client file is opened. - if (clientFileName) { - if (this.host.fileExists(clientFileName)) { - const currentClientFileInfo = this.openFile(clientFileName, /*openedByClient*/ true); - project.addRoot(currentClientFileInfo); - if (!hasTypeScriptFileExtension(currentClientFileInfo.fileName) && currentClientFileInfo.content) { - programSizeForNonTsFiles += currentClientFileInfo.content.length; - } - } - else { - return { errorMsg: "specified file " + clientFileName + " not found" }; + if (!projectOptions.compilerOptions.disableSizeLimit && projectOptions.compilerOptions.allowJs) { + if (this.exceedTotalNonTsFileSizeLimit(projectOptions.files)) { + const project = this.createProject(configFilename, projectOptions, /*languageServiceDisabled*/ true); + + // for configured projects with languageService disabled, we only watch its config file, + // do not care about the directory changes in the folder. + project.projectFileWatcher = this.host.watchFile( + toPath(configFilename, configFilename, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)), + _ => this.watchedProjectConfigFileChanged(project)); + return { success: true, project }; } } + const project = this.createProject(configFilename, projectOptions); for (const rootFilename of projectOptions.files) { - if (rootFilename === clientFileName) { - continue; - } - if (this.host.fileExists(rootFilename)) { - if (projectOptions.compilerOptions.disableSizeLimit) { - const info = this.openFile(rootFilename, /*openedByClient*/ false); - project.addRoot(info); - } - else if (programSizeForNonTsFiles <= maxProgramSizeForNonTsFiles) { - const info = this.openFile(rootFilename, /*openedByClient*/ false); - project.addRoot(info); - if (!hasTypeScriptFileExtension(rootFilename)) { - programSizeForNonTsFiles += info.content.length; - } - } - else { - break; - } + const info = this.openFile(rootFilename, /*openedByClient*/ clientFileName == rootFilename); + project.addRoot(info); } else { return { errorMsg: "specified file " + rootFilename + " not found" }; @@ -1269,6 +1345,7 @@ namespace ts.server { path => this.directoryWatchedForSourceFilesChanged(project, path), /*recursive*/ true ); + this.log("finish openConfigFile: " + new Date().getTime()); return { success: true, project: project }; } } @@ -1284,6 +1361,23 @@ namespace ts.server { return error; } else { + if (this.exceedTotalNonTsFileSizeLimit(projectOptions.files)) { + project.disableLanguageService(); + project.directoryWatcher.close(); + project.directoryWatcher = undefined; + project.setProjectOptions(projectOptions); + return; + } + + project.enableLanguageService(); + if (!project.directoryWatcher) { + project.directoryWatcher = this.host.watchDirectory( + ts.getDirectoryPath(project.projectFilename), + path => this.directoryWatchedForSourceFilesChanged(project, path), + /*recursive*/ true + ); + } + // if the project is too large, the root files might not have been all loaded if the total // program size reached the upper limit. In that case project.projectOptions.files should // be more precise. However this would only happen for configured project. @@ -1330,8 +1424,8 @@ namespace ts.server { } } - createProject(projectFilename: string, projectOptions?: ProjectOptions) { - const project = new Project(this, projectOptions); + createProject(projectFilename: string, projectOptions?: ProjectOptions, languageServiceDisabled?: boolean) { + const project = new Project(this, projectOptions, languageServiceDisabled); project.projectFilename = projectFilename; return project; } diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 5ed227ebaa7fd..3a9746dc10978 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -123,6 +123,7 @@ declare namespace ts.server.protocol { * The list of normalized file name in the project, including 'lib.d.ts' */ fileNames?: string[]; + languageServiceDisabled?: boolean; } /** diff --git a/src/server/session.ts b/src/server/session.ts index f0975a3f947b2..a66fbc8a5e408 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -288,7 +288,7 @@ namespace ts.server { private getDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -310,7 +310,7 @@ namespace ts.server { private getTypeDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -333,7 +333,7 @@ namespace ts.server { fileName = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(fileName); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -363,7 +363,7 @@ namespace ts.server { fileName = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(fileName); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -396,24 +396,29 @@ 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) { + throw Errors.NoProject; + } const projectInfo: protocol.ProjectInfo = { - configFileName: project.projectFilename + configFileName: project.projectFilename, + languageServiceDisabled: project.languageServiceDiabled }; if (needFileNameList) { projectInfo.fileNames = project.getFileNames(); } - + this.logger.info("end getProjectInfo:" + new Date().getTime()); return projectInfo; } private getRenameLocations(line: number, offset: number, fileName: string, findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -483,7 +488,7 @@ namespace ts.server { // can avoid duplicates by eliminating same ref file from subsequent projects const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -537,7 +542,7 @@ namespace ts.server { private getQuickInfo(line: number, offset: number, fileName: string): protocol.QuickInfoResponseBody { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -563,7 +568,7 @@ namespace ts.server { private getFormattingEditsForRange(line: number, offset: number, endLine: number, endOffset: number, fileName: string): protocol.CodeEdit[] { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -591,7 +596,7 @@ namespace ts.server { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -669,7 +674,7 @@ namespace ts.server { } const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -693,7 +698,7 @@ namespace ts.server { entryNames: string[], fileName: string): protocol.CompletionEntryDetails[] { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -712,7 +717,7 @@ namespace ts.server { private getSignatureHelpItems(line: number, offset: number, fileName: string): protocol.SignatureHelpItems { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -742,7 +747,7 @@ namespace ts.server { const checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => { fileName = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(fileName); - if (project) { + if (project && !project.languageServiceDiabled) { accum.push({ fileName, project }); } return accum; @@ -756,7 +761,7 @@ namespace ts.server { private change(line: number, offset: number, endLine: number, endOffset: number, insertString: string, fileName: string) { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (project) { + if (project && !project.languageServiceDiabled) { const compilerService = project.compilerService; const start = compilerService.host.lineOffsetToPosition(file, line, offset); const end = compilerService.host.lineOffsetToPosition(file, endLine, endOffset); @@ -772,7 +777,7 @@ namespace ts.server { const file = ts.normalizePath(fileName); const tmpfile = ts.normalizePath(tempFileName); const project = this.projectService.getProjectForFile(file); - if (project) { + if (project && !project.languageServiceDiabled) { this.changeSeq++; // make sure no changes happen before this one is finished project.compilerService.host.reloadScript(file, tmpfile, () => { @@ -786,7 +791,7 @@ namespace ts.server { const tmpfile = ts.normalizePath(tempFileName); const project = this.projectService.getProjectForFile(file); - if (project) { + if (project && !project.languageServiceDiabled) { project.compilerService.host.saveTo(file, tmpfile); } } @@ -821,7 +826,7 @@ namespace ts.server { private getNavigationBarItems(fileName: string): protocol.NavigationBarItem[] { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -837,7 +842,7 @@ namespace ts.server { private getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -877,7 +882,7 @@ namespace ts.server { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project) { + if (!project || project.languageServiceDiabled) { throw Errors.NoProject; } @@ -896,7 +901,11 @@ namespace ts.server { } getDiagnosticsForProject(delay: number, fileName: string) { - const { fileNames } = this.getProjectInfo(fileName, /*needFileNameList*/ true); + const { fileNames, languageServiceDisabled } = this.getProjectInfo(fileName, /*needFileNameList*/ true); + if (languageServiceDisabled) { + return; + } + // No need to analyze lib.d.ts let fileNamesInProject = fileNames.filter((value, index, array) => value.indexOf("lib.d.ts") < 0); From cb46f16406d32c1a8d38e57999ead4421fd9e496 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Thu, 17 Mar 2016 19:01:17 -0700 Subject: [PATCH 08/13] Fix failed tests --- src/compiler/commandLineParser.ts | 10 ++++++++-- src/compiler/sys.ts | 14 ++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index d7e58b20ca5cd..5ba54c6553c7e 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -649,6 +649,10 @@ namespace ts { // 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; + } + // .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")) { @@ -669,8 +673,10 @@ namespace ts { } } - filesSeen[fileName] = true; - fileNames.push(fileName); + if (!filesSeen[fileName]) { + filesSeen[fileName] = true; + fileNames.push(fileName); + } } } } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 3b8b9b3f39f95..7cdc1624582cc 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -491,8 +491,7 @@ namespace ts { return fileSystemEntryExists(path, FileSystemEntryKind.Directory); } - function visitDirectory(path: string, extension: string | string[], exclude: string[]) { - const result: string[] = []; + 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) { @@ -515,9 +514,8 @@ namespace ts { } } for (const current of directories) { - visitDirectory(current, extension, exclude); + visitDirectory(current, result, extension, exclude); } - return result; function checkExtension(name: string) { if (!extension) { @@ -533,13 +531,17 @@ namespace ts { } function readDirectoryWithMultipleExtensions(path: string, extensions: string[], exclude?: string[]): string[] { + const result: string[] = []; exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); - return visitDirectory(path, extensions, exclude); + 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))); - return visitDirectory(path, extension, exclude); + visitDirectory(path, result, extension, exclude); + return result; } return { From 74e3d7bb0133f3214770f8aadababa8ef0d4d15c Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 18 Mar 2016 11:00:26 -0700 Subject: [PATCH 09/13] Fix project updateing issue after editing config file --- src/server/editorServices.ts | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 2eef384df5c54..2c9e26e47cbe3 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1362,20 +1362,36 @@ namespace ts.server { } else { if (this.exceedTotalNonTsFileSizeLimit(projectOptions.files)) { + project.setProjectOptions(projectOptions); + if (project.languageServiceDiabled) { + return; + } + project.disableLanguageService(); project.directoryWatcher.close(); - project.directoryWatcher = undefined; - project.setProjectOptions(projectOptions); return; } - project.enableLanguageService(); - if (!project.directoryWatcher) { + if (project.languageServiceDiabled) { + project.setProjectOptions(projectOptions); + project.enableLanguageService(); project.directoryWatcher = this.host.watchDirectory( ts.getDirectoryPath(project.projectFilename), path => this.directoryWatchedForSourceFilesChanged(project, path), /*recursive*/ true ); + + for (const rootFilename of projectOptions.files) { + if (this.host.fileExists(rootFilename)) { + const info = this.openFile(rootFilename, /*openedByClient*/ false); + project.addRoot(info); + } + else { + return { errorMsg: "specified file " + rootFilename + " not found" }; + } + } + project.finishGraph(); + return; } // if the project is too large, the root files might not have been all loaded if the total @@ -1396,6 +1412,9 @@ namespace ts.server { for (const fileName of fileNamesToAdd) { let info = this.getScriptInfo(fileName); if (!info) { + if (!this.host.fileExists(info.fileName)) { + return { errorMsg: "specified file " + info.fileName + " not found" }; + } info = this.openFile(fileName, /*openedByClient*/ false); } else { From d387050aaab9f635ff3543563aced5c8a9a844db Mon Sep 17 00:00:00 2001 From: zhengbli Date: Thu, 9 Jun 2016 15:30:55 -0700 Subject: [PATCH 10/13] Fix merging issues and multiple project scenario --- src/compiler/program.ts | 2 +- src/compiler/sys.ts | 4 ++-- src/server/editorServices.ts | 10 ++-------- src/server/session.ts | 20 +++++++++++--------- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8b38d56c2b297..99ca9377d546b 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1219,7 +1219,7 @@ namespace ts { (oldOptions.noLib !== options.noLib) || (oldOptions.jsx !== options.jsx) || (oldOptions.allowJs !== options.allowJs) || - (oldOptions.disableSizeLimit !== options.disableSizeLimit) || + (oldOptions.disableSizeLimit !== options.disableSizeLimit) || (oldOptions.rootDir !== options.rootDir) || (oldOptions.typesSearchPaths !== options.typesSearchPaths) || (oldOptions.configFilePath !== options.configFilePath) || diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 1e04e97e4c5c1..659c021c6be42 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -28,7 +28,7 @@ namespace ts { getDirectories(path: string): string[]; readDirectory(path: string, extension?: string, exclude?: string[]): string[]; readDirectoryWithMultipleExtensions?(path: string, extensions: string[], exclude?: string[]): string[]; - getModifiedTime?(path: string): Date; + getModifiedTime?(path: string): Date; createHash?(data: string): string; getMemoryUsage?(): number; exit(exitCode?: number): void; @@ -549,7 +549,7 @@ namespace ts { getDirectories, readDirectory, readDirectoryWithMultipleExtensions, - getModifiedTime(path) { + getModifiedTime(path) { try { return _fs.statSync(path).mtime; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9f361cb37039b..bd29dc65ac407 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1217,7 +1217,7 @@ namespace ts.server { else { this.log("No config files found."); } - this.log("end openOrUpdateConfiguredProjectForFile: " + new Date().getTime()); + this.log("end openOrUpdateConfiguredProjectForFile: " + new Date().getTime()); return configFileName ? { configFileName } : {}; } @@ -1398,7 +1398,7 @@ namespace ts.server { } } - updateConfiguredProject(project: Project) { + updateConfiguredProject(project: Project): Diagnostic[] { if (!this.host.fileExists(project.projectFilename)) { this.log("Config file deleted"); this.removeProject(project); @@ -1434,9 +1434,6 @@ namespace ts.server { const info = this.openFile(rootFilename, /*openedByClient*/ false); project.addRoot(info); } - else { - return { errorMsg: "specified file " + rootFilename + " not found" }; - } } project.finishGraph(); return; @@ -1460,9 +1457,6 @@ namespace ts.server { for (const fileName of fileNamesToAdd) { let info = this.getScriptInfo(fileName); if (!info) { - if (!this.host.fileExists(info.fileName)) { - return { errorMsg: "specified file " + info.fileName + " not found" }; - } info = this.openFile(fileName, /*openedByClient*/ false); } else { diff --git a/src/server/session.ts b/src/server/session.ts index 6f07e97990689..17fe4b758f218 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -444,11 +444,12 @@ namespace ts.server { const file = ts.normalizePath(fileName); const info = this.projectService.getScriptInfo(file); const projects = this.projectService.findReferencingProjects(info); - if (!projects.length) { + const projectsWithLanguageServiceEnabeld = ts.filter(projects, p => !p.languageServiceDiabled); + if (projects.length === 0 || projectsWithLanguageServiceEnabeld.length === 0) { throw Errors.NoProject; } - const defaultProject = projects[0]; + const defaultProject = projectsWithLanguageServiceEnabeld[0]; // The rename info should be the same for every project const defaultProjectCompilerService = defaultProject.compilerService; const position = defaultProjectCompilerService.host.lineOffsetToPosition(file, line, offset); @@ -465,7 +466,7 @@ namespace ts.server { } const fileSpans = combineProjectOutput( - projects, + projectsWithLanguageServiceEnabeld, (project: Project) => { const compilerService = project.compilerService; const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments); @@ -526,11 +527,12 @@ namespace ts.server { const file = ts.normalizePath(fileName); const info = this.projectService.getScriptInfo(file); const projects = this.projectService.findReferencingProjects(info); - if (!projects.length) { + const projectsWithLanguageServiceEnabeld = ts.filter(projects, p => !p.languageServiceDiabled); + if (projects.length === 0 || projectsWithLanguageServiceEnabeld.length === 0) { throw Errors.NoProject; } - const defaultProject = projects[0]; + const defaultProject = projectsWithLanguageServiceEnabeld[0]; const position = defaultProject.compilerService.host.lineOffsetToPosition(file, line, offset); const nameInfo = defaultProject.compilerService.languageService.getQuickInfoAtPosition(file, position); if (!nameInfo) { @@ -542,7 +544,7 @@ namespace ts.server { const nameColStart = defaultProject.compilerService.host.positionToLineOffset(file, nameSpan.start).offset; const nameText = defaultProject.compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); const refs = combineProjectOutput( - projects, + projectsWithLanguageServiceEnabeld, (project: Project) => { const compilerService = project.compilerService; const references = compilerService.languageService.getReferencesAtPosition(file, position); @@ -902,13 +904,13 @@ namespace ts.server { const file = ts.normalizePath(fileName); const info = this.projectService.getScriptInfo(file); const projects = this.projectService.findReferencingProjects(info); - const defaultProject = projects[0]; - if (!defaultProject) { + const projectsWithLanguageServiceEnabeld = ts.filter(projects, p => !p.languageServiceDiabled); + if (projects.length === 0 || projectsWithLanguageServiceEnabeld.length === 0) { throw Errors.NoProject; } const allNavToItems = combineProjectOutput( - projects, + projectsWithLanguageServiceEnabeld, (project: Project) => { const compilerService = project.compilerService; const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount); From 4383f1a15f43a99286a2bdf0b35cde83e7bb3e1e Mon Sep 17 00:00:00 2001 From: zhengbli Date: Thu, 9 Jun 2016 16:28:42 -0700 Subject: [PATCH 11/13] Refactoring --- src/compiler/commandLineParser.ts | 2 +- src/compiler/diagnosticMessages.json | 4 --- src/compiler/program.ts | 38 ++-------------------------- src/server/editorServices.ts | 26 ++++++++++++++++--- src/server/protocol.d.ts | 3 +++ 5 files changed, 28 insertions(+), 45 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index f8ab0330dad92..ccde539fafa36 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -408,7 +408,7 @@ namespace ts { description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation_Colon }, { - name: "disableSizeLimit", + name: "disableProjectSizeLimit", type: "boolean" }, { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 47fa84dd9ce65..44062885677de 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2984,9 +2984,5 @@ "Unknown typing option '{0}'.": { "category": "Error", "code": 17010 - }, - "Too many JavaScript files in the project. Consider specifying the 'exclude' setting in project configuration to limit included source folders. The likely folder to exclude is '{0}'. To disable the project size limit, set the 'disableSizeLimit' compiler option to 'true'.": { - "category": "Error", - "code": 17012 } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 99ca9377d546b..06f32c4f4a23a 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -7,9 +7,6 @@ namespace ts { /* @internal */ export let emitTime = 0; /* @internal */ export let ioReadTime = 0; /* @internal */ export let ioWriteTime = 0; - /* @internal */ export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; - - /** The version of the TypeScript compiler release */ const emptyArray: any[] = []; @@ -19,6 +16,7 @@ namespace ts { "node_modules/@types/", ]; + /** The version of the TypeScript compiler release */ export const version = "1.9.0"; export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean): string { @@ -1059,8 +1057,6 @@ namespace ts { let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; let classifiableNames: Map; - let programSizeLimitExceeded = false; - let programSizeForNonTsFiles = 0; let resolvedTypeReferenceDirectives: Map = {}; let fileProcessingDiagnostics = createDiagnosticCollection(); @@ -1166,11 +1162,6 @@ namespace ts { return program; - function exceedProgramSizeLimit() { - return !options.disableSizeLimit && programSizeLimitExceeded; - } - - function getCommonSourceDirectory() { if (typeof commonSourceDirectory === "undefined") { if (options.rootDir && checkSourceFilesBelongToPath(files, options.rootDir)) { @@ -1219,7 +1210,6 @@ namespace ts { (oldOptions.noLib !== options.noLib) || (oldOptions.jsx !== options.jsx) || (oldOptions.allowJs !== options.allowJs) || - (oldOptions.disableSizeLimit !== options.disableSizeLimit) || (oldOptions.rootDir !== options.rootDir) || (oldOptions.typesSearchPaths !== options.typesSearchPaths) || (oldOptions.configFilePath !== options.configFilePath) || @@ -1838,7 +1828,7 @@ namespace ts { } } - if (diagnostic && !exceedProgramSizeLimit()) { + if (diagnostic) { if (refFile !== undefined && refEnd !== undefined && refPos !== undefined) { fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, diagnostic, ...diagnosticArgument)); } @@ -1871,11 +1861,6 @@ namespace ts { return file; } - const isNonTsFile = !hasTypeScriptFileExtension(fileName); - if (isNonTsFile && exceedProgramSizeLimit()) { - return undefined; - } - // We haven't looked for this file, do so now and cache result const file = host.getSourceFile(fileName, options.target, hostErrorMessage => { if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) { @@ -1887,25 +1872,6 @@ namespace ts { } }); - if (!options.disableSizeLimit && file && file.text && !hasTypeScriptFileExtension(file.fileName)) { - programSizeForNonTsFiles += file.text.length; - if (programSizeForNonTsFiles > maxProgramSizeForNonTsFiles) { - // If the program size limit was reached when processing a file, this file is - // likely in the problematic folder than contains too many files. - // Normally the folder is one level down from the commonSourceDirectory, for example, - // if the commonSourceDirectory is "/src/", and the last processed path was "/src/node_modules/a/b.js", - // we should show in the error message "/src/node_modules/". - const commonSourceDirectory = getCommonSourceDirectory(); - let rootLevelDirectory = path.substring(0, Math.max(commonSourceDirectory.length, path.indexOf(directorySeparator, commonSourceDirectory.length))); - if (rootLevelDirectory[rootLevelDirectory.length - 1] !== directorySeparator) { - rootLevelDirectory += directorySeparator; - } - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Too_many_JavaScript_files_in_the_project_Consider_specifying_the_exclude_setting_in_project_configuration_to_limit_included_source_folders_The_likely_folder_to_exclude_is_0_To_disable_the_project_size_limit_set_the_disableSizeLimit_compiler_option_to_true, rootLevelDirectory)); - programSizeLimitExceeded = true; - return undefined; - } - } - filesByName.set(path, file); if (file) { file.path = path; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index bd29dc65ac407..5af2989a717e5 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -27,6 +27,8 @@ namespace ts.server { }); } + export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; + export class ScriptInfo { svc: ScriptVersionCache; children: ScriptInfo[] = []; // files referenced by this file @@ -384,7 +386,7 @@ namespace ts.server { constructor( public projectService: ProjectService, public projectOptions?: ProjectOptions, - public languageServiceDiabled?: boolean) { + public languageServiceDiabled = false) { if (projectOptions && projectOptions.files) { // If files are listed explicitly, allow all extensions projectOptions.compilerOptions.allowNonTsExtensions = true; @@ -430,7 +432,16 @@ namespace ts.server { getFileNames() { if (this.languageServiceDiabled) { - return this.projectOptions ? this.projectOptions.files : undefined; + if (!this.projectOptions) { + return undefined; + } + + const fileNames: string[] = []; + if (this.projectOptions && this.projectOptions.compilerOptions) { + fileNames.push(getDefaultLibFilePath(this.projectOptions.compilerOptions)); + } + ts.addRange(fileNames, this.projectOptions.files); + return fileNames; } const sourceFiles = this.program.getSourceFiles(); @@ -1340,6 +1351,10 @@ namespace ts.server { private exceedTotalNonTsFileSizeLimit(fileNames: string[]) { let totalNonTsFileSize = 0; + if (!this.host.getFileSize) { + return false; + } + for (const fileName of fileNames) { if (hasTypeScriptFileExtension(fileName)) { continue; @@ -1409,14 +1424,17 @@ namespace ts.server { return errors; } else { - if (this.exceedTotalNonTsFileSizeLimit(projectOptions.files)) { + if (this.exceedTotalNonTsFileSizeLimit(projectOptions.files) && projectOptions.compilerOptions && !projectOptions.compilerOptions.disableSizeLimit) { project.setProjectOptions(projectOptions); if (project.languageServiceDiabled) { return; } project.disableLanguageService(); - project.directoryWatcher.close(); + if (project.directoryWatcher) { + project.directoryWatcher.close(); + project.directoryWatcher = undefined; + } return; } diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 018162d0fff1c..4ebf1aff5d636 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -123,6 +123,9 @@ declare namespace ts.server.protocol { * The list of normalized file name in the project, including 'lib.d.ts' */ fileNames?: string[]; + /** + * Indicates if the project has a active language service instance + */ languageServiceDisabled?: boolean; } From e41b10bbc743516b991d072540128034b5913c6d Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 10 Jun 2016 01:42:35 -0700 Subject: [PATCH 12/13] add test and spit commandLineParser changes to another PR --- src/compiler/commandLineParser.ts | 16 +----- src/compiler/sys.ts | 55 +++++-------------- src/compiler/types.ts | 1 - src/server/editorServices.ts | 8 --- src/server/protocol.d.ts | 4 +- src/server/session.ts | 2 - .../cases/unittests/tsserverProjectSystem.ts | 48 +++++++++++++++- 7 files changed, 65 insertions(+), 69 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index ccde539fafa36..7a73949cf7a6d 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 659c021c6be42..db4270ad271ca 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,40 +441,13 @@ namespace ts { directories.push(name); } } - catch (e) { } - } - } - 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)); + for (const current of directories) { + visitDirectory(current); } } } - 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 { args: process.argv.slice(2), newLine: _os.EOL, @@ -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 a386b3c1ff2c0..d43cd9f910df5 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 5af2989a717e5..572acc61a18fd 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 4ebf1aff5d636..4cdd331f1d9ad 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 17fe4b758f218..fa5fd378a0b31 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 cf382d094632c..d7b808f79b793 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 From 550d91249b1c4c72b6b0eef1fa275794c75b9faa Mon Sep 17 00:00:00 2001 From: zhengbli Date: Wed, 15 Jun 2016 14:40:29 -0700 Subject: [PATCH 13/13] Refactor code to make if statements cheaper --- src/server/editorServices.ts | 7 ++----- src/server/session.ts | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 615e843dcacea..346b2e00a109a 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -475,10 +475,7 @@ namespace ts.server { isRoot(info: ScriptInfo) { if (this.languageServiceDiabled) { - if (!this.projectOptions) { - return undefined; - } - return forEach(this.projectOptions.files, file => toPath(file, file, createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames)) === info.path); + return undefined; } return this.compilerService.host.roots.some(root => root === info); @@ -1421,7 +1418,7 @@ namespace ts.server { return errors; } else { - if (this.exceedTotalNonTsFileSizeLimit(projectOptions.files) && projectOptions.compilerOptions && !projectOptions.compilerOptions.disableSizeLimit) { + if (projectOptions.compilerOptions && !projectOptions.compilerOptions.disableSizeLimit && this.exceedTotalNonTsFileSizeLimit(projectOptions.files)) { project.setProjectOptions(projectOptions); if (project.languageServiceDiabled) { return; diff --git a/src/server/session.ts b/src/server/session.ts index 75abd94651514..65540082f6068 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -443,7 +443,7 @@ namespace ts.server { const info = this.projectService.getScriptInfo(file); const projects = this.projectService.findReferencingProjects(info); const projectsWithLanguageServiceEnabeld = ts.filter(projects, p => !p.languageServiceDiabled); - if (projects.length === 0 || projectsWithLanguageServiceEnabeld.length === 0) { + if (projectsWithLanguageServiceEnabeld.length === 0) { throw Errors.NoProject; } @@ -526,7 +526,7 @@ namespace ts.server { const info = this.projectService.getScriptInfo(file); const projects = this.projectService.findReferencingProjects(info); const projectsWithLanguageServiceEnabeld = ts.filter(projects, p => !p.languageServiceDiabled); - if (projects.length === 0 || projectsWithLanguageServiceEnabeld.length === 0) { + if (projectsWithLanguageServiceEnabeld.length === 0) { throw Errors.NoProject; } @@ -904,7 +904,7 @@ namespace ts.server { const info = this.projectService.getScriptInfo(file); const projects = this.projectService.findReferencingProjects(info); const projectsWithLanguageServiceEnabeld = ts.filter(projects, p => !p.languageServiceDiabled); - if (projects.length === 0 || projectsWithLanguageServiceEnabeld.length === 0) { + if (projectsWithLanguageServiceEnabeld.length === 0) { throw Errors.NoProject; }