From cb7643cceaf462956887d00cd5c21df2d80c010c Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 21 Oct 2016 13:37:51 -0700 Subject: [PATCH] refactor(compiler): introduce `ClassBuilder`. Part of #11683 --- .../src/directive_wrapper_compiler.ts | 111 ++++++++++-------- .../compiler/src/ng_module_compiler.ts | 50 ++++---- .../compiler/src/output/class_builder.ts | 60 ++++++++++ .../src/view_compiler/compile_view.ts | 7 +- .../src/view_compiler/event_binder.ts | 2 +- .../src/view_compiler/view_builder.ts | 18 +-- 6 files changed, 160 insertions(+), 88 deletions(-) create mode 100644 modules/@angular/compiler/src/output/class_builder.ts diff --git a/modules/@angular/compiler/src/directive_wrapper_compiler.ts b/modules/@angular/compiler/src/directive_wrapper_compiler.ts index 0aca4c284b..5c78d0d199 100644 --- a/modules/@angular/compiler/src/directive_wrapper_compiler.ts +++ b/modules/@angular/compiler/src/directive_wrapper_compiler.ts @@ -11,6 +11,7 @@ import {Injectable} from '@angular/core'; import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata'; import {CompilerConfig} from './config'; import {Identifiers, resolveIdentifier} from './identifiers'; +import {ClassBuilder, createClassStmt} from './output/class_builder'; import * as o from './output/output_ast'; import {LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core'; @@ -45,59 +46,68 @@ export class DirectiveWrapperCompiler { constructor(private compilerConfig: CompilerConfig) {} compile(dirMeta: CompileDirectiveMetadata): DirectiveWrapperCompileResult { + const builder = new DirectiveWrapperBuilder(this.compilerConfig, dirMeta); + Object.keys(dirMeta.inputs).forEach((inputFieldName) => { + createCheckInputMethod(inputFieldName, builder); + }); + createDetectChangesInternalMethod(builder); + const classStmt = builder.build(); + return new DirectiveWrapperCompileResult([classStmt], classStmt.name); + } +} + +class DirectiveWrapperBuilder implements ClassBuilder { + fields: o.ClassField[] = []; + getters: o.ClassGetter[] = []; + methods: o.ClassMethod[] = []; + ctorStmts: o.Statement[] = []; + + genChanges: boolean; + ngOnChanges: boolean; + ngOnInit: boolean; + ngDoCheck: boolean; + + constructor(public compilerConfig: CompilerConfig, public dirMeta: CompileDirectiveMetadata) { + const dirLifecycleHooks = dirMeta.type.lifecycleHooks; + this.genChanges = dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1 || + this.compilerConfig.logBindingUpdate; + this.ngOnChanges = dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1; + this.ngOnInit = dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1; + this.ngDoCheck = dirLifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1; + } + + build(): o.ClassStmt { const dirDepParamNames: string[] = []; - for (let i = 0; i < dirMeta.type.diDeps.length; i++) { + for (let i = 0; i < this.dirMeta.type.diDeps.length; i++) { dirDepParamNames.push(`p${i}`); } - const dirLifecycleHooks = dirMeta.type.lifecycleHooks; - let lifecycleHooks: GenConfig = { - genChanges: dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1 || - this.compilerConfig.logBindingUpdate, - ngOnChanges: dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1, - ngOnInit: dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1, - ngDoCheck: dirLifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1 - }; const fields: o.ClassField[] = [ - new o.ClassField(CONTEXT_FIELD_NAME, o.importType(dirMeta.type)), + new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)), new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE), ]; const ctorStmts: o.Statement[] = [o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()]; - if (lifecycleHooks.genChanges) { + if (this.genChanges) { fields.push(new o.ClassField(CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE))); ctorStmts.push(RESET_CHANGES_STMT); } - const methods: o.ClassMethod[] = []; - Object.keys(dirMeta.inputs).forEach((inputFieldName, idx) => { - const fieldName = `_${inputFieldName}`; - // private is fine here as no child view will reference the cached value... - fields.push(new o.ClassField(fieldName, null, [o.StmtModifier.Private])); - ctorStmts.push(o.THIS_EXPR.prop(fieldName) - .set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED))) - .toStmt()); - methods.push(checkInputMethod(inputFieldName, o.THIS_EXPR.prop(fieldName), lifecycleHooks)); - }); - methods.push(detectChangesInternalMethod(lifecycleHooks, this.compilerConfig.genDebugInfo)); - ctorStmts.push( o.THIS_EXPR.prop(CONTEXT_FIELD_NAME) - .set(o.importExpr(dirMeta.type) + .set(o.importExpr(this.dirMeta.type) .instantiate(dirDepParamNames.map((paramName) => o.variable(paramName)))) .toStmt()); - const ctor = new o.ClassMethod( - null, dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)), - ctorStmts); - const wrapperClassName = DirectiveWrapperCompiler.dirWrapperClassName(dirMeta.type); - const classStmt = new o.ClassStmt(wrapperClassName, null, fields, [], ctor, methods); - return new DirectiveWrapperCompileResult([classStmt], wrapperClassName); + return createClassStmt({ + name: DirectiveWrapperCompiler.dirWrapperClassName(this.dirMeta.type), + ctorParams: dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)), + builders: [{fields: fields, ctorStmts: ctorStmts}, this] + }) } } -function detectChangesInternalMethod( - lifecycleHooks: GenConfig, logBindingUpdate: boolean): o.ClassMethod { +function createDetectChangesInternalMethod(builder: DirectiveWrapperBuilder) { const changedVar = o.variable('changed'); const stmts: o.Statement[] = [ changedVar.set(o.THIS_EXPR.prop(CHANGED_FIELD_NAME)).toDeclStmt(), @@ -105,14 +115,14 @@ function detectChangesInternalMethod( ]; const lifecycleStmts: o.Statement[] = []; - if (lifecycleHooks.genChanges) { + if (builder.genChanges) { const onChangesStmts: o.Statement[] = []; - if (lifecycleHooks.ngOnChanges) { + if (builder.ngOnChanges) { onChangesStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME) .callMethod('ngOnChanges', [o.THIS_EXPR.prop(CHANGES_FIELD_NAME)]) .toStmt()); } - if (logBindingUpdate) { + if (builder.compilerConfig.logBindingUpdate) { onChangesStmts.push( o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfoForChanges)) .callFn( @@ -123,12 +133,12 @@ function detectChangesInternalMethod( lifecycleStmts.push(new o.IfStmt(changedVar, onChangesStmts)); } - if (lifecycleHooks.ngOnInit) { + if (builder.ngOnInit) { lifecycleStmts.push(new o.IfStmt( VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)), [o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()])); } - if (lifecycleHooks.ngDoCheck) { + if (builder.ngDoCheck) { lifecycleStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngDoCheck', []).toStmt()); } if (lifecycleStmts.length > 0) { @@ -136,7 +146,7 @@ function detectChangesInternalMethod( } stmts.push(new o.ReturnStatement(changedVar)); - return new o.ClassMethod( + builder.methods.push(new o.ClassMethod( 'detectChangesInternal', [ new o.FnParam( @@ -144,16 +154,22 @@ function detectChangesInternalMethod( new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE), new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE), ], - stmts, o.BOOL_TYPE); + stmts, o.BOOL_TYPE)); } -function checkInputMethod( - input: string, fieldExpr: o.ReadPropExpr, lifecycleHooks: GenConfig): o.ClassMethod { +function createCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) { + const fieldName = `_${input}`; + const fieldExpr = o.THIS_EXPR.prop(fieldName); + // private is fine here as no child view will reference the cached value... + builder.fields.push(new o.ClassField(fieldName, null, [o.StmtModifier.Private])); + builder.ctorStmts.push(o.THIS_EXPR.prop(fieldName) + .set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED))) + .toStmt()); var onChangeStatements: o.Statement[] = [ o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(true)).toStmt(), o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).prop(input).set(CURR_VALUE_VAR).toStmt(), ]; - if (lifecycleHooks.genChanges) { + if (builder.genChanges) { onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME) .key(o.literal(input)) .set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange)) @@ -168,19 +184,12 @@ function checkInputMethod( .callFn([THROW_ON_CHANGE_VAR, fieldExpr, CURR_VALUE_VAR])), onChangeStatements), ]; - return new o.ClassMethod( + builder.methods.push(new o.ClassMethod( `check_${input}`, [ new o.FnParam(CURR_VALUE_VAR.name, o.DYNAMIC_TYPE), new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE), new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE), ], - methodBody); + methodBody)); } - -interface GenConfig { - genChanges: boolean; - ngOnChanges: boolean; - ngOnInit: boolean; - ngDoCheck: boolean; -} \ No newline at end of file diff --git a/modules/@angular/compiler/src/ng_module_compiler.ts b/modules/@angular/compiler/src/ng_module_compiler.ts index 3bb1f49b1c..fbc4abb7d0 100644 --- a/modules/@angular/compiler/src/ng_module_compiler.ts +++ b/modules/@angular/compiler/src/ng_module_compiler.ts @@ -12,6 +12,7 @@ import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleM import {createDiTokenExpression} from './compiler_util/identifier_util'; import {isPresent} from './facade/lang'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers'; +import {ClassBuilder, createClassStmt} from './output/class_builder'; import * as o from './output/output_ast'; import {convertValueToOutputAst} from './output/value_util'; import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util'; @@ -82,13 +83,15 @@ export class NgModuleCompiler { } } -class _InjectorBuilder { +class _InjectorBuilder implements ClassBuilder { private _tokens: CompileTokenMetadata[] = []; private _instances = new Map(); - private _fields: o.ClassField[] = []; + fields: o.ClassField[] = []; private _createStmts: o.Statement[] = []; private _destroyStmts: o.Statement[] = []; - private _getters: o.ClassGetter[] = []; + getters: o.ClassGetter[] = []; + methods: o.ClassMethod[] = []; + ctorStmts: o.Statement[] = []; constructor( private _ngModuleMeta: CompileNgModuleMetadata, @@ -136,26 +139,23 @@ class _InjectorBuilder { ), ]; - var ctor = new o.ClassMethod( - null, - [new o.FnParam( - InjectorProps.parent.name, o.importType(resolveIdentifier(Identifiers.Injector)))], - [o.SUPER_EXPR - .callFn([ - o.variable(InjectorProps.parent.name), - o.literalArr(this._entryComponentFactories.map( - (componentFactory) => o.importExpr(componentFactory))), - o.literalArr(this._bootstrapComponentFactories.map( - (componentFactory) => o.importExpr(componentFactory))) - ]) - .toStmt()]); - + var parentArgs = [ + o.variable(InjectorProps.parent.name), + o.literalArr( + this._entryComponentFactories.map((componentFactory) => o.importExpr(componentFactory))), + o.literalArr(this._bootstrapComponentFactories.map( + (componentFactory) => o.importExpr(componentFactory))) + ]; var injClassName = `${this._ngModuleMeta.type.name}Injector`; - return new o.ClassStmt( - injClassName, o.importExpr( - resolveIdentifier(Identifiers.NgModuleInjector), - [o.importType(this._ngModuleMeta.type)]), - this._fields, this._getters, ctor, methods); + return createClassStmt({ + name: injClassName, + ctorParams: [new o.FnParam( + InjectorProps.parent.name, o.importType(resolveIdentifier(Identifiers.Injector)))], + parent: o.importExpr( + resolveIdentifier(Identifiers.NgModuleInjector), [o.importType(this._ngModuleMeta.type)]), + parentArgs: parentArgs, + builders: [{methods: methods}, this] + }); } private _getProviderValue(provider: CompileProviderMetadata): o.Expression { @@ -194,11 +194,11 @@ class _InjectorBuilder { type = o.DYNAMIC_TYPE; } if (isEager) { - this._fields.push(new o.ClassField(propName, type)); + this.fields.push(new o.ClassField(propName, type)); this._createStmts.push(o.THIS_EXPR.prop(propName).set(resolvedProviderValueExpr).toStmt()); } else { var internalField = `_${propName}`; - this._fields.push(new o.ClassField(internalField, type)); + this.fields.push(new o.ClassField(internalField, type)); // Note: Equals is important for JS so that it also checks the undefined case! var getterStmts = [ new o.IfStmt( @@ -206,7 +206,7 @@ class _InjectorBuilder { [o.THIS_EXPR.prop(internalField).set(resolvedProviderValueExpr).toStmt()]), new o.ReturnStatement(o.THIS_EXPR.prop(internalField)) ]; - this._getters.push(new o.ClassGetter(propName, getterStmts, type)); + this.getters.push(new o.ClassGetter(propName, getterStmts, type)); } return o.THIS_EXPR.prop(propName); } diff --git a/modules/@angular/compiler/src/output/class_builder.ts b/modules/@angular/compiler/src/output/class_builder.ts new file mode 100644 index 0000000000..8524e4b184 --- /dev/null +++ b/modules/@angular/compiler/src/output/class_builder.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as o from './output_ast'; + +/** + * Create a new class stmts based on the given data. + */ +export function createClassStmt(config: { + name: string, + parent?: o.Expression, + parentArgs?: o.Expression[], + ctorParams?: o.FnParam[], + builders: ClassBuilderPart | ClassBuilderPart[], modifiers?: o.StmtModifier[] +}): o.ClassStmt { + const parentArgs = config.parentArgs || []; + const superCtorStmts = config.parent ? [o.SUPER_EXPR.callFn(parentArgs).toStmt()] : []; + const builder = + concatClassBuilderParts(Array.isArray(config.builders) ? config.builders : [config.builders]); + const ctor = + new o.ClassMethod(null, config.ctorParams || [], superCtorStmts.concat(builder.ctorStmts)); + + return new o.ClassStmt( + config.name, config.parent, builder.fields, builder.getters, ctor, builder.methods, + config.modifiers || []) +} + +function concatClassBuilderParts(builders: ClassBuilderPart[]): ClassBuilder { + return { + fields: [].concat(...builders.map(builder => builder.fields || [])), + methods: [].concat(...builders.map(builder => builder.methods || [])), + getters: [].concat(...builders.map(builder => builder.getters || [])), + ctorStmts: [].concat(...builders.map(builder => builder.ctorStmts || [])), + }; +} + +/** + * Collects data for a generated class. + */ +export interface ClassBuilderPart { + fields?: o.ClassField[]; + methods?: o.ClassMethod[]; + getters?: o.ClassGetter[]; + ctorStmts?: o.Statement[]; +} + +/** + * Collects data for a generated class. + */ +export interface ClassBuilder extends ClassBuilderPart { + fields: o.ClassField[]; + methods: o.ClassMethod[]; + getters: o.ClassGetter[]; + ctorStmts: o.Statement[]; +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/view_compiler/compile_view.ts b/modules/@angular/compiler/src/view_compiler/compile_view.ts index b9e9dcae79..de64112ccb 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_view.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_view.ts @@ -12,6 +12,7 @@ import {CompilerConfig} from '../config'; import {MapWrapper} from '../facade/collection'; import {isPresent} from '../facade/lang'; import {Identifiers, resolveIdentifier} from '../identifiers'; +import {ClassBuilder} from '../output/class_builder'; import * as o from '../output/output_ast'; import {ViewType} from '../private_import_core'; @@ -24,7 +25,7 @@ import {EventHandlerVars} from './constants'; import {NameResolver} from './expression_converter'; import {createPureProxy, getPropertyInView, getViewFactoryName} from './util'; -export class CompileView implements NameResolver { +export class CompileView implements NameResolver, ClassBuilder { public viewType: ViewType; public viewQueries: Map; @@ -34,7 +35,6 @@ export class CompileView implements NameResolver { public bindings: CompileBinding[] = []; - public classStatements: o.Statement[] = []; public createMethod: CompileMethod; public animationBindingsMethod: CompileMethod; public injectorGetMethod: CompileMethod; @@ -47,8 +47,9 @@ export class CompileView implements NameResolver { public afterViewLifecycleCallbacksMethod: CompileMethod; public destroyMethod: CompileMethod; public detachMethod: CompileMethod; - public eventHandlerMethods: o.ClassMethod[] = []; + public methods: o.ClassMethod[] = []; + public ctorStmts: o.Statement[] = []; public fields: o.ClassField[] = []; public getters: o.ClassGetter[] = []; public disposables: o.Expression[] = []; diff --git a/modules/@angular/compiler/src/view_compiler/event_binder.ts b/modules/@angular/compiler/src/view_compiler/event_binder.ts index 70910ad579..78491b745f 100644 --- a/modules/@angular/compiler/src/view_compiler/event_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/event_binder.ts @@ -92,7 +92,7 @@ export class CompileEventListener { .concat(this._method.finish()) .concat([new o.ReturnStatement(resultExpr)]); // private is fine here as no child view will reference the event handler... - this.compileElement.view.eventHandlerMethods.push(new o.ClassMethod( + this.compileElement.view.methods.push(new o.ClassMethod( this._methodName, [this._eventParam], stmts, o.BOOL_TYPE, [o.StmtModifier.Private])); } diff --git a/modules/@angular/compiler/src/view_compiler/view_builder.ts b/modules/@angular/compiler/src/view_compiler/view_builder.ts index 3a403ff442..1ef321597e 100644 --- a/modules/@angular/compiler/src/view_compiler/view_builder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_builder.ts @@ -12,6 +12,7 @@ import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadat import {createDiTokenExpression, createFastArray} from '../compiler_util/identifier_util'; import {isPresent} from '../facade/lang'; import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers'; +import {createClassStmt} from '../output/class_builder'; import * as o from '../output/output_ast'; import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast'; @@ -432,9 +433,6 @@ function createViewClass( if (view.genConfig.genDebugInfo) { superConstructorArgs.push(nodeDebugInfosVar); } - var viewConstructor = new o.ClassMethod( - null, viewConstructorArgs, [o.SUPER_EXPR.callFn(superConstructorArgs).toStmt()]); - var viewMethods = [ new o.ClassMethod( 'createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)], @@ -455,12 +453,16 @@ function createViewClass( new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()), new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish()), new o.ClassMethod('detachInternal', [], view.detachMethod.finish()) - ].concat(view.eventHandlerMethods); + ].filter((method) => method.body.length > 0); var superClass = view.genConfig.genDebugInfo ? Identifiers.DebugAppView : Identifiers.AppView; - var viewClass = new o.ClassStmt( - view.className, o.importExpr(resolveIdentifier(superClass), [getContextType(view)]), - view.fields, view.getters, viewConstructor, - viewMethods.filter((method) => method.body.length > 0)); + + var viewClass = createClassStmt({ + name: view.className, + parent: o.importExpr(resolveIdentifier(superClass), [getContextType(view)]), + parentArgs: superConstructorArgs, + ctorParams: viewConstructorArgs, + builders: [{methods: viewMethods}, view] + }); return viewClass; }