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

@ -59,34 +59,38 @@ export class FileResourceLoader implements ResourceLoader {
*/ */
export class DecorationAnalyzer { export class DecorationAnalyzer {
resourceLoader = new FileResourceLoader(); resourceLoader = new FileResourceLoader();
scopeRegistry = new SelectorScopeRegistry(this.typeChecker, this.host); scopeRegistry = new SelectorScopeRegistry(this.typeChecker, this.reflectionHost);
evaluator = new PartialEvaluator(this.host, this.typeChecker); evaluator = new PartialEvaluator(this.reflectionHost, this.typeChecker);
handlers: DecoratorHandler<any, any>[] = [ handlers: DecoratorHandler<any, any>[] = [
new BaseDefDecoratorHandler(this.host, this.evaluator), new BaseDefDecoratorHandler(this.reflectionHost, this.evaluator),
new ComponentDecoratorHandler( new ComponentDecoratorHandler(
this.host, this.evaluator, this.scopeRegistry, this.isCore, this.resourceLoader, this.reflectionHost, this.evaluator, this.scopeRegistry, this.isCore, this.resourceLoader,
this.rootDirs, /* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true), this.rootDirs, /* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true),
new DirectiveDecoratorHandler(this.host, this.evaluator, this.scopeRegistry, this.isCore), new DirectiveDecoratorHandler(
new InjectableDecoratorHandler(this.host, this.isCore), this.reflectionHost, this.evaluator, this.scopeRegistry, this.isCore),
new InjectableDecoratorHandler(this.reflectionHost, this.isCore),
new NgModuleDecoratorHandler( new NgModuleDecoratorHandler(
this.host, this.evaluator, this.scopeRegistry, this.referencesRegistry, this.isCore), this.reflectionHost, this.evaluator, this.scopeRegistry, this.referencesRegistry,
new PipeDecoratorHandler(this.host, this.evaluator, this.scopeRegistry, this.isCore), this.isCore),
new PipeDecoratorHandler(this.reflectionHost, this.evaluator, this.scopeRegistry, this.isCore),
]; ];
constructor( constructor(
private typeChecker: ts.TypeChecker, private host: NgccReflectionHost, private program: ts.Program, private options: ts.CompilerOptions,
private referencesRegistry: ReferencesRegistry, private rootDirs: string[], private host: ts.CompilerHost, private typeChecker: ts.TypeChecker,
private isCore: boolean) {} private reflectionHost: NgccReflectionHost, private referencesRegistry: ReferencesRegistry,
private rootDirs: string[], private isCore: boolean) {}
/** /**
* Analyze a program to find all the decorated files should be transformed. * Analyze a program to find all the decorated files should be transformed.
* @param program The program whose files should be analysed. *
* @returns a map of the source files to the analysis for those files. * @returns a map of the source files to the analysis for those files.
*/ */
analyzeProgram(program: ts.Program): DecorationAnalyses { analyzeProgram(): DecorationAnalyses {
const decorationAnalyses = new DecorationAnalyses(); const decorationAnalyses = new DecorationAnalyses();
const analysedFiles = const analysedFiles = this.program.getSourceFiles()
program.getSourceFiles().map(sourceFile => this.analyzeFile(sourceFile)).filter(isDefined); .map(sourceFile => this.analyzeFile(sourceFile))
.filter(isDefined);
const compiledFiles = analysedFiles.map(analysedFile => this.compileFile(analysedFile)); const compiledFiles = analysedFiles.map(analysedFile => this.compileFile(analysedFile));
compiledFiles.forEach( compiledFiles.forEach(
compiledFile => decorationAnalyses.set(compiledFile.sourceFile, compiledFile)); compiledFile => decorationAnalyses.set(compiledFile.sourceFile, compiledFile));
@ -94,7 +98,7 @@ export class DecorationAnalyzer {
} }
protected analyzeFile(sourceFile: ts.SourceFile): AnalyzedFile|undefined { protected analyzeFile(sourceFile: ts.SourceFile): AnalyzedFile|undefined {
const decoratedClasses = this.host.findDecoratedClasses(sourceFile); const decoratedClasses = this.reflectionHost.findDecoratedClasses(sourceFile);
return decoratedClasses.length ? { return decoratedClasses.length ? {
sourceFile, sourceFile,
analyzedClasses: decoratedClasses.map(clazz => this.analyzeClass(clazz)).filter(isDefined) analyzedClasses: decoratedClasses.map(clazz => this.analyzeClass(clazz)).filter(isDefined)

View File

@ -19,6 +19,8 @@ import * as ts from 'typescript';
*/ */
export interface BundleProgram { export interface BundleProgram {
program: ts.Program; program: ts.Program;
options: ts.CompilerOptions;
host: ts.CompilerHost;
path: string; path: string;
file: ts.SourceFile; file: ts.SourceFile;
r3SymbolsPath: string|null; r3SymbolsPath: string|null;
@ -37,7 +39,7 @@ export function makeBundleProgram(
const file = program.getSourceFile(path) !; const file = program.getSourceFile(path) !;
const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null; const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null;
return {program, path, file, r3SymbolsPath, r3SymbolsFile}; return {program, options, host, path, file, r3SymbolsPath, r3SymbolsFile};
} }
/** /**

View File

@ -110,8 +110,9 @@ export class Transformer {
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program); const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
const decorationAnalyzer = new DecorationAnalyzer( const decorationAnalyzer = new DecorationAnalyzer(
typeChecker, reflectionHost, referencesRegistry, bundle.rootDirs, isCore); bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, reflectionHost,
const decorationAnalyses = decorationAnalyzer.analyzeProgram(bundle.src.program); referencesRegistry, bundle.rootDirs, isCore);
const decorationAnalyses = decorationAnalyzer.analyzeProgram();
const moduleWithProvidersAnalyzer = const moduleWithProvidersAnalyzer =
bundle.dts && new ModuleWithProvidersAnalyzer(reflectionHost, referencesRegistry); bundle.dts && new ModuleWithProvidersAnalyzer(reflectionHost, referencesRegistry);

View File

@ -12,8 +12,7 @@ import {DecoratorHandler} from '../../../ngtsc/transform';
import {DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {makeTestBundleProgram} from '../helpers/utils';
import {makeTestProgram} from '../helpers/utils';
const TEST_PROGRAM = { const TEST_PROGRAM = {
name: 'test.js', name: 'test.js',
@ -84,14 +83,17 @@ describe('DecorationAnalyzer', () => {
let result: DecorationAnalyses; let result: DecorationAnalyses;
beforeEach(() => { beforeEach(() => {
program = makeTestProgram(TEST_PROGRAM); const {options, host, ...bundle} = makeTestBundleProgram([TEST_PROGRAM]);
const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); program = bundle.program;
const referencesRegistry = new NgccReferencesRegistry(host);
const analyzer = const reflectionHost = new Esm2015ReflectionHost(false, program.getTypeChecker());
new DecorationAnalyzer(program.getTypeChecker(), host, referencesRegistry, [''], false); const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
const analyzer = new DecorationAnalyzer(
program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry,
[''], false);
testHandler = createTestHandler(); testHandler = createTestHandler();
analyzer.handlers = [testHandler]; analyzer.handlers = [testHandler];
result = analyzer.analyzeProgram(program); result = analyzer.analyzeProgram();
}); });
it('should return an object containing a reference to the original source file', () => { it('should return an object containing a reference to the original source file', () => {
@ -127,14 +129,15 @@ describe('DecorationAnalyzer', () => {
// is not yet solved. // is not yet solved.
it('should analyze an internally imported component, which is not publicly exported from the entry-point', it('should analyze an internally imported component, which is not publicly exported from the entry-point',
() => { () => {
const program = makeTestProgram(...INTERNAL_COMPONENT_PROGRAM); const {program, options, host} = makeTestBundleProgram(INTERNAL_COMPONENT_PROGRAM);
const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const reflectionHost = new Esm2015ReflectionHost(false, program.getTypeChecker());
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
const analyzer = new DecorationAnalyzer( const analyzer = new DecorationAnalyzer(
program.getTypeChecker(), host, referencesRegistry, [''], false); program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry,
[''], false);
const testHandler = createTestHandler(); const testHandler = createTestHandler();
analyzer.handlers = [testHandler]; analyzer.handlers = [testHandler];
const result = analyzer.analyzeProgram(program); const result = analyzer.analyzeProgram();
const file = program.getSourceFile('component.js') !; const file = program.getSourceFile('component.js') !;
const analysis = result.get(file) !; const analysis = result.get(file) !;
expect(analysis).toBeDefined(); expect(analysis).toBeDefined();
@ -144,14 +147,15 @@ describe('DecorationAnalyzer', () => {
}); });
it('should analyze an internally defined component, which is not exported at all', () => { it('should analyze an internally defined component, which is not exported at all', () => {
const program = makeTestProgram(...INTERNAL_COMPONENT_PROGRAM); const {program, options, host} = makeTestBundleProgram(INTERNAL_COMPONENT_PROGRAM);
const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const reflectionHost = new Esm2015ReflectionHost(false, program.getTypeChecker());
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
const analyzer = const analyzer = new DecorationAnalyzer(
new DecorationAnalyzer(program.getTypeChecker(), host, referencesRegistry, [''], false); program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry,
[''], false);
const testHandler = createTestHandler(); const testHandler = createTestHandler();
analyzer.handlers = [testHandler]; analyzer.handlers = [testHandler];
const result = analyzer.analyzeProgram(program); const result = analyzer.analyzeProgram();
const file = program.getSourceFile('entrypoint.js') !; const file = program.getSourceFile('entrypoint.js') !;
const analysis = result.get(file) !; const analysis = result.get(file) !;
expect(analysis).toBeDefined(); expect(analysis).toBeDefined();

View File

@ -35,20 +35,27 @@ export function makeTestEntryPointBundle(
* @param files The source files of the bundle program. * @param files The source files of the bundle program.
*/ */
export function makeTestBundleProgram(files: {name: string, contents: string}[]): BundleProgram { export function makeTestBundleProgram(files: {name: string, contents: string}[]): BundleProgram {
const program = makeTestProgram(...files); const {program, options, host} = makeTestProgramInternal(...files);
const path = files[0].name; const path = files[0].name;
const file = program.getSourceFile(path) !; const file = program.getSourceFile(path) !;
const r3SymbolsInfo = files.find(file => file.name.indexOf('r3_symbols') !== -1) || null; const r3SymbolsInfo = files.find(file => file.name.indexOf('r3_symbols') !== -1) || null;
const r3SymbolsPath = r3SymbolsInfo && r3SymbolsInfo.name; const r3SymbolsPath = r3SymbolsInfo && r3SymbolsInfo.name;
const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null; const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null;
return {program, path, file, r3SymbolsPath, r3SymbolsFile}; return {program, options, host, path, file, r3SymbolsPath, r3SymbolsFile};
} }
function makeTestProgramInternal(
...files: {name: string, contents: string, isRoot?: boolean | undefined}[]): {
program: ts.Program,
host: ts.CompilerHost,
options: ts.CompilerOptions,
} {
return makeProgram([getFakeCore(), getFakeTslib(), ...files], {allowJs: true, checkJs: false});
}
export function makeTestProgram( export function makeTestProgram(
...files: {name: string, contents: string, isRoot?: boolean | undefined}[]): ts.Program { ...files: {name: string, contents: string, isRoot?: boolean | undefined}[]): ts.Program {
return makeProgram([getFakeCore(), getFakeTslib(), ...files], {allowJs: true, checkJs: false}) return makeTestProgramInternal(...files).program;
.program;
} }
// TODO: unify this with the //packages/compiler-cli/test/ngtsc/fake_core package // TODO: unify this with the //packages/compiler-cli/test/ngtsc/fake_core package

View File

@ -21,9 +21,10 @@ function setup(file: {name: string, contents: string}) {
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(false, typeChecker); const host = new Esm2015ReflectionHost(false, typeChecker);
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses = const decorationAnalyses = new DecorationAnalyzer(
new DecorationAnalyzer(typeChecker, host, referencesRegistry, [''], false) bundle.src.program, bundle.src.options, bundle.src.host,
.analyzeProgram(bundle.src.program); typeChecker, host, referencesRegistry, [''], false)
.analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const renderer = new EsmRenderer(host, false, bundle, dir, dir); const renderer = new EsmRenderer(host, false, bundle, dir, dir);
return { return {

View File

@ -21,9 +21,10 @@ function setup(file: {name: string, contents: string}) {
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm5ReflectionHost(false, typeChecker); const host = new Esm5ReflectionHost(false, typeChecker);
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses = const decorationAnalyses = new DecorationAnalyzer(
new DecorationAnalyzer(typeChecker, host, referencesRegistry, [''], false) bundle.src.program, bundle.src.options, bundle.src.host,
.analyzeProgram(bundle.src.program); typeChecker, host, referencesRegistry, [''], false)
.analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const renderer = new Esm5Renderer(host, false, bundle, dir, dir); const renderer = new Esm5Renderer(host, false, bundle, dir, dir);
return { return {

View File

@ -54,9 +54,10 @@ function createTestRenderer(
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts); const host = new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses = const decorationAnalyses = new DecorationAnalyzer(
new DecorationAnalyzer(typeChecker, host, referencesRegistry, bundle.rootDirs, isCore) bundle.src.program, bundle.src.options, bundle.src.host,
.analyzeProgram(bundle.src.program); typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
.analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const moduleWithProvidersAnalyses = const moduleWithProvidersAnalyses =
new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program); new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);

View File

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

View File

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