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 cf55f030c33..a11d06a6445 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 15a3a4e5ef4..b5208e3aad4 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 495fbde5a99..406eeb6cd91 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 4cf94655b35..6d7847ab588 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 de10d469c87..72ad016f596 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 71907735b91..26b40b3d908 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 6d7847ab588..c70985cd018 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 26b40b3d908..4b49c2944cd 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 a11d06a6445..72deb16f0c1 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 b5208e3aad4..d96b10f61ad 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 406eeb6cd91..cdb4d3b417c 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 c70985cd018..1c7164aeb01 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 155f5c1a77f..73dc959e163 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 72ad016f596..9073e481729 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 4b49c2944cd..9cbb038f04e 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 72deb16f0c1..3b7f423bf86 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 d96b10f61ad..f8a7b627a7c 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 cdb4d3b417c..8f2f95fa4fe 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 9073e481729..7974fb66cd6 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 9cbb038f04e..456ff0ed26a 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 a562ecf18cf..e72c647db1b 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 456ff0ed26a..6f8006986ed 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 34a84ac793f..f535140eeaf 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 1c7164aeb01..924fe92990b 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 95a1e32c401..af765eb17b0 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 | 168 +++++++++++++++++++++++------- src/server/protocol.d.ts | 1 + src/server/session.ts | 53 ++++++---- 8 files changed, 249 insertions(+), 114 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index f535140eeaf..d7e58b20ca5 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 2da1fffdb8b..64a487baad2 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 924fe92990b..3b8b9b3f39f 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 af765eb17b0..3638cc3a92f 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 68db246cb9a..6520e339ac9 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 8c3429ac698..2eef384df5c 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; + if (!projectOptions.compilerOptions.disableSizeLimit && projectOptions.compilerOptions.allowJs) { + if (this.exceedTotalNonTsFileSizeLimit(projectOptions.files)) { + const project = this.createProject(configFilename, projectOptions, /*languageServiceDisabled*/ true); - // 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" }; + // 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 5ed227ebaa7..3a9746dc109 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 f0975a3f947..a66fbc8a5e4 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 d7e58b20ca5..5ba54c6553c 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 3b8b9b3f39f..7cdc1624582 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 2eef384df5c..2c9e26e47cb 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 8b38d56c2b2..99ca9377d54 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 1e04e97e4c5..659c021c6be 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 9f361cb3703..bd29dc65ac4 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 6f07e979906..17fe4b758f2 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 f8ab0330dad..ccde539fafa 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 47fa84dd9ce..44062885677 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 99ca9377d54..06f32c4f4a2 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 bd29dc65ac4..5af2989a717 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 018162d0fff..4ebf1aff5d6 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 | 57 +++++-------------- 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, 66 insertions(+), 70 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index ccde539fafa..7a73949cf7a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -727,22 +727,10 @@ namespace ts { const supportedExtensions = getSupportedExtensions(options); Debug.assert(indexOf(supportedExtensions, ".ts") < indexOf(supportedExtensions, ".d.ts"), "Changed priority of extensions to pick"); - const potentialFiles: string[] = []; - if (host.readDirectoryWithMultipleExtensions) { - addRange(potentialFiles, host.readDirectoryWithMultipleExtensions(basePath, supportedExtensions, exclude)); - } - else { - for (const extension of supportedExtensions) { - addRange(potentialFiles, host.readDirectory(basePath, extension, exclude)); - } - } // Get files of supported extensions in their order of resolution for (const extension of supportedExtensions) { - for (const fileName of potentialFiles) { - if (!fileExtensionIs(fileName, extension)) { - continue; - } - + const filesInDirWithExtension = host.readDirectory(basePath, extension, exclude); + for (const fileName of filesInDirWithExtension) { // .ts extension would read the .d.ts extension files too but since .d.ts is lower priority extension, // lets pick them when its turn comes up if (extension === ".ts" && fileExtensionIs(fileName, ".d.ts")) { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 659c021c6be..db4270ad271 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -27,7 +27,6 @@ namespace ts { getCurrentDirectory(): string; getDirectories(path: string): string[]; readDirectory(path: string, extension?: string, exclude?: string[]): string[]; - readDirectoryWithMultipleExtensions?(path: string, extensions: string[], exclude?: string[]): string[]; getModifiedTime?(path: string): Date; createHash?(data: string): string; getMemoryUsage?(): number; @@ -416,23 +415,25 @@ namespace ts { return filter(_fs.readdirSync(path), p => fileSystemEntryExists(combinePaths(path, p), FileSystemEntryKind.Directory)); } - function visitDirectory(path: string, result: string[], extension: string | string[], exclude: string[]) { - const files = _fs.readdirSync(path || ".").sort(); - const directories: string[] = []; - for (const current of files) { + function readDirectory(path: string, extension?: string, exclude?: string[]): string[] { + const result: string[] = []; + exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); + visitDirectory(path); + return result; + function visitDirectory(path: string) { + const files = _fs.readdirSync(path || ".").sort(); + const directories: string[] = []; + for (const current of files) { // This is necessary because on some file system node fails to exclude // "." and "..". See https://github.com/nodejs/node/issues/4002 if (current === "." || current === "..") { continue; } - const name = combinePaths(path, current); - if (!contains(exclude, getCanonicalPath(name))) { - // fs.statSync would throw an exception if the file is a symlink - // whose linked file doesn't exist. - try { + const name = combinePaths(path, current); + if (!contains(exclude, getCanonicalPath(name))) { const stat = _fs.statSync(name); if (stat.isFile()) { - if (checkExtension(name)) { + if (!extension || fileExtensionIs(name, extension)) { result.push(name); } } @@ -440,38 +441,11 @@ namespace ts { directories.push(name); } } - catch (e) { } + } + for (const current of directories) { + visitDirectory(current); } } - for (const current of directories) { - visitDirectory(current, result, extension, exclude); - } - - function checkExtension(name: string) { - if (!extension) { - return true; - } - if (typeof extension === "string") { - return fileExtensionIs(name, extension); - } - else { - return forEach(extension, ext => fileExtensionIs(name, ext)); - } - } - } - - function readDirectoryWithMultipleExtensions(path: string, extensions: string[], exclude?: string[]): string[] { - const result: string[] = []; - exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); - visitDirectory(path, result, extensions, exclude); - return result; - } - - function readDirectory(path: string, extension?: string, exclude?: string[]): string[] { - const result: string[] = []; - exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s))); - visitDirectory(path, result, extension, exclude); - return result; } return { @@ -548,7 +522,6 @@ namespace ts { }, getDirectories, readDirectory, - readDirectoryWithMultipleExtensions, getModifiedTime(path) { try { return _fs.statSync(path).mtime; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a386b3c1ff2..d43cd9f910d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1670,7 +1670,6 @@ namespace ts { export interface ParseConfigHost { readDirectory(rootDir: string, extension: string, exclude: string[]): string[]; - readDirectoryWithMultipleExtensions?(rootDir: string, extensions: string[], exclude: string[]): string[]; } export interface WriteFileCallback { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5af2989a717..572acc61a18 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1184,12 +1184,10 @@ namespace ts.server { * @param fileContent is a known version of the file content that is more up to date than the one on disk */ openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind): { configFileName?: string, configFileErrors?: Diagnostic[] } { - this.log("start openClientFile: " + new Date().getTime()); const { configFileName, configFileErrors } = this.openOrUpdateConfiguredProjectForFile(fileName); const info = this.openFile(fileName, /*openedByClient*/ true, fileContent, scriptKind); this.addOpenFile(info); this.printProjects(); - this.log("end openClientFile: " + new Date().getTime()); return { configFileName, configFileErrors }; } @@ -1199,7 +1197,6 @@ namespace ts.server { * the tsconfig file content and update the project; otherwise we create a new one. */ openOrUpdateConfiguredProjectForFile(fileName: string): { configFileName?: string, configFileErrors?: Diagnostic[] } { - this.log("start openOrUpdateConfiguredProjectForFile: " + new Date().getTime()); const searchPath = ts.normalizePath(getDirectoryPath(fileName)); this.log("Search path: " + searchPath, "Info"); const configFileName = this.findConfigFile(searchPath); @@ -1228,7 +1225,6 @@ namespace ts.server { else { this.log("No config files found."); } - this.log("end openOrUpdateConfiguredProjectForFile: " + new Date().getTime()); return configFileName ? { configFileName } : {}; } @@ -1368,11 +1364,8 @@ namespace ts.server { } openConfigFile(configFilename: string, clientFileName?: string): { success: boolean, project?: Project, errors?: Diagnostic[] } { - this.log("start openConfigFile: " + new Date().getTime()); const { succeeded, projectOptions, errors } = this.configFileToProjectOptions(configFilename); - this.log("finish reading config file: " + new Date().getTime()); if (!succeeded) { - this.log("finish openConfigFile: " + new Date().getTime()); return { success: false, errors }; } else { @@ -1408,7 +1401,6 @@ namespace ts.server { path => this.directoryWatchedForSourceFilesChanged(project, path), /*recursive*/ true ); - this.log("finish openConfigFile: " + new Date().getTime()); return { success: true, project: project, errors }; } } diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 4ebf1aff5d6..4cdd331f1d9 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -124,8 +124,8 @@ declare namespace ts.server.protocol { */ fileNames?: string[]; /** - * Indicates if the project has a active language service instance - */ + * Indicates if the project has a active language service instance + */ languageServiceDisabled?: boolean; } diff --git a/src/server/session.ts b/src/server/session.ts index 17fe4b758f2..fa5fd378a0b 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -421,7 +421,6 @@ namespace ts.server { } private getProjectInfo(fileName: string, needFileNameList: boolean): protocol.ProjectInfo { - this.logger.info("start getProjectInfo:" + new Date().getTime()); fileName = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(fileName); if (!project) { @@ -436,7 +435,6 @@ namespace ts.server { if (needFileNameList) { projectInfo.fileNames = project.getFileNames(); } - this.logger.info("end getProjectInfo:" + new Date().getTime()); return projectInfo; } diff --git a/tests/cases/unittests/tsserverProjectSystem.ts b/tests/cases/unittests/tsserverProjectSystem.ts index cf382d09463..d7b808f79b7 100644 --- a/tests/cases/unittests/tsserverProjectSystem.ts +++ b/tests/cases/unittests/tsserverProjectSystem.ts @@ -25,6 +25,7 @@ namespace ts { interface FileOrFolder { path: string; content?: string; + fileSize?: number; } interface FSEntry { @@ -34,6 +35,7 @@ namespace ts { interface File extends FSEntry { content: string; + fileSize?: number; } interface Folder extends FSEntry { @@ -157,7 +159,7 @@ namespace ts { const path = this.toPath(fileOrFolder.path); const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); if (typeof fileOrFolder.content === "string") { - const entry = { path, content: fileOrFolder.content, fullPath }; + const entry = { path, content: fileOrFolder.content, fullPath, fileSize: fileOrFolder.fileSize }; this.fs.set(path, entry); addFolder(getDirectoryPath(fullPath), this.toPath, this.fs).entries.push(entry); } @@ -172,6 +174,17 @@ namespace ts { return this.fs.contains(path) && isFile(this.fs.get(path)); }; + getFileSize(s: string) { + const path = this.toPath(s); + if (this.fs.contains(path)) { + const entry = this.fs.get(path); + if (isFile(entry)) { + return entry.fileSize ? entry.fileSize : entry.content.length; + } + } + return undefined; + } + directoryExists(s: string) { const path = this.toPath(s); return this.fs.contains(path) && isFolder(this.fs.get(path)); @@ -567,5 +580,38 @@ namespace ts { checkConfiguredProjectActualFiles(project, [file1.path, classicModuleFile.path]); checkNumberOfInferredProjects(projectService, 1); }); + + it("should disable language service for project with too many non-ts files", () => { + const jsFiles: FileOrFolder[] = []; + const configFile: FileOrFolder = { + path: `/a/b/jsconfig.json`, + content: "{}" + }; + jsFiles.push(configFile); + for (let i = 0; i < 1000; i++) { + jsFiles.push({ + path: `/a/b/file${i}.js`, + content: "", + fileSize: 50000 + }); + } + + const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", jsFiles); + const projectService = new server.ProjectService(host, nullLogger); + projectService.openClientFile(jsFiles[1].path); + projectService.openClientFile(jsFiles[2].path); + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + + const project = projectService.configuredProjects[0]; + assert(project.languageServiceDiabled, "the project's language service is expected to be disabled"); + + configFile.content = `{ + "files": ["/a/b/file1.js", "/a/b/file2.js", "/a/b/file3.js"] + }`; + host.reloadFS(jsFiles); + host.triggerFileWatcherCallback(configFile.path); + assert(!project.languageServiceDiabled, "after the config file change, the project's language service is expected to be enabled."); + }); }); } \ No newline at end of file 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 615e843dcac..346b2e00a10 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 75abd946515..65540082f60 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; }