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:
parent
4a09251921
commit
f5c8e0989d
|
@ -186,7 +186,7 @@ export class CompilerHost implements AotCompilerHost {
|
||||||
if (!v2Metadata && v1Metadata) {
|
if (!v2Metadata && v1Metadata) {
|
||||||
// patch up v1 to v2 by merging the metadata with metadata collected from the d.ts file
|
// patch up v1 to v2 by merging the metadata with metadata collected from the d.ts file
|
||||||
// as the only difference between the versions is whether all exports are contained in
|
// as the only difference between the versions is whether all exports are contained in
|
||||||
// the metadata
|
// the metadata and the `extends` clause.
|
||||||
v2Metadata = {'__symbolic': 'module', 'version': 2, 'metadata': {}};
|
v2Metadata = {'__symbolic': 'module', 'version': 2, 'metadata': {}};
|
||||||
if (v1Metadata.exports) {
|
if (v1Metadata.exports) {
|
||||||
v2Metadata.exports = v1Metadata.exports;
|
v2Metadata.exports = v1Metadata.exports;
|
||||||
|
|
|
@ -163,7 +163,11 @@ describe('CompilerHost', () => {
|
||||||
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
|
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
|
||||||
__symbolic: 'module',
|
__symbolic: 'module',
|
||||||
version: 2,
|
version: 2,
|
||||||
metadata: {foo: {__symbolic: 'class'}, bar: {__symbolic: 'class'}}
|
metadata: {
|
||||||
|
foo: {__symbolic: 'class'},
|
||||||
|
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
|
||||||
|
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -198,7 +202,12 @@ const FILES: Entry = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'metadata_versions': {
|
'metadata_versions': {
|
||||||
'v1.d.ts': 'export declare class bar {}',
|
'v1.d.ts': `
|
||||||
|
export declare class Bar {
|
||||||
|
ngOnInit() {}
|
||||||
|
}
|
||||||
|
export declare class BarChild extends Bar {}
|
||||||
|
`,
|
||||||
'v1.metadata.json':
|
'v1.metadata.json':
|
||||||
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,16 +70,24 @@ export class StaticSymbolCache {
|
||||||
export class StaticReflector implements ReflectorReader {
|
export class StaticReflector implements ReflectorReader {
|
||||||
private declarationCache = new Map<string, StaticSymbol>();
|
private declarationCache = new Map<string, StaticSymbol>();
|
||||||
private annotationCache = new Map<StaticSymbol, any[]>();
|
private annotationCache = new Map<StaticSymbol, any[]>();
|
||||||
private propertyCache = new Map<StaticSymbol, {[key: string]: any}>();
|
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
|
||||||
private parameterCache = new Map<StaticSymbol, any[]>();
|
private parameterCache = new Map<StaticSymbol, any[]>();
|
||||||
|
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
|
||||||
private metadataCache = new Map<string, {[key: string]: any}>();
|
private metadataCache = new Map<string, {[key: string]: any}>();
|
||||||
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
||||||
private opaqueToken: StaticSymbol;
|
private opaqueToken: StaticSymbol;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private host: StaticReflectorHost,
|
private host: StaticReflectorHost,
|
||||||
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache()) {
|
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache(),
|
||||||
|
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
|
||||||
|
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = []) {
|
||||||
this.initializeConversionMap();
|
this.initializeConversionMap();
|
||||||
|
knownMetadataClasses.forEach(
|
||||||
|
(kc) => this._registerDecoratorOrConstructor(
|
||||||
|
this.getStaticSymbol(kc.filePath, kc.name), kc.ctor));
|
||||||
|
knownMetadataFunctions.forEach(
|
||||||
|
(kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
importUri(typeOrFunc: StaticSymbol): string {
|
importUri(typeOrFunc: StaticSymbol): string {
|
||||||
|
@ -99,29 +107,45 @@ export class StaticReflector implements ReflectorReader {
|
||||||
public annotations(type: StaticSymbol): any[] {
|
public annotations(type: StaticSymbol): any[] {
|
||||||
let annotations = this.annotationCache.get(type);
|
let annotations = this.annotationCache.get(type);
|
||||||
if (!annotations) {
|
if (!annotations) {
|
||||||
|
annotations = [];
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
const classMetadata = this.getTypeMetadata(type);
|
||||||
|
if (classMetadata['extends']) {
|
||||||
|
const parentAnnotations = this.annotations(this.simplify(type, classMetadata['extends']));
|
||||||
|
annotations.push(...parentAnnotations);
|
||||||
|
}
|
||||||
if (classMetadata['decorators']) {
|
if (classMetadata['decorators']) {
|
||||||
annotations = this.simplify(type, classMetadata['decorators']);
|
const ownAnnotations: any[] = this.simplify(type, classMetadata['decorators']);
|
||||||
} else {
|
annotations.push(...ownAnnotations);
|
||||||
annotations = [];
|
|
||||||
}
|
}
|
||||||
this.annotationCache.set(type, annotations.filter(ann => !!ann));
|
this.annotationCache.set(type, annotations.filter(ann => !!ann));
|
||||||
}
|
}
|
||||||
return annotations;
|
return annotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public propMetadata(type: StaticSymbol): {[key: string]: any} {
|
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
|
||||||
let propMetadata = this.propertyCache.get(type);
|
let propMetadata = this.propertyCache.get(type);
|
||||||
if (!propMetadata) {
|
if (!propMetadata) {
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
const classMetadata = this.getTypeMetadata(type) || {};
|
||||||
const members = classMetadata ? classMetadata['members'] : {};
|
propMetadata = {};
|
||||||
propMetadata = mapStringMap(members, (propData, propName) => {
|
if (classMetadata['extends']) {
|
||||||
|
const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends']));
|
||||||
|
Object.keys(parentPropMetadata).forEach((parentProp) => {
|
||||||
|
propMetadata[parentProp] = parentPropMetadata[parentProp];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const members = classMetadata['members'] || {};
|
||||||
|
Object.keys(members).forEach((propName) => {
|
||||||
|
const propData = members[propName];
|
||||||
const prop = (<any[]>propData)
|
const prop = (<any[]>propData)
|
||||||
.find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method');
|
.find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method');
|
||||||
|
const decorators: any[] = [];
|
||||||
|
if (propMetadata[propName]) {
|
||||||
|
decorators.push(...propMetadata[propName]);
|
||||||
|
}
|
||||||
|
propMetadata[propName] = decorators;
|
||||||
if (prop && prop['decorators']) {
|
if (prop && prop['decorators']) {
|
||||||
return this.simplify(type, prop['decorators']);
|
decorators.push(...this.simplify(type, prop['decorators']));
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.propertyCache.set(type, propMetadata);
|
this.propertyCache.set(type, propMetadata);
|
||||||
|
@ -155,6 +179,8 @@ export class StaticReflector implements ReflectorReader {
|
||||||
}
|
}
|
||||||
parameters.push(nestedResult);
|
parameters.push(nestedResult);
|
||||||
});
|
});
|
||||||
|
} else if (classMetadata['extends']) {
|
||||||
|
parameters = this.parameters(this.simplify(type, classMetadata['extends']));
|
||||||
}
|
}
|
||||||
if (!parameters) {
|
if (!parameters) {
|
||||||
parameters = [];
|
parameters = [];
|
||||||
|
@ -168,23 +194,47 @@ export class StaticReflector implements ReflectorReader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _methodNames(type: any): {[key: string]: boolean} {
|
||||||
|
let methodNames = this.methodCache.get(type);
|
||||||
|
if (!methodNames) {
|
||||||
|
const classMetadata = this.getTypeMetadata(type) || {};
|
||||||
|
methodNames = {};
|
||||||
|
if (classMetadata['extends']) {
|
||||||
|
const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends']));
|
||||||
|
Object.keys(parentMethodNames).forEach((parentProp) => {
|
||||||
|
methodNames[parentProp] = parentMethodNames[parentProp];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const members = classMetadata['members'] || {};
|
||||||
|
Object.keys(members).forEach((propName) => {
|
||||||
|
const propData = members[propName];
|
||||||
|
const isMethod = (<any[]>propData).some(a => a['__symbolic'] == 'method');
|
||||||
|
methodNames[propName] = methodNames[propName] || isMethod;
|
||||||
|
});
|
||||||
|
this.methodCache.set(type, methodNames);
|
||||||
|
}
|
||||||
|
return methodNames;
|
||||||
|
}
|
||||||
|
|
||||||
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
||||||
if (!(type instanceof StaticSymbol)) {
|
if (!(type instanceof StaticSymbol)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`);
|
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`);
|
||||||
}
|
}
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
try {
|
||||||
const members = classMetadata ? classMetadata['members'] : null;
|
return !!this._methodNames(type)[lcProperty];
|
||||||
const member: any[] =
|
} catch (e) {
|
||||||
members && members.hasOwnProperty(lcProperty) ? members[lcProperty] : null;
|
console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`);
|
||||||
return member ? member.some(a => a['__symbolic'] == 'method') : false;
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
|
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
|
||||||
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
|
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerFunction(type: StaticSymbol, fn: any): void {
|
private _registerFunction(type: StaticSymbol, fn: any): void {
|
||||||
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => fn.apply(undefined, args));
|
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => fn.apply(undefined, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,50 +243,51 @@ export class StaticReflector implements ReflectorReader {
|
||||||
ANGULAR_IMPORT_LOCATIONS;
|
ANGULAR_IMPORT_LOCATIONS;
|
||||||
this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
|
this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
|
||||||
|
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
|
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
|
||||||
this.registerDecoratorOrConstructor(
|
this._registerDecoratorOrConstructor(
|
||||||
this.findDeclaration(diDecorators, 'Injectable'), Injectable);
|
this.findDeclaration(diDecorators, 'Injectable'), Injectable);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Self'), Self);
|
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Self'), Self);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
|
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Inject'), Inject);
|
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Inject'), Inject);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Optional'), Optional);
|
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Optional'), Optional);
|
||||||
this.registerDecoratorOrConstructor(
|
this._registerDecoratorOrConstructor(
|
||||||
this.findDeclaration(coreDecorators, 'Attribute'), Attribute);
|
this.findDeclaration(coreDecorators, 'Attribute'), Attribute);
|
||||||
this.registerDecoratorOrConstructor(
|
this._registerDecoratorOrConstructor(
|
||||||
this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
|
this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
|
||||||
this.registerDecoratorOrConstructor(
|
this._registerDecoratorOrConstructor(
|
||||||
this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
|
this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
|
||||||
this.registerDecoratorOrConstructor(
|
this._registerDecoratorOrConstructor(
|
||||||
this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
|
this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
|
||||||
this.registerDecoratorOrConstructor(
|
this._registerDecoratorOrConstructor(
|
||||||
this.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
|
this.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Input'), Input);
|
this._registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Input'), Input);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Output'), Output);
|
this._registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Output'), Output);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Pipe'), Pipe);
|
this._registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Pipe'), Pipe);
|
||||||
this.registerDecoratorOrConstructor(
|
this._registerDecoratorOrConstructor(
|
||||||
this.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
|
this.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
|
||||||
this.registerDecoratorOrConstructor(
|
this._registerDecoratorOrConstructor(
|
||||||
this.findDeclaration(coreDecorators, 'HostListener'), HostListener);
|
this.findDeclaration(coreDecorators, 'HostListener'), HostListener);
|
||||||
this.registerDecoratorOrConstructor(
|
this._registerDecoratorOrConstructor(
|
||||||
this.findDeclaration(coreDecorators, 'Directive'), Directive);
|
this.findDeclaration(coreDecorators, 'Directive'), Directive);
|
||||||
this.registerDecoratorOrConstructor(
|
this._registerDecoratorOrConstructor(
|
||||||
this.findDeclaration(coreDecorators, 'Component'), Component);
|
this.findDeclaration(coreDecorators, 'Component'), Component);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
|
||||||
|
|
||||||
// Note: Some metadata classes can be used directly with Provider.deps.
|
// Note: Some metadata classes can be used directly with Provider.deps.
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host);
|
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self);
|
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
|
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
|
||||||
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional);
|
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional);
|
||||||
|
|
||||||
this.registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger);
|
this._registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger);
|
||||||
this.registerFunction(this.findDeclaration(animationMetadata, 'state'), state);
|
this._registerFunction(this.findDeclaration(animationMetadata, 'state'), state);
|
||||||
this.registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition);
|
this._registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition);
|
||||||
this.registerFunction(this.findDeclaration(animationMetadata, 'style'), style);
|
this._registerFunction(this.findDeclaration(animationMetadata, 'style'), style);
|
||||||
this.registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate);
|
this._registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate);
|
||||||
this.registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes);
|
this._registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes);
|
||||||
this.registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence);
|
this._registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence);
|
||||||
this.registerFunction(this.findDeclaration(animationMetadata, 'group'), group);
|
this._registerFunction(this.findDeclaration(animationMetadata, 'group'), group);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -333,7 +384,7 @@ export class StaticReflector implements ReflectorReader {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
public simplify(context: StaticSymbol, value: any): any {
|
public simplify(context: StaticSymbol, value: any): any {
|
||||||
const _this = this;
|
const self = this;
|
||||||
let scope = BindingScope.empty;
|
let scope = BindingScope.empty;
|
||||||
const calling = new Map<StaticSymbol, boolean>();
|
const calling = new Map<StaticSymbol, boolean>();
|
||||||
|
|
||||||
|
@ -342,15 +393,15 @@ export class StaticReflector implements ReflectorReader {
|
||||||
let staticSymbol: StaticSymbol;
|
let staticSymbol: StaticSymbol;
|
||||||
if (expression['module']) {
|
if (expression['module']) {
|
||||||
staticSymbol =
|
staticSymbol =
|
||||||
_this.findDeclaration(expression['module'], expression['name'], context.filePath);
|
self.findDeclaration(expression['module'], expression['name'], context.filePath);
|
||||||
} else {
|
} else {
|
||||||
staticSymbol = _this.getStaticSymbol(context.filePath, expression['name']);
|
staticSymbol = self.getStaticSymbol(context.filePath, expression['name']);
|
||||||
}
|
}
|
||||||
return staticSymbol;
|
return staticSymbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
|
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
|
||||||
const moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
|
const moduleMetadata = self.getModuleMetadata(staticSymbol.filePath);
|
||||||
const declarationValue =
|
const declarationValue =
|
||||||
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
|
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
|
||||||
return declarationValue;
|
return declarationValue;
|
||||||
|
@ -360,7 +411,7 @@ export class StaticReflector implements ReflectorReader {
|
||||||
if (value && value.__symbolic === 'new' && value.expression) {
|
if (value && value.__symbolic === 'new' && value.expression) {
|
||||||
const target = value.expression;
|
const target = value.expression;
|
||||||
if (target.__symbolic == 'reference') {
|
if (target.__symbolic == 'reference') {
|
||||||
return sameSymbol(resolveReference(context, target), _this.opaqueToken);
|
return sameSymbol(resolveReference(context, target), self.opaqueToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -553,7 +604,7 @@ export class StaticReflector implements ReflectorReader {
|
||||||
const members = selectTarget.members ?
|
const members = selectTarget.members ?
|
||||||
(selectTarget.members as string[]).concat(member) :
|
(selectTarget.members as string[]).concat(member) :
|
||||||
[member];
|
[member];
|
||||||
return _this.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
|
return self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const member = simplify(expression['member']);
|
const member = simplify(expression['member']);
|
||||||
|
@ -589,11 +640,11 @@ export class StaticReflector implements ReflectorReader {
|
||||||
let target = expression['expression'];
|
let target = expression['expression'];
|
||||||
if (target['module']) {
|
if (target['module']) {
|
||||||
staticSymbol =
|
staticSymbol =
|
||||||
_this.findDeclaration(target['module'], target['name'], context.filePath);
|
self.findDeclaration(target['module'], target['name'], context.filePath);
|
||||||
} else {
|
} else {
|
||||||
staticSymbol = _this.getStaticSymbol(context.filePath, target['name']);
|
staticSymbol = self.getStaticSymbol(context.filePath, target['name']);
|
||||||
}
|
}
|
||||||
let converter = _this.conversionMap.get(staticSymbol);
|
let converter = self.conversionMap.get(staticSymbol);
|
||||||
if (converter) {
|
if (converter) {
|
||||||
let args: any[] = expression['arguments'];
|
let args: any[] = expression['arguments'];
|
||||||
if (!args) {
|
if (!args) {
|
||||||
|
|
|
@ -8,11 +8,12 @@
|
||||||
|
|
||||||
import {Component, Directive, HostBinding, HostListener, Injectable, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
|
import {Component, Directive, HostBinding, HostListener, Injectable, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
|
||||||
|
|
||||||
import {StringMapWrapper} from './facade/collection';
|
import {ListWrapper, StringMapWrapper} from './facade/collection';
|
||||||
import {stringify} from './facade/lang';
|
import {stringify} from './facade/lang';
|
||||||
import {ReflectorReader, reflector} from './private_import_core';
|
import {ReflectorReader, reflector} from './private_import_core';
|
||||||
import {splitAtColon} from './util';
|
import {splitAtColon} from './util';
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Resolve a `Type` for {@link Directive}.
|
* Resolve a `Type` for {@link Directive}.
|
||||||
*
|
*
|
||||||
|
@ -35,7 +36,7 @@ export class DirectiveResolver {
|
||||||
resolve(type: Type<any>, throwIfNotFound = true): Directive {
|
resolve(type: Type<any>, throwIfNotFound = true): Directive {
|
||||||
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
|
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
|
||||||
if (typeMetadata) {
|
if (typeMetadata) {
|
||||||
const metadata = typeMetadata.find(isDirectiveMetadata);
|
const metadata = ListWrapper.findLast(typeMetadata, isDirectiveMetadata);
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
const propertyMetadata = this._reflector.propMetadata(type);
|
const propertyMetadata = this._reflector.propMetadata(type);
|
||||||
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
|
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
|
||||||
|
@ -58,85 +59,76 @@ export class DirectiveResolver {
|
||||||
const queries: {[key: string]: any} = {};
|
const queries: {[key: string]: any} = {};
|
||||||
|
|
||||||
Object.keys(propertyMetadata).forEach((propName: string) => {
|
Object.keys(propertyMetadata).forEach((propName: string) => {
|
||||||
|
const input = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Input);
|
||||||
propertyMetadata[propName].forEach(a => {
|
if (input) {
|
||||||
if (a instanceof Input) {
|
if (input.bindingPropertyName) {
|
||||||
if (a.bindingPropertyName) {
|
inputs.push(`${propName}: ${input.bindingPropertyName}`);
|
||||||
inputs.push(`${propName}: ${a.bindingPropertyName}`);
|
} else {
|
||||||
} else {
|
inputs.push(propName);
|
||||||
inputs.push(propName);
|
|
||||||
}
|
|
||||||
} else if (a instanceof Output) {
|
|
||||||
const output: Output = a;
|
|
||||||
if (output.bindingPropertyName) {
|
|
||||||
outputs.push(`${propName}: ${output.bindingPropertyName}`);
|
|
||||||
} else {
|
|
||||||
outputs.push(propName);
|
|
||||||
}
|
|
||||||
} else if (a instanceof HostBinding) {
|
|
||||||
const hostBinding: HostBinding = a;
|
|
||||||
if (hostBinding.hostPropertyName) {
|
|
||||||
const startWith = hostBinding.hostPropertyName[0];
|
|
||||||
if (startWith === '(') {
|
|
||||||
throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`);
|
|
||||||
} else if (startWith === '[') {
|
|
||||||
throw new Error(
|
|
||||||
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
|
||||||
}
|
|
||||||
host[`[${hostBinding.hostPropertyName}]`] = propName;
|
|
||||||
} else {
|
|
||||||
host[`[${propName}]`] = propName;
|
|
||||||
}
|
|
||||||
} else if (a instanceof HostListener) {
|
|
||||||
const hostListener: HostListener = a;
|
|
||||||
const args = hostListener.args || [];
|
|
||||||
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
|
|
||||||
} else if (a instanceof Query) {
|
|
||||||
queries[propName] = a;
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
const output = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Output);
|
||||||
|
if (output) {
|
||||||
|
if (output.bindingPropertyName) {
|
||||||
|
outputs.push(`${propName}: ${output.bindingPropertyName}`);
|
||||||
|
} else {
|
||||||
|
outputs.push(propName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const hostBinding =
|
||||||
|
ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof HostBinding);
|
||||||
|
if (hostBinding) {
|
||||||
|
if (hostBinding.hostPropertyName) {
|
||||||
|
const startWith = hostBinding.hostPropertyName[0];
|
||||||
|
if (startWith === '(') {
|
||||||
|
throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`);
|
||||||
|
} else if (startWith === '[') {
|
||||||
|
throw new Error(
|
||||||
|
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
||||||
|
}
|
||||||
|
host[`[${hostBinding.hostPropertyName}]`] = propName;
|
||||||
|
} else {
|
||||||
|
host[`[${propName}]`] = propName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const hostListener =
|
||||||
|
ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof HostListener);
|
||||||
|
if (hostListener) {
|
||||||
|
const args = hostListener.args || [];
|
||||||
|
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
|
||||||
|
}
|
||||||
|
const query = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Query);
|
||||||
|
if (query) {
|
||||||
|
queries[propName] = query;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return this._merge(dm, inputs, outputs, host, queries, directiveType);
|
return this._merge(dm, inputs, outputs, host, queries, directiveType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _extractPublicName(def: string) { return splitAtColon(def, [null, def])[1].trim(); }
|
private _extractPublicName(def: string) { return splitAtColon(def, [null, def])[1].trim(); }
|
||||||
|
|
||||||
|
private _dedupeBindings(bindings: string[]): string[] {
|
||||||
|
const names = new Set<string>();
|
||||||
|
const reversedResult: string[] = [];
|
||||||
|
// go last to first to allow later entries to overwrite previous entries
|
||||||
|
for (let i = bindings.length - 1; i >= 0; i--) {
|
||||||
|
const binding = bindings[i];
|
||||||
|
const name = this._extractPublicName(binding);
|
||||||
|
if (!names.has(name)) {
|
||||||
|
names.add(name);
|
||||||
|
reversedResult.push(binding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reversedResult.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
private _merge(
|
private _merge(
|
||||||
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
|
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
|
||||||
queries: {[key: string]: any}, directiveType: Type<any>): Directive {
|
queries: {[key: string]: any}, directiveType: Type<any>): Directive {
|
||||||
const mergedInputs: string[] = inputs;
|
const mergedInputs =
|
||||||
|
this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs);
|
||||||
if (directive.inputs) {
|
const mergedOutputs =
|
||||||
const inputNames: string[] =
|
this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs);
|
||||||
directive.inputs.map((def: string): string => this._extractPublicName(def));
|
|
||||||
|
|
||||||
inputs.forEach((inputDef: string) => {
|
|
||||||
const publicName = this._extractPublicName(inputDef);
|
|
||||||
if (inputNames.indexOf(publicName) > -1) {
|
|
||||||
throw new Error(
|
|
||||||
`Input '${publicName}' defined multiple times in '${stringify(directiveType)}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mergedInputs.unshift(...directive.inputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedOutputs: string[] = outputs;
|
|
||||||
|
|
||||||
if (directive.outputs) {
|
|
||||||
const outputNames: string[] =
|
|
||||||
directive.outputs.map((def: string): string => this._extractPublicName(def));
|
|
||||||
|
|
||||||
outputs.forEach((outputDef: string) => {
|
|
||||||
const publicName = this._extractPublicName(outputDef);
|
|
||||||
if (outputNames.indexOf(publicName) > -1) {
|
|
||||||
throw new Error(
|
|
||||||
`Output event '${publicName}' defined multiple times in '${stringify(directiveType)}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mergedOutputs.unshift(...directive.outputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedHost = directive.host ? StringMapWrapper.merge(directive.host, host) : host;
|
const mergedHost = directive.host ? StringMapWrapper.merge(directive.host, host) : host;
|
||||||
const mergedQueries =
|
const mergedQueries =
|
||||||
directive.queries ? StringMapWrapper.merge(directive.queries, queries) : queries;
|
directive.queries ? StringMapWrapper.merge(directive.queries, queries) : queries;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import {Injectable, NgModule, Type} from '@angular/core';
|
import {Injectable, NgModule, Type} from '@angular/core';
|
||||||
|
|
||||||
|
import {ListWrapper} from './facade/collection';
|
||||||
import {isPresent, stringify} from './facade/lang';
|
import {isPresent, stringify} from './facade/lang';
|
||||||
import {ReflectorReader, reflector} from './private_import_core';
|
import {ReflectorReader, reflector} from './private_import_core';
|
||||||
|
|
||||||
|
@ -25,7 +26,8 @@ export class NgModuleResolver {
|
||||||
isNgModule(type: any) { return this._reflector.annotations(type).some(_isNgModuleMetadata); }
|
isNgModule(type: any) { return this._reflector.annotations(type).some(_isNgModuleMetadata); }
|
||||||
|
|
||||||
resolve(type: Type<any>, throwIfNotFound = true): NgModule {
|
resolve(type: Type<any>, throwIfNotFound = true): NgModule {
|
||||||
const ngModuleMeta: NgModule = this._reflector.annotations(type).find(_isNgModuleMetadata);
|
const ngModuleMeta: NgModule =
|
||||||
|
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
|
||||||
|
|
||||||
if (isPresent(ngModuleMeta)) {
|
if (isPresent(ngModuleMeta)) {
|
||||||
return ngModuleMeta;
|
return ngModuleMeta;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import {Injectable, Pipe, Type, resolveForwardRef} from '@angular/core';
|
import {Injectable, Pipe, Type, resolveForwardRef} from '@angular/core';
|
||||||
|
|
||||||
|
import {ListWrapper} from './facade/collection';
|
||||||
import {isPresent, stringify} from './facade/lang';
|
import {isPresent, stringify} from './facade/lang';
|
||||||
import {ReflectorReader, reflector} from './private_import_core';
|
import {ReflectorReader, reflector} from './private_import_core';
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ export class PipeResolver {
|
||||||
resolve(type: Type<any>, throwIfNotFound = true): Pipe {
|
resolve(type: Type<any>, throwIfNotFound = true): Pipe {
|
||||||
const metas = this._reflector.annotations(resolveForwardRef(type));
|
const metas = this._reflector.annotations(resolveForwardRef(type));
|
||||||
if (isPresent(metas)) {
|
if (isPresent(metas)) {
|
||||||
const annotation = metas.find(_isPipeMetadata);
|
const annotation = ListWrapper.findLast(metas, _isPipeMetadata);
|
||||||
if (isPresent(annotation)) {
|
if (isPresent(annotation)) {
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,14 @@ describe('StaticReflector', () => {
|
||||||
let host: StaticReflectorHost;
|
let host: StaticReflectorHost;
|
||||||
let reflector: StaticReflector;
|
let reflector: StaticReflector;
|
||||||
|
|
||||||
beforeEach(() => {
|
function init(
|
||||||
host = new MockStaticReflectorHost();
|
testData: {[key: string]: any} = DEFAULT_TEST_DATA,
|
||||||
reflector = new StaticReflector(host);
|
decorators: {name: string, filePath: string, ctor: any}[] = []) {
|
||||||
});
|
host = new MockStaticReflectorHost(testData);
|
||||||
|
reflector = new StaticReflector(host, undefined, decorators);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => init());
|
||||||
|
|
||||||
function simplify(context: StaticSymbol, value: any) {
|
function simplify(context: StaticSymbol, value: any) {
|
||||||
return reflector.simplify(context, value);
|
return reflector.simplify(context, value);
|
||||||
|
@ -517,11 +521,173 @@ describe('StaticReflector', () => {
|
||||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
|
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('inheritance', () => {
|
||||||
|
class ClassDecorator {
|
||||||
|
constructor(public value: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParamDecorator {
|
||||||
|
constructor(public value: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PropDecorator {
|
||||||
|
constructor(public value: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initWithDecorator(testData: {[key: string]: any}) {
|
||||||
|
testData['/tmp/src/decorator.ts'] = `
|
||||||
|
export function ClassDecorator(): any {}
|
||||||
|
export function ParamDecorator(): any {}
|
||||||
|
export function PropDecorator(): any {}
|
||||||
|
`;
|
||||||
|
init(testData, [
|
||||||
|
{filePath: '/tmp/src/decorator.ts', name: 'ClassDecorator', ctor: ClassDecorator},
|
||||||
|
{filePath: '/tmp/src/decorator.ts', name: 'ParamDecorator', ctor: ParamDecorator},
|
||||||
|
{filePath: '/tmp/src/decorator.ts', name: 'PropDecorator', ctor: PropDecorator}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should inherit annotations', () => {
|
||||||
|
initWithDecorator({
|
||||||
|
'/tmp/src/main.ts': `
|
||||||
|
import {ClassDecorator} from './decorator';
|
||||||
|
|
||||||
|
@ClassDecorator('parent')
|
||||||
|
export class Parent {}
|
||||||
|
|
||||||
|
@ClassDecorator('child')
|
||||||
|
export class Child extends Parent {}
|
||||||
|
|
||||||
|
export class ChildNoDecorators extends Parent {}
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.annotations(reflector.getStaticSymbol('/tmp/src/main.ts', 'Parent')))
|
||||||
|
.toEqual([new ClassDecorator('parent')]);
|
||||||
|
|
||||||
|
expect(reflector.annotations(reflector.getStaticSymbol('/tmp/src/main.ts', 'Child')))
|
||||||
|
.toEqual([new ClassDecorator('parent'), new ClassDecorator('child')]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
reflector.annotations(reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildNoDecorators')))
|
||||||
|
.toEqual([new ClassDecorator('parent')]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit parameters', () => {
|
||||||
|
initWithDecorator({
|
||||||
|
'/tmp/src/main.ts': `
|
||||||
|
import {ParamDecorator} from './decorator';
|
||||||
|
|
||||||
|
export class A {}
|
||||||
|
export class B {}
|
||||||
|
export class C {}
|
||||||
|
|
||||||
|
export class Parent {
|
||||||
|
constructor(@ParamDecorator('a') a: A, @ParamDecorator('b') b: B) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Child extends Parent {}
|
||||||
|
|
||||||
|
export class ChildWithCtor extends Parent {
|
||||||
|
constructor(@ParamDecorator('c') c: C) {}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.parameters(reflector.getStaticSymbol('/tmp/src/main.ts', 'Parent')))
|
||||||
|
.toEqual([
|
||||||
|
[reflector.getStaticSymbol('/tmp/src/main.ts', 'A'), new ParamDecorator('a')],
|
||||||
|
[reflector.getStaticSymbol('/tmp/src/main.ts', 'B'), new ParamDecorator('b')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.parameters(reflector.getStaticSymbol('/tmp/src/main.ts', 'Child'))).toEqual([
|
||||||
|
[reflector.getStaticSymbol('/tmp/src/main.ts', 'A'), new ParamDecorator('a')],
|
||||||
|
[reflector.getStaticSymbol('/tmp/src/main.ts', 'B'), new ParamDecorator('b')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.parameters(reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildWithCtor')))
|
||||||
|
.toEqual([[reflector.getStaticSymbol('/tmp/src/main.ts', 'C'), new ParamDecorator('c')]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit property metadata', () => {
|
||||||
|
initWithDecorator({
|
||||||
|
'/tmp/src/main.ts': `
|
||||||
|
import {PropDecorator} from './decorator';
|
||||||
|
|
||||||
|
export class A {}
|
||||||
|
export class B {}
|
||||||
|
export class C {}
|
||||||
|
|
||||||
|
export class Parent {
|
||||||
|
@PropDecorator('a')
|
||||||
|
a: A;
|
||||||
|
@PropDecorator('b1')
|
||||||
|
b: B;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Child extends Parent {
|
||||||
|
@PropDecorator('b2')
|
||||||
|
b: B;
|
||||||
|
@PropDecorator('c')
|
||||||
|
c: C;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.propMetadata(reflector.getStaticSymbol('/tmp/src/main.ts', 'Parent')))
|
||||||
|
.toEqual({
|
||||||
|
'a': [new PropDecorator('a')],
|
||||||
|
'b': [new PropDecorator('b1')],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(reflector.propMetadata(reflector.getStaticSymbol('/tmp/src/main.ts', 'Child')))
|
||||||
|
.toEqual({
|
||||||
|
'a': [new PropDecorator('a')],
|
||||||
|
'b': [new PropDecorator('b1'), new PropDecorator('b2')],
|
||||||
|
'c': [new PropDecorator('c')]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit lifecycle hooks', () => {
|
||||||
|
initWithDecorator({
|
||||||
|
'/tmp/src/main.ts': `
|
||||||
|
export class Parent {
|
||||||
|
hook1() {}
|
||||||
|
hook2() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Child extends Parent {
|
||||||
|
hook2() {}
|
||||||
|
hook3() {}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
function hooks(symbol: StaticSymbol, names: string[]): boolean[] {
|
||||||
|
return names.map(name => reflector.hasLifecycleHook(symbol, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(hooks(reflector.getStaticSymbol('/tmp/src/main.ts', 'Parent'), [
|
||||||
|
'hook1', 'hook2', 'hook3'
|
||||||
|
])).toEqual([true, true, false]);
|
||||||
|
|
||||||
|
expect(hooks(reflector.getStaticSymbol('/tmp/src/main.ts', 'Child'), [
|
||||||
|
'hook1', 'hook2', 'hook3'
|
||||||
|
])).toEqual([true, true, true]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
class MockStaticReflectorHost implements StaticReflectorHost {
|
class MockStaticReflectorHost implements StaticReflectorHost {
|
||||||
private collector = new MetadataCollector();
|
private collector = new MetadataCollector();
|
||||||
|
|
||||||
|
constructor(private data: {[key: string]: any}) {}
|
||||||
|
|
||||||
// In tests, assume that symbols are not re-exported
|
// In tests, assume that symbols are not re-exported
|
||||||
moduleNameToFileName(modulePath: string, containingFile?: string): string {
|
moduleNameToFileName(modulePath: string, containingFile?: string): string {
|
||||||
function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
|
function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
|
||||||
|
@ -568,7 +734,28 @@ class MockStaticReflectorHost implements StaticReflectorHost {
|
||||||
getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); }
|
getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); }
|
||||||
|
|
||||||
private _getMetadataFor(moduleId: string): any {
|
private _getMetadataFor(moduleId: string): any {
|
||||||
const data: {[key: string]: any} = {
|
if (this.data[moduleId] && moduleId.match(TS_EXT)) {
|
||||||
|
const text = this.data[moduleId];
|
||||||
|
if (typeof text === 'string') {
|
||||||
|
const sf = ts.createSourceFile(
|
||||||
|
moduleId, this.data[moduleId], ts.ScriptTarget.ES5, /* setParentNodes */ true);
|
||||||
|
const diagnostics: ts.Diagnostic[] = (<any>sf).parseDiagnostics;
|
||||||
|
if (diagnostics && diagnostics.length) {
|
||||||
|
throw Error(`Error encountered during parse of file ${moduleId}`);
|
||||||
|
}
|
||||||
|
return [this.collector.getMetadata(sf)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const result = this.data[moduleId];
|
||||||
|
if (result) {
|
||||||
|
return Array.isArray(result) ? result : [result];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||||
'/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{
|
'/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{
|
||||||
'__symbolic': 'module',
|
'__symbolic': 'module',
|
||||||
'version': 2,
|
'version': 2,
|
||||||
|
@ -1162,25 +1349,3 @@ class MockStaticReflectorHost implements StaticReflectorHost {
|
||||||
exports: [{from: './originNone'}, {from: './origin30'}]
|
exports: [{from: './originNone'}, {from: './origin30'}]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (data[moduleId] && moduleId.match(TS_EXT)) {
|
|
||||||
const text = data[moduleId];
|
|
||||||
if (typeof text === 'string') {
|
|
||||||
const sf = ts.createSourceFile(
|
|
||||||
moduleId, data[moduleId], ts.ScriptTarget.ES5, /* setParentNodes */ true);
|
|
||||||
const diagnostics: ts.Diagnostic[] = (<any>sf).parseDiagnostics;
|
|
||||||
if (diagnostics && diagnostics.length) {
|
|
||||||
throw Error(`Error encountered during parse of file ${moduleId}`);
|
|
||||||
}
|
|
||||||
return [this.collector.getMetadata(sf)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const result = data[moduleId];
|
|
||||||
if (result) {
|
|
||||||
return Array.isArray(result) ? result : [result];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,15 +8,12 @@
|
||||||
|
|
||||||
import {DirectiveResolver} from '@angular/compiler/src/directive_resolver';
|
import {DirectiveResolver} from '@angular/compiler/src/directive_resolver';
|
||||||
import {Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Input, Output, ViewChild, ViewChildren} from '@angular/core/src/metadata';
|
import {Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Input, Output, ViewChild, ViewChildren} from '@angular/core/src/metadata';
|
||||||
|
import {reflector} from '@angular/core/src/reflection/reflection';
|
||||||
|
|
||||||
@Directive({selector: 'someDirective'})
|
@Directive({selector: 'someDirective'})
|
||||||
class SomeDirective {
|
class SomeDirective {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive({selector: 'someChildDirective'})
|
|
||||||
class SomeChildDirective extends SomeDirective {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Directive({selector: 'someDirective', inputs: ['c']})
|
@Directive({selector: 'someDirective', inputs: ['c']})
|
||||||
class SomeDirectiveWithInputs {
|
class SomeDirectiveWithInputs {
|
||||||
@Input() a: any;
|
@Input() a: any;
|
||||||
|
@ -31,28 +28,6 @@ class SomeDirectiveWithOutputs {
|
||||||
c: any;
|
c: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive({selector: 'someDirective', outputs: ['a']})
|
|
||||||
class SomeDirectiveWithDuplicateOutputs {
|
|
||||||
@Output() a: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Directive({selector: 'someDirective', outputs: ['localA: a']})
|
|
||||||
class SomeDirectiveWithDuplicateRenamedOutputs {
|
|
||||||
@Output() a: any;
|
|
||||||
localA: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Directive({selector: 'someDirective', inputs: ['a']})
|
|
||||||
class SomeDirectiveWithDuplicateInputs {
|
|
||||||
@Input() a: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Directive({selector: 'someDirective', inputs: ['localA: a']})
|
|
||||||
class SomeDirectiveWithDuplicateRenamedInputs {
|
|
||||||
@Input() a: any;
|
|
||||||
localA: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Directive({selector: 'someDirective'})
|
@Directive({selector: 'someDirective'})
|
||||||
class SomeDirectiveWithSetterProps {
|
class SomeDirectiveWithSetterProps {
|
||||||
@Input('renamed')
|
@Input('renamed')
|
||||||
|
@ -150,11 +125,22 @@ export function main() {
|
||||||
}).toThrowError('No Directive annotation found on SomeDirectiveWithoutMetadata');
|
}).toThrowError('No Directive annotation found on SomeDirectiveWithoutMetadata');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not read parent class Directive metadata', function() {
|
it('should support inheriting the Directive metadata', function() {
|
||||||
const directiveMetadata = resolver.resolve(SomeChildDirective);
|
@Directive({selector: 'p'})
|
||||||
expect(directiveMetadata)
|
class Parent {
|
||||||
.toEqual(new Directive(
|
}
|
||||||
{selector: 'someChildDirective', inputs: [], outputs: [], host: {}, queries: {}}));
|
|
||||||
|
class ChildNoDecorator extends Parent {}
|
||||||
|
|
||||||
|
@Directive({selector: 'c'})
|
||||||
|
class ChildWithDecorator extends Parent {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(resolver.resolve(ChildNoDecorator))
|
||||||
|
.toEqual(new Directive({selector: 'p', inputs: [], outputs: [], host: {}, queries: {}}));
|
||||||
|
|
||||||
|
expect(resolver.resolve(ChildWithDecorator))
|
||||||
|
.toEqual(new Directive({selector: 'c', inputs: [], outputs: [], host: {}, queries: {}}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inputs', () => {
|
describe('inputs', () => {
|
||||||
|
@ -168,16 +154,52 @@ export function main() {
|
||||||
expect(directiveMetadata.inputs).toEqual(['a: renamed']);
|
expect(directiveMetadata.inputs).toEqual(['a: renamed']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if duplicate inputs', () => {
|
it('should remove duplicate inputs', () => {
|
||||||
expect(() => {
|
@Directive({selector: 'someDirective', inputs: ['a', 'a']})
|
||||||
resolver.resolve(SomeDirectiveWithDuplicateInputs);
|
class SomeDirectiveWithDuplicateInputs {
|
||||||
}).toThrowError(`Input 'a' defined multiple times in 'SomeDirectiveWithDuplicateInputs'`);
|
}
|
||||||
|
|
||||||
|
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateInputs);
|
||||||
|
expect(directiveMetadata.inputs).toEqual(['a']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if duplicate inputs (with rename)', () => {
|
it('should use the last input if duplicate inputs (with rename)', () => {
|
||||||
expect(() => { resolver.resolve(SomeDirectiveWithDuplicateRenamedInputs); })
|
@Directive({selector: 'someDirective', inputs: ['a', 'localA: a']})
|
||||||
.toThrowError(
|
class SomeDirectiveWithDuplicateInputs {
|
||||||
`Input 'a' defined multiple times in 'SomeDirectiveWithDuplicateRenamedInputs'`);
|
}
|
||||||
|
|
||||||
|
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateInputs);
|
||||||
|
expect(directiveMetadata.inputs).toEqual(['localA: a']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prefer @Input over @Directive.inputs', () => {
|
||||||
|
@Directive({selector: 'someDirective', inputs: ['a']})
|
||||||
|
class SomeDirectiveWithDuplicateInputs {
|
||||||
|
@Input('a')
|
||||||
|
propA: any;
|
||||||
|
}
|
||||||
|
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateInputs);
|
||||||
|
expect(directiveMetadata.inputs).toEqual(['propA: a']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support inheriting inputs', () => {
|
||||||
|
@Directive({selector: 'p'})
|
||||||
|
class Parent {
|
||||||
|
@Input()
|
||||||
|
p1: any;
|
||||||
|
@Input('p21')
|
||||||
|
p2: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
@Input('p22')
|
||||||
|
p2: any;
|
||||||
|
@Input()
|
||||||
|
p3: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directiveMetadata = resolver.resolve(Child);
|
||||||
|
expect(directiveMetadata.inputs).toEqual(['p1', 'p2: p22', 'p3']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -192,16 +214,52 @@ export function main() {
|
||||||
expect(directiveMetadata.outputs).toEqual(['a: renamed']);
|
expect(directiveMetadata.outputs).toEqual(['a: renamed']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if duplicate outputs', () => {
|
it('should remove duplicate outputs', () => {
|
||||||
expect(() => { resolver.resolve(SomeDirectiveWithDuplicateOutputs); })
|
@Directive({selector: 'someDirective', outputs: ['a', 'a']})
|
||||||
.toThrowError(
|
class SomeDirectiveWithDuplicateOutputs {
|
||||||
`Output event 'a' defined multiple times in 'SomeDirectiveWithDuplicateOutputs'`);
|
}
|
||||||
|
|
||||||
|
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateOutputs);
|
||||||
|
expect(directiveMetadata.outputs).toEqual(['a']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if duplicate outputs (with rename)', () => {
|
it('should use the last output if duplicate outputs (with rename)', () => {
|
||||||
expect(() => { resolver.resolve(SomeDirectiveWithDuplicateRenamedOutputs); })
|
@Directive({selector: 'someDirective', outputs: ['a', 'localA: a']})
|
||||||
.toThrowError(
|
class SomeDirectiveWithDuplicateOutputs {
|
||||||
`Output event 'a' defined multiple times in 'SomeDirectiveWithDuplicateRenamedOutputs'`);
|
}
|
||||||
|
|
||||||
|
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateOutputs);
|
||||||
|
expect(directiveMetadata.outputs).toEqual(['localA: a']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prefer @Output over @Directive.outputs', () => {
|
||||||
|
@Directive({selector: 'someDirective', outputs: ['a']})
|
||||||
|
class SomeDirectiveWithDuplicateOutputs {
|
||||||
|
@Output('a')
|
||||||
|
propA: any;
|
||||||
|
}
|
||||||
|
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateOutputs);
|
||||||
|
expect(directiveMetadata.outputs).toEqual(['propA: a']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support inheriting outputs', () => {
|
||||||
|
@Directive({selector: 'p'})
|
||||||
|
class Parent {
|
||||||
|
@Output()
|
||||||
|
p1: any;
|
||||||
|
@Output('p21')
|
||||||
|
p2: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
@Output('p22')
|
||||||
|
p2: any;
|
||||||
|
@Output()
|
||||||
|
p3: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directiveMetadata = resolver.resolve(Child);
|
||||||
|
expect(directiveMetadata.outputs).toEqual(['p1', 'p2: p22', 'p3']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -233,6 +291,46 @@ export function main() {
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support inheriting host bindings', () => {
|
||||||
|
@Directive({selector: 'p'})
|
||||||
|
class Parent {
|
||||||
|
@HostBinding()
|
||||||
|
p1: any;
|
||||||
|
@HostBinding('p21')
|
||||||
|
p2: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
@HostBinding('p22')
|
||||||
|
p2: any;
|
||||||
|
@HostBinding()
|
||||||
|
p3: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directiveMetadata = resolver.resolve(Child);
|
||||||
|
expect(directiveMetadata.host).toEqual({'[p1]': 'p1', '[p22]': 'p2', '[p3]': 'p3'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support inheriting host listeners', () => {
|
||||||
|
@Directive({selector: 'p'})
|
||||||
|
class Parent {
|
||||||
|
@HostListener('p1')
|
||||||
|
p1() {}
|
||||||
|
@HostListener('p21')
|
||||||
|
p2() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
@HostListener('p22')
|
||||||
|
p2() {}
|
||||||
|
@HostListener('p3')
|
||||||
|
p3() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const directiveMetadata = resolver.resolve(Child);
|
||||||
|
expect(directiveMetadata.host).toEqual({'(p1)': 'p1()', '(p22)': 'p2()', '(p3)': 'p3()'});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('queries', () => {
|
describe('queries', () => {
|
||||||
|
@ -259,6 +357,30 @@ export function main() {
|
||||||
expect(directiveMetadata.queries)
|
expect(directiveMetadata.queries)
|
||||||
.toEqual({'c': new ViewChild('c'), 'a': new ViewChild('a')});
|
.toEqual({'c': new ViewChild('c'), 'a': new ViewChild('a')});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support inheriting queries', () => {
|
||||||
|
@Directive({selector: 'p'})
|
||||||
|
class Parent {
|
||||||
|
@ContentChild('p1')
|
||||||
|
p1: any;
|
||||||
|
@ContentChild('p21')
|
||||||
|
p2: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
@ContentChild('p22')
|
||||||
|
p2: any;
|
||||||
|
@ContentChild('p3')
|
||||||
|
p3: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directiveMetadata = resolver.resolve(Child);
|
||||||
|
expect(directiveMetadata.queries).toEqual({
|
||||||
|
'p1': new ContentChild('p1'),
|
||||||
|
'p2': new ContentChild('p22'),
|
||||||
|
'p3': new ContentChild('p3')
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Component', () => {
|
describe('Component', () => {
|
||||||
|
|
|
@ -45,9 +45,26 @@ export function main() {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when simple class has no component decorator', () => {
|
it('should throw when simple class has no NgModule decorator', () => {
|
||||||
expect(() => resolver.resolve(SimpleClass))
|
expect(() => resolver.resolve(SimpleClass))
|
||||||
.toThrowError(`No NgModule metadata found for '${stringify(SimpleClass)}'.`);
|
.toThrowError(`No NgModule metadata found for '${stringify(SimpleClass)}'.`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support inheriting the metadata', function() {
|
||||||
|
@NgModule({id: 'p'})
|
||||||
|
class Parent {
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChildNoDecorator extends Parent {}
|
||||||
|
|
||||||
|
@NgModule({id: 'c'})
|
||||||
|
class ChildWithDecorator extends Parent {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(resolver.resolve(ChildNoDecorator)).toEqual(new NgModule({id: 'p'}));
|
||||||
|
|
||||||
|
expect(resolver.resolve(ChildWithDecorator)).toEqual(new NgModule({id: 'c'}));
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
|
||||||
|
import {Pipe} from '@angular/core/src/metadata';
|
||||||
|
import {stringify} from '../src/facade/lang';
|
||||||
|
|
||||||
|
@Pipe({name: 'somePipe', pure: true})
|
||||||
|
class SomePipe {
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleClass {}
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('PipeResolver', () => {
|
||||||
|
let resolver: PipeResolver;
|
||||||
|
|
||||||
|
beforeEach(() => { resolver = new PipeResolver(); });
|
||||||
|
|
||||||
|
it('should read out the metadata from the class', () => {
|
||||||
|
const moduleMetadata = resolver.resolve(SomePipe);
|
||||||
|
expect(moduleMetadata).toEqual(new Pipe({name: 'somePipe', pure: true}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when simple class has no pipe decorator', () => {
|
||||||
|
expect(() => resolver.resolve(SimpleClass))
|
||||||
|
.toThrowError(`No Pipe decorator found on ${stringify(SimpleClass)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support inheriting the metadata', function() {
|
||||||
|
@Pipe({name: 'p'})
|
||||||
|
class Parent {
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChildNoDecorator extends Parent {}
|
||||||
|
|
||||||
|
@Pipe({name: 'c'})
|
||||||
|
class ChildWithDecorator extends Parent {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(resolver.resolve(ChildNoDecorator)).toEqual(new Pipe({name: 'p'}));
|
||||||
|
|
||||||
|
expect(resolver.resolve(ChildWithDecorator)).toEqual(new Pipe({name: 'c'}));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
|
@ -6,7 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {makeParamDecorator} from '../util/decorators';
|
import {makeDecorator, makeParamDecorator} from '../util/decorators';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of the Inject decorator / constructor function.
|
* Type of the Inject decorator / constructor function.
|
||||||
|
@ -150,7 +151,7 @@ export interface Injectable {}
|
||||||
* @stable
|
* @stable
|
||||||
* @Annotation
|
* @Annotation
|
||||||
*/
|
*/
|
||||||
export const Injectable: InjectableDecorator = makeParamDecorator('Injectable', []);
|
export const Injectable: InjectableDecorator = <InjectableDecorator>makeDecorator('Injectable', []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of the Self decorator / constructor function.
|
* Type of the Self decorator / constructor function.
|
||||||
|
|
|
@ -12,6 +12,12 @@ import {Type} from '../type';
|
||||||
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
|
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
|
||||||
import {GetterFn, MethodFn, SetterFn} from './types';
|
import {GetterFn, MethodFn, SetterFn} from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attention: This regex has to hold even if the code is minified!
|
||||||
|
*/
|
||||||
|
export const DELEGATE_CTOR =
|
||||||
|
/^function\s+\S+\(\)\s*{\s*("use strict";)?\s*(return\s+)?\S+\.apply\(this,\s*arguments\)/;
|
||||||
|
|
||||||
export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||||
private _reflect: any;
|
private _reflect: any;
|
||||||
|
|
||||||
|
@ -49,15 +55,26 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters(type: Type<any>): any[][] {
|
private _ownParameters(type: Type<any>, parentCtor: any): any[][] {
|
||||||
|
// If we have no decorators, we only have function.length as metadata.
|
||||||
|
// In that case, to detect whether a child class declared an own constructor or not,
|
||||||
|
// we need to look inside of that constructor to check whether it is
|
||||||
|
// just calling the parent.
|
||||||
|
// This also helps to work around for https://github.com/Microsoft/TypeScript/issues/12439
|
||||||
|
// that sets 'design:paramtypes' to []
|
||||||
|
// if a class inherits from another class but has no ctor declared itself.
|
||||||
|
if (DELEGATE_CTOR.exec(type.toString())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Prefer the direct API.
|
// Prefer the direct API.
|
||||||
if ((<any>type).parameters) {
|
if ((<any>type).parameters && (<any>type).parameters !== parentCtor.parameters) {
|
||||||
return (<any>type).parameters;
|
return (<any>type).parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
// API of tsickle for lowering decorators to properties on the class.
|
// API of tsickle for lowering decorators to properties on the class.
|
||||||
const tsickleCtorParams = (<any>type).ctorParameters;
|
const tsickleCtorParams = (<any>type).ctorParameters;
|
||||||
if (tsickleCtorParams) {
|
if (tsickleCtorParams && tsickleCtorParams !== parentCtor.ctorParameters) {
|
||||||
// Newer tsickle uses a function closure
|
// Newer tsickle uses a function closure
|
||||||
// Retain the non-function case for compatibility with older tsickle
|
// Retain the non-function case for compatibility with older tsickle
|
||||||
const ctorParameters =
|
const ctorParameters =
|
||||||
|
@ -70,20 +87,35 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
// API for metadata created by invoking the decorators.
|
// API for metadata created by invoking the decorators.
|
||||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
if (isPresent(this._reflect) && isPresent(this._reflect.getOwnMetadata)) {
|
||||||
const paramAnnotations = this._reflect.getMetadata('parameters', type);
|
const paramAnnotations = this._reflect.getOwnMetadata('parameters', type);
|
||||||
const paramTypes = this._reflect.getMetadata('design:paramtypes', type);
|
const paramTypes = this._reflect.getOwnMetadata('design:paramtypes', type);
|
||||||
if (paramTypes || paramAnnotations) {
|
if (paramTypes || paramAnnotations) {
|
||||||
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
|
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The array has to be filled with `undefined` because holes would be skipped by `some`
|
|
||||||
|
// If a class has no decorators, at least create metadata
|
||||||
|
// based on function.length.
|
||||||
|
// Note: We know that this is a real constructor as we checked
|
||||||
|
// the content of the constructor above.
|
||||||
return new Array((<any>type.length)).fill(undefined);
|
return new Array((<any>type.length)).fill(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
annotations(typeOrFunc: Type<any>): any[] {
|
parameters(type: Type<any>): any[][] {
|
||||||
|
// Note: only report metadata if we have at least one class decorator
|
||||||
|
// to stay in sync with the static reflector.
|
||||||
|
const parentCtor = Object.getPrototypeOf(type.prototype).constructor;
|
||||||
|
let parameters = this._ownParameters(type, parentCtor);
|
||||||
|
if (!parameters && parentCtor !== Object) {
|
||||||
|
parameters = this.parameters(parentCtor);
|
||||||
|
}
|
||||||
|
return parameters || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _ownAnnotations(typeOrFunc: Type<any>, parentCtor: any): any[] {
|
||||||
// Prefer the direct API.
|
// Prefer the direct API.
|
||||||
if ((<any>typeOrFunc).annotations) {
|
if ((<any>typeOrFunc).annotations && (<any>typeOrFunc).annotations !== parentCtor.annotations) {
|
||||||
let annotations = (<any>typeOrFunc).annotations;
|
let annotations = (<any>typeOrFunc).annotations;
|
||||||
if (typeof annotations === 'function' && annotations.annotations) {
|
if (typeof annotations === 'function' && annotations.annotations) {
|
||||||
annotations = annotations.annotations;
|
annotations = annotations.annotations;
|
||||||
|
@ -92,21 +124,27 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
// API of tsickle for lowering decorators to properties on the class.
|
// API of tsickle for lowering decorators to properties on the class.
|
||||||
if ((<any>typeOrFunc).decorators) {
|
if ((<any>typeOrFunc).decorators && (<any>typeOrFunc).decorators !== parentCtor.decorators) {
|
||||||
return convertTsickleDecoratorIntoMetadata((<any>typeOrFunc).decorators);
|
return convertTsickleDecoratorIntoMetadata((<any>typeOrFunc).decorators);
|
||||||
}
|
}
|
||||||
|
|
||||||
// API for metadata created by invoking the decorators.
|
// API for metadata created by invoking the decorators.
|
||||||
if (this._reflect && this._reflect.getMetadata) {
|
if (this._reflect && this._reflect.getOwnMetadata) {
|
||||||
const annotations = this._reflect.getMetadata('annotations', typeOrFunc);
|
return this._reflect.getOwnMetadata('annotations', typeOrFunc);
|
||||||
if (annotations) return annotations;
|
|
||||||
}
|
}
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
|
annotations(typeOrFunc: Type<any>): any[] {
|
||||||
|
const parentCtor = Object.getPrototypeOf(typeOrFunc.prototype).constructor;
|
||||||
|
const ownAnnotations = this._ownAnnotations(typeOrFunc, parentCtor) || [];
|
||||||
|
const parentAnnotations = parentCtor !== Object ? this.annotations(parentCtor) : [];
|
||||||
|
return parentAnnotations.concat(ownAnnotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _ownPropMetadata(typeOrFunc: any, parentCtor: any): {[key: string]: any[]} {
|
||||||
// Prefer the direct API.
|
// Prefer the direct API.
|
||||||
if ((<any>typeOrFunc).propMetadata) {
|
if ((<any>typeOrFunc).propMetadata &&
|
||||||
|
(<any>typeOrFunc).propMetadata !== parentCtor.propMetadata) {
|
||||||
let propMetadata = (<any>typeOrFunc).propMetadata;
|
let propMetadata = (<any>typeOrFunc).propMetadata;
|
||||||
if (typeof propMetadata === 'function' && propMetadata.propMetadata) {
|
if (typeof propMetadata === 'function' && propMetadata.propMetadata) {
|
||||||
propMetadata = propMetadata.propMetadata;
|
propMetadata = propMetadata.propMetadata;
|
||||||
|
@ -115,7 +153,8 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
// API of tsickle for lowering decorators to properties on the class.
|
// API of tsickle for lowering decorators to properties on the class.
|
||||||
if ((<any>typeOrFunc).propDecorators) {
|
if ((<any>typeOrFunc).propDecorators &&
|
||||||
|
(<any>typeOrFunc).propDecorators !== parentCtor.propDecorators) {
|
||||||
const propDecorators = (<any>typeOrFunc).propDecorators;
|
const propDecorators = (<any>typeOrFunc).propDecorators;
|
||||||
const propMetadata = <{[key: string]: any[]}>{};
|
const propMetadata = <{[key: string]: any[]}>{};
|
||||||
Object.keys(propDecorators).forEach(prop => {
|
Object.keys(propDecorators).forEach(prop => {
|
||||||
|
@ -125,11 +164,32 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
// API for metadata created by invoking the decorators.
|
// API for metadata created by invoking the decorators.
|
||||||
if (this._reflect && this._reflect.getMetadata) {
|
if (this._reflect && this._reflect.getOwnMetadata) {
|
||||||
const propMetadata = this._reflect.getMetadata('propMetadata', typeOrFunc);
|
return this._reflect.getOwnMetadata('propMetadata', typeOrFunc);
|
||||||
if (propMetadata) return propMetadata;
|
|
||||||
}
|
}
|
||||||
return {};
|
}
|
||||||
|
|
||||||
|
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
|
||||||
|
const parentCtor = Object.getPrototypeOf(typeOrFunc.prototype).constructor;
|
||||||
|
const propMetadata: {[key: string]: any[]} = {};
|
||||||
|
if (parentCtor !== Object) {
|
||||||
|
const parentPropMetadata = this.propMetadata(parentCtor);
|
||||||
|
Object.keys(parentPropMetadata).forEach((propName) => {
|
||||||
|
propMetadata[propName] = parentPropMetadata[propName];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const ownPropMetadata = this._ownPropMetadata(typeOrFunc, parentCtor);
|
||||||
|
if (ownPropMetadata) {
|
||||||
|
Object.keys(ownPropMetadata).forEach((propName) => {
|
||||||
|
const decorators: any[] = [];
|
||||||
|
if (propMetadata.hasOwnProperty(propName)) {
|
||||||
|
decorators.push(...propMetadata[propName]);
|
||||||
|
}
|
||||||
|
decorators.push(...ownPropMetadata[propName]);
|
||||||
|
propMetadata[propName] = decorators;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return propMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
||||||
|
|
|
@ -262,7 +262,7 @@ export function makeDecorator(
|
||||||
const metaCtor = makeMetadataCtor([props]);
|
const metaCtor = makeMetadataCtor([props]);
|
||||||
|
|
||||||
function DecoratorFactory(objOrType: any): (cls: any) => any {
|
function DecoratorFactory(objOrType: any): (cls: any) => any {
|
||||||
if (!(Reflect && Reflect.getMetadata)) {
|
if (!(Reflect && Reflect.getOwnMetadata)) {
|
||||||
throw 'reflect-metadata shim is required when using class decorators';
|
throw 'reflect-metadata shim is required when using class decorators';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +327,7 @@ export function makeParamDecorator(
|
||||||
return ParamDecorator;
|
return ParamDecorator;
|
||||||
|
|
||||||
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
|
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
|
||||||
const parameters: any[][] = Reflect.getMetadata('parameters', cls) || [];
|
const parameters: any[][] = Reflect.getOwnMetadata('parameters', cls) || [];
|
||||||
|
|
||||||
// there might be gaps if some in between parameters do not have annotations.
|
// there might be gaps if some in between parameters do not have annotations.
|
||||||
// we pad with nulls.
|
// we pad with nulls.
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Reflector} from '@angular/core/src/reflection/reflection';
|
import {Reflector} from '@angular/core/src/reflection/reflection';
|
||||||
import {ReflectionCapabilities} from '@angular/core/src/reflection/reflection_capabilities';
|
import {DELEGATE_CTOR, ReflectionCapabilities} from '@angular/core/src/reflection/reflection_capabilities';
|
||||||
import {makeDecorator, makeParamDecorator, makePropDecorator} from '@angular/core/src/util/decorators';
|
import {makeDecorator, makeParamDecorator, makePropDecorator} from '@angular/core/src/util/decorators';
|
||||||
|
|
||||||
interface ClassDecoratorFactory {
|
interface ClassDecoratorFactory {
|
||||||
|
@ -107,7 +107,6 @@ export function main() {
|
||||||
class ForwardDep {}
|
class ForwardDep {}
|
||||||
expect(reflector.parameters(Forward)).toEqual([[ForwardDep]]);
|
expect(reflector.parameters(Forward)).toEqual([[ForwardDep]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('propMetadata', () => {
|
describe('propMetadata', () => {
|
||||||
|
@ -117,6 +116,15 @@ export function main() {
|
||||||
expect(p['c']).toEqual([new PropDecorator('p3')]);
|
expect(p['c']).toEqual([new PropDecorator('p3')]);
|
||||||
expect(p['someMethod']).toEqual([new PropDecorator('p4')]);
|
expect(p['someMethod']).toEqual([new PropDecorator('p4')]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should also return metadata if the class has no decorator', () => {
|
||||||
|
class Test {
|
||||||
|
@PropDecorator('test')
|
||||||
|
prop: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(reflector.propMetadata(Test)).toEqual({'prop': [new PropDecorator('test')]});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('annotations', () => {
|
describe('annotations', () => {
|
||||||
|
@ -154,5 +162,321 @@ export function main() {
|
||||||
expect(func(obj, ['value'])).toEqual('value');
|
expect(func(obj, ['value'])).toEqual('value');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ctor inheritance detection', () => {
|
||||||
|
it('should use the right regex', () => {
|
||||||
|
class Parent {}
|
||||||
|
|
||||||
|
class ChildNoCtor extends Parent {}
|
||||||
|
class ChildWithCtor extends Parent {
|
||||||
|
constructor() { super(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(DELEGATE_CTOR.exec(ChildNoCtor.toString())).toBeTruthy();
|
||||||
|
expect(DELEGATE_CTOR.exec(ChildWithCtor.toString())).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('inheritance with decorators', () => {
|
||||||
|
it('should inherit annotations', () => {
|
||||||
|
|
||||||
|
@ClassDecorator({value: 'parent'})
|
||||||
|
class Parent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@ClassDecorator({value: 'child'})
|
||||||
|
class Child extends Parent {
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChildNoDecorators extends Parent {}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.annotations(Parent)).toEqual([new ClassDecorator({value: 'parent'})]);
|
||||||
|
|
||||||
|
expect(reflector.annotations(Child)).toEqual([
|
||||||
|
new ClassDecorator({value: 'parent'}), new ClassDecorator({value: 'child'})
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.annotations(ChildNoDecorators)).toEqual([new ClassDecorator(
|
||||||
|
{value: 'parent'})]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit parameters', () => {
|
||||||
|
class A {}
|
||||||
|
class B {}
|
||||||
|
class C {}
|
||||||
|
|
||||||
|
// Note: We need the class decorator as well,
|
||||||
|
// as otherwise TS won't capture the ctor arguments!
|
||||||
|
@ClassDecorator({value: 'parent'})
|
||||||
|
class Parent {
|
||||||
|
constructor(@ParamDecorator('a') a: A, @ParamDecorator('b') b: B) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {}
|
||||||
|
|
||||||
|
// Note: We need the class decorator as well,
|
||||||
|
// as otherwise TS won't capture the ctor arguments!
|
||||||
|
@ClassDecorator({value: 'child'})
|
||||||
|
class ChildWithCtor extends Parent {
|
||||||
|
constructor(@ParamDecorator('c') c: C) { super(null, null); }
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChildWithCtorNoDecorator extends Parent {
|
||||||
|
constructor(a: any, b: any, c: any) { super(null, null); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.parameters(Parent)).toEqual([
|
||||||
|
[A, new ParamDecorator('a')], [B, new ParamDecorator('b')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.parameters(Child)).toEqual([
|
||||||
|
[A, new ParamDecorator('a')], [B, new ParamDecorator('b')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.parameters(ChildWithCtor)).toEqual([[C, new ParamDecorator('c')]]);
|
||||||
|
|
||||||
|
// If we have no decorator, we don't get metadata about the ctor params.
|
||||||
|
// But we should still get an array of the right length based on function.length.
|
||||||
|
expect(reflector.parameters(ChildWithCtorNoDecorator)).toEqual([
|
||||||
|
undefined, undefined, undefined
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit property metadata', () => {
|
||||||
|
class A {}
|
||||||
|
class B {}
|
||||||
|
class C {}
|
||||||
|
|
||||||
|
class Parent {
|
||||||
|
@PropDecorator('a')
|
||||||
|
a: A;
|
||||||
|
@PropDecorator('b1')
|
||||||
|
b: B;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
@PropDecorator('b2')
|
||||||
|
b: B;
|
||||||
|
@PropDecorator('c')
|
||||||
|
c: C;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.propMetadata(Parent)).toEqual({
|
||||||
|
'a': [new PropDecorator('a')],
|
||||||
|
'b': [new PropDecorator('b1')],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(reflector.propMetadata(Child)).toEqual({
|
||||||
|
'a': [new PropDecorator('a')],
|
||||||
|
'b': [new PropDecorator('b1'), new PropDecorator('b2')],
|
||||||
|
'c': [new PropDecorator('c')]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit lifecycle hooks', () => {
|
||||||
|
class Parent {
|
||||||
|
hook1() {}
|
||||||
|
hook2() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
hook2() {}
|
||||||
|
hook3() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hooks(symbol: any, names: string[]): boolean[] {
|
||||||
|
return names.map(name => reflector.hasLifecycleHook(symbol, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(hooks(Parent, ['hook1', 'hook2', 'hook3'])).toEqual([true, true, false]);
|
||||||
|
|
||||||
|
expect(hooks(Child, ['hook1', 'hook2', 'hook3'])).toEqual([true, true, true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('inheritance with tsickle', () => {
|
||||||
|
it('should inherit annotations', () => {
|
||||||
|
|
||||||
|
class Parent {
|
||||||
|
static decorators = [{type: ClassDecorator, args: [{value: 'parent'}]}];
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
static decorators = [{type: ClassDecorator, args: [{value: 'child'}]}];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChildNoDecorators extends Parent {}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.annotations(Parent)).toEqual([new ClassDecorator({value: 'parent'})]);
|
||||||
|
|
||||||
|
expect(reflector.annotations(Child)).toEqual([
|
||||||
|
new ClassDecorator({value: 'parent'}), new ClassDecorator({value: 'child'})
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.annotations(ChildNoDecorators)).toEqual([new ClassDecorator(
|
||||||
|
{value: 'parent'})]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit parameters', () => {
|
||||||
|
class A {}
|
||||||
|
class B {}
|
||||||
|
class C {}
|
||||||
|
|
||||||
|
class Parent {
|
||||||
|
static ctorParameters = () =>
|
||||||
|
[{type: A, decorators: [{type: ParamDecorator, args: ['a']}]},
|
||||||
|
{type: B, decorators: [{type: ParamDecorator, args: ['b']}]},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {}
|
||||||
|
|
||||||
|
class ChildWithCtor extends Parent {
|
||||||
|
static ctorParameters =
|
||||||
|
() => [{type: C, decorators: [{type: ParamDecorator, args: ['c']}]}, ];
|
||||||
|
constructor() { super(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.parameters(Parent)).toEqual([
|
||||||
|
[A, new ParamDecorator('a')], [B, new ParamDecorator('b')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.parameters(Child)).toEqual([
|
||||||
|
[A, new ParamDecorator('a')], [B, new ParamDecorator('b')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.parameters(ChildWithCtor)).toEqual([[C, new ParamDecorator('c')]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit property metadata', () => {
|
||||||
|
class A {}
|
||||||
|
class B {}
|
||||||
|
class C {}
|
||||||
|
|
||||||
|
class Parent {
|
||||||
|
static propDecorators: any = {
|
||||||
|
'a': [{type: PropDecorator, args: ['a']}],
|
||||||
|
'b': [{type: PropDecorator, args: ['b1']}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
static propDecorators: any = {
|
||||||
|
'b': [{type: PropDecorator, args: ['b2']}],
|
||||||
|
'c': [{type: PropDecorator, args: ['c']}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.propMetadata(Parent)).toEqual({
|
||||||
|
'a': [new PropDecorator('a')],
|
||||||
|
'b': [new PropDecorator('b1')],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(reflector.propMetadata(Child)).toEqual({
|
||||||
|
'a': [new PropDecorator('a')],
|
||||||
|
'b': [new PropDecorator('b1'), new PropDecorator('b2')],
|
||||||
|
'c': [new PropDecorator('c')]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('inheritance with es5 API', () => {
|
||||||
|
it('should inherit annotations', () => {
|
||||||
|
|
||||||
|
class Parent {
|
||||||
|
static annotations = [new ClassDecorator({value: 'parent'})];
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
static annotations = [new ClassDecorator({value: 'child'})];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChildNoDecorators extends Parent {}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.annotations(Parent)).toEqual([new ClassDecorator({value: 'parent'})]);
|
||||||
|
|
||||||
|
expect(reflector.annotations(Child)).toEqual([
|
||||||
|
new ClassDecorator({value: 'parent'}), new ClassDecorator({value: 'child'})
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.annotations(ChildNoDecorators)).toEqual([new ClassDecorator(
|
||||||
|
{value: 'parent'})]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit parameters', () => {
|
||||||
|
class A {}
|
||||||
|
class B {}
|
||||||
|
class C {}
|
||||||
|
|
||||||
|
class Parent {
|
||||||
|
static parameters = [
|
||||||
|
[A, new ParamDecorator('a')],
|
||||||
|
[B, new ParamDecorator('b')],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {}
|
||||||
|
|
||||||
|
class ChildWithCtor extends Parent {
|
||||||
|
static parameters = [
|
||||||
|
[C, new ParamDecorator('c')],
|
||||||
|
];
|
||||||
|
constructor() { super(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.parameters(Parent)).toEqual([
|
||||||
|
[A, new ParamDecorator('a')], [B, new ParamDecorator('b')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.parameters(Child)).toEqual([
|
||||||
|
[A, new ParamDecorator('a')], [B, new ParamDecorator('b')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(reflector.parameters(ChildWithCtor)).toEqual([[C, new ParamDecorator('c')]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inherit property metadata', () => {
|
||||||
|
class A {}
|
||||||
|
class B {}
|
||||||
|
class C {}
|
||||||
|
|
||||||
|
class Parent {
|
||||||
|
static propMetadata: any = {
|
||||||
|
'a': [new PropDecorator('a')],
|
||||||
|
'b': [new PropDecorator('b1')],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
static propMetadata: any = {
|
||||||
|
'b': [new PropDecorator('b2')],
|
||||||
|
'c': [new PropDecorator('c')],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that metadata for Parent was not changed!
|
||||||
|
expect(reflector.propMetadata(Parent)).toEqual({
|
||||||
|
'a': [new PropDecorator('a')],
|
||||||
|
'b': [new PropDecorator('b1')],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(reflector.propMetadata(Child)).toEqual({
|
||||||
|
'a': [new PropDecorator('a')],
|
||||||
|
'b': [new PropDecorator('b1'), new PropDecorator('b2')],
|
||||||
|
'c': [new PropDecorator('c')]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ export function main() {
|
||||||
it('should invoke as decorator', () => {
|
it('should invoke as decorator', () => {
|
||||||
function Type() {}
|
function Type() {}
|
||||||
TestDecorator({marker: 'WORKS'})(Type);
|
TestDecorator({marker: 'WORKS'})(Type);
|
||||||
const annotations = Reflect.getMetadata('annotations', Type);
|
const annotations = Reflect.getOwnMetadata('annotations', Type);
|
||||||
expect(annotations[0].marker).toEqual('WORKS');
|
expect(annotations[0].marker).toEqual('WORKS');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,15 @@ export class StringMapWrapper {
|
||||||
export interface Predicate<T> { (value: T, index?: number, array?: T[]): boolean; }
|
export interface Predicate<T> { (value: T, index?: number, array?: T[]): boolean; }
|
||||||
|
|
||||||
export class ListWrapper {
|
export class ListWrapper {
|
||||||
|
static findLast<T>(arr: T[], condition: (value: T) => boolean): T {
|
||||||
|
for (let i = arr.length - 1; i >= 0; i--) {
|
||||||
|
if (condition(arr[i])) {
|
||||||
|
return arr[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
static removeAll<T>(list: T[], items: T[]) {
|
static removeAll<T>(list: T[], items: T[]) {
|
||||||
for (let i = 0; i < items.length; ++i) {
|
for (let i = 0; i < items.length; ++i) {
|
||||||
const index = list.indexOf(items[i]);
|
const index = list.indexOf(items[i]);
|
||||||
|
|
|
@ -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
|
// Add class decorators
|
||||||
if (classDeclaration.decorators) {
|
if (classDeclaration.decorators) {
|
||||||
result.decorators = getDecorators(classDeclaration.decorators);
|
result.decorators = getDecorators(classDeclaration.decorators);
|
||||||
|
@ -196,8 +205,7 @@ export class MetadataCollector {
|
||||||
result.statics = statics;
|
result.statics = statics;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.decorators || members || statics ? recordEntry(result, classDeclaration) :
|
return recordEntry(result, classDeclaration);
|
||||||
undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Predeclare classes and functions
|
// Predeclare classes and functions
|
||||||
|
@ -257,11 +265,7 @@ export class MetadataCollector {
|
||||||
const className = classDeclaration.name.text;
|
const className = classDeclaration.name.text;
|
||||||
if (node.flags & ts.NodeFlags.Export) {
|
if (node.flags & ts.NodeFlags.Export) {
|
||||||
if (!metadata) metadata = {};
|
if (!metadata) metadata = {};
|
||||||
if (classDeclaration.decorators) {
|
metadata[className] = classMetadataOf(classDeclaration);
|
||||||
metadata[className] = classMetadataOf(classDeclaration);
|
|
||||||
} else {
|
|
||||||
metadata[className] = {__symbolic: 'class'};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise don't record metadata for the class.
|
// 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) {
|
if (member.decorators) {
|
||||||
member.decorators.forEach(validateExpression);
|
member.decorators.forEach(validateExpression);
|
||||||
}
|
}
|
||||||
if (isMethodMetadata(member) && member.parameterDecorators) {
|
if (isMethodMetadata(member) && member.parameterDecorators) {
|
||||||
member.parameterDecorators.forEach(validateExpression);
|
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);
|
member.parameters.forEach(validateExpression);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,7 +492,7 @@ function validateMetadata(
|
||||||
}
|
}
|
||||||
if (classData.members) {
|
if (classData.members) {
|
||||||
Object.getOwnPropertyNames(classData.members)
|
Object.getOwnPropertyNames(classData.members)
|
||||||
.forEach(name => classData.members[name].forEach(validateMember));
|
.forEach(name => classData.members[name].forEach((m) => validateMember(classData, m)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ export interface ModuleExportMetadata {
|
||||||
|
|
||||||
export interface ClassMetadata {
|
export interface ClassMetadata {
|
||||||
__symbolic: 'class';
|
__symbolic: 'class';
|
||||||
|
extends?: MetadataSymbolicExpression|MetadataError;
|
||||||
decorators?: (MetadataSymbolicExpression|MetadataError)[];
|
decorators?: (MetadataSymbolicExpression|MetadataError)[];
|
||||||
members?: MetadataMap;
|
members?: MetadataMap;
|
||||||
statics?: {[name: string]: MetadataValue | FunctionMetadata};
|
statics?: {[name: string]: MetadataValue | FunctionMetadata};
|
||||||
|
|
|
@ -44,6 +44,9 @@ describe('Collector', () => {
|
||||||
'static-method-call.ts',
|
'static-method-call.ts',
|
||||||
'static-method-with-if.ts',
|
'static-method-with-if.ts',
|
||||||
'static-method-with-default.ts',
|
'static-method-with-default.ts',
|
||||||
|
'class-inheritance.ts',
|
||||||
|
'class-inheritance-parent.ts',
|
||||||
|
'class-inheritance-declarations.d.ts'
|
||||||
]);
|
]);
|
||||||
service = ts.createLanguageService(host, documentRegistry);
|
service = ts.createLanguageService(host, documentRegistry);
|
||||||
program = service.getProgram();
|
program = service.getProgram();
|
||||||
|
@ -616,6 +619,32 @@ describe('Collector', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('inheritance', () => {
|
||||||
|
it('should record `extends` clauses for declared classes', () => {
|
||||||
|
const metadata = collector.getMetadata(program.getSourceFile('/class-inheritance.ts'));
|
||||||
|
expect(metadata.metadata['DeclaredChildClass'])
|
||||||
|
.toEqual({__symbolic: 'class', extends: {__symbolic: 'reference', name: 'ParentClass'}});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should record `extends` clauses for classes in the same file', () => {
|
||||||
|
const metadata = collector.getMetadata(program.getSourceFile('/class-inheritance.ts'));
|
||||||
|
expect(metadata.metadata['ChildClassSameFile'])
|
||||||
|
.toEqual({__symbolic: 'class', extends: {__symbolic: 'reference', name: 'ParentClass'}});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should record `extends` clauses for classes in a different file', () => {
|
||||||
|
const metadata = collector.getMetadata(program.getSourceFile('/class-inheritance.ts'));
|
||||||
|
expect(metadata.metadata['ChildClassOtherFile']).toEqual({
|
||||||
|
__symbolic: 'class',
|
||||||
|
extends: {
|
||||||
|
__symbolic: 'reference',
|
||||||
|
module: './class-inheritance-parent',
|
||||||
|
name: 'ParentClassFromOtherFile'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function override(fileName: string, content: string) {
|
function override(fileName: string, content: string) {
|
||||||
host.overrideFile(fileName, content);
|
host.overrideFile(fileName, content);
|
||||||
host.addFile(fileName);
|
host.addFile(fileName);
|
||||||
|
@ -844,6 +873,20 @@ const FILES: Directory = {
|
||||||
export abstract class AbstractClass {}
|
export abstract class AbstractClass {}
|
||||||
export declare class DeclaredClass {}
|
export declare class DeclaredClass {}
|
||||||
`,
|
`,
|
||||||
|
'class-inheritance-parent.ts': `
|
||||||
|
export class ParentClassFromOtherFile {}
|
||||||
|
`,
|
||||||
|
'class-inheritance.ts': `
|
||||||
|
import {ParentClassFromOtherFile} from './class-inheritance-parent';
|
||||||
|
|
||||||
|
export class ParentClass {}
|
||||||
|
|
||||||
|
export declare class DeclaredChildClass extends ParentClass {}
|
||||||
|
|
||||||
|
export class ChildClassSameFile extends ParentClass {}
|
||||||
|
|
||||||
|
export class ChildClassOtherFile extends ParentClassFromOtherFile {}
|
||||||
|
`,
|
||||||
'exported-functions.ts': `
|
'exported-functions.ts': `
|
||||||
export function one(a: string, b: string, c: string) {
|
export function one(a: string, b: string, c: string) {
|
||||||
return {a: a, b: b, c: c};
|
return {a: a, b: b, c: c};
|
||||||
|
@ -877,9 +920,6 @@ const FILES: Directory = {
|
||||||
export const constValue = 100;
|
export const constValue = 100;
|
||||||
`,
|
`,
|
||||||
'static-method.ts': `
|
'static-method.ts': `
|
||||||
import {Injectable} from 'angular2/core';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MyModule {
|
export class MyModule {
|
||||||
static with(comp: any): any[] {
|
static with(comp: any): any[] {
|
||||||
return [
|
return [
|
||||||
|
@ -890,9 +930,6 @@ const FILES: Directory = {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
'static-method-with-default.ts': `
|
'static-method-with-default.ts': `
|
||||||
import {Injectable} from 'angular2/core';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MyModule {
|
export class MyModule {
|
||||||
static with(comp: any, foo: boolean = true, bar: boolean = false): any[] {
|
static with(comp: any, foo: boolean = true, bar: boolean = false): any[] {
|
||||||
return [
|
return [
|
||||||
|
@ -913,9 +950,6 @@ const FILES: Directory = {
|
||||||
export class Foo { }
|
export class Foo { }
|
||||||
`,
|
`,
|
||||||
'static-field.ts': `
|
'static-field.ts': `
|
||||||
import {Injectable} from 'angular2/core';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MyModule {
|
export class MyModule {
|
||||||
static VALUE = 'Some string';
|
static VALUE = 'Some string';
|
||||||
}
|
}
|
||||||
|
@ -930,9 +964,6 @@ const FILES: Directory = {
|
||||||
export class Foo { }
|
export class Foo { }
|
||||||
`,
|
`,
|
||||||
'static-method-with-if.ts': `
|
'static-method-with-if.ts': `
|
||||||
import {Injectable} from 'angular2/core';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MyModule {
|
export class MyModule {
|
||||||
static with(cond: boolean): any[] {
|
static with(cond: boolean): any[] {
|
||||||
return [
|
return [
|
||||||
|
|
Loading…
Reference in New Issue