fix(core): add `noSideEffects()` to `make*Decorator()` functions (#35769)

This causes all the `make*Decorator()` functions to be considered pure and to be eligible for associated tree shaking by Closure.

PR Close #35769
This commit is contained in:
Doug Parker 2020-02-21 11:13:27 -08:00 committed by atscott
parent 4052dd8188
commit dc6a7918e3
2 changed files with 103 additions and 89 deletions

View File

@ -8,6 +8,10 @@
import {Type} from '../interface/type'; import {Type} from '../interface/type';
import {noSideEffects} from './closure';
/** /**
* An interface implemented by all Angular type decorators, which allows them to be used as * An interface implemented by all Angular type decorators, which allows them to be used as
* decorators as well as Angular syntax. * decorators as well as Angular syntax.
@ -44,39 +48,41 @@ export function makeDecorator<T>(
additionalProcessing?: (type: Type<T>) => void, additionalProcessing?: (type: Type<T>) => void,
typeFn?: (type: Type<T>, ...args: any[]) => void): typeFn?: (type: Type<T>, ...args: any[]) => void):
{new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} { {new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} {
const metaCtor = makeMetadataCtor(props); return noSideEffects(() => {
const metaCtor = makeMetadataCtor(props);
function DecoratorFactory( function DecoratorFactory(
this: unknown | typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => any { this: unknown | typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => any {
if (this instanceof DecoratorFactory) { if (this instanceof DecoratorFactory) {
metaCtor.call(this, ...args); metaCtor.call(this, ...args);
return this as typeof DecoratorFactory; return this as typeof DecoratorFactory;
}
const annotationInstance = new (DecoratorFactory as any)(...args);
return function TypeDecorator(cls: Type<T>) {
if (typeFn) typeFn(cls, ...args);
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
(cls as any)[ANNOTATIONS] :
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
annotations.push(annotationInstance);
if (additionalProcessing) additionalProcessing(cls);
return cls;
};
} }
const annotationInstance = new (DecoratorFactory as any)(...args); if (parentClass) {
return function TypeDecorator(cls: Type<T>) { DecoratorFactory.prototype = Object.create(parentClass.prototype);
if (typeFn) typeFn(cls, ...args); }
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
(cls as any)[ANNOTATIONS] :
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
annotations.push(annotationInstance);
DecoratorFactory.prototype.ngMetadataName = name;
if (additionalProcessing) additionalProcessing(cls); (DecoratorFactory as any).annotationCls = DecoratorFactory;
return DecoratorFactory as any;
return cls; });
};
}
if (parentClass) {
DecoratorFactory.prototype = Object.create(parentClass.prototype);
}
DecoratorFactory.prototype.ngMetadataName = name;
(DecoratorFactory as any).annotationCls = DecoratorFactory;
return DecoratorFactory as any;
} }
function makeMetadataCtor(props?: (...args: any[]) => any): any { function makeMetadataCtor(props?: (...args: any[]) => any): any {
@ -92,77 +98,82 @@ function makeMetadataCtor(props?: (...args: any[]) => any): any {
export function makeParamDecorator( export function makeParamDecorator(
name: string, props?: (...args: any[]) => any, parentClass?: any): any { name: string, props?: (...args: any[]) => any, parentClass?: any): any {
const metaCtor = makeMetadataCtor(props); return noSideEffects(() => {
function ParamDecoratorFactory( const metaCtor = makeMetadataCtor(props);
this: unknown | typeof ParamDecoratorFactory, ...args: any[]): any { function ParamDecoratorFactory(
if (this instanceof ParamDecoratorFactory) { this: unknown | typeof ParamDecoratorFactory, ...args: any[]): any {
metaCtor.apply(this, args); if (this instanceof ParamDecoratorFactory) {
return this; metaCtor.apply(this, args);
} return this;
const annotationInstance = new (<any>ParamDecoratorFactory)(...args);
(<any>ParamDecorator).annotation = annotationInstance;
return ParamDecorator;
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const parameters = cls.hasOwnProperty(PARAMETERS) ?
(cls as any)[PARAMETERS] :
Object.defineProperty(cls, PARAMETERS, {value: []})[PARAMETERS];
// there might be gaps if some in between parameters do not have annotations.
// we pad with nulls.
while (parameters.length <= index) {
parameters.push(null);
} }
const annotationInstance = new (<any>ParamDecoratorFactory)(...args);
(parameters[index] = parameters[index] || []).push(annotationInstance); (<any>ParamDecorator).annotation = annotationInstance;
return cls; return ParamDecorator;
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const parameters = cls.hasOwnProperty(PARAMETERS) ?
(cls as any)[PARAMETERS] :
Object.defineProperty(cls, PARAMETERS, {value: []})[PARAMETERS];
// there might be gaps if some in between parameters do not have annotations.
// we pad with nulls.
while (parameters.length <= index) {
parameters.push(null);
}
(parameters[index] = parameters[index] || []).push(annotationInstance);
return cls;
}
} }
} if (parentClass) {
if (parentClass) { ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
ParamDecoratorFactory.prototype = Object.create(parentClass.prototype); }
} ParamDecoratorFactory.prototype.ngMetadataName = name;
ParamDecoratorFactory.prototype.ngMetadataName = name; (<any>ParamDecoratorFactory).annotationCls = ParamDecoratorFactory;
(<any>ParamDecoratorFactory).annotationCls = ParamDecoratorFactory; return ParamDecoratorFactory;
return ParamDecoratorFactory; });
} }
export function makePropDecorator( export function makePropDecorator(
name: string, props?: (...args: any[]) => any, parentClass?: any, name: string, props?: (...args: any[]) => any, parentClass?: any,
additionalProcessing?: (target: any, name: string, ...args: any[]) => void): any { additionalProcessing?: (target: any, name: string, ...args: any[]) => void): any {
const metaCtor = makeMetadataCtor(props); return noSideEffects(() => {
const metaCtor = makeMetadataCtor(props);
function PropDecoratorFactory(this: unknown | typeof PropDecoratorFactory, ...args: any[]): any { function PropDecoratorFactory(
if (this instanceof PropDecoratorFactory) { this: unknown | typeof PropDecoratorFactory, ...args: any[]): any {
metaCtor.apply(this, args); if (this instanceof PropDecoratorFactory) {
return this; metaCtor.apply(this, args);
return this;
}
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
function PropDecorator(target: any, name: string) {
const constructor = target.constructor;
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const meta = constructor.hasOwnProperty(PROP_METADATA) ?
(constructor as any)[PROP_METADATA] :
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
meta[name].unshift(decoratorInstance);
if (additionalProcessing) additionalProcessing(target, name, ...args);
}
return PropDecorator;
} }
const decoratorInstance = new (<any>PropDecoratorFactory)(...args); if (parentClass) {
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
function PropDecorator(target: any, name: string) {
const constructor = target.constructor;
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const meta = constructor.hasOwnProperty(PROP_METADATA) ?
(constructor as any)[PROP_METADATA] :
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
meta[name].unshift(decoratorInstance);
if (additionalProcessing) additionalProcessing(target, name, ...args);
} }
return PropDecorator; PropDecoratorFactory.prototype.ngMetadataName = name;
} (<any>PropDecoratorFactory).annotationCls = PropDecoratorFactory;
return PropDecoratorFactory;
if (parentClass) { });
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
}
PropDecoratorFactory.prototype.ngMetadataName = name;
(<any>PropDecoratorFactory).annotationCls = PropDecoratorFactory;
return PropDecoratorFactory;
} }

View File

@ -220,5 +220,8 @@
}, },
{ {
"name": "ɵɵinject" "name": "ɵɵinject"
},
{
"name": "noSideEffects"
} }
] ]