refactor(language-service): Construct proper template AST from HTML ast (#34764)
The Template AST that corresponds to a given HTML AST is not always complete, and often has to be reconstructed. This commit refactors the code to make it easier to adapt to multiple cases. PR Close #34764
This commit is contained in:
parent
23cbfa791c
commit
84c659e246
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AST, AstPath, AttrAst, Attribute, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, Element, ElementAst, NAMED_ENTITIES, Node as HtmlAst, NullTemplateVisitor, ReferenceAst, TagContentType, TemplateBinding, Text, getHtmlTagDefinition} from '@angular/compiler';
|
||||
import {AST, AstPath, AttrAst, Attribute, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, Element, ElementAst, HtmlAstPath, NAMED_ENTITIES, Node as HtmlAst, NullTemplateVisitor, ReferenceAst, TagContentType, TemplateBinding, Text, getHtmlTagDefinition} from '@angular/compiler';
|
||||
import {$$, $_, isAsciiLetter, isDigit} from '@angular/compiler/src/chars';
|
||||
|
||||
import {AstResult} from './common';
|
||||
|
@ -169,19 +169,15 @@ export function getTemplateCompletions(
|
|||
result = attributeCompletionsForElement(templateInfo, ast.name);
|
||||
}
|
||||
},
|
||||
visitAttribute(ast) {
|
||||
const bindParts = ast.name.match(BIND_NAME_REGEXP);
|
||||
const isReference = bindParts && bindParts[ATTR.KW_REF_IDX] !== undefined;
|
||||
if (!isReference &&
|
||||
(!ast.valueSpan || !inSpan(templatePosition, spanOf(ast.valueSpan)))) {
|
||||
// We are in the name of an attribute. Show attribute completions.
|
||||
visitAttribute(ast: Attribute) {
|
||||
// An attribute consists of two parts, LHS="RHS".
|
||||
// Determine if completions are requested for LHS or RHS
|
||||
if (ast.valueSpan && inSpan(templatePosition, spanOf(ast.valueSpan))) {
|
||||
// RHS completion
|
||||
result = attributeValueCompletions(templateInfo, path);
|
||||
} else {
|
||||
// LHS completion
|
||||
result = attributeCompletions(templateInfo, path);
|
||||
} else if (ast.valueSpan && inSpan(templatePosition, spanOf(ast.valueSpan))) {
|
||||
if (isReference) {
|
||||
result = referenceAttributeValueCompletions(templateInfo, templatePosition, ast);
|
||||
} else {
|
||||
result = attributeValueCompletions(templateInfo, templatePosition, ast);
|
||||
}
|
||||
}
|
||||
},
|
||||
visitText(ast) {
|
||||
|
@ -300,44 +296,52 @@ function attributeCompletionsForElement(
|
|||
return results;
|
||||
}
|
||||
|
||||
function attributeValueCompletions(
|
||||
info: AstResult, position: number, attr: Attribute): ng.CompletionEntry[] {
|
||||
const path = findTemplateAstAt(info.templateAst, position);
|
||||
if (!path.tail) {
|
||||
return [];
|
||||
/**
|
||||
* Provide completions to the RHS of an attribute, which is of the form
|
||||
* LHS="RHS". The template path is computed from the specified `info` whereas
|
||||
* the context is determined from the specified `htmlPath`.
|
||||
* @param info Object that contains the template AST
|
||||
* @param htmlPath Path to the HTML node
|
||||
*/
|
||||
function attributeValueCompletions(info: AstResult, htmlPath: HtmlAstPath): ng.CompletionEntry[] {
|
||||
// Find the corresponding Template AST path.
|
||||
const templatePath = findTemplateAstAt(info.templateAst, htmlPath.position);
|
||||
const visitor = new ExpressionVisitor(info, htmlPath.position, () => {
|
||||
const dinfo = diagnosticInfoFromTemplateInfo(info);
|
||||
return getExpressionScope(dinfo, templatePath, false);
|
||||
});
|
||||
if (templatePath.tail instanceof AttrAst ||
|
||||
templatePath.tail instanceof BoundElementPropertyAst ||
|
||||
templatePath.tail instanceof BoundEventAst) {
|
||||
templatePath.tail.visit(visitor, null);
|
||||
return visitor.results;
|
||||
}
|
||||
// HtmlAst contains the `Attribute` node, however a corresponding attribute AST
|
||||
// node may be missing from the TemplateAst if the compiler fails to parse it fully. In this case,
|
||||
// manually append an `AttrAst` node to the path.
|
||||
if (!(path.tail instanceof AttrAst) && !(path.tail instanceof BoundElementPropertyAst) &&
|
||||
!(path.tail instanceof BoundEventAst)) {
|
||||
// The sourceSpan of an AttrAst is the valueSpan of the HTML Attribute.
|
||||
path.push(new AttrAst(attr.name, attr.value, attr.valueSpan !));
|
||||
// In order to provide accurate attribute value completion, we need to know
|
||||
// what the LHS is, and construct the proper AST if it is missing.
|
||||
const htmlAttr = htmlPath.tail as Attribute;
|
||||
const bindParts = htmlAttr.name.match(BIND_NAME_REGEXP);
|
||||
if (bindParts && bindParts[ATTR.KW_REF_IDX] !== undefined) {
|
||||
let refAst: ReferenceAst|undefined;
|
||||
let elemAst: ElementAst|undefined;
|
||||
if (templatePath.tail instanceof ReferenceAst) {
|
||||
refAst = templatePath.tail;
|
||||
const parent = templatePath.parentOf(refAst);
|
||||
if (parent instanceof ElementAst) {
|
||||
elemAst = parent;
|
||||
}
|
||||
} else if (templatePath.tail instanceof ElementAst) {
|
||||
refAst = new ReferenceAst(htmlAttr.name, null !, htmlAttr.value, htmlAttr.valueSpan !);
|
||||
elemAst = templatePath.tail;
|
||||
}
|
||||
if (refAst && elemAst) {
|
||||
refAst.visit(visitor, elemAst);
|
||||
}
|
||||
} else {
|
||||
// HtmlAst contains the `Attribute` node, however the corresponding `AttrAst`
|
||||
// node is missing from the TemplateAst.
|
||||
const attrAst = new AttrAst(htmlAttr.name, htmlAttr.value, htmlAttr.valueSpan !);
|
||||
attrAst.visit(visitor, null);
|
||||
}
|
||||
const dinfo = diagnosticInfoFromTemplateInfo(info);
|
||||
const visitor =
|
||||
new ExpressionVisitor(info, position, () => getExpressionScope(dinfo, path, false));
|
||||
path.tail.visit(visitor, null);
|
||||
return visitor.results;
|
||||
}
|
||||
|
||||
function referenceAttributeValueCompletions(
|
||||
info: AstResult, position: number, attr: Attribute): ng.CompletionEntry[] {
|
||||
const path = findTemplateAstAt(info.templateAst, position);
|
||||
if (!path.tail) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// When the template parser does not find a directive with matching "exportAs",
|
||||
// the ReferenceAst will be ignored.
|
||||
if (!(path.tail instanceof ReferenceAst)) {
|
||||
// The sourceSpan of an ReferenceAst is the valueSpan of the HTML Attribute.
|
||||
path.push(new ReferenceAst(attr.name, null !, attr.value, attr.valueSpan !));
|
||||
}
|
||||
const dinfo = diagnosticInfoFromTemplateInfo(info);
|
||||
const visitor =
|
||||
new ExpressionVisitor(info, position, () => getExpressionScope(dinfo, path, false));
|
||||
path.tail.visit(visitor, path.parentOf(path.tail));
|
||||
return visitor.results;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue