fix(ivy): ngcc should process undecorated base classes (#30821)

Currently undecorated classes are intentionally not processed
with ngcc. This is causing unexpected behavior because decorator
handlers such as `base_def.ts` are specifically interested in class
definitions without top-level decorators, so that the base definition
can be generated if there are Angular-specific class members.

In order to ensure that undecorated base-classes work as expected
with Ivy, we need to run the decorator handlers for all top-level
class declarations (not only for those with decorators). This is similar
to when `ngtsc` runs decorator handlers when analyzing source-files.

Resolves FW-1355. Fixes https://github.com/angular/components/issues/16178

PR Close #30821
This commit is contained in:
Paul Gschwendtner
2019-06-03 18:41:47 +02:00
committed by Igor Minar
parent 271d2b51a9
commit 2b4d5c7548
20 changed files with 520 additions and 367 deletions

View File

@ -1587,27 +1587,41 @@ describe('CommonJsReflectionHost', () => {
});
});
describe('findDecoratedClasses()', () => {
it('should return an array of all decorated classes in the given source file', () => {
describe('findClassSymbols()', () => {
it('should return an array of all classes in the given source file', () => {
const {program, host: compilerHost} = makeTestBundleProgram(DECORATED_FILES);
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
const primary = program.getSourceFile(DECORATED_FILES[0].name) !;
const primaryFile = program.getSourceFile(DECORATED_FILES[0].name) !;
const secondaryFile = program.getSourceFile(DECORATED_FILES[1].name) !;
const primaryDecoratedClasses = host.findDecoratedClasses(primary);
expect(primaryDecoratedClasses.length).toEqual(2);
const classA = primaryDecoratedClasses.find(c => c.name === 'A') !;
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
// Note that `B` is not exported from `primary.js`
const classB = primaryDecoratedClasses.find(c => c.name === 'B') !;
expect(classB.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
const classSymbolsPrimary = host.findClassSymbols(primaryFile);
expect(classSymbolsPrimary.length).toEqual(2);
expect(classSymbolsPrimary.map(c => c.name)).toEqual(['A', 'B']);
const secondary = program.getSourceFile(DECORATED_FILES[1].name) !;
const secondaryDecoratedClasses = host.findDecoratedClasses(secondary);
expect(secondaryDecoratedClasses.length).toEqual(1);
// Note that `D` is exported from `secondary.js` but not exported from `primary.js`
const classD = secondaryDecoratedClasses.find(c => c.name === 'D') !;
expect(classD.name).toEqual('D');
expect(classD.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
const classSymbolsSecondary = host.findClassSymbols(secondaryFile);
expect(classSymbolsSecondary.length).toEqual(1);
expect(classSymbolsSecondary.map(c => c.name)).toEqual(['D']);
});
});
describe('getDecoratorsOfSymbol()', () => {
it('should return decorators of class symbol', () => {
const {program, host: compilerHost} = makeTestBundleProgram(DECORATED_FILES);
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
const primaryFile = program.getSourceFile(DECORATED_FILES[0].name) !;
const secondaryFile = program.getSourceFile(DECORATED_FILES[1].name) !;
const classSymbolsPrimary = host.findClassSymbols(primaryFile);
const classDecoratorsPrimary = classSymbolsPrimary.map(s => host.getDecoratorsOfSymbol(s));
expect(classDecoratorsPrimary.length).toEqual(2);
expect(classDecoratorsPrimary[0] !.map(d => d.name)).toEqual(['Directive']);
expect(classDecoratorsPrimary[1] !.map(d => d.name)).toEqual(['Directive']);
const classSymbolsSecondary = host.findClassSymbols(secondaryFile);
const classDecoratorsSecondary =
classSymbolsSecondary.map(s => host.getDecoratorsOfSymbol(s));
expect(classDecoratorsSecondary.length).toEqual(1);
expect(classDecoratorsSecondary[0] !.map(d => d.name)).toEqual(['Directive']);
});
});