feat(ivy): setClassMetadata() for assigning decorator metadata (#26860)
This commit introduces the setClassMetadata() private function, which adds metadata to a type in a way that can be accessed via Angular's ReflectionCapabilities. Currently, it writes to static fields as if the metadata being added was downleveled from decorators by tsickle. The plan is for ngtsc to emit code which calls this function, passing metadata on to the runtime for testing purposes. Calls to this function would then be tree-shaken away for production bundles. Testing strategy: proper operation of this function will be an integral part of TestBed metadata overriding. Angular core tests will fail if this is broken. PR Close #26860
This commit is contained in:
parent
afbee736ea
commit
ca1e538752
|
@ -43,6 +43,7 @@ const CORE_SUPPORTED_SYMBOLS = new Set<string>([
|
||||||
'defineInjector',
|
'defineInjector',
|
||||||
'ɵdefineNgModule',
|
'ɵdefineNgModule',
|
||||||
'inject',
|
'inject',
|
||||||
|
'ɵsetClassMetadata',
|
||||||
'ɵInjectableDef',
|
'ɵInjectableDef',
|
||||||
'ɵInjectorDef',
|
'ɵInjectorDef',
|
||||||
'ɵNgModuleDefWithMeta',
|
'ɵNgModuleDefWithMeta',
|
||||||
|
|
|
@ -123,6 +123,7 @@ export class Identifiers {
|
||||||
moduleName: CORE,
|
moduleName: CORE,
|
||||||
};
|
};
|
||||||
static createComponentFactory: o.ExternalReference = {name: 'ɵccf', moduleName: CORE};
|
static createComponentFactory: o.ExternalReference = {name: 'ɵccf', moduleName: CORE};
|
||||||
|
static setClassMetadata: o.ExternalReference = {name: 'ɵsetClassMetadata', moduleName: CORE};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTokenForReference(reference: any): CompileTokenMetadata {
|
export function createTokenForReference(reference: any): CompileTokenMetadata {
|
||||||
|
|
|
@ -122,7 +122,8 @@ export {
|
||||||
i18nMapping as ɵi18nMapping,
|
i18nMapping as ɵi18nMapping,
|
||||||
I18nInstruction as ɵI18nInstruction,
|
I18nInstruction as ɵI18nInstruction,
|
||||||
I18nExpInstruction as ɵI18nExpInstruction,
|
I18nExpInstruction as ɵI18nExpInstruction,
|
||||||
WRAP_RENDERER_FACTORY2 as ɵWRAP_RENDERER_FACTORY2
|
WRAP_RENDERER_FACTORY2 as ɵWRAP_RENDERER_FACTORY2,
|
||||||
|
setClassMetadata as ɵsetClassMetadata,
|
||||||
} from './render3/index';
|
} from './render3/index';
|
||||||
|
|
||||||
export { Render3DebugRendererFactory2 as ɵRender3DebugRendererFactory2 } from './render3/debug';
|
export { Render3DebugRendererFactory2 as ɵRender3DebugRendererFactory2 } from './render3/debug';
|
||||||
|
|
|
@ -23,8 +23,10 @@ export {InjectableDef as ɵInjectableDef, InjectorDef as ɵInjectorDef, defineIn
|
||||||
export {inject} from './di/injector_compatibility';
|
export {inject} from './di/injector_compatibility';
|
||||||
export {NgModuleDef as ɵNgModuleDef, NgModuleDefWithMeta as ɵNgModuleDefWithMeta} from './metadata/ng_module';
|
export {NgModuleDef as ɵNgModuleDef, NgModuleDefWithMeta as ɵNgModuleDefWithMeta} from './metadata/ng_module';
|
||||||
export {defineNgModule as ɵdefineNgModule} from './render3/definition';
|
export {defineNgModule as ɵdefineNgModule} from './render3/definition';
|
||||||
|
export {setClassMetadata as ɵsetClassMetadata} from './render3/metadata';
|
||||||
export {NgModuleFactory as ɵNgModuleFactory} from './render3/ng_module_ref';
|
export {NgModuleFactory as ɵNgModuleFactory} from './render3/ng_module_ref';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The existence of this constant (in this particular file) informs the Angular compiler that the
|
* The existence of this constant (in this particular file) informs the Angular compiler that the
|
||||||
* current program is actually @angular/core, which needs to be compiled specially.
|
* current program is actually @angular/core, which needs to be compiled specially.
|
||||||
|
|
|
@ -113,6 +113,10 @@ export {
|
||||||
AttributeMarker
|
AttributeMarker
|
||||||
} from './interfaces/node';
|
} from './interfaces/node';
|
||||||
|
|
||||||
|
export {
|
||||||
|
setClassMetadata,
|
||||||
|
} from './metadata';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
pipe,
|
pipe,
|
||||||
pipeBind1,
|
pipeBind1,
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* @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 {Type} from '../type';
|
||||||
|
|
||||||
|
interface TypeWithMetadata extends Type<any> {
|
||||||
|
decorators?: any[];
|
||||||
|
ctorParameters?: any[];
|
||||||
|
propDecorators?: {[field: string]: any};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds decorator, constructor, and property metadata to a given type via static metadata fields
|
||||||
|
* on the type.
|
||||||
|
*
|
||||||
|
* These metadata fields can later be read with Angular's `ReflectionCapabilities` API.
|
||||||
|
*
|
||||||
|
* Calls to `setClassMetadata` can be marked as pure, resulting in the metadata assignments being
|
||||||
|
* tree-shaken away during production builds.
|
||||||
|
*/
|
||||||
|
export function setClassMetadata(
|
||||||
|
type: Type<any>, decorators: any[] | null, ctorParameters: any[] | null,
|
||||||
|
propDecorators: {[field: string]: any} | null): void {
|
||||||
|
const clazz = type as TypeWithMetadata;
|
||||||
|
if (decorators !== null) {
|
||||||
|
if (clazz.decorators !== undefined) {
|
||||||
|
clazz.decorators.push(...decorators);
|
||||||
|
} else {
|
||||||
|
clazz.decorators = decorators;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ctorParameters !== null) {
|
||||||
|
// Rather than merging, clobber the existing parameters. If other projects exist which use
|
||||||
|
// tsickle-style annotations and reflect over them in the same way, this could cause issues,
|
||||||
|
// but that is vanishingly unlikely.
|
||||||
|
clazz.ctorParameters = ctorParameters;
|
||||||
|
}
|
||||||
|
if (propDecorators !== null) {
|
||||||
|
// The property decorator objects are merged as it is possible different fields have different
|
||||||
|
// decorator types. Decorators on individual fields are not merged, as it's also incredibly
|
||||||
|
// unlikely that a field will be decorated both with an Angular decorator and a non-Angular
|
||||||
|
// decorator that's also been downleveled.
|
||||||
|
if (clazz.propDecorators !== undefined) {
|
||||||
|
clazz.propDecorators = {...clazz.propDecorators, ...propDecorators};
|
||||||
|
} else {
|
||||||
|
clazz.propDecorators = propDecorators;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
* @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 {setClassMetadata} from '../../src/render3/metadata';
|
||||||
|
import {Type} from '../../src/type';
|
||||||
|
|
||||||
|
interface Decorator {
|
||||||
|
type: any;
|
||||||
|
args?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HasMetadata extends Type<any> {
|
||||||
|
decorators?: Decorator[];
|
||||||
|
ctorParameters: {type: any, decorators?: Decorator[]}[];
|
||||||
|
propDecorators: {[field: string]: Decorator[]};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CtorParameter {
|
||||||
|
type: any;
|
||||||
|
decorators?: Decorator[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function metadataOf(value: Type<any>): HasMetadata {
|
||||||
|
return value as HasMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('render3 setClassMetadata()', () => {
|
||||||
|
it('should set decorator metadata on a type', () => {
|
||||||
|
const Foo = metadataOf(class Foo{});
|
||||||
|
setClassMetadata(Foo, [{type: 'test', args: ['arg']}], null, null);
|
||||||
|
expect(Foo.decorators).toEqual([{type: 'test', args: ['arg']}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge decorator metadata on a type', () => {
|
||||||
|
const Foo = metadataOf(class Foo{});
|
||||||
|
Foo.decorators = [{type: 'first'}];
|
||||||
|
setClassMetadata(Foo, [{type: 'test', args: ['arg']}], null, null);
|
||||||
|
expect(Foo.decorators).toEqual([{type: 'first'}, {type: 'test', args: ['arg']}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set ctor parameter metadata on a type', () => {
|
||||||
|
const Foo = metadataOf(class Foo{});
|
||||||
|
Foo.ctorParameters = [{type: 'initial'}];
|
||||||
|
setClassMetadata(Foo, null, [{type: 'test'}], null);
|
||||||
|
expect(Foo.ctorParameters).toEqual([{type: 'test'}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set parameter decorator metadata on a type', () => {
|
||||||
|
const Foo = metadataOf(class Foo{});
|
||||||
|
setClassMetadata(Foo, null, null, {field: [{type: 'test', args: ['arg']}]});
|
||||||
|
expect(Foo.propDecorators).toEqual({field: [{type: 'test', args: ['arg']}]});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge parameter decorator metadata on a type', () => {
|
||||||
|
const Foo = metadataOf(class Foo{});
|
||||||
|
Foo.propDecorators = {initial: [{type: 'first'}]};
|
||||||
|
setClassMetadata(Foo, null, null, {field: [{type: 'test', args: ['arg']}]});
|
||||||
|
expect(Foo.propDecorators)
|
||||||
|
.toEqual({field: [{type: 'test', args: ['arg']}], initial: [{type: 'first'}]});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue