Skip to content
Permalink
Browse files

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

angular#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 angular#27743
  • Loading branch information...
alxhub authored and ngfelixl committed Dec 18, 2018
1 parent 8cea650 commit 37dbd8eb4071002b741245eb2766c03a4a30156f
@@ -59,42 +59,46 @@ export class FileResourceLoader implements ResourceLoader {
*/
export class DecorationAnalyzer {
resourceLoader = new FileResourceLoader();
scopeRegistry = new SelectorScopeRegistry(this.typeChecker, this.host);
evaluator = new PartialEvaluator(this.host, this.typeChecker);
scopeRegistry = new SelectorScopeRegistry(this.typeChecker, this.reflectionHost);
evaluator = new PartialEvaluator(this.reflectionHost, this.typeChecker);
handlers: DecoratorHandler<any, any>[] = [
new BaseDefDecoratorHandler(this.host, this.evaluator),
new BaseDefDecoratorHandler(this.reflectionHost, this.evaluator),
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),
new DirectiveDecoratorHandler(this.host, this.evaluator, this.scopeRegistry, this.isCore),
new InjectableDecoratorHandler(this.host, this.isCore),
new DirectiveDecoratorHandler(
this.reflectionHost, this.evaluator, this.scopeRegistry, this.isCore),
new InjectableDecoratorHandler(this.reflectionHost, this.isCore),
new NgModuleDecoratorHandler(
this.host, this.evaluator, this.scopeRegistry, this.referencesRegistry, this.isCore),
new PipeDecoratorHandler(this.host, this.evaluator, this.scopeRegistry, this.isCore),
this.reflectionHost, this.evaluator, this.scopeRegistry, this.referencesRegistry,
this.isCore),
new PipeDecoratorHandler(this.reflectionHost, this.evaluator, this.scopeRegistry, this.isCore),
];

constructor(
private typeChecker: ts.TypeChecker, private host: NgccReflectionHost,
private referencesRegistry: ReferencesRegistry, private rootDirs: string[],
private isCore: boolean) {}
private program: ts.Program, private options: ts.CompilerOptions,
private host: ts.CompilerHost, private typeChecker: ts.TypeChecker,
private reflectionHost: NgccReflectionHost, private referencesRegistry: ReferencesRegistry,
private rootDirs: string[], private isCore: boolean) {}

