2016-11-22 09:10:23 -08: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
|
|
|
|
*/
|
|
|
|
|
2019-04-25 10:50:19 -07:00
|
|
|
import * as ts from 'typescript'; // used as value, passed in by tsserver at runtime
|
|
|
|
import * as tss from 'typescript/lib/tsserverlibrary'; // used as type only
|
2016-11-22 09:10:23 -08:00
|
|
|
|
|
|
|
import {createLanguageService} from './language_service';
|
2019-04-25 11:11:33 -07:00
|
|
|
import {Completion, Diagnostic, DiagnosticMessageChain, Location} from './types';
|
2016-11-22 09:10:23 -08:00
|
|
|
import {TypeScriptServiceHost} from './typescript_host';
|
|
|
|
|
2019-04-25 10:50:19 -07:00
|
|
|
const projectHostMap = new WeakMap<tss.server.Project, TypeScriptServiceHost>();
|
2017-04-25 12:13:06 -07:00
|
|
|
|
2019-04-25 10:50:19 -07:00
|
|
|
export function getExternalFiles(project: tss.server.Project): string[]|undefined {
|
2017-04-25 12:13:06 -07:00
|
|
|
const host = projectHostMap.get(project);
|
|
|
|
if (host) {
|
2019-04-16 16:36:47 -07:00
|
|
|
const externalFiles = host.getTemplateReferences();
|
|
|
|
return externalFiles;
|
2017-04-25 12:13:06 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-14 17:13:06 -08:00
|
|
|
function completionToEntry(c: Completion): ts.CompletionEntry {
|
|
|
|
return {
|
|
|
|
// TODO: remove any and fix type error.
|
|
|
|
kind: c.kind as any,
|
|
|
|
name: c.name,
|
|
|
|
sortText: c.sort,
|
|
|
|
kindModifiers: ''
|
|
|
|
};
|
|
|
|
}
|
2017-04-25 12:13:06 -07:00
|
|
|
|
2019-01-14 17:13:06 -08:00
|
|
|
function diagnosticChainToDiagnosticChain(chain: DiagnosticMessageChain):
|
|
|
|
ts.DiagnosticMessageChain {
|
|
|
|
return {
|
|
|
|
messageText: chain.message,
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
code: 0,
|
|
|
|
next: chain.next ? diagnosticChainToDiagnosticChain(chain.next) : undefined
|
|
|
|
};
|
|
|
|
}
|
2016-11-22 09:10:23 -08:00
|
|
|
|
2019-01-14 17:13:06 -08:00
|
|
|
function diagnosticMessageToDiagnosticMessageText(message: string | DiagnosticMessageChain): string|
|
|
|
|
ts.DiagnosticMessageChain {
|
|
|
|
if (typeof message === 'string') {
|
|
|
|
return message;
|
2017-11-14 17:49:47 -08:00
|
|
|
}
|
2019-01-14 17:13:06 -08:00
|
|
|
return diagnosticChainToDiagnosticChain(message);
|
|
|
|
}
|
2017-11-14 17:49:47 -08:00
|
|
|
|
2019-01-14 17:13:06 -08:00
|
|
|
function diagnosticToDiagnostic(d: Diagnostic, file: ts.SourceFile): ts.Diagnostic {
|
|
|
|
const result = {
|
|
|
|
file,
|
|
|
|
start: d.span.start,
|
|
|
|
length: d.span.end - d.span.start,
|
|
|
|
messageText: diagnosticMessageToDiagnosticMessageText(d.message),
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
code: 0,
|
|
|
|
source: 'ng'
|
|
|
|
};
|
|
|
|
return result;
|
|
|
|
}
|
2017-11-14 17:49:47 -08:00
|
|
|
|
2019-04-25 10:50:19 -07:00
|
|
|
export function create(info: tss.server.PluginCreateInfo): ts.LanguageService {
|
2019-01-14 17:13:06 -08:00
|
|
|
const oldLS: ts.LanguageService = info.languageService;
|
|
|
|
const proxy: ts.LanguageService = Object.assign({}, oldLS);
|
|
|
|
const logger = info.project.projectService.logger;
|
2016-11-22 09:10:23 -08:00
|
|
|
|
2017-04-25 12:13:06 -07:00
|
|
|
function tryOperation<T>(attempting: string, callback: () => T): T|null {
|
2017-01-06 20:43:17 -08:00
|
|
|
try {
|
2017-04-25 12:13:06 -07:00
|
|
|
return callback();
|
2017-01-06 20:43:17 -08:00
|
|
|
} catch (e) {
|
2019-01-14 17:13:06 -08:00
|
|
|
logger.info(`Failed to ${attempting}: ${e.toString()}`);
|
|
|
|
logger.info(`Stack trace: ${e.stack}`);
|
2017-04-25 12:13:06 -07:00
|
|
|
return null;
|
2017-01-06 20:43:17 -08:00
|
|
|
}
|
2016-11-22 09:10:23 -08:00
|
|
|
}
|
|
|
|
|
2019-01-14 17:13:06 -08:00
|
|
|
const serviceHost = new TypeScriptServiceHost(info.languageServiceHost, oldLS);
|
2019-04-16 16:36:47 -07:00
|
|
|
const ls = createLanguageService(serviceHost);
|
2017-04-25 12:13:06 -07:00
|
|
|
projectHostMap.set(info.project, serviceHost);
|
2017-01-06 20:43:17 -08:00
|
|
|
|
2017-12-22 09:36:47 -08:00
|
|
|
proxy.getCompletionsAtPosition = function(
|
|
|
|
fileName: string, position: number, options: ts.GetCompletionsAtPositionOptions|undefined) {
|
|
|
|
let base = oldLS.getCompletionsAtPosition(fileName, position, options) || {
|
2017-04-25 12:13:06 -07:00
|
|
|
isGlobalCompletion: false,
|
|
|
|
isMemberCompletion: false,
|
|
|
|
isNewIdentifierLocation: false,
|
|
|
|
entries: []
|
|
|
|
};
|
2017-01-06 20:43:17 -08:00
|
|
|
tryOperation('get completions', () => {
|
|
|
|
const results = ls.getCompletionsAt(fileName, position);
|
|
|
|
if (results && results.length) {
|
|
|
|
if (base === undefined) {
|
2017-01-24 09:05:34 -08:00
|
|
|
base = {
|
|
|
|
isGlobalCompletion: false,
|
|
|
|
isMemberCompletion: false,
|
|
|
|
isNewIdentifierLocation: false,
|
|
|
|
entries: []
|
|
|
|
};
|
2017-01-06 20:43:17 -08:00
|
|
|
}
|
|
|
|
for (const entry of results) {
|
|
|
|
base.entries.push(completionToEntry(entry));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return base;
|
|
|
|
};
|
|
|
|
|
2018-08-05 17:31:27 +02:00
|
|
|
proxy.getQuickInfoAtPosition = function(fileName: string, position: number): ts.QuickInfo |
|
|
|
|
undefined {
|
2019-06-11 16:39:57 -07:00
|
|
|
const base = oldLS.getQuickInfoAtPosition(fileName, position);
|
|
|
|
const ours = ls.getHoverAt(fileName, position);
|
|
|
|
if (!ours) {
|
|
|
|
return base;
|
|
|
|
}
|
|
|
|
const result: ts.QuickInfo = {
|
|
|
|
kind: ts.ScriptElementKind.unknown,
|
|
|
|
kindModifiers: ts.ScriptElementKindModifier.none,
|
|
|
|
textSpan: {
|
|
|
|
start: ours.span.start,
|
|
|
|
length: ours.span.end - ours.span.start,
|
|
|
|
},
|
|
|
|
displayParts: ours.text.map(part => {
|
|
|
|
return {
|
|
|
|
text: part.text,
|
|
|
|
kind: part.language || 'angular',
|
2018-08-05 17:31:27 +02:00
|
|
|
};
|
2019-06-11 16:39:57 -07:00
|
|
|
}),
|
|
|
|
documentation: [],
|
|
|
|
};
|
|
|
|
if (base && base.tags) {
|
|
|
|
result.tags = base.tags;
|
|
|
|
}
|
|
|
|
return result;
|
2018-08-05 17:31:27 +02:00
|
|
|
};
|
2017-01-06 20:43:17 -08:00
|
|
|
|
|
|
|
proxy.getSemanticDiagnostics = function(fileName: string) {
|
2017-04-25 12:13:06 -07:00
|
|
|
let result = oldLS.getSemanticDiagnostics(fileName);
|
|
|
|
const base = result || [];
|
2017-01-06 20:43:17 -08:00
|
|
|
tryOperation('get diagnostics', () => {
|
2019-01-14 17:13:06 -08:00
|
|
|
logger.info(`Computing Angular semantic diagnostics...`);
|
2017-01-06 20:43:17 -08:00
|
|
|
const ours = ls.getDiagnostics(fileName);
|
|
|
|
if (ours && ours.length) {
|
2018-08-05 17:31:27 +02:00
|
|
|
const file = oldLS.getProgram() !.getSourceFile(fileName);
|
2018-02-08 08:59:25 -08:00
|
|
|
if (file) {
|
|
|
|
base.push.apply(base, ours.map(d => diagnosticToDiagnostic(d, file)));
|
|
|
|
}
|
2017-01-06 20:43:17 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return base;
|
|
|
|
};
|
2016-11-22 09:10:23 -08:00
|
|
|
|
2019-04-25 11:11:33 -07:00
|
|
|
proxy.getDefinitionAtPosition = function(fileName: string, position: number):
|
|
|
|
ReadonlyArray<ts.DefinitionInfo>|
|
|
|
|
undefined {
|
|
|
|
const base = oldLS.getDefinitionAtPosition(fileName, position);
|
|
|
|
if (base && base.length) {
|
|
|
|
return base;
|
|
|
|
}
|
|
|
|
const ours = ls.getDefinitionAt(fileName, position);
|
|
|
|
if (ours && ours.length) {
|
|
|
|
return ours.map((loc: Location) => {
|
|
|
|
return {
|
|
|
|
fileName: loc.fileName,
|
|
|
|
textSpan: {
|
|
|
|
start: loc.span.start,
|
|
|
|
length: loc.span.end - loc.span.start,
|
|
|
|
},
|
|
|
|
name: '',
|
|
|
|
kind: ts.ScriptElementKind.unknown,
|
|
|
|
containerName: loc.fileName,
|
|
|
|
containerKind: ts.ScriptElementKind.unknown,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
2017-01-06 20:43:17 -08:00
|
|
|
|
2019-04-25 11:11:33 -07:00
|
|
|
proxy.getDefinitionAndBoundSpan = function(fileName: string, position: number):
|
|
|
|
ts.DefinitionInfoAndBoundSpan |
|
|
|
|
undefined {
|
|
|
|
const base = oldLS.getDefinitionAndBoundSpan(fileName, position);
|
|
|
|
if (base && base.definitions && base.definitions.length) {
|
|
|
|
return base;
|
|
|
|
}
|
|
|
|
const ours = ls.getDefinitionAt(fileName, position);
|
|
|
|
if (ours && ours.length) {
|
|
|
|
return {
|
|
|
|
definitions: ours.map((loc: Location) => {
|
|
|
|
return {
|
|
|
|
fileName: loc.fileName,
|
|
|
|
textSpan: {
|
|
|
|
start: loc.span.start,
|
|
|
|
length: loc.span.end - loc.span.start,
|
|
|
|
},
|
|
|
|
name: '',
|
|
|
|
kind: ts.ScriptElementKind.unknown,
|
|
|
|
containerName: loc.fileName,
|
|
|
|
containerKind: ts.ScriptElementKind.unknown,
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
textSpan: {
|
|
|
|
start: ours[0].span.start,
|
|
|
|
length: ours[0].span.end - ours[0].span.start,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
2017-01-06 20:43:17 -08:00
|
|
|
|
|
|
|
return proxy;
|
|
|
|
}
|