fix(ngcc): do not emit ES2015 code in ES5 files (#33514)
Previously, ngcc's `Renderer` would add some constants in the processed files which were emitted as ES2015 code (e.g. `const` declarations). This would result in invalid ES5 generated code that would break when run on browsers that do not support the emitted format. This commit fixes it by adding a `printStatement()` method to `RenderingFormatter`, which can convert statements to JavaScript code in a suitable format for the corresponding `RenderingFormatter`. Additionally, the `translateExpression()` and `translateStatement()` ngtsc helper methods are augmented to accept an extra hint to know whether the code needs to be translated to ES5 format or not. Fixes #32665 PR Close #33514
This commit is contained in:

committed by
Kara Erickson

parent
21bd8c9a2e
commit
06e36e5972
@ -119,7 +119,8 @@ runInEachFileSystem(() => {
|
||||
}
|
||||
const sf = getSourceFileOrError(program, _('/index.ts'));
|
||||
const im = new ImportManager(new NoopImportRewriter(), 'i');
|
||||
const tsStatement = translateStatement(call, im, NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
const tsStatement =
|
||||
translateStatement(call, im, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015);
|
||||
const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tsStatement, sf);
|
||||
return res.replace(/\s+/g, ' ');
|
||||
}
|
||||
|
@ -64,8 +64,9 @@ class IvyVisitor extends Visitor {
|
||||
|
||||
res.forEach(field => {
|
||||
// Translate the initializer for the field into TS nodes.
|
||||
const exprNode =
|
||||
translateExpression(field.initializer, this.importManager, this.defaultImportRecorder);
|
||||
const exprNode = translateExpression(
|
||||
field.initializer, this.importManager, this.defaultImportRecorder,
|
||||
ts.ScriptTarget.ES2015);
|
||||
|
||||
// Create a static property declaration for the new field.
|
||||
const property = ts.createProperty(
|
||||
@ -73,7 +74,9 @@ class IvyVisitor extends Visitor {
|
||||
undefined, exprNode);
|
||||
|
||||
field.statements
|
||||
.map(stmt => translateStatement(stmt, this.importManager, this.defaultImportRecorder))
|
||||
.map(
|
||||
stmt => translateStatement(
|
||||
stmt, this.importManager, this.defaultImportRecorder, ts.ScriptTarget.ES2015))
|
||||
.forEach(stmt => statements.push(stmt));
|
||||
|
||||
members.push(property);
|
||||
@ -218,7 +221,8 @@ function transformIvySourceFile(
|
||||
// Generate the constant statements first, as they may involve adding additional imports
|
||||
// to the ImportManager.
|
||||
const constants = constantPool.statements.map(
|
||||
stmt => translateStatement(stmt, importManager, defaultImportRecorder));
|
||||
stmt =>
|
||||
translateStatement(stmt, importManager, defaultImportRecorder, ts.ScriptTarget.ES2015));
|
||||
|
||||
// Preserve @fileoverview comments required by Closure, since the location might change as a
|
||||
// result of adding extra imports and constant pool statements.
|
||||
|
@ -100,17 +100,19 @@ export class ImportManager {
|
||||
}
|
||||
|
||||
export function translateExpression(
|
||||
expression: Expression, imports: ImportManager,
|
||||
defaultImportRecorder: DefaultImportRecorder): ts.Expression {
|
||||
expression: Expression, imports: ImportManager, defaultImportRecorder: DefaultImportRecorder,
|
||||
scriptTarget: Exclude<ts.ScriptTarget, ts.ScriptTarget.JSON>): ts.Expression {
|
||||
return expression.visitExpression(
|
||||
new ExpressionTranslatorVisitor(imports, defaultImportRecorder), new Context(false));
|
||||
new ExpressionTranslatorVisitor(imports, defaultImportRecorder, scriptTarget),
|
||||
new Context(false));
|
||||
}
|
||||
|
||||
export function translateStatement(
|
||||
statement: Statement, imports: ImportManager,
|
||||
defaultImportRecorder: DefaultImportRecorder): ts.Statement {
|
||||
statement: Statement, imports: ImportManager, defaultImportRecorder: DefaultImportRecorder,
|
||||
scriptTarget: Exclude<ts.ScriptTarget, ts.ScriptTarget.JSON>): ts.Statement {
|
||||
return statement.visitStatement(
|
||||
new ExpressionTranslatorVisitor(imports, defaultImportRecorder), new Context(true));
|
||||
new ExpressionTranslatorVisitor(imports, defaultImportRecorder, scriptTarget),
|
||||
new Context(true));
|
||||
}
|
||||
|
||||
export function translateType(type: Type, imports: ImportManager): ts.TypeNode {
|
||||
@ -120,10 +122,14 @@ export function translateType(type: Type, imports: ImportManager): ts.TypeNode {
|
||||
class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor {
|
||||
private externalSourceFiles = new Map<string, ts.SourceMapSource>();
|
||||
constructor(
|
||||
private imports: ImportManager, private defaultImportRecorder: DefaultImportRecorder) {}
|
||||
private imports: ImportManager, private defaultImportRecorder: DefaultImportRecorder,
|
||||
private scriptTarget: Exclude<ts.ScriptTarget, ts.ScriptTarget.JSON>) {}
|
||||
|
||||
visitDeclareVarStmt(stmt: DeclareVarStmt, context: Context): ts.VariableStatement {
|
||||
const nodeFlags = stmt.hasModifier(StmtModifier.Final) ? ts.NodeFlags.Const : ts.NodeFlags.None;
|
||||
const nodeFlags =
|
||||
((this.scriptTarget >= ts.ScriptTarget.ES2015) && stmt.hasModifier(StmtModifier.Final)) ?
|
||||
ts.NodeFlags.Const :
|
||||
ts.NodeFlags.None;
|
||||
return ts.createVariableStatement(
|
||||
undefined, ts.createVariableDeclarationList(
|
||||
[ts.createVariableDeclaration(
|
||||
@ -149,6 +155,11 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
}
|
||||
|
||||
visitDeclareClassStmt(stmt: ClassStmt, context: Context) {
|
||||
if (this.scriptTarget < ts.ScriptTarget.ES2015) {
|
||||
throw new Error(
|
||||
`Unsupported mode: Visiting a "declare class" statement (class ${stmt.name}) while ` +
|
||||
`targeting ${ts.ScriptTarget[this.scriptTarget]}.`);
|
||||
}
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@ -200,7 +211,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
const exprContext = context.withExpressionMode;
|
||||
const lhs = ts.createElementAccess(
|
||||
expr.receiver.visitExpression(this, exprContext),
|
||||
expr.index.visitExpression(this, exprContext), );
|
||||
expr.index.visitExpression(this, exprContext));
|
||||
const rhs = expr.value.visitExpression(this, exprContext);
|
||||
const result: ts.Expression = ts.createBinary(lhs, ts.SyntaxKind.EqualsToken, rhs);
|
||||
return context.isStatement ? result : ts.createParen(result);
|
||||
@ -252,6 +263,12 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
}
|
||||
|
||||
visitLocalizedString(ast: LocalizedString, context: Context): ts.Expression {
|
||||
if (this.scriptTarget < ts.ScriptTarget.ES2015) {
|
||||
// This should never happen.
|
||||
throw new Error(
|
||||
'Unsupported mode: Visiting a localized string (which produces a tagged template ' +
|
||||
`literal) ' while targeting ${ts.ScriptTarget[this.scriptTarget]}.`);
|
||||
}
|
||||
return visitLocalizedString(ast, context, this);
|
||||
}
|
||||
|
||||
@ -518,7 +535,8 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
|
||||
}
|
||||
|
||||
visitTypeofExpr(ast: TypeofExpr, context: Context): ts.TypeQueryNode {
|
||||
let expr = translateExpression(ast.expr, this.imports, NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
let expr = translateExpression(
|
||||
ast.expr, this.imports, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015);
|
||||
return ts.createTypeQueryNode(expr as ts.Identifier);
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +204,8 @@ export class Environment {
|
||||
const ngExpr = this.refEmitter.emit(ref, this.contextFile);
|
||||
|
||||
// Use `translateExpression` to convert the `Expression` into a `ts.Expression`.
|
||||
return translateExpression(ngExpr, this.importManager, NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
return translateExpression(
|
||||
ngExpr, this.importManager, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user