/**
* 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.
*/
analyzeProgram(program: ts.Program): DecorationAnalyses {
analyzeProgram(): DecorationAnalyses {
const decorationAnalyses = new DecorationAnalyses();
const analysedFiles =
program.getSourceFiles().map(sourceFile => this.analyzeFile(sourceFile)).filter(isDefined);
const analysedFiles = this.program.getSourceFiles()
.map(sourceFile => this.analyzeFile(sourceFile))
.filter(isDefined);
const compiledFiles = analysedFiles.map(analysedFile => this.compileFile(analysedFile));
compiledFiles.forEach(
compiledFile => decorationAnalyses.set(compiledFile.sourceFile, compiledFile));
return decorationAnalyses;
}

protected analyzeFile(sourceFile: ts.SourceFile): AnalyzedFile|undefined {
const decoratedClasses = this.host.findDecoratedClasses(sourceFile);
const decoratedClasses = this.reflectionHost.findDecoratedClasses(sourceFile);
return decoratedClasses.length ? {
sourceFile,
analyzedClasses: decoratedClasses.map(clazz => this.analyzeClass(clazz)).filter(isDefined)
@@ -19,6 +19,8 @@ import * as ts from 'typescript';
*/
export interface BundleProgram {
program: ts.Program;
options: ts.CompilerOptions;
host: ts.CompilerHost;
path: string;
file: ts.SourceFile;
r3SymbolsPath: string|null;
@@ -37,7 +39,7 @@ export function makeBundleProgram(
const file = program.getSourceFile(path) !;
const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null;

return {program, path, file, r3SymbolsPath, r3SymbolsFile};
return {program, options, host, path, file, r3SymbolsPath, r3SymbolsFile};
}

/**
@@ -110,8 +110,9 @@ export class Transformer {
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);

const decorationAnalyzer = new DecorationAnalyzer(
typeChecker, reflectionHost, referencesRegistry, bundle.rootDirs, isCore);
const decorationAnalyses = decorationAnalyzer.analyzeProgram(bundle.src.program);
bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, reflectionHost,
referencesRegistry, bundle.rootDirs, isCore);
const decorationAnalyses = decorationAnalyzer.analyzeProgram();

const moduleWithProvidersAnalyzer =
bundle.dts && new ModuleWithProvidersAnalyzer(reflectionHost, referencesRegistry);
@@ -12,8 +12,7 @@ import {DecoratorHandler} from '../../../ngtsc/transform';
import {DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';

import {makeTestProgram} from '../helpers/utils';
import {makeTestBundleProgram} from '../helpers/utils';

const TEST_PROGRAM = {
name: 'test.js',
@@ -84,14 +83,17 @@ describe('DecorationAnalyzer', () => {
let result: DecorationAnalyses;

beforeEach(() => {
program = makeTestProgram(TEST_PROGRAM);
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
const referencesRegistry = new NgccReferencesRegistry(host);
const analyzer =
new DecorationAnalyzer(program.getTypeChecker(), host, referencesRegistry, [''], false);
const {options, host, ...bundle} = makeTestBundleProgram([TEST_PROGRAM]);
program = bundle.program;

const reflectionHost = new Esm2015ReflectionHost(false, program.getTypeChecker());
const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
const analyzer = new DecorationAnalyzer(
program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry,
[''], false);
testHandler = createTestHandler();
analyzer.handlers = [testHandler];
result = analyzer.analyzeProgram(program);
result = analyzer.analyzeProgram();
});

it('should return an object containing a reference to the original source file', () => {
@@ -127,14 +129,15 @@ describe('DecorationAnalyzer', () => {
// is not yet solved.
it('should analyze an internally imported component, which is not publicly exported from the entry-point',
() => {
const program = makeTestProgram(...INTERNAL_COMPONENT_PROGRAM);
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
const referencesRegistry = new NgccReferencesRegistry(host);
const {program, options, host} = makeTestBundleProgram(INTERNAL_COMPONENT_PROGRAM);
const reflectionHost = new Esm2015ReflectionHost(false, program.getTypeChecker());
const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
const analyzer = new DecorationAnalyzer(
program.getTypeChecker(), host, referencesRegistry, [''], false);
program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry,
[''], false);
const testHandler = createTestHandler();
analyzer.handlers = [testHandler];
const result = analyzer.analyzeProgram(program);
const result = analyzer.analyzeProgram();
const file = program.getSourceFile('component.js') !;
const analysis = result.get(file) !;
expect(analysis).toBeDefined();
@@ -144,14 +147,15 @@ describe('DecorationAnalyzer', () => {
});

it('should analyze an internally defined component, which is not exported at all', () => {
const program = makeTestProgram(...INTERNAL_COMPONENT_PROGRAM);
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
const referencesRegistry = new NgccReferencesRegistry(host);
const analyzer =
new DecorationAnalyzer(program.getTypeChecker(), host, referencesRegistry, [''], false);
const {program, options, host} = makeTestBundleProgram(INTERNAL_COMPONENT_PROGRAM);
const reflectionHost = new Esm2015ReflectionHost(false, program.getTypeChecker());
const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
const analyzer = new DecorationAnalyzer(
program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry,
[''], false);
const testHandler = createTestHandler();
analyzer.handlers = [testHandler];
const result = analyzer.analyzeProgram(program);
const result = analyzer.analyzeProgram();
const file = program.getSourceFile('entrypoint.js') !;
const analysis = result.get(file) !;
expect(analysis).toBeDefined();
@@ -35,20 +35,27 @@ export function makeTestEntryPointBundle(
* @param files The source files of the bundle program.
*/
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 file = program.getSourceFile(path) !;
const r3SymbolsInfo = files.find(file => file.name.indexOf('r3_symbols') !== -1) || null;
const r3SymbolsPath = r3SymbolsInfo && r3SymbolsInfo.name;
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(
...files: {name: string, contents: string, isRoot?: boolean | undefined}[]): ts.Program {
return makeProgram([getFakeCore(), getFakeTslib(), ...files], {allowJs: true, checkJs: false})
.program;
return makeTestProgramInternal(...files).program;
}

// TODO: unify this with the //packages/compiler-cli/test/ngtsc/fake_core package
@@ -21,9 +21,10 @@ function setup(file: {name: string, contents: string}) {
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(false, typeChecker);
const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses =
new DecorationAnalyzer(typeChecker, host, referencesRegistry, [''], false)
.analyzeProgram(bundle.src.program);
const decorationAnalyses = new DecorationAnalyzer(
bundle.src.program, bundle.src.options, bundle.src.host,
typeChecker, host, referencesRegistry, [''], false)
.analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const renderer = new EsmRenderer(host, false, bundle, dir, dir);
return {
@@ -21,9 +21,10 @@ function setup(file: {name: string, contents: string}) {
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm5ReflectionHost(false, typeChecker);
const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses =
new DecorationAnalyzer(typeChecker, host, referencesRegistry, [''], false)
.analyzeProgram(bundle.src.program);
const decorationAnalyses = new DecorationAnalyzer(
bundle.src.program, bundle.src.options, bundle.src.host,
typeChecker, host, referencesRegistry, [''], false)
.analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const renderer = new Esm5Renderer(host, false, bundle, dir, dir);
return {
@@ -54,9 +54,10 @@ function createTestRenderer(
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses =
new DecorationAnalyzer(typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
.analyzeProgram(bundle.src.program);
const decorationAnalyses = new DecorationAnalyzer(
bundle.src.program, bundle.src.options, bundle.src.host,
typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
.analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const moduleWithProvidersAnalyses =
new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
Oops, something went wrong.

0 comments on commit 37dbd8e

Please sign in to comment.
You can’t perform that action at this time.