refactor(language-service): Remove redudant 'TemplateInfo' type (#32250)
The TemplateInfo type is an extension of AstResult, but it is not necessary at all. Instead, improve the current interface for AstResult by removing all optional fileds and include the TemplateSource in AstResult instead. PR Close #32250
This commit is contained in:
parent
6b245a39ee
commit
6d11154652
|
@ -11,26 +11,14 @@ import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, C
|
|||
import {Diagnostic, TemplateSource} from './types';
|
||||
|
||||
export interface AstResult {
|
||||
htmlAst?: HtmlAst[];
|
||||
templateAst?: TemplateAst[];
|
||||
directive?: CompileDirectiveMetadata;
|
||||
directives?: CompileDirectiveSummary[];
|
||||
pipes?: CompilePipeSummary[];
|
||||
parseErrors?: ParseError[];
|
||||
expressionParser?: Parser;
|
||||
errors?: Diagnostic[];
|
||||
}
|
||||
|
||||
export interface TemplateInfo {
|
||||
position?: number;
|
||||
fileName?: string;
|
||||
template: TemplateSource;
|
||||
htmlAst: HtmlAst[];
|
||||
templateAst: TemplateAst[];
|
||||
directive: CompileDirectiveMetadata;
|
||||
directives: CompileDirectiveSummary[];
|
||||
pipes: CompilePipeSummary[];
|
||||
templateAst: TemplateAst[];
|
||||
parseErrors?: ParseError[];
|
||||
expressionParser: Parser;
|
||||
template: TemplateSource;
|
||||
}
|
||||
|
||||
export interface AttrInfo {
|
||||
|
@ -45,3 +33,7 @@ export type SelectorInfo = {
|
|||
selectors: CssSelector[],
|
||||
map: Map<CssSelector, CompileDirectiveSummary>
|
||||
};
|
||||
|
||||
export function isAstResult(result: AstResult | Diagnostic): result is AstResult {
|
||||
return result.hasOwnProperty('templateAst');
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import {AST, AstPath, Attribute, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, CssSelector, Element, ElementAst, ImplicitReceiver, NAMED_ENTITIES, Node as HtmlAst, NullTemplateVisitor, ParseSpan, PropertyRead, SelectorMatcher, TagContentType, Text, findNode, getHtmlTagDefinition, splitNsName} from '@angular/compiler';
|
||||
import {getExpressionScope} from '@angular/compiler-cli/src/language_services';
|
||||
|
||||
import {AttrInfo, TemplateInfo} from './common';
|
||||
import {AstResult, AttrInfo} from './common';
|
||||
import {getExpressionCompletions} from './expressions';
|
||||
import {attributeNames, elementNames, eventNames, propertyNames} from './html_info';
|
||||
import {Completion, Completions, Span, Symbol, SymbolTable, TemplateSource} from './types';
|
||||
|
@ -28,76 +28,75 @@ const hiddenHtmlElements = {
|
|||
link: true,
|
||||
};
|
||||
|
||||
export function getTemplateCompletions(templateInfo: TemplateInfo): Completions|undefined {
|
||||
export function getTemplateCompletions(templateInfo: AstResult, position: number): Completions|
|
||||
undefined {
|
||||
let result: Completions|undefined = undefined;
|
||||
let {htmlAst, templateAst, template} = templateInfo;
|
||||
let {htmlAst, template} = templateInfo;
|
||||
// The templateNode starts at the delimiter character so we add 1 to skip it.
|
||||
if (templateInfo.position != null) {
|
||||
let templatePosition = templateInfo.position - template.span.start;
|
||||
let path = findNode(htmlAst, templatePosition);
|
||||
let mostSpecific = path.tail;
|
||||
if (path.empty || !mostSpecific) {
|
||||
result = elementCompletions(templateInfo, path);
|
||||
} else {
|
||||
let astPosition = templatePosition - mostSpecific.sourceSpan.start.offset;
|
||||
mostSpecific.visit(
|
||||
{
|
||||
visitElement(ast) {
|
||||
let startTagSpan = spanOf(ast.sourceSpan);
|
||||
let tagLen = ast.name.length;
|
||||
if (templatePosition <=
|
||||
startTagSpan.start + tagLen + 1 /* 1 for the opening angle bracked */) {
|
||||
// If we are in the tag then return the element completions.
|
||||
result = elementCompletions(templateInfo, path);
|
||||
} else if (templatePosition < startTagSpan.end) {
|
||||
// We are in the attribute section of the element (but not in an attribute).
|
||||
// Return the attribute completions.
|
||||
result = attributeCompletions(templateInfo, path);
|
||||
}
|
||||
},
|
||||
visitAttribute(ast) {
|
||||
if (!ast.valueSpan || !inSpan(templatePosition, spanOf(ast.valueSpan))) {
|
||||
// We are in the name of an attribute. Show attribute completions.
|
||||
result = attributeCompletions(templateInfo, path);
|
||||
} else if (ast.valueSpan && inSpan(templatePosition, spanOf(ast.valueSpan))) {
|
||||
result = attributeValueCompletions(templateInfo, templatePosition, ast);
|
||||
}
|
||||
},
|
||||
visitText(ast) {
|
||||
// Check if we are in a entity.
|
||||
result = entityCompletions(getSourceText(template, spanOf(ast)), astPosition);
|
||||
if (result) return result;
|
||||
result = interpolationCompletions(templateInfo, templatePosition);
|
||||
if (result) return result;
|
||||
let element = path.first(Element);
|
||||
if (element) {
|
||||
let definition = getHtmlTagDefinition(element.name);
|
||||
if (definition.contentType === TagContentType.PARSABLE_DATA) {
|
||||
result = voidElementAttributeCompletions(templateInfo, path);
|
||||
if (!result) {
|
||||
// If the element can hold content Show element completions.
|
||||
result = elementCompletions(templateInfo, path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If no element container, implies parsable data so show elements.
|
||||
let templatePosition = position - template.span.start;
|
||||
let path = findNode(htmlAst, templatePosition);
|
||||
let mostSpecific = path.tail;
|
||||
if (path.empty || !mostSpecific) {
|
||||
result = elementCompletions(templateInfo, path);
|
||||
} else {
|
||||
let astPosition = templatePosition - mostSpecific.sourceSpan.start.offset;
|
||||
mostSpecific.visit(
|
||||
{
|
||||
visitElement(ast) {
|
||||
let startTagSpan = spanOf(ast.sourceSpan);
|
||||
let tagLen = ast.name.length;
|
||||
if (templatePosition <=
|
||||
startTagSpan.start + tagLen + 1 /* 1 for the opening angle bracket */) {
|
||||
// If we are in the tag then return the element completions.
|
||||
result = elementCompletions(templateInfo, path);
|
||||
} else if (templatePosition < startTagSpan.end) {
|
||||
// We are in the attribute section of the element (but not in an attribute).
|
||||
// Return the attribute completions.
|
||||
result = attributeCompletions(templateInfo, path);
|
||||
}
|
||||
},
|
||||
visitAttribute(ast) {
|
||||
if (!ast.valueSpan || !inSpan(templatePosition, spanOf(ast.valueSpan))) {
|
||||
// We are in the name of an attribute. Show attribute completions.
|
||||
result = attributeCompletions(templateInfo, path);
|
||||
} else if (ast.valueSpan && inSpan(templatePosition, spanOf(ast.valueSpan))) {
|
||||
result = attributeValueCompletions(templateInfo, templatePosition, ast);
|
||||
}
|
||||
},
|
||||
visitText(ast) {
|
||||
// Check if we are in a entity.
|
||||
result = entityCompletions(getSourceText(template, spanOf(ast)), astPosition);
|
||||
if (result) return result;
|
||||
result = interpolationCompletions(templateInfo, templatePosition);
|
||||
if (result) return result;
|
||||
let element = path.first(Element);
|
||||
if (element) {
|
||||
let definition = getHtmlTagDefinition(element.name);
|
||||
if (definition.contentType === TagContentType.PARSABLE_DATA) {
|
||||
result = voidElementAttributeCompletions(templateInfo, path);
|
||||
if (!result) {
|
||||
// If the element can hold content, show element completions.
|
||||
result = elementCompletions(templateInfo, path);
|
||||
}
|
||||
}
|
||||
},
|
||||
visitComment(ast) {},
|
||||
visitExpansion(ast) {},
|
||||
visitExpansionCase(ast) {}
|
||||
} else {
|
||||
// If no element container, implies parsable data so show elements.
|
||||
result = voidElementAttributeCompletions(templateInfo, path);
|
||||
if (!result) {
|
||||
result = elementCompletions(templateInfo, path);
|
||||
}
|
||||
}
|
||||
},
|
||||
null);
|
||||
}
|
||||
visitComment(ast) {},
|
||||
visitExpansion(ast) {},
|
||||
visitExpansionCase(ast) {}
|
||||
},
|
||||
null);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function attributeCompletions(info: TemplateInfo, path: AstPath<HtmlAst>): Completions|undefined {
|
||||
function attributeCompletions(info: AstResult, path: AstPath<HtmlAst>): Completions|undefined {
|
||||
let item = path.tail instanceof Element ? path.tail : path.parentOf(path.tail);
|
||||
if (item instanceof Element) {
|
||||
return attributeCompletionsForElement(info, item.name, item);
|
||||
|
@ -106,7 +105,7 @@ function attributeCompletions(info: TemplateInfo, path: AstPath<HtmlAst>): Compl
|
|||
}
|
||||
|
||||
function attributeCompletionsForElement(
|
||||
info: TemplateInfo, elementName: string, element?: Element): Completions {
|
||||
info: AstResult, elementName: string, element?: Element): Completions {
|
||||
const attributes = getAttributeInfosForElement(info, elementName, element);
|
||||
|
||||
// Map all the attributes to a completion
|
||||
|
@ -118,7 +117,7 @@ function attributeCompletionsForElement(
|
|||
}
|
||||
|
||||
function getAttributeInfosForElement(
|
||||
info: TemplateInfo, elementName: string, element?: Element): AttrInfo[] {
|
||||
info: AstResult, elementName: string, element?: Element): AttrInfo[] {
|
||||
let attributes: AttrInfo[] = [];
|
||||
|
||||
// Add html attributes
|
||||
|
@ -189,8 +188,8 @@ function getAttributeInfosForElement(
|
|||
return attributes;
|
||||
}
|
||||
|
||||
function attributeValueCompletions(
|
||||
info: TemplateInfo, position: number, attr: Attribute): Completions|undefined {
|
||||
function attributeValueCompletions(info: AstResult, position: number, attr: Attribute): Completions|
|
||||
undefined {
|
||||
const path = findTemplateAstAt(info.templateAst, position);
|
||||
const mostSpecific = path.tail;
|
||||
const dinfo = diagnosticInfoFromTemplateInfo(info);
|
||||
|
@ -212,7 +211,7 @@ function attributeValueCompletions(
|
|||
}
|
||||
}
|
||||
|
||||
function elementCompletions(info: TemplateInfo, path: AstPath<HtmlAst>): Completions|undefined {
|
||||
function elementCompletions(info: AstResult, path: AstPath<HtmlAst>): Completions|undefined {
|
||||
let htmlNames = elementNames().filter(name => !(name in hiddenHtmlElements));
|
||||
|
||||
// Collect the elements referenced by the selectors
|
||||
|
@ -244,7 +243,7 @@ function entityCompletions(value: string, position: number): Completions|undefin
|
|||
return result;
|
||||
}
|
||||
|
||||
function interpolationCompletions(info: TemplateInfo, position: number): Completions|undefined {
|
||||
function interpolationCompletions(info: AstResult, position: number): Completions|undefined {
|
||||
// Look for an interpolation in at the position.
|
||||
const templatePath = findTemplateAstAt(info.templateAst, position);
|
||||
const mostSpecific = templatePath.tail;
|
||||
|
@ -263,7 +262,7 @@ function interpolationCompletions(info: TemplateInfo, position: number): Complet
|
|||
// the attributes of an "a" element, not requesting completion in the a text element. This
|
||||
// code checks for this case and returns element completions if it is detected or undefined
|
||||
// if it is not.
|
||||
function voidElementAttributeCompletions(info: TemplateInfo, path: AstPath<HtmlAst>): Completions|
|
||||
function voidElementAttributeCompletions(info: AstResult, path: AstPath<HtmlAst>): Completions|
|
||||
undefined {
|
||||
let tail = path.tail;
|
||||
if (tail instanceof Text) {
|
||||
|
@ -282,7 +281,7 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
|||
result: Completion[]|undefined;
|
||||
|
||||
constructor(
|
||||
private info: TemplateInfo, private position: number, private attr?: Attribute,
|
||||
private info: AstResult, private position: number, private attr?: Attribute,
|
||||
getExpressionScope?: () => SymbolTable) {
|
||||
super();
|
||||
this.getExpressionScope = getExpressionScope || (() => info.template.members);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import * as ts from 'typescript'; // used as value and is provided at runtime
|
||||
import {TemplateInfo} from './common';
|
||||
import {AstResult} from './common';
|
||||
import {locateSymbol} from './locate_symbol';
|
||||
import {Span} from './types';
|
||||
|
||||
|
@ -23,9 +23,15 @@ function ngSpanToTsTextSpan(span: Span): ts.TextSpan {
|
|||
};
|
||||
}
|
||||
|
||||
export function getDefinitionAndBoundSpan(info: TemplateInfo): ts.DefinitionInfoAndBoundSpan|
|
||||
undefined {
|
||||
const symbolInfo = locateSymbol(info);
|
||||
/**
|
||||
* Traverse the template AST and look for the symbol located at `position`, then
|
||||
* return its definition and span of bound text.
|
||||
* @param info
|
||||
* @param position
|
||||
*/
|
||||
export function getDefinitionAndBoundSpan(
|
||||
info: AstResult, position: number): ts.DefinitionInfoAndBoundSpan|undefined {
|
||||
const symbolInfo = locateSymbol(info, position);
|
||||
if (!symbolInfo) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -16,41 +16,28 @@ import {offsetSpan, spanOf} from './utils';
|
|||
|
||||
/**
|
||||
* Return diagnostic information for the parsed AST of the template.
|
||||
* @param template source of the template and class information
|
||||
* @param ast contains HTML and template AST
|
||||
*/
|
||||
export function getTemplateDiagnostics(
|
||||
template: ng.TemplateSource, ast: AstResult): ng.Diagnostic[] {
|
||||
export function getTemplateDiagnostics(ast: AstResult): ng.Diagnostic[] {
|
||||
const results: ng.Diagnostic[] = [];
|
||||
|
||||
if (ast.parseErrors && ast.parseErrors.length) {
|
||||
results.push(...ast.parseErrors.map(e => {
|
||||
const {parseErrors, templateAst, htmlAst, template} = ast;
|
||||
if (parseErrors) {
|
||||
results.push(...parseErrors.map(e => {
|
||||
return {
|
||||
kind: ng.DiagnosticKind.Error,
|
||||
span: offsetSpan(spanOf(e.span), template.span.start),
|
||||
message: e.msg,
|
||||
};
|
||||
}));
|
||||
} else if (ast.templateAst && ast.htmlAst) {
|
||||
const expressionDiagnostics = getTemplateExpressionDiagnostics({
|
||||
templateAst: ast.templateAst,
|
||||
htmlAst: ast.htmlAst,
|
||||
offset: template.span.start,
|
||||
query: template.query,
|
||||
members: template.members,
|
||||
});
|
||||
results.push(...expressionDiagnostics);
|
||||
}
|
||||
if (ast.errors) {
|
||||
results.push(...ast.errors.map(e => {
|
||||
return {
|
||||
kind: e.kind,
|
||||
span: e.span || template.span,
|
||||
message: e.message,
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
const expressionDiagnostics = getTemplateExpressionDiagnostics({
|
||||
templateAst: templateAst,
|
||||
htmlAst: htmlAst,
|
||||
offset: template.span.start,
|
||||
query: template.query,
|
||||
members: template.members,
|
||||
});
|
||||
results.push(...expressionDiagnostics);
|
||||
return results;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,15 +7,21 @@
|
|||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import {TemplateInfo} from './common';
|
||||
import {AstResult} from './common';
|
||||
import {locateSymbol} from './locate_symbol';
|
||||
|
||||
// Reverse mappings of enum would generate strings
|
||||
const SYMBOL_SPACE = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.space];
|
||||
const SYMBOL_PUNC = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.punctuation];
|
||||
|
||||
export function getHover(info: TemplateInfo): ts.QuickInfo|undefined {
|
||||
const symbolInfo = locateSymbol(info);
|
||||
/**
|
||||
* Traverse the template AST and look for the symbol located at `position`, then
|
||||
* return the corresponding quick info.
|
||||
* @param info template AST
|
||||
* @param position location of the symbol
|
||||
*/
|
||||
export function getHover(info: AstResult, position: number): ts.QuickInfo|undefined {
|
||||
const symbolInfo = locateSymbol(info, position);
|
||||
if (!symbolInfo) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
*/
|
||||
|
||||
import * as tss from 'typescript/lib/tsserverlibrary';
|
||||
|
||||
import {isAstResult} from './common';
|
||||
import {getTemplateCompletions, ngCompletionToTsCompletionEntry} from './completions';
|
||||
import {getDefinitionAndBoundSpan} from './definitions';
|
||||
import {getDeclarationDiagnostics, getTemplateDiagnostics, ngDiagnosticToTsDiagnostic, uniqueBySpan} from './diagnostics';
|
||||
|
@ -36,8 +38,12 @@ class LanguageServiceImpl implements LanguageService {
|
|||
const results: Diagnostic[] = [];
|
||||
const templates = this.host.getTemplates(fileName);
|
||||
for (const template of templates) {
|
||||
const ast = this.host.getTemplateAst(template);
|
||||
results.push(...getTemplateDiagnostics(template, ast));
|
||||
const astOrDiagnostic = this.host.getTemplateAst(template);
|
||||
if (isAstResult(astOrDiagnostic)) {
|
||||
results.push(...getTemplateDiagnostics(astOrDiagnostic));
|
||||
} else {
|
||||
results.push(astOrDiagnostic);
|
||||
}
|
||||
}
|
||||
const declarations = this.host.getDeclarations(fileName);
|
||||
if (declarations && declarations.length) {
|
||||
|
@ -52,11 +58,11 @@ class LanguageServiceImpl implements LanguageService {
|
|||
|
||||
getCompletionsAt(fileName: string, position: number): tss.CompletionInfo|undefined {
|
||||
this.host.getAnalyzedModules(); // same role as 'synchronizeHostData'
|
||||
const templateInfo = this.host.getTemplateAstAtPosition(fileName, position);
|
||||
if (!templateInfo) {
|
||||
const ast = this.host.getTemplateAstAtPosition(fileName, position);
|
||||
if (!ast) {
|
||||
return;
|
||||
}
|
||||
const results = getTemplateCompletions(templateInfo);
|
||||
const results = getTemplateCompletions(ast, position);
|
||||
if (!results || !results.length) {
|
||||
return;
|
||||
}
|
||||
|
@ -72,7 +78,7 @@ class LanguageServiceImpl implements LanguageService {
|
|||
this.host.getAnalyzedModules(); // same role as 'synchronizeHostData'
|
||||
const templateInfo = this.host.getTemplateAstAtPosition(fileName, position);
|
||||
if (templateInfo) {
|
||||
return getDefinitionAndBoundSpan(templateInfo);
|
||||
return getDefinitionAndBoundSpan(templateInfo, position);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,7 +86,7 @@ class LanguageServiceImpl implements LanguageService {
|
|||
this.host.getAnalyzedModules(); // same role as 'synchronizeHostData'
|
||||
const templateInfo = this.host.getTemplateAstAtPosition(fileName, position);
|
||||
if (templateInfo) {
|
||||
return getHover(templateInfo);
|
||||
return getHover(templateInfo, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import {AST, Attribute, BoundDirectivePropertyAst, BoundEventAst, ElementAst, TemplateAstPath, findNode, tokenReference} from '@angular/compiler';
|
||||
import {getExpressionScope} from '@angular/compiler-cli/src/language_services';
|
||||
|
||||
import {TemplateInfo} from './common';
|
||||
import {AstResult} from './common';
|
||||
import {getExpressionSymbol} from './expressions';
|
||||
import {Definition, Span, Symbol} from './types';
|
||||
import {diagnosticInfoFromTemplateInfo, findTemplateAstAt, inSpan, offsetSpan, spanOf} from './utils';
|
||||
|
@ -19,15 +19,19 @@ export interface SymbolInfo {
|
|||
span: Span;
|
||||
}
|
||||
|
||||
export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
|
||||
if (!info.position) return undefined;
|
||||
const templatePosition = info.position - info.template.span.start;
|
||||
/**
|
||||
* Traverse the template AST and locate the Symbol at the specified `position`.
|
||||
* @param info Ast and Template Source
|
||||
* @param position location to look for
|
||||
*/
|
||||
export function locateSymbol(info: AstResult, position: number): SymbolInfo|undefined {
|
||||
const templatePosition = position - info.template.span.start;
|
||||
const path = findTemplateAstAt(info.templateAst, templatePosition);
|
||||
if (path.tail) {
|
||||
let symbol: Symbol|undefined = undefined;
|
||||
let span: Span|undefined = undefined;
|
||||
const attributeValueSymbol = (ast: AST, inEvent: boolean = false): boolean => {
|
||||
const attribute = findAttribute(info);
|
||||
const attribute = findAttribute(info, position);
|
||||
if (attribute) {
|
||||
if (inSpan(templatePosition, spanOf(attribute.valueSpan))) {
|
||||
const dinfo = diagnosticInfoFromTemplateInfo(info);
|
||||
|
@ -113,17 +117,14 @@ export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
|
|||
}
|
||||
}
|
||||
|
||||
function findAttribute(info: TemplateInfo): Attribute|undefined {
|
||||
if (info.position) {
|
||||
const templatePosition = info.position - info.template.span.start;
|
||||
const path = findNode(info.htmlAst, templatePosition);
|
||||
return path.first(Attribute);
|
||||
}
|
||||
function findAttribute(info: AstResult, position: number): Attribute|undefined {
|
||||
const templatePosition = position - info.template.span.start;
|
||||
const path = findNode(info.htmlAst, templatePosition);
|
||||
return path.first(Attribute);
|
||||
}
|
||||
|
||||
function findInputBinding(
|
||||
info: TemplateInfo, path: TemplateAstPath, binding: BoundDirectivePropertyAst): Symbol|
|
||||
undefined {
|
||||
info: AstResult, path: TemplateAstPath, binding: BoundDirectivePropertyAst): Symbol|undefined {
|
||||
const element = path.first(ElementAst);
|
||||
if (element) {
|
||||
for (const directive of element.directives) {
|
||||
|
@ -139,8 +140,8 @@ function findInputBinding(
|
|||
}
|
||||
}
|
||||
|
||||
function findOutputBinding(
|
||||
info: TemplateInfo, path: TemplateAstPath, binding: BoundEventAst): Symbol|undefined {
|
||||
function findOutputBinding(info: AstResult, path: TemplateAstPath, binding: BoundEventAst): Symbol|
|
||||
undefined {
|
||||
const element = path.first(ElementAst);
|
||||
if (element) {
|
||||
for (const directive of element.directives) {
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
import {getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from '@angular/compiler-cli';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {isAstResult} from './common';
|
||||
import * as ng from './types';
|
||||
import {TypeScriptServiceHost} from './typescript_host';
|
||||
|
||||
|
@ -67,7 +69,8 @@ abstract class BaseTemplate implements ng.TemplateSource {
|
|||
// TODO: There is circular dependency here between TemplateSource and
|
||||
// TypeScriptHost. Consider refactoring the code to break this cycle.
|
||||
const ast = this.host.getTemplateAst(this);
|
||||
return getPipesTable(sourceFile, program, typeChecker, ast.pipes || []);
|
||||
const pipes = isAstResult(ast) ? ast.pipes : [];
|
||||
return getPipesTable(sourceFile, program, typeChecker, pipes);
|
||||
});
|
||||
}
|
||||
return this.queryCache;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import {CompileDirectiveMetadata, NgAnalyzedModules, StaticSymbol} from '@angular/compiler';
|
||||
import {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from '@angular/compiler-cli/src/language_services';
|
||||
|
||||
import {AstResult, TemplateInfo} from './common';
|
||||
import {AstResult} from './common';
|
||||
|
||||
export {
|
||||
BuiltinType,
|
||||
|
@ -192,12 +192,12 @@ export interface LanguageServiceHost {
|
|||
/**
|
||||
* Return the AST for both HTML and template for the contextFile.
|
||||
*/
|
||||
getTemplateAst(template: TemplateSource): AstResult;
|
||||
getTemplateAst(template: TemplateSource): AstResult|Diagnostic;
|
||||
|
||||
/**
|
||||
* Return the template AST for the node that corresponds to the position.
|
||||
*/
|
||||
getTemplateAstAtPosition(fileName: string, position: number): TemplateInfo|undefined;
|
||||
getTemplateAstAtPosition(fileName: string, position: number): AstResult|undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,17 +6,18 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotSummaryResolver, CompileMetadataResolver, CompileNgModuleMetadata, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, FormattedError, FormattedMessageChain, HtmlParser, I18NHtmlParser, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, Parser, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, analyzeNgModules, createOfflineCompileUrlResolver, isFormattedError} from '@angular/compiler';
|
||||
import {ViewEncapsulation, ɵConsole as Console} from '@angular/core';
|
||||
import {AotSummaryResolver, CompileDirectiveSummary, CompileMetadataResolver, CompileNgModuleMetadata, CompilePipeSummary, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, FormattedError, FormattedMessageChain, HtmlParser, I18NHtmlParser, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, Parser, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, analyzeNgModules, createOfflineCompileUrlResolver, isFormattedError} from '@angular/compiler';
|
||||
import {SchemaMetadata, ViewEncapsulation, ɵConsole as Console} from '@angular/core';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AstResult, TemplateInfo} from './common';
|
||||
import {AstResult, isAstResult} from './common';
|
||||
import {createLanguageService} from './language_service';
|
||||
import {ReflectorHost} from './reflector_host';
|
||||
import {ExternalTemplate, InlineTemplate, getClassDeclFromTemplateNode} from './template';
|
||||
import {Declaration, DeclarationError, Diagnostic, DiagnosticKind, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types';
|
||||
import {findTightestNode, getDirectiveClassLike} from './utils';
|
||||
|
||||
|
||||
/**
|
||||
* Create a `LanguageServiceHost`
|
||||
*/
|
||||
|
@ -385,7 +386,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
|||
* @param fileName TS or HTML file
|
||||
* @param position Position of the template in the TS file, otherwise ignored.
|
||||
*/
|
||||
getTemplateAstAtPosition(fileName: string, position: number): TemplateInfo|undefined {
|
||||
getTemplateAstAtPosition(fileName: string, position: number): AstResult|undefined {
|
||||
let template: TemplateSource|undefined;
|
||||
if (fileName.endsWith('.ts')) {
|
||||
const sourceFile = this.getSourceFile(fileName);
|
||||
|
@ -405,66 +406,94 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
|||
return;
|
||||
}
|
||||
const astResult = this.getTemplateAst(template);
|
||||
if (astResult && astResult.htmlAst && astResult.templateAst && astResult.directive &&
|
||||
astResult.directives && astResult.pipes && astResult.expressionParser) {
|
||||
return {
|
||||
position,
|
||||
fileName,
|
||||
template,
|
||||
htmlAst: astResult.htmlAst,
|
||||
directive: astResult.directive,
|
||||
directives: astResult.directives,
|
||||
pipes: astResult.pipes,
|
||||
templateAst: astResult.templateAst,
|
||||
expressionParser: astResult.expressionParser
|
||||
};
|
||||
if (!isAstResult(astResult)) {
|
||||
return;
|
||||
}
|
||||
return astResult;
|
||||
}
|
||||
|
||||
getTemplateAst(template: TemplateSource): AstResult {
|
||||
/**
|
||||
* Find the NgModule which the directive associated with the `classSymbol`
|
||||
* belongs to, then return its schema and transitive directives and pipes.
|
||||
* @param classSymbol Angular Symbol that defines a directive
|
||||
*/
|
||||
private getModuleMetadataForDirective(classSymbol: StaticSymbol) {
|
||||
const result = {
|
||||
directives: [] as CompileDirectiveSummary[],
|
||||
pipes: [] as CompilePipeSummary[],
|
||||
schemas: [] as SchemaMetadata[],
|
||||
};
|
||||
// First find which NgModule the directive belongs to.
|
||||
const ngModule = this.analyzedModules.ngModuleByPipeOrDirective.get(classSymbol) ||
|
||||
findSuitableDefaultModule(this.analyzedModules);
|
||||
if (!ngModule) {
|
||||
return result;
|
||||
}
|
||||
// Then gather all transitive directives and pipes.
|
||||
const {directives, pipes} = ngModule.transitiveModule;
|
||||
for (const directive of directives) {
|
||||
const data = this.resolver.getNonNormalizedDirectiveMetadata(directive.reference);
|
||||
if (data) {
|
||||
result.directives.push(data.metadata.toSummary());
|
||||
}
|
||||
}
|
||||
for (const pipe of pipes) {
|
||||
const metadata = this.resolver.getOrLoadPipeMetadata(pipe.reference);
|
||||
result.pipes.push(metadata.toSummary());
|
||||
}
|
||||
result.schemas.push(...ngModule.schemas);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the `template` and return its AST if there's no error. Otherwise
|
||||
* return a Diagnostic message.
|
||||
* @param template template to be parsed
|
||||
*/
|
||||
getTemplateAst(template: TemplateSource): AstResult|Diagnostic {
|
||||
const {type: classSymbol, fileName} = template;
|
||||
try {
|
||||
const resolvedMetadata = this.resolver.getNonNormalizedDirectiveMetadata(template.type);
|
||||
const metadata = resolvedMetadata && resolvedMetadata.metadata;
|
||||
if (!metadata) {
|
||||
return {};
|
||||
const data = this.resolver.getNonNormalizedDirectiveMetadata(classSymbol);
|
||||
if (!data) {
|
||||
return {
|
||||
kind: DiagnosticKind.Error,
|
||||
message: `No metadata found for '${classSymbol.name}' in ${fileName}.`,
|
||||
span: template.span,
|
||||
};
|
||||
}
|
||||
const rawHtmlParser = new HtmlParser();
|
||||
const htmlParser = new I18NHtmlParser(rawHtmlParser);
|
||||
const htmlParser = new I18NHtmlParser(new HtmlParser());
|
||||
const expressionParser = new Parser(new Lexer());
|
||||
const config = new CompilerConfig();
|
||||
const parser = new TemplateParser(
|
||||
config, this.reflector, expressionParser, new DomElementSchemaRegistry(), htmlParser,
|
||||
null !, []);
|
||||
const htmlResult = htmlParser.parse(template.source, '', {tokenizeExpansionForms: true});
|
||||
const errors: Diagnostic[]|undefined = undefined;
|
||||
const ngModule = this.analyzedModules.ngModuleByPipeOrDirective.get(template.type) ||
|
||||
// Reported by the the declaration diagnostics.
|
||||
findSuitableDefaultModule(this.analyzedModules);
|
||||
if (!ngModule) {
|
||||
return {};
|
||||
new CompilerConfig(), this.reflector, expressionParser, new DomElementSchemaRegistry(),
|
||||
htmlParser,
|
||||
null !, // console
|
||||
[] // tranforms
|
||||
);
|
||||
const htmlResult = htmlParser.parse(template.source, fileName, {
|
||||
tokenizeExpansionForms: true,
|
||||
});
|
||||
const {directives, pipes, schemas} = this.getModuleMetadataForDirective(classSymbol);
|
||||
const parseResult =
|
||||
parser.tryParseHtml(htmlResult, data.metadata, directives, pipes, schemas);
|
||||
if (!parseResult.templateAst) {
|
||||
return {
|
||||
kind: DiagnosticKind.Error,
|
||||
message: `Failed to parse template for '${classSymbol.name}' in ${fileName}`,
|
||||
span: template.span,
|
||||
};
|
||||
}
|
||||
const directives = ngModule.transitiveModule.directives
|
||||
.map(d => this.resolver.getNonNormalizedDirectiveMetadata(d.reference))
|
||||
.filter(d => d)
|
||||
.map(d => d !.metadata.toSummary());
|
||||
const pipes = ngModule.transitiveModule.pipes.map(
|
||||
p => this.resolver.getOrLoadPipeMetadata(p.reference).toSummary());
|
||||
const schemas = ngModule.schemas;
|
||||
const parseResult = parser.tryParseHtml(htmlResult, metadata, directives, pipes, schemas);
|
||||
return {
|
||||
htmlAst: htmlResult.rootNodes,
|
||||
templateAst: parseResult.templateAst,
|
||||
directive: metadata, directives, pipes,
|
||||
parseErrors: parseResult.errors, expressionParser, errors
|
||||
directive: data.metadata, directives, pipes,
|
||||
parseErrors: parseResult.errors, expressionParser, template,
|
||||
};
|
||||
} catch (e) {
|
||||
const span = e.fileName === template.fileName && template.query.getSpanAt(e.line, e.column) ||
|
||||
template.span;
|
||||
return {
|
||||
errors: [{
|
||||
kind: DiagnosticKind.Error,
|
||||
message: e.message, span,
|
||||
}],
|
||||
kind: DiagnosticKind.Error,
|
||||
message: e.message,
|
||||
span:
|
||||
e.fileName === fileName && template.query.getSpanAt(e.line, e.column) || template.span,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import {AstPath, CompileDirectiveSummary, CompileTypeMetadata, CssSelector, Dire
|
|||
import {DiagnosticTemplateInfo} from '@angular/compiler-cli/src/language_services';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {SelectorInfo, TemplateInfo} from './common';
|
||||
import {AstResult, SelectorInfo} from './common';
|
||||
import {Span} from './types';
|
||||
|
||||
export interface SpanHolder {
|
||||
|
@ -67,7 +67,7 @@ export function hasTemplateReference(type: CompileTypeMetadata): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function getSelectors(info: TemplateInfo): SelectorInfo {
|
||||
export function getSelectors(info: AstResult): SelectorInfo {
|
||||
const map = new Map<CssSelector, CompileDirectiveSummary>();
|
||||
const selectors: CssSelector[] = flatten(info.directives.map(directive => {
|
||||
const selectors: CssSelector[] = CssSelector.parse(directive.selector !);
|
||||
|
@ -113,9 +113,9 @@ export function isTypescriptVersion(low: string, high?: string) {
|
|||
return true;
|
||||
}
|
||||
|
||||
export function diagnosticInfoFromTemplateInfo(info: TemplateInfo): DiagnosticTemplateInfo {
|
||||
export function diagnosticInfoFromTemplateInfo(info: AstResult): DiagnosticTemplateInfo {
|
||||
return {
|
||||
fileName: info.fileName,
|
||||
fileName: info.template.fileName,
|
||||
offset: info.template.span.start,
|
||||
query: info.template.query,
|
||||
members: info.template.members,
|
||||
|
|
Loading…
Reference in New Issue