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
|
||||
*/
|
||||
|
||||
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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
@ -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;
|
||||
let rawKey: string;
|
||||
let key: string;
|
||||
let isVar: boolean = false;
|
||||
if (firstBinding) {
|
||||
rawKey = key = tplKey;
|
||||
firstBinding = false;
|
||||
} else {
|
||||
key = prefix + key[0].toUpperCase() + key.substring(1);
|
||||
}
|
||||
}
|
||||
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 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
|
||||
|
@ -736,7 +729,8 @@ export class _ParseAST {
|
|||
if (!this.optionalCharacter(chars.$SEMICOLON)) {
|
||||
this.optionalCharacter(chars.$COMMA);
|
||||
}
|
||||
}
|
||||
} while (this.index < this.tokens.length)
|
||||
|
||||
return new TemplateBindingParseResult(bindings, warnings, this.errors);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,79 +15,6 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
|||
import {unparse} from './unparser';
|
||||
import {validate} from './validator';
|
||||
|
||||
(function() {
|
||||
function createParser() { return new Parser(new Lexer()); }
|
||||
|
||||
function parseAction(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseAction(text, location);
|
||||
}
|
||||
|
||||
function parseBinding(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseBinding(text, location);
|
||||
}
|
||||
|
||||
function parseTemplateBindingsResult(
|
||||
text: string, location: any = null, prefix?: string): TemplateBindingParseResult {
|
||||
return createParser().parseTemplateBindings(prefix || null, text, location);
|
||||
}
|
||||
function parseTemplateBindings(
|
||||
text: string, location: any = null, prefix?: string): TemplateBinding[] {
|
||||
return parseTemplateBindingsResult(text, location, prefix).templateBindings;
|
||||
}
|
||||
|
||||
function parseInterpolation(text: string, location: any = null): ASTWithSource|null {
|
||||
return createParser().parseInterpolation(text, location);
|
||||
}
|
||||
|
||||
function splitInterpolation(text: string, location: any = null): SplitInterpolation|null {
|
||||
return createParser().splitInterpolation(text, location);
|
||||
}
|
||||
|
||||
function parseSimpleBinding(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseSimpleBinding(text, location);
|
||||
}
|
||||
|
||||
function checkInterpolation(exp: string, expected?: string) {
|
||||
const ast = parseInterpolation(exp) !;
|
||||
if (expected == null) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
validate(ast);
|
||||
}
|
||||
|
||||
function checkBinding(exp: string, expected?: string) {
|
||||
const ast = parseBinding(exp);
|
||||
if (expected == null) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
validate(ast);
|
||||
}
|
||||
|
||||
function checkAction(exp: string, expected?: string) {
|
||||
const ast = parseAction(exp);
|
||||
if (expected == null) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
validate(ast);
|
||||
}
|
||||
|
||||
function expectError(ast: {errors: ParserError[]}, message: string) {
|
||||
for (const error of ast.errors) {
|
||||
if (error.message.indexOf(message) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const errMsgs = ast.errors.map(err => err.message).join('\n');
|
||||
throw Error(
|
||||
`Expected an error containing "${message}" to be reported, but got the errors:\n` +
|
||||
errMsgs);
|
||||
}
|
||||
|
||||
function expectActionError(text: string, message: string) {
|
||||
expectError(validate(parseAction(text)), message);
|
||||
}
|
||||
|
||||
function expectBindingError(text: string, message: string) {
|
||||
expectError(validate(parseBinding(text)), message);
|
||||
}
|
||||
|
||||
describe('parser', () => {
|
||||
describe('parseAction', () => {
|
||||
it('should parse numbers', () => { checkAction('1'); });
|
||||
|
@ -321,9 +248,7 @@ import {validate} from './validator';
|
|||
|
||||
describe('parseTemplateBindings', () => {
|
||||
|
||||
function keys(templateBindings: any[]) {
|
||||
return templateBindings.map(binding => binding.key);
|
||||
}
|
||||
function keys(templateBindings: any[]) { return templateBindings.map(binding => binding.key); }
|
||||
|
||||
function keyValues(templateBindings: any[]) {
|
||||
return templateBindings.map(binding => {
|
||||
|
@ -345,131 +270,126 @@ import {validate} from './validator';
|
|||
binding => binding.expression != null ? binding.expression.source : null);
|
||||
}
|
||||
|
||||
it('should parse an empty string', () => { expect(parseTemplateBindings('')).toEqual([]); });
|
||||
it('should parse a key without a value',
|
||||
() => { expect(keys(parseTemplateBindings('a', ''))).toEqual(['a']); });
|
||||
|
||||
it('should parse a string without a value',
|
||||
() => { expect(keys(parseTemplateBindings('a'))).toEqual(['a']); });
|
||||
|
||||
it('should only allow identifier, string, or keyword including dashes as keys', () => {
|
||||
let bindings = parseTemplateBindings('a:\'b\'');
|
||||
it('should allow string including dashes as keys', () => {
|
||||
let bindings = parseTemplateBindings('a', 'b');
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
|
||||
bindings = parseTemplateBindings('\'a\':\'b\'');
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
|
||||
bindings = parseTemplateBindings('"a":\'b\'');
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
|
||||
bindings = parseTemplateBindings('a-b:\'c\'');
|
||||
bindings = parseTemplateBindings('a-b', 'c');
|
||||
expect(keys(bindings)).toEqual(['a-b']);
|
||||
|
||||
expectError(parseTemplateBindingsResult('(:0'), 'expected identifier, keyword, or string');
|
||||
|
||||
expectError(
|
||||
parseTemplateBindingsResult('1234:0'), 'expected identifier, keyword, or string');
|
||||
});
|
||||
|
||||
it('should detect expressions as value', () => {
|
||||
let bindings = parseTemplateBindings('a:b');
|
||||
let bindings = parseTemplateBindings('a', 'b');
|
||||
expect(exprSources(bindings)).toEqual(['b']);
|
||||
|
||||
bindings = parseTemplateBindings('a:1+1');
|
||||
bindings = parseTemplateBindings('a', '1+1');
|
||||
expect(exprSources(bindings)).toEqual(['1+1']);
|
||||
});
|
||||
|
||||
it('should detect names as value', () => {
|
||||
const bindings = parseTemplateBindings('a:let b');
|
||||
expect(keyValues(bindings)).toEqual(['a', 'let b=\$implicit']);
|
||||
const bindings = parseTemplateBindings('a', 'let b');
|
||||
expect(keyValues(bindings)).toEqual(['a', 'let b=$implicit']);
|
||||
});
|
||||
|
||||
it('should allow space and colon as separators', () => {
|
||||
let bindings = parseTemplateBindings('a:b');
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
expect(exprSources(bindings)).toEqual(['b']);
|
||||
|
||||
bindings = parseTemplateBindings('a b');
|
||||
let bindings = parseTemplateBindings('a', 'b');
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
expect(exprSources(bindings)).toEqual(['b']);
|
||||
});
|
||||
|
||||
it('should allow multiple pairs', () => {
|
||||
const bindings = parseTemplateBindings('a 1 b 2');
|
||||
const bindings = parseTemplateBindings('a', '1 b 2');
|
||||
expect(keys(bindings)).toEqual(['a', 'aB']);
|
||||
expect(exprSources(bindings)).toEqual(['1 ', '2']);
|
||||
});
|
||||
|
||||
it('should store the sources in the result', () => {
|
||||
const bindings = parseTemplateBindings('a 1,b 2');
|
||||
expect(bindings[0].expression.source).toEqual('1');
|
||||
expect(bindings[1].expression.source).toEqual('2');
|
||||
const bindings = parseTemplateBindings('a', '1,b 2');
|
||||
expect(bindings[0].expression !.source).toEqual('1');
|
||||
expect(bindings[1].expression !.source).toEqual('2');
|
||||
});
|
||||
|
||||
it('should store the passed-in location', () => {
|
||||
const bindings = parseTemplateBindings('a 1,b 2', 'location');
|
||||
expect(bindings[0].expression.location).toEqual('location');
|
||||
const bindings = parseTemplateBindings('a', '1,b 2', 'location');
|
||||
expect(bindings[0].expression !.location).toEqual('location');
|
||||
});
|
||||
|
||||
it('should support let notation', () => {
|
||||
let bindings = parseTemplateBindings('let i');
|
||||
expect(keyValues(bindings)).toEqual(['let i=\$implicit']);
|
||||
let bindings = parseTemplateBindings('key', 'let i');
|
||||
expect(keyValues(bindings)).toEqual(['key', 'let i=$implicit']);
|
||||
|
||||
bindings = parseTemplateBindings('let i');
|
||||
expect(keyValues(bindings)).toEqual(['let i=\$implicit']);
|
||||
|
||||
bindings = parseTemplateBindings('let a; let b');
|
||||
expect(keyValues(bindings)).toEqual(['let a=\$implicit', 'let b=\$implicit']);
|
||||
|
||||
bindings = parseTemplateBindings('let a; let b;');
|
||||
expect(keyValues(bindings)).toEqual(['let a=\$implicit', 'let b=\$implicit']);
|
||||
|
||||
bindings = parseTemplateBindings('let i-a = k-a');
|
||||
expect(keyValues(bindings)).toEqual(['let i-a=k-a']);
|
||||
|
||||
bindings = parseTemplateBindings('keyword let item; let i = k');
|
||||
expect(keyValues(bindings)).toEqual(['keyword', 'let item=\$implicit', 'let i=k']);
|
||||
|
||||
bindings = parseTemplateBindings('keyword: let item; let i = k');
|
||||
expect(keyValues(bindings)).toEqual(['keyword', 'let item=\$implicit', 'let i=k']);
|
||||
|
||||
bindings = parseTemplateBindings('directive: let item in expr; let a = b', 'location');
|
||||
bindings = parseTemplateBindings('key', 'let a; let b');
|
||||
expect(keyValues(bindings)).toEqual([
|
||||
'directive', 'let item=\$implicit', 'directiveIn=expr in location', 'let a=b'
|
||||
'key',
|
||||
'let a=$implicit',
|
||||
'let b=$implicit',
|
||||
]);
|
||||
|
||||
bindings = parseTemplateBindings('key', 'let a; let b;');
|
||||
expect(keyValues(bindings)).toEqual([
|
||||
'key',
|
||||
'let a=$implicit',
|
||||
'let b=$implicit',
|
||||
]);
|
||||
|
||||
bindings = parseTemplateBindings('key', 'let i-a = k-a');
|
||||
expect(keyValues(bindings)).toEqual([
|
||||
'key',
|
||||
'let i-a=k-a',
|
||||
]);
|
||||
|
||||
bindings = parseTemplateBindings('key', 'let item; let i = k');
|
||||
expect(keyValues(bindings)).toEqual([
|
||||
'key',
|
||||
'let item=$implicit',
|
||||
'let i=k',
|
||||
]);
|
||||
|
||||
bindings = parseTemplateBindings('directive', 'let item in expr; let a = b', 'location');
|
||||
expect(keyValues(bindings)).toEqual([
|
||||
'directive',
|
||||
'let item=$implicit',
|
||||
'directiveIn=expr in location',
|
||||
'let a=b',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support as notation', () => {
|
||||
let bindings = parseTemplateBindings('ngIf exp as local', 'location');
|
||||
let bindings = parseTemplateBindings('ngIf', 'exp as local', 'location');
|
||||
expect(keyValues(bindings)).toEqual(['ngIf=exp in location', 'let local=ngIf']);
|
||||
|
||||
bindings = parseTemplateBindings('ngFor let item of items as iter; index as i', 'L');
|
||||
bindings = parseTemplateBindings('ngFor', 'let item of items as iter; index as i', 'L');
|
||||
expect(keyValues(bindings)).toEqual([
|
||||
'ngFor', 'let item=$implicit', 'ngForOf=items in L', 'let iter=ngForOf', 'let i=index'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse pipes', () => {
|
||||
const bindings = parseTemplateBindings('key value|pipe');
|
||||
const ast = bindings[0].expression.ast;
|
||||
const bindings = parseTemplateBindings('key', 'value|pipe');
|
||||
const ast = bindings[0].expression !.ast;
|
||||
expect(ast).toBeAnInstanceOf(BindingPipe);
|
||||
});
|
||||
|
||||
describe('spans', () => {
|
||||
it('should should support let', () => {
|
||||
const source = 'let i';
|
||||
expect(keySpans(source, parseTemplateBindings(source))).toEqual(['let i']);
|
||||
expect(keySpans(source, parseTemplateBindings('key', 'let i'))).toEqual(['', 'let i']);
|
||||
});
|
||||
|
||||
it('should support multiple lets', () => {
|
||||
const source = 'let item; let i=index; let e=even;';
|
||||
expect(keySpans(source, parseTemplateBindings(source))).toEqual([
|
||||
'let item', 'let i=index', 'let e=even'
|
||||
expect(keySpans(source, parseTemplateBindings('key', source))).toEqual([
|
||||
'', 'let item', 'let i=index', 'let e=even'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support a prefix', () => {
|
||||
const source = 'let person of people';
|
||||
const prefix = 'ngFor';
|
||||
const bindings = parseTemplateBindings(source, null, prefix);
|
||||
const bindings = parseTemplateBindings(prefix, source);
|
||||
expect(keyValues(bindings)).toEqual([
|
||||
'ngFor', 'let person=$implicit', 'ngForOf=people in null'
|
||||
]);
|
||||
|
@ -506,8 +426,7 @@ import {validate} from './validator';
|
|||
'Parser Error: Blank expressions are not allowed in interpolated strings');
|
||||
});
|
||||
|
||||
it('should parse conditional expression',
|
||||
() => { checkInterpolation('{{ a < b ? a : b }}'); });
|
||||
it('should parse conditional expression', () => { checkInterpolation('{{ a < b ? a : b }}'); });
|
||||
|
||||
it('should parse expression with newline characters', () => {
|
||||
checkInterpolation(`{{ 'foo' +\n 'bar' +\r 'baz' }}`, `{{ "foo" + "bar" + "baz" }}`);
|
||||
|
@ -515,8 +434,7 @@ import {validate} from './validator';
|
|||
|
||||
it('should support custom interpolation', () => {
|
||||
const parser = new Parser(new Lexer());
|
||||
const ast =
|
||||
parser.parseInterpolation('{% a %}', null, {start: '{%', end: '%}'}) !.ast as any;
|
||||
const ast = parser.parseInterpolation('{% a %}', null, {start: '{%', end: '%}'}) !.ast as any;
|
||||
expect(ast.strings).toEqual(['', '']);
|
||||
expect(ast.expressions.length).toEqual(1);
|
||||
expect(ast.expressions[0].name).toEqual('a');
|
||||
|
@ -574,8 +492,7 @@ import {validate} from './validator';
|
|||
|
||||
describe('wrapLiteralPrimitive', () => {
|
||||
it('should wrap a literal primitive', () => {
|
||||
expect(unparse(validate(createParser().wrapLiteralPrimitive('foo', null))))
|
||||
.toEqual('"foo"');
|
||||
expect(unparse(validate(createParser().wrapLiteralPrimitive('foo', null)))).toEqual('"foo"');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -606,4 +523,76 @@ import {validate} from './validator';
|
|||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
function createParser() {
|
||||
return new Parser(new Lexer());
|
||||
}
|
||||
|
||||
function parseAction(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseAction(text, location);
|
||||
}
|
||||
|
||||
function parseBinding(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseBinding(text, location);
|
||||
}
|
||||
|
||||
function parseTemplateBindingsResult(
|
||||
key: string, value: string, location: any = null): TemplateBindingParseResult {
|
||||
return createParser().parseTemplateBindings(key, value, location);
|
||||
}
|
||||
function parseTemplateBindings(
|
||||
key: string, value: string, location: any = null): TemplateBinding[] {
|
||||
return parseTemplateBindingsResult(key, value, location).templateBindings;
|
||||
}
|
||||
|
||||
function parseInterpolation(text: string, location: any = null): ASTWithSource|null {
|
||||
return createParser().parseInterpolation(text, location);
|
||||
}
|
||||
|
||||
function splitInterpolation(text: string, location: any = null): SplitInterpolation|null {
|
||||
return createParser().splitInterpolation(text, location);
|
||||
}
|
||||
|
||||
function parseSimpleBinding(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseSimpleBinding(text, location);
|
||||
}
|
||||
|
||||
function checkInterpolation(exp: string, expected?: string) {
|
||||
const ast = parseInterpolation(exp) !;
|
||||
if (expected == null) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
validate(ast);
|
||||
}
|
||||
|
||||
function checkBinding(exp: string, expected?: string) {
|
||||
const ast = parseBinding(exp);
|
||||
if (expected == null) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
validate(ast);
|
||||
}
|
||||
|
||||
function checkAction(exp: string, expected?: string) {
|
||||
const ast = parseAction(exp);
|
||||
if (expected == null) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
validate(ast);
|
||||
}
|
||||
|
||||
function expectError(ast: {errors: ParserError[]}, message: string) {
|
||||
for (const error of ast.errors) {
|
||||
if (error.message.indexOf(message) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const errMsgs = ast.errors.map(err => err.message).join('\n');
|
||||
throw Error(
|
||||
`Expected an error containing "${message}" to be reported, but got the errors:\n` + errMsgs);
|
||||
}
|
||||
|
||||
function expectActionError(text: string, message: string) {
|
||||
expectError(validate(parseAction(text)), message);
|
||||
}
|
||||
|
||||
function expectBindingError(text: string, message: string) {
|
||||
expectError(validate(parseBinding(text)), message);
|
||||
}
|
Loading…
Reference in New Issue