diff --git a/modules/@angular/compiler/src/expression_parser/ast.ts b/modules/@angular/compiler/src/expression_parser/ast.ts index 340c47981e..0d1112880e 100644 --- a/modules/@angular/compiler/src/expression_parser/ast.ts +++ b/modules/@angular/compiler/src/expression_parser/ast.ts @@ -204,7 +204,7 @@ export class ASTWithSource extends AST { export class TemplateBinding { constructor( - public key: string, public keyIsVar: boolean, public name: string, + public span: ParseSpan, public key: string, public keyIsVar: boolean, public name: string, public expression: ASTWithSource) {} } diff --git a/modules/@angular/compiler/src/expression_parser/parser.ts b/modules/@angular/compiler/src/expression_parser/parser.ts index ee5bb77f32..137f755e39 100644 --- a/modules/@angular/compiler/src/expression_parser/parser.ts +++ b/modules/@angular/compiler/src/expression_parser/parser.ts @@ -102,8 +102,17 @@ export class Parser { return new Quote(new ParseSpan(0, input.length), prefix, uninterpretedExpression, location); } - parseTemplateBindings(input: string, location: any): TemplateBindingParseResult { - var tokens = this._lexer.tokenize(input); + parseTemplateBindings(prefixToken: string, input: 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(); } @@ -161,6 +170,8 @@ export class Parser { 'Blank expressions are not allowed in interpolated strings', input, `at column ${this._findInterpolationErrorColumn(parts, i, interpolationConfig)} in`, location); + expressions.push('$implict'); + offsets.push(offset); } } return new SplitInterpolation(strings, expressions, offsets); @@ -676,6 +687,7 @@ export class _ParseAST { let prefix: string = null; let warnings: string[] = []; while (this.index < this.tokens.length) { + const start = this.inputIndex; const keyIsVar: boolean = this.peekKeywordLet(); if (keyIsVar) { this.advance(); @@ -700,10 +712,10 @@ export class _ParseAST { } else if (this.next !== EOF && !this.peekKeywordLet()) { const start = this.inputIndex; const ast = this.parsePipe(); - const source = this.input.substring(start, this.inputIndex); + 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(key, keyIsVar, name, expression)); + bindings.push(new TemplateBinding(this.span(start), key, keyIsVar, name, expression)); if (!this.optionalCharacter(chars.$SEMICOLON)) { this.optionalCharacter(chars.$COMMA); } diff --git a/modules/@angular/compiler/src/template_parser/binding_parser.ts b/modules/@angular/compiler/src/template_parser/binding_parser.ts index a2f0f63aa0..fc815d145e 100644 --- a/modules/@angular/compiler/src/template_parser/binding_parser.ts +++ b/modules/@angular/compiler/src/template_parser/binding_parser.ts @@ -112,9 +112,9 @@ export class BindingParser { } parseInlineTemplateBinding( - name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][], - targetProps: BoundProperty[], targetVars: VariableAst[]) { - const bindings = this._parseTemplateBindings(value, sourceSpan); + name: string, prefixToken: string, value: string, sourceSpan: ParseSourceSpan, + targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) { + const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan); for (let i = 0; i < bindings.length; i++) { const binding = bindings[i]; if (binding.keyIsVar) { @@ -129,11 +129,12 @@ export class BindingParser { } } - private _parseTemplateBindings(value: string, sourceSpan: ParseSourceSpan): TemplateBinding[] { + private _parseTemplateBindings(prefixToken: string, value: string, sourceSpan: ParseSourceSpan): + TemplateBinding[] { const sourceInfo = sourceSpan.start.toString(); try { - const bindingsResult = this._exprParser.parseTemplateBindings(value, sourceInfo); + const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo); this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan); bindingsResult.templateBindings.forEach((binding) => { if (isPresent(binding.expression)) { diff --git a/modules/@angular/compiler/src/template_parser/template_parser.ts b/modules/@angular/compiler/src/template_parser/template_parser.ts index 2b8348b7c9..2fcfecce52 100644 --- a/modules/@angular/compiler/src/template_parser/template_parser.ts +++ b/modules/@angular/compiler/src/template_parser/template_parser.ts @@ -271,12 +271,13 @@ class TemplateParseVisitor implements html.Visitor { isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events, elementOrDirectiveRefs, elementVars); - let templateBindingsSource: string; + let templateBindingsSource: string|undefined = undefined; + let prefixToken: string|undefined = undefined; if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) { templateBindingsSource = attr.value; } else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) { - const key = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star - templateBindingsSource = (attr.value.length == 0) ? key : key + ' ' + attr.value; + templateBindingsSource = attr.value; + prefixToken = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star } const hasTemplateBinding = isPresent(templateBindingsSource); if (hasTemplateBinding) { @@ -287,7 +288,7 @@ class TemplateParseVisitor implements html.Visitor { } hasInlineTemplates = true; this._bindingParser.parseInlineTemplateBinding( - attr.name, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs, + attr.name, prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs, templateElementOrDirectiveProps, templateElementVars); } diff --git a/modules/@angular/compiler/test/expression_parser/parser_spec.ts b/modules/@angular/compiler/test/expression_parser/parser_spec.ts index e5eb96bfc8..0926d57cf3 100644 --- a/modules/@angular/compiler/test/expression_parser/parser_spec.ts +++ b/modules/@angular/compiler/test/expression_parser/parser_spec.ts @@ -28,11 +28,12 @@ export function main() { } function parseTemplateBindingsResult( - text: string, location: any = null): TemplateBindingParseResult { - return createParser().parseTemplateBindings(text, location); + text: string, location: any = null, prefix?: string): TemplateBindingParseResult { + return createParser().parseTemplateBindings(prefix, text, location); } - function parseTemplateBindings(text: string, location: any = null): TemplateBinding[] { - return parseTemplateBindingsResult(text, location).templateBindings; + function parseTemplateBindings( + text: string, location: any = null, prefix?: string): TemplateBinding[] { + return parseTemplateBindingsResult(text, location, prefix).templateBindings; } function parseInterpolation(text: string, location: any = null): ASTWithSource { @@ -327,6 +328,11 @@ export function main() { }); } + function keySpans(source: string, templateBindings: TemplateBinding[]) { + return templateBindings.map( + binding => source.substring(binding.span.start, binding.span.end)); + } + function exprSources(templateBindings: any[]) { return templateBindings.map( binding => isPresent(binding.expression) ? binding.expression.source : null); @@ -429,6 +435,30 @@ export function main() { var 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']); + }); + + 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' + ]); + }); + + it('should support a prefix', () => { + var source = 'let person of people'; + var prefix = 'ngFor'; + var bindings = parseTemplateBindings(source, null, prefix); + expect(keyValues(bindings)).toEqual([ + 'ngFor', 'let person=$implicit', 'ngForOf=people in null' + ]); + expect(keySpans(source, bindings)).toEqual(['', 'let person ', 'of people']); + }); + }); }); describe('parseInterpolation', () => { diff --git a/modules/@angular/compiler/test/template_parser/template_parser_spec.ts b/modules/@angular/compiler/test/template_parser/template_parser_spec.ts index 1969d8bbb3..a5785b5338 100644 --- a/modules/@angular/compiler/test/template_parser/template_parser_spec.ts +++ b/modules/@angular/compiler/test/template_parser/template_parser_spec.ts @@ -1192,7 +1192,7 @@ Reference "#a" is defined several times ("
]#a>
it('should report an error on variables declared with #', () => { expect(() => humanizeTplAst(parse('
', []))) - .toThrowError(/Parser Error: Unexpected token # at column 6/); + .toThrowError(/Parser Error: Unexpected token # at column 1/); }); it('should parse variables via let ...', () => {