From 49dccf4bfc9da80806fa10aa4662d921a8fd0f16 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 8 Mar 2019 13:18:32 -0800 Subject: [PATCH] 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 --- .../src/ngtsc/shims/src/summary_generator.ts | 35 +++++++++++++------ .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 18 ++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/shims/src/summary_generator.ts b/packages/compiler-cli/src/ngtsc/shims/src/summary_generator.ts index ef14fae7b7..6e6b6bae1b 100644 --- a/packages/compiler-cli/src/ngtsc/shims/src/summary_generator.ts +++ b/packages/compiler-cli/src/ngtsc/shims/src/summary_generator.ts @@ -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;`); diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 30d1ff7078..d9247ca57c 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -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});