Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions packages/compiler-cli/src/ngcc/src/host/fesm2015_host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, PRE_NGCC_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';

export const DECORATORS = 'decorators' as ts.__String;
export const PROP_DECORATORS = 'propDecorators' as ts.__String;
Expand Down Expand Up @@ -198,6 +198,19 @@ 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[] {
// Don't bother to walk the AST if the marker is not found in the text
return module.getText().indexOf(PRE_NGCC_MARKER) >= 0 ?
findAll(module, isSwitchableVariableDeclaration) :
[];
}

/**
* Member decorators are declared as static properties of the class in ES2015:
*
Expand Down
24 changes: 24 additions & 0 deletions packages/compiler-cli/src/ngcc/src/host/ngcc_host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make this a method on the ReflectionHost. Having both standalone functions and Class methods gets confusing (imo).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have lots of type constraint functions in the ngcc and ngtsc code. They are all standalone.

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[];
}
11 changes: 11 additions & 0 deletions packages/compiler-cli/src/ngcc/src/rendering/esm2015_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
import * as ts from 'typescript';
import MagicString from 'magic-string';
import {POST_NGCC_MARKER, PRE_NGCC_MARKER} from '../host/ngcc_host';
import {AnalyzedClass} from '../analyzer';
import {Renderer} from './renderer';

Expand Down Expand Up @@ -70,4 +71,14 @@ export class Esm2015Renderer extends Renderer {
}
});
}

rewriteSwitchableDeclarations(outputText: MagicString, sourceFile: ts.SourceFile): void {
const declarations = this.host.getSwitchableDeclarations(sourceFile);
declarations.forEach(declaration => {
const start = declaration.initializer.getStart();
const end = declaration.initializer.getEnd();
const replacement = declaration.initializer.text.replace(PRE_NGCC_MARKER, POST_NGCC_MARKER);
outputText.overwrite(start, end, replacement);
});
}
}
8 changes: 7 additions & 1 deletion packages/compiler-cli/src/ngcc/src/rendering/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,13 @@ export abstract class Renderer {
file.sourceFile);

this.addImports(outputText, importManager.getAllImports(file.sourceFile.fileName, null));
// QUESTION: do we need to remove contructor param metadata and property decorators?

// TODO: remove contructor param metadata and property decorators (we need info from the
// handlers to do this)
this.removeDecorators(outputText, decoratorsToRemove);

this.rewriteSwitchableDeclarations(outputText, file.sourceFile);

return this.renderSourceAndMap(file, input, outputText, targetPath);
}

Expand All @@ -102,6 +106,8 @@ export abstract class Renderer {
output: MagicString, analyzedClass: AnalyzedClass, definitions: string): void;
protected abstract removeDecorators(
output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void;
protected abstract rewriteSwitchableDeclarations(
outputText: MagicString, sourceFile: ts.SourceFile): void;

/**
* Add the decorator nodes that are to be removed to a map
Expand Down
20 changes: 20 additions & 0 deletions packages/compiler-cli/src/ngcc/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,23 @@ export function isDefined<T>(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<T>(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));
}
}
}
32 changes: 32 additions & 0 deletions packages/compiler-cli/src/ngcc/test/host/esm2015_host_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ const CLASSES = [
},
];

const MARKER_FILE = {
name: '/marker.js',
contents: `
let compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't mind a test with __PRE_NGCC__ at the beginning of the identifier 😁

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh. 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


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', () => {
Expand All @@ -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__']
]);
});
});
});
31 changes: 31 additions & 0 deletions packages/compiler-cli/src/ngcc/test/host/fesm2015_host_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()', () => {
Expand Down Expand Up @@ -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__']
]);
});
});
});
Loading