Skip to content

Commit

Permalink
refactor(compiler): refactor template binding parsing (#23460)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vicb committed Apr 20, 2018
1 parent ca776c5 commit 4662878
Show file tree
Hide file tree
Showing 7 changed files with 507 additions and 523 deletions.
4 changes: 2 additions & 2 deletions packages/common/test/directives/ng_for_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/

import {CommonModule, NgForOf} from '@angular/common';
import {Component, Directive} from '@angular/core';
import {CommonModule} from '@angular/common';
import {Component} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/src/matchers';
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/src/expression_parser/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export class ASTWithSource extends AST {
export class TemplateBinding {
constructor(
public span: ParseSpan, public key: string, public keyIsVar: boolean, public name: string,
public expression: ASTWithSource) {}
public expression: ASTWithSource|null) {}
}

export interface AstVisitor {
Expand Down
66 changes: 30 additions & 36 deletions packages/compiler/src/expression_parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,11 @@ export class Parser {
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 {
const tokens = this._lexer.tokenize(input);
if (prefixToken) {
// Prefix the tokens with the tokens from prefixToken but have them take no space (0 index).
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();
const tokens = this._lexer.tokenize(tplValue);
return new _ParseAST(tplValue, location, tokens, tplValue.length, false, this.errors, 0)
.parseTemplateBindings(tplKey);
}

parseInterpolation(
Expand Down Expand Up @@ -686,48 +678,49 @@ export class _ParseAST {
return result.toString();
}

parseTemplateBindings(): TemplateBindingParseResult {
// Parses the AST for `<some-tag *tplKey=AST>`
parseTemplateBindings(tplKey: string): TemplateBindingParseResult {
let firstBinding = true;
const bindings: TemplateBinding[] = [];
let prefix: string = null !;
const warnings: string[] = [];
while (this.index < this.tokens.length) {
do {
const start = this.inputIndex;
let keyIsVar: boolean = this.peekKeywordLet();
if (keyIsVar) {
this.advance();
}
let rawKey = this.expectTemplateBindingKey();
let key = rawKey;
if (!keyIsVar) {
if (prefix == null) {
prefix = key;
} else {
key = prefix + key[0].toUpperCase() + key.substring(1);
}
let rawKey: string;
let key: string;
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);
}
this.optionalCharacter(chars.$COLON);

let name: string = null !;
let expression: ASTWithSource = null !;
if (keyIsVar) {
let expression: ASTWithSource|null = null;
if (isVar) {
if (this.optionalOperator('=')) {
name = this.expectTemplateBindingKey();
} else {
name = '\$implicit';
}
} else if (this.peekKeywordAs()) {
const letStart = this.inputIndex;
this.advance(); // consume `as`
name = rawKey;
key = this.expectTemplateBindingKey(); // read local var name
keyIsVar = true;
isVar = true;
} else if (this.next !== EOF && !this.peekKeywordLet()) {
const start = this.inputIndex;
const ast = this.parsePipe();
const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
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;
this.advance(); // consume `as`
const letName = this.expectTemplateBindingKey(); // read local var name
Expand All @@ -736,8 +729,9 @@ export class _ParseAST {
if (!this.optionalCharacter(chars.$SEMICOLON)) {
this.optionalCharacter(chars.$COMMA);
}
}
return new TemplateBindingParseResult(bindings, warnings, this.errors);
} while (this.index < this.tokens.length)

return new TemplateBindingParseResult(bindings, warnings, this.errors);
}

error(message: string, index: number|null = null) {
Expand Down
8 changes: 4 additions & 4 deletions packages/compiler/src/render3/r3_template_transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,16 @@ export class HtmlToTemplateTransform implements html.Visitor {
}
isTemplateBinding = true;
elementHasInlineTemplate = true;
const templateBindingsSource = attribute.value;
const prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
const templateValue = attribute.value;
const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);

const oldVariables: VariableAst[] = [];

inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;

this.bindingParser.parseInlineTemplateBinding(
prefixToken !, templateBindingsSource !, attribute.sourceSpan,
templateMatchableAttributes, templateBoundProperties, oldVariables);
templateKey, templateValue, attribute.sourceSpan, templateMatchableAttributes,
templateBoundProperties, oldVariables);

templateVariables.push(
...oldVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan)));
Expand Down
11 changes: 6 additions & 5 deletions packages/compiler/src/template_parser/binding_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan,
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++) {
const binding = bindings[i];
if (binding.keyIsVar) {
Expand All @@ -149,12 +150,12 @@ export class BindingParser {
}
}

private _parseTemplateBindings(prefixToken: string, value: string, sourceSpan: ParseSourceSpan):
private _parseTemplateBindings(tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan):
TemplateBinding[] {
const sourceInfo = sourceSpan.start.toString();

try {
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
const bindingsResult = this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo);
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
bindingsResult.templateBindings.forEach((binding) => {
if (binding.expression) {
Expand Down
12 changes: 6 additions & 6 deletions packages/compiler/src/template_parser/template_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,16 +289,16 @@ class TemplateParseVisitor implements html.Visitor {
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
elementOrDirectiveRefs, elementVars);

let templateBindingsSource: string|undefined;
let prefixToken: string|undefined;
let templateValue: string|undefined;
let templateKey: string|undefined;
const normalizedName = this._normalizeAttributeName(attr.name);

if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
templateBindingsSource = attr.value;
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
templateValue = attr.value;
templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
}

const hasTemplateBinding = templateBindingsSource != null;
const hasTemplateBinding = templateValue != null;
if (hasTemplateBinding) {
if (hasInlineTemplates) {
this._reportError(
Expand All @@ -307,7 +307,7 @@ class TemplateParseVisitor implements html.Visitor {
}
hasInlineTemplates = true;
this._bindingParser.parseInlineTemplateBinding(
prefixToken !, templateBindingsSource !, attr.sourceSpan, templateMatchableAttrs,
templateKey !, templateValue !, attr.sourceSpan, templateMatchableAttrs,
templateElementOrDirectiveProps, templateElementVars);
}

Expand Down

0 comments on commit 4662878

Please sign in to comment.