diff --git a/modules/angular2/src/change_detection/change_detection_jit_generator.ts b/modules/angular2/src/change_detection/change_detection_jit_generator.ts index 0baa8e0467..6eb3914684 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.ts +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.ts @@ -7,6 +7,7 @@ import {DirectiveIndex, DirectiveRecord} from './directive_record'; import {ProtoRecord, RecordType} from './proto_record'; import {CodegenNameUtil, sanitizeName} from './codegen_name_util'; +import {CodegenLogicUtil} from './codegen_logic_util'; /** @@ -24,6 +25,7 @@ var IS_CHANGED_LOCAL = "isChanged"; var CHANGES_LOCAL = "changes"; export class ChangeDetectorJITGenerator { + _logic: CodegenLogicUtil; _names: CodegenNameUtil; _typeName: string; @@ -31,6 +33,7 @@ export class ChangeDetectorJITGenerator { public records: List, public directiveRecords: List, private generateCheckNoChanges: boolean) { this._names = new CodegenNameUtil(this.records, this.directiveRecords, UTIL); + this._logic = new CodegenLogicUtil(this._names, UTIL); this._typeName = sanitizeName(`ChangeDetector_${this.id}`); } @@ -206,7 +209,7 @@ export class ChangeDetectorJITGenerator { var oldValue = this._names.getFieldName(r.selfIndex); var newValue = this._names.getLocalName(r.selfIndex); var read = ` - ${this._genUpdateCurrentValue(r)} + ${this._logic.genUpdateCurrentValue(r)} `; var check = ` @@ -232,80 +235,6 @@ export class ChangeDetectorJITGenerator { } } - _genUpdateCurrentValue(r: ProtoRecord): string { - var context = (r.contextIndex == -1) ? this._names.getDirectiveName(r.directiveIndex) : - this._names.getLocalName(r.contextIndex); - var newValue = this._names.getLocalName(r.selfIndex); - var argString = r.args.map((arg) => this._names.getLocalName(arg)).join(", "); - - var rhs; - switch (r.mode) { - case RecordType.SELF: - rhs = context; - break; - - case RecordType.CONST: - rhs = JSON.stringify(r.funcOrValue); - break; - - case RecordType.PROPERTY: - rhs = `${context}.${r.name}`; - break; - - case RecordType.SAFE_PROPERTY: - rhs = `${UTIL}.isValueBlank(${context}) ? null : ${context}.${r.name}`; - break; - - case RecordType.LOCAL: - rhs = `${this._names.getLocalsAccessorName()}.get('${r.name}')`; - break; - - case RecordType.INVOKE_METHOD: - rhs = `${context}.${r.name}(${argString})`; - break; - - case RecordType.SAFE_INVOKE_METHOD: - rhs = `${UTIL}.isValueBlank(${context}) ? null : ${context}.${r.name}(${argString})`; - break; - - case RecordType.INVOKE_CLOSURE: - rhs = `${context}(${argString})`; - break; - - case RecordType.PRIMITIVE_OP: - rhs = `${UTIL}.${r.name}(${argString})`; - break; - - case RecordType.COLLECTION_LITERAL: - rhs = `${UTIL}.${r.name}(${argString})`; - break; - - case RecordType.INTERPOLATE: - rhs = this._genInterpolation(r); - break; - - case RecordType.KEYED_ACCESS: - rhs = `${context}[${this._names.getLocalName(r.args[0])}]`; - break; - - default: - throw new BaseException(`Unknown operation ${r.mode}`); - } - return `${newValue} = ${rhs}`; - } - - _genInterpolation(r: ProtoRecord): string { - var res = ""; - for (var i = 0; i < r.args.length; ++i) { - res += JSON.stringify(r.fixedArgs[i]); - res += " + "; - res += `${UTIL}.s(${this._names.getLocalName(r.args[i])})`; - res += " + "; - } - res += JSON.stringify(r.fixedArgs[r.args.length]); - return res; - } - _genChangeMarker(r: ProtoRecord): string { return r.argumentToPureFunction ? `${this._names.getChangeName(r.selfIndex)} = true` : ``; } diff --git a/modules/angular2/src/change_detection/codegen_facade.dart b/modules/angular2/src/change_detection/codegen_facade.dart new file mode 100644 index 0000000000..0c3e9275b9 --- /dev/null +++ b/modules/angular2/src/change_detection/codegen_facade.dart @@ -0,0 +1,15 @@ +library angular2.src.change_detection.codegen_facade; + +import 'dart:convert' show JSON; + +/// Converts `funcOrValue` to a string which can be used in generated code. +String codify(funcOrValue) => JSON.encode(funcOrValue).replaceAll(r'$', r'\$'); + +/// Combine the strings of generated code into a single interpolated string. +/// Each element of `vals` is expected to be a string literal or a codegen'd +/// call to a method returning a string. +/// The return format interpolates each value as an expression which reads +/// poorly, but the resulting code is easily flattened by dart2js. +String combineGeneratedStrings(List vals) { + return '"${vals.map((v) => '\${$v}').join('')}"'; +} diff --git a/modules/angular2/src/change_detection/codegen_facade.ts b/modules/angular2/src/change_detection/codegen_facade.ts new file mode 100644 index 0000000000..8064f7616e --- /dev/null +++ b/modules/angular2/src/change_detection/codegen_facade.ts @@ -0,0 +1,17 @@ +import {List} from 'angular2/src/facade/collection'; + +/** + * Converts `funcOrValue` to a string which can be used in generated code. + */ +export function codify(obj: any): string { + return JSON.stringify(obj); +} + +/** + * Combine the strings of generated code into a single interpolated string. + * Each element of `vals` is expected to be a string literal or a codegen'd + * call to a method returning a string. + */ +export function combineGeneratedStrings(vals: string[]): string { + return vals.join(' + '); +} diff --git a/modules/angular2/src/change_detection/codegen_logic_util.ts b/modules/angular2/src/change_detection/codegen_logic_util.ts new file mode 100644 index 0000000000..c2d4431d4c --- /dev/null +++ b/modules/angular2/src/change_detection/codegen_logic_util.ts @@ -0,0 +1,90 @@ +import {ListWrapper} from 'angular2/src/facade/collection'; +import {BaseException, Json} from 'angular2/src/facade/lang'; +import {CodegenNameUtil} from './codegen_name_util'; +import {codify, combineGeneratedStrings} from './codegen_facade'; +import {ProtoRecord, RecordType} from './proto_record'; + +/** + * Class responsible for providing change detection logic for chagne detector classes. + */ +export class CodegenLogicUtil { + constructor(private _names: CodegenNameUtil, private _utilName: string) {} + + /** + * Generates a statement which updates the local variable representing `protoRec` with the current + * value of the record. + */ + genUpdateCurrentValue(protoRec: ProtoRecord): string { + var context = (protoRec.contextIndex == -1) ? + this._names.getDirectiveName(protoRec.directiveIndex) : + this._names.getLocalName(protoRec.contextIndex); + var argString = + ListWrapper.map(protoRec.args, (arg) => this._names.getLocalName(arg)).join(", "); + + var rhs: string; + switch (protoRec.mode) { + case RecordType.SELF: + rhs = context; + break; + + case RecordType.CONST: + rhs = codify(protoRec.funcOrValue); + break; + + case RecordType.PROPERTY: + rhs = `${context}.${protoRec.name}`; + break; + + case RecordType.SAFE_PROPERTY: + rhs = `${this._utilName}.isValueBlank(${context}) ? null : ${context}.${protoRec.name}`; + break; + + case RecordType.LOCAL: + rhs = `${this._names.getLocalsAccessorName()}.get('${protoRec.name}')`; + break; + + case RecordType.INVOKE_METHOD: + rhs = `${context}.${protoRec.name}(${argString})`; + break; + + case RecordType.SAFE_INVOKE_METHOD: + rhs = + `${this._utilName}.isValueBlank(${context}) ? null : ${context}.${protoRec.name}(${argString})`; + break; + + case RecordType.INVOKE_CLOSURE: + rhs = `${context}(${argString})`; + break; + + case RecordType.PRIMITIVE_OP: + rhs = `${this._utilName}.${protoRec.name}(${argString})`; + break; + + case RecordType.COLLECTION_LITERAL: + rhs = `${this._utilName}.${protoRec.name}(${argString})`; + break; + + case RecordType.INTERPOLATE: + rhs = this._genInterpolation(protoRec); + break; + + case RecordType.KEYED_ACCESS: + rhs = `${context}[${this._names.getLocalName(protoRec.args[0])}]`; + break; + + default: + throw new BaseException(`Unknown operation ${protoRec.mode}`); + } + return `${this._names.getLocalName(protoRec.selfIndex)} = ${rhs};`; + } + + _genInterpolation(protoRec: ProtoRecord): string { + var iVals = []; + for (var i = 0; i < protoRec.args.length; ++i) { + iVals.push(codify(protoRec.fixedArgs[i])); + iVals.push(`${this._utilName}.s(${this._names.getLocalName(protoRec.args[i])})`); + } + iVals.push(codify(protoRec.fixedArgs[protoRec.args.length])); + return combineGeneratedStrings(iVals); + } +} diff --git a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart index b7adb068bd..6aa0642153 100644 --- a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart +++ b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart @@ -1,8 +1,9 @@ library angular2.transform.template_compiler.change_detector_codegen; -import 'dart:convert' show JSON; import 'package:angular2/src/change_detection/change_detection_util.dart'; import 'package:angular2/src/change_detection/coalesce.dart'; +import 'package:angular2/src/change_detection/codegen_facade.dart'; +import 'package:angular2/src/change_detection/codegen_logic_util.dart'; import 'package:angular2/src/change_detection/codegen_name_util.dart'; import 'package:angular2/src/change_detection/directive_record.dart'; import 'package:angular2/src/change_detection/interfaces.dart'; @@ -73,27 +74,26 @@ class _CodegenState { final String _changeDetectionMode; final List _records; final List _directiveRecords; + final CodegenLogicUtil _logic; final CodegenNameUtil _names; final bool _generateCheckNoChanges; _CodegenState._(this._changeDetectorDefId, this._contextTypeName, this._changeDetectorTypeName, String changeDetectionStrategy, - List records, List directiveRecords, + this._records, this._directiveRecords, this._logic, this._names, this._generateCheckNoChanges) - : _records = records, - _directiveRecords = directiveRecords, - _names = new CodegenNameUtil(records, directiveRecords, _UTIL), - _changeDetectionMode = ChangeDetectionUtil - .changeDetectionMode(changeDetectionStrategy); + : _changeDetectionMode = ChangeDetectionUtil + .changeDetectionMode(changeDetectionStrategy); factory _CodegenState(String typeName, String changeDetectorTypeName, ChangeDetectorDefinition def) { - var protoRecords = new ProtoRecordBuilder(); - def.bindingRecords - .forEach((rec) => protoRecords.add(rec, def.variableNames)); - var records = coalesce(protoRecords.records); + var recBuilder = new ProtoRecordBuilder(); + def.bindingRecords.forEach((rec) => recBuilder.add(rec, def.variableNames)); + var protoRecords = coalesce(recBuilder.records); + var names = new CodegenNameUtil(protoRecords, def.directiveRecords, _UTIL); + var logic = new CodegenLogicUtil(names, _UTIL); return new _CodegenState._(def.id, typeName, changeDetectorTypeName, - def.strategy, records, def.directiveRecords, + def.strategy, protoRecords, def.directiveRecords, logic, names, def.generateCheckNoChanges); } @@ -103,7 +103,7 @@ class _CodegenState { ${_genDeclareFields()} $_changeDetectorTypeName(dispatcher, protos, directiveRecords) - : super(${_encodeValue(_changeDetectorDefId)}, + : super(${codify(_changeDetectorDefId)}, dispatcher, protos, directiveRecords, '$_changeDetectionMode') { dehydrateDirectives(false); } @@ -284,7 +284,7 @@ class _CodegenState { var oldValue = _names.getFieldName(r.selfIndex); var newValue = _names.getLocalName(r.selfIndex); var read = ''' - ${_genUpdateCurrentValue(r)} + ${_logic.genUpdateCurrentValue(r)} '''; var check = ''' @@ -303,7 +303,7 @@ class _CodegenState { var condition = r.args.map((a) => _names.getChangeName(a)).join(' || '); if (r.isUsedByOtherRecord()) { return 'if ($condition) { $genCode } else { $newValue = $oldValue; }'; - } else { + } else { return 'if ($condition) { $genCode }'; } } else { @@ -311,85 +311,10 @@ class _CodegenState { } } - String _genUpdateCurrentValue(ProtoRecord r) { - var context = r.contextIndex == -1 - ? _names.getDirectiveName(r.directiveIndex) - : _names.getLocalName(r.contextIndex); - - var newValue = _names.getLocalName(r.selfIndex); - var argString = r.args.map((arg) => _names.getLocalName(arg)).join(', '); - - var rhs; - switch (r.mode) { - case RecordType.SELF: - rhs = context; - break; - - case RecordType.CONST: - rhs = _encodeValue(r.funcOrValue); - break; - - case RecordType.PROPERTY: - rhs = '$context.${r.name}'; - break; - - case RecordType.SAFE_PROPERTY: - rhs = '${_UTIL}.isValueBlank(${context}) ? null : ${context}.${r.name}'; - break; - - case RecordType.LOCAL: - rhs = '${_names.getLocalsAccessorName()}.get("${r.name}")'; - break; - - case RecordType.INVOKE_METHOD: - rhs = '$context.${r.name}($argString)'; - break; - - case RecordType.SAFE_INVOKE_METHOD: - rhs = '${_UTIL}.isValueBlank(${context}) ' - '? null : ${context}.${r.name}(${argString})'; - break; - - case RecordType.INVOKE_CLOSURE: - rhs = '$context($argString)'; - break; - - case RecordType.PRIMITIVE_OP: - rhs = '$_UTIL.${r.name}($argString)'; - break; - - case RecordType.COLLECTION_LITERAL: - rhs = '$_UTIL.${r.name}($argString)'; - break; - - case RecordType.INTERPOLATE: - rhs = _genInterpolation(r); - break; - - case RecordType.KEYED_ACCESS: - rhs = '$context[${_names.getLocalName(r.args[0])}]'; - break; - - default: - throw new FormatException( - 'Unknown operation ${r.mode}', r.expressionAsString); - } - return '$newValue = $rhs;'; - } - - String _genInterpolation(ProtoRecord r) { - var res = new StringBuffer(); - for (var i = 0; i < r.args.length; ++i) { - var name = _names.getLocalName(r.args[i]); - res.write( - '${_encodeValue(r.fixedArgs[i])} "\$\{$name == null ? "" : $name\}" '); - } - res.write(_encodeValue(r.fixedArgs[r.args.length])); - return '$res'; - } - String _genChangeMarker(ProtoRecord r) { - return r.argumentToPureFunction ? "${this._names.getChangeName(r.selfIndex)} = true;" : ""; + return r.argumentToPureFunction + ? "${this._names.getChangeName(r.selfIndex)} = true;" + : ""; } String _genUpdateDirectiveOrElement(ProtoRecord r) { @@ -438,7 +363,9 @@ class _CodegenState { String _maybeFirstInBinding(ProtoRecord r) { var prev = ChangeDetectionUtil.protoByIndex(_records, r.selfIndex - 1); var firstInBindng = prev == null || prev.bindingRecord != r.bindingRecord; - return firstInBindng ? "${_names.getFirstProtoInCurrentBinding()} = ${r.selfIndex};" : ''; + return firstInBindng + ? "${_names.getFirstProtoInCurrentBinding()} = ${r.selfIndex};" + : ''; } String _genAddToChanges(ProtoRecord r) { @@ -485,9 +412,6 @@ class _CodegenState { } '''; } - - String _encodeValue(funcOrValue) => - JSON.encode(funcOrValue).replaceAll(r'$', r'\$'); } const PROTO_CHANGE_DETECTOR_FACTORY_METHOD = 'newProtoChangeDetector'; diff --git a/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart b/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart index 8aa611ed7f..3be081861a 100644 --- a/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart +++ b/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart @@ -51,7 +51,7 @@ class _MyComponent_ChangeDetector0 } if (c_myNum0) { l_interpolate1 = - "Salad: " "${l_myNum0 == null ? "" : l_myNum0}" " is awesome"; + "${"Salad: "}${_gen.ChangeDetectionUtil.s(l_myNum0)}${" is awesome"}"; if (_gen.looseNotIdentical(l_interpolate1, this.interpolate1)) { if (throwOnChange) { this.throwOnChangeError(this.interpolate1, l_interpolate1);