From f4b771a0c6a8d94372cc0ea2bef4d7478d27f9bd Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Tue, 25 Apr 2017 12:13:06 -0700 Subject: [PATCH] feat(language-service): provide external file list to TypeScript (#16417) Also ensures that it only calls base language service for files that it contains. PR Close #16417 --- packages/language-service/index.ts | 2 +- packages/language-service/src/ts_plugin.ts | 204 ++++++++++++++++++--- 2 files changed, 178 insertions(+), 28 deletions(-) diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index 53c13320f8..4e0fc2b3be 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -12,7 +12,7 @@ * Entry point for all public APIs of the language service package. */ export {createLanguageService} from './src/language_service'; -export {create} from './src/ts_plugin'; +export * from './src/ts_plugin'; export {Completion, Completions, Declaration, Declarations, Definition, Diagnostic, Diagnostics, Hover, HoverTextSection, LanguageService, LanguageServiceHost, Location, Span, TemplateSource, TemplateSources} from './src/types'; export {TypeScriptServiceHost, createLanguageServiceFromTypescript} from './src/typescript_host'; export {VERSION} from './src/version'; diff --git a/packages/language-service/src/ts_plugin.ts b/packages/language-service/src/ts_plugin.ts index 3470097cb6..097bfaef78 100644 --- a/packages/language-service/src/ts_plugin.ts +++ b/packages/language-service/src/ts_plugin.ts @@ -12,10 +12,155 @@ import {createLanguageService} from './language_service'; import {Completion, Diagnostic, LanguageService, LanguageServiceHost} from './types'; import {TypeScriptServiceHost} from './typescript_host'; +const projectHostMap = new WeakMap(); + +export function getExternalFiles(project: any): string[]|undefined { + const host = projectHostMap.get(project); + if (host) { + return host.getTemplateReferences(); + } +} + +const angularOnlyResults = process.argv.indexOf('--angularOnlyResults') >= 0; + +function angularOnlyFilter(ls: ts.LanguageService): ts.LanguageService { + return { + cleanupSemanticCache: () => ls.cleanupSemanticCache(), + getSyntacticDiagnostics: fileName => [], + getSemanticDiagnostics: fileName => [], + getCompilerOptionsDiagnostics: () => [], + getSyntacticClassifications: (fileName, span) => [], + getSemanticClassifications: (fileName, span) => [], + getEncodedSyntacticClassifications: (fileName, span) => {undefined}, + getEncodedSemanticClassifications: (fileName, span) => undefined, + getCompletionsAtPosition: (fileName, position) => undefined, + getCompletionEntryDetails: (fileName, position, entryName) => + undefined, + getCompletionEntrySymbol: (fileName, position, entryName) => undefined, + getQuickInfoAtPosition: (fileName, position) => undefined, + getNameOrDottedNameSpan: (fileName, startPos, endPos) => undefined, + getBreakpointStatementAtPosition: (fileName, position) => undefined, + getSignatureHelpItems: (fileName, position) => undefined, + getRenameInfo: (fileName, position) => undefined, + findRenameLocations: (fileName, position, findInStrings, findInComments) => + [], + getDefinitionAtPosition: (fileName, position) => [], + getTypeDefinitionAtPosition: (fileName, position) => [], + getImplementationAtPosition: (fileName, position) => [], + getReferencesAtPosition: (fileName: string, position: number) => [], + findReferences: (fileName: string, position: number) => [], + getDocumentHighlights: (fileName, position, filesToSearch) => [], + /** @deprecated */ + getOccurrencesAtPosition: (fileName, position) => [], + getNavigateToItems: searchValue => [], + getNavigationBarItems: fileName => [], + getNavigationTree: fileName => undefined, + getOutliningSpans: fileName => [], + getTodoComments: (fileName, descriptors) => [], + getBraceMatchingAtPosition: (fileName, position) => [], + getIndentationAtPosition: (fileName, position, options) => undefined, + getFormattingEditsForRange: (fileName, start, end, options) => [], + getFormattingEditsForDocument: (fileName, options) => [], + getFormattingEditsAfterKeystroke: (fileName, position, key, options) => [], + getDocCommentTemplateAtPosition: (fileName, position) => undefined, + isValidBraceCompletionAtPosition: (fileName, position, openingBrace) => undefined, + getCodeFixesAtPosition: (fileName, start, end, errorCodes) => [], + getEmitOutput: fileName => undefined, + getProgram: () => ls.getProgram(), + dispose: () => ls.dispose() + }; +} + export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageService { // Create the proxy const proxy: ts.LanguageService = Object.create(null); - const oldLS: ts.LanguageService = info.languageService; + let oldLS: ts.LanguageService = info.languageService; + + if (angularOnlyResults) { + oldLS = angularOnlyFilter(oldLS); + } + + function tryCall(fileName: string | undefined, callback: () => T): T { + if (fileName && !oldLS.getProgram().getSourceFile(fileName)) { + return undefined as any as T; + } + try { + return callback(); + } catch (e) { + return undefined as any as T; + } + } + + function tryFilenameCall(m: (fileName: string) => T): (fileName: string) => T { + return fileName => tryCall(fileName, () => (m.call(ls, fileName))); + } + + function tryFilenameOneCall(m: (fileName: string, p: P) => T): (filename: string, p: P) => + T { + return (fileName, p) => tryCall(fileName, () => (m.call(ls, fileName, p))); + } + + function tryFilenameTwoCall(m: (fileName: string, p1: P1, p2: P2) => T): ( + filename: string, p1: P1, p2: P2) => T { + return (fileName, p1, p2) => tryCall(fileName, () => (m.call(ls, fileName, p1, p2))); + } + + function tryFilenameThreeCall(m: (fileName: string, p1: P1, p2: P2, p3: P3) => T): + (filename: string, p1: P1, p2: P2, p3: P3) => T { + return (fileName, p1, p2, p3) => tryCall(fileName, () => (m.call(ls, fileName, p1, p2, p3))); + } + + function typescriptOnly(ls: ts.LanguageService): ts.LanguageService { + return { + cleanupSemanticCache: () => ls.cleanupSemanticCache(), + getSyntacticDiagnostics: tryFilenameCall(ls.getSyntacticDiagnostics), + getSemanticDiagnostics: tryFilenameCall(ls.getSemanticDiagnostics), + getCompilerOptionsDiagnostics: () => ls.getCompilerOptionsDiagnostics(), + getSyntacticClassifications: tryFilenameOneCall(ls.getSemanticClassifications), + getSemanticClassifications: tryFilenameOneCall(ls.getSemanticClassifications), + getEncodedSyntacticClassifications: tryFilenameOneCall(ls.getEncodedSyntacticClassifications), + getEncodedSemanticClassifications: tryFilenameOneCall(ls.getEncodedSemanticClassifications), + getCompletionsAtPosition: tryFilenameOneCall(ls.getCompletionsAtPosition), + getCompletionEntryDetails: tryFilenameTwoCall(ls.getCompletionEntryDetails), + getCompletionEntrySymbol: tryFilenameTwoCall(ls.getCompletionEntrySymbol), + getQuickInfoAtPosition: tryFilenameOneCall(ls.getQuickInfoAtPosition), + getNameOrDottedNameSpan: tryFilenameTwoCall(ls.getNameOrDottedNameSpan), + getBreakpointStatementAtPosition: tryFilenameOneCall(ls.getBreakpointStatementAtPosition), + getSignatureHelpItems: tryFilenameOneCall(ls.getSignatureHelpItems), + getRenameInfo: tryFilenameOneCall(ls.getRenameInfo), + findRenameLocations: tryFilenameThreeCall(ls.findRenameLocations), + getDefinitionAtPosition: tryFilenameOneCall(ls.getDefinitionAtPosition), + getTypeDefinitionAtPosition: tryFilenameOneCall(ls.getTypeDefinitionAtPosition), + getImplementationAtPosition: tryFilenameOneCall(ls.getImplementationAtPosition), + getReferencesAtPosition: tryFilenameOneCall(ls.getReferencesAtPosition), + findReferences: tryFilenameOneCall(ls.findReferences), + getDocumentHighlights: tryFilenameTwoCall(ls.getDocumentHighlights), + /** @deprecated */ + getOccurrencesAtPosition: tryFilenameOneCall(ls.getOccurrencesAtPosition), + getNavigateToItems: + (searchValue, maxResultCount, fileName, excludeDtsFiles) => tryCall( + fileName, + () => ls.getNavigateToItems(searchValue, maxResultCount, fileName, excludeDtsFiles)), + getNavigationBarItems: tryFilenameCall(ls.getNavigationBarItems), + getNavigationTree: tryFilenameCall(ls.getNavigationTree), + getOutliningSpans: tryFilenameCall(ls.getOutliningSpans), + getTodoComments: tryFilenameOneCall(ls.getTodoComments), + getBraceMatchingAtPosition: tryFilenameOneCall(ls.getBraceMatchingAtPosition), + getIndentationAtPosition: tryFilenameTwoCall(ls.getIndentationAtPosition), + getFormattingEditsForRange: tryFilenameThreeCall(ls.getFormattingEditsForRange), + getFormattingEditsForDocument: tryFilenameOneCall(ls.getFormattingEditsForDocument), + getFormattingEditsAfterKeystroke: tryFilenameThreeCall(ls.getFormattingEditsAfterKeystroke), + getDocCommentTemplateAtPosition: tryFilenameOneCall(ls.getDocCommentTemplateAtPosition), + isValidBraceCompletionAtPosition: tryFilenameTwoCall(ls.isValidBraceCompletionAtPosition), + getCodeFixesAtPosition: tryFilenameThreeCall(ls.getCodeFixesAtPosition), + getEmitOutput: tryFilenameCall(ls.getEmitOutput), + getProgram: () => ls.getProgram(), + dispose: () => ls.dispose() + }; + } + + oldLS = typescriptOnly(oldLS); + for (const k in oldLS) { (proxy)[k] = function() { return (oldLS as any)[k].apply(oldLS, arguments); }; } @@ -35,21 +180,28 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS }; } - function tryOperation(attempting: string, callback: () => void) { + function tryOperation(attempting: string, callback: () => T): T|null { try { - callback(); + return callback(); } catch (e) { info.project.projectService.logger.info(`Failed to ${attempting}: ${e.toString()}`); info.project.projectService.logger.info(`Stack trace: ${e.stack}`); + return null; } } const serviceHost = new TypeScriptServiceHost(info.languageServiceHost, info.languageService); const ls = createLanguageService(serviceHost as any); serviceHost.setSite(ls); + projectHostMap.set(info.project, serviceHost); proxy.getCompletionsAtPosition = function(fileName: string, position: number) { - let base = oldLS.getCompletionsAtPosition(fileName, position); + let base = oldLS.getCompletionsAtPosition(fileName, position) || { + isGlobalCompletion: false, + isMemberCompletion: false, + isNewIdentifierLocation: false, + entries: [] + }; tryOperation('get completions', () => { const results = ls.getCompletionsAt(fileName, position); if (results && results.length) { @@ -72,14 +224,14 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS proxy.getQuickInfoAtPosition = function(fileName: string, position: number): ts.QuickInfo { let base = oldLS.getQuickInfoAtPosition(fileName, position); // TODO(vicb): the tags property has been removed in TS 2.2 - const tags = (base).tags; tryOperation('get quick info', () => { const ours = ls.getHoverAt(fileName, position); if (ours) { - const displayParts: typeof base.displayParts = []; + const displayParts: ts.SymbolDisplayPart[] = []; for (const part of ours.text) { displayParts.push({kind: part.language !, text: part.text}); } + const tags = base && (base).tags; base = { displayParts, documentation: [], @@ -97,10 +249,8 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS }; proxy.getSemanticDiagnostics = function(fileName: string) { - let base = oldLS.getSemanticDiagnostics(fileName); - if (base === undefined) { - base = []; - } + let result = oldLS.getSemanticDiagnostics(fileName); + const base = result || []; tryOperation('get diagnostics', () => { info.project.projectService.logger.info(`Computing Angular semantic diagnostics...`); const ours = ls.getDiagnostics(fileName); @@ -120,23 +270,23 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS return base; } - tryOperation('get definition', () => { - const ours = ls.getDefinitionAt(fileName, position); - if (ours && ours.length) { - base = base || []; - for (const loc of ours) { - base.push({ - fileName: loc.fileName, - textSpan: {start: loc.span.start, length: loc.span.end - loc.span.start}, - name: '', - kind: 'definition', - containerName: loc.fileName, - containerKind: 'file' - }); - } - } - }); - return base; + return tryOperation('get definition', () => { + const ours = ls.getDefinitionAt(fileName, position); + if (ours && ours.length) { + base = base || []; + for (const loc of ours) { + base.push({ + fileName: loc.fileName, + textSpan: {start: loc.span.start, length: loc.span.end - loc.span.start}, + name: '', + kind: 'definition', + containerName: loc.fileName, + containerKind: 'file' + }); + } + } + return base; + }) || []; }; return proxy;