187 lines
7.7 KiB
TypeScript
187 lines
7.7 KiB
TypeScript
/**
|
|
* @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 {FileSystem} from '../../../src/ngtsc/file_system';
|
|
import {Reexport} from '../../../src/ngtsc/imports';
|
|
import {CompileResult} from '../../../src/ngtsc/transform';
|
|
import {ImportManager, translateType} from '../../../src/ngtsc/translator';
|
|
import {ModuleWithProvidersAnalyses, ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyzer';
|
|
import {ExportInfo, PrivateDeclarationsAnalyses} from '../analysis/private_declarations_analyzer';
|
|
import {DecorationAnalyses} from '../analysis/types';
|
|
import {IMPORT_PREFIX} from '../constants';
|
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
|
import {Logger} from '../logging/logger';
|
|
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
|
|
|
import {RenderingFormatter} from './rendering_formatter';
|
|
import {renderSourceAndMap} from './source_maps';
|
|
import {FileToWrite, getImportRewriter} from './utils';
|
|
|
|
/**
|
|
* 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[] = [];
|
|
reexports: Reexport[] = [];
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 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 outputText = new MagicString(dtsFile.text);
|
|
const printer = ts.createPrinter();
|
|
const importManager = new ImportManager(
|
|
getImportRewriter(this.bundle.dts!.r3SymbolsFile, this.bundle.isCore, false),
|
|
IMPORT_PREFIX);
|
|
|
|
renderInfo.classInfo.forEach(dtsClass => {
|
|
const endOfClass = dtsClass.dtsDeclaration.getEnd();
|
|
dtsClass.compilation.forEach(declaration => {
|
|
const type = translateType(declaration.type, importManager);
|
|
markForEmitAsSingleLine(type);
|
|
const typeStr = printer.printNode(ts.EmitHint.Unspecified, type, dtsFile);
|
|
const newStatement = ` static ${declaration.name}: ${typeStr};\n`;
|
|
outputText.appendRight(endOfClass - 1, newStatement);
|
|
});
|
|
});
|
|
|
|
if (renderInfo.reexports.length > 0) {
|
|
for (const e of renderInfo.reexports) {
|
|
const newStatement = `\nexport {${e.symbolName} as ${e.asAlias}} from '${e.fromModule}';`;
|
|
outputText.append(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(this.logger, this.fs, dtsFile, 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 => {
|
|
let appliedReexports = false;
|
|
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});
|
|
// Only add re-exports if the .d.ts tree is overlayed with the .js tree, as re-exports in
|
|
// ngcc are only used to support deep imports into e.g. commonjs code. For a deep import
|
|
// to work, the typing file and JS file must be in parallel trees. This logic will detect
|
|
// the simplest version of this case, which is sufficient to handle most commonjs
|
|
// libraries.
|
|
if (!appliedReexports &&
|
|
compiledClass.declaration.getSourceFile().fileName ===
|
|
dtsFile.fileName.replace(/\.d\.ts$/, '.js')) {
|
|
renderInfo.reexports.push(...compiledFile.reexports);
|
|
appliedReexports = true;
|
|
}
|
|
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) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
function markForEmitAsSingleLine(node: ts.Node) {
|
|
ts.setEmitFlags(node, ts.EmitFlags.SingleLine);
|
|
ts.forEachChild(node, markForEmitAsSingleLine);
|
|
}
|