feat(compiler-cli): lower metadata useValue
and data
literal fields (#18905)
With this commit the compiler will "lower" expressions into exported variables for values the compiler does not need to know statically in order to be able to generate a factory. For example: ``` providers: [{provider: 'token', useValue: calculated()}] ``` produced an error as the expression `calculated()` is not supported by the compiler because `calculated` is not a [known function](https://angular.io/guide/metadata#annotationsdecorators) With this commit this is rewritten, during emit of the .js file, into something like: ``` export var ɵ0 = calculated(); ... provdiers: [{provider: 'token', useValue: ɵ0}] ``` The compiler then will now generate a reference to the exported `ɵ0` instead of failing to evaluate `calculated()`. PR Close #18905
This commit is contained in:

committed by
Jason Aden

parent
c7e1bda32f
commit
0e64261f26
@ -6,29 +6,92 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ModuleMetadata} from '@angular/tsc-wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {LoweringRequest, RequestLocationMap, getExpressionLoweringTransformFactory} from '../../src/transformers/lower_expressions';
|
||||
import {LowerMetadataCache, LoweringRequest, RequestLocationMap, getExpressionLoweringTransformFactory} from '../../src/transformers/lower_expressions';
|
||||
import {Directory, MockAotContext, MockCompilerHost} from '../mocks';
|
||||
|
||||
describe('Expression lowering', () => {
|
||||
it('should be able to lower a simple expression', () => {
|
||||
expect(convert('const a = 1 +◊b: 2◊;')).toBe('const b = 2; const a = 1 + b; export { b };');
|
||||
describe('transform', () => {
|
||||
it('should be able to lower a simple expression', () => {
|
||||
expect(convert('const a = 1 +◊b: 2◊;')).toBe('const b = 2; const a = 1 + b; export { b };');
|
||||
});
|
||||
|
||||
it('should be able to lower an expression in a decorator', () => {
|
||||
expect(convert(`
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
provider: [{provide: 'someToken', useFactory:◊l: () => null◊}]
|
||||
})
|
||||
class MyClass {}
|
||||
`)).toContain('const l = () => null; exports.l = l;');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to lower an expression in a decorator', () => {
|
||||
expect(convert(`
|
||||
describe('collector', () => {
|
||||
it('should request a lowering for useValue', () => {
|
||||
const collected = collect(`
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
|
||||
enum SomeEnum {
|
||||
OK,
|
||||
NotOK
|
||||
}
|
||||
|
||||
@Component({
|
||||
provider: [{provide: 'someToken', useFactory:◊l: () => null◊}]
|
||||
provider: [{provide: 'someToken', useValue:◊enum: SomeEnum.OK◊}]
|
||||
})
|
||||
class MyClass {}
|
||||
`)).toContain('const l = () => null; exports.l = l;');
|
||||
export class MyClass {}
|
||||
`);
|
||||
expect(collected.requests.has(collected.annotations[0].start))
|
||||
.toBeTruthy('did not find the useValue');
|
||||
});
|
||||
|
||||
it('should request a lowering for useFactory', () => {
|
||||
const collected = collect(`
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
provider: [{provide: 'someToken', useFactory:◊lambda: () => null◊}]
|
||||
})
|
||||
export class MyClass {}
|
||||
`);
|
||||
expect(collected.requests.has(collected.annotations[0].start))
|
||||
.toBeTruthy('did not find the useFactory');
|
||||
});
|
||||
|
||||
it('should request a lowering for data', () => {
|
||||
const collected = collect(`
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
enum SomeEnum {
|
||||
OK,
|
||||
NotOK
|
||||
}
|
||||
|
||||
@Component({
|
||||
provider: [{provide: 'someToken', data:◊enum: SomeEnum.OK◊}]
|
||||
})
|
||||
export class MyClass {}
|
||||
`);
|
||||
expect(collected.requests.has(collected.annotations[0].start))
|
||||
.toBeTruthy('did not find the data field');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function convert(annotatedSource: string) {
|
||||
// Helpers
|
||||
|
||||
interface Annotation {
|
||||
start: number;
|
||||
length: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
function getAnnotations(annotatedSource: string):
|
||||
{unannotatedSource: string, annotations: Annotation[]} {
|
||||
const annotations: {start: number, length: number, name: string}[] = [];
|
||||
let adjustment = 0;
|
||||
const unannotatedSource = annotatedSource.replace(
|
||||
@ -38,6 +101,13 @@ function convert(annotatedSource: string) {
|
||||
adjustment -= text.length - source.length;
|
||||
return source;
|
||||
});
|
||||
return {unannotatedSource, annotations};
|
||||
}
|
||||
|
||||
// Transform helpers
|
||||
|
||||
function convert(annotatedSource: string) {
|
||||
const {annotations, unannotatedSource} = getAnnotations(annotatedSource);
|
||||
|
||||
const baseFileName = 'someFile';
|
||||
const moduleName = '/' + baseFileName;
|
||||
@ -103,3 +173,16 @@ function normalizeResult(result: string): string {
|
||||
.replace(/^ /g, '')
|
||||
.replace(/ $/g, '');
|
||||
}
|
||||
|
||||
// Collector helpers
|
||||
|
||||
function collect(annotatedSource: string) {
|
||||
const {annotations, unannotatedSource} = getAnnotations(annotatedSource);
|
||||
const cache = new LowerMetadataCache({});
|
||||
const sourceFile = ts.createSourceFile(
|
||||
'someName.ts', unannotatedSource, ts.ScriptTarget.Latest, /* setParentNodes */ true);
|
||||
return {
|
||||
metadata: cache.getMetadata(sourceFile),
|
||||
requests: cache.getRequests(sourceFile), annotations
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user