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

View File

@ -13,6 +13,7 @@ import * as ts from 'typescript';
import * as api from '../transformers/api';
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from './annotations';
import {BaseDefDecoratorHandler} from './annotations/src/base_def';
import {FactoryGenerator, FactoryInfo, GeneratedFactoryHostWrapper, generatedFactoryTransform} from './factories';
import {TypeScriptReflectionHost} from './metadata';
import {FileResourceLoader, HostResourceLoader} from './resource_loader';
@ -169,6 +170,7 @@ export class NgtscProgram implements api.Program {
// Set up the IvyCompilation, which manages state for the Ivy transformer.
const handlers = [
new BaseDefDecoratorHandler(checker, this.reflector),
new ComponentDecoratorHandler(
checker, this.reflector, scopeRegistry, this.isCore, this.resourceLoader),
new DirectiveDecoratorHandler(checker, this.reflector, scopeRegistry, this.isCore),

View File

@ -20,12 +20,12 @@ import {Decorator} from '../../host';
* responsible for extracting the information required to perform compilation from the decorators
* and Typescript source, invoking the decorator compiler, and returning the result.
*/
export interface DecoratorHandler<A> {
export interface DecoratorHandler<A, M> {
/**
* Scan a set of reflected decorators and determine if this handler is responsible for compilation
* of one of them.
*/
detect(decorator: Decorator[]): Decorator|undefined;
detect(node: ts.Declaration, decorators: Decorator[]|null): M|undefined;
/**
@ -34,14 +34,14 @@ export interface DecoratorHandler<A> {
* `preAnalyze` is optional and is not guaranteed to be called through all compilation flows. It
* will only be called if asynchronicity is supported in the CompilerHost.
*/
preanalyze?(node: ts.Declaration, decorator: Decorator): Promise<void>|undefined;
preanalyze?(node: ts.Declaration, metadata: M): Promise<void>|undefined;
/**
* Perform analysis on the decorator/class combination, producing instructions for compilation
* if successful, or an array of diagnostic messages if the analysis fails or the decorator
* isn't valid.
*/
analyze(node: ts.Declaration, decorator: Decorator): AnalysisOutput<A>;
analyze(node: ts.Declaration, metadata: M): AnalysisOutput<A>;
/**
* Generate a description of the field which should be added to the class, including any

View File

@ -19,10 +19,10 @@ import {DtsFileTransformer} from './declaration';
* Record of an adapter which decided to emit a static field, and the analysis it performed to
* prepare for that operation.
*/
interface EmitFieldOperation<T> {
adapter: DecoratorHandler<T>;
analysis: AnalysisOutput<T>;
decorator: Decorator;
interface EmitFieldOperation<A, M> {
adapter: DecoratorHandler<A, M>;
analysis: AnalysisOutput<A>;
metadata: M;
}
/**
@ -36,7 +36,7 @@ export class IvyCompilation {
* Tracks classes which have been analyzed and found to have an Ivy decorator, and the
* information recorded about them for later compilation.
*/
private analysis = new Map<ts.Declaration, EmitFieldOperation<any>>();
private analysis = new Map<ts.Declaration, EmitFieldOperation<any, any>>();
/**
* Tracks factory information which needs to be generated.
@ -59,7 +59,7 @@ export class IvyCompilation {
* `null` in most cases.
*/
constructor(
private handlers: DecoratorHandler<any>[], private checker: ts.TypeChecker,
private handlers: DecoratorHandler<any, any>[], private checker: ts.TypeChecker,
private reflector: ReflectionHost, private coreImportsFrom: ts.SourceFile|null,
private sourceToFactorySymbols: Map<string, Set<string>>|null) {}
@ -78,15 +78,14 @@ export class IvyCompilation {
const analyzeClass = (node: ts.Declaration): void => {
// The first step is to reflect the decorators.
const decorators = this.reflector.getDecoratorsOfDeclaration(node);
if (decorators === null) {
return;
}
const classDecorators = this.reflector.getDecoratorsOfDeclaration(node);
// Look through the DecoratorHandlers to see if any are relevant.
this.handlers.forEach(adapter => {
// An adapter is relevant if it matches one of the decorators on the class.
const decorator = adapter.detect(decorators);
if (decorator === undefined) {
const metadata = adapter.detect(node, classDecorators);
if (metadata === undefined) {
return;
}
@ -97,14 +96,15 @@ export class IvyCompilation {
throw new Error('TODO.Diagnostic: Class has multiple Angular decorators.');
}
// Run analysis on the decorator. This will produce either diagnostics, an
// Run analysis on the metadata. This will produce either diagnostics, an
// analysis result, or both.
const analysis = adapter.analyze(node, decorator);
const analysis = adapter.analyze(node, metadata);
if (analysis.analysis !== undefined) {
this.analysis.set(node, {
adapter,
analysis: analysis.analysis, decorator,
analysis: analysis.analysis,
metadata: metadata,
});
}
@ -119,7 +119,7 @@ export class IvyCompilation {
};
if (preanalyze && adapter.preanalyze !== undefined) {
const preanalysis = adapter.preanalyze(node, decorator);
const preanalysis = adapter.preanalyze(node, metadata);
if (preanalysis !== undefined) {
promises.push(preanalysis.then(() => completeAnalysis()));
} else {
@ -185,7 +185,7 @@ export class IvyCompilation {
return undefined;
}
return this.analysis.get(original) !.decorator;
return this.analysis.get(original) !.metadata;
}
/**