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

@ -14,10 +14,10 @@ import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy,
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry} from '../../../src/ngtsc/metadata';
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
import {AbsoluteFsPath, LogicalFileSystem} from '../../../src/ngtsc/path';
import {ClassDeclaration, ClassSymbol, Decorator} from '../../../src/ngtsc/reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
import {CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform';
import {FileSystem} from '../file_system/file_system';
import {DecoratedClass} from '../host/decorated_class';
import {NgccReflectionHost} from '../host/ngcc_host';
import {isDefined} from '../utils';
@ -26,7 +26,10 @@ export interface AnalyzedFile {
analyzedClasses: AnalyzedClass[];
}
export interface AnalyzedClass extends DecoratedClass {
export interface AnalyzedClass {
name: string;
decorators: Decorator[]|null;
declaration: ClassDeclaration;
diagnostics?: ts.Diagnostic[];
matches: {handler: DecoratorHandler<any, any>; analysis: any;}[];
}
@ -133,19 +136,18 @@ export class DecorationAnalyzer {
}
protected analyzeFile(sourceFile: ts.SourceFile): AnalyzedFile|undefined {
const decoratedClasses = this.reflectionHost.findDecoratedClasses(sourceFile);
return decoratedClasses.length ? {
sourceFile,
analyzedClasses: decoratedClasses.map(clazz => this.analyzeClass(clazz)).filter(isDefined)
} :
undefined;
const analyzedClasses = this.reflectionHost.findClassSymbols(sourceFile)
.map(symbol => this.analyzeClass(symbol))
.filter(isDefined);
return analyzedClasses.length ? {sourceFile, analyzedClasses} : undefined;
}
protected analyzeClass(clazz: DecoratedClass): AnalyzedClass|null {
protected analyzeClass(symbol: ClassSymbol): AnalyzedClass|null {
const declaration = symbol.valueDeclaration;
const decorators = this.reflectionHost.getDecoratorsOfSymbol(symbol);
const matchingHandlers = this.handlers
.map(handler => {
const detected =
handler.detect(clazz.declaration, clazz.decorators);
const detected = handler.detect(declaration, decorators);
return {handler, detected};
})
.filter(isMatchingHandler);
@ -183,13 +185,19 @@ export class DecorationAnalyzer {
const matches: {handler: DecoratorHandler<any, any>, analysis: any}[] = [];
const allDiagnostics: ts.Diagnostic[] = [];
for (const {handler, detected} of detections) {
const {analysis, diagnostics} = handler.analyze(clazz.declaration, detected.metadata);
const {analysis, diagnostics} = handler.analyze(declaration, detected.metadata);
if (diagnostics !== undefined) {
allDiagnostics.push(...diagnostics);
}
matches.push({handler, analysis});
}
return {...clazz, matches, diagnostics: allDiagnostics.length > 0 ? allDiagnostics : undefined};
return {
name: symbol.name,
declaration,
decorators,
matches,
diagnostics: allDiagnostics.length > 0 ? allDiagnostics : undefined
};
}
protected compileFile(analyzedFile: AnalyzedFile): CompiledFile {