2018-07-16 10:23:37 +01:00
|
|
|
/**
|
|
|
|
|
* @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
|
|
|
|
|
*/
|
2018-07-27 22:36:54 +03:00
|
|
|
import {ConstantPool, Expression, Statement, WrappedNodeExpr, WritePropExpr} from '@angular/compiler';
|
|
|
|
|
import MagicString from 'magic-string';
|
2018-07-16 10:23:37 +01:00
|
|
|
import * as ts from 'typescript';
|
2019-11-04 19:29:01 +02:00
|
|
|
import {ImportManager} from '../../../src/ngtsc/translator';
|
2019-07-18 21:05:32 +01:00
|
|
|
import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/types';
|
2019-04-28 20:48:35 +01:00
|
|
|
import {PrivateDeclarationsAnalyses} from '../analysis/private_declarations_analyzer';
|
2018-10-04 12:19:11 +01:00
|
|
|
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
|
|
|
|
import {IMPORT_PREFIX} from '../constants';
|
2019-06-06 20:22:32 +01:00
|
|
|
import {FileSystem} from '../../../src/ngtsc/file_system';
|
2019-11-04 19:29:01 +02:00
|
|
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
2019-04-28 20:48:35 +01:00
|
|
|
import {Logger} from '../logging/logger';
|
2019-11-04 19:29:01 +02:00
|
|
|
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
2019-04-28 20:48:35 +01:00
|
|
|
import {RenderingFormatter, RedundantDecoratorMap} from './rendering_formatter';
|
2020-02-16 21:07:30 +01:00
|
|
|
import {renderSourceAndMap} from './source_maps';
|
2019-11-04 19:29:01 +02:00
|
|
|
import {FileToWrite, getImportRewriter, stripExtension} from './utils';
|
2018-11-18 21:37:30 +01:00
|
|
|
|
2018-07-16 10:23:37 +01:00
|
|
|
/**
|
2018-07-27 22:36:54 +03:00
|
|
|
* 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.
|
2018-07-16 10:23:37 +01:00
|
|
|
*/
|
2019-04-28 20:48:35 +01:00
|
|
|
export class Renderer {
|
2018-10-03 16:59:32 +01:00
|
|
|
constructor(
|
2019-11-01 16:55:10 +00:00
|
|
|
private host: NgccReflectionHost, private srcFormatter: RenderingFormatter,
|
|
|
|
|
private fs: FileSystem, private logger: Logger, private bundle: EntryPointBundle) {}
|
2018-10-04 12:19:11 +01:00
|
|
|
|
2018-11-25 22:07:51 +00:00
|
|
|
renderProgram(
|
|
|
|
|
decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses,
|
2019-04-28 20:48:35 +01:00
|
|
|
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileToWrite[] {
|
|
|
|
|
const renderedFiles: FileToWrite[] = [];
|
2018-10-10 17:52:55 +01:00
|
|
|
|
2018-10-16 08:56:54 +01:00
|
|
|
// Transform the source files.
|
2019-03-18 14:44:56 +00:00
|
|
|
this.bundle.src.program.getSourceFiles().forEach(sourceFile => {
|
2019-04-28 20:48:35 +01:00
|
|
|
if (decorationAnalyses.has(sourceFile) || switchMarkerAnalyses.has(sourceFile) ||
|
|
|
|
|
sourceFile === this.bundle.src.file) {
|
|
|
|
|
const compiledFile = decorationAnalyses.get(sourceFile);
|
|
|
|
|
const switchMarkerAnalysis = switchMarkerAnalyses.get(sourceFile);
|
2018-11-25 22:07:51 +00:00
|
|
|
renderedFiles.push(...this.renderFile(
|
|
|
|
|
sourceFile, compiledFile, switchMarkerAnalysis, privateDeclarationsAnalyses));
|
2018-10-10 17:52:55 +01:00
|
|
|
}
|
2018-10-04 12:19:11 +01:00
|
|
|
});
|
2018-10-16 08:56:54 +01:00
|
|
|
|
2018-10-04 12:19:11 +01:00
|
|
|
return renderedFiles;
|
|
|
|
|
}
|
2018-07-27 22:36:54 +03:00
|
|
|
|
2018-07-16 10:23:37 +01:00
|
|
|
/**
|
|
|
|
|
* Render the source code and source-map for an Analyzed file.
|
2018-10-16 08:56:54 +01:00
|
|
|
* @param compiledFile The analyzed file to render.
|
2018-07-16 10:23:37 +01:00
|
|
|
* @param targetPath The absolute path where the rendered file will be written.
|
|
|
|
|
*/
|
2018-10-04 12:19:11 +01:00
|
|
|
renderFile(
|
2018-10-16 08:56:54 +01:00
|
|
|
sourceFile: ts.SourceFile, compiledFile: CompiledFile|undefined,
|
2018-11-25 22:07:51 +00:00
|
|
|
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined,
|
2019-04-28 20:48:35 +01:00
|
|
|
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileToWrite[] {
|
2019-04-28 20:48:35 +01:00
|
|
|
const isEntryPoint = sourceFile === this.bundle.src.file;
|
2020-02-16 21:07:30 +01:00
|
|
|
const outputText = new MagicString(sourceFile.text);
|
2018-07-16 10:23:37 +01:00
|
|
|
|
2018-10-04 12:19:11 +01:00
|
|
|
if (switchMarkerAnalysis) {
|
2019-04-28 20:48:35 +01:00
|
|
|
this.srcFormatter.rewriteSwitchableDeclarations(
|
2018-10-04 12:19:11 +01:00
|
|
|
outputText, switchMarkerAnalysis.sourceFile, switchMarkerAnalysis.declarations);
|
|
|
|
|
}
|
2018-07-16 10:23:37 +01:00
|
|
|
|
2019-04-28 20:48:35 +01:00
|
|
|
const importManager = new ImportManager(
|
2019-05-25 20:38:33 +01:00
|
|
|
getImportRewriter(
|
|
|
|
|
this.bundle.src.r3SymbolsFile, this.bundle.isCore, this.bundle.isFlatCore),
|
2019-04-28 20:48:35 +01:00
|
|
|
IMPORT_PREFIX);
|
2018-11-18 21:37:30 +01:00
|
|
|
|
2019-04-28 20:48:35 +01:00
|
|
|
if (compiledFile) {
|
2018-11-18 21:37:30 +01:00
|
|
|
// TODO: remove constructor param metadata and property decorators (we need info from the
|
|
|
|
|
// handlers to do this)
|
|
|
|
|
const decoratorsToRemove = this.computeDecoratorsToRemove(compiledFile.compiledClasses);
|
2019-04-28 20:48:35 +01:00
|
|
|
this.srcFormatter.removeDecorators(outputText, decoratorsToRemove);
|
2018-10-04 12:19:11 +01:00
|
|
|
|
2018-10-16 08:56:54 +01:00
|
|
|
compiledFile.compiledClasses.forEach(clazz => {
|
2019-11-01 16:55:10 +00:00
|
|
|
const renderedDefinition =
|
|
|
|
|
this.renderDefinitions(compiledFile.sourceFile, clazz, importManager);
|
2019-04-28 20:48:35 +01:00
|
|
|
this.srcFormatter.addDefinitions(outputText, clazz, renderedDefinition);
|
2019-10-14 13:04:42 -07:00
|
|
|
|
2019-11-06 17:03:56 +00:00
|
|
|
const renderedStatements =
|
|
|
|
|
this.renderAdjacentStatements(compiledFile.sourceFile, clazz, importManager);
|
|
|
|
|
this.srcFormatter.addAdjacentStatements(outputText, clazz, renderedStatements);
|
2018-10-04 12:19:11 +01:00
|
|
|
});
|
|
|
|
|
|
2020-01-06 23:12:19 +01:00
|
|
|
if (!isEntryPoint && compiledFile.reexports.length > 0) {
|
|
|
|
|
this.srcFormatter.addDirectExports(
|
|
|
|
|
outputText, compiledFile.reexports, importManager, compiledFile.sourceFile);
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-28 20:48:35 +01:00
|
|
|
this.srcFormatter.addConstants(
|
2018-10-04 12:19:11 +01:00
|
|
|
outputText,
|
2019-11-04 19:29:01 +02:00
|
|
|
renderConstantPool(
|
|
|
|
|
this.srcFormatter, compiledFile.sourceFile, compiledFile.constantPool, importManager),
|
2018-10-16 08:56:54 +01:00
|
|
|
compiledFile.sourceFile);
|
2018-10-04 12:19:11 +01:00
|
|
|
}
|
2018-08-17 07:50:45 +01:00
|
|
|
|
2018-11-25 22:07:51 +00:00
|
|
|
// Add exports to the entry-point file
|
2019-04-28 20:48:35 +01:00
|
|
|
if (isEntryPoint) {
|
2018-11-25 22:07:51 +00:00
|
|
|
const entryPointBasePath = stripExtension(this.bundle.src.path);
|
2019-04-28 20:48:35 +01:00
|
|
|
this.srcFormatter.addExports(
|
2019-04-28 20:48:35 +01:00
|
|
|
outputText, entryPointBasePath, privateDeclarationsAnalyses, importManager, sourceFile);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isEntryPoint || compiledFile) {
|
2019-04-28 20:48:35 +01:00
|
|
|
this.srcFormatter.addImports(
|
|
|
|
|
outputText, importManager.getAllImports(sourceFile.fileName), sourceFile);
|
2018-11-25 22:07:51 +00:00
|
|
|
}
|
|
|
|
|
|
2019-04-28 20:48:35 +01:00
|
|
|
if (compiledFile || switchMarkerAnalysis || isEntryPoint) {
|
2020-02-16 21:07:30 +01:00
|
|
|
return renderSourceAndMap(this.fs, sourceFile, outputText);
|
2019-04-28 20:48:35 +01:00
|
|
|
} else {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2018-10-16 08:56:54 +01:00
|
|
|
}
|
|
|
|
|
|
2018-07-16 10:23:37 +01:00
|
|
|
/**
|
2018-11-18 21:37:30 +01:00
|
|
|
* 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
|
|
|
|
|
* we should remove the entire decorator property.
|
|
|
|
|
* @param classes The list of classes that may have decorators to remove.
|
|
|
|
|
* @returns A map of decorators to remove, keyed by their container node.
|
2018-07-16 10:23:37 +01:00
|
|
|
*/
|
2019-04-28 20:48:35 +01:00
|
|
|
private computeDecoratorsToRemove(classes: CompiledClass[]): RedundantDecoratorMap {
|
2018-11-18 21:37:30 +01:00
|
|
|
const decoratorsToRemove = new RedundantDecoratorMap();
|
|
|
|
|
classes.forEach(clazz => {
|
2019-06-03 18:41:47 +02:00
|
|
|
if (clazz.decorators === null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-18 21:37:30 +01:00
|
|
|
clazz.decorators.forEach(dec => {
|
refactor(ivy): mark synthetic decorators explicitly (#33362)
In ngcc's migration system, synthetic decorators can be injected into a
compilation to ensure that certain classes are compiled with Angular
logic, where the original library code did not include the necessary
decorators. Prior to this change, synthesized decorators would have a
fake AST structure as associated node and a made-up identifier. In
theory, this may introduce issues downstream:
1) a decorator's node is used for diagnostics, so it must have position
information. Having fake AST nodes without a position is therefore a
problem. Note that this is currently not a problem in practice, as
injected synthesized decorators would not produce any diagnostics.
2) the decorator's identifier should refer to an imported symbol.
Therefore, it is required that the symbol is actually imported.
Moreover, bundle formats such as UMD and CommonJS use namespaces for
imports, so a bare `ts.Identifier` would not be suitable to use as
identifier. This was also not a problem in practice, as the identifier
is only used in the `setClassMetadata` generated code, which is omitted
for synthetically injected decorators.
To remedy these potential issues, this commit makes a decorator's
identifier optional and switches its node over from a fake AST structure
to the class' name.
PR Close #33362
2019-10-20 22:45:28 +02:00
|
|
|
if (dec.node === null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-11-18 21:37:30 +01:00
|
|
|
const decoratorArray = dec.node.parent !;
|
|
|
|
|
if (!decoratorsToRemove.has(decoratorArray)) {
|
|
|
|
|
decoratorsToRemove.set(decoratorArray, [dec.node]);
|
|
|
|
|
} else {
|
|
|
|
|
decoratorsToRemove.get(decoratorArray) !.push(dec.node);
|
|
|
|
|
}
|
|
|
|
|
});
|
2018-07-16 10:23:37 +01:00
|
|
|
});
|
2018-11-18 21:37:30 +01:00
|
|
|
return decoratorsToRemove;
|
2018-07-16 10:23:37 +01:00
|
|
|
}
|
2019-11-01 16:55:10 +00:00
|
|
|
|
|
|
|
|
/**
|
2019-11-06 17:03:56 +00:00
|
|
|
* Render the definitions as source code for the given class.
|
|
|
|
|
* @param sourceFile The file containing the class to process.
|
|
|
|
|
* @param clazz The class whose definitions are to be rendered.
|
|
|
|
|
* @param compilation The results of analyzing the class - this is used to generate the rendered
|
|
|
|
|
* definitions.
|
|
|
|
|
* @param imports An object that tracks the imports that are needed by the rendered definitions.
|
|
|
|
|
*/
|
2019-11-01 16:55:10 +00:00
|
|
|
private renderDefinitions(
|
|
|
|
|
sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: ImportManager): string {
|
|
|
|
|
const name = this.host.getInternalNameOfClass(compiledClass.declaration);
|
|
|
|
|
const statements: Statement[] = compiledClass.compilation.map(
|
|
|
|
|
c => { return createAssignmentStatement(name, c.name, c.initializer); });
|
2019-11-06 17:03:56 +00:00
|
|
|
return this.renderStatements(sourceFile, statements, imports);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render the adjacent statements as source code for the given class.
|
|
|
|
|
* @param sourceFile The file containing the class to process.
|
|
|
|
|
* @param clazz The class whose statements are to be rendered.
|
|
|
|
|
* @param compilation The results of analyzing the class - this is used to generate the rendered
|
|
|
|
|
* definitions.
|
|
|
|
|
* @param imports An object that tracks the imports that are needed by the rendered definitions.
|
|
|
|
|
*/
|
|
|
|
|
private renderAdjacentStatements(
|
|
|
|
|
sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: ImportManager): string {
|
|
|
|
|
const statements: Statement[] = [];
|
2019-11-01 16:55:10 +00:00
|
|
|
for (const c of compiledClass.compilation) {
|
|
|
|
|
statements.push(...c.statements);
|
|
|
|
|
}
|
2019-11-06 17:03:56 +00:00
|
|
|
return this.renderStatements(sourceFile, statements, imports);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderStatements(
|
|
|
|
|
sourceFile: ts.SourceFile, statements: Statement[], imports: ImportManager): string {
|
2019-11-04 19:29:01 +02:00
|
|
|
const printStatement = (stmt: Statement) =>
|
|
|
|
|
this.srcFormatter.printStatement(stmt, sourceFile, imports);
|
|
|
|
|
return statements.map(printStatement).join('\n');
|
2019-11-01 16:55:10 +00:00
|
|
|
}
|
2018-07-16 10:23:37 +01:00
|
|
|
}
|
|
|
|
|
|
2018-06-27 08:41:11 -07:00
|
|
|
/**
|
|
|
|
|
* Render the constant pool as source code for the given class.
|
|
|
|
|
*/
|
|
|
|
|
export function renderConstantPool(
|
2019-11-04 19:29:01 +02:00
|
|
|
formatter: RenderingFormatter, sourceFile: ts.SourceFile, constantPool: ConstantPool,
|
|
|
|
|
imports: ImportManager): string {
|
|
|
|
|
const printStatement = (stmt: Statement) => formatter.printStatement(stmt, sourceFile, imports);
|
|
|
|
|
return constantPool.statements.map(printStatement).join('\n');
|
2018-06-27 08:41:11 -07:00
|
|
|
}
|
|
|
|
|
|
2018-07-16 10:23:37 +01:00
|
|
|
/**
|
|
|
|
|
* Create an Angular AST statement node that contains the assignment of the
|
|
|
|
|
* compiled decorator to be applied to the class.
|
|
|
|
|
* @param analyzedClass The info about the class whose statement we want to create.
|
|
|
|
|
*/
|
|
|
|
|
function createAssignmentStatement(
|
|
|
|
|
receiverName: ts.DeclarationName, propName: string, initializer: Expression): Statement {
|
|
|
|
|
const receiver = new WrappedNodeExpr(receiverName);
|
|
|
|
|
return new WritePropExpr(receiver, propName, initializer).toStmt();
|
|
|
|
|
}
|