feat(ivy): Add AOT handling for bare classes with Input and Output decorators (#25367)
PR Close #25367
This commit is contained in:
@ -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'})]);
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user