refactor(compiler): introduce `ClassBuilder`.

Part of #11683
This commit is contained in:
Tobias Bosch 2016-10-21 13:37:51 -07:00 committed by vsavkin
parent faa3478514
commit cb7643ccea
6 changed files with 160 additions and 88 deletions

View File

@ -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;
}

View File

@ -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<any, o.Expression>();
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);
}

View File

@ -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[];
}

View File

@ -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<any, CompileQuery[]>;
@ -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[] = [];

View File

@ -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]));
}

View File

@ -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;
}