2016-11-22 12:10:23 -05:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
|
|
|
import * as ts from 'typescript';
|
|
|
|
|
|
|
|
import {createLanguageService} from './language_service';
|
2017-11-14 20:49:47 -05:00
|
|
|
import {Completion, Diagnostic, DiagnosticMessageChain, LanguageService, LanguageServiceHost} from './types';
|
2016-11-22 12:10:23 -05:00
|
|
|
import {TypeScriptServiceHost} from './typescript_host';
|
|
|
|
|
2017-04-25 15:13:06 -04:00
|
|
|
const projectHostMap = new WeakMap<any, TypeScriptServiceHost>();
|
|
|
|
|
|
|
|
export function getExternalFiles(project: any): string[]|undefined {
|
|
|
|
const host = projectHostMap.get(project);
|
|
|
|
if (host) {
|
|
|
|
return host.getTemplateReferences();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-06 23:43:17 -05:00
|
|
|
export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageService {
|
|
|
|
// Create the proxy
|
|
|
|
const proxy: ts.LanguageService = Object.create(null);
|
2017-04-25 15:13:06 -04:00
|
|
|
let oldLS: ts.LanguageService = info.languageService;
|
|
|
|
|
2018-08-14 10:26:44 -04:00
|
|
|
function tryCall<T>(fileName: string | undefined, callback: () => T): T|undefined {
|
2018-08-05 11:31:27 -04:00
|
|
|
if (fileName && !oldLS.getProgram() !.getSourceFile(fileName)) {
|
2018-08-14 10:26:44 -04:00
|
|
|
return undefined;
|
2017-04-25 15:13:06 -04:00
|
|
|
}
|
|
|
|
try {
|
|
|
|
return callback();
|
2018-08-14 10:26:44 -04:00
|
|
|
} catch {
|
|
|
|
return undefined;
|
2017-04-25 15:13:06 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-14 10:26:44 -04:00
|
|
|
function tryFilenameCall<T>(m: (fileName: string) => T): (fileName: string) => T | undefined {
|
2017-04-25 15:13:06 -04:00
|
|
|
return fileName => tryCall(fileName, () => <T>(m.call(ls, fileName)));
|
|
|
|
}
|
|
|
|
|
|
|
|
function tryFilenameOneCall<T, P>(m: (fileName: string, p: P) => T): (filename: string, p: P) =>
|
2018-08-14 10:26:44 -04:00
|
|
|
T | undefined {
|
2017-04-25 15:13:06 -04:00
|
|
|
return (fileName, p) => tryCall(fileName, () => <T>(m.call(ls, fileName, p)));
|
|
|
|
}
|
|
|
|
|
|
|
|
function tryFilenameTwoCall<T, P1, P2>(m: (fileName: string, p1: P1, p2: P2) => T): (
|
2018-08-14 10:26:44 -04:00
|
|
|
filename: string, p1: P1, p2: P2) => T | undefined {
|
2017-04-25 15:13:06 -04:00
|
|
|
return (fileName, p1, p2) => tryCall(fileName, () => <T>(m.call(ls, fileName, p1, p2)));
|
|
|
|
}
|
|
|
|
|
|
|
|
function tryFilenameThreeCall<T, P1, P2, P3>(m: (fileName: string, p1: P1, p2: P2, p3: P3) => T):
|
2018-08-14 10:26:44 -04:00
|
|
|
(filename: string, p1: P1, p2: P2, p3: P3) => T | undefined {
|
2017-04-25 15:13:06 -04:00
|
|
|
return (fileName, p1, p2, p3) => tryCall(fileName, () => <T>(m.call(ls, fileName, p1, p2, p3)));
|
|
|
|
}
|
|
|
|
|
2017-05-16 16:29:38 -04:00
|
|
|
function tryFilenameFourCall<T, P1, P2, P3, P4>(
|
|
|
|
m: (fileName: string, p1: P1, p2: P2, p3: P3, p4: P4) =>
|
2018-08-14 10:26:44 -04:00
|
|
|
T): (fileName: string, p1: P1, p2: P2, p3: P3, p4: P4) => T | undefined {
|
2017-05-16 16:29:38 -04:00
|
|
|
return (fileName, p1, p2, p3, p4) =>
|
|
|
|
tryCall(fileName, () => <T>(m.call(ls, fileName, p1, p2, p3, p4)));
|
|
|
|
}
|
|
|
|
|
2018-06-25 05:11:22 -04:00
|
|
|
function tryFilenameFiveCall<T, P1, P2, P3, P4, P5>(
|
|
|
|
m: (fileName: string, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) =>
|
2018-08-14 10:26:44 -04:00
|
|
|
T): (fileName: string, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T | undefined {
|
2018-06-25 05:11:22 -04:00
|
|
|
return (fileName, p1, p2, p3, p4, p5) =>
|
|
|
|
tryCall(fileName, () => <T>(m.call(ls, fileName, p1, p2, p3, p4, p5)));
|
|
|
|
}
|
|
|
|
|
2017-04-25 15:13:06 -04:00
|
|
|
function typescriptOnly(ls: ts.LanguageService): ts.LanguageService {
|
2018-05-08 16:37:54 -04:00
|
|
|
const languageService: ts.LanguageService = {
|
2017-04-25 15:13:06 -04:00
|
|
|
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),
|
2017-12-22 12:36:47 -05:00
|
|
|
getCompletionsAtPosition: tryFilenameTwoCall(ls.getCompletionsAtPosition),
|
2018-06-25 05:11:22 -04:00
|
|
|
getCompletionEntryDetails: tryFilenameFiveCall(ls.getCompletionEntryDetails),
|
2017-12-22 12:36:47 -05:00
|
|
|
getCompletionEntrySymbol: tryFilenameThreeCall(ls.getCompletionEntrySymbol),
|
2018-08-05 11:31:27 -04:00
|
|
|
getJsxClosingTagAtPosition: tryFilenameOneCall(ls.getJsxClosingTagAtPosition),
|
2017-04-25 15:13:06 -04:00
|
|
|
getQuickInfoAtPosition: tryFilenameOneCall(ls.getQuickInfoAtPosition),
|
|
|
|
getNameOrDottedNameSpan: tryFilenameTwoCall(ls.getNameOrDottedNameSpan),
|
|
|
|
getBreakpointStatementAtPosition: tryFilenameOneCall(ls.getBreakpointStatementAtPosition),
|
2018-08-05 11:31:27 -04:00
|
|
|
getSignatureHelpItems: tryFilenameTwoCall(ls.getSignatureHelpItems),
|
2017-04-25 15:13:06 -04:00
|
|
|
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),
|
2017-12-22 12:36:47 -05:00
|
|
|
getSpanOfEnclosingComment: tryFilenameTwoCall(ls.getSpanOfEnclosingComment),
|
2018-06-25 05:11:22 -04:00
|
|
|
getCodeFixesAtPosition: tryFilenameFiveCall(ls.getCodeFixesAtPosition),
|
2017-12-22 12:36:47 -05:00
|
|
|
applyCodeActionCommand:
|
|
|
|
<any>((action: any) => tryCall(undefined, () => ls.applyCodeActionCommand(action))),
|
2017-04-25 15:13:06 -04:00
|
|
|
getEmitOutput: tryFilenameCall(ls.getEmitOutput),
|
|
|
|
getProgram: () => ls.getProgram(),
|
2017-09-08 21:40:32 -04:00
|
|
|
dispose: () => ls.dispose(),
|
2018-06-25 05:11:22 -04:00
|
|
|
getApplicableRefactors: tryFilenameTwoCall(ls.getApplicableRefactors),
|
|
|
|
getEditsForRefactor: tryFilenameFiveCall(ls.getEditsForRefactor),
|
2018-02-08 11:59:25 -05:00
|
|
|
getDefinitionAndBoundSpan: tryFilenameOneCall(ls.getDefinitionAndBoundSpan),
|
|
|
|
getCombinedCodeFix:
|
2018-06-25 05:11:22 -04:00
|
|
|
(scope: ts.CombinedCodeFixScope, fixId: {}, formatOptions: ts.FormatCodeSettings,
|
|
|
|
preferences: ts.UserPreferences) =>
|
|
|
|
tryCall(
|
|
|
|
undefined, () => ls.getCombinedCodeFix(scope, fixId, formatOptions, preferences)),
|
2018-05-08 16:37:54 -04:00
|
|
|
// TODO(kyliau): dummy implementation to compile with ts 2.8, create real one
|
|
|
|
getSuggestionDiagnostics: (fileName: string) => [],
|
|
|
|
// TODO(kyliau): dummy implementation to compile with ts 2.8, create real one
|
|
|
|
organizeImports: (scope: ts.CombinedCodeFixScope, formatOptions: ts.FormatCodeSettings) => [],
|
2018-06-25 05:11:22 -04:00
|
|
|
// TODO: dummy implementation to compile with ts 2.9, create a real one
|
|
|
|
getEditsForFileRename:
|
|
|
|
(oldFilePath: string, newFilePath: string, formatOptions: ts.FormatCodeSettings,
|
|
|
|
preferences: ts.UserPreferences | undefined) => []
|
2018-05-08 16:37:54 -04:00
|
|
|
} as ts.LanguageService;
|
|
|
|
return languageService;
|
2017-04-25 15:13:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
oldLS = typescriptOnly(oldLS);
|
|
|
|
|
2017-01-06 23:43:17 -05:00
|
|
|
for (const k in oldLS) {
|
|
|
|
(<any>proxy)[k] = function() { return (oldLS as any)[k].apply(oldLS, arguments); };
|
|
|
|
}
|
2016-11-22 12:10:23 -05:00
|
|
|
|
2017-01-06 23:43:17 -05:00
|
|
|
function completionToEntry(c: Completion): ts.CompletionEntry {
|
2017-10-26 20:23:30 -04:00
|
|
|
return {
|
|
|
|
// TODO: remove any and fix type error.
|
|
|
|
kind: c.kind as any,
|
|
|
|
name: c.name,
|
|
|
|
sortText: c.sort,
|
|
|
|
kindModifiers: ''
|
|
|
|
};
|
2017-01-06 23:43:17 -05:00
|
|
|
}
|
2016-11-22 12:10:23 -05:00
|
|
|
|
2017-11-14 20:49:47 -05:00
|
|
|
function diagnosticChainToDiagnosticChain(chain: DiagnosticMessageChain):
|
|
|
|
ts.DiagnosticMessageChain {
|
|
|
|
return {
|
|
|
|
messageText: chain.message,
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
code: 0,
|
|
|
|
next: chain.next ? diagnosticChainToDiagnosticChain(chain.next) : undefined
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function diagnosticMessageToDiagnosticMessageText(message: string | DiagnosticMessageChain):
|
|
|
|
string|ts.DiagnosticMessageChain {
|
|
|
|
if (typeof message === 'string') {
|
|
|
|
return message;
|
|
|
|
}
|
|
|
|
return diagnosticChainToDiagnosticChain(message);
|
|
|
|
}
|
|
|
|
|
2017-01-06 23:43:17 -05:00
|
|
|
function diagnosticToDiagnostic(d: Diagnostic, file: ts.SourceFile): ts.Diagnostic {
|
2017-04-28 18:10:30 -04:00
|
|
|
const result = {
|
2017-01-06 23:43:17 -05:00
|
|
|
file,
|
|
|
|
start: d.span.start,
|
|
|
|
length: d.span.end - d.span.start,
|
2017-11-14 20:49:47 -05:00
|
|
|
messageText: diagnosticMessageToDiagnosticMessageText(d.message),
|
2017-01-06 23:43:17 -05:00
|
|
|
category: ts.DiagnosticCategory.Error,
|
2017-04-28 18:10:30 -04:00
|
|
|
code: 0,
|
|
|
|
source: 'ng'
|
2017-01-06 23:43:17 -05:00
|
|
|
};
|
2017-04-28 18:10:30 -04:00
|
|
|
return result;
|
2017-01-06 23:43:17 -05:00
|
|
|
}
|
2016-11-22 12:10:23 -05:00
|
|
|
|
2017-04-25 15:13:06 -04:00
|
|
|
function tryOperation<T>(attempting: string, callback: () => T): T|null {
|
2017-01-06 23:43:17 -05:00
|
|
|
try {
|
2017-04-25 15:13:06 -04:00
|
|
|
return callback();
|
2017-01-06 23:43:17 -05:00
|
|
|
} catch (e) {
|
|
|
|
info.project.projectService.logger.info(`Failed to ${attempting}: ${e.toString()}`);
|
|
|
|
info.project.projectService.logger.info(`Stack trace: ${e.stack}`);
|
2017-04-25 15:13:06 -04:00
|
|
|
return null;
|
2017-01-06 23:43:17 -05:00
|
|
|
}
|
2016-11-22 12:10:23 -05:00
|
|
|
}
|
|
|
|
|
2017-01-06 23:43:17 -05:00
|
|
|
const serviceHost = new TypeScriptServiceHost(info.languageServiceHost, info.languageService);
|
2017-03-24 12:57:32 -04:00
|
|
|
const ls = createLanguageService(serviceHost as any);
|
2017-01-06 23:43:17 -05:00
|
|
|
serviceHost.setSite(ls);
|
2017-04-25 15:13:06 -04:00
|
|
|
projectHostMap.set(info.project, serviceHost);
|
2017-01-06 23:43:17 -05:00
|
|
|
|
2017-12-22 12:36:47 -05:00
|
|
|
proxy.getCompletionsAtPosition = function(
|
|
|
|
fileName: string, position: number, options: ts.GetCompletionsAtPositionOptions|undefined) {
|
|
|
|
let base = oldLS.getCompletionsAtPosition(fileName, position, options) || {
|
2017-04-25 15:13:06 -04:00
|
|
|
isGlobalCompletion: false,
|
|
|
|
isMemberCompletion: false,
|
|
|
|
isNewIdentifierLocation: false,
|
|
|
|
entries: []
|
|
|
|
};
|
2017-01-06 23:43:17 -05:00
|
|
|
tryOperation('get completions', () => {
|
|
|
|
const results = ls.getCompletionsAt(fileName, position);
|
|
|
|
if (results && results.length) {
|
|
|
|
if (base === undefined) {
|
2017-01-24 12:05:34 -05:00
|
|
|
base = {
|
|
|
|
isGlobalCompletion: false,
|
|
|
|
isMemberCompletion: false,
|
|
|
|
isNewIdentifierLocation: false,
|
|
|
|
entries: []
|
|
|
|
};
|
2017-01-06 23:43:17 -05:00
|
|
|
}
|
|
|
|
for (const entry of results) {
|
|
|
|
base.entries.push(completionToEntry(entry));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return base;
|
|
|
|
};
|
|
|
|
|
2018-08-05 11:31:27 -04:00
|
|
|
proxy.getQuickInfoAtPosition = function(fileName: string, position: number): ts.QuickInfo |
|
|
|
|
undefined {
|
|
|
|
let base = oldLS.getQuickInfoAtPosition(fileName, position);
|
|
|
|
// TODO(vicb): the tags property has been removed in TS 2.2
|
|
|
|
tryOperation('get quick info', () => {
|
|
|
|
const ours = ls.getHoverAt(fileName, position);
|
|
|
|
if (ours) {
|
|
|
|
const displayParts: ts.SymbolDisplayPart[] = [];
|
|
|
|
for (const part of ours.text) {
|
|
|
|
displayParts.push({kind: part.language || 'angular', text: part.text});
|
|
|
|
}
|
|
|
|
const tags = base && (<any>base).tags;
|
|
|
|
base = <any>{
|
|
|
|
displayParts,
|
|
|
|
documentation: [],
|
|
|
|
kind: 'angular',
|
|
|
|
kindModifiers: 'what does this do?',
|
|
|
|
textSpan: {start: ours.span.start, length: ours.span.end - ours.span.start},
|
|
|
|
};
|
|
|
|
if (tags) {
|
|
|
|
(<any>base).tags = tags;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2017-01-06 23:43:17 -05:00
|
|
|
|
2018-08-05 11:31:27 -04:00
|
|
|
return base;
|
|
|
|
};
|
2017-01-06 23:43:17 -05:00
|
|
|
|
|
|
|
proxy.getSemanticDiagnostics = function(fileName: string) {
|
2017-04-25 15:13:06 -04:00
|
|
|
let result = oldLS.getSemanticDiagnostics(fileName);
|
|
|
|
const base = result || [];
|
2017-01-06 23:43:17 -05:00
|
|
|
tryOperation('get diagnostics', () => {
|
|
|
|
info.project.projectService.logger.info(`Computing Angular semantic diagnostics...`);
|
|
|
|
const ours = ls.getDiagnostics(fileName);
|
|
|
|
if (ours && ours.length) {
|
2018-08-05 11:31:27 -04:00
|
|
|
const file = oldLS.getProgram() !.getSourceFile(fileName);
|
2018-02-08 11:59:25 -05:00
|
|
|
if (file) {
|
|
|
|
base.push.apply(base, ours.map(d => diagnosticToDiagnostic(d, file)));
|
|
|
|
}
|
2017-01-06 23:43:17 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return base;
|
|
|
|
};
|
2016-11-22 12:10:23 -05:00
|
|
|
|
2017-01-06 23:43:17 -05:00
|
|
|
proxy.getDefinitionAtPosition = function(
|
|
|
|
fileName: string, position: number): ts.DefinitionInfo[] {
|
|
|
|
let base = oldLS.getDefinitionAtPosition(fileName, position);
|
|
|
|
if (base && base.length) {
|
|
|
|
return base;
|
2016-11-22 12:10:23 -05:00
|
|
|
}
|
2017-01-06 23:43:17 -05:00
|
|
|
|
2017-04-25 15:13:06 -04:00
|
|
|
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: '',
|
2017-10-26 20:23:30 -04:00
|
|
|
// TODO: remove any and fix type error.
|
|
|
|
kind: 'definition' as any,
|
2017-04-25 15:13:06 -04:00
|
|
|
containerName: loc.fileName,
|
2017-10-26 20:23:30 -04:00
|
|
|
containerKind: 'file' as any,
|
2017-04-25 15:13:06 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return base;
|
|
|
|
}) || [];
|
2017-01-06 23:43:17 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
return proxy;
|
|
|
|
}
|