From 8634d0bcd8dd78718e9448b9319f54fd6eaed080 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 30 Oct 2018 11:19:10 -0700 Subject: [PATCH] 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 --- .../src/ngcc/test/rendering/renderer_spec.ts | 7 +++++-- .../src/ngtsc/annotations/src/component.ts | 13 +++++++++--- .../src/ngtsc/annotations/src/directive.ts | 15 ++++++++++--- .../src/ngtsc/annotations/src/injectable.ts | 16 ++++++++++---- .../src/ngtsc/annotations/src/ng_module.ts | 16 ++++++++++---- .../src/ngtsc/annotations/src/pipe.ts | 16 ++++++++++---- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 21 +++++++++++++++++++ .../hello_world_r2/bundle.golden_symbols.json | 2 +- 8 files changed, 85 insertions(+), 21 deletions(-) diff --git a/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts b/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts index b3a5ee0bf7..6603aa6ad0 100644 --- a/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts @@ -127,8 +127,11 @@ describe('Renderer', () => { decorators: [jasmine.objectContaining({name: 'Directive'})], })); expect(addDefinitionsSpy.calls.first().args[2]) - .toEqual( - `A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); } });`); + .toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(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', diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 8f5b5d931e..f1ecb66353 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -6,7 +6,7 @@ * 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 ts from 'typescript'; @@ -18,6 +18,7 @@ import {TypeCheckContext, TypeCheckableDirectiveMeta} from '../../typecheck'; import {ResourceLoader} from './api'; import {extractDirectiveMetadata, extractQueriesFromDecorator, parseFieldArrayValue, queriesFromFields} from './directive'; +import {generateSetClassMetadataCall} from './metadata'; import {ScopeDirective, SelectorScopeRegistry} from './selector_scope'; import {extractDirectiveGuards, isAngularCore, unwrapExpression} from './util'; @@ -26,6 +27,7 @@ const EMPTY_MAP = new Map(); export interface ComponentHandlerData { meta: R3ComponentMetadata; parsedTemplate: TmplAstNode[]; + metadataStmt: Statement|null; } /** @@ -208,6 +210,7 @@ export class ComponentDecoratorHandler implements animations, viewProviders }, + metadataStmt: generateSetClassMetadataCall(node, this.reflector, this.isCore), parsedTemplate: template.nodes, }, typeCheck: true, @@ -242,10 +245,14 @@ export class ComponentDecoratorHandler implements } const res = compileComponentFromMetadata(metadata, pool, makeBindingParser()); + + const statements = res.statements; + if (analysis.metadataStmt !== null) { + statements.push(analysis.metadataStmt); + } return { name: 'ngComponentDef', - initializer: res.expression, - statements: res.statements, + initializer: res.expression, statements, type: res.type, }; } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts index 79d660688b..0ae80e34d9 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts @@ -6,7 +6,7 @@ * 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 {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; @@ -14,12 +14,16 @@ import {ClassMember, ClassMemberKind, Decorator, Import, ReflectionHost} from '. import {Reference, ResolvedReference, filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata'; import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; +import {generateSetClassMetadataCall} from './metadata'; import {SelectorScopeRegistry} from './selector_scope'; import {extractDirectiveGuards, getConstructorDependencies, isAngularCore, unwrapExpression, unwrapForwardRef} from './util'; const EMPTY_OBJECT: {[key: string]: string} = {}; -export interface DirectiveHandlerData { meta: R3DirectiveMetadata; } +export interface DirectiveHandlerData { + meta: R3DirectiveMetadata; + metadataStmt: Statement|null; +} export class DirectiveDecoratorHandler implements DecoratorHandler { constructor( @@ -63,6 +67,7 @@ export class DirectiveDecoratorHandler implements return { 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): CompileResult { const res = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser()); + const statements = res.statements; + if (analysis.metadataStmt !== null) { + statements.push(analysis.metadataStmt); + } return { name: 'ngDirectiveDef', initializer: res.expression, - statements: res.statements, + statements: statements, type: res.type, }; } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts index 4fe9abda75..6dbfc1a5fd 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts @@ -6,7 +6,7 @@ * 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 {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; @@ -14,9 +14,13 @@ import {Decorator, ReflectionHost} from '../../host'; import {reflectObjectLiteral} from '../../metadata'; import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; +import {generateSetClassMetadataCall} from './metadata'; 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. @@ -37,16 +41,20 @@ export class InjectableDecoratorHandler implements return { analysis: { meta: extractInjectableMetadata(node, decorator, this.reflector, this.isCore), + metadataStmt: generateSetClassMetadataCall(node, this.reflector, this.isCore), }, }; } compile(node: ts.ClassDeclaration, analysis: InjectableHandlerData): CompileResult { const res = compileIvyInjectable(analysis.meta); + const statements = res.statements; + if (analysis.metadataStmt !== null) { + statements.push(analysis.metadataStmt); + } return { name: 'ngInjectableDef', - initializer: res.expression, - statements: res.statements, + initializer: res.expression, statements, type: res.type, }; } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 60ec75abcd..5482a69e12 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -6,7 +6,7 @@ * 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 {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; @@ -14,12 +14,14 @@ import {Decorator, ReflectionHost} from '../../host'; import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata'; import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; +import {generateSetClassMetadataCall} from './metadata'; import {SelectorScopeRegistry} from './selector_scope'; import {getConstructorDependencies, isAngularCore, toR3Reference, unwrapExpression} from './util'; export interface NgModuleAnalysis { ngModuleDef: R3NgModuleMetadata; ngInjectorDef: R3InjectorMetadata; + metadataStmt: Statement|null; } /** @@ -130,7 +132,9 @@ export class NgModuleDecoratorHandler implements DecoratorHandler { constructor( @@ -85,16 +89,20 @@ export class PipeDecoratorHandler implements DecoratorHandler { const jsContents = env.getContents('test.js'); 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, '); + }); }); diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index 095ee683a5..f6c76830ee 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -231,7 +231,7 @@ "name": "AttrAst" }, { - "name": "Attribute" + "name": "Attribute$1" }, { "name": "B64_DIGITS"