fix(ivy): proper component resolution in case of inheritance (#28439)

Ivy allows Components to extend Directives (but not the other way around) and as a result we may have Component and Directive annotations present at the same time. The logic that resolves annotations to pick the necessary one didn't take this into account and as a result Components were recognized as Directives (and vice versa) in case of inheritance. This change updates the resolution logic by picking known annotation that is the nearest one (in inheritance tree) and compares it with expected type. That should help avoid mis-classification of Components/Directives during resolution.

PR Close #28439
This commit is contained in:
Andrew Kushnir
2019-01-29 17:13:02 -08:00
committed by Matias Niemelä
parent ed0cf7e2cb
commit 5a2c3ff8b5
5 changed files with 180 additions and 36 deletions

View File

@ -336,7 +336,18 @@ export class TestBedRender3 implements Injector, TestBed {
// restore initial component/directive/pipe defs
this._initiaNgDefs.forEach((value: [string, PropertyDescriptor], type: Type<any>) => {
Object.defineProperty(type, value[0], value[1]);
const [prop, descriptor] = value;
if (!descriptor) {
// Delete operations are generally undesirable since they have performance implications on
// objects they were applied to. In this particular case, situations where this code is
// invoked should be quite rare to cause any noticable impact, since it's applied only to
// some test cases (for example when class with no annotations extends some @Component) when
// we need to clear 'ngComponentDef' field on a given class to restore its original state
// (before applying overrides and running tests).
delete (type as any)[prop];
} else {
Object.defineProperty(type, prop, descriptor);
}
});
this._initiaNgDefs.clear();
clearResolutionOfComponentResourcesQueue();

View File

@ -37,9 +37,21 @@ abstract class OverrideResolver<T> implements Resolver<T> {
}
getAnnotation(type: Type<any>): T|null {
// We should always return the last match from filter(), or we may return superclass data by
// mistake.
return reflection.annotations(type).filter(a => a instanceof this.type).pop() || null;
const annotations = reflection.annotations(type);
// Try to find the nearest known Type annotation and make sure that this annotation is an
// instance of the type we are looking for, so we can use it for resolution. Note: there might
// be multiple known annotations found due to the fact that Components can extend Directives (so
// both Directive and Component annotations would be present), so we always check if the known
// annotation has the right type.
for (let i = annotations.length - 1; i >= 0; i--) {
const annotation = annotations[i];
const isKnownType = annotation instanceof Directive || annotation instanceof Component ||
annotation instanceof Pipe || annotation instanceof NgModule;
if (isKnownType) {
return annotation instanceof this.type ? annotation : null;
}
}
return null;
}
resolve(type: Type<any>): T|null {