feat(ivy): emit metadata along with all Angular types (#26860)
This commit causes a call to setClassMetadata() to be emitted for every type being compiled by ngtsc (every Angular type). With this metadata, the TestBed should be able to recompile these classes when overriding decorator information. Testing strategy: Tests in the previous commit for generateSetClassMetadataCall() verify that the metadata as generated is correct. This commit enables the generation for each DecoratorHandler, and a test is added to ngtsc_spec to verify all decorated types have metadata generated for them. PR Close #26860
This commit is contained in:
parent
492576114d
commit
8634d0bcd8
|
@ -127,8 +127,11 @@ describe('Renderer', () => {
|
||||||
decorators: [jasmine.objectContaining({name: 'Directive'})],
|
decorators: [jasmine.objectContaining({name: 'Directive'})],
|
||||||
}));
|
}));
|
||||||
expect(addDefinitionsSpy.calls.first().args[2])
|
expect(addDefinitionsSpy.calls.first().args[2])
|
||||||
.toEqual(
|
.toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
|
||||||
`A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); } });`);
|
type: Directive,
|
||||||
|
args: [{ selector: '[a]' }]
|
||||||
|
}], null, { foo: [] });
|
||||||
|
A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); } });`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
|
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ConstantPool, CssSelector, Expression, R3ComponentMetadata, R3DirectiveMetadata, SelectorMatcher, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
import {ConstantPool, CssSelector, Expression, R3ComponentMetadata, R3DirectiveMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import {TypeCheckContext, TypeCheckableDirectiveMeta} from '../../typecheck';
|
||||||
|
|
||||||
import {ResourceLoader} from './api';
|
import {ResourceLoader} from './api';
|
||||||
import {extractDirectiveMetadata, extractQueriesFromDecorator, parseFieldArrayValue, queriesFromFields} from './directive';
|
import {extractDirectiveMetadata, extractQueriesFromDecorator, parseFieldArrayValue, queriesFromFields} from './directive';
|
||||||
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {ScopeDirective, SelectorScopeRegistry} from './selector_scope';
|
import {ScopeDirective, SelectorScopeRegistry} from './selector_scope';
|
||||||
import {extractDirectiveGuards, isAngularCore, unwrapExpression} from './util';
|
import {extractDirectiveGuards, isAngularCore, unwrapExpression} from './util';
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ const EMPTY_MAP = new Map<string, Expression>();
|
||||||
export interface ComponentHandlerData {
|
export interface ComponentHandlerData {
|
||||||
meta: R3ComponentMetadata;
|
meta: R3ComponentMetadata;
|
||||||
parsedTemplate: TmplAstNode[];
|
parsedTemplate: TmplAstNode[];
|
||||||
|
metadataStmt: Statement|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,6 +210,7 @@ export class ComponentDecoratorHandler implements
|
||||||
animations,
|
animations,
|
||||||
viewProviders
|
viewProviders
|
||||||
},
|
},
|
||||||
|
metadataStmt: generateSetClassMetadataCall(node, this.reflector, this.isCore),
|
||||||
parsedTemplate: template.nodes,
|
parsedTemplate: template.nodes,
|
||||||
},
|
},
|
||||||
typeCheck: true,
|
typeCheck: true,
|
||||||
|
@ -242,10 +245,14 @@ export class ComponentDecoratorHandler implements
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = compileComponentFromMetadata(metadata, pool, makeBindingParser());
|
const res = compileComponentFromMetadata(metadata, pool, makeBindingParser());
|
||||||
|
|
||||||
|
const statements = res.statements;
|
||||||
|
if (analysis.metadataStmt !== null) {
|
||||||
|
statements.push(analysis.metadataStmt);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
name: 'ngComponentDef',
|
name: 'ngComponentDef',
|
||||||
initializer: res.expression,
|
initializer: res.expression, statements,
|
||||||
statements: res.statements,
|
|
||||||
type: res.type,
|
type: res.type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ConstantPool, Expression, R3DirectiveMetadata, R3QueryMetadata, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser, parseHostBindings} from '@angular/compiler';
|
import {ConstantPool, Expression, R3DirectiveMetadata, R3QueryMetadata, Statement, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser, parseHostBindings} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
|
@ -14,12 +14,16 @@ import {ClassMember, ClassMemberKind, Decorator, Import, ReflectionHost} from '.
|
||||||
import {Reference, ResolvedReference, filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
import {Reference, ResolvedReference, filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||||
|
|
||||||
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {SelectorScopeRegistry} from './selector_scope';
|
import {SelectorScopeRegistry} from './selector_scope';
|
||||||
import {extractDirectiveGuards, getConstructorDependencies, isAngularCore, unwrapExpression, unwrapForwardRef} from './util';
|
import {extractDirectiveGuards, getConstructorDependencies, isAngularCore, unwrapExpression, unwrapForwardRef} from './util';
|
||||||
|
|
||||||
const EMPTY_OBJECT: {[key: string]: string} = {};
|
const EMPTY_OBJECT: {[key: string]: string} = {};
|
||||||
|
|
||||||
export interface DirectiveHandlerData { meta: R3DirectiveMetadata; }
|
export interface DirectiveHandlerData {
|
||||||
|
meta: R3DirectiveMetadata;
|
||||||
|
metadataStmt: Statement|null;
|
||||||
|
}
|
||||||
export class DirectiveDecoratorHandler implements
|
export class DirectiveDecoratorHandler implements
|
||||||
DecoratorHandler<DirectiveHandlerData, Decorator> {
|
DecoratorHandler<DirectiveHandlerData, Decorator> {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -63,6 +67,7 @@ export class DirectiveDecoratorHandler implements
|
||||||
return {
|
return {
|
||||||
analysis: {
|
analysis: {
|
||||||
meta: analysis,
|
meta: analysis,
|
||||||
|
metadataStmt: generateSetClassMetadataCall(node, this.reflector, this.isCore),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -70,10 +75,14 @@ export class DirectiveDecoratorHandler implements
|
||||||
compile(node: ts.ClassDeclaration, analysis: DirectiveHandlerData, pool: ConstantPool):
|
compile(node: ts.ClassDeclaration, analysis: DirectiveHandlerData, pool: ConstantPool):
|
||||||
CompileResult {
|
CompileResult {
|
||||||
const res = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser());
|
const res = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser());
|
||||||
|
const statements = res.statements;
|
||||||
|
if (analysis.metadataStmt !== null) {
|
||||||
|
statements.push(analysis.metadataStmt);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
name: 'ngDirectiveDef',
|
name: 'ngDirectiveDef',
|
||||||
initializer: res.expression,
|
initializer: res.expression,
|
||||||
statements: res.statements,
|
statements: statements,
|
||||||
type: res.type,
|
type: res.type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, Statement, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
|
@ -14,9 +14,13 @@ import {Decorator, ReflectionHost} from '../../host';
|
||||||
import {reflectObjectLiteral} from '../../metadata';
|
import {reflectObjectLiteral} from '../../metadata';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||||
|
|
||||||
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {getConstructorDependencies, isAngularCore} from './util';
|
import {getConstructorDependencies, isAngularCore} from './util';
|
||||||
|
|
||||||
export interface InjectableHandlerData { meta: R3InjectableMetadata; }
|
export interface InjectableHandlerData {
|
||||||
|
meta: R3InjectableMetadata;
|
||||||
|
metadataStmt: Statement|null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
||||||
|
@ -37,16 +41,20 @@ export class InjectableDecoratorHandler implements
|
||||||
return {
|
return {
|
||||||
analysis: {
|
analysis: {
|
||||||
meta: extractInjectableMetadata(node, decorator, this.reflector, this.isCore),
|
meta: extractInjectableMetadata(node, decorator, this.reflector, this.isCore),
|
||||||
|
metadataStmt: generateSetClassMetadataCall(node, this.reflector, this.isCore),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
compile(node: ts.ClassDeclaration, analysis: InjectableHandlerData): CompileResult {
|
compile(node: ts.ClassDeclaration, analysis: InjectableHandlerData): CompileResult {
|
||||||
const res = compileIvyInjectable(analysis.meta);
|
const res = compileIvyInjectable(analysis.meta);
|
||||||
|
const statements = res.statements;
|
||||||
|
if (analysis.metadataStmt !== null) {
|
||||||
|
statements.push(analysis.metadataStmt);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
name: 'ngInjectableDef',
|
name: 'ngInjectableDef',
|
||||||
initializer: res.expression,
|
initializer: res.expression, statements,
|
||||||
statements: res.statements,
|
|
||||||
type: res.type,
|
type: res.type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ConstantPool, Expression, LiteralArrayExpr, R3DirectiveMetadata, R3InjectorMetadata, R3NgModuleMetadata, WrappedNodeExpr, compileInjector, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler';
|
import {ConstantPool, Expression, LiteralArrayExpr, R3DirectiveMetadata, R3InjectorMetadata, R3NgModuleMetadata, Statement, WrappedNodeExpr, compileInjector, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
|
@ -14,12 +14,14 @@ import {Decorator, ReflectionHost} from '../../host';
|
||||||
import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||||
|
|
||||||
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {SelectorScopeRegistry} from './selector_scope';
|
import {SelectorScopeRegistry} from './selector_scope';
|
||||||
import {getConstructorDependencies, isAngularCore, toR3Reference, unwrapExpression} from './util';
|
import {getConstructorDependencies, isAngularCore, toR3Reference, unwrapExpression} from './util';
|
||||||
|
|
||||||
export interface NgModuleAnalysis {
|
export interface NgModuleAnalysis {
|
||||||
ngModuleDef: R3NgModuleMetadata;
|
ngModuleDef: R3NgModuleMetadata;
|
||||||
ngInjectorDef: R3InjectorMetadata;
|
ngInjectorDef: R3InjectorMetadata;
|
||||||
|
metadataStmt: Statement|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,7 +132,9 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
|
|
||||||
return {
|
return {
|
||||||
analysis: {
|
analysis: {
|
||||||
ngModuleDef, ngInjectorDef,
|
ngModuleDef,
|
||||||
|
ngInjectorDef,
|
||||||
|
metadataStmt: generateSetClassMetadataCall(node, this.reflector, this.isCore),
|
||||||
},
|
},
|
||||||
factorySymbolName: node.name !== undefined ? node.name.text : undefined,
|
factorySymbolName: node.name !== undefined ? node.name.text : undefined,
|
||||||
};
|
};
|
||||||
|
@ -139,17 +143,21 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
compile(node: ts.ClassDeclaration, analysis: NgModuleAnalysis): CompileResult[] {
|
compile(node: ts.ClassDeclaration, analysis: NgModuleAnalysis): CompileResult[] {
|
||||||
const ngInjectorDef = compileInjector(analysis.ngInjectorDef);
|
const ngInjectorDef = compileInjector(analysis.ngInjectorDef);
|
||||||
const ngModuleDef = compileNgModule(analysis.ngModuleDef);
|
const ngModuleDef = compileNgModule(analysis.ngModuleDef);
|
||||||
|
const ngModuleStatements = ngModuleDef.additionalStatements;
|
||||||
|
if (analysis.metadataStmt !== null) {
|
||||||
|
ngModuleStatements.push(analysis.metadataStmt);
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'ngModuleDef',
|
name: 'ngModuleDef',
|
||||||
initializer: ngModuleDef.expression,
|
initializer: ngModuleDef.expression,
|
||||||
statements: [],
|
statements: ngModuleStatements,
|
||||||
type: ngModuleDef.type,
|
type: ngModuleDef.type,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ngInjectorDef',
|
name: 'ngInjectorDef',
|
||||||
initializer: ngInjectorDef.expression,
|
initializer: ngInjectorDef.expression,
|
||||||
statements: [],
|
statements: ngInjectorDef.statements,
|
||||||
type: ngInjectorDef.type,
|
type: ngInjectorDef.type,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {LiteralExpr, R3PipeMetadata, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
|
import {LiteralExpr, R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
|
@ -14,10 +14,14 @@ import {Decorator, ReflectionHost} from '../../host';
|
||||||
import {reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
import {reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||||
|
|
||||||
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {SelectorScopeRegistry} from './selector_scope';
|
import {SelectorScopeRegistry} from './selector_scope';
|
||||||
import {getConstructorDependencies, isAngularCore, unwrapExpression} from './util';
|
import {getConstructorDependencies, isAngularCore, unwrapExpression} from './util';
|
||||||
|
|
||||||
export interface PipeHandlerData { meta: R3PipeMetadata; }
|
export interface PipeHandlerData {
|
||||||
|
meta: R3PipeMetadata;
|
||||||
|
metadataStmt: Statement|null;
|
||||||
|
}
|
||||||
|
|
||||||
export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, Decorator> {
|
export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, Decorator> {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -85,16 +89,20 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
|
||||||
pipeName,
|
pipeName,
|
||||||
deps: getConstructorDependencies(clazz, this.reflector, this.isCore), pure,
|
deps: getConstructorDependencies(clazz, this.reflector, this.isCore), pure,
|
||||||
},
|
},
|
||||||
|
metadataStmt: generateSetClassMetadataCall(clazz, this.reflector, this.isCore),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
compile(node: ts.ClassDeclaration, analysis: PipeHandlerData): CompileResult {
|
compile(node: ts.ClassDeclaration, analysis: PipeHandlerData): CompileResult {
|
||||||
const res = compilePipeFromMetadata(analysis.meta);
|
const res = compilePipeFromMetadata(analysis.meta);
|
||||||
|
const statements = res.statements;
|
||||||
|
if (analysis.metadataStmt !== null) {
|
||||||
|
statements.push(analysis.metadataStmt);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
name: 'ngPipeDef',
|
name: 'ngPipeDef',
|
||||||
initializer: res.expression,
|
initializer: res.expression, statements,
|
||||||
statements: [],
|
|
||||||
type: res.type,
|
type: res.type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -748,4 +748,25 @@ describe('ngtsc behavioral tests', () => {
|
||||||
const jsContents = env.getContents('test.js');
|
const jsContents = env.getContents('test.js');
|
||||||
expect(jsContents).toContain('directives: function () { return [CmpB]; }');
|
expect(jsContents).toContain('directives: function () { return [CmpB]; }');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should emit setClassMetadata calls for all types', () => {
|
||||||
|
env.tsconfig();
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Component, Directive, Injectable, NgModule, Pipe} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({selector: 'cmp', template: 'I am a component!'}) class TestComponent {}
|
||||||
|
@Directive({selector: 'dir'}) class TestDirective {}
|
||||||
|
@Injectable() class TestInjectable {}
|
||||||
|
@NgModule({declarations: [TestComponent, TestDirective]}) class TestNgModule {}
|
||||||
|
@Pipe({name: 'pipe'}) class TestPipe {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
const jsContents = env.getContents('test.js');
|
||||||
|
expect(jsContents).toContain('ɵsetClassMetadata(TestComponent, ');
|
||||||
|
expect(jsContents).toContain('ɵsetClassMetadata(TestDirective, ');
|
||||||
|
expect(jsContents).toContain('ɵsetClassMetadata(TestInjectable, ');
|
||||||
|
expect(jsContents).toContain('ɵsetClassMetadata(TestNgModule, ');
|
||||||
|
expect(jsContents).toContain('ɵsetClassMetadata(TestPipe, ');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -231,7 +231,7 @@
|
||||||
"name": "AttrAst"
|
"name": "AttrAst"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Attribute"
|
"name": "Attribute$1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "B64_DIGITS"
|
"name": "B64_DIGITS"
|
||||||
|
|
Loading…
Reference in New Issue