blob: e877ebcf4164aa6a648d0658e6b7d64a40f5bd6d [file] [log] [blame] [edit]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// ignore_for_file: omit_local_variable_types
import 'precedence.dart';
import 'printer.dart';
abstract class NodeVisitor<T> {
T visitProgram(Program node);
T visitBlock(Block node);
T visitDebuggerStatement(DebuggerStatement node);
T visitExpressionStatement(ExpressionStatement node);
T visitEmptyStatement(EmptyStatement node);
T visitIf(If node);
T visitFor(For node);
T visitForIn(ForIn node);
T visitForOf(ForOf node);
T visitWhile(While node);
T visitDo(Do node);
T visitContinue(Continue node);
T visitBreak(Break node);
T visitReturn(Return node);
T visitThrow(Throw node);
T visitTry(Try node);
T visitCatch(Catch node);
T visitSwitch(Switch node);
T visitCase(Case node);
T visitDefault(Default node);
T visitFunctionDeclaration(FunctionDeclaration node);
T visitLabeledStatement(LabeledStatement node);
T visitLiteralStatement(LiteralStatement node);
T visitDartYield(DartYield node);
T visitLiteralExpression(LiteralExpression node);
T visitVariableDeclarationList(VariableDeclarationList node);
T visitAssignment(Assignment node);
T visitVariableInitialization(VariableInitialization node);
T visitConditional(Conditional cond);
T visitNew(New node);
T visitCall(Call node);
T visitBinary(Binary node);
T visitPrefix(Prefix node);
T visitPostfix(Postfix node);
T visitSpread(Spread node);
T visitYield(Yield node);
T visitIdentifier(Identifier node);
T visitThis(This node);
T visitSuper(Super node);
T visitAccess(PropertyAccess node);
T visitRestParameter(RestParameter node);
T visitNamedFunction(NamedFunction node);
T visitFun(Fun node);
T visitArrowFun(ArrowFun node);
T visitLiteralBool(LiteralBool node);
T visitLiteralString(LiteralString node);
T visitLiteralNumber(LiteralNumber node);
T visitLiteralNull(LiteralNull node);
T visitArrayInitializer(ArrayInitializer node);
T visitArrayHole(ArrayHole node);
T visitObjectInitializer(ObjectInitializer node);
T visitProperty(Property node);
T visitRegExpLiteral(RegExpLiteral node);
T visitTemplateString(TemplateString node);
T visitTaggedTemplate(TaggedTemplate node);
T visitAwait(Await node);
T visitClassDeclaration(ClassDeclaration node);
T visitClassExpression(ClassExpression node);
T visitMethod(Method node);
T visitImportDeclaration(ImportDeclaration node);
T visitExportDeclaration(ExportDeclaration node);
T visitExportClause(ExportClause node);
T visitNameSpecifier(NameSpecifier node);
T visitComment(Comment node);
T visitCommentExpression(CommentExpression node);
T visitInterpolatedExpression(InterpolatedExpression node);
T visitInterpolatedLiteral(InterpolatedLiteral node);
T visitInterpolatedParameter(InterpolatedParameter node);
T visitInterpolatedSelector(InterpolatedSelector node);
T visitInterpolatedStatement(InterpolatedStatement node);
T visitInterpolatedMethod(InterpolatedMethod node);
T visitInterpolatedIdentifier(InterpolatedIdentifier node);
T visitArrayBindingPattern(ArrayBindingPattern node);
T visitObjectBindingPattern(ObjectBindingPattern node);
T visitDestructuredVariable(DestructuredVariable node);
T visitSimpleBindingPattern(SimpleBindingPattern node);
}
abstract class BaseVisitor<T> implements NodeVisitor<T> {
T visitNode(Node node);
@override
T visitProgram(Program node) => visitNode(node);
T visitStatement(Statement node) => visitModuleItem(node);
T visitLoop(Loop node) => visitStatement(node);
T visitJump(Statement node) => visitStatement(node);
@override
T visitBlock(Block node) => visitStatement(node);
@override
T visitDebuggerStatement(node) => visitStatement(node);
@override
T visitExpressionStatement(ExpressionStatement node) => visitStatement(node);
@override
T visitEmptyStatement(EmptyStatement node) => visitStatement(node);
@override
T visitIf(If node) => visitStatement(node);
@override
T visitFor(For node) => visitLoop(node);
@override
T visitForIn(ForIn node) => visitLoop(node);
@override
T visitForOf(ForOf node) => visitLoop(node);
@override
T visitWhile(While node) => visitLoop(node);
@override
T visitDo(Do node) => visitLoop(node);
@override
T visitContinue(Continue node) => visitJump(node);
@override
T visitBreak(Break node) => visitJump(node);
@override
T visitReturn(Return node) => visitJump(node);
@override
T visitThrow(Throw node) => visitJump(node);
@override
T visitTry(Try node) => visitStatement(node);
@override
T visitSwitch(Switch node) => visitStatement(node);
@override
T visitFunctionDeclaration(FunctionDeclaration node) => visitStatement(node);
@override
T visitLabeledStatement(LabeledStatement node) => visitStatement(node);
@override
T visitLiteralStatement(LiteralStatement node) => visitStatement(node);
@override
T visitCatch(Catch node) => visitNode(node);
@override
T visitCase(Case node) => visitNode(node);
@override
T visitDefault(Default node) => visitNode(node);
T visitExpression(Expression node) => visitNode(node);
@override
T visitLiteralExpression(LiteralExpression node) => visitExpression(node);
@override
T visitVariableDeclarationList(VariableDeclarationList node) =>
visitExpression(node);
@override
T visitAssignment(Assignment node) => visitExpression(node);
@override
T visitVariableInitialization(VariableInitialization node) =>
visitExpression(node);
@override
T visitConditional(Conditional node) => visitExpression(node);
@override
T visitNew(New node) => visitExpression(node);
@override
T visitCall(Call node) => visitExpression(node);
@override
T visitBinary(Binary node) => visitExpression(node);
@override
T visitPrefix(Prefix node) => visitExpression(node);
@override
T visitPostfix(Postfix node) => visitExpression(node);
@override
T visitSpread(Spread node) => visitPrefix(node);
@override
T visitYield(Yield node) => visitExpression(node);
@override
T visitAccess(PropertyAccess node) => visitExpression(node);
@override
T visitIdentifier(Identifier node) => visitExpression(node);
@override
T visitThis(This node) => visitExpression(node);
@override
T visitSuper(Super node) => visitExpression(node);
@override
T visitRestParameter(RestParameter node) => visitNode(node);
@override
T visitNamedFunction(NamedFunction node) => visitExpression(node);
T visitFunctionExpression(FunctionExpression node) => visitExpression(node);
@override
T visitFun(Fun node) => visitFunctionExpression(node);
@override
T visitArrowFun(ArrowFun node) => visitFunctionExpression(node);
T visitLiteral(Literal node) => visitExpression(node);
@override
T visitLiteralBool(LiteralBool node) => visitLiteral(node);
@override
T visitLiteralString(LiteralString node) => visitLiteral(node);
@override
T visitLiteralNumber(LiteralNumber node) => visitLiteral(node);
@override
T visitLiteralNull(LiteralNull node) => visitLiteral(node);
@override
T visitArrayInitializer(ArrayInitializer node) => visitExpression(node);
@override
T visitArrayHole(ArrayHole node) => visitExpression(node);
@override
T visitObjectInitializer(ObjectInitializer node) => visitExpression(node);
@override
T visitProperty(Property node) => visitNode(node);
@override
T visitRegExpLiteral(RegExpLiteral node) => visitExpression(node);
@override
T visitTemplateString(TemplateString node) => visitExpression(node);
@override
T visitTaggedTemplate(TaggedTemplate node) => visitExpression(node);
@override
T visitClassDeclaration(ClassDeclaration node) => visitStatement(node);
@override
T visitClassExpression(ClassExpression node) => visitExpression(node);
@override
T visitMethod(Method node) => visitProperty(node);
T visitModuleItem(ModuleItem node) => visitNode(node);
@override
T visitImportDeclaration(ImportDeclaration node) => visitModuleItem(node);
@override
T visitExportDeclaration(ExportDeclaration node) => visitModuleItem(node);
@override
T visitExportClause(ExportClause node) => visitNode(node);
@override
T visitNameSpecifier(NameSpecifier node) => visitNode(node);
T visitInterpolatedNode(InterpolatedNode node) => visitNode(node);
@override
T visitInterpolatedExpression(InterpolatedExpression node) =>
visitInterpolatedNode(node);
@override
T visitInterpolatedLiteral(InterpolatedLiteral node) =>
visitInterpolatedNode(node);
@override
T visitInterpolatedParameter(InterpolatedParameter node) =>
visitInterpolatedNode(node);
@override
T visitInterpolatedSelector(InterpolatedSelector node) =>
visitInterpolatedNode(node);
@override
T visitInterpolatedStatement(InterpolatedStatement node) =>
visitInterpolatedNode(node);
@override
T visitInterpolatedMethod(InterpolatedMethod node) =>
visitInterpolatedNode(node);
@override
T visitInterpolatedIdentifier(InterpolatedIdentifier node) =>
visitInterpolatedNode(node);
@override
T visitComment(Comment node);
@override
T visitCommentExpression(CommentExpression node);
@override
T visitAwait(Await node) => visitExpression(node);
@override
T visitDartYield(DartYield node) => visitStatement(node);
T visitBindingPattern(BindingPattern node) => visitNode(node);
@override
T visitArrayBindingPattern(ArrayBindingPattern node) =>
visitBindingPattern(node);
@override
T visitObjectBindingPattern(ObjectBindingPattern node) =>
visitBindingPattern(node);
@override
T visitDestructuredVariable(DestructuredVariable node) => visitNode(node);
@override
T visitSimpleBindingPattern(SimpleBindingPattern node) => visitNode(node);
}
class BaseVisitorVoid extends BaseVisitor<void> {
@override
void visitNode(Node node) {
node.visitChildren(this);
}
// Ignore comments by default.
@override
void visitComment(Comment node) {}
@override
void visitCommentExpression(CommentExpression node) {}
}
abstract class Node {
/// Sets the source location of this node. For performance reasons, we allow
/// setting this after construction.
Object? sourceInformation;
T accept<T>(NodeVisitor<T> visitor);
void visitChildren(NodeVisitor visitor);
/// Shallow clone of node.
///
/// Does not clone positions since the only use of this private method is
/// create a copy with a new position.
Node _clone();
/// Returns a node equivalent to [this], but with new source position and end
/// source position.
T _withSourceInformation<T extends Node>(
Object? sourceInformation,
T Function() cloneFunc,
) {
if (sourceInformation == this.sourceInformation) {
return this as T;
}
final clone = cloneFunc();
// TODO(sra): Should existing data be 'sticky' if we try to overwrite with
// `null`?
clone.sourceInformation = sourceInformation;
return clone;
}
Node withSourceInformation(Object? sourceInformation) =>
_withSourceInformation(sourceInformation, _clone);
bool get isCommaOperator => false;
Statement toStatement() {
throw UnsupportedError('toStatement');
}
Statement toReturn() {
throw UnsupportedError('toReturn');
}
// For debugging
@override
String toString() {
var context = SimpleJavaScriptPrintingContext();
var opts = JavaScriptPrintingOptions(allowKeywordsInProperties: true);
context.buffer.write('js_ast `');
accept(Printer(opts, context));
context.buffer.write('`');
return context.getText();
}
}
class LibraryBundle extends Program {
final List<Program> libraries;
LibraryBundle(this.libraries, {super.name, super.scriptTag, super.header})
: super(const []);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitProgram(this);
@override
void visitChildren(NodeVisitor visitor) {
for (var library in libraries) {
library.accept(visitor);
}
}
@override
LibraryBundle _clone() => LibraryBundle(
libraries,
name: name,
scriptTag: scriptTag,
header: header,
);
}
// TODO(nshahan): Rename to convey that this is a single Dart library after
// support for multiple module formats is removed.
class Program extends Node {
/// Script tag hash-bang, e.g. `#!/usr/bin/env node`.
final String? scriptTag;
/// Top-level statements in the program.
final List<ModuleItem> body;
// Top-level comments prepended to the compiled program.
final List<Comment> header;
/// The module's own name.
///
/// This is not used in ES6, but is provided to allow module lowering.
final String? name;
/// The self reference for the library that is used within it's own scope.
///
/// This manifests as the name of the argument for the library initialization
/// function to which all library members are assigned.
// TODO(nshahan): Remove nullability after support for multiple module formats
// is removed.
final Identifier? librarySelfVar;
Program(
this.body, {
this.scriptTag,
this.name,
this.header = const [],
this.librarySelfVar,
});
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitProgram(this);
@override
void visitChildren(NodeVisitor visitor) {
for (ModuleItem statement in body) {
statement.accept(visitor);
}
}
@override
Program _clone() =>
Program(body, scriptTag: scriptTag, name: name, header: header);
}
abstract class Statement extends ModuleItem {
static Statement from(List<Statement> statements) {
// TODO(jmesserly): empty block singleton? Should this use empty statement?
if (statements.isEmpty) return Block([]);
if (statements.length == 1) return statements[0];
return Block(statements);
}
/// True if this declares any name from [names].
///
/// This predicate is true if the statement declares a variable via `let` or
/// `const` with any name in the set. This does not include variables nested
/// inside of blocks. The predicate tests whether adding a declaration of one
/// of the named variables to a block containing this statement will be a
/// JavaScript syntax error due to a redeclared identifier.
bool shadows(Set<String> names) => false;
/// Whether this statement would always `return` if used as a function body.
///
/// This is only well defined on the outermost block; it cannot be used for a
/// block inside of a loop (because of `break` and `continue`).
bool get alwaysReturns => false;
@override
Statement _clone();
@override
Statement withSourceInformation(Object? sourceInformation) =>
_withSourceInformation(sourceInformation, _clone);
/// If this statement [shadows] any name from [names], this will wrap it in a
/// new scoped [Block].
Statement toScopedBlock(Set<String> names) {
return shadows(names) ? Block([this], isScope: true) : this;
}
@override
Statement toStatement() => this;
@override
Statement toReturn() => Block([this, Return()]);
Block toBlock() => Block([this]);
}
class Block extends Statement {
final List<Statement> statements;
/// True to preserve this [Block] for scoping reasons.
final bool isScope;
Block(this.statements, {this.isScope = false});
Block.empty() : statements = <Statement>[], isScope = false;
@override
bool get alwaysReturns =>
statements.isNotEmpty && statements.last.alwaysReturns;
@override
Block toBlock() => this;
@override
bool shadows(Set<String> names) =>
!isScope && statements.any((s) => s.shadows(names));
@override
Block toScopedBlock(Set<String> names) {
var scoped = statements.any((s) => s.shadows(names));
if (scoped == isScope) return this;
return Block(statements, isScope: scoped)
..sourceInformation = sourceInformation;
}
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitBlock(this);
@override
void visitChildren(NodeVisitor visitor) {
for (Statement statement in statements) {
statement.accept(visitor);
}
}
@override
Block _clone() => Block(statements);
}
class ExpressionStatement extends Statement {
final Expression expression;
ExpressionStatement(this.expression);
@override
bool shadows(Set<String> names) {
Expression expression = this.expression;
return expression is VariableDeclarationList && expression.shadows(names);
}
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitExpressionStatement(this);
@override
void visitChildren(NodeVisitor visitor) {
expression.accept(visitor);
}
@override
ExpressionStatement _clone() => ExpressionStatement(expression);
}
class EmptyStatement extends Statement {
EmptyStatement();
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitEmptyStatement(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
EmptyStatement _clone() => EmptyStatement();
}
class If extends Statement {
final Expression condition;
final Statement then;
final Statement otherwise;
If(this.condition, this.then, this.otherwise);
If.noElse(this.condition, this.then) : otherwise = EmptyStatement();
@override
bool get alwaysReturns =>
hasElse && then.alwaysReturns && otherwise.alwaysReturns;
bool get hasElse => otherwise is! EmptyStatement;
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitIf(this);
@override
void visitChildren(NodeVisitor visitor) {
condition.accept(visitor);
then.accept(visitor);
otherwise.accept(visitor);
}
@override
If _clone() => If(condition, then, otherwise);
}
abstract class Loop extends Statement {
final Statement body;
Loop(this.body);
}
class For extends Loop {
final Expression? init;
final Expression? condition;
final Expression? update;
For(this.init, this.condition, this.update, Statement body) : super(body);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitFor(this);
@override
void visitChildren(NodeVisitor visitor) {
init?.accept(visitor);
condition?.accept(visitor);
update?.accept(visitor);
body.accept(visitor);
}
@override
For _clone() => For(init, condition, update, body);
}
class ForIn extends Loop {
// Note that [VariableDeclarationList] is a subclass of [Expression].
// Therefore we can type the leftHandSide as [Expression].
final Expression leftHandSide;
final Expression object;
ForIn(this.leftHandSide, this.object, Statement body) : super(body);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitForIn(this);
@override
void visitChildren(NodeVisitor visitor) {
leftHandSide.accept(visitor);
object.accept(visitor);
body.accept(visitor);
}
@override
ForIn _clone() => ForIn(leftHandSide, object, body);
}
class ForOf extends Loop {
// Note that [VariableDeclarationList] is a subclass of [Expression].
// Therefore we can type the leftHandSide as [Expression].
final Expression leftHandSide;
final Expression iterable;
ForOf(this.leftHandSide, this.iterable, Statement body) : super(body);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitForOf(this);
@override
void visitChildren(NodeVisitor visitor) {
leftHandSide.accept(visitor);
iterable.accept(visitor);
body.accept(visitor);
}
@override
ForOf _clone() => ForOf(leftHandSide, iterable, body);
}
class While extends Loop {
final Expression condition;
While(this.condition, Statement body) : super(body);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitWhile(this);
@override
void visitChildren(NodeVisitor visitor) {
condition.accept(visitor);
body.accept(visitor);
}
@override
While _clone() => While(condition, body);
}
class Do extends Loop {
final Expression condition;
Do(super.body, this.condition);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitDo(this);
@override
void visitChildren(NodeVisitor visitor) {
body.accept(visitor);
condition.accept(visitor);
}
@override
Do _clone() => Do(body, condition);
}
class Continue extends Statement {
/// Name of the label L for `continue L;` or `null` for `continue;`.
final String? targetLabel;
Continue(this.targetLabel);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitContinue(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
Continue _clone() => Continue(targetLabel);
}
class Break extends Statement {
/// Name of the label L for `break L;` or `null` for `break;`.
final String? targetLabel;
Break(this.targetLabel);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitBreak(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
Break _clone() => Break(targetLabel);
}
class Return extends Statement {
final Expression? value;
Return([this.value]);
@override
bool get alwaysReturns => true;
@override
Statement toReturn() => this;
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitReturn(this);
@override
void visitChildren(NodeVisitor visitor) {
value?.accept(visitor);
}
@override
Return _clone() => Return(value);
static bool foundIn(Node node) {
_returnFinder.found = false;
node.accept(_returnFinder);
return _returnFinder.found;
}
}
final _returnFinder = _ReturnFinder();
class _ReturnFinder extends BaseVisitorVoid {
bool found = false;
@override
void visitReturn(Return node) {
found = true;
}
@override
void visitNode(Node node) {
if (!found) super.visitNode(node);
}
}
class Throw extends Statement {
final Expression expression;
Throw(this.expression);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitThrow(this);
@override
void visitChildren(NodeVisitor visitor) {
expression.accept(visitor);
}
@override
Throw _clone() => Throw(expression);
}
class Try extends Statement {
final Block body;
final Catch? catchPart; // Can be null if [finallyPart] is non-null.
final Block? finallyPart; // Can be null if [catchPart] is non-null.
Try(this.body, this.catchPart, this.finallyPart) {
assert(catchPart != null || finallyPart != null);
}
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitTry(this);
@override
void visitChildren(NodeVisitor visitor) {
body.accept(visitor);
catchPart?.accept(visitor);
finallyPart?.accept(visitor);
}
@override
Try _clone() => Try(body, catchPart, finallyPart);
}
class Catch extends Node {
final Identifier declaration;
final Block body;
Catch(this.declaration, this.body);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitCatch(this);
@override
void visitChildren(NodeVisitor visitor) {
declaration.accept(visitor);
body.accept(visitor);
}
@override
Catch _clone() => Catch(declaration, body);
}
class Switch extends Statement {
final Expression key;
final List<SwitchClause> cases;
Switch(this.key, this.cases);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitSwitch(this);
@override
void visitChildren(NodeVisitor visitor) {
key.accept(visitor);
for (var clause in cases) {
clause.accept(visitor);
}
}
@override
Switch _clone() => Switch(key, cases);
}
abstract class SwitchClause extends Node {
final Block body;
SwitchClause(this.body);
}
class Case extends SwitchClause {
final Expression expression;
Case(this.expression, Block body) : super(body);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitCase(this);
@override
void visitChildren(NodeVisitor visitor) {
expression.accept(visitor);
body.accept(visitor);
}
@override
Case _clone() => Case(expression, body);
}
class Default extends SwitchClause {
Default(super.body);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitDefault(this);
@override
void visitChildren(NodeVisitor visitor) {
body.accept(visitor);
}
@override
Default _clone() => Default(body);
}
class FunctionDeclaration extends Statement {
final Identifier name;
final Fun function;
FunctionDeclaration(this.name, this.function);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitFunctionDeclaration(this);
@override
void visitChildren(NodeVisitor visitor) {
name.accept(visitor);
function.accept(visitor);
}
@override
FunctionDeclaration _clone() => FunctionDeclaration(name, function);
}
class LabeledStatement extends Statement {
final String label;
final Statement body;
LabeledStatement(this.label, this.body);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitLabeledStatement(this);
@override
void visitChildren(NodeVisitor visitor) {
body.accept(visitor);
}
@override
LabeledStatement _clone() => LabeledStatement(label, body);
}
class LiteralStatement extends Statement {
final String code;
LiteralStatement(this.code);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitLiteralStatement(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
LiteralStatement _clone() => LiteralStatement(code);
}
/// Not a real JavaScript node, but represents the yield statement from a dart
/// program translated to JavaScript.
class DartYield extends Statement {
final Expression expression;
final bool hasStar;
DartYield(this.expression, this.hasStar);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitDartYield(this);
@override
void visitChildren(NodeVisitor visitor) {
expression.accept(visitor);
}
@override
DartYield _clone() => DartYield(expression, hasStar);
}
abstract class Expression extends Node {
Expression();
factory Expression.binary(List<Expression> exprs, String op) {
Expression? comma;
for (var node in exprs) {
comma = (comma == null) ? node : Binary(op, comma, node);
}
return comma!;
}
int get precedenceLevel;
@override
Statement toStatement() => ExpressionStatement(toVoidExpression());
@override
Statement toReturn() => Return(this);
Expression toVoidExpression() => this;
Expression toAssignExpression(Expression left, [String? op]) =>
Assignment.compound(left, op, this);
// TODO(jmesserly): make this work for more cases?
Statement toVariableDeclaration(VariableBinding name) =>
VariableDeclarationList('let', [
VariableInitialization(name, this),
]).toStatement();
@override
Expression _clone();
@override
Expression withSourceInformation(sourceInformation) =>
_withSourceInformation(sourceInformation, _clone);
}
class LiteralExpression extends Expression {
final String template;
LiteralExpression(this.template);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitLiteralExpression(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
LiteralExpression _clone() => LiteralExpression(template);
// Code that uses LiteralExpression must take care of operator precedences,
// and put parenthesis if needed.
@override
int get precedenceLevel => PRIMARY;
}
/// [VariableDeclarationList] is a subclass of [Expression] to simplify the AST.
class VariableDeclarationList extends Expression {
/// The `var` or `let` or `const` keyword used for this variable declaration
/// list.
///
/// Can be null in the case of non-static field declarations.
final String? keyword;
final List<VariableInitialization> declarations;
VariableDeclarationList(this.keyword, this.declarations);
/// True if this declares any name from [names].
///
/// Analogous to the predicate [Statement.shadows].
bool shadows(Set<String> names) {
if (keyword == 'var') return false;
for (var d in declarations) {
if (d.declaration.shadows(names)) return true;
}
return false;
}
@override
T accept<T>(NodeVisitor<T> visitor) =>
visitor.visitVariableDeclarationList(this);
@override
void visitChildren(NodeVisitor visitor) {
for (VariableInitialization declaration in declarations) {
declaration.accept(visitor);
}
}
@override
VariableDeclarationList _clone() =>
VariableDeclarationList(keyword, declarations);
@override
int get precedenceLevel => EXPRESSION;
}
class Assignment extends Expression {
final Expression leftHandSide;
final String? op; // `null` if the assignment is not compound.
final Expression value;
Assignment(this.leftHandSide, this.value) : op = null;
// If `this.op == null` this will be a non-compound assignment.
Assignment.compound(this.leftHandSide, this.op, this.value);
@override
int get precedenceLevel => ASSIGNMENT;
bool get isCompound => op != null;
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitAssignment(this);
@override
void visitChildren(NodeVisitor visitor) {
leftHandSide.accept(visitor);
value.accept(visitor);
}
@override
Assignment _clone() => Assignment.compound(leftHandSide, op, value);
}
class VariableInitialization extends Expression {
final VariableBinding declaration;
// The initializing value can be missing, e.g. for `a` in `var a, b=1;`.
final Expression? value;
VariableInitialization(this.declaration, this.value);
@override
T accept<T>(NodeVisitor<T> visitor) =>
visitor.visitVariableInitialization(this);
@override
VariableInitialization _clone() => VariableInitialization(declaration, value);
@override
int get precedenceLevel => ASSIGNMENT;
@override
void visitChildren(NodeVisitor visitor) {
declaration.accept(visitor);
value?.accept(visitor);
}
}
sealed class VariableBinding extends Expression {
/// True if this binding declares any name from [names].
///
/// Analogous to the predicate [Statement.shadows].
bool shadows(Set<String> names);
}
// TODO(jmesserly): destructuring was originally implemented in the context of
// Closure Compiler work. Rethink how this is represented.
class DestructuredVariable extends Expression implements Parameter {
final Identifier name;
/// The property in an object binding pattern, for example:
///
/// let key = 'z';
/// let {[key]: foo} = {z: 'bar'};
/// console.log(foo); // "bar"
///
// TODO(jmesserly): parser does not support this feature.
final Expression? property;
final BindingPattern? structure;
final Expression? defaultValue;
DestructuredVariable({
required this.name,
this.property,
this.structure,
this.defaultValue,
});
@override
bool shadows(Set<String> names) {
return name.shadows(names) || (structure?.shadows(names) ?? false);
}
@override
T accept<T>(NodeVisitor<T> visitor) =>
visitor.visitDestructuredVariable(this);
@override
void visitChildren(NodeVisitor visitor) {
name.accept(visitor);
structure?.accept(visitor);
defaultValue?.accept(visitor);
}
/// Avoid parenthesis when pretty-printing.
@override
int get precedenceLevel => PRIMARY;
@override
String get parameterName => name.name;
@override
DestructuredVariable _clone() => DestructuredVariable(
name: name,
property: property,
structure: structure,
defaultValue: defaultValue,
);
}
abstract class BindingPattern extends Expression implements VariableBinding {
final List<DestructuredVariable> variables;
BindingPattern(this.variables);
@override
bool shadows(Set<String> names) {
for (var v in variables) {
if (v.shadows(names)) return true;
}
return false;
}
@override
void visitChildren(NodeVisitor visitor) {
for (DestructuredVariable v in variables) {
v.accept(visitor);
}
}
}
class SimpleBindingPattern extends BindingPattern {
final Identifier name;
SimpleBindingPattern(this.name) : super([DestructuredVariable(name: name)]);
@override
T accept<T>(NodeVisitor<T> visitor) =>
visitor.visitSimpleBindingPattern(this);
@override
bool shadows(Set<String> names) => names.contains(name.name);
/// Avoid parenthesis when pretty-printing.
@override
int get precedenceLevel => PRIMARY;
@override
SimpleBindingPattern _clone() => SimpleBindingPattern(name);
}
class ObjectBindingPattern extends BindingPattern {
ObjectBindingPattern(super.variables);
@override
T accept<T>(NodeVisitor<T> visitor) =>
visitor.visitObjectBindingPattern(this);
/// Avoid parenthesis when pretty-printing.
@override
int get precedenceLevel => PRIMARY;
@override
ObjectBindingPattern _clone() => ObjectBindingPattern(variables);
}
class ArrayBindingPattern extends BindingPattern {
ArrayBindingPattern(super.variables);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitArrayBindingPattern(this);
/// Avoid parenthesis when pretty-printing.
@override
int get precedenceLevel => PRIMARY;
@override
ArrayBindingPattern _clone() => ArrayBindingPattern(variables);
}
/// Entirely transparent wrapper only used to identify the node as being a
/// rewritten invocation that includes correctness checks in case of a hot
/// reload at runtime.
class InvocationWithHotReloadChecks extends Conditional {
InvocationWithHotReloadChecks(super.condition, super.then, super.otherwise);
}
class Conditional extends Expression {
final Expression condition;
final Expression then;
final Expression otherwise;
Conditional(this.condition, this.then, this.otherwise);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitConditional(this);
@override
void visitChildren(NodeVisitor visitor) {
condition.accept(visitor);
then.accept(visitor);
otherwise.accept(visitor);
}
@override
Conditional _clone() => Conditional(condition, then, otherwise);
@override
int get precedenceLevel => ASSIGNMENT;
}
class Call extends Expression {
Expression target;
List<Expression> arguments;
Call(this.target, this.arguments);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitCall(this);
@override
void visitChildren(NodeVisitor visitor) {
target.accept(visitor);
for (Expression arg in arguments) {
arg.accept(visitor);
}
}
@override
Call _clone() => Call(target, arguments);
@override
int get precedenceLevel => CALL;
}
class New extends Call {
New(super.cls, super.arguments);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitNew(this);
@override
New _clone() => New(target, arguments);
@override
int get precedenceLevel => ACCESS;
}
class Binary extends Expression {
final String op;
final Expression left;
final Expression right;
Binary(this.op, this.left, this.right);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitBinary(this);
@override
Binary _clone() => Binary(op, left, right);
@override
void visitChildren(NodeVisitor visitor) {
left.accept(visitor);
right.accept(visitor);
}
@override
bool get isCommaOperator => op == ',';
@override
Expression toVoidExpression() {
if (!isCommaOperator) return super.toVoidExpression();
var l = left.toVoidExpression();
var r = right.toVoidExpression();
if (l == left && r == right) return this;
return Binary(',', l, r);
}
@override
Statement toStatement() {
if (!isCommaOperator) return super.toStatement();
return Block([left.toStatement(), right.toStatement()]);
}
@override
Statement toReturn() {
if (!isCommaOperator) return super.toReturn();
return Block([left.toStatement(), right.toReturn()]);
}
List<Expression> commaToExpressionList() {
if (!isCommaOperator) throw StateError('not a comma expression');
var exprs = <Expression>[];
_flattenComma(exprs, left);
_flattenComma(exprs, right);
return exprs;
}
static void _flattenComma(List<Expression> exprs, Expression node) {
if (node is Binary && node.isCommaOperator) {
_flattenComma(exprs, node.left);
_flattenComma(exprs, node.right);
} else {
exprs.add(node);
}
}
@override
int get precedenceLevel {
// TODO(floitsch): switch to constant map.
switch (op) {
case '**':
return EXPONENTIATION;
case '*':
case '/':
case '%':
return MULTIPLICATIVE;
case '+':
case '-':
return ADDITIVE;
case '<<':
case '>>':
case '>>>':
return SHIFT;
case '<':
case '>':
case '<=':
case '>=':
case 'instanceof':
case 'in':
return RELATIONAL;
case '==':
case '===':
case '!=':
case '!==':
return EQUALITY;
case '&':
return BIT_AND;
case '^':
return BIT_XOR;
case '|':
return BIT_OR;
case '&&':
return LOGICAL_AND;
case '||':
return LOGICAL_OR;
case ',':
return EXPRESSION;
default:
throw 'Internal Error: Unhandled binary operator: $op';
}
}
}
class Prefix extends Expression {
final String op;
final Expression argument;
Prefix(this.op, this.argument);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitPrefix(this);
@override
Prefix _clone() => Prefix(op, argument);
@override
void visitChildren(NodeVisitor visitor) {
argument.accept(visitor);
}
@override
int get precedenceLevel => UNARY;
}
/// SpreadElement isn't really a prefix expression, as it can only appear in
/// certain places such as ArgumentList and BindingPattern, but we pretend
/// it is for simplicity's sake.
class Spread extends Prefix {
Spread(Expression operand) : super('...', operand);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitSpread(this);
@override
Spread _clone() => Spread(argument);
}
class Postfix extends Expression {
final String op;
final Expression argument;
Postfix(this.op, this.argument);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitPostfix(this);
@override
Postfix _clone() => Postfix(op, argument);
@override
void visitChildren(NodeVisitor visitor) {
argument.accept(visitor);
}
@override
int get precedenceLevel => UNARY;
}
abstract class Parameter implements Expression, VariableBinding {
String get parameterName;
}
class Identifier extends Expression implements Parameter {
final String name;
final bool allowRename;
Identifier(this.name, {this.allowRename = true}) {
if (!_identifierRE.hasMatch(name)) {
throw ArgumentError.value(name, 'name', 'not a valid identifier');
}
}
static final RegExp _identifierRE = RegExp(r'^[A-Za-z_$][A-Za-z_$0-9]*$');
@override
bool shadows(Set<String> names) => names.contains(name);
@override
Identifier _clone() => Identifier(name, allowRename: allowRename);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitIdentifier(this);
@override
int get precedenceLevel => PRIMARY;
@override
String get parameterName => name;
@override
void visitChildren(NodeVisitor visitor) {}
}
/// This is an expression for convenience in the AST.
class RestParameter extends Expression implements Parameter {
final Identifier parameter;
RestParameter(this.parameter);
@override
bool shadows(Set<String> names) => names.contains(parameter.name);
@override
RestParameter _clone() => RestParameter(parameter);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitRestParameter(this);
@override
void visitChildren(NodeVisitor visitor) {
parameter.accept(visitor);
}
@override
int get precedenceLevel => PRIMARY;
@override
String get parameterName => parameter.parameterName;
}
class This extends Literal {
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitThis(this);
@override
This _clone() => This();
@override
int get precedenceLevel => PRIMARY;
@override
void visitChildren(NodeVisitor visitor) {}
}
/// `super` is more restricted in the ES6 spec, but for simplicity we accept
/// it anywhere that `this` is accepted.
class Super extends Literal {
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitSuper(this);
@override
Super _clone() => Super();
@override
int get precedenceLevel => PRIMARY;
@override
void visitChildren(NodeVisitor visitor) {}
}
class NamedFunction extends Expression {
final Identifier name;
final Fun function;
/// A heuristic to force extra parens around this function.
///
/// V8 and other engines use this IIFE (immediately invoked function
/// expression) heuristic to eagerly parse a function.
final bool immediatelyInvoked;
NamedFunction(this.name, this.function, [this.immediatelyInvoked = false]);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitNamedFunction(this);
@override
void visitChildren(NodeVisitor visitor) {
name.accept(visitor);
function.accept(visitor);
}
@override
NamedFunction _clone() => NamedFunction(name, function, immediatelyInvoked);
@override
int get precedenceLevel =>
immediatelyInvoked ? EXPRESSION : PRIMARY_LOW_PRECEDENCE;
}
abstract class FunctionExpression extends Expression {
Node get body; // Expression or block
List<Parameter> get params;
AsyncModifier get asyncModifier;
}
class Fun extends FunctionExpression {
@override
final List<Parameter> params;
@override
final Block body;
/// Whether this is a JS generator (`function*`) that may contain `yield`.
final bool isGenerator;
@override
final AsyncModifier asyncModifier;
Fun(
this.params,
this.body, {
this.isGenerator = false,
this.asyncModifier = AsyncModifier.sync,
});
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitFun(this);
@override
void visitChildren(NodeVisitor visitor) {
for (Parameter param in params) {
param.accept(visitor);
}
body.accept(visitor);
}
@override
Fun _clone() =>
Fun(params, body, isGenerator: isGenerator, asyncModifier: asyncModifier);
@override
int get precedenceLevel => PRIMARY_LOW_PRECEDENCE;
}
class ArrowFun extends FunctionExpression {
@override
final List<Parameter> params;
@override
final Node body; // Expression or Block
@override
AsyncModifier get asyncModifier => AsyncModifier.sync;
ArrowFun(this.params, this.body);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitArrowFun(this);
@override
void visitChildren(NodeVisitor visitor) {
for (Parameter param in params) {
param.accept(visitor);
}
body.accept(visitor);
}
@override
int get precedenceLevel => PRIMARY_LOW_PRECEDENCE;
@override
ArrowFun _clone() => ArrowFun(params, body);
}
/// The Dart sync, sync*, async, and async* modifier.
///
/// See [DartYield]. This is not used for JS functions.
enum AsyncModifier {
sync(isAsync: false, isYielding: false, description: 'sync'),
async(isAsync: true, isYielding: false, description: 'async'),
asyncStar(isAsync: true, isYielding: true, description: 'async*'),
syncStar(isAsync: false, isYielding: true, description: 'sync*');
const AsyncModifier({
required this.isAsync,
required this.isYielding,
required this.description,
});
final bool isAsync;
final bool isYielding;
final String description;
@override
String toString() => description;
}
class PropertyAccess extends Expression {
final Expression receiver;
final Expression selector;
PropertyAccess(this.receiver, this.selector);
PropertyAccess.field(this.receiver, String fieldName)
: selector = LiteralString('"$fieldName"');
PropertyAccess.indexed(this.receiver, int index)
: selector = LiteralNumber('$index');
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitAccess(this);
@override
void visitChildren(NodeVisitor visitor) {
receiver.accept(visitor);
selector.accept(visitor);
}
@override
PropertyAccess _clone() => PropertyAccess(receiver, selector);
@override
int get precedenceLevel => ACCESS;
}
abstract class Literal extends Expression {
@override
void visitChildren(NodeVisitor visitor) {}
@override
int get precedenceLevel => PRIMARY;
}
class LiteralBool extends Literal {
final bool value;
LiteralBool(this.value);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitLiteralBool(this);
// [visitChildren] inherited from [Literal].
@override
LiteralBool _clone() => LiteralBool(value);
}
class LiteralNull extends Literal {
LiteralNull();
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitLiteralNull(this);
@override
LiteralNull _clone() => LiteralNull();
}
class LiteralString extends Literal {
final String value;
/// Constructs a LiteralString from a string value.
///
/// The constructor does not add the required quotes. If [value] is not
/// surrounded by quotes and property escaped, the resulting object is invalid
/// as a JS value.
///
/// TODO(sra): Introduce variants for known valid strings that don't allocate
/// a new string just to add quotes.
LiteralString(this.value);
/// Gets the value inside the string without the beginning and end quotes.
String get valueWithoutQuotes => value.substring(1, value.length - 1);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitLiteralString(this);
@override
LiteralString _clone() => LiteralString(value);
}
class LiteralNumber extends Literal {
final String value; // Must be a valid JavaScript number literal.
LiteralNumber(this.value);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitLiteralNumber(this);
@override
LiteralNumber _clone() => LiteralNumber(value);
/// Use a different precedence level depending on whether the value contains a
/// dot to ensure we generate `(1).toString()` and `1.0.toString()`.
@override
int get precedenceLevel => value.contains('.') ? PRIMARY : UNARY;
}
class ArrayInitializer extends Expression {
final List<Expression> elements;
final bool multiline;
ArrayInitializer(this.elements, {this.multiline = false});
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitArrayInitializer(this);
@override
void visitChildren(NodeVisitor visitor) {
for (Expression element in elements) {
element.accept(visitor);
}
}
@override
ArrayInitializer _clone() => ArrayInitializer(elements);
@override
int get precedenceLevel => PRIMARY;
}
/// An empty place in an [ArrayInitializer].
///
/// For example the list [1, , , 2] would contain two holes.
class ArrayHole extends Expression {
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitArrayHole(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
ArrayHole _clone() => ArrayHole();
@override
int get precedenceLevel => PRIMARY;
}
class ObjectInitializer extends Expression {
final List<Property> properties;
final bool _multiline;
/// Constructs a new object-initializer containing the given [properties].
ObjectInitializer(this.properties, {bool multiline = false})
: _multiline = multiline;
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitObjectInitializer(this);
@override
void visitChildren(NodeVisitor visitor) {
for (Property init in properties) {
init.accept(visitor);
}
}
@override
ObjectInitializer _clone() => ObjectInitializer(properties);
@override
int get precedenceLevel => PRIMARY;
/// If set to true, forces a vertical layout when using the [Printer].
///
/// Otherwise, layout will be vertical if and only if any [properties] are
/// [FunctionExpression]s.
bool get multiline {
return _multiline || properties.any((p) => p.value is FunctionExpression);
}
}
class Property extends Node {
final Expression name;
final Expression value;
final bool isStatic;
final bool isClassProperty;
Property(
this.name,
this.value, {
this.isStatic = false,
this.isClassProperty = false,
});
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitProperty(this);
@override
void visitChildren(NodeVisitor visitor) {
name.accept(visitor);
value.accept(visitor);
}
@override
Property _clone() => Property(
name,
value,
isStatic: isStatic,
isClassProperty: isClassProperty,
);
}
// TODO(jmesserly): parser does not support this yet.
class TemplateString extends Expression {
/// The parts of this template string: a sequence of [String]s and
/// [Expression]s.
///
/// Strings and expressions will alternate, for example:
///
/// `foo${1 + 2} bar ${'hi'}`
///
/// would be represented by [strings]:
///
/// ['foo', ' bar ', '']
///
/// and [interpolations]:
///
/// [new JS.Binary('+', js.number(1), js.number(2)),
/// new JS.LiteralString("'hi'")]
///
/// There should be exactly one more string than interpolation expression.
final List<String> strings;
final List<Expression> interpolations;
TemplateString(this.strings, this.interpolations) {
assert(strings.length == interpolations.length + 1);
}
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitTemplateString(this);
@override
void visitChildren(NodeVisitor visitor) {
for (var element in interpolations) {
element.accept(visitor);
}
}
@override
TemplateString _clone() => TemplateString(strings, interpolations);
@override
int get precedenceLevel => PRIMARY;
}
// TODO(jmesserly): parser does not support this yet.
class TaggedTemplate extends Expression {
final Expression tag;
final TemplateString template;
TaggedTemplate(this.tag, this.template);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitTaggedTemplate(this);
@override
void visitChildren(NodeVisitor visitor) {
tag.accept(visitor);
template.accept(visitor);
}
@override
TaggedTemplate _clone() => TaggedTemplate(tag, template);
@override
int get precedenceLevel => CALL;
}
// TODO(jmesserly): parser does not support this yet.
class Yield extends Expression {
final Expression? value;
/// Whether this yield expression is a `yield*` that iterates each item in
/// [value].
final bool star;
Yield(this.value, {this.star = false});
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitYield(this);
@override
void visitChildren(NodeVisitor visitor) {
value?.accept(visitor);
}
@override
Yield _clone() => Yield(value);
@override
int get precedenceLevel => YIELD;
}
class ClassDeclaration extends Statement {
final ClassExpression classExpr;
ClassDeclaration(this.classExpr);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitClassDeclaration(this);
@override
void visitChildren(NodeVisitor visitor) => classExpr.accept(visitor);
@override
ClassDeclaration _clone() => ClassDeclaration(classExpr);
}
class ClassExpression extends Expression {
final Identifier name;
final Expression? heritage;
final List<Property> properties;
ClassExpression(this.name, this.heritage, this.properties);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitClassExpression(this);
@override
void visitChildren(NodeVisitor visitor) {
name.accept(visitor);
heritage?.accept(visitor);
for (Property element in properties) {
element.accept(visitor);
}
}
@override
ClassDeclaration toStatement() => ClassDeclaration(this);
@override
ClassExpression _clone() => ClassExpression(name, heritage, properties);
@override
int get precedenceLevel => PRIMARY_LOW_PRECEDENCE;
}
class Method extends Node implements Property {
@override
final Expression name;
final Fun function;
final bool isGetter;
final bool isSetter;
@override
final bool isStatic;
@override
final bool isClassProperty = false;
Method(
this.name,
this.function, {
this.isGetter = false,
this.isSetter = false,
this.isStatic = false,
}) {
assert(!isGetter || function.params.isEmpty);
assert(!isSetter || function.params.length == 1);
assert(!isGetter && !isSetter || !function.isGenerator);
}
@override
Fun get value => function;
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitMethod(this);
@override
void visitChildren(NodeVisitor visitor) {
name.accept(visitor);
function.accept(visitor);
}
@override
Method _clone() => Method(
name,
function,
isGetter: isGetter,
isSetter: isSetter,
isStatic: isStatic,
);
}
/// Tag class for all interpolated positions.
mixin InterpolatedNode implements Node {
dynamic get nameOrPosition;
bool get isNamed => nameOrPosition is String;
bool get isPositional => nameOrPosition is int;
}
class InterpolatedExpression extends Expression with InterpolatedNode {
@override
final Object nameOrPosition;
InterpolatedExpression(this.nameOrPosition);
@override
T accept<T>(NodeVisitor<T> visitor) =>
visitor.visitInterpolatedExpression(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
InterpolatedExpression _clone() => InterpolatedExpression(nameOrPosition);
@override
int get precedenceLevel => PRIMARY;
}
class InterpolatedLiteral extends Literal with InterpolatedNode {
@override
final Object nameOrPosition;
InterpolatedLiteral(this.nameOrPosition);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitInterpolatedLiteral(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
InterpolatedLiteral _clone() => InterpolatedLiteral(nameOrPosition);
}
class InterpolatedParameter extends Expression
with InterpolatedNode
implements Identifier {
@override
final Object nameOrPosition;
@override
String get name {
throw 'InterpolatedParameter.name must not be invoked';
}
@override
String get parameterName {
throw 'InterpolatedParameter.parameterName must not be invoked';
}
@override
bool shadows(Set<String> names) => false;
@override
bool get allowRename => false;
InterpolatedParameter(this.nameOrPosition);
@override
T accept<T>(NodeVisitor<T> visitor) =>
visitor.visitInterpolatedParameter(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
InterpolatedParameter _clone() => InterpolatedParameter(nameOrPosition);
@override
int get precedenceLevel => PRIMARY;
}
class InterpolatedSelector extends Expression with InterpolatedNode {
@override
final Object nameOrPosition;
InterpolatedSelector(this.nameOrPosition);
@override
T accept<T>(NodeVisitor<T> visitor) =>
visitor.visitInterpolatedSelector(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
InterpolatedSelector _clone() => InterpolatedSelector(nameOrPosition);
@override
int get precedenceLevel => PRIMARY;
}
class InterpolatedStatement extends Statement with InterpolatedNode {
@override
final Object nameOrPosition;
InterpolatedStatement(this.nameOrPosition);
@override
T accept<T>(NodeVisitor<T> visitor) =>
visitor.visitInterpolatedStatement(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
InterpolatedStatement _clone() => InterpolatedStatement(nameOrPosition);
}
// TODO(jmesserly): generalize this to InterpolatedProperty?
class InterpolatedMethod extends Expression
with InterpolatedNode
implements Method {
@override
final Object nameOrPosition;
InterpolatedMethod(this.nameOrPosition);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitInterpolatedMethod(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
InterpolatedMethod _clone() => InterpolatedMethod(nameOrPosition);
@override
int get precedenceLevel => PRIMARY;
@override
Expression get name => throw _unsupported;
@override
Fun get value => throw _unsupported;
@override
bool get isGetter => throw _unsupported;
@override
bool get isSetter => throw _unsupported;
@override
bool get isStatic => throw _unsupported;
@override
bool get isClassProperty => throw _unsupported;
@override
Fun get function => throw _unsupported;
Error get _unsupported =>
UnsupportedError('$runtimeType does not support this member.');
}
class InterpolatedIdentifier extends Expression
with InterpolatedNode
implements Identifier {
@override
final Object nameOrPosition;
InterpolatedIdentifier(this.nameOrPosition);
@override
bool shadows(Set<String> names) => false;
@override
T accept<T>(NodeVisitor<T> visitor) =>
visitor.visitInterpolatedIdentifier(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
InterpolatedIdentifier _clone() => InterpolatedIdentifier(nameOrPosition);
@override
int get precedenceLevel => PRIMARY;
@override
String get name => throw '$runtimeType does not support this member.';
@override
String get parameterName =>
throw '$runtimeType does not support this member.';
@override
bool get allowRename => false;
}
/// [RegExpLiteral]s, despite being called "Literal", do not inherit from
/// [Literal].
///
/// Indeed, regular expressions in JavaScript have a side-effect and are thus
/// not in the same category as numbers or strings.
class RegExpLiteral extends Expression {
/// Contains the pattern and the flags.
final String pattern;
RegExpLiteral(this.pattern);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitRegExpLiteral(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
RegExpLiteral _clone() => RegExpLiteral(pattern);
@override
int get precedenceLevel => PRIMARY;
}
/// An asynchronous await.
///
/// Not part of JavaScript. We desugar this expression before outputting.
/// Should only occur in a [Fun] with `asyncModifier` async or asyncStar.
class Await extends Expression {
/// The awaited expression.
final Expression expression;
Await(this.expression);
@override
int get precedenceLevel => UNARY;
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitAwait(this);
@override
void visitChildren(NodeVisitor visitor) => expression.accept(visitor);
@override
Await _clone() => Await(expression);
}
/// A comment.
///
/// Extends [Statement] so we can add comments before statements in [Block] and
/// [Program].
class Comment extends Statement {
final String comment;
Comment(this.comment);
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitComment(this);
@override
Comment _clone() => Comment(comment);
@override
void visitChildren(NodeVisitor visitor) {}
}
/// A comment for expressions.
///
/// Extends [Expression] so we can add comments before expressions. Has the
/// highest possible precedence, so we don't add parentheses around it.
class CommentExpression extends Expression {
final String comment;
final Expression expression;
CommentExpression(this.comment, this.expression);
@override
int get precedenceLevel => PRIMARY;
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitCommentExpression(this);
@override
CommentExpression _clone() => CommentExpression(comment, expression);
@override
void visitChildren(NodeVisitor visitor) => expression.accept(visitor);
}
class DebuggerStatement extends Statement {
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitDebuggerStatement(this);
@override
DebuggerStatement _clone() => DebuggerStatement();
@override
void visitChildren(NodeVisitor visitor) {}
}
/// Represents allowed module items:
/// [Statement], [ImportDeclaration], and [ExportDeclaration].
abstract class ModuleItem extends Node {}
class ImportDeclaration extends ModuleItem {
final Identifier? defaultBinding;
// Can be `null`, a single specifier of `* as name`, or a list.
final List<NameSpecifier>? namedImports;
final LiteralString from;
ImportDeclaration({
this.defaultBinding,
this.namedImports,
required this.from,
});
/// The `import "name.js"` form of import.
ImportDeclaration.all(LiteralString module) : this(from: module);
/// If this import has `* as name` returns the name, otherwise null.
Identifier? get importStarAs {
var imports = namedImports;
if (imports != null && imports.length == 1 && imports[0].isStar) {
return imports[0].asName;
}
return null;
}
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitImportDeclaration(this);
@override
void visitChildren(NodeVisitor visitor) {
var imports = namedImports;
if (imports != null) {
for (NameSpecifier name in imports) {
name.accept(visitor);
}
}
from.accept(visitor);
}
@override
ImportDeclaration _clone() => ImportDeclaration(
defaultBinding: defaultBinding,
namedImports: namedImports,
from: from,
);
}
class ExportDeclaration extends ModuleItem {
/// Exports a name from this module.
///
/// This can be a [ClassDeclaration] or [FunctionDeclaration].
/// If [isDefault] is true, it can also be an [Expression].
/// Otherwise it can be a [VariableDeclarationList] or an [ExportClause].
final Node exported;
/// True if this is an `export default`.
final bool isDefault;
ExportDeclaration(this.exported, {this.isDefault = false}) {
assert(
exported is ClassDeclaration ||
exported is FunctionDeclaration ||
isDefault
? exported is Expression
: exported is VariableDeclarationList || exported is ExportClause,
);
}
/// Gets the list of names exported by this export declaration, or `null`
/// if this is an `export *`.
///
/// This can be useful for lowering to other module formats.
List<NameSpecifier>? get exportedNames {
if (isDefault) return [NameSpecifier(Identifier('default'))];
var exported = this.exported;
if (exported is ClassDeclaration) {
return [NameSpecifier(exported.classExpr.name)];
}
if (exported is FunctionDeclaration) return [NameSpecifier(exported.name)];
if (exported is VariableDeclarationList) {
return exported.declarations
.map((i) => NameSpecifier(i.declaration as Identifier))
.toList();
}
if (exported is ExportClause) {
if (exported.exportStar) return null;
return exported.exports;
}
throw StateError('invalid export declaration');
}
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitExportDeclaration(this);
@override
void visitChildren(NodeVisitor visitor) => exported.accept(visitor);
@override
ExportDeclaration _clone() =>
ExportDeclaration(exported, isDefault: isDefault);
}
class ExportClause extends Node {
final List<NameSpecifier> exports;
final LiteralString? from;
ExportClause(this.exports, {this.from});
/// The `export * from 'name.js'` form.
ExportClause.star(LiteralString from)
: this([NameSpecifier.star()], from: from);
/// True if this is an `export *`.
bool get exportStar => exports.length == 1 && exports[0].isStar;
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitExportClause(this);
@override
void visitChildren(NodeVisitor visitor) {
for (NameSpecifier name in exports) {
name.accept(visitor);
}
from?.accept(visitor);
}
@override
ExportClause _clone() => ExportClause(exports, from: from);
}
/// An import or export specifier.
class NameSpecifier extends Node {
final Identifier? name;
final Identifier? asName;
NameSpecifier(this.name, {this.asName});
NameSpecifier.star() : this(null);
/// True if this is a `* as someName` specifier.
bool get isStar => name == null;
@override
T accept<T>(NodeVisitor<T> visitor) => visitor.visitNameSpecifier(this);
@override
void visitChildren(NodeVisitor visitor) {}
@override
NameSpecifier _clone() => NameSpecifier(name, asName: asName);
}
/// Visitor used to transform specific subtrees within the [Node] AST.
///
/// This transformer will only clone a node if it detects a subtree under that
/// node has changed, otherwise it returns the same node. Subclasses of this
/// transformer can override specific 'visit' methods to return modified nodes.
abstract class Transformer extends BaseVisitor<Node> {
T visit<T extends Node>(Node node) {
return (node.accept(this)..sourceInformation = node.sourceInformation) as T;
}
T? visitNullable<T extends Node>(Node? node) {
return (node?.accept(this)?..sourceInformation = node?.sourceInformation)
as T?;
}
List<T> visitList<T extends Node>(List<Node> node) {
return node.map((e) => visit<T>(e)).toList();
}
List<T>? visitNullableList<T extends Node>(List<Node>? node) {
return node?.map((e) => visit<T>(e)).toList();
}
bool isUnchanged(Node? before, Node? after) {
if (before == null) return after == null;
if (after == null) return false;
return identical(before, after) &&
identical(before.sourceInformation, after.sourceInformation);
}
bool isUnchangedList(List<Node>? before, List<Node>? after) {
if (before == null) return after == null;
if (after == null) return false;
if (before.length != after.length) return false;
for (var i = 0; i < before.length; i++) {
if (!isUnchanged(before[i], after[i])) return false;
}
return true;
}
N transformIfUnchanged1<N extends Node, T extends Node>(
N node,
T childNode,
N Function(T newChildNode) clone,
) {
final newChildNode = visit<T>(childNode);
final hasNoChange = isUnchanged(childNode, newChildNode);
return hasNoChange ? node : clone(newChildNode);
}
N transformIfUnchanged2<N extends Node, T extends Node, U extends Node>(
N node,
T childNode1,
U childNode2,
N Function(T newChildNode1, U newChildNode2) clone,
) {
final newChildNode1 = visit<T>(childNode1);
final newChildNode2 = visit<U>(childNode2);
final hasNoChange =
isUnchanged(childNode1, newChildNode1) &&
isUnchanged(childNode2, newChildNode2);
return hasNoChange ? node : clone(newChildNode1, newChildNode2);
}
N transformIfUnchanged3<
N extends Node,
T extends Node,
U extends Node,
V extends Node
>(
N node,
T childNode1,
U childNode2,
V childNode3,
N Function(T newChildNode1, U newChildNode2, V newChildNode3) clone,
) {
final newChildNode1 = visit<T>(childNode1);
final newChildNode2 = visit<U>(childNode2);
final newChildNode3 = visit<V>(childNode3);
final hasNoChange =
isUnchanged(childNode1, newChildNode1) &&
isUnchanged(childNode2, newChildNode2) &&
isUnchanged(childNode3, newChildNode3);
return hasNoChange
? node
: clone(newChildNode1, newChildNode2, newChildNode3);
}
N transformIfUnchangedList<N extends Node, T extends Node>(
N node,
List<T> childNodes,
N Function(List<T> newChildNodes) clone,
) {
final newChildNodes = visitList<T>(childNodes);
final hasNoChange = isUnchangedList(childNodes, newChildNodes);
return hasNoChange ? node : clone(newChildNodes);
}
N transformIfUnchangedList1<N extends Node, T extends Node, U extends Node>(
N node,
List<T> childNodeList,
U childNode,
N Function(List<T> newChildNodeList, U childNode) clone,
) {
final newChildNodeList = visitList<T>(childNodeList);
final newChildNode = visit<U>(childNode);
final hasNoChange =
isUnchangedList(childNodeList, newChildNodeList) &&
isUnchanged(childNode, newChildNode);
return hasNoChange ? node : clone(newChildNodeList, newChildNode);
}
@override
Node visitNode(Node node) => node;
@override
Node visitComment(Comment node) => node;
@override
Node visitAccess(PropertyAccess node) {
return transformIfUnchanged2(
node,
node.receiver,
node.selector,
PropertyAccess.new,
);
}
@override
Node visitArrayBindingPattern(ArrayBindingPattern node) {
return transformIfUnchangedList(
node,
node.variables,
ArrayBindingPattern.new,
);
}
@override
Node visitArrayInitializer(ArrayInitializer node) {
return transformIfUnchangedList(node, node.elements, ArrayInitializer.new);
}
@override
Node visitArrowFun(ArrowFun node) {
return transformIfUnchangedList1(
node,
node.params,
node.body,
ArrowFun.new,
);
}
@override
Node visitAssignment(Assignment node) {
if (node.isCompound) {
return transformIfUnchanged2(
node,
node.leftHandSide,
node.value,
(e1, e2) => Assignment.compound(e1, node.op, e2),
);
}
return transformIfUnchanged2(
node,
node.leftHandSide,
node.value,
Assignment.new,
);
}
@override
Node visitAwait(Await node) {
return transformIfUnchanged1(node, node.expression, Await.new);
}
@override
Node visitBinary(Binary node) {
return transformIfUnchanged2(
node,
node.left,
node.right,
(e1, e2) => Binary(node.op, e1, e2),
);
}
@override
Node visitBlock(Block node) {
return transformIfUnchangedList(node, node.statements, Block.new);
}
@override
Node visitCall(Call node) {
return transformIfUnchangedList1(
node,
node.arguments,
node.target,
(e1, e2) => Call(e2, e1),
);
}
@override
Node visitCase(Case node) {
return transformIfUnchanged2(node, node.expression, node.body, Case.new);
}
@override
Node visitCatch(Catch node) {
return transformIfUnchanged2(node, node.declaration, node.body, Catch.new);
}
@override
Node visitClassDeclaration(ClassDeclaration node) {
return transformIfUnchanged1(node, node.classExpr, ClassDeclaration.new);
}
@override
Node visitClassExpression(ClassExpression node) {
final name = visit<Identifier>(node.name);
final heritage = visitNullable<Expression>(node.heritage);
final properties = visitList<Property>(node.properties);
if (isUnchanged(name, node.name) &&
isUnchanged(heritage, node.heritage) &&
isUnchangedList(properties, node.properties)) {
return node;
}
return ClassExpression(name, heritage, properties);
}
@override
Node visitCommentExpression(CommentExpression node) {
return transformIfUnchanged1(
node,
node.expression,
(e) => CommentExpression(node.comment, e),
);
}
@override
Node visitConditional(Conditional node) {
return transformIfUnchanged3(
node,
node.condition,
node.then,
node.otherwise,
Conditional.new,
);
}
@override
Node visitDartYield(DartYield node) {
return transformIfUnchanged1(
node,
node.expression,
(e) => DartYield(e, node.hasStar),
);
}
@override
Node visitDefault(Default node) {
return transformIfUnchanged1(node, node.body, Default.new);
}
@override
Node visitDestructuredVariable(DestructuredVariable node) {
final name = visit<Identifier>(node.name);
final property = visitNullable<Expression>(node.property);
final structure = visitNullable<BindingPattern>(node.structure);
final defaultValue = visitNullable<Expression>(node.defaultValue);
if (isUnchanged(node.name, name) &&
isUnchanged(node.property, node.property) &&
isUnchanged(node.structure, structure) &&
isUnchanged(node.defaultValue, defaultValue)) {
return node;
}
return DestructuredVariable(
name: name,
property: property,
structure: structure,
defaultValue: defaultValue,
);
}
@override
Node visitDo(Do node) {
return transformIfUnchanged2(node, node.body, node.condition, Do.new);
}
@override
Node visitExportClause(ExportClause node) {
final exports = visitList<NameSpecifier>(node.exports);
final from = visitNullable<LiteralString>(node.from);
if (isUnchangedList(node.exports, exports) &&
isUnchanged(node.from, from)) {
return node;
}
return ExportClause(exports, from: from);
}
@override
Node visitExportDeclaration(ExportDeclaration node) {
return transformIfUnchanged1(
node,
node.exported,
(e) => ExportDeclaration(e, isDefault: node.isDefault),
);
}
@override
Node visitExpressionStatement(ExpressionStatement node) {
return transformIfUnchanged1(
node,
node.expression,
ExpressionStatement.new,
);
}
@override
Node visitFor(For node) {
final init = visitNullable<Expression>(node.init);
final condition = visitNullable<Expression>(node.condition);
final update = visitNullable<Expression>(node.update);
final body = visit<Statement>(node.body);
if (isUnchanged(node.init, init) &&
isUnchanged(node.condition, condition) &&
isUnchanged(node.update, update) &&
isUnchanged(node.body, body)) {
return node;
}
return For(init, condition, update, body);
}
@override
Node visitForIn(ForIn node) {
return transformIfUnchanged3(
node,
node.leftHandSide,
node.object,
node.body,
ForIn.new,
);
}
@override
Node visitForOf(ForOf node) {
return transformIfUnchanged3(
node,
node.leftHandSide,
node.iterable,
node.body,
ForOf.new,
);
}
@override
Node visitFun(Fun node) {
return transformIfUnchangedList1(
node,
node.params,
node.body,
(e1, e2) => Fun(
e1,
e2,
isGenerator: node.isGenerator,
asyncModifier: node.asyncModifier,
),
);
}
@override
Node visitFunctionDeclaration(FunctionDeclaration node) {
return transformIfUnchanged2(
node,
node.name,
node.function,
FunctionDeclaration.new,
);
}
@override
Node visitIf(If node) {
return transformIfUnchanged3(
node,
node.condition,
node.then,
node.otherwise,
If.new,
);
}
@override
Node visitImportDeclaration(ImportDeclaration node) {
final defaultBinding = visitNullable<Identifier>(node.defaultBinding);
final namedImports = visitNullableList<NameSpecifier>(node.namedImports);
final from = visit<LiteralString>(node.from);
if (isUnchanged(node.defaultBinding, defaultBinding) &&
isUnchangedList(node.namedImports, namedImports) &&
isUnchanged(node.from, from)) {
return node;
}
return ImportDeclaration(
defaultBinding: defaultBinding,
namedImports: namedImports,
from: from,
);
}
@override
Node visitLabeledStatement(LabeledStatement node) {
return transformIfUnchanged1(
node,
node.body,
(e) => LabeledStatement(node.label, e),
);
}
@override
Node visitMethod(Method node) {
return transformIfUnchanged2(
node,
node.name,
node.function,
(e1, e2) => Method(
e1,
e2,
isGetter: node.isGetter,
isSetter: node.isSetter,
isStatic: node.isStatic,
),
);
}
@override
Node visitNameSpecifier(NameSpecifier node) {
final name = visitNullable<Identifier>(node.name);
final asName = visitNullable<Identifier>(node.asName);
if (isUnchanged(node.name, name) && isUnchanged(node.asName, asName)) {
return node;
}
return NameSpecifier(name, asName: asName);
}
@override
Node visitNamedFunction(NamedFunction node) {
return transformIfUnchanged2(
node,
node.name,
node.function,
(e1, e2) => NamedFunction(e1, e2, node.immediatelyInvoked),
);
}
@override
Node visitNew(New node) {
return transformIfUnchangedList1(
node,
node.arguments,
node.target,
(e1, e2) => New(e2, e1),
);
}
@override
Node visitObjectBindingPattern(ObjectBindingPattern node) {
return transformIfUnchangedList(
node,
node.variables,
ObjectBindingPattern.new,
);
}
@override
Node visitObjectInitializer(ObjectInitializer node) {
return transformIfUnchangedList(
node,
node.properties,
(e) => ObjectInitializer(e, multiline: node.multiline),
);
}
@override
Node visitPostfix(Postfix node) {
return transformIfUnchanged1(
node,
node.argument,
(e) => Postfix(node.op, e),
);
}
@override
Node visitPrefix(Prefix node) {
return transformIfUnchanged1(
node,
node.argument,
(e) => Prefix(node.op, e),
);
}
@override
Node visitProgram(Program node) {
final body = visitList<ModuleItem>(node.body);
final header = visitList<Comment>(node.header);
if (isUnchangedList(node.body, body) &&
isUnchangedList(node.header, header)) {
return node;
}
return Program(
body,
scriptTag: node.scriptTag,
name: node.name,
header: header,
);
}
@override
Node visitProperty(Property node) {
return transformIfUnchanged2(node, node.name, node.value, Property.new);
}
@override
Node visitRestParameter(RestParameter node) {
return transformIfUnchanged1(node, node.parameter, RestParameter.new);
}
@override
Node visitReturn(Return node) {
final value = visitNullable<Expression>(node.value);
if (isUnchanged(node.value, value)) {
return node;
}
return Return(value);
}
@override
Node visitSimpleBindingPattern(SimpleBindingPattern node) {
return transformIfUnchanged1(node, node.name, SimpleBindingPattern.new);
}
@override
Node visitSpread(Spread node) {
return transformIfUnchanged1(node, node.argument, Spread.new);
}
@override
Node visitSwitch(Switch node) {
return transformIfUnchangedList1(
node,
node.cases,
node.key,
(e1, e2) => Switch(e2, e1),
);
}
@override
Node visitTaggedTemplate(TaggedTemplate node) {
return transformIfUnchanged2(
node,
node.tag,
node.template,
TaggedTemplate.new,
);
}
@override
Node visitTemplateString(TemplateString node) {
return transformIfUnchangedList(
node,
node.interpolations,
(e) => TemplateString(node.strings, e),
);
}
@override
Node visitThrow(Throw node) {
return transformIfUnchanged1(node, node.expression, Throw.new);
}
@override
Node visitTry(Try node) {
final body = visit<Block>(node.body);
final catchPart = visitNullable<Catch>(node.catchPart);
final finallyPart = visitNullable<Block>(node.finallyPart);
if (isUnchanged(node.body, body) &&
isUnchanged(node.catchPart, catchPart) &&
isUnchanged(node.finallyPart, finallyPart)) {
return node;
}
return Try(body, catchPart, finallyPart);
}
@override
Node visitVariableDeclarationList(VariableDeclarationList node) {
return transformIfUnchangedList(
node,
node.declarations,
(e) => VariableDeclarationList(node.keyword, e),
);
}
@override
Node visitVariableInitialization(VariableInitialization node) {
final declaration = visit<VariableBinding>(node.declaration);
final value = visitNullable<Expression>(node.value);
if (isUnchanged(node.declaration, declaration) &&
isUnchanged(node.value, value)) {
return node;
}
return VariableInitialization(declaration, value);
}
@override
Node visitWhile(While node) {
return transformIfUnchanged2(node, node.condition, node.body, While.new);
}
@override
Node visitYield(Yield node) {
final value = visitNullable<Expression>(node.value);
if (isUnchanged(node.value, value)) {
return node;
}
return Yield(value, star: node.star);
}
}