refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921)
To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
This commit is contained in:

committed by
Kara Erickson

parent
1e7e065423
commit
7186f9c016
@ -11,11 +11,12 @@ ts_library(
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/imports",
|
||||
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
@ -5,293 +5,300 @@
|
||||
* 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 * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom, getSourceFileOrError} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {Reference} from '../../imports';
|
||||
import {FunctionDefinition, TsHelperFn, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
import {DynamicValue} from '../src/dynamic';
|
||||
import {PartialEvaluator} from '../src/interface';
|
||||
import {EnumValue} from '../src/result';
|
||||
|
||||
import {evaluate, firstArgFfr, makeEvaluator, makeExpression, owningModuleOf} from './utils';
|
||||
|
||||
describe('ngtsc metadata', () => {
|
||||
it('reads a file correctly', () => {
|
||||
const value = evaluate(
|
||||
`
|
||||
runInEachFileSystem(() => {
|
||||
describe('ngtsc metadata', () => {
|
||||
let _: typeof absoluteFrom;
|
||||
beforeEach(() => _ = absoluteFrom);
|
||||
|
||||
it('reads a file correctly', () => {
|
||||
const value = evaluate(
|
||||
`
|
||||
import {Y} from './other';
|
||||
const A = Y;
|
||||
`,
|
||||
'A', [
|
||||
{
|
||||
name: 'other.ts',
|
||||
contents: `
|
||||
'A', [
|
||||
{
|
||||
name: _('/other.ts'),
|
||||
contents: `
|
||||
export const Y = 'test';
|
||||
`
|
||||
},
|
||||
]);
|
||||
},
|
||||
]);
|
||||
|
||||
expect(value).toEqual('test');
|
||||
});
|
||||
expect(value).toEqual('test');
|
||||
});
|
||||
|
||||
it('map access works',
|
||||
() => { expect(evaluate('const obj = {a: "test"};', 'obj.a')).toEqual('test'); });
|
||||
it('map access works',
|
||||
() => { expect(evaluate('const obj = {a: "test"};', 'obj.a')).toEqual('test'); });
|
||||
|
||||
it('resolves undefined property access',
|
||||
() => { expect(evaluate('const obj: any = {}', 'obj.bar')).toEqual(undefined); });
|
||||
it('resolves undefined property access',
|
||||
() => { expect(evaluate('const obj: any = {}', 'obj.bar')).toEqual(undefined); });
|
||||
|
||||
it('function calls work', () => {
|
||||
expect(evaluate(`function foo(bar) { return bar; }`, 'foo("test")')).toEqual('test');
|
||||
});
|
||||
it('function calls work', () => {
|
||||
expect(evaluate(`function foo(bar) { return bar; }`, 'foo("test")')).toEqual('test');
|
||||
});
|
||||
|
||||
it('function call default value works', () => {
|
||||
expect(evaluate(`function foo(bar = 1) { return bar; }`, 'foo()')).toEqual(1);
|
||||
expect(evaluate(`function foo(bar = 1) { return bar; }`, 'foo(2)')).toEqual(2);
|
||||
expect(evaluate(`function foo(a, c = a) { return c; }; const a = 1;`, 'foo(2)')).toEqual(2);
|
||||
});
|
||||
it('function call default value works', () => {
|
||||
expect(evaluate(`function foo(bar = 1) { return bar; }`, 'foo()')).toEqual(1);
|
||||
expect(evaluate(`function foo(bar = 1) { return bar; }`, 'foo(2)')).toEqual(2);
|
||||
expect(evaluate(`function foo(a, c = a) { return c; }; const a = 1;`, 'foo(2)')).toEqual(2);
|
||||
});
|
||||
|
||||
it('function call spread works', () => {
|
||||
expect(evaluate(`function foo(a, ...b) { return [a, b]; }`, 'foo(1, ...[2, 3])')).toEqual([
|
||||
1, [2, 3]
|
||||
]);
|
||||
});
|
||||
it('function call spread works', () => {
|
||||
expect(evaluate(`function foo(a, ...b) { return [a, b]; }`, 'foo(1, ...[2, 3])')).toEqual([
|
||||
1, [2, 3]
|
||||
]);
|
||||
});
|
||||
|
||||
it('conditionals work', () => {
|
||||
expect(evaluate(`const x = false; const y = x ? 'true' : 'false';`, 'y')).toEqual('false');
|
||||
});
|
||||
it('conditionals work', () => {
|
||||
expect(evaluate(`const x = false; const y = x ? 'true' : 'false';`, 'y')).toEqual('false');
|
||||
});
|
||||
|
||||
it('addition works', () => { expect(evaluate(`const x = 1 + 2;`, 'x')).toEqual(3); });
|
||||
it('addition works', () => { expect(evaluate(`const x = 1 + 2;`, 'x')).toEqual(3); });
|
||||
|
||||
it('static property on class works',
|
||||
() => { expect(evaluate(`class Foo { static bar = 'test'; }`, 'Foo.bar')).toEqual('test'); });
|
||||
it('static property on class works', () => {
|
||||
expect(evaluate(`class Foo { static bar = 'test'; }`, 'Foo.bar')).toEqual('test');
|
||||
});
|
||||
|
||||
it('static property call works', () => {
|
||||
expect(evaluate(`class Foo { static bar(test) { return test; } }`, 'Foo.bar("test")'))
|
||||
.toEqual('test');
|
||||
});
|
||||
it('static property call works', () => {
|
||||
expect(evaluate(`class Foo { static bar(test) { return test; } }`, 'Foo.bar("test")'))
|
||||
.toEqual('test');
|
||||
});
|
||||
|
||||
it('indirected static property call works', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
`class Foo { static bar(test) { return test; } }; const fn = Foo.bar;`, 'fn("test")'))
|
||||
.toEqual('test');
|
||||
});
|
||||
it('indirected static property call works', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
`class Foo { static bar(test) { return test; } }; const fn = Foo.bar;`, 'fn("test")'))
|
||||
.toEqual('test');
|
||||
});
|
||||
|
||||
it('array works', () => {
|
||||
expect(evaluate(`const x = 'test'; const y = [1, x, 2];`, 'y')).toEqual([1, 'test', 2]);
|
||||
});
|
||||
it('array works', () => {
|
||||
expect(evaluate(`const x = 'test'; const y = [1, x, 2];`, 'y')).toEqual([1, 'test', 2]);
|
||||
});
|
||||
|
||||
it('array spread works', () => {
|
||||
expect(evaluate(`const a = [1, 2]; const b = [4, 5]; const c = [...a, 3, ...b];`, 'c'))
|
||||
.toEqual([1, 2, 3, 4, 5]);
|
||||
});
|
||||
it('array spread works', () => {
|
||||
expect(evaluate(`const a = [1, 2]; const b = [4, 5]; const c = [...a, 3, ...b];`, 'c'))
|
||||
.toEqual([1, 2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it('&& operations work', () => {
|
||||
expect(evaluate(`const a = 'hello', b = 'world';`, 'a && b')).toEqual('world');
|
||||
expect(evaluate(`const a = false, b = 'world';`, 'a && b')).toEqual(false);
|
||||
expect(evaluate(`const a = 'hello', b = 0;`, 'a && b')).toEqual(0);
|
||||
});
|
||||
it('&& operations work', () => {
|
||||
expect(evaluate(`const a = 'hello', b = 'world';`, 'a && b')).toEqual('world');
|
||||
expect(evaluate(`const a = false, b = 'world';`, 'a && b')).toEqual(false);
|
||||
expect(evaluate(`const a = 'hello', b = 0;`, 'a && b')).toEqual(0);
|
||||
});
|
||||
|
||||
it('|| operations work', () => {
|
||||
expect(evaluate(`const a = 'hello', b = 'world';`, 'a || b')).toEqual('hello');
|
||||
expect(evaluate(`const a = false, b = 'world';`, 'a || b')).toEqual('world');
|
||||
expect(evaluate(`const a = 'hello', b = 0;`, 'a || b')).toEqual('hello');
|
||||
});
|
||||
it('|| operations work', () => {
|
||||
expect(evaluate(`const a = 'hello', b = 'world';`, 'a || b')).toEqual('hello');
|
||||
expect(evaluate(`const a = false, b = 'world';`, 'a || b')).toEqual('world');
|
||||
expect(evaluate(`const a = 'hello', b = 0;`, 'a || b')).toEqual('hello');
|
||||
});
|
||||
|
||||
it('evaluates arithmetic operators', () => {
|
||||
expect(evaluate('const a = 6, b = 3;', 'a + b')).toEqual(9);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a - b')).toEqual(3);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a * b')).toEqual(18);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a / b')).toEqual(2);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a % b')).toEqual(0);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a & b')).toEqual(2);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a | b')).toEqual(7);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a ^ b')).toEqual(5);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a ** b')).toEqual(216);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a << b')).toEqual(48);
|
||||
expect(evaluate('const a = -6, b = 2;', 'a >> b')).toEqual(-2);
|
||||
expect(evaluate('const a = -6, b = 2;', 'a >>> b')).toEqual(1073741822);
|
||||
});
|
||||
it('evaluates arithmetic operators', () => {
|
||||
expect(evaluate('const a = 6, b = 3;', 'a + b')).toEqual(9);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a - b')).toEqual(3);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a * b')).toEqual(18);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a / b')).toEqual(2);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a % b')).toEqual(0);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a & b')).toEqual(2);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a | b')).toEqual(7);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a ^ b')).toEqual(5);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a ** b')).toEqual(216);
|
||||
expect(evaluate('const a = 6, b = 3;', 'a << b')).toEqual(48);
|
||||
expect(evaluate('const a = -6, b = 2;', 'a >> b')).toEqual(-2);
|
||||
expect(evaluate('const a = -6, b = 2;', 'a >>> b')).toEqual(1073741822);
|
||||
});
|
||||
|
||||
it('evaluates comparison operators', () => {
|
||||
expect(evaluate('const a = 2, b = 3;', 'a < b')).toEqual(true);
|
||||
expect(evaluate('const a = 3, b = 3;', 'a < b')).toEqual(false);
|
||||
it('evaluates comparison operators', () => {
|
||||
expect(evaluate('const a = 2, b = 3;', 'a < b')).toEqual(true);
|
||||
expect(evaluate('const a = 3, b = 3;', 'a < b')).toEqual(false);
|
||||
|
||||
expect(evaluate('const a = 3, b = 3;', 'a <= b')).toEqual(true);
|
||||
expect(evaluate('const a = 4, b = 3;', 'a <= b')).toEqual(false);
|
||||
expect(evaluate('const a = 3, b = 3;', 'a <= b')).toEqual(true);
|
||||
expect(evaluate('const a = 4, b = 3;', 'a <= b')).toEqual(false);
|
||||
|
||||
expect(evaluate('const a = 4, b = 3;', 'a > b')).toEqual(true);
|
||||
expect(evaluate('const a = 3, b = 3;', 'a > b')).toEqual(false);
|
||||
expect(evaluate('const a = 4, b = 3;', 'a > b')).toEqual(true);
|
||||
expect(evaluate('const a = 3, b = 3;', 'a > b')).toEqual(false);
|
||||
|
||||
expect(evaluate('const a = 3, b = 3;', 'a >= b')).toEqual(true);
|
||||
expect(evaluate('const a = 2, b = 3;', 'a >= b')).toEqual(false);
|
||||
expect(evaluate('const a = 3, b = 3;', 'a >= b')).toEqual(true);
|
||||
expect(evaluate('const a = 2, b = 3;', 'a >= b')).toEqual(false);
|
||||
|
||||
expect(evaluate('const a: any = 3, b = "3";', 'a == b')).toEqual(true);
|
||||
expect(evaluate('const a: any = 2, b = "3";', 'a == b')).toEqual(false);
|
||||
expect(evaluate('const a: any = 3, b = "3";', 'a == b')).toEqual(true);
|
||||
expect(evaluate('const a: any = 2, b = "3";', 'a == b')).toEqual(false);
|
||||
|
||||
expect(evaluate('const a: any = 2, b = "3";', 'a != b')).toEqual(true);
|
||||
expect(evaluate('const a: any = 3, b = "3";', 'a != b')).toEqual(false);
|
||||
expect(evaluate('const a: any = 2, b = "3";', 'a != b')).toEqual(true);
|
||||
expect(evaluate('const a: any = 3, b = "3";', 'a != b')).toEqual(false);
|
||||
|
||||
expect(evaluate('const a: any = 3, b = 3;', 'a === b')).toEqual(true);
|
||||
expect(evaluate('const a: any = 3, b = "3";', 'a === b')).toEqual(false);
|
||||
expect(evaluate('const a: any = 3, b = 3;', 'a === b')).toEqual(true);
|
||||
expect(evaluate('const a: any = 3, b = "3";', 'a === b')).toEqual(false);
|
||||
|
||||
expect(evaluate('const a: any = 3, b = "3";', 'a !== b')).toEqual(true);
|
||||
expect(evaluate('const a: any = 3, b = 3;', 'a !== b')).toEqual(false);
|
||||
});
|
||||
expect(evaluate('const a: any = 3, b = "3";', 'a !== b')).toEqual(true);
|
||||
expect(evaluate('const a: any = 3, b = 3;', 'a !== b')).toEqual(false);
|
||||
});
|
||||
|
||||
it('parentheticals work',
|
||||
() => { expect(evaluate(`const a = 3, b = 4;`, 'a * (a + b)')).toEqual(21); });
|
||||
it('parentheticals work',
|
||||
() => { expect(evaluate(`const a = 3, b = 4;`, 'a * (a + b)')).toEqual(21); });
|
||||
|
||||
it('array access works',
|
||||
() => { expect(evaluate(`const a = [1, 2, 3];`, 'a[1] + a[0]')).toEqual(3); });
|
||||
it('array access works',
|
||||
() => { expect(evaluate(`const a = [1, 2, 3];`, 'a[1] + a[0]')).toEqual(3); });
|
||||
|
||||
it('array `length` property access works',
|
||||
() => { expect(evaluate(`const a = [1, 2, 3];`, 'a[\'length\'] + 1')).toEqual(4); });
|
||||
it('array `length` property access works',
|
||||
() => { expect(evaluate(`const a = [1, 2, 3];`, 'a[\'length\'] + 1')).toEqual(4); });
|
||||
|
||||
it('array `slice` function works', () => {
|
||||
expect(evaluate(`const a = [1, 2, 3];`, 'a[\'slice\']()')).toEqual([1, 2, 3]);
|
||||
});
|
||||
it('array `slice` function works', () => {
|
||||
expect(evaluate(`const a = [1, 2, 3];`, 'a[\'slice\']()')).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('array `concat` function works', () => {
|
||||
expect(evaluate(`const a = [1, 2], b = [3, 4];`, 'a[\'concat\'](b)')).toEqual([1, 2, 3, 4]);
|
||||
expect(evaluate(`const a = [1, 2], b = 3;`, 'a[\'concat\'](b)')).toEqual([1, 2, 3]);
|
||||
expect(evaluate(`const a = [1, 2], b = 3, c = [4, 5];`, 'a[\'concat\'](b, c)')).toEqual([
|
||||
1, 2, 3, 4, 5
|
||||
]);
|
||||
expect(evaluate(`const a = [1, 2], b = [3, 4]`, 'a[\'concat\'](...b)')).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
it('array `concat` function works', () => {
|
||||
expect(evaluate(`const a = [1, 2], b = [3, 4];`, 'a[\'concat\'](b)')).toEqual([1, 2, 3, 4]);
|
||||
expect(evaluate(`const a = [1, 2], b = 3;`, 'a[\'concat\'](b)')).toEqual([1, 2, 3]);
|
||||
expect(evaluate(`const a = [1, 2], b = 3, c = [4, 5];`, 'a[\'concat\'](b, c)')).toEqual([
|
||||
1, 2, 3, 4, 5
|
||||
]);
|
||||
expect(evaluate(`const a = [1, 2], b = [3, 4]`, 'a[\'concat\'](...b)')).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
it('negation works', () => {
|
||||
expect(evaluate(`const x = 3;`, '!x')).toEqual(false);
|
||||
expect(evaluate(`const x = 3;`, '!!x')).toEqual(true);
|
||||
});
|
||||
it('negation works', () => {
|
||||
expect(evaluate(`const x = 3;`, '!x')).toEqual(false);
|
||||
expect(evaluate(`const x = 3;`, '!!x')).toEqual(true);
|
||||
});
|
||||
|
||||
it('resolves access from external variable declarations as dynamic value', () => {
|
||||
const value = evaluate('declare const window: any;', 'window.location');
|
||||
if (!(value instanceof DynamicValue)) {
|
||||
return fail(`Should have resolved to a DynamicValue`);
|
||||
}
|
||||
expect(value.isFromDynamicInput()).toEqual(true);
|
||||
expect(value.node.getText()).toEqual('window.location');
|
||||
if (!(value.reason instanceof DynamicValue)) {
|
||||
return fail(`Should have a DynamicValue as reason`);
|
||||
}
|
||||
expect(value.reason.isFromExternalReference()).toEqual(true);
|
||||
expect(value.reason.node.getText()).toEqual('window: any');
|
||||
});
|
||||
it('resolves access from external variable declarations as dynamic value', () => {
|
||||
const value = evaluate('declare const window: any;', 'window.location');
|
||||
if (!(value instanceof DynamicValue)) {
|
||||
return fail(`Should have resolved to a DynamicValue`);
|
||||
}
|
||||
expect(value.isFromDynamicInput()).toEqual(true);
|
||||
expect(value.node.getText()).toEqual('window.location');
|
||||
if (!(value.reason instanceof DynamicValue)) {
|
||||
return fail(`Should have a DynamicValue as reason`);
|
||||
}
|
||||
expect(value.reason.isFromExternalReference()).toEqual(true);
|
||||
expect(value.reason.node.getText()).toEqual('window: any');
|
||||
});
|
||||
|
||||
it('imports work', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: 'second.ts', contents: 'export function foo(bar) { return bar; }'},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
it('imports work', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: _('/second.ts'), contents: 'export function foo(bar) { return bar; }'},
|
||||
{
|
||||
name: _('/entry.ts'),
|
||||
contents: `
|
||||
import {foo} from './second';
|
||||
const target$ = foo;
|
||||
`
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !;
|
||||
const evaluator = makeEvaluator(checker);
|
||||
const resolved = evaluator.evaluate(expr);
|
||||
if (!(resolved instanceof Reference)) {
|
||||
return fail('Expected expression to resolve to a reference');
|
||||
}
|
||||
expect(ts.isFunctionDeclaration(resolved.node)).toBe(true);
|
||||
const reference = resolved.getIdentityIn(program.getSourceFile('entry.ts') !);
|
||||
if (reference === null) {
|
||||
return fail('Expected to get an identifier');
|
||||
}
|
||||
expect(reference.getSourceFile()).toEqual(program.getSourceFile('entry.ts') !);
|
||||
});
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, _('/entry.ts'), 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !;
|
||||
const evaluator = makeEvaluator(checker);
|
||||
const resolved = evaluator.evaluate(expr);
|
||||
if (!(resolved instanceof Reference)) {
|
||||
return fail('Expected expression to resolve to a reference');
|
||||
}
|
||||
expect(ts.isFunctionDeclaration(resolved.node)).toBe(true);
|
||||
const reference = resolved.getIdentityIn(getSourceFileOrError(program, _('/entry.ts')));
|
||||
if (reference === null) {
|
||||
return fail('Expected to get an identifier');
|
||||
}
|
||||
expect(reference.getSourceFile()).toEqual(getSourceFileOrError(program, _('/entry.ts')));
|
||||
});
|
||||
|
||||
it('absolute imports work', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: 'node_modules/some_library/index.d.ts', contents: 'export declare function foo(bar);'},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
it('absolute imports work', () => {
|
||||
const {program} = makeProgram([
|
||||
{
|
||||
name: _('/node_modules/some_library/index.d.ts'),
|
||||
contents: 'export declare function foo(bar);'
|
||||
},
|
||||
{
|
||||
name: _('/entry.ts'),
|
||||
contents: `
|
||||
import {foo} from 'some_library';
|
||||
const target$ = foo;
|
||||
`
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !;
|
||||
const evaluator = makeEvaluator(checker);
|
||||
const resolved = evaluator.evaluate(expr);
|
||||
if (!(resolved instanceof Reference)) {
|
||||
return fail('Expected expression to resolve to an absolute reference');
|
||||
}
|
||||
expect(owningModuleOf(resolved)).toBe('some_library');
|
||||
expect(ts.isFunctionDeclaration(resolved.node)).toBe(true);
|
||||
const reference = resolved.getIdentityIn(program.getSourceFile('entry.ts') !);
|
||||
expect(reference).not.toBeNull();
|
||||
expect(reference !.getSourceFile()).toEqual(program.getSourceFile('entry.ts') !);
|
||||
});
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, _('/entry.ts'), 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !;
|
||||
const evaluator = makeEvaluator(checker);
|
||||
const resolved = evaluator.evaluate(expr);
|
||||
if (!(resolved instanceof Reference)) {
|
||||
return fail('Expected expression to resolve to an absolute reference');
|
||||
}
|
||||
expect(owningModuleOf(resolved)).toBe('some_library');
|
||||
expect(ts.isFunctionDeclaration(resolved.node)).toBe(true);
|
||||
const reference = resolved.getIdentityIn(getSourceFileOrError(program, _('/entry.ts')));
|
||||
expect(reference).not.toBeNull();
|
||||
expect(reference !.getSourceFile()).toEqual(getSourceFileOrError(program, _('/entry.ts')));
|
||||
});
|
||||
|
||||
it('reads values from default exports', () => {
|
||||
const value = evaluate(
|
||||
`
|
||||
it('reads values from default exports', () => {
|
||||
const value = evaluate(
|
||||
`
|
||||
import mod from './second';
|
||||
`,
|
||||
'mod.property', [
|
||||
{name: 'second.ts', contents: 'export default {property: "test"}'},
|
||||
]);
|
||||
expect(value).toEqual('test');
|
||||
});
|
||||
|
||||
it('reads values from named exports', () => {
|
||||
const value = evaluate(`import * as mod from './second';`, 'mod.a.property', [
|
||||
{name: 'second.ts', contents: 'export const a = {property: "test"};'},
|
||||
]);
|
||||
expect(value).toEqual('test');
|
||||
});
|
||||
|
||||
it('chain of re-exports works', () => {
|
||||
const value = evaluate(`import * as mod from './direct-reexport';`, 'mod.value.property', [
|
||||
{name: 'const.ts', contents: 'export const value = {property: "test"};'},
|
||||
{name: 'def.ts', contents: `import {value} from './const'; export default value;`},
|
||||
{name: 'indirect-reexport.ts', contents: `import value from './def'; export {value};`},
|
||||
{name: 'direct-reexport.ts', contents: `export {value} from './indirect-reexport';`},
|
||||
]);
|
||||
expect(value).toEqual('test');
|
||||
});
|
||||
|
||||
it('map spread works', () => {
|
||||
const map: Map<string, number> = evaluate<Map<string, number>>(
|
||||
`const a = {a: 1}; const b = {b: 2, c: 1}; const c = {...a, ...b, c: 3};`, 'c');
|
||||
|
||||
const obj: {[key: string]: number} = {};
|
||||
map.forEach((value, key) => obj[key] = value);
|
||||
expect(obj).toEqual({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
'mod.property', [
|
||||
{name: _('/second.ts'), contents: 'export default {property: "test"}'},
|
||||
]);
|
||||
expect(value).toEqual('test');
|
||||
});
|
||||
});
|
||||
|
||||
it('indirected-via-object function call works', () => {
|
||||
expect(evaluate(
|
||||
`
|
||||
it('reads values from named exports', () => {
|
||||
const value = evaluate(`import * as mod from './second';`, 'mod.a.property', [
|
||||
{name: _('/second.ts'), contents: 'export const a = {property: "test"};'},
|
||||
]);
|
||||
expect(value).toEqual('test');
|
||||
});
|
||||
|
||||
it('chain of re-exports works', () => {
|
||||
const value = evaluate(`import * as mod from './direct-reexport';`, 'mod.value.property', [
|
||||
{name: _('/const.ts'), contents: 'export const value = {property: "test"};'},
|
||||
{name: _('/def.ts'), contents: `import {value} from './const'; export default value;`},
|
||||
{name: _('/indirect-reexport.ts'), contents: `import value from './def'; export {value};`},
|
||||
{name: _('/direct-reexport.ts'), contents: `export {value} from './indirect-reexport';`},
|
||||
]);
|
||||
expect(value).toEqual('test');
|
||||
});
|
||||
|
||||
it('map spread works', () => {
|
||||
const map: Map<string, number> = evaluate<Map<string, number>>(
|
||||
`const a = {a: 1}; const b = {b: 2, c: 1}; const c = {...a, ...b, c: 3};`, 'c');
|
||||
|
||||
const obj: {[key: string]: number} = {};
|
||||
map.forEach((value, key) => obj[key] = value);
|
||||
expect(obj).toEqual({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
});
|
||||
});
|
||||
|
||||
it('indirected-via-object function call works', () => {
|
||||
expect(evaluate(
|
||||
`
|
||||
function fn(res) { return res; }
|
||||
const obj = {fn};
|
||||
`,
|
||||
'obj.fn("test")'))
|
||||
.toEqual('test');
|
||||
});
|
||||
'obj.fn("test")'))
|
||||
.toEqual('test');
|
||||
});
|
||||
|
||||
it('template expressions work',
|
||||
() => { expect(evaluate('const a = 2, b = 4;', '`1${a}3${b}5`')).toEqual('12345'); });
|
||||
it('template expressions work',
|
||||
() => { expect(evaluate('const a = 2, b = 4;', '`1${a}3${b}5`')).toEqual('12345'); });
|
||||
|
||||
it('enum resolution works', () => {
|
||||
const result = evaluate(
|
||||
`
|
||||
it('enum resolution works', () => {
|
||||
const result = evaluate(
|
||||
`
|
||||
enum Foo {
|
||||
A,
|
||||
B,
|
||||
@ -300,194 +307,209 @@ describe('ngtsc metadata', () => {
|
||||
|
||||
const r = Foo.B;
|
||||
`,
|
||||
'r');
|
||||
if (!(result instanceof EnumValue)) {
|
||||
return fail(`result is not an EnumValue`);
|
||||
}
|
||||
expect(result.enumRef.node.name.text).toBe('Foo');
|
||||
expect(result.name).toBe('B');
|
||||
});
|
||||
'r');
|
||||
if (!(result instanceof EnumValue)) {
|
||||
return fail(`result is not an EnumValue`);
|
||||
}
|
||||
expect(result.enumRef.node.name.text).toBe('Foo');
|
||||
expect(result.name).toBe('B');
|
||||
});
|
||||
|
||||
it('variable declaration resolution works', () => {
|
||||
const value = evaluate(`import {value} from './decl';`, 'value', [
|
||||
{name: 'decl.d.ts', contents: 'export declare let value: number;'},
|
||||
]);
|
||||
expect(value instanceof Reference).toBe(true);
|
||||
});
|
||||
it('variable declaration resolution works', () => {
|
||||
const value = evaluate(`import {value} from './decl';`, 'value', [
|
||||
{name: _('/decl.d.ts'), contents: 'export declare let value: number;'},
|
||||
]);
|
||||
expect(value instanceof Reference).toBe(true);
|
||||
});
|
||||
|
||||
it('should resolve shorthand properties to values', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: 'entry.ts', contents: `const prop = 42; const target$ = {prop};`},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !as ts.ObjectLiteralExpression;
|
||||
const prop = expr.properties[0] as ts.ShorthandPropertyAssignment;
|
||||
const evaluator = makeEvaluator(checker);
|
||||
const resolved = evaluator.evaluate(prop.name);
|
||||
expect(resolved).toBe(42);
|
||||
});
|
||||
it('should resolve shorthand properties to values', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: _('/entry.ts'), contents: `const prop = 42; const target$ = {prop};`},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, _('/entry.ts'), 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !as ts.ObjectLiteralExpression;
|
||||
const prop = expr.properties[0] as ts.ShorthandPropertyAssignment;
|
||||
const evaluator = makeEvaluator(checker);
|
||||
const resolved = evaluator.evaluate(prop.name);
|
||||
expect(resolved).toBe(42);
|
||||
});
|
||||
|
||||
it('should resolve dynamic values in object literals', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: 'decl.d.ts', contents: 'export declare const fn: any;'},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `import {fn} from './decl'; const prop = fn.foo(); const target$ = {value: prop};`
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !as ts.ObjectLiteralExpression;
|
||||
const evaluator = makeEvaluator(checker);
|
||||
const resolved = evaluator.evaluate(expr);
|
||||
if (!(resolved instanceof Map)) {
|
||||
return fail('Should have resolved to a Map');
|
||||
}
|
||||
const value = resolved.get('value') !;
|
||||
if (!(value instanceof DynamicValue)) {
|
||||
return fail(`Should have resolved 'value' to a DynamicValue`);
|
||||
}
|
||||
const prop = expr.properties[0] as ts.PropertyAssignment;
|
||||
expect(value.node).toBe(prop.initializer);
|
||||
});
|
||||
it('should resolve dynamic values in object literals', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: _('/decl.d.ts'), contents: 'export declare const fn: any;'},
|
||||
{
|
||||
name: _('/entry.ts'),
|
||||
contents:
|
||||
`import {fn} from './decl'; const prop = fn.foo(); const target$ = {value: prop};`
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, _('/entry.ts'), 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !as ts.ObjectLiteralExpression;
|
||||
const evaluator = makeEvaluator(checker);
|
||||
const resolved = evaluator.evaluate(expr);
|
||||
if (!(resolved instanceof Map)) {
|
||||
return fail('Should have resolved to a Map');
|
||||
}
|
||||
const value = resolved.get('value') !;
|
||||
if (!(value instanceof DynamicValue)) {
|
||||
return fail(`Should have resolved 'value' to a DynamicValue`);
|
||||
}
|
||||
const prop = expr.properties[0] as ts.PropertyAssignment;
|
||||
expect(value.node).toBe(prop.initializer);
|
||||
});
|
||||
|
||||
it('should resolve enums in template expressions', () => {
|
||||
const value =
|
||||
evaluate(`enum Test { VALUE = 'test', } const value = \`a.\${Test.VALUE}.b\`;`, 'value');
|
||||
expect(value).toBe('a.test.b');
|
||||
});
|
||||
it('should resolve enums in template expressions', () => {
|
||||
const value =
|
||||
evaluate(`enum Test { VALUE = 'test', } const value = \`a.\${Test.VALUE}.b\`;`, 'value');
|
||||
expect(value).toBe('a.test.b');
|
||||
});
|
||||
|
||||
it('should not attach identifiers to FFR-resolved values', () => {
|
||||
const value = evaluate(
|
||||
`
|
||||
it('should not attach identifiers to FFR-resolved values', () => {
|
||||
const value = evaluate(
|
||||
`
|
||||
declare function foo(arg: any): any;
|
||||
class Target {}
|
||||
|
||||
const indir = foo(Target);
|
||||
const value = indir;
|
||||
`,
|
||||
'value', [], firstArgFfr);
|
||||
if (!(value instanceof Reference)) {
|
||||
return fail('Expected value to be a Reference');
|
||||
}
|
||||
const id = value.getIdentityIn(value.node.getSourceFile());
|
||||
if (id === null) {
|
||||
return fail('Expected value to have an identity');
|
||||
}
|
||||
expect(id.text).toEqual('Target');
|
||||
});
|
||||
'value', [], firstArgFfr);
|
||||
if (!(value instanceof Reference)) {
|
||||
return fail('Expected value to be a Reference');
|
||||
}
|
||||
const id = value.getIdentityIn(value.node.getSourceFile());
|
||||
if (id === null) {
|
||||
return fail('Expected value to have an identity');
|
||||
}
|
||||
expect(id.text).toEqual('Target');
|
||||
});
|
||||
|
||||
it('should resolve functions with more than one statement to an unknown value', () => {
|
||||
const value = evaluate(`function foo(bar) { const b = bar; return b; }`, 'foo("test")');
|
||||
it('should resolve functions with more than one statement to an unknown value', () => {
|
||||
const value = evaluate(`function foo(bar) { const b = bar; return b; }`, 'foo("test")');
|
||||
|
||||
if (!(value instanceof DynamicValue)) {
|
||||
return fail(`Should have resolved to a DynamicValue`);
|
||||
}
|
||||
if (!(value instanceof DynamicValue)) {
|
||||
return fail(`Should have resolved to a DynamicValue`);
|
||||
}
|
||||
|
||||
expect(value.isFromUnknown()).toBe(true);
|
||||
expect((value.node as ts.CallExpression).expression.getText()).toBe('foo');
|
||||
});
|
||||
expect(value.isFromUnknown()).toBe(true);
|
||||
expect((value.node as ts.CallExpression).expression.getText()).toBe('foo');
|
||||
});
|
||||
|
||||
it('should evaluate TypeScript __spread helper', () => {
|
||||
const {checker, expression} = makeExpression(
|
||||
`
|
||||
it('should evaluate TypeScript __spread helper', () => {
|
||||
const {checker, expression} = makeExpression(
|
||||
`
|
||||
import * as tslib from 'tslib';
|
||||
const a = [1];
|
||||
const b = [2, 3];
|
||||
`,
|
||||
'tslib.__spread(a, b)', [
|
||||
{
|
||||
name: 'node_modules/tslib/index.d.ts',
|
||||
contents: `
|
||||
'tslib.__spread(a, b)', [
|
||||
{
|
||||
name: _('/node_modules/tslib/index.d.ts'),
|
||||
contents: `
|
||||
export declare function __spread(...args: any[]): any[];
|
||||
`
|
||||
},
|
||||
]);
|
||||
const reflectionHost = new TsLibAwareReflectionHost(checker);
|
||||
const evaluator = new PartialEvaluator(reflectionHost, checker);
|
||||
const value = evaluator.evaluate(expression);
|
||||
expect(value).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
describe('(visited file tracking)', () => {
|
||||
it('should track each time a source file is visited', () => {
|
||||
const trackFileDependency = jasmine.createSpy('DependencyTracker');
|
||||
const {expression, checker} =
|
||||
makeExpression(`class A { static foo = 42; } function bar() { return A.foo; }`, 'bar()');
|
||||
const evaluator = makeEvaluator(checker, {trackFileDependency});
|
||||
evaluator.evaluate(expression);
|
||||
expect(trackFileDependency).toHaveBeenCalledTimes(2); // two declaration visited
|
||||
expect(trackFileDependency.calls.allArgs().map(args => [args[0].fileName, args[1].fileName]))
|
||||
.toEqual([['/entry.ts', '/entry.ts'], ['/entry.ts', '/entry.ts']]);
|
||||
},
|
||||
]);
|
||||
const reflectionHost = new TsLibAwareReflectionHost(checker);
|
||||
const evaluator = new PartialEvaluator(reflectionHost, checker);
|
||||
const value = evaluator.evaluate(expression);
|
||||
expect(value).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('should track imported source files', () => {
|
||||
const trackFileDependency = jasmine.createSpy('DependencyTracker');
|
||||
const {expression, checker} = makeExpression(`import {Y} from './other'; const A = Y;`, 'A', [
|
||||
{name: 'other.ts', contents: `export const Y = 'test';`},
|
||||
{name: 'not-visited.ts', contents: `export const Z = 'nope';`}
|
||||
]);
|
||||
const evaluator = makeEvaluator(checker, {trackFileDependency});
|
||||
evaluator.evaluate(expression);
|
||||
expect(trackFileDependency).toHaveBeenCalledTimes(2);
|
||||
expect(trackFileDependency.calls.allArgs().map(args => [args[0].fileName, args[1].fileName]))
|
||||
.toEqual([
|
||||
['/entry.ts', '/entry.ts'],
|
||||
['/other.ts', '/entry.ts'],
|
||||
]);
|
||||
});
|
||||
describe('(visited file tracking)', () => {
|
||||
it('should track each time a source file is visited', () => {
|
||||
const trackFileDependency = jasmine.createSpy('DependencyTracker');
|
||||
const {expression, checker} = makeExpression(
|
||||
`class A { static foo = 42; } function bar() { return A.foo; }`, 'bar()');
|
||||
const evaluator = makeEvaluator(checker, {trackFileDependency});
|
||||
evaluator.evaluate(expression);
|
||||
expect(trackFileDependency).toHaveBeenCalledTimes(2); // two declaration visited
|
||||
expect(
|
||||
trackFileDependency.calls.allArgs().map(args => [args[0].fileName, args[1].fileName]))
|
||||
.toEqual([[_('/entry.ts'), _('/entry.ts')], [_('/entry.ts'), _('/entry.ts')]]);
|
||||
});
|
||||
|
||||
it('should track files passed through during re-exports', () => {
|
||||
const trackFileDependency = jasmine.createSpy('DependencyTracker');
|
||||
const {expression, checker} =
|
||||
makeExpression(`import * as mod from './direct-reexport';`, 'mod.value.property', [
|
||||
{name: 'const.ts', contents: 'export const value = {property: "test"};'},
|
||||
{name: 'def.ts', contents: `import {value} from './const'; export default value;`},
|
||||
{name: 'indirect-reexport.ts', contents: `import value from './def'; export {value};`},
|
||||
{name: 'direct-reexport.ts', contents: `export {value} from './indirect-reexport';`},
|
||||
]);
|
||||
const evaluator = makeEvaluator(checker, {trackFileDependency});
|
||||
evaluator.evaluate(expression);
|
||||
expect(trackFileDependency).toHaveBeenCalledTimes(2);
|
||||
expect(trackFileDependency.calls.allArgs().map(args => [args[0].fileName, args[1].fileName]))
|
||||
.toEqual([
|
||||
['/direct-reexport.ts', '/entry.ts'],
|
||||
// Not '/indirect-reexport.ts' or '/def.ts'.
|
||||
// TS skips through them when finding the original symbol for `value`
|
||||
['/const.ts', '/entry.ts'],
|
||||
]);
|
||||
it('should track imported source files', () => {
|
||||
const trackFileDependency = jasmine.createSpy('DependencyTracker');
|
||||
const {expression, checker} =
|
||||
makeExpression(`import {Y} from './other'; const A = Y;`, 'A', [
|
||||
{name: _('/other.ts'), contents: `export const Y = 'test';`},
|
||||
{name: _('/not-visited.ts'), contents: `export const Z = 'nope';`}
|
||||
]);
|
||||
const evaluator = makeEvaluator(checker, {trackFileDependency});
|
||||
evaluator.evaluate(expression);
|
||||
expect(trackFileDependency).toHaveBeenCalledTimes(2);
|
||||
expect(
|
||||
trackFileDependency.calls.allArgs().map(args => [args[0].fileName, args[1].fileName]))
|
||||
.toEqual([
|
||||
[_('/entry.ts'), _('/entry.ts')],
|
||||
[_('/other.ts'), _('/entry.ts')],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should track files passed through during re-exports', () => {
|
||||
const trackFileDependency = jasmine.createSpy('DependencyTracker');
|
||||
const {expression, checker} =
|
||||
makeExpression(`import * as mod from './direct-reexport';`, 'mod.value.property', [
|
||||
{name: _('/const.ts'), contents: 'export const value = {property: "test"};'},
|
||||
{
|
||||
name: _('/def.ts'),
|
||||
contents: `import {value} from './const'; export default value;`
|
||||
},
|
||||
{
|
||||
name: _('/indirect-reexport.ts'),
|
||||
contents: `import value from './def'; export {value};`
|
||||
},
|
||||
{
|
||||
name: _('/direct-reexport.ts'),
|
||||
contents: `export {value} from './indirect-reexport';`
|
||||
},
|
||||
]);
|
||||
const evaluator = makeEvaluator(checker, {trackFileDependency});
|
||||
evaluator.evaluate(expression);
|
||||
expect(trackFileDependency).toHaveBeenCalledTimes(2);
|
||||
expect(
|
||||
trackFileDependency.calls.allArgs().map(args => [args[0].fileName, args[1].fileName]))
|
||||
.toEqual([
|
||||
[_('/direct-reexport.ts'), _('/entry.ts')],
|
||||
// Not '/indirect-reexport.ts' or '/def.ts'.
|
||||
// TS skips through them when finding the original symbol for `value`
|
||||
[_('/const.ts'), _('/entry.ts')],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Customizes the resolution of functions to recognize functions from tslib. Such functions are not
|
||||
* handled specially in the default TypeScript host, as only ngcc's ES5 host will have special
|
||||
* powers to recognize functions from tslib.
|
||||
*/
|
||||
class TsLibAwareReflectionHost extends TypeScriptReflectionHost {
|
||||
getDefinitionOfFunction(node: ts.Node): FunctionDefinition|null {
|
||||
if (ts.isFunctionDeclaration(node)) {
|
||||
const helper = getTsHelperFn(node);
|
||||
if (helper !== null) {
|
||||
return {
|
||||
node,
|
||||
body: null, helper,
|
||||
parameters: [],
|
||||
};
|
||||
/**
|
||||
* Customizes the resolution of functions to recognize functions from tslib. Such functions are
|
||||
* not handled specially in the default TypeScript host, as only ngcc's ES5 host will have special
|
||||
* powers to recognize functions from tslib.
|
||||
*/
|
||||
class TsLibAwareReflectionHost extends TypeScriptReflectionHost {
|
||||
getDefinitionOfFunction(node: ts.Node): FunctionDefinition|null {
|
||||
if (ts.isFunctionDeclaration(node)) {
|
||||
const helper = getTsHelperFn(node);
|
||||
if (helper !== null) {
|
||||
return {
|
||||
node,
|
||||
body: null, helper,
|
||||
parameters: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
return super.getDefinitionOfFunction(node);
|
||||
}
|
||||
return super.getDefinitionOfFunction(node);
|
||||
}
|
||||
}
|
||||
|
||||
function getTsHelperFn(node: ts.FunctionDeclaration): TsHelperFn|null {
|
||||
const name = node.name !== undefined && ts.isIdentifier(node.name) && node.name.text;
|
||||
function getTsHelperFn(node: ts.FunctionDeclaration): TsHelperFn|null {
|
||||
const name = node.name !== undefined && ts.isIdentifier(node.name) && node.name.text;
|
||||
|
||||
if (name === '__spread') {
|
||||
return TsHelperFn.Spread;
|
||||
} else {
|
||||
return null;
|
||||
if (name === '__spread') {
|
||||
return TsHelperFn.Spread;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -5,27 +5,29 @@
|
||||
* 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 * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom} from '../../file_system';
|
||||
import {TestFile} from '../../file_system/testing';
|
||||
import {Reference} from '../../imports';
|
||||
import {TypeScriptReflectionHost} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
import {DependencyTracker, ForeignFunctionResolver, PartialEvaluator} from '../src/interface';
|
||||
import {ResolvedValue} from '../src/result';
|
||||
|
||||
export function makeExpression(
|
||||
code: string, expr: string, supportingFiles: {name: string, contents: string}[] = []): {
|
||||
export function makeExpression(code: string, expr: string, supportingFiles: TestFile[] = []): {
|
||||
expression: ts.Expression,
|
||||
host: ts.CompilerHost,
|
||||
checker: ts.TypeChecker,
|
||||
program: ts.Program,
|
||||
options: ts.CompilerOptions
|
||||
} {
|
||||
const {program, options, host} = makeProgram(
|
||||
[{name: 'entry.ts', contents: `${code}; const target$ = ${expr};`}, ...supportingFiles]);
|
||||
const {program, options, host} = makeProgram([
|
||||
{name: absoluteFrom('/entry.ts'), contents: `${code}; const target$ = ${expr};`},
|
||||
...supportingFiles
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const decl = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
const decl =
|
||||
getDeclaration(program, absoluteFrom('/entry.ts'), 'target$', ts.isVariableDeclaration);
|
||||
return {
|
||||
expression: decl.initializer !,
|
||||
host,
|
||||
@ -42,7 +44,7 @@ export function makeEvaluator(
|
||||
}
|
||||
|
||||
export function evaluate<T extends ResolvedValue>(
|
||||
code: string, expr: string, supportingFiles: {name: string, contents: string}[] = [],
|
||||
code: string, expr: string, supportingFiles: TestFile[] = [],
|
||||
foreignFunctionResolver?: ForeignFunctionResolver): T {
|
||||
const {expression, checker} = makeExpression(code, expr, supportingFiles);
|
||||
const evaluator = makeEvaluator(checker);
|
||||
|
Reference in New Issue
Block a user