mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(compiler): support tagged template literals in code generator (#39122)
Add a TaggedTemplateExpr to represent tagged template literals in Angular's syntax tree (more specifically Expression in output_ast.ts). Also update classes that implement ExpressionVisitor to add support for tagged template literals in different contexts, such as JIT compilation and conversion to JS. Partial support for tagged template literals had already been implemented to support the $localize tag used by Angular's i18n framework. Where applicable, this code was refactored to support arbitrary tags, although completely replacing the i18n-specific support for the $localize tag with the new generic support for tagged template literals may not be completely trivial, and is left as future work. PR Close #39122
This commit is contained in:
@@ -280,7 +280,7 @@ function transformIvySourceFile(
|
||||
const constants =
|
||||
constantPool.statements.map(stmt => translateStatement(stmt, importManager, {
|
||||
recordWrappedNodeExpr,
|
||||
downlevelLocalizedStrings: downlevelTranslatedCode,
|
||||
downlevelTaggedTemplates: downlevelTranslatedCode,
|
||||
downlevelVariableDeclarations: downlevelTranslatedCode,
|
||||
}));
|
||||
|
||||
|
||||
@@ -12,5 +12,5 @@ export {Context} from './src/context';
|
||||
export {ImportManager} from './src/import_manager';
|
||||
export {ExpressionTranslatorVisitor, RecordWrappedNodeExprFn, TranslatorOptions} from './src/translator';
|
||||
export {translateType} from './src/type_translator';
|
||||
export {attachComments, TypeScriptAstFactory} from './src/typescript_ast_factory';
|
||||
export {attachComments, createTemplateMiddle, createTemplateTail, TypeScriptAstFactory} from './src/typescript_ast_factory';
|
||||
export {translateExpression, translateStatement} from './src/typescript_translator';
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as o from '@angular/compiler';
|
||||
import {createTaggedTemplate} from 'typescript';
|
||||
|
||||
import {AstFactory, BinaryOperator, ObjectLiteralProperty, SourceMapRange, TemplateElement, TemplateLiteral, UnaryOperator} from './api/ast_factory';
|
||||
import {ImportGenerator} from './api/import_generator';
|
||||
@@ -38,21 +39,21 @@ const BINARY_OPERATORS = new Map<o.BinaryOperator, BinaryOperator>([
|
||||
export type RecordWrappedNodeExprFn<TExpression> = (expr: TExpression) => void;
|
||||
|
||||
export interface TranslatorOptions<TExpression> {
|
||||
downlevelLocalizedStrings?: boolean;
|
||||
downlevelTaggedTemplates?: boolean;
|
||||
downlevelVariableDeclarations?: boolean;
|
||||
recordWrappedNodeExpr?: RecordWrappedNodeExprFn<TExpression>;
|
||||
}
|
||||
|
||||
export class ExpressionTranslatorVisitor<TStatement, TExpression> implements o.ExpressionVisitor,
|
||||
o.StatementVisitor {
|
||||
private downlevelLocalizedStrings: boolean;
|
||||
private downlevelTaggedTemplates: boolean;
|
||||
private downlevelVariableDeclarations: boolean;
|
||||
private recordWrappedNodeExpr: RecordWrappedNodeExprFn<TExpression>;
|
||||
|
||||
constructor(
|
||||
private factory: AstFactory<TStatement, TExpression>,
|
||||
private imports: ImportGenerator<TExpression>, options: TranslatorOptions<TExpression>) {
|
||||
this.downlevelLocalizedStrings = options.downlevelLocalizedStrings === true;
|
||||
this.downlevelTaggedTemplates = options.downlevelTaggedTemplates === true;
|
||||
this.downlevelVariableDeclarations = options.downlevelVariableDeclarations === true;
|
||||
this.recordWrappedNodeExpr = options.recordWrappedNodeExpr || (() => {});
|
||||
}
|
||||
@@ -168,6 +169,19 @@ export class ExpressionTranslatorVisitor<TStatement, TExpression> implements o.E
|
||||
ast.sourceSpan);
|
||||
}
|
||||
|
||||
visitTaggedTemplateExpr(ast: o.TaggedTemplateExpr, context: Context): TExpression {
|
||||
return this.setSourceMapRange(
|
||||
this.createTaggedTemplateExpression(ast.tag.visitExpression(this, context), {
|
||||
elements: ast.template.elements.map(e => createTemplateElement({
|
||||
cooked: e.text,
|
||||
raw: e.rawText,
|
||||
range: e.sourceSpan ?? ast.sourceSpan,
|
||||
})),
|
||||
expressions: ast.template.expressions.map(e => e.visitExpression(this, context))
|
||||
}),
|
||||
ast.sourceSpan);
|
||||
}
|
||||
|
||||
visitInstantiateExpr(ast: o.InstantiateExpr, context: Context): TExpression {
|
||||
return this.factory.createNewExpression(
|
||||
ast.classExpr.visitExpression(this, context),
|
||||
@@ -202,13 +216,14 @@ export class ExpressionTranslatorVisitor<TStatement, TExpression> implements o.E
|
||||
}
|
||||
|
||||
const localizeTag = this.factory.createIdentifier('$localize');
|
||||
return this.setSourceMapRange(
|
||||
this.createTaggedTemplateExpression(localizeTag, {elements, expressions}), ast.sourceSpan);
|
||||
}
|
||||
|
||||
// Now choose which implementation to use to actually create the necessary AST nodes.
|
||||
const localizeCall = this.downlevelLocalizedStrings ?
|
||||
this.createES5TaggedTemplateFunctionCall(localizeTag, {elements, expressions}) :
|
||||
this.factory.createTaggedTemplate(localizeTag, {elements, expressions});
|
||||
|
||||
return this.setSourceMapRange(localizeCall, ast.sourceSpan);
|
||||
private createTaggedTemplateExpression(tag: TExpression, template: TemplateLiteral<TExpression>):
|
||||
TExpression {
|
||||
return this.downlevelTaggedTemplates ? this.createES5TaggedTemplateFunctionCall(tag, template) :
|
||||
this.factory.createTaggedTemplate(tag, template);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -98,6 +98,10 @@ export class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitTaggedTemplateExpr(ast: o.TaggedTemplateExpr, context: Context): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitInstantiateExpr(ast: o.InstantiateExpr, context: Context): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LeadingComment, leadingComment, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, LocalizedString, NotExpr, ParseSourceFile, ParseSourceSpan, PartialModule, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, TypeofExpr, UnaryOperator, UnaryOperatorExpr, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LeadingComment, leadingComment, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, LocalizedString, NotExpr, ParseSourceFile, ParseSourceSpan, PartialModule, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, TaggedTemplateExpr, ThrowStmt, TryCatchStmt, TypeofExpr, UnaryOperator, UnaryOperatorExpr, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {attachComments} from '../ngtsc/translator';
|
||||
@@ -544,6 +544,10 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
|
||||
expr.args.map(arg => arg.visitExpression(this, null))));
|
||||
}
|
||||
|
||||
visitTaggedTemplateExpr(expr: TaggedTemplateExpr): RecordedNode<ts.TaggedTemplateExpression> {
|
||||
throw new Error('tagged templates are not supported in pre-ivy mode.');
|
||||
}
|
||||
|
||||
visitInstantiateExpr(expr: InstantiateExpr): RecordedNode<ts.NewExpression> {
|
||||
return this.postProcess(
|
||||
expr,
|
||||
|
||||
Reference in New Issue
Block a user