diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 5ea0df91ca..356a7a8962 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -1007,6 +1007,41 @@ namespace ts.projectSystem { checkProjectRootFiles(projectService.configuredProjects[0], [commonFile1.path, commonFile2.path]); }); + it("should disable features when the files are too large", () => { + const file1 = { + path: "/a/b/f1.js", + content: "let x =1;", + fileSize: 10 * 1024 * 1024 + }; + const file2 = { + path: "/a/b/f2.js", + content: "let y =1;", + fileSize: 6 * 1024 * 1024 + }; + const file3 = { + path: "/a/b/f3.js", + content: "let y =1;", + fileSize: 6 * 1024 * 1024 + }; + + const proj1name = "proj1", proj2name = "proj2", proj3name = "proj3"; + + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path]), options: {}, projectFileName: proj1name }); + const proj1 = projectService.findProject(proj1name); + assert.isTrue(proj1.languageServiceEnabled); + + projectService.openExternalProject({ rootFiles: toExternalFiles([file2.path]), options: {}, projectFileName: proj2name }); + const proj2 = projectService.findProject(proj2name); + assert.isTrue(proj2.languageServiceEnabled); + + projectService.openExternalProject({ rootFiles: toExternalFiles([file3.path]), options: {}, projectFileName: proj3name }); + const proj3 = projectService.findProject(proj3name); + assert.isFalse(proj3.languageServiceEnabled); + }); + it("should use only one inferred project if 'useOneInferredProject' is set", () => { const file1 = { path: "/a/b/main.ts", diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 397de9ead3..8af6fe5d34 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -254,6 +254,7 @@ namespace ts.server { private compilerOptionsForInferredProjects: CompilerOptions; private compileOnSaveForInferredProjects: boolean; + private readonly projectToSizeMap: Map = createMap(); private readonly directoryWatchers: DirectoryWatchers; private readonly throttledOperations: ThrottledOperations; @@ -563,9 +564,11 @@ namespace ts.server { switch (project.projectKind) { case ProjectKind.External: removeItemFromSet(this.externalProjects, project); + this.projectToSizeMap.delete((project as ExternalProject).externalProjectName); break; case ProjectKind.Configured: removeItemFromSet(this.configuredProjects, project); + this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath); break; case ProjectKind.Inferred: removeItemFromSet(this.inferredProjects, project); @@ -852,10 +855,15 @@ namespace ts.server { return { success: true, projectOptions, configFileErrors: errors }; } - private exceededTotalSizeLimitForNonTsFiles(options: CompilerOptions, fileNames: T[], propertyReader: FilePropertyReader) { + private exceededTotalSizeLimitForNonTsFiles(name: string, options: CompilerOptions, fileNames: T[], propertyReader: FilePropertyReader) { if (options && options.disableSizeLimit || !this.host.getFileSize) { return false; } + + let availableSpace = maxProgramSizeForNonTsFiles; + this.projectToSizeMap.set(name, 0); + this.projectToSizeMap.forEach(val => (availableSpace -= (val || 0))); + let totalNonTsFileSize = 0; for (const f of fileNames) { const fileName = propertyReader.getFileName(f); @@ -864,9 +872,16 @@ namespace ts.server { } totalNonTsFileSize += this.host.getFileSize(fileName); if (totalNonTsFileSize > maxProgramSizeForNonTsFiles) { + // Keep the size as zero since it's disabled return true; } } + + if (totalNonTsFileSize > availableSpace) { + return true; + } + + this.projectToSizeMap.set(name, totalNonTsFileSize); return false; } @@ -877,7 +892,7 @@ namespace ts.server { this, this.documentRegistry, compilerOptions, - /*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(compilerOptions, files, externalFilePropertyReader), + /*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(projectFileName, compilerOptions, files, externalFilePropertyReader), options.compileOnSave === undefined ? true : options.compileOnSave); this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typeAcquisition, /*configFileErrors*/ undefined); @@ -897,7 +912,7 @@ namespace ts.server { } private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], clientFileName?: string) { - const sizeLimitExceeded = this.exceededTotalSizeLimitForNonTsFiles(projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); + const sizeLimitExceeded = this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); const project = new ConfiguredProject( configFileName, this, @@ -1050,7 +1065,7 @@ namespace ts.server { return configFileErrors; } - if (this.exceededTotalSizeLimitForNonTsFiles(projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { + if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { project.setCompilerOptions(projectOptions.compilerOptions); if (!project.languageServiceEnabled) { // language service is already disabled @@ -1414,7 +1429,7 @@ namespace ts.server { if (externalProject) { if (!tsConfigFiles) { const compilerOptions = convertCompilerOptions(proj.options); - if (this.exceededTotalSizeLimitForNonTsFiles(compilerOptions, proj.rootFiles, externalFilePropertyReader)) { + if (this.exceededTotalSizeLimitForNonTsFiles(proj.projectFileName, compilerOptions, proj.rootFiles, externalFilePropertyReader)) { externalProject.disableLanguageService(); } else { diff --git a/src/server/project.ts b/src/server/project.ts index 385816b102..e57e605d65 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1031,7 +1031,7 @@ namespace ts.server { export class ExternalProject extends Project { private typeAcquisition: TypeAcquisition; - constructor(externalProjectName: string, + constructor(public externalProjectName: string, projectService: ProjectService, documentRegistry: ts.DocumentRegistry, compilerOptions: CompilerOptions, diff --git a/src/server/session.ts b/src/server/session.ts index 8dd0411d07..11c99dfe46 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -25,8 +25,14 @@ namespace ts.server { return ((1e9 * seconds) + nanoseconds) / 1000000.0; } - function shouldSkipSematicCheck(project: Project) { - return (project.projectKind === ProjectKind.Inferred || project.projectKind === ProjectKind.External) && project.isJsOnlyProject(); + function shouldSkipSemanticCheck(project: Project) { + if (project.projectKind === ProjectKind.Inferred || project.projectKind === ProjectKind.External) { + return project.isJsOnlyProject(); + } + else { + // For configured projects, require that skipLibCheck be set also + return project.getCompilerOptions().skipLibCheck && project.isJsOnlyProject(); + } } interface FileStart { @@ -447,7 +453,7 @@ namespace ts.server { private semanticCheck(file: NormalizedPath, project: Project) { try { let diags: Diagnostic[] = []; - if (!shouldSkipSematicCheck(project)) { + if (!shouldSkipSemanticCheck(project)) { diags = project.getLanguageService().getSemanticDiagnostics(file); } @@ -555,7 +561,7 @@ namespace ts.server { private getDiagnosticsWorker(args: protocol.FileRequestArgs, isSemantic: boolean, selector: (project: Project, file: string) => Diagnostic[], includeLinePosition: boolean) { const { project, file } = this.getFileAndProject(args); - if (isSemantic && shouldSkipSematicCheck(project)) { + if (isSemantic && shouldSkipSemanticCheck(project)) { return []; } const scriptInfo = project.getScriptInfoForNormalizedPath(file);