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

@ -1,28 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
/**
* A simple container that holds the details of a decorated class that has been
* found in a `DecoratedFile`.
*/
export class DecoratedClass {
/**
* Initialize a `DecoratedClass` that was found in a `DecoratedFile`.
* @param name The name of the class that has been found. This is mostly used
* for informational purposes.
* @param declaration The TypeScript AST node where this class is declared. In ES5 code, where a
* class can be represented by both a variable declaration and a function declaration (inside an
* IIFE), `declaration` will always refer to the outer variable declaration, which represents the
* class to the rest of the program.
* @param decorators The collection of decorators that have been found on this class.
*/
constructor(
public name: string, public declaration: ClassDeclaration, public decorators: Decorator[]) {}
}

View File

@ -13,7 +13,6 @@ import {Logger} from '../logging/logger';
import {BundleProgram} from '../packages/bundle_program';
import {findAll, getNameText, hasNameIdentifier, isDefined} from '../utils';
import {DecoratedClass} from './decorated_class';
import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
export const DECORATORS = 'decorators' as ts.__String;
@ -233,6 +232,16 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
return superDeclaration;
}
/** Gets all decorators of the given class symbol. */
getDecoratorsOfSymbol(symbol: ClassSymbol): Decorator[]|null {
const decoratorsProperty = this.getStaticProperty(symbol, DECORATORS);
if (decoratorsProperty) {
return this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
} else {
return this.getClassDecoratorsFromHelperCall(symbol);
}
}
/**
* Search the given module for variable declarations in which the initializer
* is an identifier marked with the `PRE_R3_MARKER`.
@ -306,24 +315,24 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
}
/**
* Find all the classes that contain decorations in a given file.
* @param sourceFile The source file to search for decorated classes.
* @returns An array of decorated classes.
* Find all top-level class symbols in the given file.
* @param sourceFile The source file to search for classes.
* @returns An array of class symbols.
*/
findDecoratedClasses(sourceFile: ts.SourceFile): DecoratedClass[] {
const classes: DecoratedClass[] = [];
findClassSymbols(sourceFile: ts.SourceFile): ClassSymbol[] {
const classes: ClassSymbol[] = [];
this.getModuleStatements(sourceFile).forEach(statement => {
if (ts.isVariableStatement(statement)) {
statement.declarationList.declarations.forEach(declaration => {
const decoratedClass = this.getDecoratedClassFromSymbol(this.getClassSymbol(declaration));
if (decoratedClass) {
classes.push(decoratedClass);
const classSymbol = this.getClassSymbol(declaration);
if (classSymbol) {
classes.push(classSymbol);
}
});
} else if (ts.isClassDeclaration(statement)) {
const decoratedClass = this.getDecoratedClassFromSymbol(this.getClassSymbol(statement));
if (decoratedClass) {
classes.push(decoratedClass);
const classSymbol = this.getClassSymbol(statement);
if (classSymbol) {
classes.push(classSymbol);
}
}
});
@ -488,25 +497,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
return Array.from(sourceFile.statements);
}
protected getDecoratorsOfSymbol(symbol: ClassSymbol): Decorator[]|null {
const decoratorsProperty = this.getStaticProperty(symbol, DECORATORS);
if (decoratorsProperty) {
return this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
} else {
return this.getClassDecoratorsFromHelperCall(symbol);
}
}
protected getDecoratedClassFromSymbol(symbol: ClassSymbol|undefined): DecoratedClass|null {
if (symbol) {
const decorators = this.getDecoratorsOfSymbol(symbol);
if (decorators && decorators.length) {
return new DecoratedClass(symbol.name, symbol.valueDeclaration, decorators);
}
}
return null;
}
/**
* Walk the AST looking for an assignment to the specified symbol.
* @param node The current node we are searching.

View File

@ -211,6 +211,16 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
return this.getMembersOfSymbol(innerFunctionSymbol);
}
/** Gets all decorators of the given class symbol. */
getDecoratorsOfSymbol(symbol: ClassSymbol): Decorator[]|null {
// The necessary info is on the inner function declaration (inside the ES5 class IIFE).
const innerFunctionSymbol =
this.getInnerFunctionSymbolFromClassDeclaration(symbol.valueDeclaration);
if (!innerFunctionSymbol) return null;
return super.getDecoratorsOfSymbol(innerFunctionSymbol);
}
///////////// Protected Helpers /////////////
@ -321,15 +331,6 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
return super.getConstructorParamInfo(innerFunctionSymbol, parameterNodes);
}
protected getDecoratorsOfSymbol(symbol: ClassSymbol): Decorator[]|null {
// The necessary info is on the inner function declaration (inside the ES5 class IIFE).
const innerFunctionSymbol =
this.getInnerFunctionSymbolFromClassDeclaration(symbol.valueDeclaration);
if (!innerFunctionSymbol) return null;
return super.getDecoratorsOfSymbol(innerFunctionSymbol);
}
/**
* Get the parameter type and decorators for the constructor of a class,
* where the information is stored on a static method of the class.

View File

@ -6,8 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {ClassDeclaration, ClassSymbol, Declaration, ReflectionHost} from '../../../src/ngtsc/reflection';
import {DecoratedClass} from './decorated_class';
import {ClassDeclaration, ClassSymbol, Declaration, Decorator, ReflectionHost} from '../../../src/ngtsc/reflection';
export const PRE_R3_MARKER = '__PRE_R3__';
export const POST_R3_MARKER = '__POST_R3__';
@ -64,11 +63,18 @@ export interface NgccReflectionHost extends ReflectionHost {
getSwitchableDeclarations(module: ts.Node): SwitchableVariableDeclaration[];
/**
* Find all the classes that contain decorations in a given file.
* @param sourceFile The source file to search for decorated classes.
* @returns An array of decorated classes.
* Retrieves all decorators of a given class symbol.
* @param symbol Class symbol that can refer to a declaration which can hold decorators.
* @returns An array of decorators or null if none are declared.
*/
findDecoratedClasses(sourceFile: ts.SourceFile): DecoratedClass[];
getDecoratorsOfSymbol(symbol: ClassSymbol): Decorator[]|null;
/**
* Retrieves all class symbols of a given source file.
* @param sourceFile The source file to search for classes.
* @returns An array of found class symbols.
*/
findClassSymbols(sourceFile: ts.SourceFile): ClassSymbol[];
/**
* Search the given source file for exported functions and static class methods that return