Previously, we had tested that expressions parsed in a Render3 AST had correctly-defined absolute spans (spans relative to the entire template, not the local expression). Sometimes we use Template ASTs rather than Render3 ASTs, and it's desirable to test for correct expression spans in the template parser as well. Adding these tests resolved one bug, similar to the one fixed in fd4fed14d8935866fe16d30648d115f3ebb5fd43, where expressions in the value of a template attribute were not given an absolute span corresponding to the start of the attribute name rather than the start of the attribute value. The diff on this commit is large, partially because it involves some structural changes of the template parser testing layout. In particular, the following is done: 1. Move `createMeta*`-like functions from `template_parser_spec.ts` to be exported from a new test utility file. 2. Create an `ExpressionSourceHumanizer`, similar to the one created in b04488d692ca11c83444da56f51d53c3ac868243, to allow convenient testing of expressions' locations. 3. Create `template_parser_absolute_span_spec.ts`, testing the spans of expressions parsed by the template parser. This is very similar to the `r3_ast_absolute_span_spec`. PR Close #33253
151 lines
4.7 KiB
TypeScript
151 lines
4.7 KiB
TypeScript
/**
|
|
* @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 {AbsoluteSourceSpan} from '@angular/compiler';
|
|
import * as e from '../../../src/expression_parser/ast';
|
|
import * as t from '../../../src/template_parser/template_ast';
|
|
import {unparse} from '../../expression_parser/utils/unparser';
|
|
|
|
type HumanizedExpressionSource = [string, AbsoluteSourceSpan];
|
|
class ExpressionSourceHumanizer extends e.RecursiveAstVisitor implements t.TemplateAstVisitor {
|
|
result: HumanizedExpressionSource[] = [];
|
|
|
|
private recordAst(ast: e.AST) { this.result.push([unparse(ast), ast.sourceSpan]); }
|
|
|
|
visitASTWithSource(ast: e.ASTWithSource) {
|
|
this.recordAst(ast);
|
|
this.visitAll([ast.ast], null);
|
|
}
|
|
visitBinary(ast: e.Binary) {
|
|
this.recordAst(ast);
|
|
super.visitBinary(ast, null);
|
|
}
|
|
visitChain(ast: e.Chain) {
|
|
this.recordAst(ast);
|
|
super.visitChain(ast, null);
|
|
}
|
|
visitConditional(ast: e.Conditional) {
|
|
this.recordAst(ast);
|
|
super.visitConditional(ast, null);
|
|
}
|
|
visitFunctionCall(ast: e.FunctionCall) {
|
|
this.recordAst(ast);
|
|
super.visitFunctionCall(ast, null);
|
|
}
|
|
visitImplicitReceiver(ast: e.ImplicitReceiver) {
|
|
this.recordAst(ast);
|
|
super.visitImplicitReceiver(ast, null);
|
|
}
|
|
visitInterpolation(ast: e.Interpolation) {
|
|
this.recordAst(ast);
|
|
super.visitInterpolation(ast, null);
|
|
}
|
|
visitKeyedRead(ast: e.KeyedRead) {
|
|
this.recordAst(ast);
|
|
super.visitKeyedRead(ast, null);
|
|
}
|
|
visitKeyedWrite(ast: e.KeyedWrite) {
|
|
this.recordAst(ast);
|
|
super.visitKeyedWrite(ast, null);
|
|
}
|
|
visitLiteralPrimitive(ast: e.LiteralPrimitive) {
|
|
this.recordAst(ast);
|
|
super.visitLiteralPrimitive(ast, null);
|
|
}
|
|
visitLiteralArray(ast: e.LiteralArray) {
|
|
this.recordAst(ast);
|
|
super.visitLiteralArray(ast, null);
|
|
}
|
|
visitLiteralMap(ast: e.LiteralMap) {
|
|
this.recordAst(ast);
|
|
super.visitLiteralMap(ast, null);
|
|
}
|
|
visitMethodCall(ast: e.MethodCall) {
|
|
this.recordAst(ast);
|
|
super.visitMethodCall(ast, null);
|
|
}
|
|
visitNonNullAssert(ast: e.NonNullAssert) {
|
|
this.recordAst(ast);
|
|
super.visitNonNullAssert(ast, null);
|
|
}
|
|
visitPipe(ast: e.BindingPipe) {
|
|
this.recordAst(ast);
|
|
super.visitPipe(ast, null);
|
|
}
|
|
visitPrefixNot(ast: e.PrefixNot) {
|
|
this.recordAst(ast);
|
|
super.visitPrefixNot(ast, null);
|
|
}
|
|
visitPropertyRead(ast: e.PropertyRead) {
|
|
this.recordAst(ast);
|
|
super.visitPropertyRead(ast, null);
|
|
}
|
|
visitPropertyWrite(ast: e.PropertyWrite) {
|
|
this.recordAst(ast);
|
|
super.visitPropertyWrite(ast, null);
|
|
}
|
|
visitSafeMethodCall(ast: e.SafeMethodCall) {
|
|
this.recordAst(ast);
|
|
super.visitSafeMethodCall(ast, null);
|
|
}
|
|
visitSafePropertyRead(ast: e.SafePropertyRead) {
|
|
this.recordAst(ast);
|
|
super.visitSafePropertyRead(ast, null);
|
|
}
|
|
visitQuote(ast: e.Quote) {
|
|
this.recordAst(ast);
|
|
super.visitQuote(ast, null);
|
|
}
|
|
|
|
visitNgContent(ast: t.NgContentAst) {}
|
|
visitEmbeddedTemplate(ast: t.EmbeddedTemplateAst) {
|
|
t.templateVisitAll(this, ast.attrs);
|
|
t.templateVisitAll(this, ast.children);
|
|
t.templateVisitAll(this, ast.directives);
|
|
t.templateVisitAll(this, ast.outputs);
|
|
t.templateVisitAll(this, ast.providers);
|
|
t.templateVisitAll(this, ast.references);
|
|
t.templateVisitAll(this, ast.variables);
|
|
}
|
|
visitElement(ast: t.ElementAst) {
|
|
t.templateVisitAll(this, ast.attrs);
|
|
t.templateVisitAll(this, ast.children);
|
|
t.templateVisitAll(this, ast.directives);
|
|
t.templateVisitAll(this, ast.inputs);
|
|
t.templateVisitAll(this, ast.outputs);
|
|
t.templateVisitAll(this, ast.providers);
|
|
t.templateVisitAll(this, ast.references);
|
|
}
|
|
visitReference(ast: t.ReferenceAst) {}
|
|
visitVariable(ast: t.VariableAst) {}
|
|
visitEvent(ast: t.BoundEventAst) { ast.handler.visit(this); }
|
|
visitElementProperty(ast: t.BoundElementPropertyAst) { ast.value.visit(this); }
|
|
visitAttr(ast: t.AttrAst) {}
|
|
visitBoundText(ast: t.BoundTextAst) { ast.value.visit(this); }
|
|
visitText(ast: t.TextAst) {}
|
|
visitDirective(ast: t.DirectiveAst) {
|
|
t.templateVisitAll(this, ast.hostEvents);
|
|
t.templateVisitAll(this, ast.hostProperties);
|
|
t.templateVisitAll(this, ast.inputs);
|
|
}
|
|
visitDirectiveProperty(ast: t.BoundDirectivePropertyAst) { ast.value.visit(this); }
|
|
}
|
|
|
|
/**
|
|
* Humanizes expression AST source spans in a template by returning an array of tuples
|
|
* [unparsed AST, AST source span]
|
|
* for each expression in the template.
|
|
* @param templateAsts template AST to humanize
|
|
*/
|
|
export function humanizeExpressionSource(templateAsts: t.TemplateAst[]):
|
|
HumanizedExpressionSource[] {
|
|
const humanizer = new ExpressionSourceHumanizer();
|
|
t.templateVisitAll(humanizer, templateAsts);
|
|
return humanizer.result;
|
|
}
|