fix(ivy): ngcc - separate typings rendering from src rendering (#25445)
Previously the same `Renderer` was used to render typings (.d.ts) files. But the new `UmdRenderer` is not able to render typings files correctly. This commit splits out the typings rendering from the src rendering. To achieve this the previous renderers have been refactored from sub-classes of the abstract `Renderer` class to classes that implement the `RenderingFormatter` interface, which are then passed to the `Renderer` and `DtsRenderer` to modify its rendering behaviour. Along the way a few utility interfaces and classes have been moved around and renamed for clarity. PR Close #25445
This commit is contained in:
parent
f4655ea98a
commit
c613596658
|
@ -18,10 +18,13 @@ import {Esm5ReflectionHost} from '../host/esm5_host';
|
|||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {UmdReflectionHost} from '../host/umd_host';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {Esm5Renderer} from '../rendering/esm5_renderer';
|
||||
import {EsmRenderer} from '../rendering/esm_renderer';
|
||||
import {FileInfo, Renderer} from '../rendering/renderer';
|
||||
import {UmdRenderer} from '../rendering/umd_renderer';
|
||||
import {DtsRenderer} from '../rendering/dts_renderer';
|
||||
import {Esm5RenderingFormatter} from '../rendering/esm5_rendering_formatter';
|
||||
import {EsmRenderingFormatter} from '../rendering/esm_rendering_formatter';
|
||||
import {Renderer} from '../rendering/renderer';
|
||||
import {RenderingFormatter} from '../rendering/rendering_formatter';
|
||||
import {UmdRenderingFormatter} from '../rendering/umd_rendering_formatter';
|
||||
import {FileToWrite} from '../rendering/utils';
|
||||
|
||||
import {EntryPointBundle} from './entry_point_bundle';
|
||||
|
||||
|
@ -56,7 +59,7 @@ export class Transformer {
|
|||
* @param bundle the bundle to transform.
|
||||
* @returns information about the files that were transformed.
|
||||
*/
|
||||
transform(bundle: EntryPointBundle): FileInfo[] {
|
||||
transform(bundle: EntryPointBundle): FileToWrite[] {
|
||||
const isCore = bundle.isCore;
|
||||
const reflectionHost = this.getHost(isCore, bundle);
|
||||
|
||||
|
@ -65,10 +68,21 @@ export class Transformer {
|
|||
moduleWithProvidersAnalyses} = this.analyzeProgram(reflectionHost, isCore, bundle);
|
||||
|
||||
// Transform the source files and source maps.
|
||||
const renderer = this.getRenderer(reflectionHost, isCore, bundle);
|
||||
const renderedFiles = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const srcFormatter = this.getRenderingFormatter(reflectionHost, isCore, bundle);
|
||||
|
||||
const renderer =
|
||||
new Renderer(srcFormatter, this.fs, this.logger, reflectionHost, isCore, bundle);
|
||||
let renderedFiles = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
|
||||
if (bundle.dts) {
|
||||
const dtsFormatter = new EsmRenderingFormatter(reflectionHost, isCore);
|
||||
const dtsRenderer =
|
||||
new DtsRenderer(dtsFormatter, this.fs, this.logger, reflectionHost, isCore, bundle);
|
||||
const renderedDtsFiles = dtsRenderer.renderProgram(
|
||||
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
|
||||
renderedFiles = renderedFiles.concat(renderedDtsFiles);
|
||||
}
|
||||
|
||||
return renderedFiles;
|
||||
}
|
||||
|
@ -88,17 +102,18 @@ export class Transformer {
|
|||
}
|
||||
}
|
||||
|
||||
getRenderer(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle): Renderer {
|
||||
getRenderingFormatter(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle):
|
||||
RenderingFormatter {
|
||||
switch (bundle.format) {
|
||||
case 'esm2015':
|
||||
return new EsmRenderer(this.fs, this.logger, host, isCore, bundle);
|
||||
return new EsmRenderingFormatter(host, isCore);
|
||||
case 'esm5':
|
||||
return new Esm5Renderer(this.fs, this.logger, host, isCore, bundle);
|
||||
return new Esm5RenderingFormatter(host, isCore);
|
||||
case 'umd':
|
||||
if (!(host instanceof UmdReflectionHost)) {
|
||||
throw new Error('UmdRenderer requires a UmdReflectionHost');
|
||||
}
|
||||
return new UmdRenderer(this.fs, this.logger, host, isCore, bundle);
|
||||
return new UmdRenderingFormatter(host, isCore);
|
||||
default:
|
||||
throw new Error(`Renderer for "${bundle.format}" not yet implemented.`);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
* @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 MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {translateType, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {DecorationAnalyses} from '../analysis/decoration_analyzer';
|
||||
import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer';
|
||||
import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {IMPORT_PREFIX} from '../constants';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {FileToWrite, getImportRewriter} from './utils';
|
||||
import {RenderingFormatter} from './rendering_formatter';
|
||||
import {extractSourceMap, renderSourceAndMap} from './source_maps';
|
||||
import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform';
|
||||
|
||||
/**
|
||||
* 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[] = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Information about a class in a typings file.
|
||||
*/
|
||||
export interface DtsClassInfo {
|
||||
dtsDeclaration: ts.Declaration;
|
||||
compilation: CompileResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A base-class for rendering an `AnalyzedFile`.
|
||||
*
|
||||
* Package formats have output files that must be rendered differently. Concrete sub-classes must
|
||||
* implement the `addImports`, `addDefinitions` and `removeDecorators` abstract methods.
|
||||
*/
|
||||
export class DtsRenderer {
|
||||
constructor(
|
||||
private dtsFormatter: RenderingFormatter, private fs: FileSystem, private logger: Logger,
|
||||
private host: NgccReflectionHost, private isCore: boolean, private bundle: EntryPointBundle) {
|
||||
}
|
||||
|
||||
renderProgram(
|
||||
decorationAnalyses: DecorationAnalyses,
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null): FileToWrite[] {
|
||||
const renderedFiles: FileToWrite[] = [];
|
||||
|
||||
// Transform the .d.ts files
|
||||
if (this.bundle.dts) {
|
||||
const dtsFiles = this.getTypingsFilesToRender(
|
||||
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
|
||||
|
||||
// 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.
|
||||
if (!dtsFiles.has(this.bundle.dts.file)) {
|
||||
dtsFiles.set(this.bundle.dts.file, new DtsRenderInfo());
|
||||
}
|
||||
dtsFiles.forEach(
|
||||
(renderInfo, file) => renderedFiles.push(...this.renderDtsFile(file, renderInfo)));
|
||||
}
|
||||
|
||||
return renderedFiles;
|
||||
}
|
||||
|
||||
renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileToWrite[] {
|
||||
const input = extractSourceMap(this.fs, this.logger, dtsFile);
|
||||
const outputText = new MagicString(input.source);
|
||||
const printer = ts.createPrinter();
|
||||
const importManager = new ImportManager(
|
||||
getImportRewriter(this.bundle.dts !.r3SymbolsFile, this.isCore, false), IMPORT_PREFIX);
|
||||
|
||||
renderInfo.classInfo.forEach(dtsClass => {
|
||||
const endOfClass = dtsClass.dtsDeclaration.getEnd();
|
||||
dtsClass.compilation.forEach(declaration => {
|
||||
const type = translateType(declaration.type, importManager);
|
||||
const typeStr = printer.printNode(ts.EmitHint.Unspecified, type, dtsFile);
|
||||
const newStatement = ` static ${declaration.name}: ${typeStr};\n`;
|
||||
outputText.appendRight(endOfClass - 1, newStatement);
|
||||
});
|
||||
});
|
||||
|
||||
this.dtsFormatter.addModuleWithProvidersParams(
|
||||
outputText, renderInfo.moduleWithProviders, importManager);
|
||||
this.dtsFormatter.addExports(
|
||||
outputText, dtsFile.fileName, renderInfo.privateExports, importManager, dtsFile);
|
||||
this.dtsFormatter.addImports(
|
||||
outputText, importManager.getAllImports(dtsFile.fileName), dtsFile);
|
||||
|
||||
|
||||
|
||||
return renderSourceAndMap(dtsFile, input, outputText);
|
||||
}
|
||||
|
||||
private getTypingsFilesToRender(
|
||||
decorationAnalyses: DecorationAnalyses,
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
|
||||
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 => {
|
||||
const dtsDeclaration = this.host.getDtsDeclaration(compiledClass.declaration);
|
||||
if (dtsDeclaration) {
|
||||
const dtsFile = dtsDeclaration.getSourceFile();
|
||||
const renderInfo = dtsMap.has(dtsFile) ? dtsMap.get(dtsFile) ! : new DtsRenderInfo();
|
||||
renderInfo.classInfo.push({dtsDeclaration, compilation: compiledClass.compilation});
|
||||
dtsMap.set(dtsFile, renderInfo);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Capture the ModuleWithProviders functions/methods that need updating
|
||||
if (moduleWithProvidersAnalyses !== null) {
|
||||
moduleWithProvidersAnalyses.forEach((moduleWithProvidersToFix, dtsFile) => {
|
||||
const renderInfo = dtsMap.has(dtsFile) ? 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) {
|
||||
privateDeclarationsAnalyses.forEach(e => {
|
||||
if (!e.dtsFrom && !e.alias) {
|
||||
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.`);
|
||||
}
|
||||
});
|
||||
const dtsEntryPoint = this.bundle.dts !.file;
|
||||
const renderInfo =
|
||||
dtsMap.has(dtsEntryPoint) ? dtsMap.get(dtsEntryPoint) ! : new DtsRenderInfo();
|
||||
renderInfo.privateExports = privateDeclarationsAnalyses;
|
||||
dtsMap.set(dtsEntryPoint, renderInfo);
|
||||
}
|
||||
|
||||
return dtsMap;
|
||||
}
|
||||
}
|
|
@ -8,22 +8,16 @@
|
|||
import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {getIifeBody} from '../host/esm5_host';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {EsmRenderer} from './esm_renderer';
|
||||
|
||||
export class Esm5Renderer extends EsmRenderer {
|
||||
constructor(
|
||||
fs: FileSystem, logger: Logger, host: NgccReflectionHost, isCore: boolean,
|
||||
bundle: EntryPointBundle) {
|
||||
super(fs, logger, host, isCore, bundle);
|
||||
}
|
||||
import {EsmRenderingFormatter} from './esm_rendering_formatter';
|
||||
|
||||
/**
|
||||
* A RenderingFormatter that works with files that use ECMAScript Module `import` and `export`
|
||||
* statements, but instead of `class` declarations it uses ES5 `function` wrappers for classes.
|
||||
*/
|
||||
export class Esm5RenderingFormatter extends EsmRenderingFormatter {
|
||||
/**
|
||||
* Add the definitions to each decorated class
|
||||
* Add the definitions inside the IIFE of each decorated class
|
||||
*/
|
||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void {
|
||||
const iifeBody = getIifeBody(compiledClass.declaration);
|
|
@ -1,140 +0,0 @@
|
|||
/**
|
||||
* @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 MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {PathSegment, AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
|
||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {RedundantDecoratorMap, Renderer, stripExtension} from './renderer';
|
||||
|
||||
export class EsmRenderer extends Renderer {
|
||||
constructor(
|
||||
fs: FileSystem, logger: Logger, host: NgccReflectionHost, isCore: boolean,
|
||||
bundle: EntryPointBundle) {
|
||||
super(fs, logger, host, isCore, bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the imports at the top of the file
|
||||
*/
|
||||
addImports(output: MagicString, imports: Import[], sf: ts.SourceFile): void {
|
||||
const insertionPoint = findEndOfImports(sf);
|
||||
const renderedImports =
|
||||
imports.map(i => `import * as ${i.qualifier} from '${i.specifier}';\n`).join('');
|
||||
output.appendLeft(insertionPoint, renderedImports);
|
||||
}
|
||||
|
||||
addExports(
|
||||
output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[],
|
||||
importManager: ImportManager, file: ts.SourceFile): void {
|
||||
exports.forEach(e => {
|
||||
let exportFrom = '';
|
||||
const isDtsFile = isDtsPath(entryPointBasePath);
|
||||
const from = isDtsFile ? e.dtsFrom : e.from;
|
||||
|
||||
if (from) {
|
||||
const basePath = stripExtension(from);
|
||||
const relativePath =
|
||||
'./' + PathSegment.relative(AbsoluteFsPath.dirname(entryPointBasePath), basePath);
|
||||
exportFrom = entryPointBasePath !== basePath ? ` from '${relativePath}'` : '';
|
||||
}
|
||||
|
||||
// aliases should only be added in dts files as these are lost when rolling up dts file.
|
||||
const exportStatement = e.alias && isDtsFile ? `${e.alias} as ${e.identifier}` : e.identifier;
|
||||
const exportStr = `\nexport {${exportStatement}}${exportFrom};`;
|
||||
output.append(exportStr);
|
||||
});
|
||||
}
|
||||
|
||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||
if (constants === '') {
|
||||
return;
|
||||
}
|
||||
const insertionPoint = findEndOfImports(file);
|
||||
|
||||
// Append the constants to the right of the insertion point, to ensure they get ordered after
|
||||
// added imports (those are appended left to the insertion point).
|
||||
output.appendRight(insertionPoint, '\n' + constants + '\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the definitions to each decorated class
|
||||
*/
|
||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void {
|
||||
const classSymbol = this.host.getClassSymbol(compiledClass.declaration);
|
||||
if (!classSymbol) {
|
||||
throw new Error(`Compiled class does not have a valid symbol: ${compiledClass.name}`);
|
||||
}
|
||||
const insertionPoint = classSymbol.valueDeclaration !.getEnd();
|
||||
output.appendLeft(insertionPoint, '\n' + definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove static decorator properties from classes
|
||||
*/
|
||||
removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap): void {
|
||||
decoratorsToRemove.forEach((nodesToRemove, containerNode) => {
|
||||
if (ts.isArrayLiteralExpression(containerNode)) {
|
||||
const items = containerNode.elements;
|
||||
if (items.length === nodesToRemove.length) {
|
||||
// Remove the entire statement
|
||||
const statement = findStatement(containerNode);
|
||||
if (statement) {
|
||||
output.remove(statement.getFullStart(), statement.getEnd());
|
||||
}
|
||||
} else {
|
||||
nodesToRemove.forEach(node => {
|
||||
// remove any trailing comma
|
||||
const end = (output.slice(node.getEnd(), node.getEnd() + 1) === ',') ?
|
||||
node.getEnd() + 1 :
|
||||
node.getEnd();
|
||||
output.remove(node.getFullStart(), end);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rewriteSwitchableDeclarations(
|
||||
outputText: MagicString, sourceFile: ts.SourceFile,
|
||||
declarations: SwitchableVariableDeclaration[]): void {
|
||||
declarations.forEach(declaration => {
|
||||
const start = declaration.initializer.getStart();
|
||||
const end = declaration.initializer.getEnd();
|
||||
const replacement = declaration.initializer.text.replace(PRE_R3_MARKER, POST_R3_MARKER);
|
||||
outputText.overwrite(start, end, replacement);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function findEndOfImports(sf: ts.SourceFile): number {
|
||||
for (const stmt of sf.statements) {
|
||||
if (!ts.isImportDeclaration(stmt) && !ts.isImportEqualsDeclaration(stmt) &&
|
||||
!ts.isNamespaceImport(stmt)) {
|
||||
return stmt.getStart();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function findStatement(node: ts.Node) {
|
||||
while (node) {
|
||||
if (ts.isExpressionStatement(node)) {
|
||||
return node;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
/**
|
||||
* @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 MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {PathSegment, AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
|
||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||
import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
import {ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyzer';
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {RenderingFormatter, RedundantDecoratorMap} from './rendering_formatter';
|
||||
import {stripExtension} from './utils';
|
||||
|
||||
/**
|
||||
* A RenderingFormatter that works with ECMAScript Module import and export statements.
|
||||
*/
|
||||
export class EsmRenderingFormatter implements RenderingFormatter {
|
||||
constructor(protected host: NgccReflectionHost, protected isCore: boolean) {}
|
||||
|
||||
/**
|
||||
* Add the imports at the top of the file, after any imports that are already there.
|
||||
*/
|
||||
addImports(output: MagicString, imports: Import[], sf: ts.SourceFile): void {
|
||||
const insertionPoint = this.findEndOfImports(sf);
|
||||
const renderedImports =
|
||||
imports.map(i => `import * as ${i.qualifier} from '${i.specifier}';\n`).join('');
|
||||
output.appendLeft(insertionPoint, renderedImports);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the exports to the end of the file.
|
||||
*/
|
||||
addExports(
|
||||
output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[],
|
||||
importManager: ImportManager, file: ts.SourceFile): void {
|
||||
exports.forEach(e => {
|
||||
let exportFrom = '';
|
||||
const isDtsFile = isDtsPath(entryPointBasePath);
|
||||
const from = isDtsFile ? e.dtsFrom : e.from;
|
||||
|
||||
if (from) {
|
||||
const basePath = stripExtension(from);
|
||||
const relativePath =
|
||||
'./' + PathSegment.relative(AbsoluteFsPath.dirname(entryPointBasePath), basePath);
|
||||
exportFrom = entryPointBasePath !== basePath ? ` from '${relativePath}'` : '';
|
||||
}
|
||||
|
||||
// aliases should only be added in dts files as these are lost when rolling up dts file.
|
||||
const exportStatement = e.alias && isDtsFile ? `${e.alias} as ${e.identifier}` : e.identifier;
|
||||
const exportStr = `\nexport {${exportStatement}}${exportFrom};`;
|
||||
output.append(exportStr);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constants directly after the imports.
|
||||
*/
|
||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||
if (constants === '') {
|
||||
return;
|
||||
}
|
||||
const insertionPoint = this.findEndOfImports(file);
|
||||
|
||||
// Append the constants to the right of the insertion point, to ensure they get ordered after
|
||||
// added imports (those are appended left to the insertion point).
|
||||
output.appendRight(insertionPoint, '\n' + constants + '\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the definitions directly after their decorated class.
|
||||
*/
|
||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void {
|
||||
const classSymbol = this.host.getClassSymbol(compiledClass.declaration);
|
||||
if (!classSymbol) {
|
||||
throw new Error(`Compiled class does not have a valid symbol: ${compiledClass.name}`);
|
||||
}
|
||||
const insertionPoint = classSymbol.valueDeclaration !.getEnd();
|
||||
output.appendLeft(insertionPoint, '\n' + definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove static decorator properties from classes.
|
||||
*/
|
||||
removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap): void {
|
||||
decoratorsToRemove.forEach((nodesToRemove, containerNode) => {
|
||||
if (ts.isArrayLiteralExpression(containerNode)) {
|
||||
const items = containerNode.elements;
|
||||
if (items.length === nodesToRemove.length) {
|
||||
// Remove the entire statement
|
||||
const statement = findStatement(containerNode);
|
||||
if (statement) {
|
||||
output.remove(statement.getFullStart(), statement.getEnd());
|
||||
}
|
||||
} else {
|
||||
nodesToRemove.forEach(node => {
|
||||
// remove any trailing comma
|
||||
const end = (output.slice(node.getEnd(), node.getEnd() + 1) === ',') ?
|
||||
node.getEnd() + 1 :
|
||||
node.getEnd();
|
||||
output.remove(node.getFullStart(), end);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite the the IVY switch markers to indicate we are in IVY mode.
|
||||
*/
|
||||
rewriteSwitchableDeclarations(
|
||||
outputText: MagicString, sourceFile: ts.SourceFile,
|
||||
declarations: SwitchableVariableDeclaration[]): void {
|
||||
declarations.forEach(declaration => {
|
||||
const start = declaration.initializer.getStart();
|
||||
const end = declaration.initializer.getEnd();
|
||||
const replacement = declaration.initializer.text.replace(PRE_R3_MARKER, POST_R3_MARKER);
|
||||
outputText.overwrite(start, end, replacement);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add the type parameters to the appropriate functions that return `ModuleWithProviders`
|
||||
* structures.
|
||||
*
|
||||
* This function will only get called on typings files.
|
||||
*/
|
||||
addModuleWithProvidersParams(
|
||||
outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[],
|
||||
importManager: ImportManager): void {
|
||||
moduleWithProviders.forEach(info => {
|
||||
const ngModuleName = info.ngModule.node.name.text;
|
||||
const declarationFile = AbsoluteFsPath.fromSourceFile(info.declaration.getSourceFile());
|
||||
const ngModuleFile = AbsoluteFsPath.fromSourceFile(info.ngModule.node.getSourceFile());
|
||||
const importPath = info.ngModule.viaModule ||
|
||||
(declarationFile !== ngModuleFile ?
|
||||
stripExtension(
|
||||
`./${PathSegment.relative(AbsoluteFsPath.dirname(declarationFile), ngModuleFile)}`) :
|
||||
null);
|
||||
const ngModule = generateImportString(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,
|
||||
`: ${generateImportString(importManager, '@angular/core', 'ModuleWithProviders')}<${ngModule}>`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected findEndOfImports(sf: ts.SourceFile): number {
|
||||
for (const stmt of sf.statements) {
|
||||
if (!ts.isImportDeclaration(stmt) && !ts.isImportEqualsDeclaration(stmt) &&
|
||||
!ts.isNamespaceImport(stmt)) {
|
||||
return stmt.getStart();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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'));
|
||||
}
|
||||
}
|
||||
|
||||
function findStatement(node: ts.Node) {
|
||||
while (node) {
|
||||
if (ts.isExpressionStatement(node)) {
|
||||
return node;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function generateImportString(
|
||||
importManager: ImportManager, importPath: string | null, importName: string) {
|
||||
const importAs = importPath ? importManager.generateNamedImport(importPath, importName) : null;
|
||||
return importAs ? `${importAs.moduleImport}.${importAs.symbol}` : `${importName}`;
|
||||
}
|
|
@ -6,72 +6,22 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ConstantPool, Expression, Statement, WrappedNodeExpr, WritePropExpr} from '@angular/compiler';
|
||||
import {SourceMapConverter, commentRegex, fromJSON, fromObject, fromSource, generateMapFileComment, mapFileCommentRegex, removeComments, removeMapFileComments} from 'convert-source-map';
|
||||
import MagicString from 'magic-string';
|
||||
import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {NoopImportRewriter, ImportRewriter, R3SymbolsImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER} from '../../../src/ngtsc/imports';
|
||||
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
|
||||
import {CompileResult} from '../../../src/ngtsc/transform';
|
||||
import {translateStatement, translateType, Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {NOOP_DEFAULT_IMPORT_RECORDER} from '@angular/compiler-cli/src/ngtsc/imports';
|
||||
import {translateStatement, ImportManager} from '../../../src/ngtsc/translator';
|
||||
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} from '../analysis/private_declarations_analyzer';
|
||||
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
||||
import {IMPORT_PREFIX} from '../constants';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {NgccFlatImportRewriter} from './ngcc_import_rewriter';
|
||||
|
||||
interface SourceMapInfo {
|
||||
source: string;
|
||||
map: SourceMapConverter|null;
|
||||
isInline: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a file that has been rendered.
|
||||
*/
|
||||
export interface FileInfo {
|
||||
/**
|
||||
* Path to where the file should be written.
|
||||
*/
|
||||
path: AbsoluteFsPath;
|
||||
/**
|
||||
* The contents of the file to be be written.
|
||||
*/
|
||||
contents: string;
|
||||
}
|
||||
|
||||
interface DtsClassInfo {
|
||||
dtsDeclaration: ts.Declaration;
|
||||
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
|
||||
* of Ivy static fields. The map is keyed by the container node, such that we
|
||||
* can tell if we should remove the entire decorator property
|
||||
*/
|
||||
export type RedundantDecoratorMap = Map<ts.Node, ts.Node[]>;
|
||||
export const RedundantDecoratorMap = Map;
|
||||
import {Logger} from '../logging/logger';
|
||||
import {FileToWrite, getImportRewriter, stripExtension} from './utils';
|
||||
import {RenderingFormatter, RedundantDecoratorMap} from './rendering_formatter';
|
||||
import {extractSourceMap, renderSourceAndMap} from './source_maps';
|
||||
|
||||
/**
|
||||
* A base-class for rendering an `AnalyzedFile`.
|
||||
|
@ -79,42 +29,28 @@ export const RedundantDecoratorMap = Map;
|
|||
* Package formats have output files that must be rendered differently. Concrete sub-classes must
|
||||
* implement the `addImports`, `addDefinitions` and `removeDecorators` abstract methods.
|
||||
*/
|
||||
export abstract class Renderer {
|
||||
export class Renderer {
|
||||
constructor(
|
||||
protected fs: FileSystem, protected logger: Logger, protected host: NgccReflectionHost,
|
||||
protected isCore: boolean, protected bundle: EntryPointBundle) {}
|
||||
private srcFormatter: RenderingFormatter, private fs: FileSystem, private logger: Logger,
|
||||
private host: NgccReflectionHost, private isCore: boolean, private bundle: EntryPointBundle) {
|
||||
}
|
||||
|
||||
renderProgram(
|
||||
decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses,
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null): FileInfo[] {
|
||||
const renderedFiles: FileInfo[] = [];
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileToWrite[] {
|
||||
const renderedFiles: FileToWrite[] = [];
|
||||
|
||||
// Transform the source files.
|
||||
this.bundle.src.program.getSourceFiles().forEach(sourceFile => {
|
||||
const compiledFile = decorationAnalyses.get(sourceFile);
|
||||
const switchMarkerAnalysis = switchMarkerAnalyses.get(sourceFile);
|
||||
|
||||
if (compiledFile || switchMarkerAnalysis || sourceFile === this.bundle.src.file) {
|
||||
if (decorationAnalyses.has(sourceFile) || switchMarkerAnalyses.has(sourceFile) ||
|
||||
sourceFile === this.bundle.src.file) {
|
||||
const compiledFile = decorationAnalyses.get(sourceFile);
|
||||
const switchMarkerAnalysis = switchMarkerAnalyses.get(sourceFile);
|
||||
renderedFiles.push(...this.renderFile(
|
||||
sourceFile, compiledFile, switchMarkerAnalysis, privateDeclarationsAnalyses));
|
||||
}
|
||||
});
|
||||
|
||||
// Transform the .d.ts files
|
||||
if (this.bundle.dts) {
|
||||
const dtsFiles = this.getTypingsFilesToRender(
|
||||
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
|
||||
|
||||
// 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.
|
||||
if (!dtsFiles.has(this.bundle.dts.file)) {
|
||||
dtsFiles.set(this.bundle.dts.file, new DtsRenderInfo());
|
||||
}
|
||||
dtsFiles.forEach(
|
||||
(renderInfo, file) => renderedFiles.push(...this.renderDtsFile(file, renderInfo)));
|
||||
}
|
||||
|
||||
return renderedFiles;
|
||||
}
|
||||
|
||||
|
@ -126,32 +62,32 @@ export abstract class Renderer {
|
|||
renderFile(
|
||||
sourceFile: ts.SourceFile, compiledFile: CompiledFile|undefined,
|
||||
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined,
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] {
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileToWrite[] {
|
||||
const isEntryPoint = sourceFile === this.bundle.src.file;
|
||||
const input = this.extractSourceMap(sourceFile);
|
||||
const input = extractSourceMap(this.fs, this.logger, sourceFile);
|
||||
const outputText = new MagicString(input.source);
|
||||
|
||||
if (switchMarkerAnalysis) {
|
||||
this.rewriteSwitchableDeclarations(
|
||||
this.srcFormatter.rewriteSwitchableDeclarations(
|
||||
outputText, switchMarkerAnalysis.sourceFile, switchMarkerAnalysis.declarations);
|
||||
}
|
||||
|
||||
const importManager = new ImportManager(
|
||||
this.getImportRewriter(this.bundle.src.r3SymbolsFile, this.bundle.isFlatCore),
|
||||
getImportRewriter(this.bundle.src.r3SymbolsFile, this.isCore, this.bundle.isFlatCore),
|
||||
IMPORT_PREFIX);
|
||||
|
||||
if (compiledFile) {
|
||||
// TODO: remove constructor param metadata and property decorators (we need info from the
|
||||
// handlers to do this)
|
||||
const decoratorsToRemove = this.computeDecoratorsToRemove(compiledFile.compiledClasses);
|
||||
this.removeDecorators(outputText, decoratorsToRemove);
|
||||
this.srcFormatter.removeDecorators(outputText, decoratorsToRemove);
|
||||
|
||||
compiledFile.compiledClasses.forEach(clazz => {
|
||||
const renderedDefinition = renderDefinitions(compiledFile.sourceFile, clazz, importManager);
|
||||
this.addDefinitions(outputText, clazz, renderedDefinition);
|
||||
this.srcFormatter.addDefinitions(outputText, clazz, renderedDefinition);
|
||||
});
|
||||
|
||||
this.addConstants(
|
||||
this.srcFormatter.addConstants(
|
||||
outputText,
|
||||
renderConstantPool(compiledFile.sourceFile, compiledFile.constantPool, importManager),
|
||||
compiledFile.sourceFile);
|
||||
|
@ -160,115 +96,22 @@ export abstract class Renderer {
|
|||
// Add exports to the entry-point file
|
||||
if (isEntryPoint) {
|
||||
const entryPointBasePath = stripExtension(this.bundle.src.path);
|
||||
this.addExports(
|
||||
this.srcFormatter.addExports(
|
||||
outputText, entryPointBasePath, privateDeclarationsAnalyses, importManager, sourceFile);
|
||||
}
|
||||
|
||||
if (isEntryPoint || compiledFile) {
|
||||
this.addImports(outputText, importManager.getAllImports(sourceFile.fileName), sourceFile);
|
||||
this.srcFormatter.addImports(
|
||||
outputText, importManager.getAllImports(sourceFile.fileName), sourceFile);
|
||||
}
|
||||
|
||||
if (compiledFile || switchMarkerAnalysis || isEntryPoint) {
|
||||
return this.renderSourceAndMap(sourceFile, input, outputText);
|
||||
return renderSourceAndMap(sourceFile, input, outputText);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileInfo[] {
|
||||
const input = this.extractSourceMap(dtsFile);
|
||||
const outputText = new MagicString(input.source);
|
||||
const printer = createPrinter();
|
||||
const importManager = new ImportManager(
|
||||
this.getImportRewriter(this.bundle.dts !.r3SymbolsFile, false), IMPORT_PREFIX);
|
||||
|
||||
renderInfo.classInfo.forEach(dtsClass => {
|
||||
const endOfClass = dtsClass.dtsDeclaration.getEnd();
|
||||
dtsClass.compilation.forEach(declaration => {
|
||||
const type = translateType(declaration.type, importManager);
|
||||
const typeStr = printer.printNode(ts.EmitHint.Unspecified, type, dtsFile);
|
||||
const newStatement = ` static ${declaration.name}: ${typeStr};\n`;
|
||||
outputText.appendRight(endOfClass - 1, newStatement);
|
||||
});
|
||||
});
|
||||
|
||||
this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager);
|
||||
this.addImports(outputText, importManager.getAllImports(dtsFile.fileName), dtsFile);
|
||||
|
||||
this.addExports(
|
||||
outputText, AbsoluteFsPath.fromSourceFile(dtsFile), renderInfo.privateExports,
|
||||
importManager, dtsFile);
|
||||
|
||||
|
||||
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: ImportManager): void {
|
||||
moduleWithProviders.forEach(info => {
|
||||
const ngModuleName = info.ngModule.node.name.text;
|
||||
const declarationFile = AbsoluteFsPath.fromSourceFile(info.declaration.getSourceFile());
|
||||
const ngModuleFile = AbsoluteFsPath.fromSourceFile(info.ngModule.node.getSourceFile());
|
||||
const importPath = info.ngModule.viaModule ||
|
||||
(declarationFile !== ngModuleFile ?
|
||||
stripExtension(
|
||||
`./${PathSegment.relative(AbsoluteFsPath.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):
|
||||
void;
|
||||
protected abstract addImports(output: MagicString, imports: Import[], sf: ts.SourceFile): void;
|
||||
protected abstract addExports(
|
||||
output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[],
|
||||
importManager: ImportManager, file: ts.SourceFile): void;
|
||||
protected abstract addDefinitions(
|
||||
output: MagicString, compiledClass: CompiledClass, definitions: string): void;
|
||||
protected abstract removeDecorators(
|
||||
output: MagicString, decoratorsToRemove: RedundantDecoratorMap): void;
|
||||
protected abstract rewriteSwitchableDeclarations(
|
||||
outputText: MagicString, sourceFile: ts.SourceFile,
|
||||
declarations: SwitchableVariableDeclaration[]): void;
|
||||
|
||||
/**
|
||||
* From the given list of classes, computes a map of decorators that should be removed.
|
||||
* The decorators to remove are keyed by their container node, such that we can tell if
|
||||
|
@ -276,7 +119,7 @@ export abstract class Renderer {
|
|||
* @param classes The list of classes that may have decorators to remove.
|
||||
* @returns A map of decorators to remove, keyed by their container node.
|
||||
*/
|
||||
protected computeDecoratorsToRemove(classes: CompiledClass[]): RedundantDecoratorMap {
|
||||
private computeDecoratorsToRemove(classes: CompiledClass[]): RedundantDecoratorMap {
|
||||
const decoratorsToRemove = new RedundantDecoratorMap();
|
||||
classes.forEach(clazz => {
|
||||
clazz.decorators.forEach(dec => {
|
||||
|
@ -290,191 +133,6 @@ export abstract class Renderer {
|
|||
});
|
||||
return decoratorsToRemove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map from the source (note whether it is inline or external)
|
||||
*/
|
||||
protected extractSourceMap(file: ts.SourceFile): SourceMapInfo {
|
||||
const inline = commentRegex.test(file.text);
|
||||
const external = mapFileCommentRegex.exec(file.text);
|
||||
|
||||
if (inline) {
|
||||
const inlineSourceMap = fromSource(file.text);
|
||||
return {
|
||||
source: removeComments(file.text).replace(/\n\n$/, '\n'),
|
||||
map: inlineSourceMap,
|
||||
isInline: true,
|
||||
};
|
||||
} else if (external) {
|
||||
let externalSourceMap: SourceMapConverter|null = null;
|
||||
try {
|
||||
const fileName = external[1] || external[2];
|
||||
const filePath = AbsoluteFsPath.resolve(
|
||||
AbsoluteFsPath.dirname(AbsoluteFsPath.fromSourceFile(file)), fileName);
|
||||
const mappingFile = this.fs.readFile(filePath);
|
||||
externalSourceMap = fromJSON(mappingFile);
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
this.logger.warn(
|
||||
`The external map file specified in the source code comment "${e.path}" was not found on the file system.`);
|
||||
const mapPath = AbsoluteFsPath.fromUnchecked(file.fileName + '.map');
|
||||
if (PathSegment.basename(e.path) !== PathSegment.basename(mapPath) &&
|
||||
this.fs.stat(mapPath).isFile()) {
|
||||
this.logger.warn(
|
||||
`Guessing the map file name from the source file name: "${PathSegment.basename(mapPath)}"`);
|
||||
try {
|
||||
externalSourceMap = fromObject(JSON.parse(this.fs.readFile(mapPath)));
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
source: removeMapFileComments(file.text).replace(/\n\n$/, '\n'),
|
||||
map: externalSourceMap,
|
||||
isInline: false,
|
||||
};
|
||||
} else {
|
||||
return {source: file.text, map: null, isInline: false};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the input and output source-maps, replacing the source-map comment in the output file
|
||||
* with an appropriate source-map comment pointing to the merged source-map.
|
||||
*/
|
||||
protected renderSourceAndMap(
|
||||
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString): FileInfo[] {
|
||||
const outputPath = AbsoluteFsPath.fromSourceFile(sourceFile);
|
||||
const outputMapPath = AbsoluteFsPath.fromUnchecked(`${outputPath}.map`);
|
||||
const relativeSourcePath = PathSegment.basename(outputPath);
|
||||
const relativeMapPath = `${relativeSourcePath}.map`;
|
||||
|
||||
const outputMap = output.generateMap({
|
||||
source: outputPath,
|
||||
includeContent: true,
|
||||
// hires: true // TODO: This results in accurate but huge sourcemaps. Instead we should fix
|
||||
// the merge algorithm.
|
||||
});
|
||||
|
||||
// we must set this after generation as magic string does "manipulation" on the path
|
||||
outputMap.file = relativeSourcePath;
|
||||
|
||||
const mergedMap =
|
||||
mergeSourceMaps(input.map && input.map.toObject(), JSON.parse(outputMap.toString()));
|
||||
|
||||
const result: FileInfo[] = [];
|
||||
if (input.isInline) {
|
||||
result.push({path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`});
|
||||
} else {
|
||||
result.push({
|
||||
path: outputPath,
|
||||
contents: `${output.toString()}\n${generateMapFileComment(relativeMapPath)}`
|
||||
});
|
||||
result.push({path: outputMapPath, contents: mergedMap.toJSON()});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getTypingsFilesToRender(
|
||||
decorationAnalyses: DecorationAnalyses,
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
|
||||
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 => {
|
||||
const dtsDeclaration = this.host.getDtsDeclaration(compiledClass.declaration);
|
||||
if (dtsDeclaration) {
|
||||
const dtsFile = dtsDeclaration.getSourceFile();
|
||||
const renderInfo = dtsMap.get(dtsFile) || new DtsRenderInfo();
|
||||
renderInfo.classInfo.push({dtsDeclaration, compilation: compiledClass.compilation});
|
||||
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) {
|
||||
privateDeclarationsAnalyses.forEach(e => {
|
||||
if (!e.dtsFrom && !e.alias) {
|
||||
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.`);
|
||||
}
|
||||
});
|
||||
const dtsEntryPoint = this.bundle.dts !.file;
|
||||
const renderInfo = dtsMap.get(dtsEntryPoint) || new DtsRenderInfo();
|
||||
renderInfo.privateExports = privateDeclarationsAnalyses;
|
||||
dtsMap.set(dtsEntryPoint, renderInfo);
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
private getImportRewriter(r3SymbolsFile: ts.SourceFile|null, isFlat: boolean): ImportRewriter {
|
||||
if (this.isCore && isFlat) {
|
||||
return new NgccFlatImportRewriter();
|
||||
} else if (this.isCore) {
|
||||
return new R3SymbolsImportRewriter(r3SymbolsFile !.fileName);
|
||||
} else {
|
||||
return new NoopImportRewriter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the two specified source-maps into a single source-map that hides the intermediate
|
||||
* source-map.
|
||||
* E.g. Consider these mappings:
|
||||
*
|
||||
* ```
|
||||
* OLD_SRC -> OLD_MAP -> INTERMEDIATE_SRC -> NEW_MAP -> NEW_SRC
|
||||
* ```
|
||||
*
|
||||
* this will be replaced with:
|
||||
*
|
||||
* ```
|
||||
* OLD_SRC -> MERGED_MAP -> NEW_SRC
|
||||
* ```
|
||||
*/
|
||||
export function mergeSourceMaps(
|
||||
oldMap: RawSourceMap | null, newMap: RawSourceMap): SourceMapConverter {
|
||||
if (!oldMap) {
|
||||
return fromObject(newMap);
|
||||
}
|
||||
const oldMapConsumer = new SourceMapConsumer(oldMap);
|
||||
const newMapConsumer = new SourceMapConsumer(newMap);
|
||||
const mergedMapGenerator = SourceMapGenerator.fromSourceMap(newMapConsumer);
|
||||
mergedMapGenerator.applySourceMap(oldMapConsumer);
|
||||
const merged = fromJSON(mergedMapGenerator.toString());
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -515,10 +173,6 @@ export function renderDefinitions(
|
|||
return definitions;
|
||||
}
|
||||
|
||||
export function stripExtension<T extends string>(filePath: T): T {
|
||||
return filePath.replace(/\.(js|d\.ts)$/, '') as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Angular AST statement node that contains the assignment of the
|
||||
* compiled decorator to be applied to the class.
|
||||
|
@ -530,12 +184,6 @@ function createAssignmentStatement(
|
|||
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}`;
|
||||
}
|
||||
|
||||
function createPrinter(): ts.Printer {
|
||||
return ts.createPrinter({newLine: ts.NewLineKind.LineFeed});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* @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 MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||
import {SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
import {ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyzer';
|
||||
|
||||
/**
|
||||
* 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
|
||||
* can tell if we should remove the entire decorator property
|
||||
*/
|
||||
export type RedundantDecoratorMap = Map<ts.Node, ts.Node[]>;
|
||||
export const RedundantDecoratorMap = Map;
|
||||
|
||||
/**
|
||||
* Implement this interface with methods that know how to render a specific format,
|
||||
* such as ESM5 or UMD.
|
||||
*/
|
||||
export interface RenderingFormatter {
|
||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void;
|
||||
addImports(output: MagicString, imports: Import[], sf: ts.SourceFile): void;
|
||||
addExports(
|
||||
output: MagicString, entryPointBasePath: string, exports: ExportInfo[],
|
||||
importManager: ImportManager, file: ts.SourceFile): void;
|
||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void;
|
||||
removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap): void;
|
||||
rewriteSwitchableDeclarations(
|
||||
outputText: MagicString, sourceFile: ts.SourceFile,
|
||||
declarations: SwitchableVariableDeclaration[]): void;
|
||||
addModuleWithProvidersParams(
|
||||
outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[],
|
||||
importManager: ImportManager): void;
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* @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 {SourceMapConverter, commentRegex, fromJSON, fromObject, fromSource, generateMapFileComment, mapFileCommentRegex, removeComments, removeMapFileComments} from 'convert-source-map';
|
||||
import MagicString from 'magic-string';
|
||||
import {RawSourceMap, SourceMapConsumer, SourceMapGenerator} from 'source-map';
|
||||
import * as ts from 'typescript';
|
||||
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {FileToWrite} from './utils';
|
||||
|
||||
export interface SourceMapInfo {
|
||||
source: string;
|
||||
map: SourceMapConverter|null;
|
||||
isInline: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map from the source (note whether it is inline or external)
|
||||
*/
|
||||
export function extractSourceMap(
|
||||
fs: FileSystem, logger: Logger, file: ts.SourceFile): SourceMapInfo {
|
||||
const inline = commentRegex.test(file.text);
|
||||
const external = mapFileCommentRegex.exec(file.text);
|
||||
|
||||
if (inline) {
|
||||
const inlineSourceMap = fromSource(file.text);
|
||||
return {
|
||||
source: removeComments(file.text).replace(/\n\n$/, '\n'),
|
||||
map: inlineSourceMap,
|
||||
isInline: true,
|
||||
};
|
||||
} else if (external) {
|
||||
let externalSourceMap: SourceMapConverter|null = null;
|
||||
try {
|
||||
const fileName = external[1] || external[2];
|
||||
const filePath = AbsoluteFsPath.resolve(
|
||||
AbsoluteFsPath.dirname(AbsoluteFsPath.fromSourceFile(file)), fileName);
|
||||
const mappingFile = fs.readFile(filePath);
|
||||
externalSourceMap = fromJSON(mappingFile);
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
logger.warn(
|
||||
`The external map file specified in the source code comment "${e.path}" was not found on the file system.`);
|
||||
const mapPath = AbsoluteFsPath.fromUnchecked(file.fileName + '.map');
|
||||
if (PathSegment.basename(e.path) !== PathSegment.basename(mapPath) && fs.exists(mapPath) &&
|
||||
fs.stat(mapPath).isFile()) {
|
||||
logger.warn(
|
||||
`Guessing the map file name from the source file name: "${PathSegment.basename(mapPath)}"`);
|
||||
try {
|
||||
externalSourceMap = fromObject(JSON.parse(fs.readFile(mapPath)));
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
source: removeMapFileComments(file.text).replace(/\n\n$/, '\n'),
|
||||
map: externalSourceMap,
|
||||
isInline: false,
|
||||
};
|
||||
} else {
|
||||
return {source: file.text, map: null, isInline: false};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the input and output source-maps, replacing the source-map comment in the output file
|
||||
* with an appropriate source-map comment pointing to the merged source-map.
|
||||
*/
|
||||
export function renderSourceAndMap(
|
||||
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString): FileToWrite[] {
|
||||
const outputPath = AbsoluteFsPath.fromSourceFile(sourceFile);
|
||||
const outputMapPath = AbsoluteFsPath.fromUnchecked(`${outputPath}.map`);
|
||||
const relativeSourcePath = PathSegment.basename(outputPath);
|
||||
const relativeMapPath = `${relativeSourcePath}.map`;
|
||||
|
||||
const outputMap = output.generateMap({
|
||||
source: outputPath,
|
||||
includeContent: true,
|
||||
// hires: true // TODO: This results in accurate but huge sourcemaps. Instead we should fix
|
||||
// the merge algorithm.
|
||||
});
|
||||
|
||||
// we must set this after generation as magic string does "manipulation" on the path
|
||||
outputMap.file = relativeSourcePath;
|
||||
|
||||
const mergedMap =
|
||||
mergeSourceMaps(input.map && input.map.toObject(), JSON.parse(outputMap.toString()));
|
||||
|
||||
const result: FileToWrite[] = [];
|
||||
if (input.isInline) {
|
||||
result.push({path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`});
|
||||
} else {
|
||||
result.push({
|
||||
path: outputPath,
|
||||
contents: `${output.toString()}\n${generateMapFileComment(relativeMapPath)}`
|
||||
});
|
||||
result.push({path: outputMapPath, contents: mergedMap.toJSON()});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Merge the two specified source-maps into a single source-map that hides the intermediate
|
||||
* source-map.
|
||||
* E.g. Consider these mappings:
|
||||
*
|
||||
* ```
|
||||
* OLD_SRC -> OLD_MAP -> INTERMEDIATE_SRC -> NEW_MAP -> NEW_SRC
|
||||
* ```
|
||||
*
|
||||
* this will be replaced with:
|
||||
*
|
||||
* ```
|
||||
* OLD_SRC -> MERGED_MAP -> NEW_SRC
|
||||
* ```
|
||||
*/
|
||||
export function mergeSourceMaps(
|
||||
oldMap: RawSourceMap | null, newMap: RawSourceMap): SourceMapConverter {
|
||||
if (!oldMap) {
|
||||
return fromObject(newMap);
|
||||
}
|
||||
const oldMapConsumer = new SourceMapConsumer(oldMap);
|
||||
const newMapConsumer = new SourceMapConsumer(newMap);
|
||||
const mergedMapGenerator = SourceMapGenerator.fromSourceMap(newMapConsumer);
|
||||
mergedMapGenerator.applySourceMap(oldMapConsumer);
|
||||
const merged = fromJSON(mergedMapGenerator.toString());
|
||||
return merged;
|
||||
}
|
|
@ -10,25 +10,23 @@ import * as ts from 'typescript';
|
|||
import MagicString from 'magic-string';
|
||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {UmdReflectionHost} from '../host/umd_host';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {Esm5Renderer} from './esm5_renderer';
|
||||
import {stripExtension} from './renderer';
|
||||
import {Esm5RenderingFormatter} from './esm5_rendering_formatter';
|
||||
import {stripExtension} from './utils';
|
||||
|
||||
type CommonJsConditional = ts.ConditionalExpression & {whenTrue: ts.CallExpression};
|
||||
type AmdConditional = ts.ConditionalExpression & {whenTrue: ts.CallExpression};
|
||||
|
||||
export class UmdRenderer extends Esm5Renderer {
|
||||
constructor(
|
||||
fs: FileSystem, logger: Logger, protected umdHost: UmdReflectionHost, isCore: boolean,
|
||||
bundle: EntryPointBundle) {
|
||||
super(fs, logger, umdHost, isCore, bundle);
|
||||
}
|
||||
/**
|
||||
* A RenderingFormatter that works with UMD files, instead of `import` and `export` statements
|
||||
* the module is an IIFE with a factory function call with dependencies, which are defined in a
|
||||
* wrapper function for AMD, CommonJS and global module formats.
|
||||
*/
|
||||
export class UmdRenderingFormatter extends Esm5RenderingFormatter {
|
||||
constructor(protected umdHost: UmdReflectionHost, isCore: boolean) { super(umdHost, isCore); }
|
||||
|
||||
/**
|
||||
* Add the imports at the top of the file
|
||||
* Add the imports to the UMD module IIFE.
|
||||
*/
|
||||
addImports(output: MagicString, imports: Import[], file: ts.SourceFile): void {
|
||||
// Assume there is only one UMD module in the file
|
||||
|
@ -46,6 +44,9 @@ export class UmdRenderer extends Esm5Renderer {
|
|||
renderFactoryParameters(output, wrapperFunction, imports);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the exports to the bottom of the UMD module factory function.
|
||||
*/
|
||||
addExports(
|
||||
output: MagicString, entryPointBasePath: string, exports: ExportInfo[],
|
||||
importManager: ImportManager, file: ts.SourceFile): void {
|
||||
|
@ -70,6 +71,9 @@ export class UmdRenderer extends Esm5Renderer {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constants to the top of the UMD factory function.
|
||||
*/
|
||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||
if (constants === '') {
|
||||
return;
|
||||
|
@ -86,6 +90,9 @@ export class UmdRenderer extends Esm5Renderer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dependencies to the CommonJS part of the UMD wrapper function.
|
||||
*/
|
||||
function renderCommonJsDependencies(
|
||||
output: MagicString, wrapperFunction: ts.FunctionExpression, imports: Import[]) {
|
||||
const conditional = find(wrapperFunction.body.statements[0], isCommonJSConditional);
|
||||
|
@ -98,6 +105,9 @@ function renderCommonJsDependencies(
|
|||
imports.forEach(i => output.appendLeft(injectionPoint, `,require('${i.specifier}')`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dependencies to the AMD part of the UMD wrapper function.
|
||||
*/
|
||||
function renderAmdDependencies(
|
||||
output: MagicString, wrapperFunction: ts.FunctionExpression, imports: Import[]) {
|
||||
const conditional = find(wrapperFunction.body.statements[0], isAmdConditional);
|
||||
|
@ -113,17 +123,23 @@ function renderAmdDependencies(
|
|||
imports.forEach(i => output.appendLeft(injectionPoint, `,'${i.specifier}'`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dependencies to the global part of the UMD wrapper function.
|
||||
*/
|
||||
function renderGlobalDependencies(
|
||||
output: MagicString, wrapperFunction: ts.FunctionExpression, imports: Import[]) {
|
||||
const globalFactoryCall = find(wrapperFunction.body.statements[0], isGlobalFactoryCall);
|
||||
if (!globalFactoryCall) {
|
||||
return;
|
||||
}
|
||||
const injectionPoint = globalFactoryCall.getEnd() -
|
||||
1; // Backup one char to account for the closing parenthesis on the call
|
||||
// Backup one char to account for the closing parenthesis after the argument list of the call.
|
||||
const injectionPoint = globalFactoryCall.getEnd() - 1;
|
||||
imports.forEach(i => output.appendLeft(injectionPoint, `,global.${getGlobalIdentifier(i)}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dependency parameters to the UMD factory function.
|
||||
*/
|
||||
function renderFactoryParameters(
|
||||
output: MagicString, wrapperFunction: ts.FunctionExpression, imports: Import[]) {
|
||||
const wrapperCall = wrapperFunction.parent as ts.CallExpression;
|
||||
|
@ -143,6 +159,9 @@ function renderFactoryParameters(
|
|||
imports.forEach(i => output.appendLeft(injectionPoint, `,${i.qualifier}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this node the CommonJS conditional expression in the UMD wrapper?
|
||||
*/
|
||||
function isCommonJSConditional(value: ts.Node): value is CommonJsConditional {
|
||||
if (!ts.isConditionalExpression(value)) {
|
||||
return false;
|
||||
|
@ -160,6 +179,9 @@ function isCommonJSConditional(value: ts.Node): value is CommonJsConditional {
|
|||
return value.whenTrue.expression.text === 'factory';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this node the AMD conditional expression in the UMD wrapper?
|
||||
*/
|
||||
function isAmdConditional(value: ts.Node): value is AmdConditional {
|
||||
if (!ts.isConditionalExpression(value)) {
|
||||
return false;
|
||||
|
@ -177,6 +199,9 @@ function isAmdConditional(value: ts.Node): value is AmdConditional {
|
|||
return value.whenTrue.expression.text === 'define';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this node the call to setup the global dependencies in the UMD wrapper?
|
||||
*/
|
||||
function isGlobalFactoryCall(value: ts.Node): value is ts.CallExpression {
|
||||
if (ts.isCallExpression(value) && !!value.parent) {
|
||||
// Be resilient to the value being inside parentheses
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* @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 {ImportRewriter, NoopImportRewriter, R3SymbolsImportRewriter} from '../../../src/ngtsc/imports';
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {NgccFlatImportRewriter} from './ngcc_import_rewriter';
|
||||
|
||||
/**
|
||||
* Information about a file that has been rendered.
|
||||
*/
|
||||
export interface FileToWrite {
|
||||
/** Path to where the file should be written. */
|
||||
path: AbsoluteFsPath;
|
||||
/** The contents of the file to be be written. */
|
||||
contents: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an appropriate ImportRewriter given the parameters.
|
||||
*/
|
||||
export function getImportRewriter(
|
||||
r3SymbolsFile: ts.SourceFile | null, isCore: boolean, isFlat: boolean): ImportRewriter {
|
||||
if (isCore && isFlat) {
|
||||
return new NgccFlatImportRewriter();
|
||||
} else if (isCore) {
|
||||
return new R3SymbolsImportRewriter(r3SymbolsFile !.fileName);
|
||||
} else {
|
||||
return new NoopImportRewriter();
|
||||
}
|
||||
}
|
||||
|
||||
export function stripExtension<T extends string>(filePath: T): T {
|
||||
return filePath.replace(/\.(js|d\.ts)$/, '') as T;
|
||||
}
|
|
@ -8,12 +8,13 @@
|
|||
*/
|
||||
import {EntryPoint} from '../packages/entry_point';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {FileInfo} from '../rendering/renderer';
|
||||
import {FileToWrite} from '../rendering/utils';
|
||||
|
||||
|
||||
/**
|
||||
* Responsible for writing out the transformed files to disk.
|
||||
*/
|
||||
export interface FileWriter {
|
||||
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileInfo[]): void;
|
||||
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileToWrite[]):
|
||||
void;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
|||
import {FileSystem} from '../file_system/file_system';
|
||||
import {EntryPoint} from '../packages/entry_point';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {FileInfo} from '../rendering/renderer';
|
||||
import {FileToWrite} from '../rendering/utils';
|
||||
import {FileWriter} from './file_writer';
|
||||
|
||||
/**
|
||||
|
@ -20,11 +20,11 @@ import {FileWriter} from './file_writer';
|
|||
export class InPlaceFileWriter implements FileWriter {
|
||||
constructor(protected fs: FileSystem) {}
|
||||
|
||||
writeBundle(_entryPoint: EntryPoint, _bundle: EntryPointBundle, transformedFiles: FileInfo[]) {
|
||||
writeBundle(_entryPoint: EntryPoint, _bundle: EntryPointBundle, transformedFiles: FileToWrite[]) {
|
||||
transformedFiles.forEach(file => this.writeFileAndBackup(file));
|
||||
}
|
||||
|
||||
protected writeFileAndBackup(file: FileInfo): void {
|
||||
protected writeFileAndBackup(file: FileToWrite): void {
|
||||
this.fs.ensureDir(AbsoluteFsPath.dirname(file.path));
|
||||
const backPath = AbsoluteFsPath.fromUnchecked(`${file.path}.__ivy_ngcc_bak`);
|
||||
if (this.fs.exists(backPath)) {
|
||||
|
|
|
@ -10,7 +10,7 @@ import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
|
|||
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
|
||||
import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {FileInfo} from '../rendering/renderer';
|
||||
import {FileToWrite} from '../rendering/utils';
|
||||
|
||||
import {InPlaceFileWriter} from './in_place_file_writer';
|
||||
|
||||
|
@ -25,7 +25,7 @@ const NGCC_DIRECTORY = '__ivy_ngcc__';
|
|||
* `InPlaceFileWriter`).
|
||||
*/
|
||||
export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileInfo[]) {
|
||||
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileToWrite[]) {
|
||||
// The new folder is at the root of the overall package
|
||||
const ngccFolder = AbsoluteFsPath.join(entryPoint.package, NGCC_DIRECTORY);
|
||||
this.copyBundle(bundle, entryPoint.package, ngccFolder);
|
||||
|
@ -47,7 +47,7 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
|||
});
|
||||
}
|
||||
|
||||
protected writeFile(file: FileInfo, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath):
|
||||
protected writeFile(file: FileToWrite, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath):
|
||||
void {
|
||||
if (isDtsPath(file.path.replace(/\.map$/, ''))) {
|
||||
// This is either `.d.ts` or `.d.ts.map` file
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/**
|
||||
* @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 MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {fromObject} from 'convert-source-map';
|
||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||
import {ModuleWithProvidersAnalyzer, ModuleWithProvidersInfo} from '../../src/analysis/module_with_providers_analyzer';
|
||||
import {PrivateDeclarationsAnalyzer, ExportInfo} from '../../src/analysis/private_declarations_analyzer';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {DtsRenderer} from '../../src/rendering/dts_renderer';
|
||||
import {makeTestEntryPointBundle, createFileSystemFromProgramFiles} from '../helpers/utils';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {RenderingFormatter, RedundantDecoratorMap} from '../../src/rendering/rendering_formatter';
|
||||
import {MockFileSystem} from '../helpers/mock_file_system';
|
||||
import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/path';
|
||||
|
||||
const _ = AbsoluteFsPath.fromUnchecked;
|
||||
|
||||
class TestRenderingFormatter implements RenderingFormatter {
|
||||
addImports(output: MagicString, imports: Import[], sf: ts.SourceFile) {
|
||||
output.prepend('\n// ADD IMPORTS\n');
|
||||
}
|
||||
addExports(output: MagicString, baseEntryPointPath: string, exports: ExportInfo[]) {
|
||||
output.prepend('\n// ADD EXPORTS\n');
|
||||
}
|
||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||
output.prepend('\n// ADD CONSTANTS\n');
|
||||
}
|
||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string) {
|
||||
output.prepend('\n// ADD DEFINITIONS\n');
|
||||
}
|
||||
removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap) {
|
||||
output.prepend('\n// REMOVE DECORATORS\n');
|
||||
}
|
||||
rewriteSwitchableDeclarations(output: MagicString, sourceFile: ts.SourceFile): void {
|
||||
output.prepend('\n// REWRITTEN DECLARATIONS\n');
|
||||
}
|
||||
addModuleWithProvidersParams(
|
||||
output: MagicString, moduleWithProviders: ModuleWithProvidersInfo[],
|
||||
importManager: ImportManager): void {
|
||||
output.prepend('\n// ADD MODUlE WITH PROVIDERS PARAMS\n');
|
||||
}
|
||||
}
|
||||
|
||||
function createTestRenderer(
|
||||
packageName: string, files: {name: string, contents: string}[],
|
||||
dtsFiles?: {name: string, contents: string}[],
|
||||
mappingFiles?: {name: string, contents: string}[]) {
|
||||
const logger = new MockLogger();
|
||||
const fs = new MockFileSystem(createFileSystemFromProgramFiles(files, dtsFiles, mappingFiles));
|
||||
const isCore = packageName === '@angular/core';
|
||||
const bundle = makeTestEntryPointBundle('es2015', 'esm2015', isCore, files, dtsFiles);
|
||||
const typeChecker = bundle.src.program.getTypeChecker();
|
||||
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
const decorationAnalyses = new DecorationAnalyzer(
|
||||
fs, bundle.src.program, bundle.src.options, bundle.src.host,
|
||||
typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
|
||||
.analyzeProgram();
|
||||
const moduleWithProvidersAnalyses =
|
||||
new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
||||
const privateDeclarationsAnalyses =
|
||||
new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
||||
const testFormatter = new TestRenderingFormatter();
|
||||
spyOn(testFormatter, 'addExports').and.callThrough();
|
||||
spyOn(testFormatter, 'addImports').and.callThrough();
|
||||
spyOn(testFormatter, 'addDefinitions').and.callThrough();
|
||||
spyOn(testFormatter, 'addConstants').and.callThrough();
|
||||
spyOn(testFormatter, 'removeDecorators').and.callThrough();
|
||||
spyOn(testFormatter, 'rewriteSwitchableDeclarations').and.callThrough();
|
||||
spyOn(testFormatter, 'addModuleWithProvidersParams').and.callThrough();
|
||||
|
||||
const renderer = new DtsRenderer(testFormatter, fs, logger, host, isCore, bundle);
|
||||
|
||||
return {renderer,
|
||||
testFormatter,
|
||||
decorationAnalyses,
|
||||
moduleWithProvidersAnalyses,
|
||||
privateDeclarationsAnalyses,
|
||||
bundle};
|
||||
}
|
||||
|
||||
|
||||
describe('DtsRenderer', () => {
|
||||
const INPUT_PROGRAM = {
|
||||
name: '/src/file.js',
|
||||
contents:
|
||||
`import { Directive } from '@angular/core';\nexport class A {\n foo(x) {\n return x;\n }\n}\nA.decorators = [\n { type: Directive, args: [{ selector: '[a]' }] }\n];\n`
|
||||
};
|
||||
const INPUT_DTS_PROGRAM = {
|
||||
name: '/typings/file.d.ts',
|
||||
contents: `export declare class A {\nfoo(x: number): number;\n}\n`
|
||||
};
|
||||
|
||||
const INPUT_PROGRAM_MAP = fromObject({
|
||||
'version': 3,
|
||||
'file': '/src/file.js',
|
||||
'sourceRoot': '',
|
||||
'sources': ['/src/file.ts'],
|
||||
'names': [],
|
||||
'mappings':
|
||||
'AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,MAAM;IACF,GAAG,CAAC,CAAS;QACT,OAAO,CAAC,CAAC;IACb,CAAC;;AACM,YAAU,GAAG;IAChB,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE;CACnD,CAAC',
|
||||
'sourcesContent': [INPUT_PROGRAM.contents]
|
||||
});
|
||||
|
||||
const RENDERED_CONTENTS = `
|
||||
// ADD IMPORTS
|
||||
|
||||
// ADD EXPORTS
|
||||
|
||||
// ADD CONSTANTS
|
||||
|
||||
// ADD DEFINITIONS
|
||||
|
||||
// REMOVE DECORATORS
|
||||
` + INPUT_PROGRAM.contents;
|
||||
|
||||
const MERGED_OUTPUT_PROGRAM_MAP = fromObject({
|
||||
'version': 3,
|
||||
'sources': ['/src/file.ts'],
|
||||
'names': [],
|
||||
'mappings': ';;;;;;;;;;AAAA',
|
||||
'file': 'file.js',
|
||||
'sourcesContent': [INPUT_PROGRAM.contents]
|
||||
});
|
||||
|
||||
it('should render extract types into typings files', () => {
|
||||
const {renderer, decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||
expect(typingsFile.contents)
|
||||
.toContain(
|
||||
'foo(x: number): number;\n static ngDirectiveDef: ɵngcc0.ΔDirectiveDefWithMeta');
|
||||
});
|
||||
|
||||
it('should render imports into typings files', () => {
|
||||
const {renderer, decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||
expect(typingsFile.contents).toContain(`\n// ADD IMPORTS\n`);
|
||||
});
|
||||
|
||||
it('should render exports into typings files', () => {
|
||||
const {renderer, decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||
|
||||
// Add a mock export to trigger export rendering
|
||||
privateDeclarationsAnalyses.push(
|
||||
{identifier: 'ComponentB', from: _('/src/file.js'), dtsFrom: _('/typings/b.d.ts')});
|
||||
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||
expect(typingsFile.contents).toContain(`\n// ADD EXPORTS\n`);
|
||||
});
|
||||
|
||||
it('should render ModuleWithProviders type params', () => {
|
||||
const {renderer, decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||
expect(typingsFile.contents).toContain(`\n// ADD MODUlE WITH PROVIDERS PARAMS\n`);
|
||||
});
|
||||
});
|
|
@ -15,7 +15,7 @@ import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registr
|
|||
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||
import {IMPORT_PREFIX} from '../../src/constants';
|
||||
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
||||
import {Esm5Renderer} from '../../src/rendering/esm5_renderer';
|
||||
import {Esm5RenderingFormatter} from '../../src/rendering/esm5_rendering_formatter';
|
||||
import {makeTestEntryPointBundle, getDeclaration} from '../helpers/utils';
|
||||
import {MockFileSystem} from '../helpers/mock_file_system';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
|
@ -35,7 +35,7 @@ function setup(file: {name: AbsoluteFsPath, contents: string}) {
|
|||
referencesRegistry, [AbsoluteFsPath.fromUnchecked('/')], false)
|
||||
.analyzeProgram();
|
||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
|
||||
const renderer = new Esm5Renderer(fs, logger, host, false, bundle);
|
||||
const renderer = new Esm5RenderingFormatter(host, false);
|
||||
const importManager = new ImportManager(new NoopImportRewriter(), IMPORT_PREFIX);
|
||||
return {
|
||||
host,
|
||||
|
@ -155,7 +155,7 @@ export { D };
|
|||
// Some other content`
|
||||
};
|
||||
|
||||
describe('Esm5Renderer', () => {
|
||||
describe('Esm5RenderingFormatter', () => {
|
||||
|
||||
describe('addImports', () => {
|
||||
it('should insert the given imports after existing imports of the source file', () => {
|
|
@ -15,29 +15,33 @@ import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registr
|
|||
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||
import {IMPORT_PREFIX} from '../../src/constants';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {EsmRenderer} from '../../src/rendering/esm_renderer';
|
||||
import {EsmRenderingFormatter} from '../../src/rendering/esm_rendering_formatter';
|
||||
import {makeTestEntryPointBundle} from '../helpers/utils';
|
||||
import {MockFileSystem} from '../helpers/mock_file_system';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
|
||||
|
||||
const _ = AbsoluteFsPath.fromUnchecked;
|
||||
|
||||
function setup(file: {name: AbsoluteFsPath, contents: string}) {
|
||||
function setup(
|
||||
files: {name: string, contents: string}[],
|
||||
dtsFiles?: {name: string, contents: string, isRoot?: boolean}[]) {
|
||||
const fs = new MockFileSystem();
|
||||
const logger = new MockLogger();
|
||||
const bundle = makeTestEntryPointBundle('es2015', 'esm2015', false, [file]) !;
|
||||
const bundle = makeTestEntryPointBundle('es2015', 'esm2015', false, files, dtsFiles) !;
|
||||
const typeChecker = bundle.src.program.getTypeChecker();
|
||||
const host = new Esm2015ReflectionHost(logger, false, typeChecker);
|
||||
const host = new Esm2015ReflectionHost(logger, false, typeChecker, bundle.dts);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
const decorationAnalyses = new DecorationAnalyzer(
|
||||
fs, bundle.src.program, bundle.src.options, bundle.src.host,
|
||||
typeChecker, host, referencesRegistry, [_('/')], false)
|
||||
.analyzeProgram();
|
||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
|
||||
const renderer = new EsmRenderer(fs, logger, host, false, bundle);
|
||||
const renderer = new EsmRenderingFormatter(host, false);
|
||||
const importManager = new ImportManager(new NoopImportRewriter(), IMPORT_PREFIX);
|
||||
return {
|
||||
host,
|
||||
bundle,
|
||||
program: bundle.src.program,
|
||||
sourceFile: bundle.src.file, renderer, decorationAnalyses, switchMarkerAnalyses, importManager,
|
||||
};
|
||||
|
@ -79,9 +83,207 @@ function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {
|
|||
// Some other content`
|
||||
};
|
||||
|
||||
const PROGRAM_DECORATE_HELPER = {
|
||||
name: _('/some/file.js'),
|
||||
contents: `
|
||||
describe('EsmRenderingFormatter', () => {
|
||||
|
||||
describe('addImports', () => {
|
||||
it('should insert the given imports after existing imports of the source file', () => {
|
||||
const {renderer, sourceFile} = setup([PROGRAM]);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addImports(
|
||||
output,
|
||||
[
|
||||
{specifier: '@angular/core', qualifier: 'i0'},
|
||||
{specifier: '@angular/common', qualifier: 'i1'}
|
||||
],
|
||||
sourceFile);
|
||||
expect(output.toString()).toContain(`/* A copyright notice */
|
||||
import 'some-side-effect';
|
||||
import {Directive} from '@angular/core';
|
||||
import * as i0 from '@angular/core';
|
||||
import * as i1 from '@angular/common';`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addExports', () => {
|
||||
it('should insert the given exports at the end of the source file', () => {
|
||||
const {importManager, renderer, sourceFile} = setup([PROGRAM]);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addExports(
|
||||
output, _(PROGRAM.name.replace(/\.js$/, '')),
|
||||
[
|
||||
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'},
|
||||
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'},
|
||||
{from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'},
|
||||
{from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'},
|
||||
],
|
||||
importManager, sourceFile);
|
||||
expect(output.toString()).toContain(`
|
||||
// Some other content
|
||||
export {ComponentA1} from './a';
|
||||
export {ComponentA2} from './a';
|
||||
export {ComponentB} from './foo/b';
|
||||
export {TopLevelComponent};`);
|
||||
});
|
||||
|
||||
it('should not insert alias exports in js output', () => {
|
||||
const {importManager, renderer, sourceFile} = setup([PROGRAM]);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addExports(
|
||||
output, _(PROGRAM.name.replace(/\.js$/, '')),
|
||||
[
|
||||
{from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'},
|
||||
{from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'},
|
||||
{from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'},
|
||||
{from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'},
|
||||
],
|
||||
importManager, sourceFile);
|
||||
const outputString = output.toString();
|
||||
expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`);
|
||||
expect(outputString).not.toContain(`{eComponentB as ComponentB}`);
|
||||
expect(outputString).not.toContain(`{eTopLevelComponent as TopLevelComponent}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addConstants', () => {
|
||||
it('should insert the given constants after imports in the source file', () => {
|
||||
const {renderer, program} = setup([PROGRAM]);
|
||||
const file = program.getSourceFile('some/file.js');
|
||||
if (file === undefined) {
|
||||
throw new Error(`Could not find source file`);
|
||||
}
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addConstants(output, 'const x = 3;', file);
|
||||
expect(output.toString()).toContain(`
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
const x = 3;
|
||||
export class A {}`);
|
||||
});
|
||||
|
||||
it('should insert constants after inserted imports', () => {
|
||||
const {renderer, program} = setup([PROGRAM]);
|
||||
const file = program.getSourceFile('some/file.js');
|
||||
if (file === undefined) {
|
||||
throw new Error(`Could not find source file`);
|
||||
}
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addConstants(output, 'const x = 3;', file);
|
||||
renderer.addImports(output, [{specifier: '@angular/core', qualifier: 'i0'}], file);
|
||||
expect(output.toString()).toContain(`
|
||||
import {Directive} from '@angular/core';
|
||||
import * as i0 from '@angular/core';
|
||||
|
||||
const x = 3;
|
||||
export class A {`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rewriteSwitchableDeclarations', () => {
|
||||
it('should switch marked declaration initializers', () => {
|
||||
const {renderer, program, switchMarkerAnalyses, sourceFile} = setup([PROGRAM]);
|
||||
const file = program.getSourceFile('some/file.js');
|
||||
if (file === undefined) {
|
||||
throw new Error(`Could not find source file`);
|
||||
}
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.rewriteSwitchableDeclarations(
|
||||
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
|
||||
expect(output.toString())
|
||||
.not.toContain(`let compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;`);
|
||||
expect(output.toString())
|
||||
.toContain(`let badlyFormattedVariable = __PRE_R3__badlyFormattedVariable;`);
|
||||
expect(output.toString())
|
||||
.toContain(`let compileNgModuleFactory = compileNgModuleFactory__POST_R3__;`);
|
||||
expect(output.toString())
|
||||
.toContain(`function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {`);
|
||||
expect(output.toString())
|
||||
.toContain(`function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addDefinitions', () => {
|
||||
it('should insert the definitions directly after the class declaration', () => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup([PROGRAM]);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT');
|
||||
expect(output.toString()).toContain(`
|
||||
export class A {}
|
||||
SOME DEFINITION TEXT
|
||||
A.decorators = [
|
||||
`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('removeDecorators', () => {
|
||||
describe('[static property declaration]', () => {
|
||||
it('should delete the decorator (and following comma) that was matched in the analysis',
|
||||
() => {
|
||||
const {decorationAnalyses, sourceFile, renderer} = setup([PROGRAM]);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
expect(output.toString())
|
||||
.not.toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`);
|
||||
expect(output.toString()).toContain(`{ type: OtherA }`);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`);
|
||||
expect(output.toString()).toContain(`{ type: OtherB }`);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`);
|
||||
});
|
||||
|
||||
|
||||
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
|
||||
() => {
|
||||
const {decorationAnalyses, sourceFile, renderer} = setup([PROGRAM]);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`);
|
||||
expect(output.toString()).toContain(`{ type: OtherA }`);
|
||||
expect(output.toString())
|
||||
.not.toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`);
|
||||
expect(output.toString()).toContain(`{ type: OtherB }`);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`);
|
||||
});
|
||||
|
||||
|
||||
it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis',
|
||||
() => {
|
||||
const {decorationAnalyses, sourceFile, renderer} = setup([PROGRAM]);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`);
|
||||
expect(output.toString()).toContain(`{ type: OtherA }`);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`);
|
||||
expect(output.toString()).toContain(`{ type: OtherB }`);
|
||||
expect(output.toString())
|
||||
.not.toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`);
|
||||
expect(output.toString()).not.toContain(`C.decorators = [`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[__decorate declarations]', () => {
|
||||
|
||||
const PROGRAM_DECORATE_HELPER = {
|
||||
name: '/some/file.js',
|
||||
contents: `
|
||||
import * as tslib_1 from "tslib";
|
||||
var D_1;
|
||||
/* A copyright notice */
|
||||
|
@ -115,207 +317,10 @@ D = D_1 = tslib_1.__decorate([
|
|||
], D);
|
||||
export { D };
|
||||
// Some other content`
|
||||
};
|
||||
};
|
||||
|
||||
describe('Esm2015Renderer', () => {
|
||||
|
||||
describe('addImports', () => {
|
||||
it('should insert the given imports after existing imports of the source file', () => {
|
||||
const {renderer, sourceFile} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addImports(
|
||||
output,
|
||||
[
|
||||
{specifier: '@angular/core', qualifier: 'i0'},
|
||||
{specifier: '@angular/common', qualifier: 'i1'}
|
||||
],
|
||||
sourceFile);
|
||||
expect(output.toString()).toContain(`/* A copyright notice */
|
||||
import 'some-side-effect';
|
||||
import {Directive} from '@angular/core';
|
||||
import * as i0 from '@angular/core';
|
||||
import * as i1 from '@angular/common';`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addExports', () => {
|
||||
it('should insert the given exports at the end of the source file', () => {
|
||||
const {importManager, renderer, sourceFile} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addExports(
|
||||
output, _(PROGRAM.name.replace(/\.js$/, '')),
|
||||
[
|
||||
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'},
|
||||
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'},
|
||||
{from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'},
|
||||
{from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'},
|
||||
],
|
||||
importManager, sourceFile);
|
||||
expect(output.toString()).toContain(`
|
||||
// Some other content
|
||||
export {ComponentA1} from './a';
|
||||
export {ComponentA2} from './a';
|
||||
export {ComponentB} from './foo/b';
|
||||
export {TopLevelComponent};`);
|
||||
});
|
||||
|
||||
it('should not insert alias exports in js output', () => {
|
||||
const {importManager, renderer, sourceFile} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addExports(
|
||||
output, _(PROGRAM.name.replace(/\.js$/, '')),
|
||||
[
|
||||
{from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'},
|
||||
{from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'},
|
||||
{from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'},
|
||||
{from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'},
|
||||
],
|
||||
importManager, sourceFile);
|
||||
const outputString = output.toString();
|
||||
expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`);
|
||||
expect(outputString).not.toContain(`{eComponentB as ComponentB}`);
|
||||
expect(outputString).not.toContain(`{eTopLevelComponent as TopLevelComponent}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addConstants', () => {
|
||||
it('should insert the given constants after imports in the source file', () => {
|
||||
const {renderer, program} = setup(PROGRAM);
|
||||
const file = program.getSourceFile('some/file.js');
|
||||
if (file === undefined) {
|
||||
throw new Error(`Could not find source file`);
|
||||
}
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addConstants(output, 'const x = 3;', file);
|
||||
expect(output.toString()).toContain(`
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
const x = 3;
|
||||
export class A {}`);
|
||||
});
|
||||
|
||||
it('should insert constants after inserted imports', () => {
|
||||
const {renderer, program} = setup(PROGRAM);
|
||||
const file = program.getSourceFile('some/file.js');
|
||||
if (file === undefined) {
|
||||
throw new Error(`Could not find source file`);
|
||||
}
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addConstants(output, 'const x = 3;', file);
|
||||
renderer.addImports(output, [{specifier: '@angular/core', qualifier: 'i0'}], file);
|
||||
expect(output.toString()).toContain(`
|
||||
import {Directive} from '@angular/core';
|
||||
import * as i0 from '@angular/core';
|
||||
|
||||
const x = 3;
|
||||
export class A {`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rewriteSwitchableDeclarations', () => {
|
||||
it('should switch marked declaration initializers', () => {
|
||||
const {renderer, program, switchMarkerAnalyses, sourceFile} = setup(PROGRAM);
|
||||
const file = program.getSourceFile('some/file.js');
|
||||
if (file === undefined) {
|
||||
throw new Error(`Could not find source file`);
|
||||
}
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.rewriteSwitchableDeclarations(
|
||||
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
|
||||
expect(output.toString())
|
||||
.not.toContain(`let compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;`);
|
||||
expect(output.toString())
|
||||
.toContain(`let badlyFormattedVariable = __PRE_R3__badlyFormattedVariable;`);
|
||||
expect(output.toString())
|
||||
.toContain(`let compileNgModuleFactory = compileNgModuleFactory__POST_R3__;`);
|
||||
expect(output.toString())
|
||||
.toContain(`function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {`);
|
||||
expect(output.toString())
|
||||
.toContain(`function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addDefinitions', () => {
|
||||
it('should insert the definitions directly after the class declaration', () => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT');
|
||||
expect(output.toString()).toContain(`
|
||||
export class A {}
|
||||
SOME DEFINITION TEXT
|
||||
A.decorators = [
|
||||
`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('removeDecorators', () => {
|
||||
describe('[static property declaration]', () => {
|
||||
it('should delete the decorator (and following comma) that was matched in the analysis',
|
||||
() => {
|
||||
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
expect(output.toString())
|
||||
.not.toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`);
|
||||
expect(output.toString()).toContain(`{ type: OtherA }`);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`);
|
||||
expect(output.toString()).toContain(`{ type: OtherB }`);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`);
|
||||
});
|
||||
|
||||
|
||||
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
|
||||
() => {
|
||||
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`);
|
||||
expect(output.toString()).toContain(`{ type: OtherA }`);
|
||||
expect(output.toString())
|
||||
.not.toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`);
|
||||
expect(output.toString()).toContain(`{ type: OtherB }`);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`);
|
||||
});
|
||||
|
||||
|
||||
it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis',
|
||||
() => {
|
||||
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`);
|
||||
expect(output.toString()).toContain(`{ type: OtherA }`);
|
||||
expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`);
|
||||
expect(output.toString()).toContain(`{ type: OtherB }`);
|
||||
expect(output.toString())
|
||||
.not.toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`);
|
||||
expect(output.toString()).not.toContain(`C.decorators = [`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[__decorate declarations]', () => {
|
||||
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup([PROGRAM_DECORATE_HELPER]);
|
||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
|
@ -332,7 +337,7 @@ A.decorators = [
|
|||
|
||||
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup([PROGRAM_DECORATE_HELPER]);
|
||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||
|
@ -350,7 +355,7 @@ A.decorators = [
|
|||
|
||||
it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis',
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup([PROGRAM_DECORATE_HELPER]);
|
||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||
|
@ -367,4 +372,140 @@ A.decorators = [
|
|||
expect(output.toString()).toContain(`let C = class C {\n};\nexport { C };`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addModuleWithProvidersParams', () => {
|
||||
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 {}'
|
||||
},
|
||||
];
|
||||
|
||||
it('should fixup functions/methods that return ModuleWithProviders structures', () => {
|
||||
const {bundle, renderer, host} =
|
||||
setup(MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM);
|
||||
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
const moduleWithProvidersAnalyses = new ModuleWithProvidersAnalyzer(host, referencesRegistry)
|
||||
.analyzeProgram(bundle.src.program);
|
||||
const typingsFile = bundle.dts !.program.getSourceFile('/typings/index.d.ts') !;
|
||||
const moduleWithProvidersInfo = moduleWithProvidersAnalyses.get(typingsFile) !;
|
||||
|
||||
const output = new MagicString(MODULE_WITH_PROVIDERS_DTS_PROGRAM[0].contents);
|
||||
const importManager = new ImportManager(new NoopImportRewriter(), 'i');
|
||||
renderer.addModuleWithProvidersParams(output, moduleWithProvidersInfo, importManager);
|
||||
|
||||
expect(output.toString()).toContain(`
|
||||
static withProviders1(): ModuleWithProviders<SomeModule>;
|
||||
static withProviders2(): ModuleWithProviders<SomeModule>;
|
||||
static withProviders3(): ModuleWithProviders<SomeClass>;
|
||||
static withProviders4(): ModuleWithProviders<i0.ExternalModule>;
|
||||
static withProviders5(): i1.ModuleWithProviders<i0.ExternalModule>;
|
||||
static withProviders6(): ModuleWithProviders<i2.LibraryModule>;
|
||||
static withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule};
|
||||
static withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`);
|
||||
expect(output.toString()).toContain(`
|
||||
export declare function withProviders1(): ModuleWithProviders<SomeModule>;
|
||||
export declare function withProviders2(): ModuleWithProviders<SomeModule>;
|
||||
export declare function withProviders3(): ModuleWithProviders<SomeClass>;
|
||||
export declare function withProviders4(): ModuleWithProviders<i0.ExternalModule>;
|
||||
export declare function withProviders5(): i1.ModuleWithProviders<i0.ExternalModule>;
|
||||
export declare function withProviders6(): ModuleWithProviders<i2.LibraryModule>;
|
||||
export declare function withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule};
|
||||
export declare function withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`);
|
||||
});
|
||||
|
||||
it('should not mistake `ModuleWithProviders` types that are not imported from `@angular/core',
|
||||
() => {
|
||||
const {bundle, renderer, host} =
|
||||
setup(MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM);
|
||||
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
const moduleWithProvidersAnalyses =
|
||||
new ModuleWithProvidersAnalyzer(host, referencesRegistry)
|
||||
.analyzeProgram(bundle.src.program);
|
||||
const typingsFile = bundle.dts !.program.getSourceFile('/typings/module.d.ts') !;
|
||||
const moduleWithProvidersInfo = moduleWithProvidersAnalyses.get(typingsFile) !;
|
||||
|
||||
const output = new MagicString(MODULE_WITH_PROVIDERS_DTS_PROGRAM[1].contents);
|
||||
const importManager = new ImportManager(new NoopImportRewriter(), 'i');
|
||||
renderer.addModuleWithProvidersParams(output, moduleWithProvidersInfo, importManager);
|
||||
expect(output.toString()).toContain(`
|
||||
static withProviders1(): (ModuleWithProviders)&{ngModule:ExternalModule};
|
||||
static withProviders2(): (ModuleWithProviders)&{ngModule:ExternalModule};`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -9,29 +9,22 @@ import MagicString from 'magic-string';
|
|||
import * as ts from 'typescript';
|
||||
import {fromObject, generateMapFileComment} from 'convert-source-map';
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {Import} from '../../../src/ngtsc/translator';
|
||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||
import {ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
|
||||
import {ModuleWithProvidersInfo} from '../../src/analysis/module_with_providers_analyzer';
|
||||
import {PrivateDeclarationsAnalyzer, ExportInfo} from '../../src/analysis/private_declarations_analyzer';
|
||||
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {RedundantDecoratorMap, Renderer} from '../../src/rendering/renderer';
|
||||
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
||||
import {makeTestEntryPointBundle, createFileSystemFromProgramFiles} from '../helpers/utils';
|
||||
import {Logger} from '../../src/logging/logger';
|
||||
import {MockFileSystem} from '../helpers/mock_file_system';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {FileSystem} from '../../src/file_system/file_system';
|
||||
|
||||
const _ = AbsoluteFsPath.fromUnchecked;
|
||||
|
||||
class TestRenderer extends Renderer {
|
||||
constructor(
|
||||
fs: FileSystem, logger: Logger, host: Esm2015ReflectionHost, isCore: boolean,
|
||||
bundle: EntryPointBundle) {
|
||||
super(fs, logger, host, isCore, bundle);
|
||||
}
|
||||
import {Renderer} from '../../src/rendering/renderer';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {RenderingFormatter, RedundantDecoratorMap} from '../../src/rendering/rendering_formatter';
|
||||
import {makeTestEntryPointBundle, createFileSystemFromProgramFiles} from '../helpers/utils';
|
||||
import {MockFileSystem} from '../helpers/mock_file_system';
|
||||
|
||||
class TestRenderingFormatter implements RenderingFormatter {
|
||||
addImports(output: MagicString, imports: Import[], sf: ts.SourceFile) {
|
||||
output.prepend('\n// ADD IMPORTS\n');
|
||||
}
|
||||
|
@ -50,6 +43,11 @@ class TestRenderer extends Renderer {
|
|||
rewriteSwitchableDeclarations(output: MagicString, sourceFile: ts.SourceFile): void {
|
||||
output.prepend('\n// REWRITTEN DECLARATIONS\n');
|
||||
}
|
||||
addModuleWithProvidersParams(
|
||||
output: MagicString, moduleWithProviders: ModuleWithProvidersInfo[],
|
||||
importManager: ImportManager): void {
|
||||
output.prepend('\n// ADD MODUlE WITH PROVIDERS PARAMS\n');
|
||||
}
|
||||
}
|
||||
|
||||
function createTestRenderer(
|
||||
|
@ -68,21 +66,23 @@ function createTestRenderer(
|
|||
typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
|
||||
.analyzeProgram();
|
||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
|
||||
const moduleWithProvidersAnalyses =
|
||||
new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
||||
const privateDeclarationsAnalyses =
|
||||
new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
||||
const renderer = new TestRenderer(fs, logger, host, isCore, bundle);
|
||||
spyOn(renderer, 'addExports').and.callThrough();
|
||||
spyOn(renderer, 'addImports').and.callThrough();
|
||||
spyOn(renderer, 'addDefinitions').and.callThrough();
|
||||
spyOn(renderer, 'addConstants').and.callThrough();
|
||||
spyOn(renderer, 'removeDecorators').and.callThrough();
|
||||
const testFormatter = new TestRenderingFormatter();
|
||||
spyOn(testFormatter, 'addExports').and.callThrough();
|
||||
spyOn(testFormatter, 'addImports').and.callThrough();
|
||||
spyOn(testFormatter, 'addDefinitions').and.callThrough();
|
||||
spyOn(testFormatter, 'addConstants').and.callThrough();
|
||||
spyOn(testFormatter, 'removeDecorators').and.callThrough();
|
||||
spyOn(testFormatter, 'rewriteSwitchableDeclarations').and.callThrough();
|
||||
spyOn(testFormatter, 'addModuleWithProvidersParams').and.callThrough();
|
||||
|
||||
const renderer = new Renderer(testFormatter, fs, logger, host, isCore, bundle);
|
||||
|
||||
return {renderer,
|
||||
testFormatter,
|
||||
decorationAnalyses,
|
||||
switchMarkerAnalyses,
|
||||
moduleWithProvidersAnalyses,
|
||||
privateDeclarationsAnalyses,
|
||||
bundle};
|
||||
}
|
||||
|
@ -94,10 +94,6 @@ describe('Renderer', () => {
|
|||
contents:
|
||||
`import { Directive } from '@angular/core';\nexport class A {\n foo(x) {\n return x;\n }\n}\nA.decorators = [\n { type: Directive, args: [{ selector: '[a]' }] }\n];\n`
|
||||
};
|
||||
const INPUT_DTS_PROGRAM = {
|
||||
name: '/typings/file.d.ts',
|
||||
contents: `export declare class A {\nfoo(x: number): number;\n}\n`
|
||||
};
|
||||
|
||||
const COMPONENT_PROGRAM = {
|
||||
name: '/src/component.js',
|
||||
|
@ -149,11 +145,10 @@ describe('Renderer', () => {
|
|||
describe('renderProgram()', () => {
|
||||
it('should render the modified contents; and a new map file, if the original provided no map file.',
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} = createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
expect(result[0].path).toEqual('/src/file.js');
|
||||
expect(result[0].contents)
|
||||
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('file.js.map'));
|
||||
|
@ -164,11 +159,9 @@ describe('Renderer', () => {
|
|||
|
||||
it('should render as JavaScript', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} = createTestRenderer('test-package', [COMPONENT_PROGRAM]);
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||
testFormatter} = createTestRenderer('test-package', [COMPONENT_PROGRAM]);
|
||||
renderer.renderProgram(decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy;
|
||||
expect(addDefinitionsSpy.calls.first().args[2])
|
||||
.toEqual(
|
||||
`A.ngComponentDef = ɵngcc0.ΔdefineComponent({ type: A, selectors: [["a"]], factory: function A_Factory(t) { return new (t || A)(); }, consts: 1, vars: 1, template: function A_Template(rf, ctx) { if (rf & 1) {
|
||||
|
@ -184,16 +177,14 @@ describe('Renderer', () => {
|
|||
});
|
||||
|
||||
|
||||
describe('calling abstract methods', () => {
|
||||
describe('calling RenderingFormatter methods', () => {
|
||||
it('should call addImports with the source code and info about the core Angular library.',
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
testFormatter} = createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const addImportsSpy = renderer.addImports as jasmine.Spy;
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const addImportsSpy = testFormatter.addImports as jasmine.Spy;
|
||||
expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||
expect(addImportsSpy.calls.first().args[1]).toEqual([
|
||||
{specifier: '@angular/core', qualifier: 'ɵngcc0'}
|
||||
|
@ -203,12 +194,10 @@ describe('Renderer', () => {
|
|||
it('should call addDefinitions with the source code, the analyzed class and the rendered definitions.',
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
testFormatter} = createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy;
|
||||
expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||
expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({
|
||||
name: _('A'),
|
||||
|
@ -226,12 +215,10 @@ describe('Renderer', () => {
|
|||
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
testFormatter} = createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy;
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const removeDecoratorsSpy = testFormatter.removeDecorators as jasmine.Spy;
|
||||
expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||
|
||||
// Each map key is the TS node of the decorator container
|
||||
|
@ -251,14 +238,13 @@ describe('Renderer', () => {
|
|||
it('should call renderImports after other abstract methods', () => {
|
||||
// This allows the other methods to add additional imports if necessary
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} = createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const addExportsSpy = renderer.addExports as jasmine.Spy;
|
||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||
const addConstantsSpy = renderer.addConstants as jasmine.Spy;
|
||||
const addImportsSpy = renderer.addImports as jasmine.Spy;
|
||||
testFormatter} = createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const addExportsSpy = testFormatter.addExports as jasmine.Spy;
|
||||
const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy;
|
||||
const addConstantsSpy = testFormatter.addConstants as jasmine.Spy;
|
||||
const addImportsSpy = testFormatter.addImports as jasmine.Spy;
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
expect(addExportsSpy).toHaveBeenCalledBefore(addImportsSpy);
|
||||
expect(addDefinitionsSpy).toHaveBeenCalledBefore(addImportsSpy);
|
||||
expect(addConstantsSpy).toHaveBeenCalledBefore(addImportsSpy);
|
||||
|
@ -268,16 +254,14 @@ describe('Renderer', () => {
|
|||
describe('source map merging', () => {
|
||||
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,
|
||||
moduleWithProvidersAnalyses} =
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
createTestRenderer(
|
||||
'test-package', [{
|
||||
...INPUT_PROGRAM,
|
||||
contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment()
|
||||
}]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
expect(result[0].path).toEqual('/src/file.js');
|
||||
expect(result[0].contents)
|
||||
.toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment());
|
||||
|
@ -292,12 +276,10 @@ describe('Renderer', () => {
|
|||
}];
|
||||
const mappingFiles =
|
||||
[{name: INPUT_PROGRAM.name + '.map', contents: INPUT_PROGRAM_MAP.toJSON()}];
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
createTestRenderer('test-package', sourceFiles, undefined, mappingFiles);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
expect(result[0].path).toEqual('/src/file.js');
|
||||
expect(result[0].contents)
|
||||
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('file.js.map'));
|
||||
|
@ -320,15 +302,13 @@ describe('Renderer', () => {
|
|||
};
|
||||
// The package name of `@angular/core` indicates that we are compiling the core library.
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('@angular/core', [CORE_FILE, R3_SYMBOLS_FILE]);
|
||||
testFormatter} = createTestRenderer('@angular/core', [CORE_FILE, R3_SYMBOLS_FILE]);
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy;
|
||||
expect(addDefinitionsSpy.calls.first().args[2])
|
||||
.toContain(`/*@__PURE__*/ ɵngcc0.setClassMetadata(`);
|
||||
const addImportsSpy = renderer.addImports as jasmine.Spy;
|
||||
const addImportsSpy = testFormatter.addImports as jasmine.Spy;
|
||||
expect(addImportsSpy.calls.first().args[1]).toEqual([
|
||||
{specifier: './r3_symbols', qualifier: 'ɵngcc0'}
|
||||
]);
|
||||
|
@ -342,230 +322,15 @@ describe('Renderer', () => {
|
|||
};
|
||||
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} = createTestRenderer('@angular/core', [CORE_FILE]);
|
||||
testFormatter} = createTestRenderer('@angular/core', [CORE_FILE]);
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy;
|
||||
expect(addDefinitionsSpy.calls.first().args[2])
|
||||
.toContain(`/*@__PURE__*/ setClassMetadata(`);
|
||||
const addImportsSpy = renderer.addImports as jasmine.Spy;
|
||||
const addImportsSpy = testFormatter.addImports as jasmine.Spy;
|
||||
expect(addImportsSpy.calls.first().args[1]).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering typings', () => {
|
||||
it('should render extract types into typings files', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||
expect(typingsFile.contents)
|
||||
.toContain(
|
||||
'foo(x: number): number;\n static ngDirectiveDef: ɵngcc0.ΔDirectiveDefWithMeta');
|
||||
});
|
||||
|
||||
it('should render imports into typings files', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||
expect(typingsFile.contents).toContain(`\n// ADD IMPORTS\n`);
|
||||
});
|
||||
|
||||
it('should render exports into typings files', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||
|
||||
// Add a mock export to trigger export rendering
|
||||
privateDeclarationsAnalyses.push(
|
||||
{identifier: 'ComponentB', from: _('/src/file.js'), dtsFrom: _('/typings/b.d.ts')});
|
||||
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||
expect(typingsFile.contents).toContain(`\n// ADD EXPORTS\n`);
|
||||
});
|
||||
|
||||
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,
|
||||
bundle} =
|
||||
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),
|
||||
[
|
||||
{specifier: './module', qualifier: 'ɵngcc0'},
|
||||
{specifier: '@angular/core', qualifier: 'ɵngcc1'},
|
||||
{specifier: 'some-library', qualifier: 'ɵngcc2'},
|
||||
],
|
||||
bundle.dts !.file);
|
||||
|
||||
|
||||
// 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};`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,8 +14,8 @@ import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registr
|
|||
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||
import {UmdReflectionHost} from '../../src/host/umd_host';
|
||||
import {ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {UmdRenderer} from '../../src/rendering/umd_renderer';
|
||||
import {MockFileSystem} from '../helpers/mock_file_system';
|
||||
import {UmdRenderingFormatter} from '../../src/rendering/umd_rendering_formatter';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {getDeclaration, makeTestEntryPointBundle, createFileSystemFromProgramFiles} from '../helpers/utils';
|
||||
|
||||
|
@ -34,7 +34,7 @@ function setup(file: {name: string, contents: string}) {
|
|||
referencesRegistry, [AbsoluteFsPath.fromUnchecked('/')], false)
|
||||
.analyzeProgram();
|
||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(src.program);
|
||||
const renderer = new UmdRenderer(fs, logger, host, false, bundle);
|
||||
const renderer = new UmdRenderingFormatter(host, false);
|
||||
const importManager = new ImportManager(new NoopImportRewriter(), 'i');
|
||||
return {
|
||||
decorationAnalyses,
|
||||
|
@ -169,7 +169,7 @@ typeof define === 'function' && define.amd ? define('file', ['exports','/tslib',
|
|||
})));`
|
||||
};
|
||||
|
||||
describe('UmdRenderer', () => {
|
||||
describe('UmdRenderingFormatter', () => {
|
||||
|
||||
describe('addImports', () => {
|
||||
it('should append the given imports into the CommonJS factory call', () => {
|
Loading…
Reference in New Issue