feat(ivy): ngcc - add typings to `ModuleWithProviders` functions (#27326)
Exported functions or static method that return a `ModuleWithProviders` compatible structure need to provide information about the referenced `NgModule` type in their return type. This allows ngtsc to be able to understand the type of `NgModule` that is being returned from calls to the function, without having to dig into the internals of the compiled library. There are two ways to provide this information: * Add a type parameter to the `ModuleWithProviders` return type. E.g. ``` static forRoot(): ModuleWithProviders<SomeNgModule>; ``` * Convert the return type to a union that includes a literal type. E.g. ``` static forRoot(): (SomeOtherType)&{ngModule:SomeNgModule}; ``` This commit updates the rendering of typings files to include this type information on all matching functions/methods. PR Close #27326
This commit is contained in:
parent
cfb8c17511
commit
f2a1c66031
|
@ -0,0 +1,118 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ReferencesRegistry} from '../../../ngtsc/annotations';
|
||||||
|
import {Declaration} from '../../../ngtsc/host';
|
||||||
|
import {ResolvedReference} from '../../../ngtsc/metadata';
|
||||||
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||||
|
import {isDefined} from '../utils';
|
||||||
|
|
||||||
|
export interface ModuleWithProvidersInfo {
|
||||||
|
/**
|
||||||
|
* The declaration (in the .d.ts file) of the function that returns
|
||||||
|
* a `ModuleWithProviders object, but has a signature that needs
|
||||||
|
* a type parameter adding.
|
||||||
|
*/
|
||||||
|
declaration: ts.MethodDeclaration|ts.FunctionDeclaration;
|
||||||
|
/**
|
||||||
|
* The NgModule class declaration (in the .d.ts file) to add as a type parameter.
|
||||||
|
*/
|
||||||
|
ngModule: Declaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModuleWithProvidersAnalyses = Map<ts.SourceFile, ModuleWithProvidersInfo[]>;
|
||||||
|
export const ModuleWithProvidersAnalyses = Map;
|
||||||
|
|
||||||
|
export class ModuleWithProvidersAnalyzer {
|
||||||
|
constructor(private host: NgccReflectionHost, private referencesRegistry: ReferencesRegistry) {}
|
||||||
|
|
||||||
|
analyzeProgram(program: ts.Program): ModuleWithProvidersAnalyses {
|
||||||
|
const analyses = new ModuleWithProvidersAnalyses();
|
||||||
|
const rootFiles = this.getRootFiles(program);
|
||||||
|
rootFiles.forEach(f => {
|
||||||
|
const fns = this.host.getModuleWithProvidersFunctions(f);
|
||||||
|
fns && fns.forEach(fn => {
|
||||||
|
const dtsFn = this.getDtsDeclaration(fn.declaration);
|
||||||
|
const typeParam = dtsFn.type && ts.isTypeReferenceNode(dtsFn.type) &&
|
||||||
|
dtsFn.type.typeArguments && dtsFn.type.typeArguments[0] ||
|
||||||
|
null;
|
||||||
|
if (!typeParam || isAnyKeyword(typeParam)) {
|
||||||
|
// Either we do not have a parameterized type or the type is `any`.
|
||||||
|
let ngModule = this.host.getDeclarationOfIdentifier(fn.ngModule);
|
||||||
|
if (!ngModule) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot find a declaration for NgModule ${fn.ngModule.text} referenced in ${fn.declaration.getText()}`);
|
||||||
|
}
|
||||||
|
// For internal (non-library) module references, redirect the module's value declaration
|
||||||
|
// to its type declaration.
|
||||||
|
if (ngModule.viaModule === null) {
|
||||||
|
const dtsNgModule = this.host.getDtsDeclaration(ngModule.node);
|
||||||
|
if (!dtsNgModule) {
|
||||||
|
throw new Error(
|
||||||
|
`No typings declaration can be found for the referenced NgModule class in ${fn.declaration.getText()}.`);
|
||||||
|
}
|
||||||
|
if (!ts.isClassDeclaration(dtsNgModule)) {
|
||||||
|
throw new Error(
|
||||||
|
`The referenced NgModule in ${fn.declaration.getText()} is not a class declaration in the typings program; instead we get ${dtsNgModule.getText()}`);
|
||||||
|
}
|
||||||
|
// Record the usage of the internal module as it needs to become an exported symbol
|
||||||
|
this.referencesRegistry.add(new ResolvedReference(ngModule.node, fn.ngModule));
|
||||||
|
|
||||||
|
ngModule = {node: dtsNgModule, viaModule: null};
|
||||||
|
}
|
||||||
|
const dtsFile = dtsFn.getSourceFile();
|
||||||
|
const analysis = analyses.get(dtsFile) || [];
|
||||||
|
analysis.push({declaration: dtsFn, ngModule});
|
||||||
|
analyses.set(dtsFile, analysis);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return analyses;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRootFiles(program: ts.Program): ts.SourceFile[] {
|
||||||
|
return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDtsDeclaration(fn: ts.SignatureDeclaration) {
|
||||||
|
let dtsFn: ts.Declaration|null = null;
|
||||||
|
const containerClass = this.host.getClassSymbol(fn.parent);
|
||||||
|
const fnName = fn.name && ts.isIdentifier(fn.name) && fn.name.text;
|
||||||
|
if (containerClass && fnName) {
|
||||||
|
const dtsClass = this.host.getDtsDeclaration(containerClass.valueDeclaration);
|
||||||
|
// Get the declaration of the matching static method
|
||||||
|
dtsFn = dtsClass && ts.isClassDeclaration(dtsClass) ?
|
||||||
|
dtsClass.members
|
||||||
|
.find(
|
||||||
|
member => ts.isMethodDeclaration(member) && ts.isIdentifier(member.name) &&
|
||||||
|
member.name.text === fnName) as ts.Declaration :
|
||||||
|
null;
|
||||||
|
} else {
|
||||||
|
dtsFn = this.host.getDtsDeclaration(fn);
|
||||||
|
}
|
||||||
|
if (!dtsFn) {
|
||||||
|
throw new Error(`Matching type declaration for ${fn.getText()} is missing`);
|
||||||
|
}
|
||||||
|
if (!isFunctionOrMethod(dtsFn)) {
|
||||||
|
throw new Error(
|
||||||
|
`Matching type declaration for ${fn.getText()} is not a function: ${dtsFn.getText()}`);
|
||||||
|
}
|
||||||
|
return dtsFn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function isFunctionOrMethod(declaration: ts.Declaration): declaration is ts.FunctionDeclaration|
|
||||||
|
ts.MethodDeclaration {
|
||||||
|
return ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAnyKeyword(typeParam: ts.TypeNode): typeParam is ts.KeywordTypeNode {
|
||||||
|
return typeParam.kind === ts.SyntaxKind.AnyKeyword;
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ import {hasNameIdentifier, isDefined} from '../utils';
|
||||||
export interface ExportInfo {
|
export interface ExportInfo {
|
||||||
identifier: string;
|
identifier: string;
|
||||||
from: string;
|
from: string;
|
||||||
dtsFrom: string|null;
|
dtsFrom?: string|null;
|
||||||
}
|
}
|
||||||
export type PrivateDeclarationsAnalyses = ExportInfo[];
|
export type PrivateDeclarationsAnalyses = ExportInfo[];
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {BundleProgram} from '../packages/bundle_program';
|
||||||
import {findAll, getNameText, isDefined} from '../utils';
|
import {findAll, getNameText, isDefined} from '../utils';
|
||||||
|
|
||||||
import {DecoratedClass} from './decorated_class';
|
import {DecoratedClass} from './decorated_class';
|
||||||
import {NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
||||||
|
|
||||||
export const DECORATORS = 'decorators' as ts.__String;
|
export const DECORATORS = 'decorators' as ts.__String;
|
||||||
export const PROP_DECORATORS = 'propDecorators' as ts.__String;
|
export const PROP_DECORATORS = 'propDecorators' as ts.__String;
|
||||||
|
@ -357,6 +357,37 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
return this.dtsDeclarationMap.get(declaration.name.text) || null;
|
return this.dtsDeclarationMap.get(declaration.name.text) || 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 (this.isClass(declaration.node)) {
|
||||||
|
this.getMembersOfClass(declaration.node).forEach(member => {
|
||||||
|
if (member.isStatic) {
|
||||||
|
const info = this.parseForModuleWithProviders(member.node);
|
||||||
|
if (info) {
|
||||||
|
infos.push(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const info = this.parseForModuleWithProviders(declaration.node);
|
||||||
|
if (info) {
|
||||||
|
infos.push(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
///////////// Protected Helpers /////////////
|
///////////// Protected Helpers /////////////
|
||||||
|
|
||||||
protected getDecoratorsOfSymbol(symbol: ts.Symbol): Decorator[]|null {
|
protected getDecoratorsOfSymbol(symbol: ts.Symbol): Decorator[]|null {
|
||||||
|
@ -1017,6 +1048,31 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
sourceFile => { collectExportedDeclarations(checker, dtsDeclarationMap, sourceFile); });
|
sourceFile => { collectExportedDeclarations(checker, dtsDeclarationMap, sourceFile); });
|
||||||
return dtsDeclarationMap;
|
return dtsDeclarationMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given node, to see if it is a function that returns a `ModuleWithProviders` object.
|
||||||
|
* @param node a node to check to see if it is a function that returns a `ModuleWithProviders`
|
||||||
|
* object.
|
||||||
|
* @returns info about the function if it does return a `ModuleWithProviders` object; `null`
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
protected parseForModuleWithProviders(node: ts.Node|null): ModuleWithProvidersFunction|null {
|
||||||
|
const declaration =
|
||||||
|
node && (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) ? node : null;
|
||||||
|
const body = declaration ? this.getDefinitionOfFunction(declaration).body : null;
|
||||||
|
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;
|
||||||
|
const ngModule = ngModuleProperty && ts.isPropertyAssignment(ngModuleProperty) &&
|
||||||
|
ts.isIdentifier(ngModuleProperty.initializer) && ngModuleProperty.initializer ||
|
||||||
|
null;
|
||||||
|
return ngModule && declaration && {ngModule, declaration};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////// Exported Helpers /////////////
|
///////////// Exported Helpers /////////////
|
||||||
|
|
|
@ -19,6 +19,21 @@ export function isSwitchableVariableDeclaration(node: ts.Node):
|
||||||
ts.isIdentifier(node.initializer) && node.initializer.text.endsWith(PRE_R3_MARKER);
|
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 declaration of the function that returns the `ModuleWithProviders` object.
|
||||||
|
*/
|
||||||
|
declaration: ts.SignatureDeclaration;
|
||||||
|
/**
|
||||||
|
* The identifier of the `ngModule` property on the `ModuleWithProviders` object.
|
||||||
|
*/
|
||||||
|
ngModule: ts.Identifier;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reflection host that has extra methods for looking at non-Typescript package formats
|
* A reflection host that has extra methods for looking at non-Typescript package formats
|
||||||
*/
|
*/
|
||||||
|
@ -45,4 +60,13 @@ export interface NgccReflectionHost extends ReflectionHost {
|
||||||
* @returns An array of decorated classes.
|
* @returns An array of decorated classes.
|
||||||
*/
|
*/
|
||||||
findDecoratedClasses(sourceFile: ts.SourceFile): DecoratedClass[];
|
findDecoratedClasses(sourceFile: ts.SourceFile): DecoratedClass[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {mkdir, mv} from 'shelljs';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {CompiledFile, DecorationAnalyzer} from '../analysis/decoration_analyzer';
|
import {CompiledFile, DecorationAnalyzer} from '../analysis/decoration_analyzer';
|
||||||
|
import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../analysis/module_with_providers_analyzer';
|
||||||
import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry';
|
import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry';
|
||||||
import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer';
|
import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer';
|
||||||
import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
|
import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
|
||||||
|
@ -25,6 +26,7 @@ import {EntryPoint} from './entry_point';
|
||||||
import {EntryPointBundle} from './entry_point_bundle';
|
import {EntryPointBundle} from './entry_point_bundle';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Package is stored in a directory on disk and that directory can contain one or more package
|
* A Package is stored in a directory on disk and that directory can contain one or more package
|
||||||
* formats - e.g. fesm2015, UMD, etc. Additionally, each package provides typings (`.d.ts` files).
|
* formats - e.g. fesm2015, UMD, etc. Additionally, each package provides typings (`.d.ts` files).
|
||||||
|
@ -59,13 +61,14 @@ export class Transformer {
|
||||||
const reflectionHost = this.getHost(isCore, bundle);
|
const reflectionHost = this.getHost(isCore, bundle);
|
||||||
|
|
||||||
// Parse and analyze the files.
|
// Parse and analyze the files.
|
||||||
const {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
this.analyzeProgram(reflectionHost, isCore, bundle);
|
moduleWithProvidersAnalyses} = this.analyzeProgram(reflectionHost, isCore, bundle);
|
||||||
|
|
||||||
// Transform the source files and source maps.
|
// Transform the source files and source maps.
|
||||||
const renderer = this.getRenderer(reflectionHost, isCore, bundle);
|
const renderer = this.getRenderer(reflectionHost, isCore, bundle);
|
||||||
const renderedFiles = renderer.renderProgram(
|
const renderedFiles = renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
|
|
||||||
// Write out all the transformed files.
|
// Write out all the transformed files.
|
||||||
renderedFiles.forEach(file => this.writeFile(file));
|
renderedFiles.forEach(file => this.writeFile(file));
|
||||||
|
@ -102,16 +105,26 @@ export class Transformer {
|
||||||
ProgramAnalyses {
|
ProgramAnalyses {
|
||||||
const typeChecker = bundle.src.program.getTypeChecker();
|
const typeChecker = bundle.src.program.getTypeChecker();
|
||||||
const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
|
const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
|
||||||
|
|
||||||
|
const switchMarkerAnalyzer = new SwitchMarkerAnalyzer(reflectionHost);
|
||||||
|
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
|
||||||
|
|
||||||
const decorationAnalyzer = new DecorationAnalyzer(
|
const decorationAnalyzer = new DecorationAnalyzer(
|
||||||
typeChecker, reflectionHost, referencesRegistry, bundle.rootDirs, isCore);
|
typeChecker, reflectionHost, referencesRegistry, bundle.rootDirs, isCore);
|
||||||
const switchMarkerAnalyzer = new SwitchMarkerAnalyzer(reflectionHost);
|
const decorationAnalyses = decorationAnalyzer.analyzeProgram(bundle.src.program);
|
||||||
|
|
||||||
|
const moduleWithProvidersAnalyzer =
|
||||||
|
bundle.dts && new ModuleWithProvidersAnalyzer(reflectionHost, referencesRegistry);
|
||||||
|
const moduleWithProvidersAnalyses = moduleWithProvidersAnalyzer &&
|
||||||
|
moduleWithProvidersAnalyzer.analyzeProgram(bundle.src.program);
|
||||||
|
|
||||||
const privateDeclarationsAnalyzer =
|
const privateDeclarationsAnalyzer =
|
||||||
new PrivateDeclarationsAnalyzer(reflectionHost, referencesRegistry);
|
new PrivateDeclarationsAnalyzer(reflectionHost, referencesRegistry);
|
||||||
const decorationAnalyses = decorationAnalyzer.analyzeProgram(bundle.src.program);
|
|
||||||
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
|
|
||||||
const privateDeclarationsAnalyses =
|
const privateDeclarationsAnalyses =
|
||||||
privateDeclarationsAnalyzer.analyzeProgram(bundle.src.program);
|
privateDeclarationsAnalyzer.analyzeProgram(bundle.src.program);
|
||||||
return {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses};
|
|
||||||
|
return {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses};
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFile(file: FileInfo): void {
|
writeFile(file: FileInfo): void {
|
||||||
|
@ -129,4 +142,5 @@ interface ProgramAnalyses {
|
||||||
decorationAnalyses: Map<ts.SourceFile, CompiledFile>;
|
decorationAnalyses: Map<ts.SourceFile, CompiledFile>;
|
||||||
switchMarkerAnalyses: SwitchMarkerAnalyses;
|
switchMarkerAnalyses: SwitchMarkerAnalyses;
|
||||||
privateDeclarationsAnalyses: ExportInfo[];
|
privateDeclarationsAnalyses: ExportInfo[];
|
||||||
|
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,10 @@ import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Decorator} from '../../../ngtsc/host';
|
import {Decorator} from '../../../ngtsc/host';
|
||||||
import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform';
|
import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform';
|
||||||
import {translateStatement, translateType} from '../../../ngtsc/translator';
|
import {translateStatement, translateType, ImportManager} from '../../../ngtsc/translator';
|
||||||
import {NgccImportManager} from './ngcc_import_manager';
|
import {NgccImportManager} from './ngcc_import_manager';
|
||||||
import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer';
|
import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer';
|
||||||
|
import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer';
|
||||||
import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer';
|
import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||||
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
||||||
import {IMPORT_PREFIX} from '../constants';
|
import {IMPORT_PREFIX} from '../constants';
|
||||||
|
@ -49,6 +50,20 @@ interface DtsClassInfo {
|
||||||
compilation: CompileResult[];
|
compilation: CompileResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A structure that captures information about what needs to be rendered
|
||||||
|
* in a typings file.
|
||||||
|
*
|
||||||
|
* It is created as a result of processing the analysis passed to the renderer.
|
||||||
|
*
|
||||||
|
* The `renderDtsFile()` method consumes it when rendering a typings file.
|
||||||
|
*/
|
||||||
|
class DtsRenderInfo {
|
||||||
|
classInfo: DtsClassInfo[] = [];
|
||||||
|
moduleWithProviders: ModuleWithProvidersInfo[] = [];
|
||||||
|
privateExports: ExportInfo[] = [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The collected decorators that have become redundant after the compilation
|
* The collected decorators that have become redundant after the compilation
|
||||||
* of Ivy static fields. The map is keyed by the container node, such that we
|
* of Ivy static fields. The map is keyed by the container node, such that we
|
||||||
|
@ -71,7 +86,8 @@ export abstract class Renderer {
|
||||||
|
|
||||||
renderProgram(
|
renderProgram(
|
||||||
decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses,
|
decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses,
|
||||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] {
|
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null): FileInfo[] {
|
||||||
const renderedFiles: FileInfo[] = [];
|
const renderedFiles: FileInfo[] = [];
|
||||||
|
|
||||||
// Transform the source files.
|
// Transform the source files.
|
||||||
|
@ -87,16 +103,16 @@ export abstract class Renderer {
|
||||||
|
|
||||||
// Transform the .d.ts files
|
// Transform the .d.ts files
|
||||||
if (this.bundle.dts) {
|
if (this.bundle.dts) {
|
||||||
const dtsFiles = this.getTypingsFilesToRender(decorationAnalyses);
|
const dtsFiles = this.getTypingsFilesToRender(
|
||||||
|
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
|
||||||
|
|
||||||
// If the dts entry-point is not already there (it did not have compiled classes)
|
// If the dts entry-point is not already there (it did not have compiled classes)
|
||||||
// then add it now, to ensure it gets its extra exports rendered.
|
// then add it now, to ensure it gets its extra exports rendered.
|
||||||
if (!dtsFiles.has(this.bundle.dts.file)) {
|
if (!dtsFiles.has(this.bundle.dts.file)) {
|
||||||
dtsFiles.set(this.bundle.dts.file, []);
|
dtsFiles.set(this.bundle.dts.file, new DtsRenderInfo());
|
||||||
}
|
}
|
||||||
dtsFiles.forEach(
|
dtsFiles.forEach(
|
||||||
(classes, file) => renderedFiles.push(
|
(renderInfo, file) => renderedFiles.push(...this.renderDtsFile(file, renderInfo)));
|
||||||
...this.renderDtsFile(file, classes, privateDeclarationsAnalyses)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderedFiles;
|
return renderedFiles;
|
||||||
|
@ -151,14 +167,12 @@ export abstract class Renderer {
|
||||||
return this.renderSourceAndMap(sourceFile, input, outputText);
|
return this.renderSourceAndMap(sourceFile, input, outputText);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDtsFile(
|
renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileInfo[] {
|
||||||
dtsFile: ts.SourceFile, dtsClasses: DtsClassInfo[],
|
|
||||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] {
|
|
||||||
const input = this.extractSourceMap(dtsFile);
|
const input = this.extractSourceMap(dtsFile);
|
||||||
const outputText = new MagicString(input.source);
|
const outputText = new MagicString(input.source);
|
||||||
const importManager = new NgccImportManager(false, this.isCore, IMPORT_PREFIX);
|
const importManager = new NgccImportManager(false, this.isCore, IMPORT_PREFIX);
|
||||||
|
|
||||||
dtsClasses.forEach(dtsClass => {
|
renderInfo.classInfo.forEach(dtsClass => {
|
||||||
const endOfClass = dtsClass.dtsDeclaration.getEnd();
|
const endOfClass = dtsClass.dtsDeclaration.getEnd();
|
||||||
dtsClass.compilation.forEach(declaration => {
|
dtsClass.compilation.forEach(declaration => {
|
||||||
const type = translateType(declaration.type, importManager);
|
const type = translateType(declaration.type, importManager);
|
||||||
|
@ -167,26 +181,67 @@ export abstract class Renderer {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager);
|
||||||
this.addImports(
|
this.addImports(
|
||||||
outputText, importManager.getAllImports(dtsFile.fileName, this.bundle.dts !.r3SymbolsFile));
|
outputText, importManager.getAllImports(dtsFile.fileName, this.bundle.dts !.r3SymbolsFile));
|
||||||
|
|
||||||
if (dtsFile === this.bundle.dts !.file) {
|
this.addExports(outputText, dtsFile.fileName, renderInfo.privateExports);
|
||||||
const dtsExports = privateDeclarationsAnalyses.map(e => {
|
|
||||||
if (!e.dtsFrom) {
|
|
||||||
throw new Error(
|
|
||||||
`There is no typings path for ${e.identifier} in ${e.from}.\n` +
|
|
||||||
`We need to add an export for this class to a .d.ts typings file because ` +
|
|
||||||
`Angular compiler needs to be able to reference this class in compiled code, such as templates.\n` +
|
|
||||||
`The simplest fix for this is to ensure that this class is exported from the package's entry-point.`);
|
|
||||||
}
|
|
||||||
return {identifier: e.identifier, from: e.dtsFrom};
|
|
||||||
});
|
|
||||||
this.addExports(outputText, dtsFile.fileName, dtsExports);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.renderSourceAndMap(dtsFile, input, outputText);
|
return this.renderSourceAndMap(dtsFile, input, outputText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the type parameters to the appropriate functions that return `ModuleWithProviders`
|
||||||
|
* structures.
|
||||||
|
*
|
||||||
|
* This function only gets called on typings files, so it doesn't need different implementations
|
||||||
|
* for each bundle format.
|
||||||
|
*/
|
||||||
|
protected addModuleWithProvidersParams(
|
||||||
|
outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[],
|
||||||
|
importManager: NgccImportManager): void {
|
||||||
|
moduleWithProviders.forEach(info => {
|
||||||
|
const ngModuleName = (info.ngModule.node as ts.ClassDeclaration).name !.text;
|
||||||
|
const declarationFile = info.declaration.getSourceFile().fileName;
|
||||||
|
const ngModuleFile = info.ngModule.node.getSourceFile().fileName;
|
||||||
|
const importPath = info.ngModule.viaModule ||
|
||||||
|
(declarationFile !== ngModuleFile ?
|
||||||
|
stripExtension(`./${relative(dirname(declarationFile), ngModuleFile)}`) :
|
||||||
|
null);
|
||||||
|
const ngModule = getImportString(importManager, importPath, ngModuleName);
|
||||||
|
|
||||||
|
if (info.declaration.type) {
|
||||||
|
const typeName = info.declaration.type && ts.isTypeReferenceNode(info.declaration.type) ?
|
||||||
|
info.declaration.type.typeName :
|
||||||
|
null;
|
||||||
|
if (this.isCoreModuleWithProvidersType(typeName)) {
|
||||||
|
// The declaration already returns `ModuleWithProvider` but it needs the `NgModule` type
|
||||||
|
// parameter adding.
|
||||||
|
outputText.overwrite(
|
||||||
|
info.declaration.type.getStart(), info.declaration.type.getEnd(),
|
||||||
|
`ModuleWithProviders<${ngModule}>`);
|
||||||
|
} else {
|
||||||
|
// The declaration returns an unknown type so we need to convert it to a union that
|
||||||
|
// includes the ngModule property.
|
||||||
|
const originalTypeString = info.declaration.type.getText();
|
||||||
|
outputText.overwrite(
|
||||||
|
info.declaration.type.getStart(), info.declaration.type.getEnd(),
|
||||||
|
`(${originalTypeString})&{ngModule:${ngModule}}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The declaration has no return type so provide one.
|
||||||
|
const lastToken = info.declaration.getLastToken();
|
||||||
|
const insertPoint = lastToken && lastToken.kind === ts.SyntaxKind.SemicolonToken ?
|
||||||
|
lastToken.getStart() :
|
||||||
|
info.declaration.getEnd();
|
||||||
|
outputText.appendLeft(
|
||||||
|
insertPoint,
|
||||||
|
`: ${getImportString(importManager, '@angular/core', 'ModuleWithProviders')}<${ngModule}>`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
|
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
|
||||||
void;
|
void;
|
||||||
protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void;
|
protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void;
|
||||||
|
@ -302,22 +357,67 @@ export abstract class Renderer {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getTypingsFilesToRender(analyses: DecorationAnalyses):
|
protected getTypingsFilesToRender(
|
||||||
Map<ts.SourceFile, DtsClassInfo[]> {
|
decorationAnalyses: DecorationAnalyses,
|
||||||
const dtsMap = new Map<ts.SourceFile, DtsClassInfo[]>();
|
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
|
||||||
analyses.forEach(compiledFile => {
|
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|
|
||||||
|
null): Map<ts.SourceFile, DtsRenderInfo> {
|
||||||
|
const dtsMap = new Map<ts.SourceFile, DtsRenderInfo>();
|
||||||
|
|
||||||
|
// Capture the rendering info from the decoration analyses
|
||||||
|
decorationAnalyses.forEach(compiledFile => {
|
||||||
compiledFile.compiledClasses.forEach(compiledClass => {
|
compiledFile.compiledClasses.forEach(compiledClass => {
|
||||||
const dtsDeclaration = this.host.getDtsDeclaration(compiledClass.declaration);
|
const dtsDeclaration = this.host.getDtsDeclaration(compiledClass.declaration);
|
||||||
if (dtsDeclaration) {
|
if (dtsDeclaration) {
|
||||||
const dtsFile = dtsDeclaration.getSourceFile();
|
const dtsFile = dtsDeclaration.getSourceFile();
|
||||||
const classes = dtsMap.get(dtsFile) || [];
|
const renderInfo = dtsMap.get(dtsFile) || new DtsRenderInfo();
|
||||||
classes.push({dtsDeclaration, compilation: compiledClass.compilation});
|
renderInfo.classInfo.push({dtsDeclaration, compilation: compiledClass.compilation});
|
||||||
dtsMap.set(dtsFile, classes);
|
dtsMap.set(dtsFile, renderInfo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Capture the ModuleWithProviders functions/methods that need updating
|
||||||
|
if (moduleWithProvidersAnalyses !== null) {
|
||||||
|
moduleWithProvidersAnalyses.forEach((moduleWithProvidersToFix, dtsFile) => {
|
||||||
|
const renderInfo = dtsMap.get(dtsFile) || new DtsRenderInfo();
|
||||||
|
renderInfo.moduleWithProviders = moduleWithProvidersToFix;
|
||||||
|
dtsMap.set(dtsFile, renderInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture the private declarations that need to be re-exported
|
||||||
|
if (privateDeclarationsAnalyses.length) {
|
||||||
|
const dtsExports = privateDeclarationsAnalyses.map(e => {
|
||||||
|
if (!e.dtsFrom) {
|
||||||
|
throw new Error(
|
||||||
|
`There is no typings path for ${e.identifier} in ${e.from}.\n` +
|
||||||
|
`We need to add an export for this class to a .d.ts typings file because ` +
|
||||||
|
`Angular compiler needs to be able to reference this class in compiled code, such as templates.\n` +
|
||||||
|
`The simplest fix for this is to ensure that this class is exported from the package's entry-point.`);
|
||||||
|
}
|
||||||
|
return {identifier: e.identifier, from: e.dtsFrom};
|
||||||
|
});
|
||||||
|
const dtsEntryPoint = this.bundle.dts !.file;
|
||||||
|
const renderInfo = dtsMap.get(dtsEntryPoint) || new DtsRenderInfo();
|
||||||
|
renderInfo.privateExports = dtsExports;
|
||||||
|
dtsMap.set(dtsEntryPoint, renderInfo);
|
||||||
|
}
|
||||||
|
|
||||||
return dtsMap;
|
return dtsMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given type is the core Angular `ModuleWithProviders` interface.
|
||||||
|
* @param typeName The type to check.
|
||||||
|
* @returns true if the type is the core Angular `ModuleWithProviders` interface.
|
||||||
|
*/
|
||||||
|
private isCoreModuleWithProvidersType(typeName: ts.EntityName|null) {
|
||||||
|
const id =
|
||||||
|
typeName && ts.isIdentifier(typeName) ? this.host.getImportOfIdentifier(typeName) : null;
|
||||||
|
return (
|
||||||
|
id && id.name === 'ModuleWithProviders' && (this.isCore || id.from === '@angular/core'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -386,7 +486,7 @@ export function renderDefinitions(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stripExtension(filePath: string): string {
|
export function stripExtension(filePath: string): string {
|
||||||
return filePath.replace(/\.(js|d\.ts$)/, '');
|
return filePath.replace(/\.(js|d\.ts)$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -399,3 +499,9 @@ function createAssignmentStatement(
|
||||||
const receiver = new WrappedNodeExpr(receiverName);
|
const receiver = new WrappedNodeExpr(receiverName);
|
||||||
return new WritePropExpr(receiver, propName, initializer).toStmt();
|
return new WritePropExpr(receiver, propName, initializer).toStmt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getImportString(
|
||||||
|
importManager: ImportManager, importPath: string | null, importName: string) {
|
||||||
|
const importAs = importPath ? importManager.generateNamedImport(importPath, importName) : null;
|
||||||
|
return importAs ? `${importAs.moduleImport}.${importAs.symbol}` : `${importName}`;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,401 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
|
||||||
|
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||||
|
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||||
|
import {BundleProgram} from '../../src/packages/bundle_program';
|
||||||
|
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
|
||||||
|
|
||||||
|
const TEST_PROGRAM = [
|
||||||
|
{
|
||||||
|
name: '/src/entry-point.js',
|
||||||
|
contents: `
|
||||||
|
export * from './explicit';
|
||||||
|
export * from './any';
|
||||||
|
export * from './implicit';
|
||||||
|
export * from './no-providers';
|
||||||
|
export * from './module';
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/src/explicit.js',
|
||||||
|
contents: `
|
||||||
|
import {ExternalModule} from './module';
|
||||||
|
import {LibraryModule} from 'some-library';
|
||||||
|
export class ExplicitInternalModule {}
|
||||||
|
export function explicitInternalFunction() {
|
||||||
|
return {
|
||||||
|
ngModule: ExplicitInternalModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function explicitExternalFunction() {
|
||||||
|
return {
|
||||||
|
ngModule: ExternalModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function explicitLibraryFunction() {
|
||||||
|
return {
|
||||||
|
ngModule: LibraryModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export class ExplicitClass {
|
||||||
|
static explicitInternalMethod() {
|
||||||
|
return {
|
||||||
|
ngModule: ExplicitInternalModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static explicitExternalMethod() {
|
||||||
|
return {
|
||||||
|
ngModule: ExternalModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static explicitLibraryMethod() {
|
||||||
|
return {
|
||||||
|
ngModule: LibraryModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/src/any.js',
|
||||||
|
contents: `
|
||||||
|
import {ExternalModule} from './module';
|
||||||
|
import {LibraryModule} from 'some-library';
|
||||||
|
export class AnyInternalModule {}
|
||||||
|
export function anyInternalFunction() {
|
||||||
|
return {
|
||||||
|
ngModule: AnyInternalModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function anyExternalFunction() {
|
||||||
|
return {
|
||||||
|
ngModule: ExternalModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function anyLibraryFunction() {
|
||||||
|
return {
|
||||||
|
ngModule: LibraryModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export class AnyClass {
|
||||||
|
static anyInternalMethod() {
|
||||||
|
return {
|
||||||
|
ngModule: AnyInternalModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static anyExternalMethod() {
|
||||||
|
return {
|
||||||
|
ngModule: ExternalModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static anyLibraryMethod() {
|
||||||
|
return {
|
||||||
|
ngModule: LibraryModule,
|
||||||
|
providers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/src/implicit.js',
|
||||||
|
contents: `
|
||||||
|
import {ExternalModule} from './module';
|
||||||
|
import {LibraryModule} from 'some-library';
|
||||||
|
export class ImplicitInternalModule {}
|
||||||
|
export function implicitInternalFunction() {
|
||||||
|
return {
|
||||||
|
ngModule: ImplicitInternalModule,
|
||||||
|
providers: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function implicitExternalFunction() {
|
||||||
|
return {
|
||||||
|
ngModule: ExternalModule,
|
||||||
|
providers: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function implicitLibraryFunction() {
|
||||||
|
return {
|
||||||
|
ngModule: LibraryModule,
|
||||||
|
providers: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export class ImplicitClass {
|
||||||
|
static implicitInternalMethod() {
|
||||||
|
return {
|
||||||
|
ngModule: ImplicitInternalModule,
|
||||||
|
providers: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static implicitExternalMethod() {
|
||||||
|
return {
|
||||||
|
ngModule: ExternalModule,
|
||||||
|
providers: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static implicitLibraryMethod() {
|
||||||
|
return {
|
||||||
|
ngModule: LibraryModule,
|
||||||
|
providers: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/src/no-providers.js',
|
||||||
|
contents: `
|
||||||
|
import {ExternalModule} from './module';
|
||||||
|
import {LibraryModule} from 'some-library';
|
||||||
|
export class NoProvidersInternalModule {}
|
||||||
|
export function noProvExplicitInternalFunction() {
|
||||||
|
return {ngModule: NoProvidersInternalModule};
|
||||||
|
}
|
||||||
|
export function noProvExplicitExternalFunction() {
|
||||||
|
return {ngModule: ExternalModule};
|
||||||
|
}
|
||||||
|
export function noProvExplicitLibraryFunction() {
|
||||||
|
return {ngModule: LibraryModule};
|
||||||
|
}
|
||||||
|
export function noProvAnyInternalFunction() {
|
||||||
|
return {ngModule: NoProvidersInternalModule};
|
||||||
|
}
|
||||||
|
export function noProvAnyExternalFunction() {
|
||||||
|
return {ngModule: ExternalModule};
|
||||||
|
}
|
||||||
|
export function noProvAnyLibraryFunction() {
|
||||||
|
return {ngModule: LibraryModule};
|
||||||
|
}
|
||||||
|
export function noProvImplicitInternalFunction() {
|
||||||
|
return {ngModule: NoProvidersInternalModule};
|
||||||
|
}
|
||||||
|
export function noProvImplicitExternalFunction() {
|
||||||
|
return {ngModule: ExternalModule};
|
||||||
|
}
|
||||||
|
export function noProvImplicitLibraryFunction() {
|
||||||
|
return {ngModule: LibraryModule};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/src/module.js',
|
||||||
|
contents: `
|
||||||
|
export class ExternalModule {}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/node_modules/some-library/index.d.ts',
|
||||||
|
contents: 'export declare class LibraryModule {}'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const TEST_DTS_PROGRAM = [
|
||||||
|
{
|
||||||
|
name: '/typings/entry-point.d.ts',
|
||||||
|
contents: `
|
||||||
|
export * from './explicit';
|
||||||
|
export * from './any';
|
||||||
|
export * from './implicit';
|
||||||
|
export * from './no-providers';
|
||||||
|
export * from './module';
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/typings/explicit.d.ts',
|
||||||
|
contents: `
|
||||||
|
import {ModuleWithProviders} from './core';
|
||||||
|
import {ExternalModule} from './module';
|
||||||
|
import {LibraryModule} from 'some-library';
|
||||||
|
export declare class ExplicitInternalModule {}
|
||||||
|
export declare function explicitInternalFunction(): ModuleWithProviders<ExplicitInternalModule>;
|
||||||
|
export declare function explicitExternalFunction(): ModuleWithProviders<ExternalModule>;
|
||||||
|
export declare function explicitLibraryFunction(): ModuleWithProviders<LibraryModule>;
|
||||||
|
export declare class ExplicitClass {
|
||||||
|
static explicitInternalMethod(): ModuleWithProviders<ExplicitInternalModule>;
|
||||||
|
static explicitExternalMethod(): ModuleWithProviders<ExternalModule>;
|
||||||
|
static explicitLibraryMethod(): ModuleWithProviders<LibraryModule>;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/typings/any.d.ts',
|
||||||
|
contents: `
|
||||||
|
import {ModuleWithProviders} from './core';
|
||||||
|
export declare class AnyInternalModule {}
|
||||||
|
export declare function anyInternalFunction(): ModuleWithProviders<any>;
|
||||||
|
export declare function anyExternalFunction(): ModuleWithProviders<any>;
|
||||||
|
export declare function anyLibraryFunction(): ModuleWithProviders<any>;
|
||||||
|
export declare class AnyClass {
|
||||||
|
static anyInternalMethod(): ModuleWithProviders<any>;
|
||||||
|
static anyExternalMethod(): ModuleWithProviders<any>;
|
||||||
|
static anyLibraryMethod(): ModuleWithProviders<any>;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/typings/implicit.d.ts',
|
||||||
|
contents: `
|
||||||
|
import {ExternalModule} from './module';
|
||||||
|
import {LibraryModule} from 'some-library';
|
||||||
|
export declare class ImplicitInternalModule {}
|
||||||
|
export declare function implicitInternalFunction(): { ngModule: typeof ImplicitInternalModule; providers: never[]; };
|
||||||
|
export declare function implicitExternalFunction(): { ngModule: typeof ExternalModule; providers: never[]; };
|
||||||
|
export declare function implicitLibraryFunction(): { ngModule: typeof LibraryModule; providers: never[]; };
|
||||||
|
export declare class ImplicitClass {
|
||||||
|
static implicitInternalMethod(): { ngModule: typeof ImplicitInternalModule; providers: never[]; };
|
||||||
|
static implicitExternalMethod(): { ngModule: typeof ExternalModule; providers: never[]; };
|
||||||
|
static implicitLibraryMethod(): { ngModule: typeof LibraryModule; providers: never[]; };
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/typings/no-providers.d.ts',
|
||||||
|
contents: `
|
||||||
|
import {ModuleWithProviders} from './core';
|
||||||
|
import {ExternalModule} from './module';
|
||||||
|
import {LibraryModule} from 'some-library';
|
||||||
|
export declare class NoProvidersInternalModule {}
|
||||||
|
export declare function noProvExplicitInternalFunction(): ModuleWithProviders<NoProvidersInternalModule>;
|
||||||
|
export declare function noProvExplicitExternalFunction(): ModuleWithProviders<ExternalModule>;
|
||||||
|
export declare function noProvExplicitLibraryFunction(): ModuleWithProviders<LibraryModule>;
|
||||||
|
export declare function noProvAnyInternalFunction(): ModuleWithProviders<any>;
|
||||||
|
export declare function noProvAnyExternalFunction(): ModuleWithProviders<any>;
|
||||||
|
export declare function noProvAnyLibraryFunction(): ModuleWithProviders<any>;
|
||||||
|
export declare function noProvImplicitInternalFunction(): { ngModule: typeof NoProvidersInternalModule; };
|
||||||
|
export declare function noProvImplicitExternalFunction(): { ngModule: typeof ExternalModule; };
|
||||||
|
export declare function noProvImplicitLibraryFunction(): { ngModule: typeof LibraryModule; };
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/typings/module.d.ts',
|
||||||
|
contents: `
|
||||||
|
export declare class ExternalModule {}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/typings/core.d.ts',
|
||||||
|
contents: `
|
||||||
|
|
||||||
|
export declare interface Type<T> {
|
||||||
|
new (...args: any[]): T
|
||||||
|
}
|
||||||
|
export declare type Provider = any;
|
||||||
|
export declare interface ModuleWithProviders<T> {
|
||||||
|
ngModule: Type<T>
|
||||||
|
providers?: Provider[]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/node_modules/some-library/index.d.ts',
|
||||||
|
contents: 'export declare class LibraryModule {}'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('ModuleWithProvidersAnalyzer', () => {
|
||||||
|
describe('analyzeProgram()', () => {
|
||||||
|
let analyses: ModuleWithProvidersAnalyses;
|
||||||
|
let program: ts.Program;
|
||||||
|
let dtsProgram: BundleProgram;
|
||||||
|
let referencesRegistry: NgccReferencesRegistry;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
program = makeTestProgram(...TEST_PROGRAM);
|
||||||
|
dtsProgram = makeTestBundleProgram(TEST_DTS_PROGRAM);
|
||||||
|
const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dtsProgram);
|
||||||
|
referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
|
||||||
|
const analyzer = new ModuleWithProvidersAnalyzer(host, referencesRegistry);
|
||||||
|
analyses = analyzer.analyzeProgram(program);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore declarations that already have explicit NgModule type params',
|
||||||
|
() => { expect(getAnalysisDescription(analyses, '/typings/explicit.d.ts')).toEqual([]); });
|
||||||
|
|
||||||
|
it('should find declarations that use `any` for the NgModule type param', () => {
|
||||||
|
const anyAnalysis = getAnalysisDescription(analyses, '/typings/any.d.ts');
|
||||||
|
expect(anyAnalysis).toContain(['anyInternalFunction', 'AnyInternalModule', null]);
|
||||||
|
expect(anyAnalysis).toContain(['anyExternalFunction', 'ExternalModule', null]);
|
||||||
|
expect(anyAnalysis).toContain(['anyLibraryFunction', 'LibraryModule', 'some-library']);
|
||||||
|
expect(anyAnalysis).toContain(['anyInternalMethod', 'AnyInternalModule', null]);
|
||||||
|
expect(anyAnalysis).toContain(['anyExternalMethod', 'ExternalModule', null]);
|
||||||
|
expect(anyAnalysis).toContain(['anyLibraryMethod', 'LibraryModule', 'some-library']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should track internal module references in the references registry', () => {
|
||||||
|
const declarations = referencesRegistry.getDeclarationMap();
|
||||||
|
const externalModuleDeclaration =
|
||||||
|
getDeclaration(program, '/src/module.js', 'ExternalModule', ts.isClassDeclaration);
|
||||||
|
const libraryModuleDeclaration = getDeclaration(
|
||||||
|
program, '/node_modules/some-library/index.d.ts', 'LibraryModule', ts.isClassDeclaration);
|
||||||
|
expect(declarations.has(externalModuleDeclaration.name !)).toBe(true);
|
||||||
|
expect(declarations.has(libraryModuleDeclaration.name !)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find declarations that have implicit return types', () => {
|
||||||
|
const anyAnalysis = getAnalysisDescription(analyses, '/typings/implicit.d.ts');
|
||||||
|
expect(anyAnalysis).toContain(['implicitInternalFunction', 'ImplicitInternalModule', null]);
|
||||||
|
expect(anyAnalysis).toContain(['implicitExternalFunction', 'ExternalModule', null]);
|
||||||
|
expect(anyAnalysis).toContain(['implicitLibraryFunction', 'LibraryModule', 'some-library']);
|
||||||
|
expect(anyAnalysis).toContain(['implicitInternalMethod', 'ImplicitInternalModule', null]);
|
||||||
|
expect(anyAnalysis).toContain(['implicitExternalMethod', 'ExternalModule', null]);
|
||||||
|
expect(anyAnalysis).toContain(['implicitLibraryMethod', 'LibraryModule', 'some-library']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find declarations that do not specify a `providers` property in the return type',
|
||||||
|
() => {
|
||||||
|
const anyAnalysis = getAnalysisDescription(analyses, '/typings/no-providers.d.ts');
|
||||||
|
expect(anyAnalysis).not.toContain([
|
||||||
|
'noProvExplicitInternalFunction', 'NoProvidersInternalModule'
|
||||||
|
]);
|
||||||
|
expect(anyAnalysis).not.toContain([
|
||||||
|
'noProvExplicitExternalFunction', 'ExternalModule', null
|
||||||
|
]);
|
||||||
|
expect(anyAnalysis).toContain([
|
||||||
|
'noProvAnyInternalFunction', 'NoProvidersInternalModule', null
|
||||||
|
]);
|
||||||
|
expect(anyAnalysis).toContain(['noProvAnyExternalFunction', 'ExternalModule', null]);
|
||||||
|
expect(anyAnalysis).toContain([
|
||||||
|
'noProvAnyLibraryFunction', 'LibraryModule', 'some-library'
|
||||||
|
]);
|
||||||
|
expect(anyAnalysis).toContain([
|
||||||
|
'noProvImplicitInternalFunction', 'NoProvidersInternalModule', null
|
||||||
|
]);
|
||||||
|
expect(anyAnalysis).toContain(['noProvImplicitExternalFunction', 'ExternalModule', null]);
|
||||||
|
expect(anyAnalysis).toContain([
|
||||||
|
'noProvImplicitLibraryFunction', 'LibraryModule', 'some-library'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getAnalysisDescription(analyses: ModuleWithProvidersAnalyses, fileName: string) {
|
||||||
|
const file = dtsProgram.program.getSourceFile(fileName) !;
|
||||||
|
const analysis = analyses.get(file);
|
||||||
|
return analysis ?
|
||||||
|
analysis.map(
|
||||||
|
info =>
|
||||||
|
[info.declaration.name !.getText(),
|
||||||
|
(info.ngModule.node as ts.ClassDeclaration).name !.getText(),
|
||||||
|
info.ngModule.viaModule]) :
|
||||||
|
[];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -84,6 +84,8 @@ export function getFakeCore() {
|
||||||
export class InjectionToken {
|
export class InjectionToken {
|
||||||
constructor(name: string) {}
|
constructor(name: string) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ModuleWithProviders<T = any> {}
|
||||||
`
|
`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -485,6 +485,54 @@ const TYPINGS_DTS_FILES = [
|
||||||
{name: '/typings/class3.d.ts', contents: `export declare class Class3 {}`},
|
{name: '/typings/class3.d.ts', contents: `export declare class Class3 {}`},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const MODULE_WITH_PROVIDERS_PROGRAM = [
|
||||||
|
{
|
||||||
|
name: '/src/functions.js',
|
||||||
|
contents: `
|
||||||
|
import {ExternalModule} 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 }; }
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/src/methods.js',
|
||||||
|
contents: `
|
||||||
|
import {ExternalModule} 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 }; }
|
||||||
|
|
||||||
|
instanceNgModuleIdentifier() { return { ngModule: InternalModule }; }
|
||||||
|
instanceNgModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
|
||||||
|
instanceNgModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
|
||||||
|
instanceExternalNgModule() { return { ngModule: ExternalModule }; }
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{name: '/src/module', contents: 'export class ExternalModule {}'},
|
||||||
|
];
|
||||||
|
|
||||||
describe('Fesm2015ReflectionHost', () => {
|
describe('Fesm2015ReflectionHost', () => {
|
||||||
|
|
||||||
describe('getDecoratorsOfDeclaration()', () => {
|
describe('getDecoratorsOfDeclaration()', () => {
|
||||||
|
@ -1375,4 +1423,34 @@ describe('Fesm2015ReflectionHost', () => {
|
||||||
.toEqual('/typings/class2.d.ts');
|
.toEqual('/typings/class2.d.ts');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getModuleWithProvidersFunctions', () => {
|
||||||
|
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
|
||||||
|
() => {
|
||||||
|
const srcProgram = makeTestProgram(...MODULE_WITH_PROVIDERS_PROGRAM);
|
||||||
|
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker());
|
||||||
|
const file = srcProgram.getSourceFile('/src/functions.js') !;
|
||||||
|
const fns = host.getModuleWithProvidersFunctions(file);
|
||||||
|
expect(fns.map(info => [info.declaration.name !.getText(), info.ngModule.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',
|
||||||
|
() => {
|
||||||
|
const srcProgram = makeTestProgram(...MODULE_WITH_PROVIDERS_PROGRAM);
|
||||||
|
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker());
|
||||||
|
const file = srcProgram.getSourceFile('/src/methods.js') !;
|
||||||
|
const fn = host.getModuleWithProvidersFunctions(file);
|
||||||
|
expect(fn.map(fn => [fn.declaration.name !.getText(), fn.ngModule.text])).toEqual([
|
||||||
|
['ngModuleIdentifier', 'InternalModule'],
|
||||||
|
['ngModuleWithEmptyProviders', 'InternalModule'],
|
||||||
|
['ngModuleWithProviders', 'InternalModule'],
|
||||||
|
['externalNgModule', 'ExternalModule'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ import * as ts from 'typescript';
|
||||||
import {fromObject, generateMapFileComment} from 'convert-source-map';
|
import {fromObject, generateMapFileComment} from 'convert-source-map';
|
||||||
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||||
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||||
|
import {ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
|
||||||
import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer';
|
import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer';
|
||||||
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||||
|
@ -47,9 +48,9 @@ class TestRenderer extends Renderer {
|
||||||
|
|
||||||
function createTestRenderer(
|
function createTestRenderer(
|
||||||
packageName: string, files: {name: string, contents: string}[],
|
packageName: string, files: {name: string, contents: string}[],
|
||||||
dtsFile?: {name: string, contents: string}) {
|
dtsFiles?: {name: string, contents: string}[]) {
|
||||||
const isCore = packageName === '@angular/core';
|
const isCore = packageName === '@angular/core';
|
||||||
const bundle = makeTestEntryPointBundle('esm2015', files, dtsFile && [dtsFile]);
|
const bundle = makeTestEntryPointBundle('esm2015', files, dtsFiles);
|
||||||
const typeChecker = bundle.src.program.getTypeChecker();
|
const typeChecker = bundle.src.program.getTypeChecker();
|
||||||
const host = new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts);
|
const host = new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts);
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
@ -57,6 +58,8 @@ function createTestRenderer(
|
||||||
new DecorationAnalyzer(typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
|
new DecorationAnalyzer(typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
|
||||||
.analyzeProgram(bundle.src.program);
|
.analyzeProgram(bundle.src.program);
|
||||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
|
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
|
||||||
|
const moduleWithProvidersAnalyses =
|
||||||
|
new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
||||||
const privateDeclarationsAnalyses =
|
const privateDeclarationsAnalyses =
|
||||||
new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
||||||
const renderer = new TestRenderer(host, isCore, bundle);
|
const renderer = new TestRenderer(host, isCore, bundle);
|
||||||
|
@ -64,7 +67,8 @@ function createTestRenderer(
|
||||||
spyOn(renderer, 'addDefinitions').and.callThrough();
|
spyOn(renderer, 'addDefinitions').and.callThrough();
|
||||||
spyOn(renderer, 'removeDecorators').and.callThrough();
|
spyOn(renderer, 'removeDecorators').and.callThrough();
|
||||||
|
|
||||||
return {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses};
|
return {renderer, decorationAnalyses, switchMarkerAnalyses, moduleWithProvidersAnalyses,
|
||||||
|
privateDeclarationsAnalyses};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,10 +125,11 @@ describe('Renderer', () => {
|
||||||
describe('renderProgram()', () => {
|
describe('renderProgram()', () => {
|
||||||
it('should render the modified contents; and a new map file, if the original provided no map file.',
|
it('should render the modified contents; and a new map file, if the original provided no map file.',
|
||||||
() => {
|
() => {
|
||||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
moduleWithProvidersAnalyses} = createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||||
const result = renderer.renderProgram(
|
const result = renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
expect(result[0].path).toEqual('/dist/file.js');
|
expect(result[0].path).toEqual('/dist/file.js');
|
||||||
expect(result[0].contents)
|
expect(result[0].contents)
|
||||||
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
|
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
|
||||||
|
@ -134,9 +139,11 @@ describe('Renderer', () => {
|
||||||
|
|
||||||
|
|
||||||
it('should render as JavaScript', () => {
|
it('should render as JavaScript', () => {
|
||||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
createTestRenderer('test-package', [COMPONENT_PROGRAM]);
|
moduleWithProvidersAnalyses} = createTestRenderer('test-package', [COMPONENT_PROGRAM]);
|
||||||
renderer.renderProgram(decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
renderer.renderProgram(
|
||||||
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||||
expect(addDefinitionsSpy.calls.first().args[2])
|
expect(addDefinitionsSpy.calls.first().args[2])
|
||||||
.toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
|
.toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
|
||||||
|
@ -154,10 +161,12 @@ A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], fact
|
||||||
describe('calling abstract methods', () => {
|
describe('calling abstract methods', () => {
|
||||||
it('should call addImports with the source code and info about the core Angular library.',
|
it('should call addImports with the source code and info about the core Angular library.',
|
||||||
() => {
|
() => {
|
||||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses} =
|
||||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||||
const result = renderer.renderProgram(
|
const result = renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
const addImportsSpy = renderer.addImports as jasmine.Spy;
|
const addImportsSpy = renderer.addImports as jasmine.Spy;
|
||||||
expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||||
expect(addImportsSpy.calls.first().args[1]).toEqual([
|
expect(addImportsSpy.calls.first().args[1]).toEqual([
|
||||||
|
@ -167,10 +176,12 @@ A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], fact
|
||||||
|
|
||||||
it('should call addDefinitions with the source code, the analyzed class and the rendered definitions.',
|
it('should call addDefinitions with the source code, the analyzed class and the rendered definitions.',
|
||||||
() => {
|
() => {
|
||||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses} =
|
||||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||||
const result = renderer.renderProgram(
|
const result = renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||||
expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||||
expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({
|
expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({
|
||||||
|
@ -187,10 +198,12 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||||
|
|
||||||
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
|
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
|
||||||
() => {
|
() => {
|
||||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses} =
|
||||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||||
const result = renderer.renderProgram(
|
const result = renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy;
|
const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy;
|
||||||
expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||||
|
|
||||||
|
@ -212,14 +225,16 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||||
describe('source map merging', () => {
|
describe('source map merging', () => {
|
||||||
it('should merge any inline source map from the original file and write the output as an inline source map',
|
it('should merge any inline source map from the original file and write the output as an inline source map',
|
||||||
() => {
|
() => {
|
||||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses} =
|
||||||
createTestRenderer(
|
createTestRenderer(
|
||||||
'test-package', [{
|
'test-package', [{
|
||||||
...INPUT_PROGRAM,
|
...INPUT_PROGRAM,
|
||||||
contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment()
|
contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment()
|
||||||
}]);
|
}]);
|
||||||
const result = renderer.renderProgram(
|
const result = renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
expect(result[0].path).toEqual('/dist/file.js');
|
expect(result[0].path).toEqual('/dist/file.js');
|
||||||
expect(result[0].contents)
|
expect(result[0].contents)
|
||||||
.toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment());
|
.toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment());
|
||||||
|
@ -230,14 +245,16 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||||
() => {
|
() => {
|
||||||
// Mock out reading the map file from disk
|
// Mock out reading the map file from disk
|
||||||
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
|
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
|
||||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses} =
|
||||||
createTestRenderer(
|
createTestRenderer(
|
||||||
'test-package', [{
|
'test-package', [{
|
||||||
...INPUT_PROGRAM,
|
...INPUT_PROGRAM,
|
||||||
contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map'
|
contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map'
|
||||||
}]);
|
}]);
|
||||||
const result = renderer.renderProgram(
|
const result = renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
expect(result[0].path).toEqual('/dist/file.js');
|
expect(result[0].path).toEqual('/dist/file.js');
|
||||||
expect(result[0].contents)
|
expect(result[0].contents)
|
||||||
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
|
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
|
||||||
|
@ -259,10 +276,12 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||||
contents: `export const NgModule = () => null;`
|
contents: `export const NgModule = () => null;`
|
||||||
};
|
};
|
||||||
// The package name of `@angular/core` indicates that we are compiling the core library.
|
// The package name of `@angular/core` indicates that we are compiling the core library.
|
||||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses} =
|
||||||
createTestRenderer('@angular/core', [CORE_FILE, R3_SYMBOLS_FILE]);
|
createTestRenderer('@angular/core', [CORE_FILE, R3_SYMBOLS_FILE]);
|
||||||
renderer.renderProgram(
|
renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||||
expect(addDefinitionsSpy.calls.first().args[2])
|
expect(addDefinitionsSpy.calls.first().args[2])
|
||||||
.toContain(`/*@__PURE__*/ ɵngcc0.setClassMetadata(`);
|
.toContain(`/*@__PURE__*/ ɵngcc0.setClassMetadata(`);
|
||||||
|
@ -277,10 +296,11 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||||
export class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n`
|
export class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n`
|
||||||
};
|
};
|
||||||
|
|
||||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
createTestRenderer('@angular/core', [CORE_FILE]);
|
moduleWithProvidersAnalyses} = createTestRenderer('@angular/core', [CORE_FILE]);
|
||||||
renderer.renderProgram(
|
renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||||
expect(addDefinitionsSpy.calls.first().args[2])
|
expect(addDefinitionsSpy.calls.first().args[2])
|
||||||
.toContain(`/*@__PURE__*/ setClassMetadata(`);
|
.toContain(`/*@__PURE__*/ setClassMetadata(`);
|
||||||
|
@ -291,10 +311,12 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||||
|
|
||||||
describe('rendering typings', () => {
|
describe('rendering typings', () => {
|
||||||
it('should render extract types into typings files', () => {
|
it('should render extract types into typings files', () => {
|
||||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM);
|
moduleWithProvidersAnalyses} =
|
||||||
|
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||||
const result = renderer.renderProgram(
|
const result = renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
|
|
||||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||||
expect(typingsFile.contents)
|
expect(typingsFile.contents)
|
||||||
|
@ -303,30 +325,195 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render imports into typings files', () => {
|
it('should render imports into typings files', () => {
|
||||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM);
|
moduleWithProvidersAnalyses} =
|
||||||
|
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||||
const result = renderer.renderProgram(
|
const result = renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
|
|
||||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||||
expect(typingsFile.contents).toContain(`// ADD IMPORTS\nexport declare class A`);
|
expect(typingsFile.contents).toContain(`// ADD IMPORTS\nexport declare class A`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render exports into typings files', () => {
|
it('should render exports into typings files', () => {
|
||||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM);
|
moduleWithProvidersAnalyses} =
|
||||||
|
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||||
|
|
||||||
// Add a mock export to trigger export rendering
|
// Add a mock export to trigger export rendering
|
||||||
privateDeclarationsAnalyses.push(
|
privateDeclarationsAnalyses.push(
|
||||||
{identifier: 'ComponentB', from: '/src/file.js', dtsFrom: '/typings/b.d.ts'});
|
{identifier: 'ComponentB', from: '/src/file.js', dtsFrom: '/typings/b.d.ts'});
|
||||||
|
|
||||||
const result = renderer.renderProgram(
|
const result = renderer.renderProgram(
|
||||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
|
|
||||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||||
expect(typingsFile.contents)
|
expect(typingsFile.contents)
|
||||||
.toContain(`// ADD EXPORTS\n\n// ADD IMPORTS\nexport declare class A`);
|
.toContain(`// ADD EXPORTS\n\n// ADD IMPORTS\nexport declare class A`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fixup functions/methods that return ModuleWithProviders structures', () => {
|
||||||
|
const MODULE_WITH_PROVIDERS_PROGRAM = [
|
||||||
|
{
|
||||||
|
name: '/src/index.js',
|
||||||
|
contents: `
|
||||||
|
import {ExternalModule} from './module';
|
||||||
|
import {LibraryModule} from 'some-library';
|
||||||
|
export class SomeClass {}
|
||||||
|
export class SomeModule {
|
||||||
|
static withProviders1() {
|
||||||
|
return {ngModule: SomeModule};
|
||||||
|
}
|
||||||
|
static withProviders2() {
|
||||||
|
return {ngModule: SomeModule};
|
||||||
|
}
|
||||||
|
static withProviders3() {
|
||||||
|
return {ngModule: SomeClass};
|
||||||
|
}
|
||||||
|
static withProviders4() {
|
||||||
|
return {ngModule: ExternalModule};
|
||||||
|
}
|
||||||
|
static withProviders5() {
|
||||||
|
return {ngModule: ExternalModule};
|
||||||
|
}
|
||||||
|
static withProviders6() {
|
||||||
|
return {ngModule: LibraryModule};
|
||||||
|
}
|
||||||
|
static withProviders7() {
|
||||||
|
return {ngModule: SomeModule, providers: []};
|
||||||
|
};
|
||||||
|
static withProviders8() {
|
||||||
|
return {ngModule: SomeModule};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function withProviders1() {
|
||||||
|
return {ngModule: SomeModule};
|
||||||
|
}
|
||||||
|
export function withProviders2() {
|
||||||
|
return {ngModule: SomeModule};
|
||||||
|
}
|
||||||
|
export function withProviders3() {
|
||||||
|
return {ngModule: SomeClass};
|
||||||
|
}
|
||||||
|
export function withProviders4() {
|
||||||
|
return {ngModule: ExternalModule};
|
||||||
|
}
|
||||||
|
export function withProviders5() {
|
||||||
|
return {ngModule: ExternalModule};
|
||||||
|
}
|
||||||
|
export function withProviders6() {
|
||||||
|
return {ngModule: LibraryModule};
|
||||||
|
}
|
||||||
|
export function withProviders7() {
|
||||||
|
return {ngModule: SomeModule, providers: []};
|
||||||
|
};
|
||||||
|
export function withProviders8() {
|
||||||
|
return {ngModule: SomeModule};
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/src/module.js',
|
||||||
|
contents: `
|
||||||
|
export class ExternalModule {
|
||||||
|
static withProviders1() {
|
||||||
|
return {ngModule: ExternalModule};
|
||||||
|
}
|
||||||
|
static withProviders2() {
|
||||||
|
return {ngModule: ExternalModule};
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/node_modules/some-library/index.d.ts',
|
||||||
|
contents: 'export declare class LibraryModule {}'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const MODULE_WITH_PROVIDERS_DTS_PROGRAM = [
|
||||||
|
{
|
||||||
|
name: '/typings/index.d.ts',
|
||||||
|
contents: `
|
||||||
|
import {ModuleWithProviders} from '@angular/core';
|
||||||
|
export declare class SomeClass {}
|
||||||
|
export interface MyModuleWithProviders extends ModuleWithProviders {}
|
||||||
|
export declare class SomeModule {
|
||||||
|
static withProviders1(): ModuleWithProviders;
|
||||||
|
static withProviders2(): ModuleWithProviders<any>;
|
||||||
|
static withProviders3(): ModuleWithProviders<SomeClass>;
|
||||||
|
static withProviders4(): ModuleWithProviders;
|
||||||
|
static withProviders5();
|
||||||
|
static withProviders6(): ModuleWithProviders;
|
||||||
|
static withProviders7(): {ngModule: SomeModule, providers: any[]};
|
||||||
|
static withProviders8(): MyModuleWithProviders;
|
||||||
|
}
|
||||||
|
export declare function withProviders1(): ModuleWithProviders;
|
||||||
|
export declare function withProviders2(): ModuleWithProviders<any>;
|
||||||
|
export declare function withProviders3(): ModuleWithProviders<SomeClass>;
|
||||||
|
export declare function withProviders4(): ModuleWithProviders;
|
||||||
|
export declare function withProviders5();
|
||||||
|
export declare function withProviders6(): ModuleWithProviders;
|
||||||
|
export declare function withProviders7(): {ngModule: SomeModule, providers: any[]};
|
||||||
|
export declare function withProviders8(): MyModuleWithProviders;`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/typings/module.d.ts',
|
||||||
|
contents: `
|
||||||
|
export interface ModuleWithProviders {}
|
||||||
|
export declare class ExternalModule {
|
||||||
|
static withProviders1(): ModuleWithProviders;
|
||||||
|
static withProviders2(): ModuleWithProviders;
|
||||||
|
}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '/node_modules/some-library/index.d.ts',
|
||||||
|
contents: 'export declare class LibraryModule {}'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses} =
|
||||||
|
createTestRenderer(
|
||||||
|
'test-package', MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM);
|
||||||
|
|
||||||
|
const result = renderer.renderProgram(
|
||||||
|
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||||
|
moduleWithProvidersAnalyses);
|
||||||
|
|
||||||
|
const typingsFile = result.find(f => f.path === '/typings/index.d.ts') !;
|
||||||
|
|
||||||
|
expect(typingsFile.contents).toContain(`
|
||||||
|
static withProviders1(): ModuleWithProviders<SomeModule>;
|
||||||
|
static withProviders2(): ModuleWithProviders<SomeModule>;
|
||||||
|
static withProviders3(): ModuleWithProviders<SomeClass>;
|
||||||
|
static withProviders4(): ModuleWithProviders<ɵngcc0.ExternalModule>;
|
||||||
|
static withProviders5(): ɵngcc1.ModuleWithProviders<ɵngcc0.ExternalModule>;
|
||||||
|
static withProviders6(): ModuleWithProviders<ɵngcc2.LibraryModule>;
|
||||||
|
static withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule};
|
||||||
|
static withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`);
|
||||||
|
expect(typingsFile.contents).toContain(`
|
||||||
|
export declare function withProviders1(): ModuleWithProviders<SomeModule>;
|
||||||
|
export declare function withProviders2(): ModuleWithProviders<SomeModule>;
|
||||||
|
export declare function withProviders3(): ModuleWithProviders<SomeClass>;
|
||||||
|
export declare function withProviders4(): ModuleWithProviders<ɵngcc0.ExternalModule>;
|
||||||
|
export declare function withProviders5(): ɵngcc1.ModuleWithProviders<ɵngcc0.ExternalModule>;
|
||||||
|
export declare function withProviders6(): ModuleWithProviders<ɵngcc2.LibraryModule>;
|
||||||
|
export declare function withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule};
|
||||||
|
export declare function withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`);
|
||||||
|
|
||||||
|
expect(renderer.addImports).toHaveBeenCalledWith(jasmine.any(MagicString), [
|
||||||
|
{name: './module', as: 'ɵngcc0'},
|
||||||
|
{name: '@angular/core', as: 'ɵngcc1'},
|
||||||
|
{name: 'some-library', as: 'ɵngcc2'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
// The following expectation checks that we do not mistake `ModuleWithProviders` types
|
||||||
|
// that are not imported from `@angular/core`.
|
||||||
|
const typingsFile2 = result.find(f => f.path === '/typings/module.d.ts') !;
|
||||||
|
expect(typingsFile2.contents).toContain(`
|
||||||
|
static withProviders1(): (ModuleWithProviders)&{ngModule:ExternalModule};
|
||||||
|
static withProviders2(): (ModuleWithProviders)&{ngModule:ExternalModule};`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue