fix(compiler): fix inheritance for AOT with summaries (#15583)
Allows to inherit ctor args, lifecycle hooks and statics from a class in another compilation unit. Will error if trying to inherit from a class in another compilation unit that has an `@Component` / `@Directive` / `@Pipe` / `@NgModule`.
This commit is contained in:

committed by
Alex Rickabaugh

parent
28bf222a6a
commit
8ef621ad2a
@ -47,7 +47,7 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
|
||||
const symbolCache = new StaticSymbolCache();
|
||||
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
|
||||
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
|
||||
const staticReflector = new StaticReflector(symbolResolver);
|
||||
const staticReflector = new StaticReflector(summaryResolver, symbolResolver);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
const console = new Console();
|
||||
const htmlParser = new I18NHtmlParser(
|
||||
|
@ -7,7 +7,11 @@
|
||||
*/
|
||||
|
||||
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger, ɵReflectorReader} from '@angular/core';
|
||||
|
||||
import {CompileSummaryKind} from '../compile_metadata';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
import {syntaxError} from '../util';
|
||||
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||
|
||||
@ -35,8 +39,11 @@ export class StaticReflector implements ɵReflectorReader {
|
||||
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
||||
private injectionToken: StaticSymbol;
|
||||
private opaqueToken: StaticSymbol;
|
||||
private annotationForParentClassWithSummaryKind = new Map<CompileSummaryKind, any[]>();
|
||||
private annotationNames = new Map<any, string>();
|
||||
|
||||
constructor(
|
||||
private summaryResolver: SummaryResolver<StaticSymbol>,
|
||||
private symbolResolver: StaticSymbolResolver,
|
||||
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
|
||||
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
|
||||
@ -47,6 +54,17 @@ export class StaticReflector implements ɵReflectorReader {
|
||||
this.getStaticSymbol(kc.filePath, kc.name), kc.ctor));
|
||||
knownMetadataFunctions.forEach(
|
||||
(kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn));
|
||||
this.annotationForParentClassWithSummaryKind.set(
|
||||
CompileSummaryKind.Directive, [Directive, Component]);
|
||||
this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.Pipe, [Pipe]);
|
||||
this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.NgModule, [NgModule]);
|
||||
this.annotationForParentClassWithSummaryKind.set(
|
||||
CompileSummaryKind.Injectable, [Injectable, Pipe, Directive, Component, NgModule]);
|
||||
this.annotationNames.set(Directive, 'Directive');
|
||||
this.annotationNames.set(Component, 'Component');
|
||||
this.annotationNames.set(Pipe, 'Pipe');
|
||||
this.annotationNames.set(NgModule, 'NgModule');
|
||||
this.annotationNames.set(Injectable, 'Injectable');
|
||||
}
|
||||
|
||||
importUri(typeOrFunc: StaticSymbol): string {
|
||||
@ -96,17 +114,33 @@ export class StaticReflector implements ɵReflectorReader {
|
||||
if (!annotations) {
|
||||
annotations = [];
|
||||
const classMetadata = this.getTypeMetadata(type);
|
||||
if (classMetadata['extends']) {
|
||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
||||
if (parentType && (parentType instanceof StaticSymbol)) {
|
||||
const parentAnnotations = this.annotations(parentType);
|
||||
annotations.push(...parentAnnotations);
|
||||
}
|
||||
const parentType = this.findParentType(type, classMetadata);
|
||||
if (parentType) {
|
||||
const parentAnnotations = this.annotations(parentType);
|
||||
annotations.push(...parentAnnotations);
|
||||
}
|
||||
let ownAnnotations: any[] = [];
|
||||
if (classMetadata['decorators']) {
|
||||
const ownAnnotations: any[] = this.simplify(type, classMetadata['decorators']);
|
||||
ownAnnotations = this.simplify(type, classMetadata['decorators']);
|
||||
annotations.push(...ownAnnotations);
|
||||
}
|
||||
if (parentType && !this.summaryResolver.isLibraryFile(type.filePath) &&
|
||||
this.summaryResolver.isLibraryFile(parentType.filePath)) {
|
||||
const summary = this.summaryResolver.resolveSummary(parentType);
|
||||
if (summary && summary.type) {
|
||||
const requiredAnnotationTypes =
|
||||
this.annotationForParentClassWithSummaryKind.get(summary.type.summaryKind);
|
||||
const typeHasRequiredAnnotation = requiredAnnotationTypes.some(
|
||||
requiredType => ownAnnotations.some(ann => ann instanceof requiredType));
|
||||
if (!typeHasRequiredAnnotation) {
|
||||
this.reportError(
|
||||
syntaxError(
|
||||
`Class ${type.name} in ${type.filePath} extends from a ${CompileSummaryKind[summary.type.summaryKind]} in another compilation unit without duplicating the decorator. ` +
|
||||
`Please add a ${requiredAnnotationTypes.map(type => this.annotationNames.get(type)).join(' or ')} decorator to the class.`),
|
||||
type);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.annotationCache.set(type, annotations.filter(ann => !!ann));
|
||||
}
|
||||
return annotations;
|
||||
@ -117,14 +151,12 @@ export class StaticReflector implements ɵReflectorReader {
|
||||
if (!propMetadata) {
|
||||
const classMetadata = this.getTypeMetadata(type);
|
||||
propMetadata = {};
|
||||
if (classMetadata['extends']) {
|
||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
||||
if (parentType instanceof StaticSymbol) {
|
||||
const parentPropMetadata = this.propMetadata(parentType);
|
||||
Object.keys(parentPropMetadata).forEach((parentProp) => {
|
||||
propMetadata[parentProp] = parentPropMetadata[parentProp];
|
||||
});
|
||||
}
|
||||
const parentType = this.findParentType(type, classMetadata);
|
||||
if (parentType) {
|
||||
const parentPropMetadata = this.propMetadata(parentType);
|
||||
Object.keys(parentPropMetadata).forEach((parentProp) => {
|
||||
propMetadata[parentProp] = parentPropMetadata[parentProp];
|
||||
});
|
||||
}
|
||||
|
||||
const members = classMetadata['members'] || {};
|
||||
@ -157,6 +189,7 @@ export class StaticReflector implements ɵReflectorReader {
|
||||
let parameters = this.parameterCache.get(type);
|
||||
if (!parameters) {
|
||||
const classMetadata = this.getTypeMetadata(type);
|
||||
const parentType = this.findParentType(type, classMetadata);
|
||||
const members = classMetadata ? classMetadata['members'] : null;
|
||||
const ctorData = members ? members['__ctor__'] : null;
|
||||
if (ctorData) {
|
||||
@ -175,11 +208,8 @@ export class StaticReflector implements ɵReflectorReader {
|
||||
}
|
||||
parameters.push(nestedResult);
|
||||
});
|
||||
} else if (classMetadata['extends']) {
|
||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
||||
if (parentType instanceof StaticSymbol) {
|
||||
parameters = this.parameters(parentType);
|
||||
}
|
||||
} else if (parentType) {
|
||||
parameters = this.parameters(parentType);
|
||||
}
|
||||
if (!parameters) {
|
||||
parameters = [];
|
||||
@ -198,14 +228,12 @@ export class StaticReflector implements ɵReflectorReader {
|
||||
if (!methodNames) {
|
||||
const classMetadata = this.getTypeMetadata(type);
|
||||
methodNames = {};
|
||||
if (classMetadata['extends']) {
|
||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
||||
if (parentType instanceof StaticSymbol) {
|
||||
const parentMethodNames = this._methodNames(parentType);
|
||||
Object.keys(parentMethodNames).forEach((parentProp) => {
|
||||
methodNames[parentProp] = parentMethodNames[parentProp];
|
||||
});
|
||||
}
|
||||
const parentType = this.findParentType(type, classMetadata);
|
||||
if (parentType) {
|
||||
const parentMethodNames = this._methodNames(parentType);
|
||||
Object.keys(parentMethodNames).forEach((parentProp) => {
|
||||
methodNames[parentProp] = parentMethodNames[parentProp];
|
||||
});
|
||||
}
|
||||
|
||||
const members = classMetadata['members'] || {};
|
||||
@ -219,6 +247,13 @@ export class StaticReflector implements ɵReflectorReader {
|
||||
return methodNames;
|
||||
}
|
||||
|
||||
private findParentType(type: StaticSymbol, classMetadata: any): StaticSymbol|null {
|
||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
||||
if (parentType instanceof StaticSymbol) {
|
||||
return parentType;
|
||||
}
|
||||
}
|
||||
|
||||
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
||||
if (!(type instanceof StaticSymbol)) {
|
||||
this.reportError(
|
||||
|
@ -307,6 +307,17 @@ export class StaticSymbolResolver {
|
||||
private createResolvedSymbol(
|
||||
sourceSymbol: StaticSymbol, topLevelPath: string, topLevelSymbolNames: Set<string>,
|
||||
metadata: any): ResolvedStaticSymbol {
|
||||
// For classes that don't have Angular summaries / metadata,
|
||||
// we only keep their arity, but nothing else
|
||||
// (e.g. their constructor parameters).
|
||||
// We do this to prevent introducing deep imports
|
||||
// as we didn't generate .ngfactory.ts files with proper reexports.
|
||||
if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath) && metadata &&
|
||||
metadata['__symbolic'] === 'class') {
|
||||
const transformedMeta = {__symbolic: 'class', arity: metadata.arity};
|
||||
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
|
||||
class ReferenceTransformer extends ValueTransformer {
|
||||
|
@ -53,7 +53,7 @@ export function serializeSummaries(
|
||||
// (in a minimal way).
|
||||
types.forEach((typeSummary) => {
|
||||
serializer.addOrMergeSummary(
|
||||
{symbol: typeSummary.type.reference, metadata: {__symbolic: 'class'}, type: typeSummary});
|
||||
{symbol: typeSummary.type.reference, metadata: null, type: typeSummary});
|
||||
if (typeSummary.summaryKind === CompileSummaryKind.NgModule) {
|
||||
const ngModuleSummary = <CompileNgModuleSummary>typeSummary;
|
||||
ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => {
|
||||
@ -94,10 +94,21 @@ class Serializer extends ValueTransformer {
|
||||
addOrMergeSummary(summary: Summary<StaticSymbol>) {
|
||||
let symbolMeta = summary.metadata;
|
||||
if (symbolMeta && symbolMeta.__symbolic === 'class') {
|
||||
// For classes, we only keep their statics and arity, but not the metadata
|
||||
// of the class itself as that has been captured already via other summaries
|
||||
// (e.g. DirectiveSummary, ...).
|
||||
symbolMeta = {__symbolic: 'class', statics: symbolMeta.statics, arity: symbolMeta.arity};
|
||||
// For classes, we keep everything except their class decorators.
|
||||
// We need to keep e.g. the ctor args, method names, method decorators
|
||||
// so that the class can be extended in another compilation unit.
|
||||
// We don't keep the class decorators as
|
||||
// 1) they refer to data
|
||||
// that should not cause a rebuild of downstream compilation units
|
||||
// (e.g. inline templates of @Component, or @NgModule.declarations)
|
||||
// 2) their data is already captured in TypeSummaries, e.g. DirectiveSummary.
|
||||
const clone: {[key: string]: any} = {};
|
||||
Object.keys(symbolMeta).forEach((propName) => {
|
||||
if (propName !== 'decorators') {
|
||||
clone[propName] = symbolMeta[propName];
|
||||
}
|
||||
});
|
||||
symbolMeta = clone;
|
||||
}
|
||||
|
||||
let processedSummary = this.processedSummaryBySymbol.get(summary.symbol);
|
||||
|
@ -94,7 +94,7 @@ export class Extractor {
|
||||
const symbolCache = new StaticSymbolCache();
|
||||
const summaryResolver = new AotSummaryResolver(host, symbolCache);
|
||||
const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver);
|
||||
const staticReflector = new StaticReflector(staticSymbolResolver);
|
||||
const staticReflector = new StaticReflector(summaryResolver, staticSymbolResolver);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
|
||||
const config =
|
||||
|
@ -933,14 +933,12 @@ export class CompileMetadataResolver {
|
||||
const dirMeta = this.getNonNormalizedDirectiveMetadata(dirType);
|
||||
if (dirMeta && dirMeta.metadata.isComponent) {
|
||||
return {componentType: dirType, componentFactory: dirMeta.metadata.componentFactory};
|
||||
} else {
|
||||
const dirSummary =
|
||||
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
|
||||
if (dirSummary && dirSummary.isComponent) {
|
||||
return {componentType: dirType, componentFactory: dirSummary.componentFactory};
|
||||
}
|
||||
}
|
||||
|
||||
const dirSummary =
|
||||
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
|
||||
if (dirSummary && dirSummary.isComponent) {
|
||||
return {componentType: dirType, componentFactory: dirSummary.componentFactory};
|
||||
}
|
||||
if (throwIfNotFound) {
|
||||
throw syntaxError(`${dirType.name} cannot be used as an entry component.`);
|
||||
}
|
||||
|
Reference in New Issue
Block a user