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 #11606
Closes #12892
This commit is contained in:
Tobias Bosch
2016-11-18 15:17:44 -08:00
committed by vsavkin
parent 4a09251921
commit f5c8e0989d
19 changed files with 1102 additions and 260 deletions

View File

@ -93,6 +93,15 @@ export class MetadataCollector {
}
}
// Add class parents
if (classDeclaration.heritageClauses) {
classDeclaration.heritageClauses.forEach((hc) => {
if (hc.token === ts.SyntaxKind.ExtendsKeyword && hc.types) {
hc.types.forEach(type => result.extends = referenceFrom(type.expression));
}
});
}
// Add class decorators
if (classDeclaration.decorators) {
result.decorators = getDecorators(classDeclaration.decorators);
@ -196,8 +205,7 @@ export class MetadataCollector {
result.statics = statics;
}
return result.decorators || members || statics ? recordEntry(result, classDeclaration) :
undefined;
return recordEntry(result, classDeclaration);
}
// Predeclare classes and functions
@ -257,11 +265,7 @@ export class MetadataCollector {
const className = classDeclaration.name.text;
if (node.flags & ts.NodeFlags.Export) {
if (!metadata) metadata = {};
if (classDeclaration.decorators) {
metadata[className] = classMetadataOf(classDeclaration);
} else {
metadata[className] = {__symbolic: 'class'};
}
metadata[className] = classMetadataOf(classDeclaration);
}
}
// Otherwise don't record metadata for the class.
@ -469,14 +473,15 @@ function validateMetadata(
}
}
function validateMember(member: MemberMetadata) {
function validateMember(classData: ClassMetadata, member: MemberMetadata) {
if (member.decorators) {
member.decorators.forEach(validateExpression);
}
if (isMethodMetadata(member) && member.parameterDecorators) {
member.parameterDecorators.forEach(validateExpression);
}
if (isConstructorMetadata(member) && member.parameters) {
// Only validate parameters of classes for which we know that are used with our DI
if (classData.decorators && isConstructorMetadata(member) && member.parameters) {
member.parameters.forEach(validateExpression);
}
}
@ -487,7 +492,7 @@ function validateMetadata(
}
if (classData.members) {
Object.getOwnPropertyNames(classData.members)
.forEach(name => classData.members[name].forEach(validateMember));
.forEach(name => classData.members[name].forEach((m) => validateMember(classData, m)));
}
}