feat(ivy): generator of setClassMetadata statements for Angular types (#26860)
This commit introduces generateSetClassMetadataCall(), an API in ngtsc for generating calls to setClassMetadata() for a given declaration. The reflection API is used to enumerate Angular decorators on the declaration, which are converted to a format that ReflectionCapabilities can understand. The reflection metadata is then patched onto the declared type via a call to setClassMetadata(). This is simply a utility, a future commit invokes this utility for each DecoratorHandler. Testing strategy: tests are included which exercise generateSetClassMetadata in isolation. PR Close #26860
This commit is contained in:

committed by
Matias Niemelä

parent
ca1e538752
commit
492576114d
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* @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 {Statement} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {TypeScriptReflectionHost} from '../../metadata';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {ImportManager, translateStatement} from '../../translator';
|
||||
import {generateSetClassMetadataCall} from '../src/metadata';
|
||||
|
||||
const CORE = {
|
||||
name: 'node_modules/@angular/core/index.d.ts',
|
||||
contents: `
|
||||
export declare function Input(...args: any[]): any;
|
||||
export declare function Inject(...args: any[]): any;
|
||||
export declare function Component(...args: any[]): any;
|
||||
export declare class Injector {}
|
||||
`
|
||||
};
|
||||
|
||||
describe('ngtsc setClassMetadata converter', () => {
|
||||
it('should convert decorated class metadata', () => {
|
||||
const res = compileAndPrint(`
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component('metadata') class Target {}
|
||||
`);
|
||||
expect(res).toEqual(
|
||||
`/*@__PURE__*/ i0.ɵsetClassMetadata(Target, [{ type: Component, args: ['metadata'] }], null, null);`);
|
||||
});
|
||||
|
||||
it('should convert decorated class construtor parameter metadata', () => {
|
||||
const res = compileAndPrint(`
|
||||
import {Component, Inject, Injector} from '@angular/core';
|
||||
const FOO = 'foo';
|
||||
|
||||
@Component('metadata') class Target {
|
||||
constructor(@Inject(FOO) foo: any, bar: Injector) {}
|
||||
}
|
||||
`);
|
||||
expect(res).toContain(
|
||||
`[{ type: undefined, decorators: [{ type: Inject, args: [FOO] }] }, { type: Injector }], null);`);
|
||||
});
|
||||
|
||||
it('should convert decorated field metadata', () => {
|
||||
const res = compileAndPrint(`
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component('metadata') class Target {
|
||||
@Input() foo: string;
|
||||
|
||||
@Input('value') bar: string;
|
||||
|
||||
notDecorated: string;
|
||||
}
|
||||
`);
|
||||
expect(res).toContain(`{ foo: [{ type: Input }], bar: [{ type: Input, args: ['value'] }] })`);
|
||||
});
|
||||
|
||||
it('should not convert non-angular decorators to metadata', () => {
|
||||
const res = compileAndPrint(`
|
||||
declare function NotAComponent(...args: any[]): any;
|
||||
|
||||
@NotAComponent('metadata') class Target {}
|
||||
`);
|
||||
expect(res).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
function compileAndPrint(contents: string): string {
|
||||
const {program} = makeProgram([
|
||||
CORE, {
|
||||
name: 'index.ts',
|
||||
contents,
|
||||
}
|
||||
]);
|
||||
const host = new TypeScriptReflectionHost(program.getTypeChecker());
|
||||
const target = getDeclaration(program, 'index.ts', 'Target', ts.isClassDeclaration);
|
||||
const call = generateSetClassMetadataCall(target, host, false);
|
||||
if (call === null) {
|
||||
return '';
|
||||
}
|
||||
const sf = program.getSourceFile('index.ts') !;
|
||||
const im = new ImportManager(false, 'i');
|
||||
const tsStatement = translateStatement(call, im);
|
||||
const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tsStatement, sf);
|
||||
return res.replace(/\s+/g, ' ');
|
||||
}
|
Reference in New Issue
Block a user