Skip to content

Commit f5c8e09

Browse files
tboschvsavkin
authored andcommitted
feat(core): properly support inheritance
## Inheritance Semantics: Decorators: 1) list the decorators of the class and its parents in the ancestor first order 2) only use the last decorator of each kind (e.g. @component / ...) Constructor parameters: If a class inherits from a parent class and does not declare a constructor, it inherits the parent class constructor, and with it the parameter metadata of that parent class. Lifecycle hooks: Follow the normal class inheritance model, i.e. lifecycle hooks of parent classes will be called even if the method is not overwritten in the child class. ## Example E.g. the following is a valid use of inheritance and it will also inherit all metadata: ``` @directive({selector: 'someDir'}) class ParentDirective { constructor(someDep: SomeDep) {} ngOnInit() {} } class ChildDirective extends ParentDirective {} ``` Closes angular#11606 Closes angular#12892
1 parent 4a09251 commit f5c8e09

File tree

19 files changed

+1102
-260
lines changed

19 files changed

+1102
-260
lines changed

modules/@angular/compiler-cli/src/compiler_host.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ export class CompilerHost implements AotCompilerHost {
186186
if (!v2Metadata && v1Metadata) {
187187
// patch up v1 to v2 by merging the metadata with metadata collected from the d.ts file
188188
// as the only difference between the versions is whether all exports are contained in
189-
// the metadata
189+
// the metadata and the `extends` clause.
190190
v2Metadata = {'__symbolic': 'module', 'version': 2, 'metadata': {}};
191191
if (v1Metadata.exports) {
192192
v2Metadata.exports = v1Metadata.exports;

modules/@angular/compiler-cli/test/aot_host_spec.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,11 @@ describe('CompilerHost', () => {
163163
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
164164
__symbolic: 'module',
165165
version: 2,
166-
metadata: {foo: {__symbolic: 'class'}, bar: {__symbolic: 'class'}}
166+
metadata: {
167+
foo: {__symbolic: 'class'},
168+
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
169+
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}}
170+
}
167171
}
168172
]);
169173
});
@@ -198,7 +202,12 @@ const FILES: Entry = {
198202
}
199203
},
200204
'metadata_versions': {
201-
'v1.d.ts': 'export declare class bar {}',
205+
'v1.d.ts': `
206+
export declare class Bar {
207+
ngOnInit() {}
208+
}
209+
export declare class BarChild extends Bar {}
210+
`,
202211
'v1.metadata.json':
203212
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
204213
}

modules/@angular/compiler/src/aot/static_reflector.ts

Lines changed: 111 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,24 @@ export class StaticSymbolCache {
7070
export class StaticReflector implements ReflectorReader {
7171
private declarationCache = new Map<string, StaticSymbol>();
7272
private annotationCache = new Map<StaticSymbol, any[]>();
73-
private propertyCache = new Map<StaticSymbol, {[key: string]: any}>();
73+
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
7474
private parameterCache = new Map<StaticSymbol, any[]>();
75+
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
7576
private metadataCache = new Map<string, {[key: string]: any}>();
7677
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
7778
private opaqueToken: StaticSymbol;
7879

7980
constructor(
8081
private host: StaticReflectorHost,
81-
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache()) {
82+
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache(),
83+
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
84+
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = []) {
8285
this.initializeConversionMap();
86+
knownMetadataClasses.forEach(
87+
(kc) => this._registerDecoratorOrConstructor(
88+
this.getStaticSymbol(kc.filePath, kc.name), kc.ctor));
89+
knownMetadataFunctions.forEach(
90+
(kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn));
8391
}
8492

8593
importUri(typeOrFunc: StaticSymbol): string {
@@ -99,29 +107,45 @@ export class StaticReflector implements ReflectorReader {
99107
public annotations(type: StaticSymbol): any[] {
100108
let annotations = this.annotationCache.get(type);
101109
if (!annotations) {
110+
annotations = [];
102111
const classMetadata = this.getTypeMetadata(type);
112+
if (classMetadata['extends']) {
113+
const parentAnnotations = this.annotations(this.simplify(type, classMetadata['extends']));
114+
annotations.push(...parentAnnotations);
115+
}
103116
if (classMetadata['decorators']) {
104-
annotations = this.simplify(type, classMetadata['decorators']);
105-
} else {
106-
annotations = [];
117+
const ownAnnotations: any[] = this.simplify(type, classMetadata['decorators']);
118+
annotations.push(...ownAnnotations);
107119
}
108120
this.annotationCache.set(type, annotations.filter(ann => !!ann));
109121
}
110122
return annotations;
111123
}
112124

113-
public propMetadata(type: StaticSymbol): {[key: string]: any} {
125+
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
114126
let propMetadata = this.propertyCache.get(type);
115127
if (!propMetadata) {
116-
const classMetadata = this.getTypeMetadata(type);
117-
const members = classMetadata ? classMetadata['members'] : {};
118-
propMetadata = mapStringMap(members, (propData, propName) => {
128+
const classMetadata = this.getTypeMetadata(type) || {};
129+
propMetadata = {};
130+
if (classMetadata['extends']) {
131+
const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends']));
132+
Object.keys(parentPropMetadata).forEach((parentProp) => {
133+
propMetadata[parentProp] = parentPropMetadata[parentProp];
134+
});
135+
}
136+
137+
const members = classMetadata['members'] || {};
138+
Object.keys(members).forEach((propName) => {
139+
const propData = members[propName];
119140
const prop = (<any[]>propData)
120141
.find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method');
142+
const decorators: any[] = [];
143+
if (propMetadata[propName]) {
144+
decorators.push(...propMetadata[propName]);
145+
}
146+
propMetadata[propName] = decorators;
121147
if (prop && prop['decorators']) {
122-
return this.simplify(type, prop['decorators']);
123-
} else {
124-
return [];
148+
decorators.push(...this.simplify(type, prop['decorators']));
125149
}
126150
});
127151
this.propertyCache.set(type, propMetadata);
@@ -155,6 +179,8 @@ export class StaticReflector implements ReflectorReader {
155179
}
156180
parameters.push(nestedResult);
157181
});
182+
} else if (classMetadata['extends']) {
183+
parameters = this.parameters(this.simplify(type, classMetadata['extends']));
158184
}
159185
if (!parameters) {
160186
parameters = [];
@@ -168,23 +194,47 @@ export class StaticReflector implements ReflectorReader {
168194
}
169195
}
170196

197+
private _methodNames(type: any): {[key: string]: boolean} {
198+
let methodNames = this.methodCache.get(type);
199+
if (!methodNames) {
200+
const classMetadata = this.getTypeMetadata(type) || {};
201+
methodNames = {};
202+
if (classMetadata['extends']) {
203+
const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends']));
204+
Object.keys(parentMethodNames).forEach((parentProp) => {
205+
methodNames[parentProp] = parentMethodNames[parentProp];
206+
});
207+
}
208+
209+
const members = classMetadata['members'] || {};
210+
Object.keys(members).forEach((propName) => {
211+
const propData = members[propName];
212+
const isMethod = (<any[]>propData).some(a => a['__symbolic'] == 'method');
213+
methodNames[propName] = methodNames[propName] || isMethod;
214+
});
215+
this.methodCache.set(type, methodNames);
216+
}
217+
return methodNames;
218+
}
219+
171220
hasLifecycleHook(type: any, lcProperty: string): boolean {
172221
if (!(type instanceof StaticSymbol)) {
173222
throw new Error(
174223
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`);
175224
}
176-
const classMetadata = this.getTypeMetadata(type);
177-
const members = classMetadata ? classMetadata['members'] : null;
178-
const member: any[] =
179-
members && members.hasOwnProperty(lcProperty) ? members[lcProperty] : null;
180-
return member ? member.some(a => a['__symbolic'] == 'method') : false;
225+
try {
226+
return !!this._methodNames(type)[lcProperty];
227+
} catch (e) {
228+
console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`);
229+
throw e;
230+
}
181231
}
182232

183-
private registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
233+
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
184234
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
185235
}
186236

187-
private registerFunction(type: StaticSymbol, fn: any): void {
237+
private _registerFunction(type: StaticSymbol, fn: any): void {
188238
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => fn.apply(undefined, args));
189239
}
190240

@@ -193,50 +243,51 @@ export class StaticReflector implements ReflectorReader {
193243
ANGULAR_IMPORT_LOCATIONS;
194244
this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
195245

196-
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
197-
this.registerDecoratorOrConstructor(
246+
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
247+
this._registerDecoratorOrConstructor(
198248
this.findDeclaration(diDecorators, 'Injectable'), Injectable);
199-
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Self'), Self);
200-
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
201-
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Inject'), Inject);
202-
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Optional'), Optional);
203-
this.registerDecoratorOrConstructor(
249+
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Self'), Self);
250+
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
251+
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Inject'), Inject);
252+
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Optional'), Optional);
253+
this._registerDecoratorOrConstructor(
204254
this.findDeclaration(coreDecorators, 'Attribute'), Attribute);
205-
this.registerDecoratorOrConstructor(
255+
this._registerDecoratorOrConstructor(
206256
this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
207-
this.registerDecoratorOrConstructor(
257+
this._registerDecoratorOrConstructor(
208258
this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
209-
this.registerDecoratorOrConstructor(
259+
this._registerDecoratorOrConstructor(
210260
this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
211-
this.registerDecoratorOrConstructor(
261+
this._registerDecoratorOrConstructor(
212262
this.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
213-
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Input'), Input);
214-
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Output'), Output);
215-
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Pipe'), Pipe);
216-
this.registerDecoratorOrConstructor(
263+
this._registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Input'), Input);
264+
this._registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Output'), Output);
265+
this._registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Pipe'), Pipe);
266+
this._registerDecoratorOrConstructor(
217267
this.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
218-
this.registerDecoratorOrConstructor(
268+
this._registerDecoratorOrConstructor(
219269
this.findDeclaration(coreDecorators, 'HostListener'), HostListener);
220-
this.registerDecoratorOrConstructor(
270+
this._registerDecoratorOrConstructor(
221271
this.findDeclaration(coreDecorators, 'Directive'), Directive);
222-
this.registerDecoratorOrConstructor(
272+
this._registerDecoratorOrConstructor(
223273
this.findDeclaration(coreDecorators, 'Component'), Component);
224-
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
274+
this._registerDecoratorOrConstructor(
275+
this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
225276

226277
// Note: Some metadata classes can be used directly with Provider.deps.
227-
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host);
228-
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self);
229-
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
230-
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional);
231-
232-
this.registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger);
233-
this.registerFunction(this.findDeclaration(animationMetadata, 'state'), state);
234-
this.registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition);
235-
this.registerFunction(this.findDeclaration(animationMetadata, 'style'), style);
236-
this.registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate);
237-
this.registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes);
238-
this.registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence);
239-
this.registerFunction(this.findDeclaration(animationMetadata, 'group'), group);
278+
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host);
279+
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self);
280+
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
281+
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional);
282+
283+
this._registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger);
284+
this._registerFunction(this.findDeclaration(animationMetadata, 'state'), state);
285+
this._registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition);
286+
this._registerFunction(this.findDeclaration(animationMetadata, 'style'), style);
287+
this._registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate);
288+
this._registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes);
289+
this._registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence);
290+
this._registerFunction(this.findDeclaration(animationMetadata, 'group'), group);
240291
}
241292

242293
/**
@@ -333,7 +384,7 @@ export class StaticReflector implements ReflectorReader {
333384

334385
/** @internal */
335386
public simplify(context: StaticSymbol, value: any): any {
336-
const _this = this;
387+
const self = this;
337388
let scope = BindingScope.empty;
338389
const calling = new Map<StaticSymbol, boolean>();
339390

@@ -342,15 +393,15 @@ export class StaticReflector implements ReflectorReader {
342393
let staticSymbol: StaticSymbol;
343394
if (expression['module']) {
344395
staticSymbol =
345-
_this.findDeclaration(expression['module'], expression['name'], context.filePath);
396+
self.findDeclaration(expression['module'], expression['name'], context.filePath);
346397
} else {
347-
staticSymbol = _this.getStaticSymbol(context.filePath, expression['name']);
398+
staticSymbol = self.getStaticSymbol(context.filePath, expression['name']);
348399
}
349400
return staticSymbol;
350401
}
351402

352403
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
353-
const moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
404+
const moduleMetadata = self.getModuleMetadata(staticSymbol.filePath);
354405
const declarationValue =
355406
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
356407
return declarationValue;
@@ -360,7 +411,7 @@ export class StaticReflector implements ReflectorReader {
360411
if (value && value.__symbolic === 'new' && value.expression) {
361412
const target = value.expression;
362413
if (target.__symbolic == 'reference') {
363-
return sameSymbol(resolveReference(context, target), _this.opaqueToken);
414+
return sameSymbol(resolveReference(context, target), self.opaqueToken);
364415
}
365416
}
366417
return false;
@@ -553,7 +604,7 @@ export class StaticReflector implements ReflectorReader {
553604
const members = selectTarget.members ?
554605
(selectTarget.members as string[]).concat(member) :
555606
[member];
556-
return _this.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
607+
return self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
557608
}
558609
}
559610
const member = simplify(expression['member']);
@@ -589,11 +640,11 @@ export class StaticReflector implements ReflectorReader {
589640
let target = expression['expression'];
590641
if (target['module']) {
591642
staticSymbol =
592-
_this.findDeclaration(target['module'], target['name'], context.filePath);
643+
self.findDeclaration(target['module'], target['name'], context.filePath);
593644
} else {
594-
staticSymbol = _this.getStaticSymbol(context.filePath, target['name']);
645+
staticSymbol = self.getStaticSymbol(context.filePath, target['name']);
595646
}
596-
let converter = _this.conversionMap.get(staticSymbol);
647+
let converter = self.conversionMap.get(staticSymbol);
597648
if (converter) {
598649
let args: any[] = expression['arguments'];
599650
if (!args) {

0 commit comments

Comments
 (0)