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
|
|
|
|
*/
|
|
|
|
|
2019-04-25 13:50:19 -04: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 12:10:23 -05:00
|
|
|
|
|
|
|
import {createLanguageService} from './language_service';
|
2019-08-01 16:07:32 -04:00
|
|
|
import {Completion, Diagnostic, DiagnosticMessageChain} from './types';
|
2016-11-22 12:10:23 -05:00
|
|
|
import {TypeScriptServiceHost} from './typescript_host';
|
|
|
|
|
2019-04-25 13:50:19 -04:00
|
|
|
const projectHostMap = new WeakMap<tss.server.Project, TypeScriptServiceHost>();
|
2017-04-25 15:13:06 -04:00
|
|
|
|
2019-04-25 13:50:19 -04:00
|
|
|
export function getExternalFiles(project: tss.server.Project): string[]|undefined {
|
2017-04-25 15:13:06 -04:00
|
|
|
const host = projectHostMap.get(project);
|
|
|
|
if (host) {
|
2019-04-16 19:36:47 -04:00
|
|
|
const externalFiles = host.getTemplateReferences();
|
|
|
|
return externalFiles;
|
2017-04-25 15:13:06 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-31 13:55:45 -04:00
|
|
|
function completionToEntry(c: Completion): tss.CompletionEntry {
|
2019-01-14 20:13:06 -05:00
|
|
|
return {
|
|
|
|
// TODO: remove any and fix type error.
|
|
|
|
kind: c.kind as any,
|
|
|
|
name: c.name,
|
|
|
|
sortText: c.sort,
|
|
|
|
kindModifiers: ''
|
|
|
|
};
|
|
|
|
}
|
2017-04-25 15:13:06 -04:00
|
|
|
|
2019-01-14 20:13:06 -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
|
|
|
|
};
|
|
|
|
}
|
2016-11-22 12:10:23 -05:00
|
|
|
|
2019-01-14 20:13:06 -05:00
|
|
|
function diagnosticMessageToDiagnosticMessageText(message: string | DiagnosticMessageChain): string|
|
2019-07-31 13:55:45 -04:00
|
|
|
tss.DiagnosticMessageChain {
|
2019-01-14 20:13:06 -05:00
|
|
|
if (typeof message === 'string') {
|
|
|
|
return message;
|
2017-11-14 20:49:47 -05:00
|
|
|
}
|
2019-01-14 20:13:06 -05:00
|
|
|
return diagnosticChainToDiagnosticChain(message);
|
|
|
|
}
|
2017-11-14 20:49:47 -05:00
|
|
|
|
2019-07-31 13:55:45 -04:00
|
|
|
function diagnosticToDiagnostic(d: Diagnostic, file: tss.SourceFile | undefined): tss.Diagnostic {
|
|
|
|
return {
|
2019-01-14 20:13:06 -05:00
|
|
|
file,
|
|
|
|
start: d.span.start,
|
|
|
|
length: d.span.end - d.span.start,
|
|
|
|
messageText: diagnosticMessageToDiagnosticMessageText(d.message),
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
code: 0,
|
|
|
|
source: 'ng'
|
|
|
|
};
|
|
|
|
}
|
2017-11-14 20:49:47 -05:00
|
|
|
|
2019-07-31 13:55:45 -04:00
|
|
|
export function create(info: tss.server.PluginCreateInfo): tss.LanguageService {
|
|
|
|
const {project, languageService: tsLS, languageServiceHost: tsLSHost, config} = info;
|
|
|
|
// This plugin could operate under two different modes:
|
|
|
|
// 1. TS + Angular
|
|
|
|
// Plugin augments TS language service to provide additional Angular
|
|
|
|
// information. This only works with inline templates and is meant to be
|
|
|
|
// used as a local plugin (configured via tsconfig.json)
|
|
|
|
// 2. Angular only
|
|
|
|
// Plugin only provides information on Angular templates, no TS info at all.
|
|
|
|
// This effectively disables native TS features and is meant for internal
|
|
|
|
// use only.
|
|
|
|
const angularOnly = config ? config.angularOnly === true : false;
|
|
|
|
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
|
|
|
const ngLS = createLanguageService(ngLSHost);
|
|
|
|
projectHostMap.set(project, ngLSHost);
|
2017-01-06 23:43:17 -05:00
|
|
|
|
2019-08-01 16:07:32 -04:00
|
|
|
function getCompletionsAtPosition(
|
|
|
|
fileName: string, position: number,
|
|
|
|
options: tss.GetCompletionsAtPositionOptions | undefined) {
|
2019-07-31 13:55:45 -04:00
|
|
|
if (!angularOnly) {
|
|
|
|
const results = tsLS.getCompletionsAtPosition(fileName, position, options);
|
|
|
|
if (results && results.entries.length) {
|
|
|
|
// If TS could answer the query, then return results immediately.
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const results = ngLS.getCompletionsAt(fileName, position);
|
|
|
|
if (!results || !results.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return {
|
2017-04-25 15:13:06 -04:00
|
|
|
isGlobalCompletion: false,
|
|
|
|
isMemberCompletion: false,
|
|
|
|
isNewIdentifierLocation: false,
|
2019-07-31 13:55:45 -04:00
|
|
|
entries: results.map(completionToEntry),
|
2017-04-25 15:13:06 -04:00
|
|
|
};
|
2019-08-01 16:07:32 -04:00
|
|
|
}
|
2017-01-06 23:43:17 -05:00
|
|
|
|
2019-08-01 16:07:32 -04:00
|
|
|
function getQuickInfoAtPosition(fileName: string, position: number): tss.QuickInfo|undefined {
|
|
|
|
if (!angularOnly) {
|
|
|
|
const result = tsLS.getQuickInfoAtPosition(fileName, position);
|
|
|
|
if (result) {
|
|
|
|
// If TS could answer the query, then return results immediately.
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ngLS.getHoverAt(fileName, position);
|
|
|
|
}
|
2017-01-06 23:43:17 -05:00
|
|
|
|
2019-08-01 16:07:32 -04:00
|
|
|
function getSemanticDiagnostics(fileName: string): tss.Diagnostic[] {
|
2019-07-31 13:55:45 -04:00
|
|
|
const results: tss.Diagnostic[] = [];
|
|
|
|
if (!angularOnly) {
|
|
|
|
const tsResults = tsLS.getSemanticDiagnostics(fileName);
|
|
|
|
results.push(...tsResults);
|
|
|
|
}
|
|
|
|
// For semantic diagnostics we need to combine both TS + Angular results
|
|
|
|
const ngResults = ngLS.getDiagnostics(fileName);
|
|
|
|
if (!ngResults.length) {
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
const sourceFile = fileName.endsWith('.ts') ? ngLSHost.getSourceFile(fileName) : undefined;
|
|
|
|
results.push(...ngResults.map(d => diagnosticToDiagnostic(d, sourceFile)));
|
|
|
|
return results;
|
2019-08-01 16:07:32 -04:00
|
|
|
}
|
2016-11-22 12:10:23 -05:00
|
|
|
|
2019-08-01 16:07:32 -04:00
|
|
|
function getDefinitionAtPosition(
|
|
|
|
fileName: string, position: number): ReadonlyArray<tss.DefinitionInfo>|undefined {
|
|
|
|
if (!angularOnly) {
|
|
|
|
const results = tsLS.getDefinitionAtPosition(fileName, position);
|
|
|
|
if (results) {
|
|
|
|
// If TS could answer the query, then return results immediately.
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const result = ngLS.getDefinitionAt(fileName, position);
|
|
|
|
if (!result || !result.definitions || !result.definitions.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return result.definitions;
|
|
|
|
}
|
2017-01-06 23:43:17 -05:00
|
|
|
|
2019-08-01 16:07:32 -04:00
|
|
|
function getDefinitionAndBoundSpan(
|
|
|
|
fileName: string, position: number): tss.DefinitionInfoAndBoundSpan|undefined {
|
|
|
|
if (!angularOnly) {
|
|
|
|
const result = tsLS.getDefinitionAndBoundSpan(fileName, position);
|
|
|
|
if (result) {
|
|
|
|
// If TS could answer the query, then return results immediately.
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ngLS.getDefinitionAt(fileName, position);
|
|
|
|
}
|
2017-01-06 23:43:17 -05:00
|
|
|
|
2019-08-01 16:07:32 -04:00
|
|
|
const proxy: tss.LanguageService = Object.assign(
|
|
|
|
// First clone the original TS language service
|
|
|
|
{}, tsLS,
|
|
|
|
// Then override the methods supported by Angular language service
|
|
|
|
{
|
|
|
|
getCompletionsAtPosition, getQuickInfoAtPosition, getSemanticDiagnostics,
|
|
|
|
getDefinitionAtPosition, getDefinitionAndBoundSpan,
|
|
|
|
});
|
2017-01-06 23:43:17 -05:00
|
|
|
return proxy;
|
|
|
|
}
|