feat(ivy): Add AOT handling for bare classes with Input and Output decorators (#25367)
PR Close #25367
This commit is contained in:
parent
26066f282e
commit
a0a29fdd27
|
@ -9,7 +9,7 @@ import {ConstantPool} from '@angular/compiler';
|
|||
import * as fs from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from '../../ngtsc/annotations';
|
||||
import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from '../../ngtsc/annotations';
|
||||
import {Decorator} from '../../ngtsc/host';
|
||||
import {CompileResult, DecoratorHandler} from '../../ngtsc/transform';
|
||||
|
||||
|
@ -18,8 +18,8 @@ import {ParsedClass} from './parsing/parsed_class';
|
|||
import {ParsedFile} from './parsing/parsed_file';
|
||||
import {isDefined} from './utils';
|
||||
|
||||
export interface AnalyzedClass<T = any> extends ParsedClass {
|
||||
handler: DecoratorHandler<T>;
|
||||
export interface AnalyzedClass<A = any, M = any> extends ParsedClass {
|
||||
handler: DecoratorHandler<A, M>;
|
||||
analysis: any;
|
||||
diagnostics?: ts.Diagnostic[];
|
||||
compilation: CompileResult[];
|
||||
|
@ -31,9 +31,9 @@ export interface AnalyzedFile {
|
|||
constantPool: ConstantPool;
|
||||
}
|
||||
|
||||
export interface MatchingHandler<T> {
|
||||
handler: DecoratorHandler<T>;
|
||||
decorator: Decorator;
|
||||
export interface MatchingHandler<A, M> {
|
||||
handler: DecoratorHandler<A, M>;
|
||||
match: M;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,7 +46,8 @@ export class FileResourceLoader implements ResourceLoader {
|
|||
export class Analyzer {
|
||||
resourceLoader = new FileResourceLoader();
|
||||
scopeRegistry = new SelectorScopeRegistry(this.typeChecker, this.host);
|
||||
handlers: DecoratorHandler<any>[] = [
|
||||
handlers: DecoratorHandler<any, any>[] = [
|
||||
new BaseDefDecoratorHandler(this.typeChecker, this.host),
|
||||
new ComponentDecoratorHandler(
|
||||
this.typeChecker, this.host, this.scopeRegistry, false, this.resourceLoader),
|
||||
new DirectiveDecoratorHandler(this.typeChecker, this.host, this.scopeRegistry, false),
|
||||
|
@ -76,20 +77,23 @@ export class Analyzer {
|
|||
|
||||
protected analyzeClass(file: ts.SourceFile, pool: ConstantPool, clazz: ParsedClass): AnalyzedClass
|
||||
|undefined {
|
||||
const matchingHandlers =
|
||||
this.handlers.map(handler => ({handler, decorator: handler.detect(clazz.decorators)}))
|
||||
.filter(isMatchingHandler);
|
||||
const matchingHandlers = this.handlers
|
||||
.map(handler => ({
|
||||
handler,
|
||||
match: handler.detect(clazz.declaration, clazz.decorators),
|
||||
}))
|
||||
.filter(isMatchingHandler);
|
||||
|
||||
if (matchingHandlers.length > 1) {
|
||||
throw new Error('TODO.Diagnostic: Class has multiple Angular decorators.');
|
||||
}
|
||||
|
||||
if (matchingHandlers.length == 0) {
|
||||
if (matchingHandlers.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const {handler, decorator} = matchingHandlers[0];
|
||||
const {analysis, diagnostics} = handler.analyze(clazz.declaration, decorator);
|
||||
const {handler, match} = matchingHandlers[0];
|
||||
const {analysis, diagnostics} = handler.analyze(clazz.declaration, match);
|
||||
let compilation = handler.compile(clazz.declaration, analysis, pool);
|
||||
if (!Array.isArray(compilation)) {
|
||||
compilation = [compilation];
|
||||
|
@ -98,6 +102,7 @@ export class Analyzer {
|
|||
}
|
||||
}
|
||||
|
||||
function isMatchingHandler<T>(handler: Partial<MatchingHandler<T>>): handler is MatchingHandler<T> {
|
||||
return !!handler.decorator;
|
||||
function isMatchingHandler<A, M>(handler: Partial<MatchingHandler<A, M>>):
|
||||
handler is MatchingHandler<A, M> {
|
||||
return !!handler.match;
|
||||
}
|
|
@ -28,14 +28,18 @@ const TEST_PROGRAM = {
|
|||
};
|
||||
|
||||
function createTestHandler() {
|
||||
const handler = jasmine.createSpyObj<DecoratorHandler<any>>('TestDecoratorHandler', [
|
||||
const handler = jasmine.createSpyObj<DecoratorHandler<any, any>>('TestDecoratorHandler', [
|
||||
'detect',
|
||||
'analyze',
|
||||
'compile',
|
||||
]);
|
||||
// Only detect the Component decorator
|
||||
handler.detect.and.callFake(
|
||||
(decorators: Decorator[]) => decorators.find(d => d.name === 'Component'));
|
||||
handler.detect.and.callFake((node: ts.Declaration, decorators: Decorator[]) => {
|
||||
if (!decorators) {
|
||||
return undefined;
|
||||
}
|
||||
return decorators.find(d => d.name === 'Component');
|
||||
});
|
||||
// The "test" analysis is just the name of the decorator being analyzed
|
||||
handler.analyze.and.callFake(
|
||||
((decl: ts.Declaration, dec: Decorator) => ({analysis: dec.name, diagnostics: null})));
|
||||
|
@ -69,7 +73,7 @@ function createParsedFile(program: ts.Program) {
|
|||
describe('Analyzer', () => {
|
||||
describe('analyzeFile()', () => {
|
||||
let program: ts.Program;
|
||||
let testHandler: jasmine.SpyObj<DecoratorHandler<any>>;
|
||||
let testHandler: jasmine.SpyObj<DecoratorHandler<any, any>>;
|
||||
let result: AnalyzedFile;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -87,9 +91,9 @@ describe('Analyzer', () => {
|
|||
|
||||
it('should call detect on the decorator handlers with each class from the parsed file', () => {
|
||||
expect(testHandler.detect).toHaveBeenCalledTimes(2);
|
||||
expect(testHandler.detect.calls.allArgs()[0][0]).toEqual([jasmine.objectContaining(
|
||||
expect(testHandler.detect.calls.allArgs()[0][1]).toEqual([jasmine.objectContaining(
|
||||
{name: 'Component'})]);
|
||||
expect(testHandler.detect.calls.allArgs()[1][0]).toEqual([jasmine.objectContaining(
|
||||
expect(testHandler.detect.calls.allArgs()[1][1]).toEqual([jasmine.objectContaining(
|
||||
{name: 'Injectable'})]);
|
||||
});
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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}>;
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,7 +14,7 @@ import * as ts from 'typescript';
|
|||
|
||||
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
|
||||
import {compareVersions} from '../diagnostics/typescript_version';
|
||||
import {MetadataCollector, ModuleMetadata, createBundleIndexHost} from '../metadata/index';
|
||||
import {MetadataCollector, ModuleMetadata, createBundleIndexHost} from '../metadata';
|
||||
import {NgtscProgram} from '../ngtsc/program';
|
||||
|
||||
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, DiagnosticMessageChain, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback, TsMergeEmitResultsCallback} from './api';
|
||||
|
|
|
@ -1837,4 +1837,226 @@ describe('compiler compliance', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('inherited bare classes', () => {
|
||||
it('should add ngBaseDef if one or more @Input is present', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule, Input} from '@angular/core';
|
||||
export class BaseClass {
|
||||
@Input()
|
||||
input1 = 'test';
|
||||
|
||||
@Input('alias2')
|
||||
input2 = 'whatever';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div>{{input1}} {{input2}}</div>\`
|
||||
})
|
||||
export class MyComponent extends BaseClass {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MyComponent]
|
||||
})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
const expectedOutput = `
|
||||
// ...
|
||||
BaseClass.ngBaseDef = i0.ɵdefineBase({
|
||||
inputs: {
|
||||
input1: "input1",
|
||||
input2: ["alias2", "input2"]
|
||||
}
|
||||
});
|
||||
// ...
|
||||
`;
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, expectedOutput, 'Invalid base definition');
|
||||
});
|
||||
|
||||
it('should add ngBaseDef if one or more @Output is present', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule, Output, EventEmitter} from '@angular/core';
|
||||
export class BaseClass {
|
||||
@Output()
|
||||
output1 = new EventEmitter<string>();
|
||||
|
||||
@Output()
|
||||
output2 = new EventEmitter<string>();
|
||||
|
||||
clicked() {
|
||||
this.output1.emit('test');
|
||||
this.output2.emit('test');
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<button (click)="clicked()">Click Me</button>\`
|
||||
})
|
||||
export class MyComponent extends BaseClass {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MyComponent]
|
||||
})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
const expectedOutput = `
|
||||
// ...
|
||||
BaseClass.ngBaseDef = i0.ɵdefineBase({
|
||||
outputs: {
|
||||
output1: "output1",
|
||||
output2: "output2"
|
||||
}
|
||||
});
|
||||
// ...
|
||||
`;
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, expectedOutput, 'Invalid base definition');
|
||||
});
|
||||
|
||||
it('should add ngBaseDef if a mixture of @Input and @Output props are present', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule, Input, Output, EventEmitter} from '@angular/core';
|
||||
export class BaseClass {
|
||||
@Output()
|
||||
output1 = new EventEmitter<string>();
|
||||
|
||||
@Output()
|
||||
output2 = new EventEmitter<string>();
|
||||
|
||||
@Input()
|
||||
input1 = 'test';
|
||||
|
||||
@Input('whatever')
|
||||
input2 = 'blah';
|
||||
|
||||
clicked() {
|
||||
this.output1.emit('test');
|
||||
this.output2.emit('test');
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<button (click)="clicked()">Click Me</button>\`
|
||||
})
|
||||
export class MyComponent extends BaseClass {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MyComponent]
|
||||
})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
const expectedOutput = `
|
||||
// ...
|
||||
BaseClass.ngBaseDef = i0.ɵdefineBase({
|
||||
inputs: {
|
||||
input1: "input1",
|
||||
input2: ["whatever", "input2"]
|
||||
},
|
||||
outputs: {
|
||||
output1: "output1",
|
||||
output2: "output2"
|
||||
}
|
||||
});
|
||||
// ...
|
||||
`;
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, expectedOutput, 'Invalid base definition');
|
||||
});
|
||||
|
||||
it('should NOT add ngBaseDef if @Component is present', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule, Output, EventEmitter} from '@angular/core';
|
||||
@Component({
|
||||
selector: 'whatever',
|
||||
template: '<button (click)="clicked()">Click {{input1}}</button>'
|
||||
})
|
||||
export class BaseClass {
|
||||
@Output()
|
||||
output1 = new EventEmitter<string>();
|
||||
|
||||
@Input()
|
||||
input1 = 'whatever';
|
||||
|
||||
clicked() {
|
||||
this.output1.emit('test');
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div>What is this developer doing?</div>\`
|
||||
})
|
||||
export class MyComponent extends BaseClass {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MyComponent]
|
||||
})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
const result = compile(files, angularFiles);
|
||||
expect(result.source).not.toContain('ngBaseDef');
|
||||
});
|
||||
|
||||
it('should NOT add ngBaseDef if @Directive is present', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Directive, NgModule, Output, EventEmitter} from '@angular/core';
|
||||
@Directive({
|
||||
selector: 'whatever',
|
||||
})
|
||||
export class BaseClass {
|
||||
@Output()
|
||||
output1 = new EventEmitter<string>();
|
||||
|
||||
@Input()
|
||||
input1 = 'whatever';
|
||||
|
||||
clicked() {
|
||||
this.output1.emit('test');
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: '<button (click)="clicked()">Click {{input1}}</button>'
|
||||
})
|
||||
export class MyComponent extends BaseClass {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MyComponent]
|
||||
})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
const result = compile(files, angularFiles);
|
||||
expect(result.source).not.toContain('ngBaseDef');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -87,5 +87,5 @@ export {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata
|
|||
export {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler';
|
||||
export {makeBindingParser, parseTemplate} from './render3/view/template';
|
||||
export {R3Reference} from './render3/util';
|
||||
export {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings} from './render3/view/compiler';
|
||||
export {compileBaseDefFromMetadata, R3BaseRefMetaData, compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings} from './render3/view/compiler';
|
||||
// This file only reexports content of the `src` folder. Keep it that way.
|
|
@ -115,6 +115,13 @@ export class Identifiers {
|
|||
|
||||
static directiveInject: o.ExternalReference = {name: 'ɵdirectiveInject', moduleName: CORE};
|
||||
|
||||
static defineBase: o.ExternalReference = {name: 'ɵdefineBase', moduleName: CORE};
|
||||
|
||||
static BaseDef: o.ExternalReference = {
|
||||
name: 'ɵBaseDef',
|
||||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static defineComponent: o.ExternalReference = {name: 'ɵdefineComponent', moduleName: CORE};
|
||||
|
||||
static ComponentDef: o.ExternalReference = {
|
||||
|
|
|
@ -106,6 +106,42 @@ export function compileDirectiveFromMetadata(
|
|||
return {expression, type, statements};
|
||||
}
|
||||
|
||||
export interface R3BaseRefMetaData {
|
||||
inputs?: {[key: string]: string | [string, string]};
|
||||
outputs?: {[key: string]: string};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a base definition for the render3 runtime as defined by {@link R3BaseRefMetadata}
|
||||
* @param meta the metadata used for compilation.
|
||||
*/
|
||||
export function compileBaseDefFromMetadata(meta: R3BaseRefMetaData) {
|
||||
const definitionMap = new DefinitionMap();
|
||||
if (meta.inputs) {
|
||||
const inputs = meta.inputs;
|
||||
const inputsMap = Object.keys(inputs).map(key => {
|
||||
const v = inputs[key];
|
||||
const value = Array.isArray(v) ? o.literalArr(v.map(vx => o.literal(vx))) : o.literal(v);
|
||||
return {key, value, quoted: false};
|
||||
});
|
||||
definitionMap.set('inputs', o.literalMap(inputsMap));
|
||||
}
|
||||
if (meta.outputs) {
|
||||
const outputs = meta.outputs;
|
||||
const outputsMap = Object.keys(outputs).map(key => {
|
||||
const value = o.literal(outputs[key]);
|
||||
return {key, value, quoted: false};
|
||||
});
|
||||
definitionMap.set('outputs', o.literalMap(outputsMap));
|
||||
}
|
||||
|
||||
const expression = o.importExpr(R3.defineBase).callFn([definitionMap.toLiteralMap()]);
|
||||
|
||||
const type = new o.ExpressionType(o.importExpr(R3.BaseDef));
|
||||
|
||||
return {expression, type};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`.
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
// clang-format off
|
||||
export {
|
||||
defineBase as ɵdefineBase,
|
||||
defineComponent as ɵdefineComponent,
|
||||
defineDirective as ɵdefineDirective,
|
||||
definePipe as ɵdefinePipe,
|
||||
|
@ -97,6 +98,7 @@ export {
|
|||
st as ɵst,
|
||||
ld as ɵld,
|
||||
Pp as ɵPp,
|
||||
BaseDef as ɵBaseDef,
|
||||
ComponentDef as ɵComponentDef,
|
||||
ComponentDefInternal as ɵComponentDefInternal,
|
||||
DirectiveDef as ɵDirectiveDef,
|
||||
|
|
|
@ -779,6 +779,11 @@ const initializeBaseDef = (target: any): void => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to get the minified alias of ngBaseDef
|
||||
*/
|
||||
const NG_BASE_DEF = Object.keys({ngBaseDef: true})[0];
|
||||
|
||||
/**
|
||||
* Does the work of creating the `ngBaseDef` property for the @Input and @Output decorators.
|
||||
* @param key "inputs" or "outputs"
|
||||
|
@ -787,7 +792,7 @@ const updateBaseDefFromIOProp = (getProp: (baseDef: {inputs?: any, outputs?: any
|
|||
(target: any, name: string, ...args: any[]) => {
|
||||
const constructor = target.constructor;
|
||||
|
||||
if (!constructor.hasOwnProperty('ngBaseDef')) {
|
||||
if (!constructor.hasOwnProperty(NG_BASE_DEF)) {
|
||||
initializeBaseDef(target);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import {defineBase, defineComponent, defineDirective, defineNgModule, definePipe
|
|||
import {InheritDefinitionFeature} from './features/inherit_definition_feature';
|
||||
import {NgOnChangesFeature} from './features/ng_onchanges_feature';
|
||||
import {PublicFeature} from './features/public_feature';
|
||||
import {ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
|
||||
import {BaseDef, ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
|
||||
|
||||
export {ComponentFactory, ComponentFactoryResolver, ComponentRef, WRAP_RENDERER_FACTORY2} from './component_ref';
|
||||
export {Render3DebugRendererFactory2} from './debug';
|
||||
|
@ -152,6 +152,7 @@ export {
|
|||
// clang-format on
|
||||
|
||||
export {
|
||||
BaseDef,
|
||||
ComponentDef,
|
||||
ComponentDefInternal,
|
||||
ComponentTemplate,
|
||||
|
|
|
@ -18,6 +18,7 @@ import * as sanitization from '../../sanitization/sanitization';
|
|||
* This should be kept up to date with the public exports of @angular/core.
|
||||
*/
|
||||
export const angularCoreEnv: {[name: string]: Function} = {
|
||||
'ɵdefineBase': r3.defineBase,
|
||||
'ɵdefineComponent': r3.defineComponent,
|
||||
'ɵdefineDirective': r3.defineDirective,
|
||||
'defineInjectable': defineInjectable,
|
||||
|
|
|
@ -12,6 +12,7 @@ import {Identifiers} from '@angular/compiler/src/render3/r3_identifiers';
|
|||
import {angularCoreEnv} from '../../src/render3/jit/environment';
|
||||
|
||||
const INTERFACE_EXCEPTIONS = new Set<string>([
|
||||
'ɵBaseDef',
|
||||
'ɵComponentDef',
|
||||
'ɵDirectiveDef',
|
||||
'ɵInjectorDef',
|
||||
|
|
Loading…
Reference in New Issue