From 6f168b7a0f60e559e8269e4c1963e460831d8cfb Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 17 Aug 2018 07:50:55 +0100 Subject: [PATCH] feat(ivy): implement `NgccReflectionHost.getSwitchableDeclarations()` (#25534) This method will be used to find all the places where the "ivy switch" will occur. See #25238 PR Close #25534 --- .../src/ngcc/src/host/fesm2015_host.ts | 14 ++++++-- .../src/ngcc/src/host/ngcc_host.ts | 24 ++++++++++++++ packages/compiler-cli/src/ngcc/src/utils.ts | 20 ++++++++++++ .../src/ngcc/test/host/esm2015_host_spec.ts | 32 +++++++++++++++++++ .../src/ngcc/test/host/fesm2015_host_spec.ts | 31 ++++++++++++++++++ 5 files changed, 119 insertions(+), 2 deletions(-) diff --git a/packages/compiler-cli/src/ngcc/src/host/fesm2015_host.ts b/packages/compiler-cli/src/ngcc/src/host/fesm2015_host.ts index 5a03b789f1..0f145b09e4 100644 --- a/packages/compiler-cli/src/ngcc/src/host/fesm2015_host.ts +++ b/packages/compiler-cli/src/ngcc/src/host/fesm2015_host.ts @@ -10,9 +10,9 @@ import * as ts from 'typescript'; import {ClassMember, ClassMemberKind, CtorParameter, Decorator} from '../../../ngtsc/host'; import {TypeScriptReflectionHost, reflectObjectLiteral} from '../../../ngtsc/metadata'; -import {getNameText} from '../utils'; +import {findAll, getNameText} from '../utils'; -import {NgccReflectionHost} from './ngcc_host'; +import {NgccReflectionHost, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host'; export const DECORATORS = 'decorators' as ts.__String; export const PROP_DECORATORS = 'propDecorators' as ts.__String; @@ -198,6 +198,16 @@ export class Fesm2015ReflectionHost extends TypeScriptReflectionHost implements undefined; } + /** + * Search the given module for variable declarations in which the initializer + * is an identifier marked with the `PRE_NGCC_MARKER`. + * @param module The module in which to search for switchable declarations. + * @returns An array of variable declarations that match. + */ + getSwitchableDeclarations(module: ts.Node): SwitchableVariableDeclaration[] { + return findAll(module, isSwitchableVariableDeclaration); + } + /** * Member decorators are declared as static properties of the class in ES2015: * diff --git a/packages/compiler-cli/src/ngcc/src/host/ngcc_host.ts b/packages/compiler-cli/src/ngcc/src/host/ngcc_host.ts index c0746643fc..efe7b09383 100644 --- a/packages/compiler-cli/src/ngcc/src/host/ngcc_host.ts +++ b/packages/compiler-cli/src/ngcc/src/host/ngcc_host.ts @@ -8,9 +8,33 @@ import * as ts from 'typescript'; import {ReflectionHost} from '../../../ngtsc/host'; +export const PRE_NGCC_MARKER = '__PRE_NGCC__'; +export const POST_NGCC_MARKER = '__POST_NGCC__'; + +export type SwitchableVariableDeclaration = ts.VariableDeclaration & {initializer: ts.Identifier}; +export function isSwitchableVariableDeclaration(node: ts.Node): + node is SwitchableVariableDeclaration { + return ts.isVariableDeclaration(node) && !!node.initializer && + ts.isIdentifier(node.initializer) && node.initializer.text.endsWith(PRE_NGCC_MARKER); +} + /** * A reflection host that has extra methods for looking at non-Typescript package formats */ export interface NgccReflectionHost extends ReflectionHost { + /** + * Find a symbol for a declaration that we think is a class. + * @param declaration The declaration whose symbol we are finding + * @returns the symbol for the declaration or `undefined` if it is not + * a "class" or has no symbol. + */ getClassSymbol(node: ts.Node): ts.Symbol|undefined; + + /** + * Search the given module for variable declarations in which the initializer + * is an identifier marked with the `PRE_NGCC_MARKER`. + * @param module The module in which to search for switchable declarations. + * @returns An array of variable declarations that match. + */ + getSwitchableDeclarations(module: ts.Node): SwitchableVariableDeclaration[]; } diff --git a/packages/compiler-cli/src/ngcc/src/utils.ts b/packages/compiler-cli/src/ngcc/src/utils.ts index 7fe3aee36f..94682bd8b6 100644 --- a/packages/compiler-cli/src/ngcc/src/utils.ts +++ b/packages/compiler-cli/src/ngcc/src/utils.ts @@ -20,3 +20,23 @@ export function isDefined(value: T | undefined | null): value is T { export function getNameText(name: ts.PropertyName | ts.BindingName): string { return ts.isIdentifier(name) || ts.isLiteralExpression(name) ? name.text : name.getText(); } + +/** + * Parse down the AST and capture all the nodes that satisfy the test. + * @param node The start node. + * @param test The function that tests whether a node should be included. + * @returns a collection of nodes that satisfy the test. + */ +export function findAll(node: ts.Node, test: (node: ts.Node) => node is ts.Node & T): T[] { + const nodes: T[] = []; + findAllVisitor(node); + return nodes; + + function findAllVisitor(n: ts.Node) { + if (test(n)) { + nodes.push(n); + } else { + n.forEachChild(child => findAllVisitor(child)); + } + } +} diff --git a/packages/compiler-cli/src/ngcc/test/host/esm2015_host_spec.ts b/packages/compiler-cli/src/ngcc/test/host/esm2015_host_spec.ts index 5296927c89..b2f705ebe9 100644 --- a/packages/compiler-cli/src/ngcc/test/host/esm2015_host_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/host/esm2015_host_spec.ts @@ -32,6 +32,24 @@ const CLASSES = [ }, ]; +const MARKER_FILE = { + name: '/marker.js', + contents: ` + let compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__; + + function compileNgModuleFactory__PRE_NGCC__(injector, options, moduleType) { + const compilerFactory = injector.get(CompilerFactory); + const compiler = compilerFactory.createCompiler([options]); + return compiler.compileModuleAsync(moduleType); + } + + function compileNgModuleFactory__POST_NGCC__(injector, options, moduleType) { + ngDevMode && assertNgModuleType(moduleType); + return Promise.resolve(new R3NgModuleFactory(moduleType)); + } + ` +}; + describe('Esm2015ReflectionHost', () => { describe('getGenericArityOfClass()', () => { it('should properly count type parameters', () => { @@ -52,4 +70,18 @@ describe('Esm2015ReflectionHost', () => { expect(host.getGenericArityOfClass(twoTypeParamsClass)).toBe(2); }); }); + + describe('getSwitchableDeclarations()', () => { + it('should return a collection of all the switchable variable declarations in the given module', + () => { + const program = makeProgram(MARKER_FILE); + const dtsMapper = new DtsMapper('/src', '/typings'); + const host = new Esm2015ReflectionHost(program.getTypeChecker(), dtsMapper); + const file = program.getSourceFile(MARKER_FILE.name) !; + const declarations = host.getSwitchableDeclarations(file); + expect(declarations.map(d => [d.name.getText(), d.initializer !.getText()])).toEqual([ + ['compileNgModuleFactory', 'compileNgModuleFactory__PRE_NGCC__'] + ]); + }); + }); }); diff --git a/packages/compiler-cli/src/ngcc/test/host/fesm2015_host_spec.ts b/packages/compiler-cli/src/ngcc/test/host/fesm2015_host_spec.ts index b2e0b9d6a4..0d8d6373ad 100644 --- a/packages/compiler-cli/src/ngcc/test/host/fesm2015_host_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/host/fesm2015_host_spec.ts @@ -385,6 +385,24 @@ const FUNCTION_BODY_FILE = { ` }; +const MARKER_FILE = { + name: '/marker.js', + contents: ` + var compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__; + + function compileNgModuleFactory__PRE_NGCC__(injector, options, moduleType) { + var compilerFactory = injector.get(CompilerFactory); + var compiler = compilerFactory.createCompiler([options]); + return compiler.compileModuleAsync(moduleType); + } + + function compileNgModuleFactory__POST_NGCC__(injector, options, moduleType) { + ngDevMode && assertNgModuleType(moduleType); + return Promise.resolve(new R3NgModuleFactory(moduleType)); + } + ` +}; + describe('Fesm2015ReflectionHost', () => { describe('getDecoratorsOfDeclaration()', () => { @@ -1120,4 +1138,17 @@ describe('Fesm2015ReflectionHost', () => { expect(host.getGenericArityOfClass(node)).toBe(0); }); }); + + describe('getSwitchableDeclarations()', () => { + it('should return a collection of all the switchable variable declarations in the given module', + () => { + const program = makeProgram(MARKER_FILE); + const host = new Fesm2015ReflectionHost(program.getTypeChecker()); + const file = program.getSourceFile(MARKER_FILE.name) !; + const declarations = host.getSwitchableDeclarations(file); + expect(declarations.map(d => [d.name.getText(), d.initializer !.getText()])).toEqual([ + ['compileNgModuleFactory', 'compileNgModuleFactory__PRE_NGCC__'] + ]); + }); + }); });