diff --git a/modules/@angular/common/test/directives/ng_for_spec.ts b/modules/@angular/common/test/directives/ng_for_spec.ts index d5bf5290c7..528a405f55 100644 --- a/modules/@angular/common/test/directives/ng_for_spec.ts +++ b/modules/@angular/common/test/directives/ng_for_spec.ts @@ -14,6 +14,8 @@ import {NgFor, NgIf} from '@angular/common'; import {expect} from '@angular/platform-browser/testing/matchers'; import {By} from '@angular/platform-browser/src/dom/debug/by'; +let thisArg: any; + export function main() { describe('ngFor', () => { const TEMPLATE = @@ -460,6 +462,22 @@ export function main() { })); describe('track by', () => { + it('should set the context to the component instance', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + const template = + ``; + tcb.overrideTemplate(TestComponent, template) + .createAsync(TestComponent) + .then((fixture) => { + thisArg = null; + fixture.detectChanges(); + expect(thisArg).toBe(fixture.debugElement.componentInstance); + async.done(); + }); + })); + it('should not replace tracked items', inject( [TestComponentBuilder, AsyncTestCompleter], @@ -557,6 +575,7 @@ class TestComponent { constructor() { this.items = [1, 2]; } trackById(index: number, item: any): string { return item['id']; } trackByIndex(index: number, item: any): number { return index; } + trackByContext(): void { thisArg = this; } } @Component({selector: 'outer-cmp', directives: [TestComponent], template: ''}) diff --git a/modules/@angular/compiler/src/expression_parser/lexer.ts b/modules/@angular/compiler/src/expression_parser/lexer.ts index b8b6777aaa..bc294b27d0 100644 --- a/modules/@angular/compiler/src/expression_parser/lexer.ts +++ b/modules/@angular/compiler/src/expression_parser/lexer.ts @@ -8,7 +8,6 @@ import {Injectable} from '@angular/core'; import * as chars from '../chars'; -import {BaseException} from '../facade/exceptions'; import {NumberWrapper, StringJoiner, StringWrapper, isPresent} from '../facade/lang'; export enum TokenType { @@ -21,7 +20,7 @@ export enum TokenType { Error } -const KEYWORDS = ['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else']; +const KEYWORDS = ['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this']; @Injectable() export class Lexer { @@ -74,6 +73,8 @@ export class Token { isKeywordFalse(): boolean { return this.type == TokenType.Keyword && this.strValue == 'false'; } + isKeywordThis(): boolean { return this.type == TokenType.Keyword && this.strValue == 'this'; } + isError(): boolean { return this.type == TokenType.Error; } toNumber(): number { return this.type == TokenType.Number ? this.numValue : -1; } diff --git a/modules/@angular/compiler/src/expression_parser/parser.ts b/modules/@angular/compiler/src/expression_parser/parser.ts index cc1c7888a9..d72d1b4213 100644 --- a/modules/@angular/compiler/src/expression_parser/parser.ts +++ b/modules/@angular/compiler/src/expression_parser/parser.ts @@ -523,6 +523,10 @@ export class _ParseAST { this.advance(); return new LiteralPrimitive(this.span(start), false); + } else if (this.next.isKeywordThis()) { + this.advance(); + return new ImplicitReceiver(this.span(start)); + } else if (this.optionalCharacter(chars.$LBRACKET)) { this.rbracketsExpected++; const elements = this.parseExpressionList(chars.$RBRACKET); @@ -780,13 +784,7 @@ class SimpleExpressionChecker implements AstVisitor { visitKeyedWrite(ast: KeyedWrite, context: any) { this.simple = false; } - visitAll(asts: any[]): any[] { - var res = ListWrapper.createFixedSize(asts.length); - for (var i = 0; i < asts.length; ++i) { - res[i] = asts[i].visit(this); - } - return res; - } + visitAll(asts: any[]): any[] { return asts.map(node => node.visit(this)); } visitChain(ast: Chain, context: any) { this.simple = false; } diff --git a/modules/@angular/compiler/src/output/abstract_emitter.ts b/modules/@angular/compiler/src/output/abstract_emitter.ts index 8f2b10ec81..47a1b5f4b8 100644 --- a/modules/@angular/compiler/src/output/abstract_emitter.ts +++ b/modules/@angular/compiler/src/output/abstract_emitter.ts @@ -283,7 +283,7 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex abstract visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, context: any): any; visitBinaryOperatorExpr(ast: o.BinaryOperatorExpr, ctx: EmitterVisitorContext): any { - var opStr: any /** TODO #9100 */; + var opStr: string; switch (ast.operator) { case o.BinaryOperator.Equals: opStr = '=='; diff --git a/modules/@angular/compiler/src/output/ts_emitter.ts b/modules/@angular/compiler/src/output/ts_emitter.ts index 80ca74b387..3c6f4469d5 100644 --- a/modules/@angular/compiler/src/output/ts_emitter.ts +++ b/modules/@angular/compiler/src/output/ts_emitter.ts @@ -70,10 +70,12 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ctx.print(defaultType); } } + visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any { this._visitIdentifier(ast.value, ast.typeParams, ctx); return null; } + visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any { if (ctx.isExportedVar(stmt.name)) { ctx.print(`export `); @@ -90,6 +92,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ctx.println(`;`); return null; } + visitCastExpr(ast: o.CastExpr, ctx: EmitterVisitorContext): any { ctx.print(`(<`); ast.type.visitType(this, ctx); @@ -98,6 +101,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ctx.print(`)`); return null; } + visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any { ctx.pushClass(stmt); if (ctx.isExportedVar(stmt.name)) { @@ -121,6 +125,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ctx.popClass(); return null; } + private _visitClassField(field: o.ClassField, ctx: EmitterVisitorContext) { if (field.hasModifier(o.StmtModifier.Private)) { ctx.print(`private `); @@ -130,6 +135,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor this.visitType(field.type, ctx); ctx.println(`;`); } + private _visitClassGetter(getter: o.ClassGetter, ctx: EmitterVisitorContext) { if (getter.hasModifier(o.StmtModifier.Private)) { ctx.print(`private `); @@ -143,6 +149,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ctx.decIndent(); ctx.println(`}`); } + private _visitClassConstructor(stmt: o.ClassStmt, ctx: EmitterVisitorContext) { ctx.print(`constructor(`); this._visitParams(stmt.constructorMethod.params, ctx); @@ -152,6 +159,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ctx.decIndent(); ctx.println(`}`); } + private _visitClassMethod(method: o.ClassMethod, ctx: EmitterVisitorContext) { if (method.hasModifier(o.StmtModifier.Private)) { ctx.print(`private `); @@ -166,6 +174,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ctx.decIndent(); ctx.println(`}`); } + visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any { ctx.print(`(`); this._visitParams(ast.params, ctx); @@ -178,6 +187,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ctx.print(`}`); return null; } + visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): any { if (ctx.isExportedVar(stmt.name)) { ctx.print(`export `); @@ -193,6 +203,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ctx.println(`}`); return null; } + visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: EmitterVisitorContext): any { ctx.println(`try {`); ctx.incIndent(); @@ -237,15 +248,18 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ctx.print(typeStr); return null; } + visitExternalType(ast: o.ExternalType, ctx: EmitterVisitorContext): any { this._visitIdentifier(ast.value, ast.typeParams, ctx); return null; } + visitArrayType(type: o.ArrayType, ctx: EmitterVisitorContext): any { this.visitType(type.of, ctx); ctx.print(`[]`); return null; } + visitMapType(type: o.MapType, ctx: EmitterVisitorContext): any { ctx.print(`{[key: string]:`); this.visitType(type.valueType, ctx); @@ -271,7 +285,6 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor return name; } - private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void { this.visitAllObjects((param: any /** TODO #9100 */) => { ctx.print(param.name); diff --git a/modules/@angular/compiler/src/output/value_util.ts b/modules/@angular/compiler/src/output/value_util.ts index 6a223130da..b57adbf23f 100644 --- a/modules/@angular/compiler/src/output/value_util.ts +++ b/modules/@angular/compiler/src/output/value_util.ts @@ -21,6 +21,7 @@ class _ValueOutputAstTransformer implements ValueTransformer { visitArray(arr: any[], type: o.Type): o.Expression { return o.literalArr(arr.map(value => visitValue(value, this, null)), type); } + visitStringMap(map: {[key: string]: any}, type: o.MapType): o.Expression { var entries: Array[] = []; StringMapWrapper.forEach(map, (value: any, key: string) => { @@ -28,7 +29,9 @@ class _ValueOutputAstTransformer implements ValueTransformer { }); return o.literalMap(entries, type); } + visitPrimitive(value: any, type: o.Type): o.Expression { return o.literal(value, type); } + visitOther(value: any, type: o.Type): o.Expression { if (value instanceof CompileIdentifierMetadata) { return o.importExpr(value); diff --git a/modules/@angular/compiler/src/view_compiler/expression_converter.ts b/modules/@angular/compiler/src/view_compiler/expression_converter.ts index e3b2d94b03..f469547f58 100644 --- a/modules/@angular/compiler/src/view_compiler/expression_converter.ts +++ b/modules/@angular/compiler/src/view_compiler/expression_converter.ts @@ -12,8 +12,6 @@ import {isArray, isBlank, isPresent} from '../facade/lang'; import {Identifiers} from '../identifiers'; import * as o from '../output/output_ast'; -var IMPLICIT_RECEIVER = o.variable('#implicit'); - export interface NameResolver { callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression; getLocal(name: string): o.Expression; @@ -132,10 +130,12 @@ class _AstToIrVisitor implements cdAst.AstVisitor { new o.BinaryOperatorExpr( op, this.visit(ast.left, _Mode.Expression), this.visit(ast.right, _Mode.Expression))); } + visitChain(ast: cdAst.Chain, mode: _Mode): any { ensureStatementMode(mode, ast); return this.visitAll(ast.expressions, mode); } + visitConditional(ast: cdAst.Conditional, mode: _Mode): any { const value: o.Expression = this.visit(ast.condition, _Mode.Expression); return convertToStatementIfNeeded( @@ -143,6 +143,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor { value.conditional( this.visit(ast.trueExp, _Mode.Expression), this.visit(ast.falseExp, _Mode.Expression))); } + visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any { const input = this.visit(ast.exp, _Mode.Expression); const args = this.visitAll(ast.args, _Mode.Expression); @@ -150,15 +151,18 @@ class _AstToIrVisitor implements cdAst.AstVisitor { this.needsValueUnwrapper = true; return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value])); } + visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any { return convertToStatementIfNeeded( mode, this.visit(ast.target, _Mode.Expression).callFn(this.visitAll(ast.args, _Mode.Expression))); } + visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any { ensureExpressionMode(mode, ast); - return IMPLICIT_RECEIVER; + return this._implicitReceiver; } + visitInterpolation(ast: cdAst.Interpolation, mode: _Mode): any { ensureExpressionMode(mode, ast); const args = [o.literal(ast.expressions.length)]; @@ -169,20 +173,24 @@ class _AstToIrVisitor implements cdAst.AstVisitor { args.push(o.literal(ast.strings[ast.strings.length - 1])); return o.importExpr(Identifiers.interpolate).callFn(args); } + visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any { return convertToStatementIfNeeded( mode, this.visit(ast.obj, _Mode.Expression).key(this.visit(ast.key, _Mode.Expression))); } + visitKeyedWrite(ast: cdAst.KeyedWrite, mode: _Mode): any { const obj: o.Expression = this.visit(ast.obj, _Mode.Expression); const key: o.Expression = this.visit(ast.key, _Mode.Expression); const value: o.Expression = this.visit(ast.value, _Mode.Expression); return convertToStatementIfNeeded(mode, obj.key(key).set(value)); } + visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any { return convertToStatementIfNeeded( mode, this._nameResolver.createLiteralArray(this.visitAll(ast.expressions, mode))); } + visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any { let parts: any[] = []; for (let i = 0; i < ast.keys.length; i++) { @@ -190,9 +198,11 @@ class _AstToIrVisitor implements cdAst.AstVisitor { } return convertToStatementIfNeeded(mode, this._nameResolver.createLiteralMap(parts)); } + visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any { return convertToStatementIfNeeded(mode, o.literal(ast.value)); } + visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any { const leftMostSafe = this.leftMostSafeNode(ast); if (leftMostSafe) { @@ -201,12 +211,10 @@ class _AstToIrVisitor implements cdAst.AstVisitor { const args = this.visitAll(ast.args, _Mode.Expression); let result: any = null; let receiver = this.visit(ast.receiver, _Mode.Expression); - if (receiver === IMPLICIT_RECEIVER) { + if (receiver === this._implicitReceiver) { var varExpr = this._nameResolver.getLocal(ast.name); if (isPresent(varExpr)) { result = varExpr.callFn(args); - } else { - receiver = this._implicitReceiver; } } if (isBlank(result)) { @@ -215,9 +223,11 @@ class _AstToIrVisitor implements cdAst.AstVisitor { return convertToStatementIfNeeded(mode, result); } } + visitPrefixNot(ast: cdAst.PrefixNot, mode: _Mode): any { return convertToStatementIfNeeded(mode, o.not(this.visit(ast.expression, _Mode.Expression))); } + visitPropertyRead(ast: cdAst.PropertyRead, mode: _Mode): any { const leftMostSafe = this.leftMostSafeNode(ast); if (leftMostSafe) { @@ -225,11 +235,8 @@ class _AstToIrVisitor implements cdAst.AstVisitor { } else { let result: any = null; var receiver = this.visit(ast.receiver, _Mode.Expression); - if (receiver === IMPLICIT_RECEIVER) { + if (receiver === this._implicitReceiver) { result = this._nameResolver.getLocal(ast.name); - if (isBlank(result)) { - receiver = this._implicitReceiver; - } } if (isBlank(result)) { result = receiver.prop(ast.name); @@ -237,25 +244,29 @@ class _AstToIrVisitor implements cdAst.AstVisitor { return convertToStatementIfNeeded(mode, result); } } + visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any { let receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression); - if (receiver === IMPLICIT_RECEIVER) { + if (receiver === this._implicitReceiver) { var varExpr = this._nameResolver.getLocal(ast.name); if (isPresent(varExpr)) { throw new BaseException('Cannot assign to a reference or variable!'); } - receiver = this._implicitReceiver; } return convertToStatementIfNeeded( mode, receiver.prop(ast.name).set(this.visit(ast.value, _Mode.Expression))); } + visitSafePropertyRead(ast: cdAst.SafePropertyRead, mode: _Mode): any { return this.convertSafeAccess(ast, this.leftMostSafeNode(ast), mode); } + visitSafeMethodCall(ast: cdAst.SafeMethodCall, mode: _Mode): any { return this.convertSafeAccess(ast, this.leftMostSafeNode(ast), mode); } + visitAll(asts: cdAst.AST[], mode: _Mode): any { return asts.map(ast => this.visit(ast, mode)); } + visitQuote(ast: cdAst.Quote, mode: _Mode): any { throw new BaseException('Quotes are not supported for evaluation!'); } diff --git a/modules/@angular/compiler/test/expression_parser/lexer_spec.ts b/modules/@angular/compiler/test/expression_parser/lexer_spec.ts index 01e86ed867..eba3bdf604 100644 --- a/modules/@angular/compiler/test/expression_parser/lexer_spec.ts +++ b/modules/@angular/compiler/test/expression_parser/lexer_spec.ts @@ -67,6 +67,12 @@ export function main() { expectIdentifierToken(tokens[0], 0, 'j'); }); + it('should tokenize "this"', () => { + var tokens: number[] = lex('this'); + expect(tokens.length).toEqual(1); + expectKeywordToken(tokens[0], 0, 'this'); + }); + it('should tokenize a dotted identifier', () => { var tokens: number[] = lex('j.k'); expect(tokens.length).toEqual(3); diff --git a/modules/@angular/compiler/test/expression_parser/parser_spec.ts b/modules/@angular/compiler/test/expression_parser/parser_spec.ts index b88e88ee04..ffd17e4dd1 100644 --- a/modules/@angular/compiler/test/expression_parser/parser_spec.ts +++ b/modules/@angular/compiler/test/expression_parser/parser_spec.ts @@ -163,6 +163,7 @@ export function main() { describe('member access', () => { it('should parse field access', () => { checkAction('a'); + checkAction('this.a', 'a'); checkAction('a.a'); });