UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

471 lines (424 loc) 17.9 kB
import {Type, isBlank, isPresent, isString} from 'angular2/src/facade/lang'; import {BaseException} from 'angular2/src/facade/exceptions'; import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import { PropertyRead, PropertyWrite, KeyedWrite, AST, ASTWithSource, AstVisitor, Binary, Chain, Conditional, BindingPipe, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, PrefixNot, Quote, SafePropertyRead, SafeMethodCall } from './parser/ast'; import {ChangeDetector, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces'; import {ChangeDetectionUtil} from './change_detection_util'; import {DynamicChangeDetector} from './dynamic_change_detector'; import {BindingRecord, BindingTarget} from './binding_record'; import {DirectiveRecord, DirectiveIndex} from './directive_record'; import {EventBinding} from './event_binding'; import {coalesce} from './coalesce'; import {ProtoRecord, RecordType} from './proto_record'; export class DynamicProtoChangeDetector implements ProtoChangeDetector { /** @internal */ _propertyBindingRecords: ProtoRecord[]; /** @internal */ _propertyBindingTargets: BindingTarget[]; /** @internal */ _eventBindingRecords: EventBinding[]; /** @internal */ _directiveIndices: DirectiveIndex[]; constructor(private _definition: ChangeDetectorDefinition) { this._propertyBindingRecords = createPropertyRecords(_definition); this._eventBindingRecords = createEventRecords(_definition); this._propertyBindingTargets = this._definition.bindingRecords.map(b => b.target); this._directiveIndices = this._definition.directiveRecords.map(d => d.directiveIndex); } instantiate(): ChangeDetector { return new DynamicChangeDetector( this._definition.id, this._propertyBindingRecords.length, this._propertyBindingTargets, this._directiveIndices, this._definition.strategy, this._propertyBindingRecords, this._eventBindingRecords, this._definition.directiveRecords, this._definition.genConfig); } } export function createPropertyRecords(definition: ChangeDetectorDefinition): ProtoRecord[] { var recordBuilder = new ProtoRecordBuilder(); ListWrapper.forEachWithIndex(definition.bindingRecords, (b, index) => recordBuilder.add(b, definition.variableNames, index)); return coalesce(recordBuilder.records); } export function createEventRecords(definition: ChangeDetectorDefinition): EventBinding[] { // TODO: vsavkin: remove $event when the compiler handles render-side variables properly var varNames = ListWrapper.concat(['$event'], definition.variableNames); return definition.eventRecords.map(er => { var records = _ConvertAstIntoProtoRecords.create(er, varNames); var dirIndex = er.implicitReceiver instanceof DirectiveIndex ? er.implicitReceiver : null; return new EventBinding(er.target.name, er.target.elementIndex, dirIndex, records); }); } export class ProtoRecordBuilder { records: ProtoRecord[] = []; add(b: BindingRecord, variableNames: string[], bindingIndex: number) { var oldLast = ListWrapper.last(this.records); if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) { oldLast.lastInDirective = false; } var numberOfRecordsBefore = this.records.length; this._appendRecords(b, variableNames, bindingIndex); var newLast = ListWrapper.last(this.records); if (isPresent(newLast) && newLast !== oldLast) { newLast.lastInBinding = true; newLast.lastInDirective = true; this._setArgumentToPureFunction(numberOfRecordsBefore); } } /** @internal */ _setArgumentToPureFunction(startIndex: number): void { for (var i = startIndex; i < this.records.length; ++i) { var rec = this.records[i]; if (rec.isPureFunction()) { rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction = true); } if (rec.mode === RecordType.Pipe) { rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction = true); this.records[rec.contextIndex - 1].argumentToPureFunction = true; } } } /** @internal */ _appendRecords(b: BindingRecord, variableNames: string[], bindingIndex: number) { if (b.isDirectiveLifecycle()) { this.records.push(new ProtoRecord(RecordType.DirectiveLifecycle, b.lifecycleEvent, null, [], [], -1, null, this.records.length + 1, b, false, false, false, false, null)); } else { _ConvertAstIntoProtoRecords.append(this.records, b, variableNames, bindingIndex); } } } class _ConvertAstIntoProtoRecords implements AstVisitor { constructor(private _records: ProtoRecord[], private _bindingRecord: BindingRecord, private _variableNames: string[], private _bindingIndex: number) {} static append(records: ProtoRecord[], b: BindingRecord, variableNames: string[], bindingIndex: number) { var c = new _ConvertAstIntoProtoRecords(records, b, variableNames, bindingIndex); b.ast.visit(c); } static create(b: BindingRecord, variableNames: any[]): ProtoRecord[] { var rec = []; _ConvertAstIntoProtoRecords.append(rec, b, variableNames, null); rec[rec.length - 1].lastInBinding = true; return rec; } visitImplicitReceiver(ast: ImplicitReceiver): any { return this._bindingRecord.implicitReceiver; } visitInterpolation(ast: Interpolation): number { var args = this._visitAll(ast.expressions); return this._addRecord(RecordType.Interpolate, "interpolate", _interpolationFn(ast.strings), args, ast.strings, 0); } visitLiteralPrimitive(ast: LiteralPrimitive): number { return this._addRecord(RecordType.Const, "literal", ast.value, [], null, 0); } visitPropertyRead(ast: PropertyRead): number { var receiver = ast.receiver.visit(this); if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name) && ast.receiver instanceof ImplicitReceiver) { return this._addRecord(RecordType.Local, ast.name, ast.name, [], null, receiver); } else { return this._addRecord(RecordType.PropertyRead, ast.name, ast.getter, [], null, receiver); } } visitPropertyWrite(ast: PropertyWrite): number { if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name) && ast.receiver instanceof ImplicitReceiver) { throw new BaseException(`Cannot reassign a variable binding ${ast.name}`); } else { var receiver = ast.receiver.visit(this); var value = ast.value.visit(this); return this._addRecord(RecordType.PropertyWrite, ast.name, ast.setter, [value], null, receiver); } } visitKeyedWrite(ast: KeyedWrite): number { var obj = ast.obj.visit(this); var key = ast.key.visit(this); var value = ast.value.visit(this); return this._addRecord(RecordType.KeyedWrite, null, null, [key, value], null, obj); } visitSafePropertyRead(ast: SafePropertyRead): number { var receiver = ast.receiver.visit(this); return this._addRecord(RecordType.SafeProperty, ast.name, ast.getter, [], null, receiver); } visitMethodCall(ast: MethodCall): number { var receiver = ast.receiver.visit(this); var args = this._visitAll(ast.args); if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name)) { var target = this._addRecord(RecordType.Local, ast.name, ast.name, [], null, receiver); return this._addRecord(RecordType.InvokeClosure, "closure", null, args, null, target); } else { return this._addRecord(RecordType.InvokeMethod, ast.name, ast.fn, args, null, receiver); } } visitSafeMethodCall(ast: SafeMethodCall): number { var receiver = ast.receiver.visit(this); var args = this._visitAll(ast.args); return this._addRecord(RecordType.SafeMethodInvoke, ast.name, ast.fn, args, null, receiver); } visitFunctionCall(ast: FunctionCall): number { var target = ast.target.visit(this); var args = this._visitAll(ast.args); return this._addRecord(RecordType.InvokeClosure, "closure", null, args, null, target); } visitLiteralArray(ast: LiteralArray): number { var primitiveName = `arrayFn${ast.expressions.length}`; return this._addRecord(RecordType.CollectionLiteral, primitiveName, _arrayFn(ast.expressions.length), this._visitAll(ast.expressions), null, 0); } visitLiteralMap(ast: LiteralMap): number { return this._addRecord(RecordType.CollectionLiteral, _mapPrimitiveName(ast.keys), ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null, 0); } visitBinary(ast: Binary): number { var left = ast.left.visit(this); switch (ast.operation) { case '&&': var branchEnd = [null]; this._addRecord(RecordType.SkipRecordsIfNot, "SkipRecordsIfNot", null, [], branchEnd, left); var right = ast.right.visit(this); branchEnd[0] = right; return this._addRecord(RecordType.PrimitiveOp, "cond", ChangeDetectionUtil.cond, [left, right, left], null, 0); case '||': var branchEnd = [null]; this._addRecord(RecordType.SkipRecordsIf, "SkipRecordsIf", null, [], branchEnd, left); var right = ast.right.visit(this); branchEnd[0] = right; return this._addRecord(RecordType.PrimitiveOp, "cond", ChangeDetectionUtil.cond, [left, left, right], null, 0); default: var right = ast.right.visit(this); return this._addRecord(RecordType.PrimitiveOp, _operationToPrimitiveName(ast.operation), _operationToFunction(ast.operation), [left, right], null, 0); } } visitPrefixNot(ast: PrefixNot): number { var exp = ast.expression.visit(this); return this._addRecord(RecordType.PrimitiveOp, "operation_negate", ChangeDetectionUtil.operation_negate, [exp], null, 0); } visitConditional(ast: Conditional): number { var condition = ast.condition.visit(this); var startOfFalseBranch = [null]; var endOfFalseBranch = [null]; this._addRecord(RecordType.SkipRecordsIfNot, "SkipRecordsIfNot", null, [], startOfFalseBranch, condition); var whenTrue = ast.trueExp.visit(this); var skip = this._addRecord(RecordType.SkipRecords, "SkipRecords", null, [], endOfFalseBranch, 0); var whenFalse = ast.falseExp.visit(this); startOfFalseBranch[0] = skip; endOfFalseBranch[0] = whenFalse; return this._addRecord(RecordType.PrimitiveOp, "cond", ChangeDetectionUtil.cond, [condition, whenTrue, whenFalse], null, 0); } visitPipe(ast: BindingPipe): number { var value = ast.exp.visit(this); var args = this._visitAll(ast.args); return this._addRecord(RecordType.Pipe, ast.name, ast.name, args, null, value); } visitKeyedRead(ast: KeyedRead): number { var obj = ast.obj.visit(this); var key = ast.key.visit(this); return this._addRecord(RecordType.KeyedRead, "keyedAccess", ChangeDetectionUtil.keyedAccess, [key], null, obj); } visitChain(ast: Chain): number { var args = ast.expressions.map(e => e.visit(this)); return this._addRecord(RecordType.Chain, "chain", null, args, null, 0); } visitQuote(ast: Quote): void { throw new BaseException( `Caught uninterpreted expression at ${ast.location}: ${ast.uninterpretedExpression}. ` + `Expression prefix ${ast.prefix} did not match a template transformer to interpret the expression.`); } private _visitAll(asts: any[]) { var res = ListWrapper.createFixedSize(asts.length); for (var i = 0; i < asts.length; ++i) { res[i] = asts[i].visit(this); } return res; } /** * Adds a `ProtoRecord` and returns its selfIndex. */ private _addRecord(type, name, funcOrValue, args, fixedArgs, context): number { var selfIndex = this._records.length + 1; if (context instanceof DirectiveIndex) { this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context, selfIndex, this._bindingRecord, false, false, false, false, this._bindingIndex)); } else { this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null, selfIndex, this._bindingRecord, false, false, false, false, this._bindingIndex)); } return selfIndex; } } function _arrayFn(length: number): Function { switch (length) { case 0: return ChangeDetectionUtil.arrayFn0; case 1: return ChangeDetectionUtil.arrayFn1; case 2: return ChangeDetectionUtil.arrayFn2; case 3: return ChangeDetectionUtil.arrayFn3; case 4: return ChangeDetectionUtil.arrayFn4; case 5: return ChangeDetectionUtil.arrayFn5; case 6: return ChangeDetectionUtil.arrayFn6; case 7: return ChangeDetectionUtil.arrayFn7; case 8: return ChangeDetectionUtil.arrayFn8; case 9: return ChangeDetectionUtil.arrayFn9; default: throw new BaseException(`Does not support literal maps with more than 9 elements`); } } function _mapPrimitiveName(keys: any[]) { var stringifiedKeys = keys.map(k => isString(k) ? `"${k}"` : `${k}`).join(', '); return `mapFn([${stringifiedKeys}])`; } function _operationToPrimitiveName(operation: string): string { switch (operation) { case '+': return "operation_add"; case '-': return "operation_subtract"; case '*': return "operation_multiply"; case '/': return "operation_divide"; case '%': return "operation_remainder"; case '==': return "operation_equals"; case '!=': return "operation_not_equals"; case '===': return "operation_identical"; case '!==': return "operation_not_identical"; case '<': return "operation_less_then"; case '>': return "operation_greater_then"; case '<=': return "operation_less_or_equals_then"; case '>=': return "operation_greater_or_equals_then"; default: throw new BaseException(`Unsupported operation ${operation}`); } } function _operationToFunction(operation: string): Function { switch (operation) { case '+': return ChangeDetectionUtil.operation_add; case '-': return ChangeDetectionUtil.operation_subtract; case '*': return ChangeDetectionUtil.operation_multiply; case '/': return ChangeDetectionUtil.operation_divide; case '%': return ChangeDetectionUtil.operation_remainder; case '==': return ChangeDetectionUtil.operation_equals; case '!=': return ChangeDetectionUtil.operation_not_equals; case '===': return ChangeDetectionUtil.operation_identical; case '!==': return ChangeDetectionUtil.operation_not_identical; case '<': return ChangeDetectionUtil.operation_less_then; case '>': return ChangeDetectionUtil.operation_greater_then; case '<=': return ChangeDetectionUtil.operation_less_or_equals_then; case '>=': return ChangeDetectionUtil.operation_greater_or_equals_then; default: throw new BaseException(`Unsupported operation ${operation}`); } } function s(v): string { return isPresent(v) ? `${v}` : ''; } function _interpolationFn(strings: any[]) { var length = strings.length; var c0 = length > 0 ? strings[0] : null; var c1 = length > 1 ? strings[1] : null; var c2 = length > 2 ? strings[2] : null; var c3 = length > 3 ? strings[3] : null; var c4 = length > 4 ? strings[4] : null; var c5 = length > 5 ? strings[5] : null; var c6 = length > 6 ? strings[6] : null; var c7 = length > 7 ? strings[7] : null; var c8 = length > 8 ? strings[8] : null; var c9 = length > 9 ? strings[9] : null; switch (length - 1) { case 1: return (a1) => c0 + s(a1) + c1; case 2: return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2; case 3: return (a1, a2, a3) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3; case 4: return (a1, a2, a3, a4) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4; case 5: return (a1, a2, a3, a4, a5) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5; case 6: return (a1, a2, a3, a4, a5, a6) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6; case 7: return (a1, a2, a3, a4, a5, a6, a7) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7; case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8; case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8 + s(a9) + c9; default: throw new BaseException(`Does not support more than 9 expressions`); } }