fix(core): make decorators closure safe (#16905)

This is required as e.g. `token` from `@Inject` is
accessed in string form via makeParamDecorator
but as a property in the `ReflectiveInjector`.

Closes #16889 as this is a more general fix.
This commit is contained in:
Tobias Bosch 2017-05-23 10:52:40 -07:00 committed by Chuck Jazdzewski
parent 5af143e8e4
commit a80ac0a8d3
9 changed files with 76 additions and 122 deletions

View File

@ -114,9 +114,15 @@ export function main() {
it('should read out the Directive metadata', () => { it('should read out the Directive metadata', () => {
const directiveMetadata = resolver.resolve(SomeDirective); const directiveMetadata = resolver.resolve(SomeDirective);
expect(directiveMetadata) expect(directiveMetadata).toEqual(new Directive({
.toEqual(new Directive( selector: 'someDirective',
{selector: 'someDirective', inputs: [], outputs: [], host: {}, queries: {}})); inputs: [],
outputs: [],
host: {},
queries: {},
exportAs: undefined,
providers: undefined
}));
}); });
it('should throw if not matching metadata is found', () => { it('should throw if not matching metadata is found', () => {
@ -136,11 +142,25 @@ export function main() {
class ChildWithDecorator extends Parent { class ChildWithDecorator extends Parent {
} }
expect(resolver.resolve(ChildNoDecorator)) expect(resolver.resolve(ChildNoDecorator)).toEqual(new Directive({
.toEqual(new Directive({selector: 'p', inputs: [], outputs: [], host: {}, queries: {}})); selector: 'p',
inputs: [],
outputs: [],
host: {},
queries: {},
exportAs: undefined,
providers: undefined
}));
expect(resolver.resolve(ChildWithDecorator)) expect(resolver.resolve(ChildWithDecorator)).toEqual(new Directive({
.toEqual(new Directive({selector: 'c', inputs: [], outputs: [], host: {}, queries: {}})); selector: 'c',
inputs: [],
outputs: [],
host: {},
queries: {},
exportAs: undefined,
providers: undefined
}));
}); });
describe('inputs', () => { describe('inputs', () => {

View File

@ -58,7 +58,7 @@ export interface Inject { token: any; }
* @stable * @stable
* @Annotation * @Annotation
*/ */
export const Inject: InjectDecorator = makeParamDecorator('Inject', [['token', undefined]]); export const Inject: InjectDecorator = makeParamDecorator('Inject', (token: any) => ({token}));
/** /**
@ -104,7 +104,7 @@ export interface Optional {}
* @stable * @stable
* @Annotation * @Annotation
*/ */
export const Optional: OptionalDecorator = makeParamDecorator('Optional', []); export const Optional: OptionalDecorator = makeParamDecorator('Optional');
/** /**
* Type of the Injectable decorator / constructor function. * Type of the Injectable decorator / constructor function.
@ -151,7 +151,7 @@ export interface Injectable {}
* @stable * @stable
* @Annotation * @Annotation
*/ */
export const Injectable: InjectableDecorator = <InjectableDecorator>makeDecorator('Injectable', []); export const Injectable: InjectableDecorator = <InjectableDecorator>makeDecorator('Injectable');
/** /**
* Type of the Self decorator / constructor function. * Type of the Self decorator / constructor function.
@ -195,7 +195,7 @@ export interface Self {}
* @stable * @stable
* @Annotation * @Annotation
*/ */
export const Self: SelfDecorator = makeParamDecorator('Self', []); export const Self: SelfDecorator = makeParamDecorator('Self');
/** /**
@ -240,7 +240,7 @@ export interface SkipSelf {}
* @stable * @stable
* @Annotation * @Annotation
*/ */
export const SkipSelf: SkipSelfDecorator = makeParamDecorator('SkipSelf', []); export const SkipSelf: SkipSelfDecorator = makeParamDecorator('SkipSelf');
/** /**
* Type of the Host decorator / constructor function. * Type of the Host decorator / constructor function.
@ -285,4 +285,4 @@ export interface Host {}
* @stable * @stable
* @Annotation * @Annotation
*/ */
export const Host: HostDecorator = makeParamDecorator('Host', []); export const Host: HostDecorator = makeParamDecorator('Host');

View File

@ -225,7 +225,7 @@ function _extractToken(
if (!Array.isArray(metadata)) { if (!Array.isArray(metadata)) {
if (metadata instanceof Inject) { if (metadata instanceof Inject) {
return _createDependency(metadata['token'], optional, null); return _createDependency(metadata.token, optional, null);
} else { } else {
return _createDependency(metadata, optional, null); return _createDependency(metadata, optional, null);
} }
@ -240,7 +240,7 @@ function _extractToken(
token = paramMetadata; token = paramMetadata;
} else if (paramMetadata instanceof Inject) { } else if (paramMetadata instanceof Inject) {
token = paramMetadata['token']; token = paramMetadata.token;
} else if (paramMetadata instanceof Optional) { } else if (paramMetadata instanceof Optional) {
optional = true; optional = true;

View File

@ -119,7 +119,7 @@ export interface Attribute { attributeName?: string; }
* @Annotation * @Annotation
*/ */
export const Attribute: AttributeDecorator = export const Attribute: AttributeDecorator =
makeParamDecorator('Attribute', [['attributeName', undefined]]); makeParamDecorator('Attribute', (attributeName?: string) => ({attributeName}));
/** /**
* Type of the Query metadata. * Type of the Query metadata.
@ -207,14 +207,8 @@ export type ContentChildren = Query;
export const ContentChildren: ContentChildrenDecorator = export const ContentChildren: ContentChildrenDecorator =
<ContentChildrenDecorator>makePropDecorator( <ContentChildrenDecorator>makePropDecorator(
'ContentChildren', 'ContentChildren',
[ (selector?: any, data: any = {}) =>
['selector', undefined], { ({selector, first: false, isViewQuery: false, descendants: false, ...data}),
first: false,
isViewQuery: false,
descendants: false,
read: undefined,
}
],
Query); Query);
/** /**
@ -273,15 +267,8 @@ export type ContentChild = Query;
* @Annotation * @Annotation
*/ */
export const ContentChild: ContentChildDecorator = makePropDecorator( export const ContentChild: ContentChildDecorator = makePropDecorator(
'ContentChild', 'ContentChild', (selector?: any, data: any = {}) =>
[ ({selector, first: true, isViewQuery: false, descendants: true, ...data}),
['selector', undefined], {
first: true,
isViewQuery: false,
descendants: true,
read: undefined,
}
],
Query); Query);
/** /**
@ -339,15 +326,8 @@ export type ViewChildren = Query;
* @Annotation * @Annotation
*/ */
export const ViewChildren: ViewChildrenDecorator = makePropDecorator( export const ViewChildren: ViewChildrenDecorator = makePropDecorator(
'ViewChildren', 'ViewChildren', (selector?: any, data: any = {}) =>
[ ({selector, first: false, isViewQuery: true, descendants: true, ...data}),
['selector', undefined], {
first: false,
isViewQuery: true,
descendants: true,
read: undefined,
}
],
Query); Query);
/** /**
@ -403,13 +383,6 @@ export type ViewChild = Query;
* @Annotation * @Annotation
*/ */
export const ViewChild: ViewChildDecorator = makePropDecorator( export const ViewChild: ViewChildDecorator = makePropDecorator(
'ViewChild', 'ViewChild', (selector: any, data: any) =>
[ ({selector, first: true, isViewQuery: true, descendants: true, ...data}),
['selector', undefined], {
first: true,
isViewQuery: true,
descendants: true,
read: undefined,
}
],
Query); Query);

View File

@ -399,15 +399,8 @@ export interface Directive {
* @stable * @stable
* @Annotation * @Annotation
*/ */
export const Directive: DirectiveDecorator = <DirectiveDecorator>makeDecorator('Directive', { export const Directive: DirectiveDecorator =
selector: undefined, <DirectiveDecorator>makeDecorator('Directive', (dir: Directive = {}) => dir);
inputs: undefined,
outputs: undefined,
host: undefined,
providers: undefined,
exportAs: undefined,
queries: undefined
});
/** /**
* Type of the Component decorator / constructor function. * Type of the Component decorator / constructor function.
@ -691,26 +684,7 @@ export interface Component extends Directive {
* @Annotation * @Annotation
*/ */
export const Component: ComponentDecorator = <ComponentDecorator>makeDecorator( export const Component: ComponentDecorator = <ComponentDecorator>makeDecorator(
'Component', { 'Component', (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}),
selector: undefined,
inputs: undefined,
outputs: undefined,
host: undefined,
exportAs: undefined,
moduleId: undefined,
providers: undefined,
viewProviders: undefined,
changeDetection: ChangeDetectionStrategy.Default,
queries: undefined,
templateUrl: undefined,
template: undefined,
styleUrls: undefined,
styles: undefined,
animations: undefined,
encapsulation: undefined,
interpolation: undefined,
entryComponents: undefined
},
Directive); Directive);
/** /**
@ -750,10 +724,8 @@ export interface Pipe {
* @stable * @stable
* @Annotation * @Annotation
*/ */
export const Pipe: PipeDecorator = <PipeDecorator>makeDecorator('Pipe', { export const Pipe: PipeDecorator =
name: undefined, <PipeDecorator>makeDecorator('Pipe', (p: Pipe) => ({pure: true, ...p}));
pure: true,
});
/** /**
@ -825,7 +797,7 @@ export interface Input {
* @Annotation * @Annotation
*/ */
export const Input: InputDecorator = export const Input: InputDecorator =
makePropDecorator('Input', [['bindingPropertyName', undefined]]); makePropDecorator('Input', (bindingPropertyName?: string) => ({bindingPropertyName}));
/** /**
* Type of the Output decorator / constructor function. * Type of the Output decorator / constructor function.
@ -891,7 +863,7 @@ export interface Output { bindingPropertyName?: string; }
* @Annotation * @Annotation
*/ */
export const Output: OutputDecorator = export const Output: OutputDecorator =
makePropDecorator('Output', [['bindingPropertyName', undefined]]); makePropDecorator('Output', (bindingPropertyName?: string) => ({bindingPropertyName}));
/** /**
@ -951,7 +923,7 @@ export interface HostBinding { hostPropertyName?: string; }
* @Annotation * @Annotation
*/ */
export const HostBinding: HostBindingDecorator = export const HostBinding: HostBindingDecorator =
makePropDecorator('HostBinding', [['hostPropertyName', undefined]]); makePropDecorator('HostBinding', (hostPropertyName?: string) => ({hostPropertyName}));
/** /**
@ -1013,4 +985,4 @@ export interface HostListener {
* @Annotation * @Annotation
*/ */
export const HostListener: HostListenerDecorator = export const HostListener: HostListenerDecorator =
makePropDecorator('HostListener', [['eventName', undefined], ['args', []]]); makePropDecorator('HostListener', (eventName?: string, args?: string[]) => ({eventName, args}));

View File

@ -190,13 +190,5 @@ export interface NgModule {
* @stable * @stable
* @Annotation * @Annotation
*/ */
export const NgModule: NgModuleDecorator = <NgModuleDecorator>makeDecorator('NgModule', { export const NgModule: NgModuleDecorator =
providers: undefined, <NgModuleDecorator>makeDecorator('NgModule', (ngModule: NgModule) => ngModule);
declarations: undefined,
imports: undefined,
exports: undefined,
entryComponents: undefined,
bootstrap: undefined,
schemas: undefined,
id: undefined,
});

View File

@ -262,9 +262,9 @@ export function Class(clsDef: ClassDefinition): Type<any> {
* @suppress {globalThis} * @suppress {globalThis}
*/ */
export function makeDecorator( export function makeDecorator(
name: string, props: {[name: string]: any}, parentClass?: any, name: string, props?: (...args: any[]) => any, parentClass?: any,
chainFn?: (fn: Function) => void): (...args: any[]) => (cls: any) => any { chainFn?: (fn: Function) => void): (...args: any[]) => (cls: any) => any {
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.getOwnMetadata)) { if (!(Reflect && Reflect.getOwnMetadata)) {
@ -301,25 +301,19 @@ export function makeDecorator(
return DecoratorFactory; return DecoratorFactory;
} }
function makeMetadataCtor(props: ([string, any] | {[key: string]: any})[]): any { function makeMetadataCtor(props?: (...args: any[]) => any): any {
return function ctor(...args: any[]) { return function ctor(...args: any[]) {
props.forEach((prop, i) => { if (props) {
const argVal = args[i]; const values = props(...args);
if (Array.isArray(prop)) { for (const propName in values) {
// plain parameter this[propName] = values[propName];
this[prop[0]] = argVal === undefined ? prop[1] : argVal;
} else {
for (const propName in prop) {
this[propName] =
argVal && argVal.hasOwnProperty(propName) ? argVal[propName] : prop[propName];
}
} }
}); }
}; };
} }
export function makeParamDecorator( export function makeParamDecorator(
name: string, props: ([string, any] | {[name: string]: any})[], parentClass?: any): any { name: string, props?: (...args: any[]) => any, parentClass?: any): any {
const metaCtor = makeMetadataCtor(props); const metaCtor = makeMetadataCtor(props);
function ParamDecoratorFactory(...args: any[]): any { function ParamDecoratorFactory(...args: any[]): any {
if (this instanceof ParamDecoratorFactory) { if (this instanceof ParamDecoratorFactory) {
@ -356,7 +350,7 @@ export function makeParamDecorator(
} }
export function makePropDecorator( export function makePropDecorator(
name: string, props: ([string, any] | {[key: string]: any})[], parentClass?: any): any { name: string, props?: (...args: any[]) => any, parentClass?: any): any {
const metaCtor = makeMetadataCtor(props); const metaCtor = makeMetadataCtor(props);
function PropDecoratorFactory(...args: any[]): any { function PropDecoratorFactory(...args: any[]): any {

View File

@ -29,10 +29,11 @@ interface PropDecorator {
} }
/** @Annotation */ const ClassDecorator = /** @Annotation */ const ClassDecorator =
<ClassDecoratorFactory>makeDecorator('ClassDecorator', {value: undefined}); <ClassDecoratorFactory>makeDecorator('ClassDecorator', (data: any) => data);
/** @Annotation */ const ParamDecorator = /** @Annotation */ const ParamDecorator =
makeParamDecorator('ParamDecorator', [['value', undefined]]); makeParamDecorator('ParamDecorator', (value: any) => ({value}));
/** @Annotation */ const PropDecorator = makePropDecorator('PropDecorator', [['value', undefined]]); /** @Annotation */ const PropDecorator =
makePropDecorator('PropDecorator', (value: any) => ({value}));
class AType { class AType {
constructor(public value: any) {} constructor(public value: any) {}

View File

@ -17,14 +17,15 @@ class DecoratedChild extends DecoratedParent {}
export function main() { export function main() {
const Reflect = global['Reflect']; const Reflect = global['Reflect'];
const TerminalDecorator = makeDecorator('TerminalDecorator', {terminal: true}); const TerminalDecorator =
makeDecorator('TerminalDecorator', (data: any) => ({terminal: true, ...data}));
const TestDecorator = makeDecorator( const TestDecorator = makeDecorator(
'TestDecorator', {marker: undefined}, Object, (fn: any) => fn.Terminal = TerminalDecorator); 'TestDecorator', (data: any) => data, Object, (fn: any) => fn.Terminal = TerminalDecorator);
describe('Property decorators', () => { describe('Property decorators', () => {
// https://github.com/angular/angular/issues/12224 // https://github.com/angular/angular/issues/12224
it('should work on the "watch" property', () => { it('should work on the "watch" property', () => {
const Prop = makePropDecorator('Prop', [['value', undefined]]); const Prop = makePropDecorator('Prop', (value: any) => ({value}));
class TestClass { class TestClass {
@Prop('firefox!') @Prop('firefox!')
@ -36,13 +37,14 @@ export function main() {
}); });
it('should work with any default plain values', () => { it('should work with any default plain values', () => {
const Default = makePropDecorator('Default', [['value', 5]]); const Default =
makePropDecorator('Default', (data: any) => ({value: data != null ? data : 5}));
expect(new Default(0)['value']).toEqual(0); expect(new Default(0)['value']).toEqual(0);
}); });
it('should work with any object values', () => { it('should work with any object values', () => {
// make sure we don't walk up the prototype chain // make sure we don't walk up the prototype chain
const Default = makePropDecorator('Default', [{value: 5}]); const Default = makePropDecorator('Default', (data: any) => ({value: 5, ...data}));
const value = Object.create({value: 10}); const value = Object.create({value: 10});
expect(new Default(value)['value']).toEqual(5); expect(new Default(value)['value']).toEqual(5);
}); });