refactor(ngcc): move `getModuleWithProvidersFunctions()` into the analyzer (#36948)

Previously this method was implemented on the `NgccReflectionHost`,
but really it is asking too much of the host, since it actually needs to do
some static evaluation of the code to be able to support a wider range
of function shapes. Also there was only one implementation of the method
in the `Esm2015ReflectionHost` since it has no format specific code in
in.

This commit moves the whole function (and supporting helpers) into the
`ModuleWithProvidersAnalyzer`, which is the only place it was being used.
This class will be able to do further static evaluation of the function bodies
in order to support more function shapes than the host can do on its own.

The commit removes a whole set of reflection host tests but these are
already covered by the tests of the analyzer.

PR Close #36948
This commit is contained in:
Pete Bacon Darwin 2020-05-05 10:19:28 +01:00 committed by Alex Rickabaugh
parent c9e0db55f7
commit e010f2ca54
8 changed files with 124 additions and 868 deletions

View File

@ -10,7 +10,7 @@ import * as ts from 'typescript';
import {ReferencesRegistry} from '../../../src/ngtsc/annotations';
import {Reference} from '../../../src/ngtsc/imports';
import {ClassDeclaration, ConcreteDeclaration} from '../../../src/ngtsc/reflection';
import {ModuleWithProvidersFunction, NgccReflectionHost} from '../host/ngcc_host';
import {NgccReflectionHost} from '../host/ngcc_host';
import {hasNameIdentifier, isDefined} from '../utils';
export interface ModuleWithProvidersInfo {
@ -38,7 +38,7 @@ export class ModuleWithProvidersAnalyzer {
const analyses = new ModuleWithProvidersAnalyses();
const rootFiles = this.getRootFiles(program);
rootFiles.forEach(f => {
const fns = this.host.getModuleWithProvidersFunctions(f);
const fns = this.getModuleWithProvidersFunctions(f);
fns && fns.forEach(fn => {
if (fn.ngModule.viaModule === null) {
// Record the usage of an internal module as it needs to become an exported symbol
@ -68,6 +68,100 @@ export class ModuleWithProvidersAnalyzer {
return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined);
}
private getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersFunction[] {
const exports = this.host.getExportsOfModule(f);
if (!exports) return [];
const infos: ModuleWithProvidersFunction[] = [];
exports.forEach((declaration, name) => {
if (declaration.node === null) {
return;
}
if (this.host.isClass(declaration.node)) {
this.host.getMembersOfClass(declaration.node).forEach(member => {
if (member.isStatic) {
const info = this.parseForModuleWithProviders(
member.name, member.node, member.implementation, declaration.node);
if (info) {
infos.push(info);
}
}
});
} else {
if (hasNameIdentifier(declaration.node)) {
const info =
this.parseForModuleWithProviders(declaration.node.name.text, declaration.node);
if (info) {
infos.push(info);
}
}
}
});
return infos;
}
/**
* Parse a function/method node (or its implementation), to see if it returns a
* `ModuleWithProviders` object.
* @param name The name of the function.
* @param node the node to check - this could be a function, a method or a variable declaration.
* @param implementation the actual function expression if `node` is a variable declaration.
* @param container the class that contains the function, if it is a method.
* @returns info about the function if it does return a `ModuleWithProviders` object; `null`
* otherwise.
*/
private parseForModuleWithProviders(
name: string, node: ts.Node|null, implementation: ts.Node|null = node,
container: ts.Declaration|null = null): ModuleWithProvidersFunction|null {
if (implementation === null ||
(!ts.isFunctionDeclaration(implementation) && !ts.isMethodDeclaration(implementation) &&
!ts.isFunctionExpression(implementation))) {
return null;
}
const declaration = implementation;
const definition = this.host.getDefinitionOfFunction(declaration);
if (definition === null) {
return null;
}
const body = definition.body;
const lastStatement = body && body[body.length - 1];
const returnExpression =
lastStatement && ts.isReturnStatement(lastStatement) && lastStatement.expression || null;
const ngModuleProperty = returnExpression && ts.isObjectLiteralExpression(returnExpression) &&
returnExpression.properties.find(
prop =>
!!prop.name && ts.isIdentifier(prop.name) && prop.name.text === 'ngModule') ||
null;
if (!ngModuleProperty || !ts.isPropertyAssignment(ngModuleProperty)) {
return null;
}
// The ngModuleValue could be of the form `SomeModule` or `namespace_1.SomeModule`
let ngModuleValue = ngModuleProperty.initializer;
if (ts.isPropertyAccessExpression(ngModuleValue)) {
ngModuleValue = ngModuleValue.expression;
}
if (!ts.isIdentifier(ngModuleValue)) {
return null;
}
const ngModuleDeclaration = this.host.getDeclarationOfIdentifier(ngModuleValue);
if (!ngModuleDeclaration || ngModuleDeclaration.node === null) {
throw new Error(`Cannot find a declaration for NgModule ${
ngModuleValue.getText()} referenced in "${declaration!.getText()}"`);
}
if (!hasNameIdentifier(ngModuleDeclaration.node)) {
return null;
}
return {
name,
ngModule: ngModuleDeclaration as ConcreteDeclaration<ClassDeclaration>,
declaration,
container
};
}
private getDtsDeclarationForFunction(fn: ModuleWithProvidersFunction) {
let dtsFn: ts.Declaration|null = null;
const containerClass = fn.container && this.host.getClassSymbol(fn.container);
@ -128,3 +222,27 @@ function isFunctionOrMethod(declaration: ts.Declaration): declaration is ts.Func
function isAnyKeyword(typeParam: ts.TypeNode): typeParam is ts.KeywordTypeNode {
return typeParam.kind === ts.SyntaxKind.AnyKeyword;
}
/**
* A structure returned from `getModuleWithProvidersFunction` that describes functions
* that return ModuleWithProviders objects.
*/
export interface ModuleWithProvidersFunction {
/**
* The name of the declared function.
*/
name: string;
/**
* The declaration of the function that returns the `ModuleWithProviders` object.
*/
declaration: ts.SignatureDeclaration;
/**
* Declaration of the containing class (if this is a method)
*/
container: ts.Declaration|null;
/**
* The declaration of the class that the `ngModule` property on the `ModuleWithProviders` object
* refers to.
*/
ngModule: ConcreteDeclaration<ClassDeclaration>;
}

View File

@ -11,7 +11,7 @@ import * as ts from 'typescript';
import {ClassDeclaration, ClassMember, CtorParameter, Declaration, Decorator, FunctionDefinition, Import, ReflectionHost} from '../../../src/ngtsc/reflection';
import {isFromDtsFile} from '../../../src/ngtsc/util/src/typescript';
import {ModuleWithProvidersFunction, NgccClassSymbol, NgccReflectionHost, SwitchableVariableDeclaration} from './ngcc_host';
import {NgccClassSymbol, NgccReflectionHost, SwitchableVariableDeclaration} from './ngcc_host';
/**
* A reflection host implementation that delegates reflector queries depending on whether they
@ -149,10 +149,6 @@ export class DelegatingReflectionHost implements NgccReflectionHost {
return this.ngccHost.getDecoratorsOfSymbol(symbol);
}
getModuleWithProvidersFunctions(sf: ts.SourceFile): ModuleWithProvidersFunction[] {
return this.ngccHost.getModuleWithProvidersFunctions(sf);
}
getSwitchableDeclarations(module: ts.Node): SwitchableVariableDeclaration[] {
return this.ngccHost.getSwitchableDeclarations(module);
}

View File

@ -8,13 +8,13 @@
import * as ts from 'typescript';
import {ClassDeclaration, ClassMember, ClassMemberKind, ConcreteDeclaration, CtorParameter, Declaration, Decorator, EnumMember, isDecoratorIdentifier, isNamedClassDeclaration, KnownDeclaration, reflectObjectLiteral, SpecialDeclarationKind, TypeScriptReflectionHost, TypeValueReference} from '../../../src/ngtsc/reflection';
import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, EnumMember, isDecoratorIdentifier, isNamedClassDeclaration, KnownDeclaration, reflectObjectLiteral, SpecialDeclarationKind, TypeScriptReflectionHost, TypeValueReference} from '../../../src/ngtsc/reflection';
import {isWithinPackage} from '../analysis/util';
import {Logger} from '../logging/logger';
import {BundleProgram} from '../packages/bundle_program';
import {findAll, getNameText, hasNameIdentifier, isDefined, stripDollarSuffix} from '../utils';
import {ClassSymbol, isSwitchableVariableDeclaration, ModuleWithProvidersFunction, NgccClassSymbol, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration} from './ngcc_host';
import {ClassSymbol, isSwitchableVariableDeclaration, NgccClassSymbol, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration} from './ngcc_host';
import {stripParentheses} from './utils';
export const DECORATORS = 'decorators' as ts.__String;
@ -568,44 +568,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
return null;
}
/**
* Search the given source file for exported functions and static class methods that return
* ModuleWithProviders objects.
* @param f The source file to search for these functions
* @returns An array of function declarations that look like they return ModuleWithProviders
* objects.
*/
getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersFunction[] {
const exports = this.getExportsOfModule(f);
if (!exports) return [];
const infos: ModuleWithProvidersFunction[] = [];
exports.forEach((declaration, name) => {
if (declaration.node === null) {
return;
}
if (this.isClass(declaration.node)) {
this.getMembersOfClass(declaration.node).forEach(member => {
if (member.isStatic) {
const info = this.parseForModuleWithProviders(
member.name, member.node, member.implementation, declaration.node);
if (info) {
infos.push(info);
}
}
});
} else {
if (isNamedDeclaration(declaration.node)) {
const info =
this.parseForModuleWithProviders(declaration.node.name.text, declaration.node);
if (info) {
infos.push(info);
}
}
}
});
return infos;
}
getEndOfClass(classSymbol: NgccClassSymbol): ts.Node {
let last: ts.Node = classSymbol.declaration.valueDeclaration;
@ -1711,65 +1673,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
}
}
/**
* Parse a function/method node (or its implementation), to see if it returns a
* `ModuleWithProviders` object.
* @param name The name of the function.
* @param node the node to check - this could be a function, a method or a variable declaration.
* @param implementation the actual function expression if `node` is a variable declaration.
* @param container the class that contains the function, if it is a method.
* @returns info about the function if it does return a `ModuleWithProviders` object; `null`
* otherwise.
*/
protected parseForModuleWithProviders(
name: string, node: ts.Node|null, implementation: ts.Node|null = node,
container: ts.Declaration|null = null): ModuleWithProvidersFunction|null {
if (implementation === null ||
(!ts.isFunctionDeclaration(implementation) && !ts.isMethodDeclaration(implementation) &&
!ts.isFunctionExpression(implementation))) {
return null;
}
const declaration = implementation;
const definition = this.getDefinitionOfFunction(declaration);
if (definition === null) {
return null;
}
const body = definition.body;
const lastStatement = body && body[body.length - 1];
const returnExpression =
lastStatement && ts.isReturnStatement(lastStatement) && lastStatement.expression || null;
const ngModuleProperty = returnExpression && ts.isObjectLiteralExpression(returnExpression) &&
returnExpression.properties.find(
prop =>
!!prop.name && ts.isIdentifier(prop.name) && prop.name.text === 'ngModule') ||
null;
if (!ngModuleProperty || !ts.isPropertyAssignment(ngModuleProperty)) {
return null;
}
// The ngModuleValue could be of the form `SomeModule` or `namespace_1.SomeModule`
const ngModuleValue = ngModuleProperty.initializer;
if (!ts.isIdentifier(ngModuleValue) && !ts.isPropertyAccessExpression(ngModuleValue)) {
return null;
}
const ngModuleDeclaration = this.getDeclarationOfExpression(ngModuleValue);
if (!ngModuleDeclaration || ngModuleDeclaration.node === null) {
throw new Error(`Cannot find a declaration for NgModule ${
ngModuleValue.getText()} referenced in "${declaration!.getText()}"`);
}
if (!hasNameIdentifier(ngModuleDeclaration.node)) {
return null;
}
return {
name,
ngModule: ngModuleDeclaration as ConcreteDeclaration<ClassDeclaration>,
declaration,
container
};
}
protected getDeclarationOfExpression(expression: ts.Expression): Declaration|null {
if (ts.isIdentifier(expression)) {
return this.getDeclarationOfIdentifier(expression);

View File

@ -7,7 +7,7 @@
*/
import * as ts from 'typescript';
import {ClassDeclaration, ConcreteDeclaration, Declaration, Decorator, ReflectionHost} from '../../../src/ngtsc/reflection';
import {ClassDeclaration, Declaration, Decorator, ReflectionHost} from '../../../src/ngtsc/reflection';
export const PRE_R3_MARKER = '__PRE_R3__';
export const POST_R3_MARKER = '__POST_R3__';
@ -19,30 +19,6 @@ export function isSwitchableVariableDeclaration(node: ts.Node):
ts.isIdentifier(node.initializer) && node.initializer.text.endsWith(PRE_R3_MARKER);
}
/**
* A structure returned from `getModuleWithProviderInfo` that describes functions
* that return ModuleWithProviders objects.
*/
export interface ModuleWithProvidersFunction {
/**
* The name of the declared function.
*/
name: string;
/**
* The declaration of the function that returns the `ModuleWithProviders` object.
*/
declaration: ts.SignatureDeclaration;
/**
* Declaration of the containing class (if this is a method)
*/
container: ts.Declaration|null;
/**
* The declaration of the class that the `ngModule` property on the `ModuleWithProviders` object
* refers to.
*/
ngModule: ConcreteDeclaration<ClassDeclaration>;
}
/**
* The symbol corresponding to a "class" declaration. I.e. a `ts.Symbol` whose `valueDeclaration` is
* a `ClassDeclaration`.
@ -108,15 +84,6 @@ export interface NgccReflectionHost extends ReflectionHost {
*/
findClassSymbols(sourceFile: ts.SourceFile): NgccClassSymbol[];
/**
* Search the given source file for exported functions and static class methods that return
* ModuleWithProviders objects.
* @param f The source file to search for these functions
* @returns An array of info items about each of the functions that return ModuleWithProviders
* objects.
*/
getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersFunction[];
/**
* Find the last node that is relevant to the specified class.
*

View File

@ -45,7 +45,6 @@ runInEachFileSystem(() => {
let DECORATED_FILES: TestFile[];
let TYPINGS_SRC_FILES: TestFile[];
let TYPINGS_DTS_FILES: TestFile[];
let MODULE_WITH_PROVIDERS_PROGRAM: TestFile[];
// Helpers
const createHost = (bundle: BundleProgram, ngccHost: CommonJsReflectionHost) => {
@ -796,131 +795,6 @@ exports.MissingClass2 = MissingClass2;
{name: _('/ep/typings/shadow-class.d.ts'), contents: `export declare class ShadowClass {}`},
{name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'},
];
MODULE_WITH_PROVIDERS_PROGRAM = [
{
name: _('/src/index.js'),
contents: `
var functions = require('./functions');
var methods = require('./methods');
var outer_aliased_class = require('./outer_aliased_class');
var inner_aliased_class = require('./inner_aliased_class');
`
},
{
name: _('/src/functions.js'),
contents: `
var mod = require('./module');
var SomeService = (function() {
function SomeService() {}
return SomeService;
}());
var InternalModule = (function() {
function InternalModule() {}
return InternalModule;
}());
function aNumber() { return 42; }
function aString() { return 'foo'; }
function emptyObject() { return {}; }
function ngModuleIdentifier() { return { ngModule: InternalModule }; }
function ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
function ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
function onlyProviders() { return { providers: [SomeService] }; }
function ngModuleNumber() { return { ngModule: 42 }; }
function ngModuleString() { return { ngModule: 'foo' }; }
function ngModuleObject() { return { ngModule: { foo: 42 } }; }
function externalNgModule() { return { ngModule: mod.ExternalModule }; }
// NOTE: We do not include the "namespaced" export tests in CommonJS as all CommonJS exports are already namespaced.
// function namespacedExternalNgModule() { return { ngModule: mod.ExternalModule }; }
exports.aNumber = aNumber;
exports.aString = aString;
exports.emptyObject = emptyObject;
exports.ngModuleIdentifier = ngModuleIdentifier;
exports.ngModuleWithEmptyProviders = ngModuleWithEmptyProviders;
exports.ngModuleWithProviders = ngModuleWithProviders;
exports.onlyProviders = onlyProviders;
exports.ngModuleNumber = ngModuleNumber;
exports.ngModuleString = ngModuleString;
exports.ngModuleObject = ngModuleObject;
exports.externalNgModule = externalNgModule;
exports.SomeService = SomeService;
exports.InternalModule = InternalModule;
`
},
{
name: _('/src/methods.js'),
contents: `
var mod = require('./module');
var SomeService = (function() {
function SomeService() {}
return SomeService;
}());
var InternalModule = (function() {
function InternalModule() {}
InternalModule.prototype = {
instanceNgModuleIdentifier: function() { return { ngModule: InternalModule }; },
instanceNgModuleWithEmptyProviders: function() { return { ngModule: InternalModule, providers: [] }; },
instanceNgModuleWithProviders: function() { return { ngModule: InternalModule, providers: [SomeService] }; },
instanceExternalNgModule: function() { return { ngModule: mod.ExternalModule }; },
};
InternalModule.aNumber = function() { return 42; };
InternalModule.aString = function() { return 'foo'; };
InternalModule.emptyObject = function() { return {}; };
InternalModule.ngModuleIdentifier = function() { return { ngModule: InternalModule }; };
InternalModule.ngModuleWithEmptyProviders = function() { return { ngModule: InternalModule, providers: [] }; };
InternalModule.ngModuleWithProviders = function() { return { ngModule: InternalModule, providers: [SomeService] }; };
InternalModule.onlyProviders = function() { return { providers: [SomeService] }; };
InternalModule.ngModuleNumber = function() { return { ngModule: 42 }; };
InternalModule.ngModuleString = function() { return { ngModule: 'foo' }; };
InternalModule.ngModuleObject = function() { return { ngModule: { foo: 42 } }; };
InternalModule.externalNgModule = function() { return { ngModule: mod.ExternalModule }; };
return InternalModule;
}());
exports.SomeService = SomeService;
exports.InternalModule = InternalModule;
`
},
{
name: _('/src/outer_aliased_class.js'),
contents: `
var AliasedModule = AliasedModule_1 = (function() {
function AliasedModule() {}
return AliasedModule;
}());
AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; };
exports.AliasedModule = AliasedModule;
var AliasedModule_1;
`
},
{
name: _('/src/inner_aliased_class.js'),
contents: `
var AliasedModule = (function() {
function AliasedModule() {}
AliasedModule_1 = AliasedModule;
AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; };
var AliasedModule_1;
return AliasedModule;
}());
exports.AliasedModule = AliasedModule;
`
},
{
name: _('/src/module.js'),
contents: `
var ExternalModule = (function() {
function ExternalModule() {}
return ExternalModule;
}());
exports.ExternalModule = ExternalModule;
`
},
];
});
describe('CommonJsReflectionHost', () => {
@ -2850,80 +2724,6 @@ exports.ExternalModule = ExternalModule;
expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass');
});
});
describe('getModuleWithProvidersFunctions', () => {
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(_('/src/index.js'));
const host =
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/functions.js'));
const fns = host.getModuleWithProvidersFunctions(file);
expect(fns.map(fn => [fn.declaration.name!.getText(), fn.ngModule.node.name.text]))
.toEqual([
['ngModuleIdentifier', 'InternalModule'],
['ngModuleWithEmptyProviders', 'InternalModule'],
['ngModuleWithProviders', 'InternalModule'],
['externalNgModule', 'ExternalModule'],
]);
});
it('should find every static method on exported classes that return an object that looks like a ModuleWithProviders object',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(_('/src/index.js'));
const host =
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/methods.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
[
'function() { return { ngModule: InternalModule }; }',
'InternalModule',
],
[
'function() { return { ngModule: InternalModule, providers: [] }; }',
'InternalModule',
],
[
'function() { return { ngModule: InternalModule, providers: [SomeService] }; }',
'InternalModule',
],
[
'function() { return { ngModule: mod.ExternalModule }; }',
'ExternalModule',
],
]);
});
it('should resolve aliased module references to their original declaration (outer alias)',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(_('/src/index.js'));
const host =
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/outer_aliased_class.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
]);
});
// https://github.com/angular/angular/issues/29078
it('should resolve aliased module references to their original declaration (inner alias)',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(_('/src/index.js'));
const host =
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/inner_aliased_class.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
]);
});
});
});
});
});

View File

@ -46,7 +46,6 @@ runInEachFileSystem(() => {
let ARITY_CLASSES: TestFile[];
let TYPINGS_SRC_FILES: TestFile[];
let TYPINGS_DTS_FILES: TestFile[];
let MODULE_WITH_PROVIDERS_PROGRAM: TestFile[];
let NAMESPACED_IMPORT_FILE: TestFile;
let INDEX_SIGNATURE_PROP_FILE: TestFile;
@ -650,77 +649,6 @@ runInEachFileSystem(() => {
{name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'},
];
MODULE_WITH_PROVIDERS_PROGRAM = [
{
name: _('/src/index.js'),
contents: `
import * as functions from './functions';
import * as methods from './methods';
import * as aliased_class from './aliased_class';
`
},
{
name: _('/src/functions.js'),
contents: `
import {ExternalModule} from './module';
import * as mod from './module';
export class SomeService {}
export class InternalModule {}
export function aNumber() { return 42; }
export function aString() { return 'foo'; }
export function emptyObject() { return {}; }
export function ngModuleIdentifier() { return { ngModule: InternalModule }; }
export function ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
export function ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
export function onlyProviders() { return { providers: [SomeService] }; }
export function ngModuleNumber() { return { ngModule: 42 }; }
export function ngModuleString() { return { ngModule: 'foo' }; }
export function ngModuleObject() { return { ngModule: { foo: 42 } }; }
export function externalNgModule() { return { ngModule: ExternalModule }; }
export function namespacedExternalNgModule() { return { ngModule: mod.ExternalModule }; }
`
},
{
name: _('/src/methods.js'),
contents: `
import {ExternalModule} from './module';
import * as mod from './module';
export class SomeService {}
export class InternalModule {
static aNumber() { return 42; }
static aString() { return 'foo'; }
static emptyObject() { return {}; }
static ngModuleIdentifier() { return { ngModule: InternalModule }; }
static ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
static ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
static onlyProviders() { return { providers: [SomeService] }; }
static ngModuleNumber() { return { ngModule: 42 }; }
static ngModuleString() { return { ngModule: 'foo' }; }
static ngModuleObject() { return { ngModule: { foo: 42 } }; }
static externalNgModule() { return { ngModule: ExternalModule }; }
static namespacedExternalNgModule() { return { ngModule: mod.ExternalModule }; }
instanceNgModuleIdentifier() { return { ngModule: InternalModule }; }
instanceNgModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
instanceNgModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
instanceExternalNgModule() { return { ngModule: ExternalModule }; }
instanceNamespacedExternalNgModule() { return { ngModule: mod.ExternalModule }; }
}
`
},
{
name: _('/src/aliased_class.js'),
contents: `
var AliasedModule_1;
let AliasedModule = AliasedModule_1 = class AliasedModule {
static forRoot() { return { ngModule: AliasedModule_1 }; }
};
export { AliasedModule };
`
},
{name: _('/src/module.js'), contents: 'export class ExternalModule {}'},
];
NAMESPACED_IMPORT_FILE = {
name: _('/some_directive.js'),
contents: `
@ -2432,56 +2360,6 @@ runInEachFileSystem(() => {
});
});
describe('getModuleWithProvidersFunctions()', () => {
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
const host =
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/functions.js'));
const fns = host.getModuleWithProvidersFunctions(file);
expect(fns.map(fn => [fn.declaration.name!.getText(), fn.ngModule.node.name.text]))
.toEqual([
['ngModuleIdentifier', 'InternalModule'],
['ngModuleWithEmptyProviders', 'InternalModule'],
['ngModuleWithProviders', 'InternalModule'],
['externalNgModule', 'ExternalModule'],
['namespacedExternalNgModule', 'ExternalModule'],
]);
});
it('should find every static method on exported classes that return an object that looks like a ModuleWithProviders object',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
const host =
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/methods.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.name!.getText(), fn.ngModule.node.name.text]))
.toEqual([
['ngModuleIdentifier', 'InternalModule'],
['ngModuleWithEmptyProviders', 'InternalModule'],
['ngModuleWithProviders', 'InternalModule'],
['externalNgModule', 'ExternalModule'],
['namespacedExternalNgModule', 'ExternalModule'],
]);
});
// https://github.com/angular/angular/issues/29078
it('should resolve aliased module references to their original declaration', () => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/aliased_class.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.name!.getText(), fn.ngModule.node.name.text])).toEqual([
['forRoot', 'AliasedModule'],
]);
});
});
describe('getEndOfClass()', () => {
it('should return the last static property of the class', () => {
const testFile: TestFile = {

View File

@ -47,7 +47,6 @@ runInEachFileSystem(() => {
let UNWANTED_PROTOTYPE_EXPORT_FILE: TestFile;
let TYPINGS_SRC_FILES: TestFile[];
let TYPINGS_DTS_FILES: TestFile[];
let MODULE_WITH_PROVIDERS_PROGRAM: TestFile[];
let NAMESPACED_IMPORT_FILE: TestFile;
// Helpers
@ -774,110 +773,6 @@ runInEachFileSystem(() => {
{name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'},
];
MODULE_WITH_PROVIDERS_PROGRAM = [
{
name: _('/src/index.js'),
contents: `
import * as functions from './functions';
import * as methods from './methods';
import * as outer_aliased_class from './outer_aliased_class';
import * as inner_aliased_class from './inner_aliased_class';
`
},
{
name: _('/src/functions.js'),
contents: `
import {ExternalModule} from './module';
import * as mod from './module';
var SomeService = (function() {
function SomeService() {}
return SomeService;
}());
var InternalModule = (function() {
function InternalModule() {}
return InternalModule;
}());
export function aNumber() { return 42; }
export function aString() { return 'foo'; }
export function emptyObject() { return {}; }
export function ngModuleIdentifier() { return { ngModule: InternalModule }; }
export function ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
export function ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
export function onlyProviders() { return { providers: [SomeService] }; }
export function ngModuleNumber() { return { ngModule: 42 }; }
export function ngModuleString() { return { ngModule: 'foo' }; }
export function ngModuleObject() { return { ngModule: { foo: 42 } }; }
export function externalNgModule() { return { ngModule: ExternalModule }; }
export function namespacedExternalNgModule() { return { ngModule: mod.ExternalModule }; }
export {SomeService, InternalModule};
`
},
{
name: _('/src/methods.js'),
contents: `
import {ExternalModule} from './module';
import * as mod from './module';
var SomeService = (function() {
function SomeService() {}
return SomeService;
}());
var InternalModule = (function() {
function InternalModule() {}
InternalModule.prototype = {
instanceNgModuleIdentifier: function() { return { ngModule: InternalModule }; },
instanceNgModuleWithEmptyProviders: function() { return { ngModule: InternalModule, providers: [] }; },
instanceNgModuleWithProviders: function() { return { ngModule: InternalModule, providers: [SomeService] }; },
instanceExternalNgModule: function() { return { ngModule: ExternalModule }; },
namespacedExternalNgModule = function() { return { ngModule: mod.ExternalModule }; },
};
InternalModule.aNumber = function() { return 42; };
InternalModule.aString = function() { return 'foo'; };
InternalModule.emptyObject = function() { return {}; };
InternalModule.ngModuleIdentifier = function() { return { ngModule: InternalModule }; };
InternalModule.ngModuleWithEmptyProviders = function() { return { ngModule: InternalModule, providers: [] }; };
InternalModule.ngModuleWithProviders = function() { return { ngModule: InternalModule, providers: [SomeService] }; };
InternalModule.onlyProviders = function() { return { providers: [SomeService] }; };
InternalModule.ngModuleNumber = function() { return { ngModule: 42 }; };
InternalModule.ngModuleString = function() { return { ngModule: 'foo' }; };
InternalModule.ngModuleObject = function() { return { ngModule: { foo: 42 } }; };
InternalModule.externalNgModule = function() { return { ngModule: ExternalModule }; };
InternalModule.namespacedExternalNgModule = function() { return { ngModule: mod.ExternalModule }; };
return InternalModule;
}());
export {SomeService, InternalModule};
`
},
{
name: _('/src/outer_aliased_class.js'),
contents: `
var AliasedModule = AliasedModule_1 = (function() {
function AliasedModule() {}
return AliasedModule;
}());
AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; };
export { AliasedModule };
var AliasedModule_1;
`
},
{
name: _('/src/inner_aliased_class.js'),
contents: `
var AliasedModule = (function() {
function AliasedModule() {}
AliasedModule_1 = AliasedModule;
AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; };
var AliasedModule_1;
return AliasedModule;
}());
export { AliasedModule };
`
},
{name: _('/src/module.js'), contents: 'export class ExternalModule {}'},
];
NAMESPACED_IMPORT_FILE = {
name: _('/some_directive.js'),
contents: `
@ -2878,81 +2773,6 @@ runInEachFileSystem(() => {
});
});
describe('getModuleWithProvidersFunctions', () => {
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(_('/src/index.js'));
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/functions.js'));
const fns = host.getModuleWithProvidersFunctions(file);
expect(fns.map(fn => [fn.declaration.name!.getText(), fn.ngModule.node.name.text]))
.toEqual([
['ngModuleIdentifier', 'InternalModule'],
['ngModuleWithEmptyProviders', 'InternalModule'],
['ngModuleWithProviders', 'InternalModule'],
['externalNgModule', 'ExternalModule'],
['namespacedExternalNgModule', 'ExternalModule'],
]);
});
it('should find every static method on exported classes that return an object that looks like a ModuleWithProviders object',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(_('/src/index.js'));
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/methods.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
[
'function() { return { ngModule: InternalModule }; }',
'InternalModule',
],
[
'function() { return { ngModule: InternalModule, providers: [] }; }',
'InternalModule',
],
[
'function() { return { ngModule: InternalModule, providers: [SomeService] }; }',
'InternalModule',
],
[
'function() { return { ngModule: ExternalModule }; }',
'ExternalModule',
],
[
'function() { return { ngModule: mod.ExternalModule }; }',
'ExternalModule',
],
]);
});
it('should resolve aliased module references to their original declaration (outer alias)',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(_('/src/index.js'));
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/outer_aliased_class.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
]);
});
// https://github.com/angular/angular/issues/29078
it('should resolve aliased module references to their original declaration (inner alias)',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(_('/src/index.js'));
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/inner_aliased_class.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
]);
});
});
describe('getEndOfClass()', () => {
it('should return the last static property of the class', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);

View File

@ -46,7 +46,6 @@ runInEachFileSystem(() => {
let DECORATED_FILES: TestFile[];
let TYPINGS_SRC_FILES: TestFile[];
let TYPINGS_DTS_FILES: TestFile[];
let MODULE_WITH_PROVIDERS_PROGRAM: TestFile[];
// Helpers
const createHost = (bundle: BundleProgram, ngccHost: UmdReflectionHost) => {
@ -932,161 +931,6 @@ runInEachFileSystem(() => {
{name: _('/ep/typings/shadow-class.d.ts'), contents: `export declare class ShadowClass {}`},
{name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'},
];
MODULE_WITH_PROVIDERS_PROGRAM = [
{
name: _('/src/index.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./functions'), require('./methods'), require('./outer_aliased_class'), require('./inner_aliased_class')) :
typeof define === 'function' && define.amd ? define('index', ['exports', './functions', './methods', './outer_aliased_class', './inner_aliased_class'], factory) :
(factory(global.index,global.functions,global.methods,global.outer_aliased_class,global.inner_aliased_class));
}(this, (function (exports,functions,methods,outer_aliased_class,inner_aliased_class) { 'use strict';
}))));
`,
},
{
name: _('/src/functions.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./module')) :
typeof define === 'function' && define.amd ? define('functions', ['exports', './module'], factory) :
(factory(global.functions,global.module));
}(this, (function (exports,module) { 'use strict';
var SomeService = (function() {
function SomeService() {}
return SomeService;
}());
var InternalModule = (function() {
function InternalModule() {}
return InternalModule;
}());
function aNumber() { return 42; }
function aString() { return 'foo'; }
function emptyObject() { return {}; }
function ngModuleIdentifier() { return { ngModule: InternalModule }; }
function ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
function ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
function onlyProviders() { return { providers: [SomeService] }; }
function ngModuleNumber() { return { ngModule: 42 }; }
function ngModuleString() { return { ngModule: 'foo' }; }
function ngModuleObject() { return { ngModule: { foo: 42 } }; }
function externalNgModule() { return { ngModule: module.ExternalModule }; }
// NOTE: We do not include the "namespaced" export tests in UMD as all UMD exports are already namespaced.
// function namespacedExternalNgModule() { return { ngModule: mod.ExternalModule }; }
exports.aNumber = aNumber;
exports.aString = aString;
exports.emptyObject = emptyObject;
exports.ngModuleIdentifier = ngModuleIdentifier;
exports.ngModuleWithEmptyProviders = ngModuleWithEmptyProviders;
exports.ngModuleWithProviders = ngModuleWithProviders;
exports.onlyProviders = onlyProviders;
exports.ngModuleNumber = ngModuleNumber;
exports.ngModuleString = ngModuleString;
exports.ngModuleObject = ngModuleObject;
exports.externalNgModule = externalNgModule;
exports.SomeService = SomeService;
exports.InternalModule = InternalModule;
})));
`
},
{
name: _('/src/methods.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./module')) :
typeof define === 'function' && define.amd ? define('methods', ['exports', './module'], factory) :
(factory(global.methods,global.module));
}(this, (function (exports,module) { 'use strict';
var SomeService = (function() {
function SomeService() {}
return SomeService;
}());
var InternalModule = (function() {
function InternalModule() {}
InternalModule.prototype = {
instanceNgModuleIdentifier: function() { return { ngModule: InternalModule }; },
instanceNgModuleWithEmptyProviders: function() { return { ngModule: InternalModule, providers: [] }; },
instanceNgModuleWithProviders: function() { return { ngModule: InternalModule, providers: [SomeService] }; },
instanceExternalNgModule: function() { return { ngModule: module.ExternalModule }; },
};
InternalModule.aNumber = function() { return 42; };
InternalModule.aString = function() { return 'foo'; };
InternalModule.emptyObject = function() { return {}; };
InternalModule.ngModuleIdentifier = function() { return { ngModule: InternalModule }; };
InternalModule.ngModuleWithEmptyProviders = function() { return { ngModule: InternalModule, providers: [] }; };
InternalModule.ngModuleWithProviders = function() { return { ngModule: InternalModule, providers: [SomeService] }; };
InternalModule.onlyProviders = function() { return { providers: [SomeService] }; };
InternalModule.ngModuleNumber = function() { return { ngModule: 42 }; };
InternalModule.ngModuleString = function() { return { ngModule: 'foo' }; };
InternalModule.ngModuleObject = function() { return { ngModule: { foo: 42 } }; };
InternalModule.externalNgModule = function() { return { ngModule: module.ExternalModule }; };
return InternalModule;
}());
exports.SomeService = SomeService;
exports.InternalModule = InternalModule;
})));
`
},
{
name: _('/src/outer_aliased_class.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define('outer_aliased_class', ['exports'], factory) :
(factory(global.outer_aliased_class));
}(this, (function (exports,module) { 'use strict';
var AliasedModule = AliasedModule_1 = (function() {
function AliasedModule() {}
return AliasedModule;
}());
AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; };
exports.AliasedModule = AliasedModule;
var AliasedModule_1;
})));
`
},
{
name: _('/src/inner_aliased_class.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define('inner_aliased_class', ['exports'], factory) :
(factory(global.inner_aliased_class));
}(this, (function (exports,module) { 'use strict';
var AliasedModule = (function() {
function AliasedModule() {}
AliasedModule_1 = AliasedModule;
AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; };
var AliasedModule_1;
return AliasedModule;
}());
exports.AliasedModule = AliasedModule;
})));
`
},
{
name: _('/src/module.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define('module', ['exports'], factory) :
(factory(global.module));
}(this, (function (exports,module) { 'use strict';
var ExternalModule = (function() {
function ExternalModule() {}
return ExternalModule;
}());
exports.ExternalModule = ExternalModule;
})));
`
},
];
});
describe('getDecoratorsOfDeclaration()', () => {
@ -3065,75 +2909,5 @@ runInEachFileSystem(() => {
expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass');
});
});
describe('getModuleWithProvidersFunctions', () => {
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/functions.js'));
const fns = host.getModuleWithProvidersFunctions(file);
expect(fns.map(fn => [fn.declaration.name!.getText(), fn.ngModule.node.name.text]))
.toEqual([
['ngModuleIdentifier', 'InternalModule'],
['ngModuleWithEmptyProviders', 'InternalModule'],
['ngModuleWithProviders', 'InternalModule'],
['externalNgModule', 'ExternalModule'],
]);
});
it('should find every static method on exported classes that return an object that looks like a ModuleWithProviders object',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/methods.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
[
'function() { return { ngModule: InternalModule }; }',
'InternalModule',
],
[
'function() { return { ngModule: InternalModule, providers: [] }; }',
'InternalModule',
],
[
'function() { return { ngModule: InternalModule, providers: [SomeService] }; }',
'InternalModule',
],
[
'function() { return { ngModule: module.ExternalModule }; }',
'ExternalModule',
],
]);
});
it('should resolve aliased module references to their original declaration (outer alias)',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/outer_aliased_class.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
]);
});
// https://github.com/angular/angular/issues/29078
it('should resolve aliased module references to their original declaration (inner alias)',
() => {
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const file = getSourceFileOrError(bundle.program, _('/src/inner_aliased_class.js'));
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
]);
});
});
});
});