refactor(ivy): prep ngtsc and ngcc for upcoming import resolution work (#27743)

Upcoming work to implement import resolution will change the dependencies
of some higher-level classes in ngtsc & ngcc. This necessitates changes in
how these classes are created and the lifecycle of the ts.Program in ngtsc
& ngcc.

To avoid complicating the implementation work with refactoring as a result
of the new dependencies, the refactoring is performed in this commit as a
separate prepatory step.

In ngtsc, the testing harness is modified to allow easier access to some
aspects of the ts.Program.

In ngcc, the main change is that the DecorationAnalyzer is created with the
ts.Program as a constructor parameter. This is not a lifecycle change, as
it was previously created with the ts.TypeChecker which is derived from the
ts.Program anyways. This change requires some reorganization in ngcc to
accommodate, especially in testing harnesses where DecorationAnalyzer is
created manually in a number of specs.

PR Close #27743
This commit is contained in:
Alex Rickabaugh
2018-12-17 16:17:38 -08:00
committed by Kara Erickson
parent 2a6108af97
commit f4a9f5dae8
10 changed files with 127 additions and 139 deletions

View File

@ -20,47 +20,50 @@ function makeSimpleProgram(contents: string): ts.Program {
}
function makeExpression(
code: string, expr: string): {expression: ts.Expression, checker: ts.TypeChecker} {
const {program} =
makeProgram([{name: 'entry.ts', contents: `${code}; const target$ = ${expr};`}]);
code: string, expr: string, supportingFiles: {name: string, contents: string}[] = []): {
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 checker = program.getTypeChecker();
const decl = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
return {
expression: decl.initializer !,
host,
options,
checker,
program,
};
}
function evaluate<T extends ResolvedValue>(code: string, expr: string): T {
const {expression, checker} = makeExpression(code, expr);
const host = new TypeScriptReflectionHost(checker);
const evaluator = new PartialEvaluator(host, checker);
function evaluate<T extends ResolvedValue>(
code: string, expr: string, supportingFiles: {name: string, contents: string}[] = []): T {
const {expression, checker} = makeExpression(code, expr, supportingFiles);
const reflectionHost = new TypeScriptReflectionHost(checker);
const evaluator = new PartialEvaluator(reflectionHost, checker);
return evaluator.evaluate(expression) as T;
}
describe('ngtsc metadata', () => {
it('reads a file correctly', () => {
const {program} = makeProgram([
{
name: 'entry.ts',
contents: `
import {Y} from './other';
const A = Y;
export const X = A;
`
},
{
name: 'other.ts',
contents: `
const value = evaluate(
`
import {Y} from './other';
const A = Y;
`,
'A', [
{
name: 'other.ts',
contents: `
export const Y = 'test';
`
}
]);
const decl = getDeclaration(program, 'entry.ts', 'X', ts.isVariableDeclaration);
const host = new TypeScriptReflectionHost(program.getTypeChecker());
const evaluator = new PartialEvaluator(host, program.getTypeChecker());
},
]);
const value = evaluator.evaluate(decl.initializer !);
expect(value).toEqual('test');
});
@ -143,10 +146,10 @@ describe('ngtsc metadata', () => {
},
]);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const reflectionHost = new TypeScriptReflectionHost(checker);
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
const evaluator = new PartialEvaluator(host, checker);
const evaluator = new PartialEvaluator(reflectionHost, checker);
const resolved = evaluator.evaluate(expr);
if (!(resolved instanceof Reference)) {
return fail('Expected expression to resolve to a reference');
@ -175,10 +178,10 @@ describe('ngtsc metadata', () => {
},
]);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const reflectionHost = new TypeScriptReflectionHost(checker);
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
const evaluator = new PartialEvaluator(host, checker);
const evaluator = new PartialEvaluator(reflectionHost, checker);
const resolved = evaluator.evaluate(expr);
if (!(resolved instanceof AbsoluteReference)) {
return fail('Expected expression to resolve to an absolute reference');
@ -197,60 +200,31 @@ describe('ngtsc metadata', () => {
});
it('reads values from default exports', () => {
const {program} = makeProgram([
{name: 'second.ts', contents: 'export default {property: "test"}'},
{
name: 'entry.ts',
contents: `
import mod from './second';
const target$ = mod.property;
`
},
]);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
const evaluator = new PartialEvaluator(host, checker);
expect(evaluator.evaluate(expr)).toEqual('test');
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 {program} = makeProgram([
const value = evaluate(`import * as mod from './second';`, 'mod.a.property', [
{name: 'second.ts', contents: 'export const a = {property: "test"};'},
{
name: 'entry.ts',
contents: `
import * as mod from './second';
const target$ = mod.a.property;
`
},
]);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
const evaluator = new PartialEvaluator(host, checker);
expect(evaluator.evaluate(expr)).toEqual('test');
expect(value).toEqual('test');
});
it('chain of re-exports works', () => {
const {program} = makeProgram([
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';`},
{
name: 'entry.ts',
contents: `import * as mod from './direct-reexport'; const target$ = mod.value.property;`
},
]);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
const evaluator = new PartialEvaluator(host, checker);
expect(evaluator.evaluate(expr)).toEqual('test');
expect(value).toEqual('test');
});
it('map spread works', () => {
@ -299,15 +273,9 @@ describe('ngtsc metadata', () => {
});
it('variable declaration resolution works', () => {
const {program} = makeProgram([
const value = evaluate(`import {value} from './decl';`, 'value', [
{name: 'decl.d.ts', contents: 'export declare let value: number;'},
{name: 'entry.ts', contents: `import {value} from './decl'; const target$ = value;`},
]);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const evaluator = new PartialEvaluator(host, checker);
const res = evaluator.evaluate(result.initializer !);
expect(res instanceof Reference).toBe(true);
expect(value instanceof Reference).toBe(true);
});
});

View File

@ -13,19 +13,18 @@ import * as ts from 'typescript';
export function makeProgram(
files: {name: string, contents: string, isRoot?: boolean}[], options?: ts.CompilerOptions,
host: ts.CompilerHost = new InMemoryHost(),
checkForErrors: boolean = true): {program: ts.Program, host: ts.CompilerHost} {
host: ts.CompilerHost = new InMemoryHost(), checkForErrors: boolean = true):
{program: ts.Program, host: ts.CompilerHost, options: ts.CompilerOptions} {
files.forEach(file => host.writeFile(file.name, file.contents, false, undefined, []));
const rootNames =
files.filter(file => file.isRoot !== false).map(file => host.getCanonicalFileName(file.name));
const program = ts.createProgram(
rootNames, {
noLib: true,
experimentalDecorators: true,
moduleResolution: ts.ModuleResolutionKind.NodeJs, ...options
},
host);
const compilerOptions = {
noLib: true,
experimentalDecorators: true,
moduleResolution: ts.ModuleResolutionKind.NodeJs, ...options
};
const program = ts.createProgram(rootNames, compilerOptions, host);
if (checkForErrors) {
const diags = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics()];
if (diags.length > 0) {
@ -41,7 +40,7 @@ export function makeProgram(
throw new Error(`Typescript diagnostics failed! ${errors.join(', ')}`);
}
}
return {program, host};
return {program, host, options: compilerOptions};
}
export class InMemoryHost implements ts.CompilerHost {