diff --git a/modules/change_detection/src/parser/ast.js b/modules/change_detection/src/parser/ast.js index d493301f24..5c5e3520fc 100644 --- a/modules/change_detection/src/parser/ast.js +++ b/modules/change_detection/src/parser/ast.js @@ -425,10 +425,12 @@ export class ASTWithSource extends AST { export class TemplateBinding { key:string; + keyIsVar:boolean; name:string; expression:ASTWithSource; - constructor(key:string, name:string, expression:ASTWithSource) { + constructor(key:string, keyIsVar:boolean, name:string, expression:ASTWithSource) { this.key = key; + this.keyIsVar = keyIsVar; // only either name or expression will be filled. this.name = name; this.expression = expression; diff --git a/modules/change_detection/src/parser/lexer.js b/modules/change_detection/src/parser/lexer.js index c2d6dc68cd..6c71486904 100644 --- a/modules/change_detection/src/parser/lexer.js +++ b/modules/change_detection/src/parser/lexer.js @@ -62,6 +62,10 @@ export class Token { return (this.type == TOKEN_TYPE_KEYWORD); } + isKeywordVar():boolean { + return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "var"); + } + isKeywordNull():boolean { return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "null"); } @@ -469,6 +473,7 @@ var OPERATORS = SetWrapper.createFromList([ var KEYWORDS = SetWrapper.createFromList([ + 'var', 'null', 'undefined', 'true', diff --git a/modules/change_detection/src/parser/parser.js b/modules/change_detection/src/parser/parser.js index d0423429bd..df00dc2e18 100644 --- a/modules/change_detection/src/parser/parser.js +++ b/modules/change_detection/src/parser/parser.js @@ -123,6 +123,19 @@ class _ParseAST { } } + optionalKeywordVar():boolean { + if (this.peekKeywordVar()) { + this.advance(); + return true; + } else { + return false; + } + } + + peekKeywordVar():boolean { + return this.next.isKeywordVar() || this.next.isOperator('#'); + } + expectCharacter(code:int) { if (this.optionalCharacter(code)) return; this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`); @@ -469,21 +482,26 @@ class _ParseAST { parseTemplateBindings() { var bindings = []; while (this.index < this.tokens.length) { + var keyIsVar:boolean = this.optionalKeywordVar(); var key = this.expectTemplateBindingKey(); this.optionalCharacter($COLON); var name = null; var expression = null; if (this.next !== EOF) { - if (this.optionalOperator("#")) { - name = this.expectIdentifierOrKeyword(); - } else { + if (keyIsVar) { + if (this.optionalOperator("=")) { + name = this.expectTemplateBindingKey(); + } else { + name = '\$implicit'; + } + } else if (!this.peekKeywordVar()) { var start = this.inputIndex; var ast = this.parseExpression(); var source = this.input.substring(start, this.inputIndex); expression = new ASTWithSource(ast, source, this.location); } } - ListWrapper.push(bindings, new TemplateBinding(key, name, expression)); + ListWrapper.push(bindings, new TemplateBinding(key, keyIsVar, name, expression)); if (!this.optionalCharacter($SEMICOLON)) { this.optionalCharacter($COMMA); }; diff --git a/modules/change_detection/test/parser/parser_spec.js b/modules/change_detection/test/parser/parser_spec.js index efc5c3ada2..87a515abd0 100644 --- a/modules/change_detection/test/parser/parser_spec.js +++ b/modules/change_detection/test/parser/parser_spec.js @@ -408,6 +408,16 @@ export function main() { return ListWrapper.map(templateBindings, (binding) => binding.key ); } + function keyValues(templateBindings) { + return ListWrapper.map(templateBindings, (binding) => { + if (binding.keyIsVar) { + return '#' + binding.key + (isBlank(binding.name) ? '' : '=' + binding.name); + } else { + return binding.key + (isBlank(binding.expression) ? '' : `=${binding.expression}`) + } + }); + } + function names(templateBindings) { return ListWrapper.map(templateBindings, (binding) => binding.name ); } @@ -466,9 +476,7 @@ export function main() { it('should detect names as value', () => { var bindings = parseTemplateBindings("a:#b"); - expect(names(bindings)).toEqual(['b']); - expect(exprSources(bindings)).toEqual([null]); - expect(exprAsts(bindings)).toEqual([null]); + expect(keyValues(bindings)).toEqual(['a', '#b']); }); it('should allow space and colon as separators', () => { @@ -497,6 +505,26 @@ export function main() { var bindings = parseTemplateBindings("a 1,b 2", 'location'); expect(bindings[0].expression.location).toEqual('location'); }); + + it('should support var/# notation', () => { + var bindings = parseTemplateBindings("var i"); + expect(keyValues(bindings)).toEqual(['#i']); + + bindings = parseTemplateBindings("#i"); + expect(keyValues(bindings)).toEqual(['#i']); + + bindings = parseTemplateBindings("var i-a = k-a"); + expect(keyValues(bindings)).toEqual(['#i-a=k-a']); + + bindings = parseTemplateBindings("keyword var item; var i = k"); + expect(keyValues(bindings)).toEqual(['keyword', '#item=\$implicit', '#i=k']); + + bindings = parseTemplateBindings("keyword: #item; #i = k"); + expect(keyValues(bindings)).toEqual(['keyword', '#item=\$implicit', '#i=k']); + + bindings = parseTemplateBindings("directive: var item in expr; var a = b", 'location'); + expect(keyValues(bindings)).toEqual(['directive', '#item=\$implicit', 'in=expr in location', '#a=b']); + }); }); describe('parseInterpolation', () => { diff --git a/modules/core/src/compiler/pipeline/compile_element.js b/modules/core/src/compiler/pipeline/compile_element.js index ceb05425f3..b0ab1171ef 100644 --- a/modules/core/src/compiler/pipeline/compile_element.js +++ b/modules/core/src/compiler/pipeline/compile_element.js @@ -23,6 +23,10 @@ export class CompileElement { textNodeBindings:Map; propertyBindings:Map; eventBindings:Map; + + /// Store directive name to template name mapping. + /// Directive name is what the directive exports the variable as + /// Template name is how it is reffered to it in template variableBindings:Map; decoratorDirectives:List; templateDirective:DirectiveMetadata; @@ -102,11 +106,11 @@ export class CompileElement { MapWrapper.set(this.propertyBindings, property, expression); } - addVariableBinding(contextName:string, templateName:string) { + addVariableBinding(directiveName:string, templateName:string) { if (isBlank(this.variableBindings)) { this.variableBindings = MapWrapper.create(); } - MapWrapper.set(this.variableBindings, contextName, templateName); + MapWrapper.set(this.variableBindings, templateName, directiveName); } addEventBinding(eventName:string, expression:AST) { diff --git a/modules/core/src/compiler/pipeline/element_binder_builder.js b/modules/core/src/compiler/pipeline/element_binder_builder.js index 94caabde67..282f33d361 100644 --- a/modules/core/src/compiler/pipeline/element_binder_builder.js +++ b/modules/core/src/compiler/pipeline/element_binder_builder.js @@ -97,8 +97,8 @@ export class ElementBinderBuilder extends CompileStep { MapWrapper.get(compileElement.propertyBindings, elProp) : null; if (isBlank(expression)) { - throw new BaseException('No element binding found for property '+elProp - +' which is required by directive '+stringify(directive.type)); + throw new BaseException("No element binding found for property '" + elProp + + "' which is required by directive '" + stringify(directive.type) + "'"); } var len = dirProp.length; var dirBindingName = dirProp; diff --git a/modules/core/src/compiler/pipeline/property_binding_parser.js b/modules/core/src/compiler/pipeline/property_binding_parser.js index ebece54e36..aed44b9dc8 100644 --- a/modules/core/src/compiler/pipeline/property_binding_parser.js +++ b/modules/core/src/compiler/pipeline/property_binding_parser.js @@ -9,7 +9,7 @@ import {CompileElement} from './compile_element'; import {CompileControl} from './compile_control'; // TODO(tbosch): Cannot make this const/final right now because of the transpiler... -var BIND_NAME_REGEXP = RegExpWrapper.create('^(?:(?:(bind)|(let)|(on))-(.+))|\\[([^\\]]+)\\]|\\(([^\\)]+)\\)'); +var BIND_NAME_REGEXP = RegExpWrapper.create('^(?:(?:(bind)|(var)|(on))-(.+))|\\[([^\\]]+)\\]|\\(([^\\)]+)\\)'); /** * Parses the property bindings on a single element. @@ -40,7 +40,7 @@ export class PropertyBindingParser extends CompileStep { // Note: We assume that the ViewSplitter already did its work, i.e. template directive should // only be present on