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:
Chuck Jazdzewski
2017-08-23 10:22:17 -07:00
committed by Jason Aden
parent 2f2d5f35bd
commit c685cc2f0a
8 changed files with 324 additions and 28 deletions

View File

@ -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
};
}