From 3225e555486fc98d528393671dee42c22b8eda20 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 20 Jun 2016 15:22:00 -0700 Subject: [PATCH] merge with origin/master --- src/server/editorServices.ts | 231 ++++++++++++++---- src/server/session.ts | 32 +-- .../cases/unittests/tsserverProjectSystem.ts | 4 +- 3 files changed, 209 insertions(+), 58 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index b957a99527d..7348f79bfbe 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -123,7 +123,66 @@ namespace ts.server { } } - export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost { + + function throwLanguageServiceIsDisabledError() {; + throw new Error("LanguageService is disabled"); + } + + const nullLanguageService: ts.LanguageService = { + cleanupSemanticCache: (): any => throwLanguageServiceIsDisabledError(), + getSyntacticDiagnostics: (): any => throwLanguageServiceIsDisabledError(), + getSemanticDiagnostics: (): any => throwLanguageServiceIsDisabledError(), + getCompilerOptionsDiagnostics: (): any => throwLanguageServiceIsDisabledError(), + getSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(), + getEncodedSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(), + getSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(), + getEncodedSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(), + getCompletionsAtPosition: (): any => throwLanguageServiceIsDisabledError(), + findReferences: (): any => throwLanguageServiceIsDisabledError(), + getCompletionEntryDetails: (): any => throwLanguageServiceIsDisabledError(), + getQuickInfoAtPosition: (): any => throwLanguageServiceIsDisabledError(), + findRenameLocations: (): any => throwLanguageServiceIsDisabledError(), + getNameOrDottedNameSpan: (): any => throwLanguageServiceIsDisabledError(), + getBreakpointStatementAtPosition: (): any => throwLanguageServiceIsDisabledError(), + getBraceMatchingAtPosition: (): any => throwLanguageServiceIsDisabledError(), + getSignatureHelpItems: (): any => throwLanguageServiceIsDisabledError(), + getDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(), + getRenameInfo: (): any => throwLanguageServiceIsDisabledError(), + getTypeDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(), + getReferencesAtPosition: (): any => throwLanguageServiceIsDisabledError(), + getDocumentHighlights: (): any => throwLanguageServiceIsDisabledError(), + getOccurrencesAtPosition: (): any => throwLanguageServiceIsDisabledError(), + getNavigateToItems: (): any => throwLanguageServiceIsDisabledError(), + getNavigationBarItems: (): any => throwLanguageServiceIsDisabledError(), + getOutliningSpans: (): any => throwLanguageServiceIsDisabledError(), + getTodoComments: (): any => throwLanguageServiceIsDisabledError(), + getIndentationAtPosition: (): any => throwLanguageServiceIsDisabledError(), + getFormattingEditsForRange: (): any => throwLanguageServiceIsDisabledError(), + getFormattingEditsForDocument: (): any => throwLanguageServiceIsDisabledError(), + getFormattingEditsAfterKeystroke: (): any => throwLanguageServiceIsDisabledError(), + getDocCommentTemplateAtPosition: (): any => throwLanguageServiceIsDisabledError(), + isValidBraceCompletionAtPostion: (): any => throwLanguageServiceIsDisabledError(), + getEmitOutput: (): any => throwLanguageServiceIsDisabledError(), + getProgram: (): any => throwLanguageServiceIsDisabledError(), + getNonBoundSourceFile: (): any => throwLanguageServiceIsDisabledError(), + dispose: (): any => throwLanguageServiceIsDisabledError(), + }; + + interface ServerLanguageServiceHost { + getCompilationSettings(): CompilerOptions; + setCompilationSettings(options: CompilerOptions): void; + removeRoot(info: ScriptInfo): void; + removeReferencedFile(info: ScriptInfo): void; + } + + const nullLanguageServiceHost: ServerLanguageServiceHost = { + getCompilationSettings: () => undefined, + setCompilationSettings: () => undefined, + removeRoot: () => undefined, + removeReferencedFile: () => undefined + }; + + export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost, ServerLanguageServiceHost { private compilationSettings: ts.CompilerOptions; private resolvedModuleNames: ts.FileMap>; private resolvedTypeReferenceDirectives: ts.FileMap>; @@ -302,31 +361,62 @@ namespace ts.server { export abstract class Project { private rootFiles: ScriptInfo[] = []; private rootFilesMap: FileMap = createFileMap(); - private readonly lsHost: LSHost; + private lsHost: ServerLanguageServiceHost; - readonly languageService: LanguageService; + languageService: LanguageService; protected program: ts.Program; constructor( readonly projectKind: ProjectKind, readonly projectService: ProjectService, - documentRegistry: ts.DocumentRegistry, + private documentRegistry: ts.DocumentRegistry, hasExplicitListOfFiles: boolean, - compilerOptions: CompilerOptions) { + public languageServiceEnabled: boolean, + private compilerOptions: CompilerOptions) { - if (!compilerOptions) { - compilerOptions = ts.getDefaultCompilerOptions(); - compilerOptions.allowNonTsExtensions = true; - compilerOptions.allowJs = true; + if (!this.compilerOptions) { + this.compilerOptions = ts.getDefaultCompilerOptions(); + this.compilerOptions.allowNonTsExtensions = true; + this.compilerOptions.allowJs = true; } else if (hasExplicitListOfFiles) { // If files are listed explicitly, allow all extensions - compilerOptions.allowNonTsExtensions = true; + this.compilerOptions.allowNonTsExtensions = true; } - this.lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken); - this.lsHost.setCompilationSettings(compilerOptions); - this.languageService = ts.createLanguageService(this.lsHost, documentRegistry); + if (languageServiceEnabled) { + this.enableLanguageServiceWorker(); + } + else { + this.disableLanguageServiceWorker(); + } + } + + enableLanguageService() { + if (!this.languageServiceEnabled) { + this.enableLanguageServiceWorker(); + } + } + + private enableLanguageServiceWorker() { + const lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken); + lsHost.setCompilationSettings(this.compilerOptions); + this.languageService = ts.createLanguageService(lsHost, this.documentRegistry); + + this.lsHost = lsHost; + this.languageServiceEnabled = true; + } + + disableLanguageService() { + if (this.languageServiceEnabled) { + this.disableLanguageServiceWorker(); + } + } + + private disableLanguageServiceWorker() { + this.languageService = nullLanguageService; + this.lsHost = nullLanguageServiceHost; + this.languageServiceEnabled = false; } getProjectFileName(): string { @@ -343,29 +433,29 @@ namespace ts.server { } getRootFiles() { + if (!this.languageServiceEnabled && this.projectKind === ProjectKind.Inferred) { + return undefined; + } return this.rootFiles.map(info => info.fileName); } getFileNames() { - if (this.languageServiceDiabled) { - if (!this.projectOptions) { - return undefined; + if (!this.languageServiceEnabled) { + let rootFiles = this.getRootFiles(); + if (this.compilerOptions) { + const defaultLibrary = getDefaultLibFilePath(this.compilerOptions); + if (defaultLibrary) { + (rootFiles || (rootFiles = [])).push(defaultLibrary); + } } - - const fileNames: string[] = []; - if (this.projectOptions && this.projectOptions.compilerOptions) { - fileNames.push(getDefaultLibFilePath(this.projectOptions.compilerOptions)); - } - ts.addRange(fileNames, this.projectOptions.files); - return fileNames; + return rootFiles; } - const sourceFiles = this.program.getSourceFiles(); return sourceFiles.map(sourceFile => sourceFile.fileName); } containsScriptInfo(info: ScriptInfo): boolean { - return this.program.getSourceFileByPath(info.path) !== undefined; + return this.program && this.program.getSourceFileByPath(info.path) !== undefined; } containsFile(filename: string, requireOpen?: boolean) { @@ -455,8 +545,13 @@ namespace ts.server { // Used to keep track of what directories are watched for this project directoriesWatchedForTsconfig: string[] = []; - constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry) { - super(ProjectKind.Inferred, projectService, documentRegistry, /*files*/ undefined, /*compilerOptions*/ undefined); + constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, languageServiceEnabled: boolean) { + super(ProjectKind.Inferred, + projectService, + documentRegistry, + /*files*/ undefined, + languageServiceEnabled, + /*compilerOptions*/ undefined); } close() { @@ -483,6 +578,9 @@ namespace ts.server { currentVersion: number = 1; updateGraph() { + if (!this.languageServiceEnabled) { + return; + } const oldProgram = this.program; super.updateGraph(); @@ -538,8 +636,13 @@ namespace ts.server { /** Used for configured projects which may have multiple open roots */ openRefCount = 0; - constructor(readonly configFileName: string, projectService: ProjectService, documentRegistry: ts.DocumentRegistry, hasExplicitListOfFiles: boolean, compilerOptions: CompilerOptions) { - super(ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, compilerOptions); + constructor(readonly configFileName: string, + projectService: ProjectService, + documentRegistry: ts.DocumentRegistry, + hasExplicitListOfFiles: boolean, + compilerOptions: CompilerOptions, + languageServiceEnabled: boolean) { + super(ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions); } getProjectFileName() { @@ -551,20 +654,29 @@ namespace ts.server { } watchConfigDirectory(callback: (project: ConfiguredProject, path: string) => void) { + if (this.directoryWatcher) { + return; + } + const directoryToWatch = ts.getDirectoryPath(this.configFileName); this.projectService.log(`Add recursive watcher for: ${directoryToWatch}`); this.directoryWatcher = this.projectService.host.watchDirectory(directoryToWatch, path => callback(this, path), /*recursive*/ true); } + stopWatchingDirectory() { + if (this.directoryWatcher) { + this.directoryWatcher.close(); + this.directoryWatcher = undefined; + } + } + close() { super.close(); if (this.projectFileWatcher) { this.projectFileWatcher.close(); } - if (this.directoryWatcher) { - this.directoryWatcher.close(); - } + this.stopWatchingDirectory(); } addOpenRef() { @@ -578,8 +690,12 @@ namespace ts.server { } class ExternalProject extends VersionedProject { - constructor(readonly projectFileName: string, projectService: ProjectService, documentRegistry: ts.DocumentRegistry, compilerOptions: CompilerOptions) { - super(ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, compilerOptions); + constructor(readonly projectFileName: string, + projectService: ProjectService, + documentRegistry: ts.DocumentRegistry, + compilerOptions: CompilerOptions, + languageServiceEnabled: boolean) { + super(ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions); } getProjectFileName() { @@ -1149,7 +1265,10 @@ namespace ts.server { } } - private exceedTotalNonTsFileSizeLimit(fileNames: string[]) { + private exceedTotalNonTsFileSizeLimit(options: CompilerOptions, fileNames: string[]) { + if (options && options.disableSizeLimit) { + return false; + } let totalNonTsFileSize = 0; if (!this.host.getFileSize) { return false; @@ -1168,23 +1287,38 @@ namespace ts.server { } private createAndAddExternalProject(projectFileName: string, files: string[], compilerOptions: CompilerOptions, clientFileName?: string) { - const project = new ExternalProject(projectFileName, this, this.documentRegistry, compilerOptions); + const sizeLimitExceeded = this.exceedTotalNonTsFileSizeLimit(compilerOptions, files); + const project = new ExternalProject(projectFileName, this, this.documentRegistry, compilerOptions, !sizeLimitExceeded); const errors = this.addFilesToProject(project, files, clientFileName); this.externalProjects.push(project); return { project, errors }; } private createAndAddConfiguredProject(configFileName: string, projectOptions: ProjectOptions, clientFileName?: string) { - const project = new ConfiguredProject(configFileName, this, this.documentRegistry, projectOptions.configHasFilesProperty, projectOptions.compilerOptions); + const sizeLimitExceeded = this.exceedTotalNonTsFileSizeLimit(projectOptions.compilerOptions, projectOptions.files); + const project = new ConfiguredProject( + configFileName, + this, + this.documentRegistry, + projectOptions.configHasFilesProperty, + projectOptions.compilerOptions, + !sizeLimitExceeded); + const errors = this.addFilesToProject(project, projectOptions.files, clientFileName); project.watchConfigFile(project => this.onConfigChangedForConfiguredProject(project)); - if (!projectOptions.configHasFilesProperty) { - project.watchConfigDirectory((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path)); + if (!sizeLimitExceeded) { + this.watchConfigDirectoryForProject(project, projectOptions); } this.configuredProjects.push(project); return { project, errors }; } + private watchConfigDirectoryForProject(project: ConfiguredProject, options: ProjectOptions) { + if (!options.configHasFilesProperty) { + project.watchConfigDirectory((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path)); + } + } + private addFilesToProject(project: ConfiguredProject | ExternalProject, files: string[], clientFileName: string): Diagnostic[] { let errors: Diagnostic[]; for (const rootFilename of files) { @@ -1266,13 +1400,28 @@ namespace ts.server { return errors; } else { - this.updateVersionedProjectWorker(project, projectOptions.files, projectOptions.compilerOptions); + if (this.exceedTotalNonTsFileSizeLimit(projectOptions.compilerOptions, projectOptions.files)) { + project.setCompilerOptions(projectOptions.compilerOptions); + if (!project.languageServiceEnabled) { + // language service is already disabled + return; + } + project.disableLanguageService(); + project.stopWatchingDirectory(); + } + else { + if (!project.languageServiceEnabled) { + project.enableLanguageService(); + } + this.watchConfigDirectoryForProject(project, projectOptions); + this.updateVersionedProjectWorker(project, projectOptions.files, projectOptions.compilerOptions); + } } } } createAndAddInferredProject(root: ScriptInfo) { - const project = new InferredProject(this, this.documentRegistry); + const project = new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true); project.addRoot(root); let currentPath = ts.getDirectoryPath(root.fileName); diff --git a/src/server/session.ts b/src/server/session.ts index d34bacc8188..237af69fffd 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -421,7 +421,7 @@ namespace ts.server { private getDefinition(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.FileSpan[] | DefinitionInfo[] { const file = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(file); - if (!project || project.languageServiceDiabled) { + if (!project) { throw Errors.NoProject; } @@ -451,7 +451,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 || project.languageServiceDiabled) { + if (!project) { throw Errors.NoProject; } @@ -477,7 +477,7 @@ namespace ts.server { fileName = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(fileName); - if (!project || project.languageServiceDiabled) { + if (!project) { throw Errors.NoProject; } @@ -508,7 +508,7 @@ namespace ts.server { const fileName = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(fileName); - if (!project || project.languageServiceDiabled) { + if (!project) { throw Errors.NoProject; } @@ -554,7 +554,8 @@ namespace ts.server { } const projectInfo: protocol.ProjectInfo = { - configFileName: project.getProjectFileName() + configFileName: project.getProjectFileName(), + languageServiceDisabled: !project.languageServiceEnabled }; if (needFileNameList) { @@ -583,6 +584,7 @@ namespace ts.server { const info = this.projectService.getScriptInfo(file); projects = this.projectService.findReferencingProjects(info); } + projects = filter(projects, p => p.languageServiceEnabled); if (!projects || !projects.length) { throw Errors.NoProject; } @@ -781,7 +783,7 @@ namespace ts.server { private getFileAndProject(fileName: string) { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project || project.languageServiceDiabled) { + if (!project) { throw Errors.NoProject; } return { file, project }; @@ -862,7 +864,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 || project.languageServiceDiabled) { + if (!project) { throw Errors.NoProject; } @@ -919,7 +921,7 @@ namespace ts.server { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); - if (!project || project.languageServiceDiabled) { + if (!project) { throw Errors.NoProject; } @@ -990,7 +992,7 @@ namespace ts.server { const prefix = args.prefix || ""; const file = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(file); - if (!project || project.languageServiceDiabled) { + if (!project) { throw Errors.NoProject; } @@ -1017,7 +1019,7 @@ namespace ts.server { private getCompletionEntryDetails(args: protocol.CompletionDetailsRequestArgs): protocol.CompletionEntryDetails[] { const file = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(file); - if (!project || project.languageServiceDiabled) { + if (!project) { throw Errors.NoProject; } @@ -1036,7 +1038,7 @@ namespace ts.server { private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems { const file = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(file); - if (!project || project.languageServiceDiabled) { + if (!project) { throw Errors.NoProject; } @@ -1069,7 +1071,7 @@ namespace ts.server { const checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => { fileName = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(fileName); - if (project && !project.languageServiceDiabled) { + if (project) { accum.push({ fileName, project }); } return accum; @@ -1099,7 +1101,7 @@ namespace ts.server { const file = ts.normalizePath(fileName); const tmpfile = ts.normalizePath(tempFileName); const project = this.projectService.getProjectForFile(file); - if (project && !project.languageServiceDiabled) { + if (project) { this.changeSeq++; // make sure no changes happen before this one is finished project.reloadScript(file, tmpfile, () => { @@ -1126,7 +1128,7 @@ namespace ts.server { this.projectService.closeClientFile(file); } - private decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[], lineIndex: LineIndex): protocol.NavigationBarItem[] { + private decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): protocol.NavigationBarItem[] { if (!items) { return undefined; } @@ -1141,7 +1143,7 @@ namespace ts.server { start: scriptInfo.positionToLineOffset(span.start), end: scriptInfo.positionToLineOffset(ts.textSpanEnd(span)) })), - childItems: this.decorateNavigationBarItem(project, fileName, item.childItems, lineIndex), + childItems: this.decorateNavigationBarItem(project, fileName, item.childItems), indent: item.indent })); } diff --git a/tests/cases/unittests/tsserverProjectSystem.ts b/tests/cases/unittests/tsserverProjectSystem.ts index afd0813b31a..488c2e8c69c 100644 --- a/tests/cases/unittests/tsserverProjectSystem.ts +++ b/tests/cases/unittests/tsserverProjectSystem.ts @@ -604,7 +604,7 @@ namespace ts { }` }; const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [file1, file2, configFile]); - const projectService = new server.ProjectService(host, nullLogger); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); projectService.openClientFile(file1.path); projectService.closeClientFile(file1.path); projectService.openClientFile(file2.path); @@ -631,7 +631,7 @@ namespace ts { }` }; const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [file1, file2, configFile]); - const projectService = new server.ProjectService(host, nullLogger); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); projectService.openClientFile(file1.path); projectService.closeClientFile(file1.path); projectService.openClientFile(file2.path);