fix(ngcc): support ModuleWithProviders functions that delegate (#36948)
In #36892 the `ModuleWithProviders` type parameter becomes required. This exposes a bug in ngcc, where it can only handle functions that have a specific form: ``` function forRoot() { return { ... }; } ``` In other words, it only accepts functions that return an object literal. In some libraries, the function instead returns a call to another function. For example in `angular-in-memory-web-api`: ``` InMemoryWebApiModule.forFeature = function (dbCreator, options) { return InMemoryWebApiModule_1.forRoot(dbCreator, options); }; ``` This commit changes the parsing of such functions to use the `PartialEvaluator`, which can evaluate these more complex function bodies. PR Close #36948
This commit is contained in:
parent
e010f2ca54
commit
fafa50d97f
|
@ -9,53 +9,67 @@ import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ReferencesRegistry} from '../../../src/ngtsc/annotations';
|
import {ReferencesRegistry} from '../../../src/ngtsc/annotations';
|
||||||
import {Reference} from '../../../src/ngtsc/imports';
|
import {Reference} from '../../../src/ngtsc/imports';
|
||||||
import {ClassDeclaration, ConcreteDeclaration} from '../../../src/ngtsc/reflection';
|
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
||||||
|
import {ClassDeclaration, isNamedClassDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
||||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||||
import {hasNameIdentifier, isDefined} from '../utils';
|
import {hasNameIdentifier, isDefined} from '../utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A structure returned from `getModuleWithProvidersFunctions()` that describes functions
|
||||||
|
* that return ModuleWithProviders objects.
|
||||||
|
*/
|
||||||
export interface ModuleWithProvidersInfo {
|
export interface ModuleWithProvidersInfo {
|
||||||
/**
|
/**
|
||||||
* The declaration (in the .d.ts file) of the function that returns
|
* The name of the declared function.
|
||||||
* a `ModuleWithProviders object, but has a signature that needs
|
|
||||||
* a type parameter adding.
|
|
||||||
*/
|
*/
|
||||||
declaration: ts.MethodDeclaration|ts.FunctionDeclaration;
|
name: string;
|
||||||
/**
|
/**
|
||||||
* The NgModule class declaration (in the .d.ts file) to add as a type parameter.
|
* The declaration of the function that returns the `ModuleWithProviders` object.
|
||||||
*/
|
*/
|
||||||
ngModule: ConcreteDeclaration<ClassDeclaration>;
|
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: Reference<ClassDeclaration>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModuleWithProvidersAnalyses = Map<ts.SourceFile, ModuleWithProvidersInfo[]>;
|
export type ModuleWithProvidersAnalyses = Map<ts.SourceFile, ModuleWithProvidersInfo[]>;
|
||||||
export const ModuleWithProvidersAnalyses = Map;
|
export const ModuleWithProvidersAnalyses = Map;
|
||||||
|
|
||||||
export class ModuleWithProvidersAnalyzer {
|
export class ModuleWithProvidersAnalyzer {
|
||||||
|
private evaluator = new PartialEvaluator(this.host, this.typeChecker, null);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private host: NgccReflectionHost, private referencesRegistry: ReferencesRegistry,
|
private host: NgccReflectionHost, private typeChecker: ts.TypeChecker,
|
||||||
private processDts: boolean) {}
|
private referencesRegistry: ReferencesRegistry, private processDts: boolean) {}
|
||||||
|
|
||||||
analyzeProgram(program: ts.Program): ModuleWithProvidersAnalyses {
|
analyzeProgram(program: ts.Program): ModuleWithProvidersAnalyses {
|
||||||
const analyses = new ModuleWithProvidersAnalyses();
|
const analyses: ModuleWithProvidersAnalyses = new ModuleWithProvidersAnalyses();
|
||||||
const rootFiles = this.getRootFiles(program);
|
const rootFiles = this.getRootFiles(program);
|
||||||
rootFiles.forEach(f => {
|
rootFiles.forEach(f => {
|
||||||
const fns = this.getModuleWithProvidersFunctions(f);
|
const fns = this.getModuleWithProvidersFunctions(f);
|
||||||
fns && fns.forEach(fn => {
|
fns && fns.forEach(fn => {
|
||||||
if (fn.ngModule.viaModule === null) {
|
if (fn.ngModule.bestGuessOwningModule === null) {
|
||||||
// Record the usage of an internal module as it needs to become an exported symbol
|
// Record the usage of an internal module as it needs to become an exported symbol
|
||||||
this.referencesRegistry.add(fn.ngModule.node, new Reference(fn.ngModule.node));
|
this.referencesRegistry.add(fn.ngModule.node, new Reference(fn.ngModule.node));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only when processing the dts files do we need to determine which declaration to update.
|
// Only when processing the dts files do we need to determine which declaration to update.
|
||||||
if (this.processDts) {
|
if (this.processDts) {
|
||||||
const dtsFn = this.getDtsDeclarationForFunction(fn);
|
const dtsFn = this.getDtsModuleWithProvidersFunction(fn);
|
||||||
const typeParam = dtsFn.type && ts.isTypeReferenceNode(dtsFn.type) &&
|
const dtsFnType = dtsFn.declaration.type;
|
||||||
dtsFn.type.typeArguments && dtsFn.type.typeArguments[0] ||
|
const typeParam = dtsFnType && ts.isTypeReferenceNode(dtsFnType) &&
|
||||||
|
dtsFnType.typeArguments && dtsFnType.typeArguments[0] ||
|
||||||
null;
|
null;
|
||||||
if (!typeParam || isAnyKeyword(typeParam)) {
|
if (!typeParam || isAnyKeyword(typeParam)) {
|
||||||
const ngModule = this.resolveNgModuleReference(fn);
|
const dtsFile = dtsFn.declaration.getSourceFile();
|
||||||
const dtsFile = dtsFn.getSourceFile();
|
const analysis = analyses.has(dtsFile) ? analyses.get(dtsFile)! : [];
|
||||||
const analysis = analyses.has(dtsFile) ? analyses.get(dtsFile) : [];
|
analysis.push(dtsFn);
|
||||||
analysis.push({declaration: dtsFn, ngModule});
|
|
||||||
analyses.set(dtsFile, analysis);
|
analyses.set(dtsFile, analysis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,11 +82,11 @@ export class ModuleWithProvidersAnalyzer {
|
||||||
return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined);
|
return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersFunction[] {
|
private getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersInfo[] {
|
||||||
const exports = this.host.getExportsOfModule(f);
|
const exports = this.host.getExportsOfModule(f);
|
||||||
if (!exports) return [];
|
if (!exports) return [];
|
||||||
const infos: ModuleWithProvidersFunction[] = [];
|
const infos: ModuleWithProvidersInfo[] = [];
|
||||||
exports.forEach((declaration, name) => {
|
exports.forEach((declaration) => {
|
||||||
if (declaration.node === null) {
|
if (declaration.node === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -111,7 +125,7 @@ export class ModuleWithProvidersAnalyzer {
|
||||||
*/
|
*/
|
||||||
private parseForModuleWithProviders(
|
private parseForModuleWithProviders(
|
||||||
name: string, node: ts.Node|null, implementation: ts.Node|null = node,
|
name: string, node: ts.Node|null, implementation: ts.Node|null = node,
|
||||||
container: ts.Declaration|null = null): ModuleWithProvidersFunction|null {
|
container: ts.Declaration|null = null): ModuleWithProvidersInfo|null {
|
||||||
if (implementation === null ||
|
if (implementation === null ||
|
||||||
(!ts.isFunctionDeclaration(implementation) && !ts.isMethodDeclaration(implementation) &&
|
(!ts.isFunctionDeclaration(implementation) && !ts.isMethodDeclaration(implementation) &&
|
||||||
!ts.isFunctionExpression(implementation))) {
|
!ts.isFunctionExpression(implementation))) {
|
||||||
|
@ -122,47 +136,40 @@ export class ModuleWithProvidersAnalyzer {
|
||||||
if (definition === null) {
|
if (definition === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = definition.body;
|
const body = definition.body;
|
||||||
const lastStatement = body && body[body.length - 1];
|
if (body === null || body.length === 0) {
|
||||||
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ngModuleValue could be of the form `SomeModule` or `namespace_1.SomeModule`
|
// Get hold of the return statement expression for the function
|
||||||
let ngModuleValue = ngModuleProperty.initializer;
|
const lastStatement = body[body.length - 1];
|
||||||
if (ts.isPropertyAccessExpression(ngModuleValue)) {
|
if (!ts.isReturnStatement(lastStatement) || lastStatement.expression === undefined) {
|
||||||
ngModuleValue = ngModuleValue.expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ts.isIdentifier(ngModuleValue)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ngModuleDeclaration = this.host.getDeclarationOfIdentifier(ngModuleValue);
|
// Evaluate this expression and extract the `ngModule` reference
|
||||||
if (!ngModuleDeclaration || ngModuleDeclaration.node === null) {
|
const result = this.evaluator.evaluate(lastStatement.expression);
|
||||||
throw new Error(`Cannot find a declaration for NgModule ${
|
if (!(result instanceof Map) || !result.has('ngModule')) {
|
||||||
ngModuleValue.getText()} referenced in "${declaration!.getText()}"`);
|
|
||||||
}
|
|
||||||
if (!hasNameIdentifier(ngModuleDeclaration.node)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
name,
|
const ngModuleRef = result.get('ngModule')!;
|
||||||
ngModule: ngModuleDeclaration as ConcreteDeclaration<ClassDeclaration>,
|
if (!(ngModuleRef instanceof Reference)) {
|
||||||
declaration,
|
return null;
|
||||||
container
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDtsDeclarationForFunction(fn: ModuleWithProvidersFunction) {
|
if (!isNamedClassDeclaration(ngModuleRef.node) &&
|
||||||
|
!isNamedVariableDeclaration(ngModuleRef.node)) {
|
||||||
|
throw new Error(`The identity given by ${ngModuleRef.debugName} referenced in "${
|
||||||
|
declaration!.getText()}" doesn't appear to be a "class" declaration.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ngModule = ngModuleRef as Reference<ClassDeclaration>;
|
||||||
|
return {name, ngModule, declaration, container};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDtsModuleWithProvidersFunction(fn: ModuleWithProvidersInfo): ModuleWithProvidersInfo {
|
||||||
let dtsFn: ts.Declaration|null = null;
|
let dtsFn: ts.Declaration|null = null;
|
||||||
const containerClass = fn.container && this.host.getClassSymbol(fn.container);
|
const containerClass = fn.container && this.host.getClassSymbol(fn.container);
|
||||||
if (containerClass) {
|
if (containerClass) {
|
||||||
|
@ -183,15 +190,16 @@ export class ModuleWithProvidersAnalyzer {
|
||||||
throw new Error(`Matching type declaration for ${
|
throw new Error(`Matching type declaration for ${
|
||||||
fn.declaration.getText()} is not a function: ${dtsFn.getText()}`);
|
fn.declaration.getText()} is not a function: ${dtsFn.getText()}`);
|
||||||
}
|
}
|
||||||
return dtsFn;
|
const container = containerClass ? containerClass.declaration.valueDeclaration : null;
|
||||||
|
const ngModule = this.resolveNgModuleReference(fn);
|
||||||
|
return {name: fn.name, container, declaration: dtsFn, ngModule};
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveNgModuleReference(fn: ModuleWithProvidersFunction):
|
private resolveNgModuleReference(fn: ModuleWithProvidersInfo): Reference<ClassDeclaration> {
|
||||||
ConcreteDeclaration<ClassDeclaration> {
|
|
||||||
const ngModule = fn.ngModule;
|
const ngModule = fn.ngModule;
|
||||||
|
|
||||||
// For external module references, use the declaration as is.
|
// For external module references, use the declaration as is.
|
||||||
if (ngModule.viaModule !== null) {
|
if (ngModule.bestGuessOwningModule !== null) {
|
||||||
return ngModule;
|
return ngModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,14 +210,13 @@ export class ModuleWithProvidersAnalyzer {
|
||||||
throw new Error(`No typings declaration can be found for the referenced NgModule class in ${
|
throw new Error(`No typings declaration can be found for the referenced NgModule class in ${
|
||||||
fn.declaration.getText()}.`);
|
fn.declaration.getText()}.`);
|
||||||
}
|
}
|
||||||
if (!ts.isClassDeclaration(dtsNgModule) || !hasNameIdentifier(dtsNgModule)) {
|
if (!isNamedClassDeclaration(dtsNgModule)) {
|
||||||
throw new Error(`The referenced NgModule in ${
|
throw new Error(`The referenced NgModule in ${
|
||||||
fn.declaration
|
fn.declaration
|
||||||
.getText()} is not a named class declaration in the typings program; instead we get ${
|
.getText()} is not a named class declaration in the typings program; instead we get ${
|
||||||
dtsNgModule.getText()}`);
|
dtsNgModule.getText()}`);
|
||||||
}
|
}
|
||||||
|
return new Reference(dtsNgModule, null);
|
||||||
return {node: dtsNgModule, known: null, viaModule: null, identity: null};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,27 +229,3 @@ function isFunctionOrMethod(declaration: ts.Declaration): declaration is ts.Func
|
||||||
function isAnyKeyword(typeParam: ts.TypeNode): typeParam is ts.KeywordTypeNode {
|
function isAnyKeyword(typeParam: ts.TypeNode): typeParam is ts.KeywordTypeNode {
|
||||||
return typeParam.kind === ts.SyntaxKind.AnyKeyword;
|
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>;
|
|
||||||
}
|
|
||||||
|
|
|
@ -156,8 +156,9 @@ export class Transformer {
|
||||||
diagnostic => diagnostics.push(diagnostic), this.tsConfig);
|
diagnostic => diagnostics.push(diagnostic), this.tsConfig);
|
||||||
const decorationAnalyses = decorationAnalyzer.analyzeProgram();
|
const decorationAnalyses = decorationAnalyzer.analyzeProgram();
|
||||||
|
|
||||||
const moduleWithProvidersAnalyzer =
|
const moduleWithProvidersAnalyzer = new ModuleWithProvidersAnalyzer(
|
||||||
new ModuleWithProvidersAnalyzer(reflectionHost, referencesRegistry, bundle.dts !== null);
|
reflectionHost, bundle.src.program.getTypeChecker(), referencesRegistry,
|
||||||
|
bundle.dts !== null);
|
||||||
const moduleWithProvidersAnalyses = moduleWithProvidersAnalyzer &&
|
const moduleWithProvidersAnalyses = moduleWithProvidersAnalyzer &&
|
||||||
moduleWithProvidersAnalyzer.analyzeProgram(bundle.src.program);
|
moduleWithProvidersAnalyzer.analyzeProgram(bundle.src.program);
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,7 @@ export class EsmRenderingFormatter implements RenderingFormatter {
|
||||||
const ngModuleName = info.ngModule.node.name.text;
|
const ngModuleName = info.ngModule.node.name.text;
|
||||||
const declarationFile = absoluteFromSourceFile(info.declaration.getSourceFile());
|
const declarationFile = absoluteFromSourceFile(info.declaration.getSourceFile());
|
||||||
const ngModuleFile = absoluteFromSourceFile(info.ngModule.node.getSourceFile());
|
const ngModuleFile = absoluteFromSourceFile(info.ngModule.node.getSourceFile());
|
||||||
const importPath = info.ngModule.viaModule ||
|
const importPath = info.ngModule.ownedByModuleGuess ||
|
||||||
(declarationFile !== ngModuleFile ?
|
(declarationFile !== ngModuleFile ?
|
||||||
stripExtension(`./${relative(dirname(declarationFile), ngModuleFile)}`) :
|
stripExtension(`./${relative(dirname(declarationFile), ngModuleFile)}`) :
|
||||||
null);
|
null);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import * as ts from 'typescript';
|
||||||
|
|
||||||
import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError} from '../../../src/ngtsc/file_system';
|
import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError} from '../../../src/ngtsc/file_system';
|
||||||
import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing';
|
import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing';
|
||||||
|
import {isNamedClassDeclaration} from '../../../src/ngtsc/reflection';
|
||||||
import {getDeclaration} from '../../../src/ngtsc/testing';
|
import {getDeclaration} from '../../../src/ngtsc/testing';
|
||||||
import {loadTestFiles} from '../../../test/helpers';
|
import {loadTestFiles} from '../../../test/helpers';
|
||||||
import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
|
import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
|
||||||
|
@ -39,6 +40,7 @@ runInEachFileSystem(() => {
|
||||||
export * from './implicit';
|
export * from './implicit';
|
||||||
export * from './no-providers';
|
export * from './no-providers';
|
||||||
export * from './module';
|
export * from './module';
|
||||||
|
export * from './delegated';
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -214,6 +216,89 @@ runInEachFileSystem(() => {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/src/delegated.js'),
|
||||||
|
contents: `
|
||||||
|
import * as implicit from './implicit';
|
||||||
|
import * as explicit from './explicit';
|
||||||
|
import * as anyModule from './any';
|
||||||
|
|
||||||
|
export function delegatedImplicitInternalFunction() {
|
||||||
|
return implicit.implicitInternalFunction();
|
||||||
|
}
|
||||||
|
export function delegatedImplicitExternalFunction() {
|
||||||
|
return implicit.implicitExternalFunction();
|
||||||
|
}
|
||||||
|
export function delegatedImplicitLibraryFunction() {
|
||||||
|
return implicit.implicitLibraryFunction();
|
||||||
|
}
|
||||||
|
export class DelegatedImplicitClass {
|
||||||
|
static implicitInternalMethod() {
|
||||||
|
return implicit.ImplicitClass.implicitInternalMethod();
|
||||||
|
}
|
||||||
|
static implicitExternalMethod() {
|
||||||
|
return implicit.ImplicitClass.implicitExternalMethod();
|
||||||
|
}
|
||||||
|
static implicitLibraryMethod() {
|
||||||
|
return implicit.ImplicitClass.implicitLibraryMethod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delegatedExplicitInternalFunction() {
|
||||||
|
return explicit.explicitInternalFunction();
|
||||||
|
}
|
||||||
|
export function delegatedExplicitExternalFunction() {
|
||||||
|
return explicit.explicitExternalFunction();
|
||||||
|
}
|
||||||
|
export function delegatedExplicitLibraryFunction() {
|
||||||
|
return explicit.explicitLibraryFunction();
|
||||||
|
}
|
||||||
|
export class DelegatedExplicitClass {
|
||||||
|
static explicitInternalMethod() {
|
||||||
|
return explicit.ExplicitClass.explicitInternalMethod();
|
||||||
|
}
|
||||||
|
static explicitExternalMethod() {
|
||||||
|
return explicit.ExplicitClass.explicitExternalMethod();
|
||||||
|
}
|
||||||
|
static explicitLibraryMethod() {
|
||||||
|
return explicit.ExplicitClass.explicitLibraryMethod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delegatedAnyInternalFunction() {
|
||||||
|
return anyModule.anyInternalFunction();
|
||||||
|
}
|
||||||
|
export function delegatedAnyExternalFunction() {
|
||||||
|
return anyModule.anyExternalFunction();
|
||||||
|
}
|
||||||
|
export function delegatedAnyLibraryFunction() {
|
||||||
|
return anyModule.anyLibraryFunction();
|
||||||
|
}
|
||||||
|
export class DelegatedAnyClass {
|
||||||
|
static anyInternalMethod() {
|
||||||
|
return anyModule.AnyClass.anyInternalMethod();
|
||||||
|
}
|
||||||
|
static anyExternalMethod() {
|
||||||
|
return anyModule.AnyClass.anyExternalMethod();
|
||||||
|
}
|
||||||
|
static anyLibraryMethod() {
|
||||||
|
return anyModule.AnyClass.anyLibraryMethod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withParams(a: string) {
|
||||||
|
return explicit.explicitInternalFunction();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withOptionalParams(a: string = 'default') {
|
||||||
|
return explicit.explicitInternalFunction();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doubleDelegation(a: string = 'default') {
|
||||||
|
return withParams(a);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: _('/node_modules/test-package/src/module.js'),
|
name: _('/node_modules/test-package/src/module.js'),
|
||||||
contents: `
|
contents: `
|
||||||
|
@ -234,6 +319,7 @@ runInEachFileSystem(() => {
|
||||||
export * from './implicit';
|
export * from './implicit';
|
||||||
export * from './no-providers';
|
export * from './no-providers';
|
||||||
export * from './module';
|
export * from './module';
|
||||||
|
export * from './delegated';
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -284,6 +370,47 @@ runInEachFileSystem(() => {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/typings/delegated.d.ts'),
|
||||||
|
contents: `
|
||||||
|
// None of the ModuleWithProviders functions/methods in this file provide the
|
||||||
|
// necessary type parameters and so need to be processed by the analyzer.
|
||||||
|
// Each group of functions/methods here delegate their return values to other
|
||||||
|
// functions/methods that either explicitly provide a type parameter or need
|
||||||
|
// processing by the analyzer themselves.
|
||||||
|
|
||||||
|
export declare function delegatedImplicitInternalFunction(): ModuleWithProviders;
|
||||||
|
export declare function delegatedImplicitExternalFunction(): ModuleWithProviders;
|
||||||
|
export declare function delegatedImplicitLibraryFunction(): ModuleWithProviders;
|
||||||
|
export declare class DelegatedImplicitClass {
|
||||||
|
static implicitInternalMethod(): ModuleWithProviders;
|
||||||
|
static implicitExternalMethod(): ModuleWithProviders;
|
||||||
|
static implicitLibraryMethod(): ModuleWithProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare function delegatedExplicitInternalFunction(): ModuleWithProviders;
|
||||||
|
export declare function delegatedExplicitExternalFunction(): ModuleWithProviders;
|
||||||
|
export declare function delegatedExplicitLibraryFunction(): ModuleWithProviders;
|
||||||
|
export declare class DelegatedExplicitClass {
|
||||||
|
static explicitInternalMethod(): ModuleWithProviders;
|
||||||
|
static explicitExternalMethod(): ModuleWithProviders;
|
||||||
|
static explicitLibraryMethod(): ModuleWithProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare function delegatedAnyInternalFunction(): ModuleWithProviders;
|
||||||
|
export declare function delegatedAnyExternalFunction(): ModuleWithProviders;
|
||||||
|
export declare function delegatedAnyLibraryFunction(): ModuleWithProviders;
|
||||||
|
export declare class DelegatedAnyClass {
|
||||||
|
static anyInternalMethod(): ModuleWithProviders;
|
||||||
|
static anyExternalMethod(): ModuleWithProviders;
|
||||||
|
static anyLibraryMethod(): ModuleWithProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare function withParams(a: string): ModuleWithProviders;
|
||||||
|
export declare function withOptionalParams(a: string = 'default'): ModuleWithProviders;
|
||||||
|
export declare function doubleDelegation(a: string = 'default'): ModuleWithProviders;
|
||||||
|
`
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: _('/node_modules/test-package/typings/no-providers.d.ts'),
|
name: _('/node_modules/test-package/typings/no-providers.d.ts'),
|
||||||
contents: `
|
contents: `
|
||||||
|
@ -338,7 +465,8 @@ runInEachFileSystem(() => {
|
||||||
referencesRegistry = new NgccReferencesRegistry(host);
|
referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
|
||||||
const processDts = true;
|
const processDts = true;
|
||||||
const analyzer = new ModuleWithProvidersAnalyzer(host, referencesRegistry, processDts);
|
const analyzer = new ModuleWithProvidersAnalyzer(
|
||||||
|
host, bundle.src.program.getTypeChecker(), referencesRegistry, processDts);
|
||||||
analyses = analyzer.analyzeProgram(program);
|
analyses = analyzer.analyzeProgram(program);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -354,9 +482,11 @@ runInEachFileSystem(() => {
|
||||||
expect(anyAnalysis).toContain(['anyInternalFunction', 'AnyInternalModule', null]);
|
expect(anyAnalysis).toContain(['anyInternalFunction', 'AnyInternalModule', null]);
|
||||||
expect(anyAnalysis).toContain(['anyExternalFunction', 'ExternalModule', null]);
|
expect(anyAnalysis).toContain(['anyExternalFunction', 'ExternalModule', null]);
|
||||||
expect(anyAnalysis).toContain(['anyLibraryFunction', 'LibraryModule', 'some-library']);
|
expect(anyAnalysis).toContain(['anyLibraryFunction', 'LibraryModule', 'some-library']);
|
||||||
expect(anyAnalysis).toContain(['anyInternalMethod', 'AnyInternalModule', null]);
|
expect(anyAnalysis).toContain(['AnyClass.anyInternalMethod', 'AnyInternalModule', null]);
|
||||||
expect(anyAnalysis).toContain(['anyExternalMethod', 'ExternalModule', null]);
|
expect(anyAnalysis).toContain(['AnyClass.anyExternalMethod', 'ExternalModule', null]);
|
||||||
expect(anyAnalysis).toContain(['anyLibraryMethod', 'LibraryModule', 'some-library']);
|
expect(anyAnalysis).toContain([
|
||||||
|
'AnyClass.anyLibraryMethod', 'LibraryModule', 'some-library'
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should track internal module references in the references registry', () => {
|
it('should track internal module references in the references registry', () => {
|
||||||
|
@ -377,9 +507,82 @@ runInEachFileSystem(() => {
|
||||||
expect(anyAnalysis).toContain(['implicitInternalFunction', 'ImplicitInternalModule', null]);
|
expect(anyAnalysis).toContain(['implicitInternalFunction', 'ImplicitInternalModule', null]);
|
||||||
expect(anyAnalysis).toContain(['implicitExternalFunction', 'ExternalModule', null]);
|
expect(anyAnalysis).toContain(['implicitExternalFunction', 'ExternalModule', null]);
|
||||||
expect(anyAnalysis).toContain(['implicitLibraryFunction', 'LibraryModule', 'some-library']);
|
expect(anyAnalysis).toContain(['implicitLibraryFunction', 'LibraryModule', 'some-library']);
|
||||||
expect(anyAnalysis).toContain(['implicitInternalMethod', 'ImplicitInternalModule', null]);
|
expect(anyAnalysis).toContain([
|
||||||
expect(anyAnalysis).toContain(['implicitExternalMethod', 'ExternalModule', null]);
|
'ImplicitClass.implicitInternalMethod', 'ImplicitInternalModule', null
|
||||||
expect(anyAnalysis).toContain(['implicitLibraryMethod', 'LibraryModule', 'some-library']);
|
]);
|
||||||
|
expect(anyAnalysis).toContain([
|
||||||
|
'ImplicitClass.implicitExternalMethod', 'ExternalModule', null
|
||||||
|
]);
|
||||||
|
expect(anyAnalysis).toContain([
|
||||||
|
'ImplicitClass.implicitLibraryMethod', 'LibraryModule', 'some-library'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should find declarations that delegate by calling another function', () => {
|
||||||
|
const delegatedAnalysis = getAnalysisDescription(
|
||||||
|
analyses, _('/node_modules/test-package/typings/delegated.d.ts'));
|
||||||
|
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'delegatedExplicitInternalFunction', 'ExplicitInternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'delegatedExplicitExternalFunction', 'ExternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'delegatedExplicitLibraryFunction', 'LibraryModule', 'some-library'
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'DelegatedExplicitClass.explicitInternalMethod', 'ExplicitInternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'DelegatedExplicitClass.explicitExternalMethod', 'ExternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'DelegatedExplicitClass.explicitLibraryMethod', 'LibraryModule', 'some-library'
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'delegatedImplicitInternalFunction', 'ImplicitInternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'delegatedImplicitExternalFunction', 'ExternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'delegatedImplicitLibraryFunction', 'LibraryModule', 'some-library'
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'DelegatedImplicitClass.implicitInternalMethod', 'ImplicitInternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'DelegatedImplicitClass.implicitExternalMethod', 'ExternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'DelegatedImplicitClass.implicitLibraryMethod', 'LibraryModule', 'some-library'
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'delegatedAnyInternalFunction', 'AnyInternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'delegatedAnyExternalFunction', 'ExternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'delegatedAnyLibraryFunction', 'LibraryModule', 'some-library'
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'DelegatedAnyClass.anyInternalMethod', 'AnyInternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'DelegatedAnyClass.anyExternalMethod', 'ExternalModule', null
|
||||||
|
]);
|
||||||
|
expect(delegatedAnalysis).toContain([
|
||||||
|
'DelegatedAnyClass.anyLibraryMethod', 'LibraryModule', 'some-library'
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(delegatedAnalysis).toContain(['withParams', 'ExplicitInternalModule', null]);
|
||||||
|
expect(delegatedAnalysis).toContain(['withOptionalParams', 'ExplicitInternalModule', null]);
|
||||||
|
expect(delegatedAnalysis).toContain(['doubleDelegation', 'ExplicitInternalModule', null]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find declarations that do not specify a `providers` property in the return type',
|
it('should find declarations that do not specify a `providers` property in the return type',
|
||||||
|
@ -416,11 +619,15 @@ runInEachFileSystem(() => {
|
||||||
const analysis = analyses.get(file);
|
const analysis = analyses.get(file);
|
||||||
return analysis ? analysis.map(
|
return analysis ? analysis.map(
|
||||||
info =>
|
info =>
|
||||||
[info.declaration.name!.getText(),
|
[getName(info.container) + info.declaration.name!.getText(),
|
||||||
(info.ngModule.node as ts.ClassDeclaration).name!.getText(),
|
(info.ngModule.node as ts.ClassDeclaration).name!.getText(),
|
||||||
info.ngModule.viaModule]) :
|
info.ngModule.ownedByModuleGuess]) :
|
||||||
[];
|
[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getName(node: ts.Declaration|null): string {
|
||||||
|
return node && isNamedClassDeclaration(node) ? `${node.name.text}.` : '';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -540,7 +747,8 @@ runInEachFileSystem(() => {
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
|
||||||
const processDts = true;
|
const processDts = true;
|
||||||
const analyzer = new ModuleWithProvidersAnalyzer(host, referencesRegistry, processDts);
|
const analyzer = new ModuleWithProvidersAnalyzer(
|
||||||
|
host, bundle.src.program.getTypeChecker(), referencesRegistry, processDts);
|
||||||
const analyses = analyzer.analyzeProgram(program);
|
const analyses = analyzer.analyzeProgram(program);
|
||||||
|
|
||||||
const file = getSourceFileOrError(
|
const file = getSourceFileOrError(
|
||||||
|
@ -570,7 +778,8 @@ runInEachFileSystem(() => {
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
|
||||||
const processDts = false; // Emulate the scenario where typings have already been processed
|
const processDts = false; // Emulate the scenario where typings have already been processed
|
||||||
const analyzer = new ModuleWithProvidersAnalyzer(host, referencesRegistry, processDts);
|
const analyzer = new ModuleWithProvidersAnalyzer(
|
||||||
|
host, bundle.src.program.getTypeChecker(), referencesRegistry, processDts);
|
||||||
const analyses = analyzer.analyzeProgram(program);
|
const analyses = analyzer.analyzeProgram(program);
|
||||||
|
|
||||||
expect(analyses.size).toBe(0);
|
expect(analyses.size).toBe(0);
|
||||||
|
|
|
@ -78,7 +78,8 @@ function createTestRenderer(
|
||||||
const decorationAnalyses =
|
const decorationAnalyses =
|
||||||
new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram();
|
new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram();
|
||||||
const moduleWithProvidersAnalyses =
|
const moduleWithProvidersAnalyses =
|
||||||
new ModuleWithProvidersAnalyzer(host, referencesRegistry, true)
|
new ModuleWithProvidersAnalyzer(
|
||||||
|
host, bundle.src.program.getTypeChecker(), referencesRegistry, true)
|
||||||
.analyzeProgram(bundle.src.program);
|
.analyzeProgram(bundle.src.program);
|
||||||
const privateDeclarationsAnalyses =
|
const privateDeclarationsAnalyses =
|
||||||
new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
||||||
|
|
|
@ -574,7 +574,8 @@ export { D };
|
||||||
|
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
const moduleWithProvidersAnalyses =
|
const moduleWithProvidersAnalyses =
|
||||||
new ModuleWithProvidersAnalyzer(host, referencesRegistry, true)
|
new ModuleWithProvidersAnalyzer(
|
||||||
|
host, bundle.src.program.getTypeChecker(), referencesRegistry, true)
|
||||||
.analyzeProgram(bundle.src.program);
|
.analyzeProgram(bundle.src.program);
|
||||||
const typingsFile = getSourceFileOrError(
|
const typingsFile = getSourceFileOrError(
|
||||||
bundle.dts!.program, _('/node_modules/test-package/typings/index.d.ts'));
|
bundle.dts!.program, _('/node_modules/test-package/typings/index.d.ts'));
|
||||||
|
@ -611,7 +612,8 @@ export { D };
|
||||||
|
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
const moduleWithProvidersAnalyses =
|
const moduleWithProvidersAnalyses =
|
||||||
new ModuleWithProvidersAnalyzer(host, referencesRegistry, true)
|
new ModuleWithProvidersAnalyzer(
|
||||||
|
host, bundle.src.program.getTypeChecker(), referencesRegistry, true)
|
||||||
.analyzeProgram(bundle.src.program);
|
.analyzeProgram(bundle.src.program);
|
||||||
const typingsFile = getSourceFileOrError(
|
const typingsFile = getSourceFileOrError(
|
||||||
bundle.dts!.program, _('/node_modules/test-package/typings/module.d.ts'));
|
bundle.dts!.program, _('/node_modules/test-package/typings/module.d.ts'));
|
||||||
|
|
Loading…
Reference in New Issue