fix(ivy): process separate declarations and exports for summaries (#29193)

ngsummary files were generated with an export for each class declaration.
However, some Angular code declares classes (class Foo) and exports them
(export {Foo}) separately, which was causing incomplete summary files.

This commit expands the set of symbol names for which summary exports will
be generated, fixing this issue.

PR Close #29193
This commit is contained in:
Alex Rickabaugh 2019-03-08 13:18:32 -08:00 committed by Kara Erickson
parent 3a6ba00286
commit 49dccf4bfc
2 changed files with 43 additions and 10 deletions

View File

@ -34,16 +34,31 @@ export class SummaryGenerator implements ShimGenerator {
// to semantically understand which decorators are Angular decorators. It's okay to output an
// overly broad set of summary exports as the exports are no-ops anyway, and summaries are a
// compatibility layer which will be removed after Ivy is enabled.
const symbolNames = original
.statements
// Pick out top level class declarations...
.filter(ts.isClassDeclaration)
// which are named, exported, and have decorators.
.filter(
decl => isExported(decl) && decl.decorators !== undefined &&
decl.name !== undefined)
// Grab the symbol name.
.map(decl => decl.name !.text);
const symbolNames: string[] = [];
for (const stmt of original.statements) {
if (ts.isClassDeclaration(stmt)) {
// If the class isn't exported, or if it's not decorated, then skip it.
if (!isExported(stmt) || stmt.decorators === undefined || stmt.name === undefined) {
continue;
}
symbolNames.push(stmt.name.text);
} else if (ts.isExportDeclaration(stmt)) {
// Look for an export statement of the form "export {...};". If it doesn't match that, then
// skip it.
if (stmt.exportClause === undefined || stmt.moduleSpecifier !== undefined) {
continue;
}
for (const specifier of stmt.exportClause.elements) {
// At this point, there is no guarantee that specifier here refers to a class declaration,
// but that's okay.
// Use specifier.name as that's guaranteed to be the exported name, regardless of whether
// specifier.propertyName is set.
symbolNames.push(specifier.name.text);
}
}
}
const varLines = symbolNames.map(name => `export const ${name}NgSummary: any = null;`);

View File

@ -1768,6 +1768,24 @@ describe('ngtsc behavioral tests', () => {
expect(summaryContents).toEqual(`export var TestModuleNgSummary = null;\n`);
});
it('should generate a summary stub for classes exported via exports', () => {
env.tsconfig({'allowEmptyCodegenFiles': true});
env.write('test.ts', `
import {Injectable, NgModule} from '@angular/core';
@NgModule({})
class NotDirectlyExported {}
export {NotDirectlyExported};
`);
env.driveMain();
const summaryContents = env.getContents('test.ngsummary.js');
expect(summaryContents).toEqual(`export var NotDirectlyExportedNgSummary = null;\n`);
});
it('it should generate empty export when there are no other summary symbols, to ensure the output is a valid ES module',
() => {
env.tsconfig({'allowEmptyCodegenFiles': true});