feat(ivy): Add AOT handling for bare classes with Input and Output decorators (#25367)

PR Close #25367
This commit is contained in:
Ben Lesh
2018-08-07 12:04:39 -07:00
parent 26066f282e
commit a0a29fdd27
22 changed files with 483 additions and 60 deletions

View File

@ -7,6 +7,7 @@
*/
export {ResourceLoader} from './src/api';
export {BaseDefDecoratorHandler} from './src/base_def';
export {ComponentDecoratorHandler} from './src/component';
export {DirectiveDecoratorHandler} from './src/directive';
export {InjectableDecoratorHandler} from './src/injectable';

View File

@ -0,0 +1,120 @@
/**
* @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 {R3BaseRefMetaData, compileBaseDefFromMetadata} from '@angular/compiler';
import * as ts from 'typescript';
import {ClassMember, Decorator, ReflectionHost} from '../../host';
import {staticallyResolve} from '../../metadata';
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
import {isAngularCore} from './util';
function containsNgTopLevelDecorator(decorators: Decorator[] | null): boolean {
if (!decorators) {
return false;
}
return decorators.find(
decorator => (decorator.name === 'Component' || decorator.name === 'Directive' ||
decorator.name === 'NgModule') &&
isAngularCore(decorator)) !== undefined;
}
export class BaseDefDecoratorHandler implements
DecoratorHandler<R3BaseRefMetaData, R3BaseRefDecoratorDetection> {
constructor(private checker: ts.TypeChecker, private reflector: ReflectionHost, ) {}
detect(node: ts.ClassDeclaration, decorators: Decorator[]|null): R3BaseRefDecoratorDetection
|undefined {
if (containsNgTopLevelDecorator(decorators)) {
// If the class is already decorated by @Component or @Directive let that
// DecoratorHandler handle this. BaseDef is unnecessary.
return undefined;
}
let result: R3BaseRefDecoratorDetection|undefined = undefined;
this.reflector.getMembersOfClass(node).forEach(property => {
const {decorators} = property;
if (decorators) {
for (const decorator of decorators) {
const decoratorName = decorator.name;
if (decoratorName === 'Input' && isAngularCore(decorator)) {
result = result || {};
const inputs = result.inputs = result.inputs || [];
inputs.push({decorator, property});
} else if (decoratorName === 'Output' && isAngularCore(decorator)) {
result = result || {};
const outputs = result.outputs = result.outputs || [];
outputs.push({decorator, property});
}
}
}
});
return result;
}
analyze(node: ts.ClassDeclaration, metadata: R3BaseRefDecoratorDetection):
AnalysisOutput<R3BaseRefMetaData> {
const analysis: R3BaseRefMetaData = {};
if (metadata.inputs) {
const inputs = analysis.inputs = {} as{[key: string]: string | [string, string]};
metadata.inputs.forEach(({decorator, property}) => {
const propName = property.name;
const args = decorator.args;
let value: string|[string, string];
if (args && args.length > 0) {
const resolvedValue = staticallyResolve(args[0], this.reflector, this.checker);
if (typeof resolvedValue !== 'string') {
throw new TypeError('Input alias does not resolve to a string value');
}
value = [resolvedValue, propName];
} else {
value = propName;
}
inputs[propName] = value;
});
}
if (metadata.outputs) {
const outputs = analysis.outputs = {} as{[key: string]: string};
metadata.outputs.forEach(({decorator, property}) => {
const propName = property.name;
const args = decorator.args;
let value: string;
if (args && args.length > 0) {
const resolvedValue = staticallyResolve(args[0], this.reflector, this.checker);
if (typeof resolvedValue !== 'string') {
throw new TypeError('Output alias does not resolve to a string value');
}
value = resolvedValue;
} else {
value = propName;
}
outputs[propName] = value;
});
}
return {analysis};
}
compile(node: ts.Declaration, analysis: R3BaseRefMetaData): CompileResult[]|CompileResult {
const {expression, type} = compileBaseDefFromMetadata(analysis);
return {
name: 'ngBaseDef',
initializer: expression, type,
statements: [],
};
}
}
export interface R3BaseRefDecoratorDetection {
inputs?: Array<{property: ClassMember, decorator: Decorator}>;
outputs?: Array<{property: ClassMember, decorator: Decorator}>;
}

View File

@ -24,7 +24,7 @@ const EMPTY_MAP = new Map<string, Expression>();
/**
* `DecoratorHandler` which handles the `@Component` annotation.
*/
export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMetadata> {
export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMetadata, Decorator> {
constructor(
private checker: ts.TypeChecker, private reflector: ReflectionHost,
private scopeRegistry: SelectorScopeRegistry, private isCore: boolean,
@ -33,7 +33,10 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
detect(decorators: Decorator[]): Decorator|undefined {
detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined {
if (!decorators) {
return undefined;
}
return decorators.find(
decorator => decorator.name === 'Component' && (this.isCore || isAngularCore(decorator)));
}

View File

@ -18,12 +18,15 @@ import {getConstructorDependencies, isAngularCore, unwrapExpression, unwrapForwa
const EMPTY_OBJECT: {[key: string]: string} = {};
export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMetadata> {
export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMetadata, Decorator> {
constructor(
private checker: ts.TypeChecker, private reflector: ReflectionHost,
private scopeRegistry: SelectorScopeRegistry, private isCore: boolean) {}
detect(decorators: Decorator[]): Decorator|undefined {
detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined {
if (!decorators) {
return undefined;
}
return decorators.find(
decorator => decorator.name === 'Directive' && (this.isCore || isAngularCore(decorator)));
}

View File

@ -19,11 +19,15 @@ import {getConstructorDependencies, isAngularCore} from './util';
/**
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
*/
export class InjectableDecoratorHandler implements DecoratorHandler<R3InjectableMetadata> {
export class InjectableDecoratorHandler implements
DecoratorHandler<R3InjectableMetadata, Decorator> {
constructor(private reflector: ReflectionHost, private isCore: boolean) {}
detect(decorator: Decorator[]): Decorator|undefined {
return decorator.find(
detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined {
if (!decorators) {
return undefined;
}
return decorators.find(
decorator => decorator.name === 'Injectable' && (this.isCore || isAngularCore(decorator)));
}

View File

@ -26,12 +26,15 @@ export interface NgModuleAnalysis {
*
* TODO(alxhub): handle injector side of things as well.
*/
export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalysis> {
export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalysis, Decorator> {
constructor(
private checker: ts.TypeChecker, private reflector: ReflectionHost,
private scopeRegistry: SelectorScopeRegistry, private isCore: boolean) {}
detect(decorators: Decorator[]): Decorator|undefined {
detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined {
if (!decorators) {
return undefined;
}
return decorators.find(
decorator => decorator.name === 'NgModule' && (this.isCore || isAngularCore(decorator)));
}

View File

@ -16,13 +16,16 @@ import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
import {SelectorScopeRegistry} from './selector_scope';
import {getConstructorDependencies, isAngularCore, unwrapExpression} from './util';
export class PipeDecoratorHandler implements DecoratorHandler<R3PipeMetadata> {
export class PipeDecoratorHandler implements DecoratorHandler<R3PipeMetadata, Decorator> {
constructor(
private checker: ts.TypeChecker, private reflector: ReflectionHost,
private scopeRegistry: SelectorScopeRegistry, private isCore: boolean) {}
detect(decorator: Decorator[]): Decorator|undefined {
return decorator.find(
detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined {
if (!decorators) {
return undefined;
}
return decorators.find(
decorator => decorator.name === 'Pipe' && (this.isCore || isAngularCore(decorator)));
}