fix(ngcc): render adjacent statements after static properties (#33630)
See https://github.com/angular/angular/pull/33337#issuecomment-545487737 Fixes FW-1664 PR Close #33630
This commit is contained in:

committed by
atscott

parent
7b87392f47
commit
fe12d0dc78
@ -35,4 +35,25 @@ export class Esm5RenderingFormatter extends EsmRenderingFormatter {
|
||||
const insertionPoint = returnStatement.getFullStart();
|
||||
output.appendLeft(insertionPoint, '\n' + definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the adjacent statements inside the IIFE of each decorated class
|
||||
*/
|
||||
addAdjacentStatements(output: MagicString, compiledClass: CompiledClass, statements: string):
|
||||
void {
|
||||
const iifeBody = getIifeBody(compiledClass.declaration);
|
||||
if (!iifeBody) {
|
||||
throw new Error(
|
||||
`Compiled class declaration is not inside an IIFE: ${compiledClass.name} in ${compiledClass.declaration.getSourceFile().fileName}`);
|
||||
}
|
||||
|
||||
const returnStatement = iifeBody.statements.find(ts.isReturnStatement);
|
||||
if (!returnStatement) {
|
||||
throw new Error(
|
||||
`Compiled class wrapper IIFE does not have a return statement: ${compiledClass.name} in ${compiledClass.declaration.getSourceFile().fileName}`);
|
||||
}
|
||||
|
||||
const insertionPoint = returnStatement.getFullStart();
|
||||
output.appendLeft(insertionPoint, '\n' + statements);
|
||||
}
|
||||
}
|
||||
|
@ -100,6 +100,30 @@ export class EsmRenderingFormatter implements RenderingFormatter {
|
||||
output.appendLeft(insertionPoint, '\n' + definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the adjacent statements after all static properties of the class.
|
||||
*/
|
||||
addAdjacentStatements(output: MagicString, compiledClass: CompiledClass, statements: string):
|
||||
void {
|
||||
const classSymbol = this.host.getClassSymbol(compiledClass.declaration);
|
||||
if (!classSymbol) {
|
||||
throw new Error(`Compiled class does not have a valid symbol: ${compiledClass.name}`);
|
||||
}
|
||||
|
||||
let insertionPoint = classSymbol.declaration.valueDeclaration.getEnd();
|
||||
|
||||
// If there are static members on this class then insert after the last one
|
||||
if (classSymbol.declaration.exports !== undefined) {
|
||||
classSymbol.declaration.exports.forEach(exportSymbol => {
|
||||
const exportStatement = getContainingStatement(exportSymbol);
|
||||
if (exportStatement !== null) {
|
||||
insertionPoint = Math.max(insertionPoint, exportStatement.getEnd());
|
||||
}
|
||||
});
|
||||
}
|
||||
output.appendLeft(insertionPoint, '\n' + statements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove static decorator properties from classes.
|
||||
*/
|
||||
@ -244,3 +268,21 @@ function getNextSiblingInArray<T extends ts.Node>(node: T, array: ts.NodeArray<T
|
||||
const index = array.indexOf(node);
|
||||
return index !== -1 && array.length > index + 1 ? array[index + 1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the statement that contains the given class member
|
||||
* @param symbol the symbol of a static member of a class
|
||||
*/
|
||||
function getContainingStatement(symbol: ts.Symbol): ts.ExpressionStatement|null {
|
||||
if (symbol.valueDeclaration === undefined) {
|
||||
return null;
|
||||
}
|
||||
let node: ts.Node|null = symbol.valueDeclaration;
|
||||
while (node) {
|
||||
if (ts.isExpressionStatement(node)) {
|
||||
break;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return node || null;
|
||||
}
|
||||
|
@ -86,6 +86,10 @@ export class Renderer {
|
||||
this.renderDefinitions(compiledFile.sourceFile, clazz, importManager);
|
||||
this.srcFormatter.addDefinitions(outputText, clazz, renderedDefinition);
|
||||
|
||||
const renderedStatements =
|
||||
this.renderAdjacentStatements(compiledFile.sourceFile, clazz, importManager);
|
||||
this.srcFormatter.addAdjacentStatements(outputText, clazz, renderedStatements);
|
||||
|
||||
if (!isEntryPoint && clazz.reexports.length > 0) {
|
||||
this.srcFormatter.addDirectExports(
|
||||
outputText, clazz.reexports, importManager, compiledFile.sourceFile);
|
||||
@ -147,26 +151,45 @@ export class Renderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
* 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.
|
||||
*/
|
||||
private renderDefinitions(
|
||||
sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: ImportManager): string {
|
||||
const printer = createPrinter();
|
||||
const name = this.host.getInternalNameOfClass(compiledClass.declaration);
|
||||
const statements: Statement[] = compiledClass.compilation.map(
|
||||
c => { return createAssignmentStatement(name, c.name, c.initializer); });
|
||||
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[] = [];
|
||||
for (const c of compiledClass.compilation) {
|
||||
statements.push(...c.statements);
|
||||
}
|
||||
return this.renderStatements(sourceFile, statements, imports);
|
||||
}
|
||||
|
||||
private renderStatements(
|
||||
sourceFile: ts.SourceFile, statements: Statement[], imports: ImportManager): string {
|
||||
const printer = createPrinter();
|
||||
const translate = (stmt: Statement) =>
|
||||
translateStatement(stmt, imports, NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
const print = (stmt: Statement) =>
|
||||
printer.printNode(ts.EmitHint.Unspecified, translate(stmt), sourceFile);
|
||||
const statements: Statement[] = compiledClass.compilation.map(
|
||||
c => { return createAssignmentStatement(name, c.name, c.initializer); });
|
||||
for (const c of compiledClass.compilation) {
|
||||
statements.push(...c.statements);
|
||||
}
|
||||
return statements.map(print).join('\n');
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ export interface RenderingFormatter {
|
||||
output: MagicString, exports: Reexport[], importManager: ImportManager,
|
||||
file: ts.SourceFile): void;
|
||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void;
|
||||
addAdjacentStatements(output: MagicString, compiledClass: CompiledClass, statements: string):
|
||||
void;
|
||||
removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap): void;
|
||||
rewriteSwitchableDeclarations(
|
||||
outputText: MagicString, sourceFile: ts.SourceFile,
|
||||
|
Reference in New Issue
Block a user