fix(ivy): use a single constant pool per source file (#25392)
Previously, ngtsc used a new ConstantPool for each decorator compilation. This could result in collisions between constants in the top-level scope. Now, ngtsc uses a single ConstantPool for each source file being compiled, and merges the constant statements into the file after the import section. PR Close #25392
This commit is contained in:

committed by
Ben Lesh

parent
9c92a6fc7a
commit
fba276d3d1
@ -146,9 +146,8 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||
};
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: R3ComponentMetadata): CompileResult {
|
||||
const pool = new ConstantPool();
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: R3ComponentMetadata, pool: ConstantPool):
|
||||
CompileResult {
|
||||
// Check whether this component was registered with an NgModule. If so, it should be compiled
|
||||
// under that module's compilation scope.
|
||||
const scope = this.scopeRegistry.lookupCompilationScope(node);
|
||||
@ -163,7 +162,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||
return {
|
||||
name: 'ngComponentDef',
|
||||
initializer: res.expression,
|
||||
statements: pool.statements,
|
||||
statements: [],
|
||||
type: res.type,
|
||||
};
|
||||
}
|
||||
|
@ -6,11 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Expression, Statement, Type} from '@angular/compiler';
|
||||
import {ConstantPool, Expression, Statement, Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator} from '../../host';
|
||||
|
||||
|
||||
/**
|
||||
* Provides the interface between a decorator compiler from @angular/compiler and the Typescript
|
||||
* compiler/transform.
|
||||
@ -46,7 +47,8 @@ export interface DecoratorHandler<A> {
|
||||
* Generate a description of the field which should be added to the class, including any
|
||||
* initialization code to be generated.
|
||||
*/
|
||||
compile(node: ts.Declaration, analysis: A): CompileResult|CompileResult[];
|
||||
compile(node: ts.Declaration, analysis: A, constantPool: ConstantPool): CompileResult
|
||||
|CompileResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ConstantPool} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, ReflectionHost} from '../../host';
|
||||
@ -151,7 +152,7 @@ export class IvyCompilation {
|
||||
* Perform a compilation operation on the given class declaration and return instructions to an
|
||||
* AST transformer if any are available.
|
||||
*/
|
||||
compileIvyFieldFor(node: ts.Declaration): CompileResult[]|undefined {
|
||||
compileIvyFieldFor(node: ts.Declaration, constantPool: ConstantPool): CompileResult[]|undefined {
|
||||
// Look to see whether the original node was analyzed. If not, there's nothing to do.
|
||||
const original = ts.getOriginalNode(node) as ts.Declaration;
|
||||
if (!this.analysis.has(original)) {
|
||||
@ -160,7 +161,7 @@ export class IvyCompilation {
|
||||
const op = this.analysis.get(original) !;
|
||||
|
||||
// Run the actual compilation, which generates an Expression for the Ivy field.
|
||||
let res: CompileResult|CompileResult[] = op.adapter.compile(node, op.analysis);
|
||||
let res: CompileResult|CompileResult[] = op.adapter.compile(node, op.analysis, constantPool);
|
||||
if (!Array.isArray(res)) {
|
||||
res = [res];
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {WrappedNodeExpr} from '@angular/compiler';
|
||||
import {ConstantPool} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, ReflectionHost} from '../../host';
|
||||
@ -32,7 +32,8 @@ export function ivyTransformFactory(
|
||||
class IvyVisitor extends Visitor {
|
||||
constructor(
|
||||
private compilation: IvyCompilation, private reflector: ReflectionHost,
|
||||
private importManager: ImportManager, private isCore: boolean) {
|
||||
private importManager: ImportManager, private isCore: boolean,
|
||||
private constantPool: ConstantPool) {
|
||||
super();
|
||||
}
|
||||
|
||||
@ -40,7 +41,7 @@ class IvyVisitor extends Visitor {
|
||||
VisitListEntryResult<ts.Statement, ts.ClassDeclaration> {
|
||||
// Determine if this class has an Ivy field that needs to be added, and compile the field
|
||||
// to an expression if so.
|
||||
const res = this.compilation.compileIvyFieldFor(node);
|
||||
const res = this.compilation.compileIvyFieldFor(node, this.constantPool);
|
||||
|
||||
if (res !== undefined) {
|
||||
// There is at least one field to add.
|
||||
@ -189,24 +190,35 @@ class IvyVisitor extends Visitor {
|
||||
function transformIvySourceFile(
|
||||
compilation: IvyCompilation, context: ts.TransformationContext, reflector: ReflectionHost,
|
||||
coreImportsFrom: ts.SourceFile | null, file: ts.SourceFile): ts.SourceFile {
|
||||
const constantPool = new ConstantPool();
|
||||
const importManager = new ImportManager(coreImportsFrom !== null);
|
||||
|
||||
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
|
||||
const sf = visit(
|
||||
file, new IvyVisitor(compilation, reflector, importManager, coreImportsFrom !== null),
|
||||
context);
|
||||
const visitor =
|
||||
new IvyVisitor(compilation, reflector, importManager, coreImportsFrom !== null, constantPool);
|
||||
const sf = visit(file, visitor, context);
|
||||
|
||||
// Generate the constant statements first, as they may involve adding additional imports
|
||||
// to the ImportManager.
|
||||
const constants = constantPool.statements.map(stmt => translateStatement(stmt, importManager));
|
||||
|
||||
// Generate the import statements to prepend.
|
||||
const imports = importManager.getAllImports(file.fileName, coreImportsFrom).map(i => {
|
||||
const addedImports = importManager.getAllImports(file.fileName, coreImportsFrom).map(i => {
|
||||
return ts.createImportDeclaration(
|
||||
undefined, undefined,
|
||||
ts.createImportClause(undefined, ts.createNamespaceImport(ts.createIdentifier(i.as))),
|
||||
ts.createLiteral(i.name));
|
||||
});
|
||||
|
||||
// Filter out the existing imports and the source file body. All new statements
|
||||
// will be inserted between them.
|
||||
const existingImports = sf.statements.filter(stmt => isImportStatement(stmt));
|
||||
const body = sf.statements.filter(stmt => !isImportStatement(stmt));
|
||||
|
||||
// Prepend imports if needed.
|
||||
if (imports.length > 0) {
|
||||
sf.statements = ts.createNodeArray([...imports, ...sf.statements]);
|
||||
if (addedImports.length > 0) {
|
||||
sf.statements =
|
||||
ts.createNodeArray([...existingImports, ...addedImports, ...constants, ...body]);
|
||||
}
|
||||
return sf;
|
||||
}
|
||||
@ -227,3 +239,8 @@ function maybeFilterDecorator(
|
||||
function isFromAngularCore(decorator: Decorator): boolean {
|
||||
return decorator.import !== null && decorator.import.from === '@angular/core';
|
||||
}
|
||||
|
||||
function isImportStatement(stmt: ts.Statement): boolean {
|
||||
return ts.isImportDeclaration(stmt) || ts.isImportEqualsDeclaration(stmt) ||
|
||||
ts.isNamespaceImport(stmt);
|
||||
}
|
||||
|
Reference in New Issue
Block a user