| // 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; | |
| /// Size in bytes of the Dart source code this JavaScript was compiled from. | |
| // TODO(nshahan): Remove nullability after support for multiple module formats | |
| // is removed. | |
| final int? dartSize; | |
| Program( | |
| this.body, { | |
| this.scriptTag, | |
| this.name, | |
| this.header = const [], | |
| this.librarySelfVar, | |
| this.dartSize, | |
| }); | |
| @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, {this._multiline = false}); | |
| @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); | |
| } | |
| } |