refactor(compiler): refactor template binding parsing (#23460)
A long time ago Angular used to support both those attribute notations: - `*attr='binding'` - `template=`attr: binding` Because the last notation has been dropped we can refactor the binding parsing. Source maps will benefit from that as no `attr:` prefix is added artificialy any more. PR Close #23460
This commit is contained in:
parent
ca776c59dd
commit
4662878a1f
@ -6,8 +6,8 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CommonModule, NgForOf} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {Component, Directive} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
|
@ -215,7 +215,7 @@ export class ASTWithSource extends AST {
|
|||||||
export class TemplateBinding {
|
export class TemplateBinding {
|
||||||
constructor(
|
constructor(
|
||||||
public span: ParseSpan, public key: string, public keyIsVar: boolean, public name: string,
|
public span: ParseSpan, public key: string, public keyIsVar: boolean, public name: string,
|
||||||
public expression: ASTWithSource) {}
|
public expression: ASTWithSource|null) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AstVisitor {
|
export interface AstVisitor {
|
||||||
|
@ -98,19 +98,11 @@ export class Parser {
|
|||||||
return new Quote(new ParseSpan(0, input.length), prefix, uninterpretedExpression, location);
|
return new Quote(new ParseSpan(0, input.length), prefix, uninterpretedExpression, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseTemplateBindings(prefixToken: string|null, input: string, location: any):
|
parseTemplateBindings(tplKey: string, tplValue: string, location: any):
|
||||||
TemplateBindingParseResult {
|
TemplateBindingParseResult {
|
||||||
const tokens = this._lexer.tokenize(input);
|
const tokens = this._lexer.tokenize(tplValue);
|
||||||
if (prefixToken) {
|
return new _ParseAST(tplValue, location, tokens, tplValue.length, false, this.errors, 0)
|
||||||
// Prefix the tokens with the tokens from prefixToken but have them take no space (0 index).
|
.parseTemplateBindings(tplKey);
|
||||||
const prefixTokens = this._lexer.tokenize(prefixToken).map(t => {
|
|
||||||
t.index = 0;
|
|
||||||
return t;
|
|
||||||
});
|
|
||||||
tokens.unshift(...prefixTokens);
|
|
||||||
}
|
|
||||||
return new _ParseAST(input, location, tokens, input.length, false, this.errors, 0)
|
|
||||||
.parseTemplateBindings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parseInterpolation(
|
parseInterpolation(
|
||||||
@ -686,48 +678,49 @@ export class _ParseAST {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
parseTemplateBindings(): TemplateBindingParseResult {
|
// Parses the AST for `<some-tag *tplKey=AST>`
|
||||||
|
parseTemplateBindings(tplKey: string): TemplateBindingParseResult {
|
||||||
|
let firstBinding = true;
|
||||||
const bindings: TemplateBinding[] = [];
|
const bindings: TemplateBinding[] = [];
|
||||||
let prefix: string = null !;
|
|
||||||
const warnings: string[] = [];
|
const warnings: string[] = [];
|
||||||
while (this.index < this.tokens.length) {
|
do {
|
||||||
const start = this.inputIndex;
|
const start = this.inputIndex;
|
||||||
let keyIsVar: boolean = this.peekKeywordLet();
|
let rawKey: string;
|
||||||
if (keyIsVar) {
|
let key: string;
|
||||||
this.advance();
|
let isVar: boolean = false;
|
||||||
|
if (firstBinding) {
|
||||||
|
rawKey = key = tplKey;
|
||||||
|
firstBinding = false;
|
||||||
|
} else {
|
||||||
|
isVar = this.peekKeywordLet();
|
||||||
|
if (isVar) this.advance()
|
||||||
|
rawKey = this.expectTemplateBindingKey();
|
||||||
|
key = isVar ? rawKey : tplKey + rawKey[0].toUpperCase() + rawKey.substring(1);
|
||||||
|
this.optionalCharacter(chars.$COLON);
|
||||||
}
|
}
|
||||||
let rawKey = this.expectTemplateBindingKey();
|
|
||||||
let key = rawKey;
|
|
||||||
if (!keyIsVar) {
|
|
||||||
if (prefix == null) {
|
|
||||||
prefix = key;
|
|
||||||
} else {
|
|
||||||
key = prefix + key[0].toUpperCase() + key.substring(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.optionalCharacter(chars.$COLON);
|
|
||||||
let name: string = null !;
|
let name: string = null !;
|
||||||
let expression: ASTWithSource = null !;
|
let expression: ASTWithSource|null = null;
|
||||||
if (keyIsVar) {
|
if (isVar) {
|
||||||
if (this.optionalOperator('=')) {
|
if (this.optionalOperator('=')) {
|
||||||
name = this.expectTemplateBindingKey();
|
name = this.expectTemplateBindingKey();
|
||||||
} else {
|
} else {
|
||||||
name = '\$implicit';
|
name = '\$implicit';
|
||||||
}
|
}
|
||||||
} else if (this.peekKeywordAs()) {
|
} else if (this.peekKeywordAs()) {
|
||||||
const letStart = this.inputIndex;
|
|
||||||
this.advance(); // consume `as`
|
this.advance(); // consume `as`
|
||||||
name = rawKey;
|
name = rawKey;
|
||||||
key = this.expectTemplateBindingKey(); // read local var name
|
key = this.expectTemplateBindingKey(); // read local var name
|
||||||
keyIsVar = true;
|
isVar = true;
|
||||||
} else if (this.next !== EOF && !this.peekKeywordLet()) {
|
} else if (this.next !== EOF && !this.peekKeywordLet()) {
|
||||||
const start = this.inputIndex;
|
const start = this.inputIndex;
|
||||||
const ast = this.parsePipe();
|
const ast = this.parsePipe();
|
||||||
const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
|
const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
|
||||||
expression = new ASTWithSource(ast, source, this.location, this.errors);
|
expression = new ASTWithSource(ast, source, this.location, this.errors);
|
||||||
}
|
}
|
||||||
bindings.push(new TemplateBinding(this.span(start), key, keyIsVar, name, expression));
|
|
||||||
if (this.peekKeywordAs() && !keyIsVar) {
|
bindings.push(new TemplateBinding(this.span(start), key, isVar, name, expression));
|
||||||
|
if (this.peekKeywordAs() && !isVar) {
|
||||||
const letStart = this.inputIndex;
|
const letStart = this.inputIndex;
|
||||||
this.advance(); // consume `as`
|
this.advance(); // consume `as`
|
||||||
const letName = this.expectTemplateBindingKey(); // read local var name
|
const letName = this.expectTemplateBindingKey(); // read local var name
|
||||||
@ -736,8 +729,9 @@ export class _ParseAST {
|
|||||||
if (!this.optionalCharacter(chars.$SEMICOLON)) {
|
if (!this.optionalCharacter(chars.$SEMICOLON)) {
|
||||||
this.optionalCharacter(chars.$COMMA);
|
this.optionalCharacter(chars.$COMMA);
|
||||||
}
|
}
|
||||||
}
|
} while (this.index < this.tokens.length)
|
||||||
return new TemplateBindingParseResult(bindings, warnings, this.errors);
|
|
||||||
|
return new TemplateBindingParseResult(bindings, warnings, this.errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message: string, index: number|null = null) {
|
error(message: string, index: number|null = null) {
|
||||||
|
@ -107,16 +107,16 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
|||||||
}
|
}
|
||||||
isTemplateBinding = true;
|
isTemplateBinding = true;
|
||||||
elementHasInlineTemplate = true;
|
elementHasInlineTemplate = true;
|
||||||
const templateBindingsSource = attribute.value;
|
const templateValue = attribute.value;
|
||||||
const prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
|
const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
|
||||||
|
|
||||||
const oldVariables: VariableAst[] = [];
|
const oldVariables: VariableAst[] = [];
|
||||||
|
|
||||||
inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;
|
inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;
|
||||||
|
|
||||||
this.bindingParser.parseInlineTemplateBinding(
|
this.bindingParser.parseInlineTemplateBinding(
|
||||||
prefixToken !, templateBindingsSource !, attribute.sourceSpan,
|
templateKey, templateValue, attribute.sourceSpan, templateMatchableAttributes,
|
||||||
templateMatchableAttributes, templateBoundProperties, oldVariables);
|
templateBoundProperties, oldVariables);
|
||||||
|
|
||||||
templateVariables.push(
|
templateVariables.push(
|
||||||
...oldVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan)));
|
...oldVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan)));
|
||||||
|
@ -130,11 +130,12 @@ export class BindingParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse an inline template binding. ie `<tag *prefixToken="<value>">`
|
// Parse an inline template binding. ie `<tag *tplKey="<tplValue>">`
|
||||||
parseInlineTemplateBinding(
|
parseInlineTemplateBinding(
|
||||||
prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan,
|
||||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
||||||
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
|
const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan);
|
||||||
|
|
||||||
for (let i = 0; i < bindings.length; i++) {
|
for (let i = 0; i < bindings.length; i++) {
|
||||||
const binding = bindings[i];
|
const binding = bindings[i];
|
||||||
if (binding.keyIsVar) {
|
if (binding.keyIsVar) {
|
||||||
@ -149,12 +150,12 @@ export class BindingParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _parseTemplateBindings(prefixToken: string, value: string, sourceSpan: ParseSourceSpan):
|
private _parseTemplateBindings(tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan):
|
||||||
TemplateBinding[] {
|
TemplateBinding[] {
|
||||||
const sourceInfo = sourceSpan.start.toString();
|
const sourceInfo = sourceSpan.start.toString();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
|
const bindingsResult = this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo);
|
||||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||||
bindingsResult.templateBindings.forEach((binding) => {
|
bindingsResult.templateBindings.forEach((binding) => {
|
||||||
if (binding.expression) {
|
if (binding.expression) {
|
||||||
|
@ -289,16 +289,16 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
|
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
|
||||||
elementOrDirectiveRefs, elementVars);
|
elementOrDirectiveRefs, elementVars);
|
||||||
|
|
||||||
let templateBindingsSource: string|undefined;
|
let templateValue: string|undefined;
|
||||||
let prefixToken: string|undefined;
|
let templateKey: string|undefined;
|
||||||
const normalizedName = this._normalizeAttributeName(attr.name);
|
const normalizedName = this._normalizeAttributeName(attr.name);
|
||||||
|
|
||||||
if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||||
templateBindingsSource = attr.value;
|
templateValue = attr.value;
|
||||||
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
|
templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasTemplateBinding = templateBindingsSource != null;
|
const hasTemplateBinding = templateValue != null;
|
||||||
if (hasTemplateBinding) {
|
if (hasTemplateBinding) {
|
||||||
if (hasInlineTemplates) {
|
if (hasInlineTemplates) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
@ -307,7 +307,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
}
|
}
|
||||||
hasInlineTemplates = true;
|
hasInlineTemplates = true;
|
||||||
this._bindingParser.parseInlineTemplateBinding(
|
this._bindingParser.parseInlineTemplateBinding(
|
||||||
prefixToken !, templateBindingsSource !, attr.sourceSpan, templateMatchableAttrs,
|
templateKey !, templateValue !, attr.sourceSpan, templateMatchableAttrs,
|
||||||
templateElementOrDirectiveProps, templateElementVars);
|
templateElementOrDirectiveProps, templateElementVars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user