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:
parent
28bf222a6a
commit
8ef621ad2a
|
@ -124,7 +124,7 @@ export class NgTools_InternalApi_NG_2 {
|
||||||
const symbolCache = new StaticSymbolCache();
|
const symbolCache = new StaticSymbolCache();
|
||||||
const summaryResolver = new AotSummaryResolver(ngCompilerHost, symbolCache);
|
const summaryResolver = new AotSummaryResolver(ngCompilerHost, symbolCache);
|
||||||
const symbolResolver = new StaticSymbolResolver(ngCompilerHost, symbolCache, summaryResolver);
|
const symbolResolver = new StaticSymbolResolver(ngCompilerHost, symbolCache, summaryResolver);
|
||||||
const staticReflector = new StaticReflector(symbolResolver);
|
const staticReflector = new StaticReflector(summaryResolver, symbolResolver);
|
||||||
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
|
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
|
||||||
|
|
||||||
return Object.keys(routeMap).reduce(
|
return Object.keys(routeMap).reduce(
|
||||||
|
|
|
@ -47,7 +47,7 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
|
||||||
const symbolCache = new StaticSymbolCache();
|
const symbolCache = new StaticSymbolCache();
|
||||||
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
|
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
|
||||||
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
|
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
|
||||||
const staticReflector = new StaticReflector(symbolResolver);
|
const staticReflector = new StaticReflector(summaryResolver, symbolResolver);
|
||||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||||
const console = new Console();
|
const console = new Console();
|
||||||
const htmlParser = new I18NHtmlParser(
|
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 {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 {syntaxError} from '../util';
|
||||||
|
|
||||||
import {StaticSymbol} from './static_symbol';
|
import {StaticSymbol} from './static_symbol';
|
||||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
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 conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
||||||
private injectionToken: StaticSymbol;
|
private injectionToken: StaticSymbol;
|
||||||
private opaqueToken: StaticSymbol;
|
private opaqueToken: StaticSymbol;
|
||||||
|
private annotationForParentClassWithSummaryKind = new Map<CompileSummaryKind, any[]>();
|
||||||
|
private annotationNames = new Map<any, string>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private summaryResolver: SummaryResolver<StaticSymbol>,
|
||||||
private symbolResolver: StaticSymbolResolver,
|
private symbolResolver: StaticSymbolResolver,
|
||||||
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
|
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
|
||||||
knownMetadataFunctions: {name: string, filePath: string, fn: 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));
|
this.getStaticSymbol(kc.filePath, kc.name), kc.ctor));
|
||||||
knownMetadataFunctions.forEach(
|
knownMetadataFunctions.forEach(
|
||||||
(kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn));
|
(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 {
|
importUri(typeOrFunc: StaticSymbol): string {
|
||||||
|
@ -96,17 +114,33 @@ export class StaticReflector implements ɵReflectorReader {
|
||||||
if (!annotations) {
|
if (!annotations) {
|
||||||
annotations = [];
|
annotations = [];
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
const classMetadata = this.getTypeMetadata(type);
|
||||||
if (classMetadata['extends']) {
|
const parentType = this.findParentType(type, classMetadata);
|
||||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
if (parentType) {
|
||||||
if (parentType && (parentType instanceof StaticSymbol)) {
|
|
||||||
const parentAnnotations = this.annotations(parentType);
|
const parentAnnotations = this.annotations(parentType);
|
||||||
annotations.push(...parentAnnotations);
|
annotations.push(...parentAnnotations);
|
||||||
}
|
}
|
||||||
}
|
let ownAnnotations: any[] = [];
|
||||||
if (classMetadata['decorators']) {
|
if (classMetadata['decorators']) {
|
||||||
const ownAnnotations: any[] = this.simplify(type, classMetadata['decorators']);
|
ownAnnotations = this.simplify(type, classMetadata['decorators']);
|
||||||
annotations.push(...ownAnnotations);
|
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));
|
this.annotationCache.set(type, annotations.filter(ann => !!ann));
|
||||||
}
|
}
|
||||||
return annotations;
|
return annotations;
|
||||||
|
@ -117,15 +151,13 @@ export class StaticReflector implements ɵReflectorReader {
|
||||||
if (!propMetadata) {
|
if (!propMetadata) {
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
const classMetadata = this.getTypeMetadata(type);
|
||||||
propMetadata = {};
|
propMetadata = {};
|
||||||
if (classMetadata['extends']) {
|
const parentType = this.findParentType(type, classMetadata);
|
||||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
if (parentType) {
|
||||||
if (parentType instanceof StaticSymbol) {
|
|
||||||
const parentPropMetadata = this.propMetadata(parentType);
|
const parentPropMetadata = this.propMetadata(parentType);
|
||||||
Object.keys(parentPropMetadata).forEach((parentProp) => {
|
Object.keys(parentPropMetadata).forEach((parentProp) => {
|
||||||
propMetadata[parentProp] = parentPropMetadata[parentProp];
|
propMetadata[parentProp] = parentPropMetadata[parentProp];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const members = classMetadata['members'] || {};
|
const members = classMetadata['members'] || {};
|
||||||
Object.keys(members).forEach((propName) => {
|
Object.keys(members).forEach((propName) => {
|
||||||
|
@ -157,6 +189,7 @@ export class StaticReflector implements ɵReflectorReader {
|
||||||
let parameters = this.parameterCache.get(type);
|
let parameters = this.parameterCache.get(type);
|
||||||
if (!parameters) {
|
if (!parameters) {
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
const classMetadata = this.getTypeMetadata(type);
|
||||||
|
const parentType = this.findParentType(type, classMetadata);
|
||||||
const members = classMetadata ? classMetadata['members'] : null;
|
const members = classMetadata ? classMetadata['members'] : null;
|
||||||
const ctorData = members ? members['__ctor__'] : null;
|
const ctorData = members ? members['__ctor__'] : null;
|
||||||
if (ctorData) {
|
if (ctorData) {
|
||||||
|
@ -175,12 +208,9 @@ export class StaticReflector implements ɵReflectorReader {
|
||||||
}
|
}
|
||||||
parameters.push(nestedResult);
|
parameters.push(nestedResult);
|
||||||
});
|
});
|
||||||
} else if (classMetadata['extends']) {
|
} else if (parentType) {
|
||||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
|
||||||
if (parentType instanceof StaticSymbol) {
|
|
||||||
parameters = this.parameters(parentType);
|
parameters = this.parameters(parentType);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!parameters) {
|
if (!parameters) {
|
||||||
parameters = [];
|
parameters = [];
|
||||||
}
|
}
|
||||||
|
@ -198,15 +228,13 @@ export class StaticReflector implements ɵReflectorReader {
|
||||||
if (!methodNames) {
|
if (!methodNames) {
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
const classMetadata = this.getTypeMetadata(type);
|
||||||
methodNames = {};
|
methodNames = {};
|
||||||
if (classMetadata['extends']) {
|
const parentType = this.findParentType(type, classMetadata);
|
||||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
if (parentType) {
|
||||||
if (parentType instanceof StaticSymbol) {
|
|
||||||
const parentMethodNames = this._methodNames(parentType);
|
const parentMethodNames = this._methodNames(parentType);
|
||||||
Object.keys(parentMethodNames).forEach((parentProp) => {
|
Object.keys(parentMethodNames).forEach((parentProp) => {
|
||||||
methodNames[parentProp] = parentMethodNames[parentProp];
|
methodNames[parentProp] = parentMethodNames[parentProp];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const members = classMetadata['members'] || {};
|
const members = classMetadata['members'] || {};
|
||||||
Object.keys(members).forEach((propName) => {
|
Object.keys(members).forEach((propName) => {
|
||||||
|
@ -219,6 +247,13 @@ export class StaticReflector implements ɵReflectorReader {
|
||||||
return methodNames;
|
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 {
|
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
||||||
if (!(type instanceof StaticSymbol)) {
|
if (!(type instanceof StaticSymbol)) {
|
||||||
this.reportError(
|
this.reportError(
|
||||||
|
|
|
@ -307,6 +307,17 @@ export class StaticSymbolResolver {
|
||||||
private createResolvedSymbol(
|
private createResolvedSymbol(
|
||||||
sourceSymbol: StaticSymbol, topLevelPath: string, topLevelSymbolNames: Set<string>,
|
sourceSymbol: StaticSymbol, topLevelPath: string, topLevelSymbolNames: Set<string>,
|
||||||
metadata: any): ResolvedStaticSymbol {
|
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;
|
const self = this;
|
||||||
|
|
||||||
class ReferenceTransformer extends ValueTransformer {
|
class ReferenceTransformer extends ValueTransformer {
|
||||||
|
|
|
@ -53,7 +53,7 @@ export function serializeSummaries(
|
||||||
// (in a minimal way).
|
// (in a minimal way).
|
||||||
types.forEach((typeSummary) => {
|
types.forEach((typeSummary) => {
|
||||||
serializer.addOrMergeSummary(
|
serializer.addOrMergeSummary(
|
||||||
{symbol: typeSummary.type.reference, metadata: {__symbolic: 'class'}, type: typeSummary});
|
{symbol: typeSummary.type.reference, metadata: null, type: typeSummary});
|
||||||
if (typeSummary.summaryKind === CompileSummaryKind.NgModule) {
|
if (typeSummary.summaryKind === CompileSummaryKind.NgModule) {
|
||||||
const ngModuleSummary = <CompileNgModuleSummary>typeSummary;
|
const ngModuleSummary = <CompileNgModuleSummary>typeSummary;
|
||||||
ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => {
|
ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => {
|
||||||
|
@ -94,10 +94,21 @@ class Serializer extends ValueTransformer {
|
||||||
addOrMergeSummary(summary: Summary<StaticSymbol>) {
|
addOrMergeSummary(summary: Summary<StaticSymbol>) {
|
||||||
let symbolMeta = summary.metadata;
|
let symbolMeta = summary.metadata;
|
||||||
if (symbolMeta && symbolMeta.__symbolic === 'class') {
|
if (symbolMeta && symbolMeta.__symbolic === 'class') {
|
||||||
// For classes, we only keep their statics and arity, but not the metadata
|
// For classes, we keep everything except their class decorators.
|
||||||
// of the class itself as that has been captured already via other summaries
|
// We need to keep e.g. the ctor args, method names, method decorators
|
||||||
// (e.g. DirectiveSummary, ...).
|
// so that the class can be extended in another compilation unit.
|
||||||
symbolMeta = {__symbolic: 'class', statics: symbolMeta.statics, arity: symbolMeta.arity};
|
// 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);
|
let processedSummary = this.processedSummaryBySymbol.get(summary.symbol);
|
||||||
|
|
|
@ -94,7 +94,7 @@ export class Extractor {
|
||||||
const symbolCache = new StaticSymbolCache();
|
const symbolCache = new StaticSymbolCache();
|
||||||
const summaryResolver = new AotSummaryResolver(host, symbolCache);
|
const summaryResolver = new AotSummaryResolver(host, symbolCache);
|
||||||
const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver);
|
const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver);
|
||||||
const staticReflector = new StaticReflector(staticSymbolResolver);
|
const staticReflector = new StaticReflector(summaryResolver, staticSymbolResolver);
|
||||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||||
|
|
||||||
const config =
|
const config =
|
||||||
|
|
|
@ -933,14 +933,12 @@ export class CompileMetadataResolver {
|
||||||
const dirMeta = this.getNonNormalizedDirectiveMetadata(dirType);
|
const dirMeta = this.getNonNormalizedDirectiveMetadata(dirType);
|
||||||
if (dirMeta && dirMeta.metadata.isComponent) {
|
if (dirMeta && dirMeta.metadata.isComponent) {
|
||||||
return {componentType: dirType, componentFactory: dirMeta.metadata.componentFactory};
|
return {componentType: dirType, componentFactory: dirMeta.metadata.componentFactory};
|
||||||
} else {
|
}
|
||||||
const dirSummary =
|
const dirSummary =
|
||||||
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
|
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
|
||||||
if (dirSummary && dirSummary.isComponent) {
|
if (dirSummary && dirSummary.isComponent) {
|
||||||
return {componentType: dirType, componentFactory: dirSummary.componentFactory};
|
return {componentType: dirType, componentFactory: dirSummary.componentFactory};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (throwIfNotFound) {
|
if (throwIfNotFound) {
|
||||||
throw syntaxError(`${dirType.name} cannot be used as an entry component.`);
|
throw syntaxError(`${dirType.name} cannot be used as an entry component.`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
|
import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
|
||||||
import {RenderComponentType, ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
|
import {RenderComponentType, ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
|
||||||
|
import {NodeFlags} from '@angular/core/src/view/index';
|
||||||
import {async} from '@angular/core/testing';
|
import {async} from '@angular/core/testing';
|
||||||
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
|
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
@ -368,6 +369,293 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('inheritance with summaries', () => {
|
||||||
|
function compileWithSummaries(
|
||||||
|
libInput: MockData, appInput: MockData): Promise<GeneratedFile[]> {
|
||||||
|
const libHost = new MockCompilerHost(['/lib/base.ts'], libInput, angularFiles);
|
||||||
|
const libAotHost = new MockAotCompilerHost(libHost);
|
||||||
|
libAotHost.tsFilesOnly();
|
||||||
|
const appHost = new MockCompilerHost(['/app/main.ts'], appInput, angularFiles);
|
||||||
|
const appAotHost = new MockAotCompilerHost(appHost);
|
||||||
|
appAotHost.tsFilesOnly();
|
||||||
|
return compile(libHost, libAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit)
|
||||||
|
.then(() => {
|
||||||
|
libHost.writtenFiles.forEach((value, key) => appHost.writeFile(key, value, false));
|
||||||
|
libHost.overrides.forEach((value, key) => appHost.override(key, value));
|
||||||
|
|
||||||
|
return compile(appHost, appAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileParentAndChild(
|
||||||
|
{parentClassDecorator, parentModuleDecorator, childClassDecorator, childModuleDecorator}: {
|
||||||
|
parentClassDecorator: string,
|
||||||
|
parentModuleDecorator: string,
|
||||||
|
childClassDecorator: string,
|
||||||
|
childModuleDecorator: string
|
||||||
|
}) {
|
||||||
|
const libInput: MockData = {
|
||||||
|
'lib': {
|
||||||
|
'base.ts': `
|
||||||
|
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
${parentClassDecorator}
|
||||||
|
export class Base {}
|
||||||
|
|
||||||
|
${parentModuleDecorator}
|
||||||
|
export class BaseModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const appInput: MockData = {
|
||||||
|
'app': {
|
||||||
|
'main.ts': `
|
||||||
|
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
|
||||||
|
import {Base} from '../lib/base';
|
||||||
|
|
||||||
|
${childClassDecorator}
|
||||||
|
export class Extends extends Base {}
|
||||||
|
|
||||||
|
${childModuleDecorator}
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return compileWithSummaries(libInput, appInput)
|
||||||
|
.then((generatedFiles) => generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts'));
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should inherit ctor and lifecycle hooks from classes in other compilation units',
|
||||||
|
async(() => {
|
||||||
|
const libInput: MockData = {
|
||||||
|
'lib': {
|
||||||
|
'base.ts': `
|
||||||
|
export class AParam {}
|
||||||
|
|
||||||
|
export class Base {
|
||||||
|
constructor(a: AParam) {}
|
||||||
|
ngOnDestroy() {}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const appInput: MockData = {
|
||||||
|
'app': {
|
||||||
|
'main.ts': `
|
||||||
|
import {NgModule, Component} from '@angular/core';
|
||||||
|
import {Base} from '../lib/base';
|
||||||
|
|
||||||
|
@Component({template: ''})
|
||||||
|
export class Extends extends Base {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Extends]
|
||||||
|
})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
compileWithSummaries(libInput, appInput).then((generatedFiles) => {
|
||||||
|
const mainNgFactory = generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||||
|
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||||
|
expect(mainNgFactory.source)
|
||||||
|
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam]`);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should inherit ctor and lifecycle hooks from classes in other compilation units over 2 levels',
|
||||||
|
async(() => {
|
||||||
|
const lib1Input: MockData = {
|
||||||
|
'lib1': {
|
||||||
|
'base.ts': `
|
||||||
|
export class AParam {}
|
||||||
|
|
||||||
|
export class Base {
|
||||||
|
constructor(a: AParam) {}
|
||||||
|
ngOnDestroy() {}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const lib2Input: MockData = {
|
||||||
|
'lib2': {
|
||||||
|
'middle.ts': `
|
||||||
|
import {Base} from '../lib1/base';
|
||||||
|
export class Middle extends Base {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const appInput: MockData = {
|
||||||
|
'app': {
|
||||||
|
'main.ts': `
|
||||||
|
import {NgModule, Component} from '@angular/core';
|
||||||
|
import {Middle} from '../lib2/middle';
|
||||||
|
|
||||||
|
@Component({template: ''})
|
||||||
|
export class Extends extends Middle {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Extends]
|
||||||
|
})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const lib1Host = new MockCompilerHost(['/lib1/base.ts'], lib1Input, angularFiles);
|
||||||
|
const lib1AotHost = new MockAotCompilerHost(lib1Host);
|
||||||
|
lib1AotHost.tsFilesOnly();
|
||||||
|
const lib2Host = new MockCompilerHost(['/lib2/middle.ts'], lib2Input, angularFiles);
|
||||||
|
const lib2AotHost = new MockAotCompilerHost(lib2Host);
|
||||||
|
lib2AotHost.tsFilesOnly();
|
||||||
|
const appHost = new MockCompilerHost(['/app/main.ts'], appInput, angularFiles);
|
||||||
|
const appAotHost = new MockAotCompilerHost(appHost);
|
||||||
|
appAotHost.tsFilesOnly();
|
||||||
|
compile(lib1Host, lib1AotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit)
|
||||||
|
.then(() => {
|
||||||
|
lib1Host.writtenFiles.forEach((value, key) => lib2Host.writeFile(key, value, false));
|
||||||
|
lib1Host.overrides.forEach((value, key) => lib2Host.override(key, value));
|
||||||
|
return compile(
|
||||||
|
lib2Host, lib2AotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
lib2Host.writtenFiles.forEach((value, key) => appHost.writeFile(key, value, false));
|
||||||
|
lib2Host.overrides.forEach((value, key) => appHost.override(key, value));
|
||||||
|
return compile(appHost, appAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
|
||||||
|
})
|
||||||
|
.then((generatedFiles) => {
|
||||||
|
const mainNgFactory = generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||||
|
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||||
|
expect(mainNgFactory.source)
|
||||||
|
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam_2]`);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Injectable', () => {
|
||||||
|
it('should allow to inherit', async(() => {
|
||||||
|
compileParentAndChild({
|
||||||
|
parentClassDecorator: '@Injectable()',
|
||||||
|
parentModuleDecorator: '@NgModule({providers: [Base]})',
|
||||||
|
childClassDecorator: '@Injectable()',
|
||||||
|
childModuleDecorator: '@NgModule({providers: [Extends]})',
|
||||||
|
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); });
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should error if the child class has no matching decorator', async(() => {
|
||||||
|
compileParentAndChild({
|
||||||
|
parentClassDecorator: '@Injectable()',
|
||||||
|
parentModuleDecorator: '@NgModule({providers: [Base]})',
|
||||||
|
childClassDecorator: '',
|
||||||
|
childModuleDecorator: '@NgModule({providers: [Extends]})',
|
||||||
|
}).then(fail, (e) => {
|
||||||
|
expect(e.message).toContain(
|
||||||
|
'Class Extends in /app/main.ts extends from a Injectable in another compilation unit without duplicating the decorator. ' +
|
||||||
|
'Please add a Injectable or Pipe or Directive or Component or NgModule decorator to the class.');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Component', () => {
|
||||||
|
it('should allow to inherit', async(() => {
|
||||||
|
compileParentAndChild({
|
||||||
|
parentClassDecorator: `@Component({template: ''})`,
|
||||||
|
parentModuleDecorator: '@NgModule({declarations: [Base]})',
|
||||||
|
childClassDecorator: `@Component({template: ''})`,
|
||||||
|
childModuleDecorator: '@NgModule({declarations: [Extends]})',
|
||||||
|
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); });
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should error if the child class has no matching decorator', async(() => {
|
||||||
|
compileParentAndChild({
|
||||||
|
parentClassDecorator: `@Component({template: ''})`,
|
||||||
|
parentModuleDecorator: '@NgModule({declarations: [Base]})',
|
||||||
|
childClassDecorator: '',
|
||||||
|
childModuleDecorator: '@NgModule({declarations: [Extends]})',
|
||||||
|
}).then(fail, (e) => {
|
||||||
|
expect(e.message).toContain(
|
||||||
|
'Class Extends in /app/main.ts extends from a Directive in another compilation unit without duplicating the decorator. ' +
|
||||||
|
'Please add a Directive or Component decorator to the class.');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Directive', () => {
|
||||||
|
it('should allow to inherit', async(() => {
|
||||||
|
compileParentAndChild({
|
||||||
|
parentClassDecorator: `@Directive({selector: '[someDir]'})`,
|
||||||
|
parentModuleDecorator: '@NgModule({declarations: [Base]})',
|
||||||
|
childClassDecorator: `@Directive({selector: '[someDir]'})`,
|
||||||
|
childModuleDecorator: '@NgModule({declarations: [Extends]})',
|
||||||
|
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); });
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should error if the child class has no matching decorator', async(() => {
|
||||||
|
compileParentAndChild({
|
||||||
|
parentClassDecorator: `@Directive({selector: '[someDir]'})`,
|
||||||
|
parentModuleDecorator: '@NgModule({declarations: [Base]})',
|
||||||
|
childClassDecorator: '',
|
||||||
|
childModuleDecorator: '@NgModule({declarations: [Extends]})',
|
||||||
|
}).then(fail, (e) => {
|
||||||
|
expect(e.message).toContain(
|
||||||
|
'Class Extends in /app/main.ts extends from a Directive in another compilation unit without duplicating the decorator. ' +
|
||||||
|
'Please add a Directive or Component decorator to the class.');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Pipe', () => {
|
||||||
|
it('should allow to inherit', async(() => {
|
||||||
|
compileParentAndChild({
|
||||||
|
parentClassDecorator: `@Pipe({name: 'somePipe'})`,
|
||||||
|
parentModuleDecorator: '@NgModule({declarations: [Base]})',
|
||||||
|
childClassDecorator: `@Pipe({name: 'somePipe'})`,
|
||||||
|
childModuleDecorator: '@NgModule({declarations: [Extends]})',
|
||||||
|
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); });
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should error if the child class has no matching decorator', async(() => {
|
||||||
|
compileParentAndChild({
|
||||||
|
parentClassDecorator: `@Pipe({name: 'somePipe'})`,
|
||||||
|
parentModuleDecorator: '@NgModule({declarations: [Base]})',
|
||||||
|
childClassDecorator: '',
|
||||||
|
childModuleDecorator: '@NgModule({declarations: [Extends]})',
|
||||||
|
}).then(fail, (e) => {
|
||||||
|
expect(e.message).toContain(
|
||||||
|
'Class Extends in /app/main.ts extends from a Pipe in another compilation unit without duplicating the decorator. ' +
|
||||||
|
'Please add a Pipe decorator to the class.');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('NgModule', () => {
|
||||||
|
it('should allow to inherit', async(() => {
|
||||||
|
compileParentAndChild({
|
||||||
|
parentClassDecorator: `@NgModule()`,
|
||||||
|
parentModuleDecorator: '',
|
||||||
|
childClassDecorator: `@NgModule()`,
|
||||||
|
childModuleDecorator: '',
|
||||||
|
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); });
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should error if the child class has no matching decorator', async(() => {
|
||||||
|
compileParentAndChild({
|
||||||
|
parentClassDecorator: `@NgModule()`,
|
||||||
|
parentModuleDecorator: '',
|
||||||
|
childClassDecorator: '',
|
||||||
|
childModuleDecorator: '',
|
||||||
|
}).then(fail, (e) => {
|
||||||
|
expect(e.message).toContain(
|
||||||
|
'Class Extends in /app/main.ts extends from a NgModule in another compilation unit without duplicating the decorator. ' +
|
||||||
|
'Please add a NgModule decorator to the class.');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compiler (bundled Angular)', () => {
|
describe('compiler (bundled Angular)', () => {
|
||||||
|
@ -513,6 +801,11 @@ function expectNoDiagnostics(program: ts.Program) {
|
||||||
expectNoDiagnostics(program.getSemanticDiagnostics());
|
expectNoDiagnostics(program.getSemanticDiagnostics());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expectNoDiagnosticsAndEmit(program: ts.Program) {
|
||||||
|
expectNoDiagnostics(program);
|
||||||
|
program.emit();
|
||||||
|
}
|
||||||
|
|
||||||
function isDTS(fileName: string): boolean {
|
function isDTS(fileName: string): boolean {
|
||||||
return /\.d\.ts$/.test(fileName);
|
return /\.d\.ts$/.test(fileName);
|
||||||
}
|
}
|
||||||
|
@ -544,7 +837,7 @@ function summaryCompile(
|
||||||
function compile(
|
function compile(
|
||||||
host: MockCompilerHost, aotHost: AotCompilerHost, preCompile?: (program: ts.Program) => void,
|
host: MockCompilerHost, aotHost: AotCompilerHost, preCompile?: (program: ts.Program) => void,
|
||||||
postCompile: (program: ts.Program) => void = expectNoDiagnostics,
|
postCompile: (program: ts.Program) => void = expectNoDiagnostics,
|
||||||
options: AotCompilerOptions = {}) {
|
options: AotCompilerOptions = {}): Promise<GeneratedFile[]> {
|
||||||
const scripts = host.scriptNames.slice(0);
|
const scripts = host.scriptNames.slice(0);
|
||||||
const program = ts.createProgram(scripts, settings, host);
|
const program = ts.createProgram(scripts, settings, host);
|
||||||
if (preCompile) preCompile(program);
|
if (preCompile) preCompile(program);
|
||||||
|
|
|
@ -24,9 +24,10 @@ describe('StaticReflector', () => {
|
||||||
errorRecorder?: (error: any, fileName: string) => void, collectorOptions?: CollectorOptions) {
|
errorRecorder?: (error: any, fileName: string) => void, collectorOptions?: CollectorOptions) {
|
||||||
const symbolCache = new StaticSymbolCache();
|
const symbolCache = new StaticSymbolCache();
|
||||||
host = new MockStaticSymbolResolverHost(testData, collectorOptions);
|
host = new MockStaticSymbolResolverHost(testData, collectorOptions);
|
||||||
symbolResolver =
|
const summaryResolver = new MockSummaryResolver([]);
|
||||||
new StaticSymbolResolver(host, symbolCache, new MockSummaryResolver([]), errorRecorder);
|
spyOn(summaryResolver, 'isLibraryFile').and.returnValue(false);
|
||||||
reflector = new StaticReflector(symbolResolver, decorators, [], errorRecorder);
|
symbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver, errorRecorder);
|
||||||
|
reflector = new StaticReflector(summaryResolver, symbolResolver, decorators, [], errorRecorder);
|
||||||
noContext = reflector.getStaticSymbol('', '');
|
noContext = reflector.getStaticSymbol('', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -283,6 +283,31 @@ describe('StaticSymbolResolver', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should only use the arity for classes from libraries without summaries', () => {
|
||||||
|
init({
|
||||||
|
'/test.d.ts': [{
|
||||||
|
'__symbolic': 'module',
|
||||||
|
'version': 3,
|
||||||
|
'metadata': {
|
||||||
|
'AParam': {__symbolic: 'class'},
|
||||||
|
'AClass': {
|
||||||
|
__symbolic: 'class',
|
||||||
|
arity: 1,
|
||||||
|
members: {
|
||||||
|
__ctor__: [{
|
||||||
|
__symbolic: 'constructor',
|
||||||
|
parameters: [symbolCache.get('/test.d.ts', 'AParam')]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(symbolResolver.resolveSymbol(symbolCache.get('/test.d.ts', 'AClass')).metadata)
|
||||||
|
.toEqual({__symbolic: 'class', arity: 1});
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to trace a named export', () => {
|
it('should be able to trace a named export', () => {
|
||||||
const symbol = symbolResolver
|
const symbol = symbolResolver
|
||||||
.resolveSymbol(symbolResolver.getSymbolByModule(
|
.resolveSymbol(symbolResolver.getSymbolByModule(
|
||||||
|
|
|
@ -61,7 +61,8 @@ export function main() {
|
||||||
metadata: {
|
metadata: {
|
||||||
__symbolic: 'class',
|
__symbolic: 'class',
|
||||||
members: {'aMethod': {__symbolic: 'function'}},
|
members: {'aMethod': {__symbolic: 'function'}},
|
||||||
statics: {aStatic: true}
|
statics: {aStatic: true},
|
||||||
|
decorators: ['aDecoratorData']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -88,8 +89,12 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService'));
|
expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService'));
|
||||||
// serialization should only keep the statics...
|
// serialization should drop class decorators
|
||||||
expect(summaries[1].metadata).toEqual({__symbolic: 'class', statics: {aStatic: true}});
|
expect(summaries[1].metadata).toEqual({
|
||||||
|
__symbolic: 'class',
|
||||||
|
members: {aMethod: {__symbolic: 'function'}},
|
||||||
|
statics: {aStatic: true}
|
||||||
|
});
|
||||||
expect(summaries[1].type.type.reference)
|
expect(summaries[1].type.type.reference)
|
||||||
.toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService'));
|
.toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -177,8 +177,8 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||||
|
|
||||||
private angularSourcePath: string|undefined;
|
private angularSourcePath: string|undefined;
|
||||||
private nodeModulesPath: string|undefined;
|
private nodeModulesPath: string|undefined;
|
||||||
private overrides = new Map<string, string>();
|
public overrides = new Map<string, string>();
|
||||||
private writtenFiles = new Map<string, string>();
|
public writtenFiles = new Map<string, string>();
|
||||||
private sourceFiles = new Map<string, ts.SourceFile>();
|
private sourceFiles = new Map<string, ts.SourceFile>();
|
||||||
private assumeExists = new Set<string>();
|
private assumeExists = new Set<string>();
|
||||||
private traces: string[] = [];
|
private traces: string[] = [];
|
||||||
|
|
|
@ -74,6 +74,7 @@ export class DummyResourceLoader extends ResourceLoader {
|
||||||
export class TypeScriptServiceHost implements LanguageServiceHost {
|
export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||||
private _resolver: CompileMetadataResolver;
|
private _resolver: CompileMetadataResolver;
|
||||||
private _staticSymbolCache = new StaticSymbolCache();
|
private _staticSymbolCache = new StaticSymbolCache();
|
||||||
|
private _summaryResolver: AotSummaryResolver;
|
||||||
private _staticSymbolResolver: StaticSymbolResolver;
|
private _staticSymbolResolver: StaticSymbolResolver;
|
||||||
private _reflector: StaticReflector;
|
private _reflector: StaticReflector;
|
||||||
private _reflectorHost: ReflectorHost;
|
private _reflectorHost: ReflectorHost;
|
||||||
|
@ -407,7 +408,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||||
private get staticSymbolResolver(): StaticSymbolResolver {
|
private get staticSymbolResolver(): StaticSymbolResolver {
|
||||||
let result = this._staticSymbolResolver;
|
let result = this._staticSymbolResolver;
|
||||||
if (!result) {
|
if (!result) {
|
||||||
const summaryResolver = new AotSummaryResolver(
|
this._summaryResolver = new AotSummaryResolver(
|
||||||
{
|
{
|
||||||
loadSummary(filePath: string) { return null; },
|
loadSummary(filePath: string) { return null; },
|
||||||
isSourceFile(sourceFilePath: string) { return true; },
|
isSourceFile(sourceFilePath: string) { return true; },
|
||||||
|
@ -415,7 +416,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||||
},
|
},
|
||||||
this._staticSymbolCache);
|
this._staticSymbolCache);
|
||||||
result = this._staticSymbolResolver = new StaticSymbolResolver(
|
result = this._staticSymbolResolver = new StaticSymbolResolver(
|
||||||
this.reflectorHost, this._staticSymbolCache, summaryResolver,
|
this.reflectorHost, this._staticSymbolCache, this._summaryResolver,
|
||||||
(e, filePath) => this.collectError(e, filePath));
|
(e, filePath) => this.collectError(e, filePath));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -424,8 +425,9 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||||
private get reflector(): StaticReflector {
|
private get reflector(): StaticReflector {
|
||||||
let result = this._reflector;
|
let result = this._reflector;
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
const ssr = this.staticSymbolResolver;
|
||||||
result = this._reflector = new StaticReflector(
|
result = this._reflector = new StaticReflector(
|
||||||
this.staticSymbolResolver, [], [], (e, filePath) => this.collectError(e, filePath));
|
this._summaryResolver, ssr, [], [], (e, filePath) => this.collectError(e, filePath));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue