fn gen_node, path: Option>)()

in unsupported/juno/crates/juno/src/gen_js.rs [289:3045]


    fn gen_node<'gc>(&mut self, ctx: &'gc GCLock, node: &'gc Node<'gc>, path: Option<Path<'gc>>) {
        match node {
            Node::Empty(_) => {}
            Node::Metadata(_) => {}

            Node::Program(Program { metadata: _, body }) => {
                self.visit_stmt_list(ctx, body, Path::new(node, NodeField::body));
            }
            Node::Module(Module { metadata: _, body }) => {
                self.visit_stmt_list(ctx, body, Path::new(node, NodeField::body));
            }

            Node::FunctionExpression(FunctionExpression {
                metadata: _,
                id,
                params,
                body,
                type_parameters,
                return_type,
                predicate,
                generator,
                is_async,
            })
            | Node::FunctionDeclaration(FunctionDeclaration {
                metadata: _,
                id,
                params,
                body,
                type_parameters,
                return_type,
                predicate,
                generator,
                is_async,
            }) => {
                if *is_async {
                    out_token!(self, node, "async function");
                } else {
                    out_token!(self, node, "function");
                }
                if *generator {
                    out!(self, "*");
                    if id.is_some() {
                        self.space(ForceSpace::No);
                    }
                } else if id.is_some() {
                    self.space(ForceSpace::Yes);
                }
                if let Some(id) = id {
                    id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                }
                self.visit_func_params_body(
                    ctx,
                    params,
                    *type_parameters,
                    *return_type,
                    *predicate,
                    *body,
                    node,
                );
            }

            Node::ArrowFunctionExpression(ArrowFunctionExpression {
                metadata: _,
                id: _,
                params,
                body,
                type_parameters,
                return_type,
                predicate,
                expression,
                is_async,
            }) => {
                let mut need_sep = false;
                if *is_async {
                    out!(self, "async");
                    need_sep = true;
                }
                if let Some(type_parameters) = type_parameters {
                    type_parameters.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_parameters)),
                    );
                    need_sep = false;
                }
                // Single parameter without type info doesn't need parens.
                // But only in expression mode, otherwise it is ugly.
                if params.len() == 1
                    && type_parameters.is_none()
                    && return_type.is_none()
                    && predicate.is_none()
                    && node_isa!(Node::Identifier, params[0])
                    && node_cast!(Node::Identifier, params[0])
                        .type_annotation
                        .is_none()
                    && !node_cast!(Node::Identifier, params[0]).optional
                    && (*expression || self.pretty == Pretty::No)
                {
                    if need_sep {
                        out!(self, " ");
                    }
                    params[0].visit(ctx, self, Some(Path::new(node, NodeField::params)));
                } else {
                    out!(self, "(");
                    for (i, param) in params.iter().enumerate() {
                        if i > 0 {
                            self.comma();
                        }
                        param.visit(ctx, self, Some(Path::new(node, NodeField::params)));
                    }
                    out!(self, ")");
                }
                if let Some(return_type) = return_type {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    self.print_child(
                        ctx,
                        Some(return_type),
                        Path::new(node, NodeField::return_type),
                        ChildPos::Anywhere,
                    );
                }
                if let Some(predicate) = predicate {
                    self.space(ForceSpace::Yes);
                    predicate.visit(ctx, self, Some(Path::new(node, NodeField::predicate)));
                }
                self.space(ForceSpace::No);
                out!(self, "=>");
                self.space(ForceSpace::No);
                match &body {
                    Node::BlockStatement(_) => {
                        body.visit(ctx, self, Some(Path::new(node, NodeField::body)));
                    }
                    _ => {
                        self.print_child(
                            ctx,
                            Some(*body),
                            Path::new(node, NodeField::body),
                            ChildPos::Right,
                        );
                    }
                }
            }

            Node::WhileStatement(WhileStatement {
                metadata: _,
                body,
                test,
            }) => {
                out!(self, "while");
                self.space(ForceSpace::No);
                out!(self, "(");
                test.visit(ctx, self, Some(Path::new(node, NodeField::test)));
                out!(self, ")");
                self.visit_stmt_or_block(
                    ctx,
                    *body,
                    ForceBlock::No,
                    Path::new(node, NodeField::body),
                );
            }
            Node::DoWhileStatement(DoWhileStatement {
                metadata: _,
                body,
                test,
            }) => {
                out!(self, "do");
                let block = self.visit_stmt_or_block(
                    ctx,
                    *body,
                    ForceBlock::No,
                    Path::new(node, NodeField::body),
                );
                if block {
                    self.space(ForceSpace::No);
                } else {
                    out!(self, ";");
                    self.newline();
                }
                out!(self, "while");
                self.space(ForceSpace::No);
                out!(self, "(");
                test.visit(ctx, self, Some(Path::new(node, NodeField::test)));
                out!(self, ")");
            }

            Node::ForInStatement(ForInStatement {
                metadata: _,
                left,
                right,
                body,
            }) => {
                out!(self, "for(");
                left.visit(ctx, self, Some(Path::new(node, NodeField::left)));
                out!(self, " in ");
                right.visit(ctx, self, Some(Path::new(node, NodeField::right)));
                out!(self, ")");
                self.visit_stmt_or_block(
                    ctx,
                    *body,
                    ForceBlock::No,
                    Path::new(node, NodeField::body),
                );
            }
            Node::ForOfStatement(ForOfStatement {
                metadata: _,
                left,
                right,
                body,
                is_await,
            }) => {
                out!(self, "for{}(", if *is_await { " await" } else { "" });
                left.visit(ctx, self, Some(Path::new(node, NodeField::left)));
                out!(self, " of ");
                right.visit(ctx, self, Some(Path::new(node, NodeField::right)));
                out!(self, ")");
                self.visit_stmt_or_block(
                    ctx,
                    *body,
                    ForceBlock::No,
                    Path::new(node, NodeField::body),
                );
            }
            Node::ForStatement(ForStatement {
                metadata: _,
                init,
                test,
                update,
                body,
            }) => {
                out!(self, "for(");
                self.print_child(ctx, *init, Path::new(node, NodeField::init), ChildPos::Left);
                out!(self, ";");
                if let Some(test) = test {
                    self.space(ForceSpace::No);
                    test.visit(ctx, self, Some(Path::new(node, NodeField::test)));
                }
                out!(self, ";");
                if let Some(update) = update {
                    self.space(ForceSpace::No);
                    update.visit(ctx, self, Some(Path::new(node, NodeField::update)));
                }
                out!(self, ")");
                self.visit_stmt_or_block(
                    ctx,
                    *body,
                    ForceBlock::No,
                    Path::new(node, NodeField::body),
                );
            }

            Node::DebuggerStatement(_) => {
                out!(self, "debugger");
            }
            Node::EmptyStatement(_) => {}

            Node::BlockStatement(BlockStatement { metadata: _, body }) => {
                if body.is_empty() {
                    out!(self, "{{}}");
                } else {
                    out!(self, "{{");
                    self.inc_indent();
                    self.newline();
                    self.visit_stmt_list(ctx, body, Path::new(node, NodeField::body));
                    self.dec_indent();
                    self.newline();
                    out!(self, "}}");
                }
            }

            Node::BreakStatement(BreakStatement { metadata: _, label }) => {
                out!(self, "break");
                if let Some(label) = label {
                    self.space(ForceSpace::Yes);
                    label.visit(ctx, self, Some(Path::new(node, NodeField::label)));
                }
            }
            Node::ContinueStatement(ContinueStatement { metadata: _, label }) => {
                out!(self, "continue");
                if let Some(label) = label {
                    self.space(ForceSpace::Yes);
                    label.visit(ctx, self, Some(Path::new(node, NodeField::label)));
                }
            }

            Node::ThrowStatement(ThrowStatement {
                metadata: _,
                argument,
            }) => {
                out_token!(self, node, "throw ");
                argument.visit(ctx, self, Some(Path::new(node, NodeField::argument)));
            }
            Node::ReturnStatement(ReturnStatement {
                metadata: _,
                argument,
            }) => {
                out_token!(self, node, "return");
                if let Some(argument) = argument {
                    out!(self, " ");
                    argument.visit(ctx, self, Some(Path::new(node, NodeField::argument)));
                }
            }
            Node::WithStatement(WithStatement {
                metadata: _,
                object,
                body,
            }) => {
                out_token!(self, node, "with");
                self.space(ForceSpace::No);
                out!(self, "(");
                object.visit(ctx, self, Some(Path::new(node, NodeField::object)));
                out!(self, ")");
                self.visit_stmt_or_block(
                    ctx,
                    *body,
                    ForceBlock::No,
                    Path::new(node, NodeField::body),
                );
            }

            Node::SwitchStatement(SwitchStatement {
                metadata: _,
                discriminant,
                cases,
            }) => {
                out_token!(self, node, "switch");
                self.space(ForceSpace::No);
                out!(self, "(");
                discriminant.visit(ctx, self, Some(Path::new(node, NodeField::discriminant)));
                out!(self, ")");
                self.space(ForceSpace::No);
                out!(self, "{{");
                self.newline();
                for case in cases {
                    case.visit(ctx, self, Some(Path::new(node, NodeField::cases)));
                    self.newline();
                }
                out!(self, "}}");
            }
            Node::SwitchCase(SwitchCase {
                metadata: _,
                test,
                consequent,
            }) => {
                match test {
                    Some(test) => {
                        out_token!(self, node, "case ");
                        test.visit(ctx, self, Some(Path::new(node, NodeField::test)));
                    }
                    None => {
                        out_token!(self, node, "default");
                    }
                };
                out!(self, ":");
                if !consequent.is_empty() {
                    self.inc_indent();
                    self.newline();
                    self.visit_stmt_list(ctx, consequent, Path::new(node, NodeField::consequent));
                    self.dec_indent();
                }
            }

            Node::LabeledStatement(LabeledStatement {
                metadata: _,
                label,
                body,
            }) => {
                label.visit(ctx, self, Some(Path::new(node, NodeField::label)));
                out!(self, ":");
                self.newline();
                body.visit(ctx, self, Some(Path::new(node, NodeField::body)));
            }

            Node::ExpressionStatement(ExpressionStatement {
                metadata: _,
                expression,
                directive: _,
            }) => {
                self.print_child(
                    ctx,
                    Some(*expression),
                    Path::new(node, NodeField::expression),
                    ChildPos::Anywhere,
                );
            }

            Node::TryStatement(TryStatement {
                metadata: _,
                block,
                handler,
                finalizer,
            }) => {
                out_token!(self, node, "try");
                self.visit_stmt_or_block(
                    ctx,
                    *block,
                    ForceBlock::Yes,
                    Path::new(node, NodeField::block),
                );
                if let Some(handler) = handler {
                    handler.visit(ctx, self, Some(Path::new(node, NodeField::handler)));
                }
                if let Some(finalizer) = finalizer {
                    out!(self, "finally");
                    self.space(ForceSpace::No);
                    self.visit_stmt_or_block(
                        ctx,
                        *finalizer,
                        ForceBlock::Yes,
                        Path::new(node, NodeField::finalizer),
                    );
                }
            }

            Node::IfStatement(IfStatement {
                metadata: _,
                test,
                consequent,
                alternate,
            }) => {
                out_token!(self, node, "if");
                self.space(ForceSpace::No);
                out!(self, "(");
                test.visit(ctx, self, Some(Path::new(node, NodeField::test)));
                out!(self, ")");
                let force_block = if alternate.is_some() && is_if_without_else(consequent) {
                    ForceBlock::Yes
                } else {
                    ForceBlock::No
                };
                self.visit_stmt_or_block(
                    ctx,
                    *consequent,
                    force_block,
                    Path::new(node, NodeField::consequent),
                );
                if let Some(alternate) = alternate {
                    out!(self, "else");
                    self.space(if matches!(alternate, Node::BlockStatement(_)) {
                        ForceSpace::No
                    } else {
                        ForceSpace::Yes
                    });
                    self.visit_stmt_or_block(
                        ctx,
                        *alternate,
                        ForceBlock::No,
                        Path::new(node, NodeField::alternate),
                    );
                }
            }

            Node::BooleanLiteral(BooleanLiteral { metadata: _, value }) => {
                out_token!(self, node, "{}", if *value { "true" } else { "false" });
            }
            Node::NullLiteral(_) => {
                out_token!(self, node, "null");
            }
            Node::StringLiteral(StringLiteral { metadata: _, value }) => {
                out_token!(self, node, "\"");
                self.print_escaped_string_literal(value, '"');
                out!(self, "\"");
            }
            Node::NumericLiteral(NumericLiteral { metadata: _, value }) => {
                out_token!(self, node, "{}", convert::number_to_string(*value));
            }
            Node::RegExpLiteral(RegExpLiteral {
                metadata: _,
                pattern,
                flags,
            }) => {
                out_token!(self, node, "/");
                // Parser doesn't handle escapes when lexing RegExp,
                // so we don't need to do any manual escaping here.
                self.write_utf8(ctx.str(*pattern));
                out!(self, "/");
                self.write_utf8(ctx.str(*flags));
            }
            Node::ThisExpression(_) => {
                out_token!(self, node, "this");
            }
            Node::Super(_) => {
                out_token!(self, node, "super");
            }

            Node::SequenceExpression(SequenceExpression {
                metadata: _,
                expressions,
            }) => {
                out!(self, "(");
                for (i, expr) in expressions.iter().enumerate() {
                    if i > 0 {
                        self.comma();
                    }
                    self.print_child(
                        ctx,
                        Some(*expr),
                        Path::new(node, NodeField::expressions),
                        if i == 1 {
                            ChildPos::Left
                        } else {
                            ChildPos::Right
                        },
                    );
                }
                out!(self, ")");
            }

            Node::ObjectExpression(ObjectExpression {
                metadata: _,
                properties,
            }) => {
                self.visit_props(ctx, properties, Path::new(node, NodeField::properties));
            }
            Node::ArrayExpression(ArrayExpression {
                metadata: _,
                elements,
                trailing_comma,
            }) => {
                out_token!(self, node, "[");
                for (i, elem) in elements.iter().enumerate() {
                    if i > 0 {
                        self.comma();
                    }
                    if let Node::SpreadElement(_) = elem {
                        elem.visit(ctx, self, Some(Path::new(node, NodeField::elements)));
                    } else {
                        self.print_comma_expression(
                            ctx,
                            *elem,
                            Path::new(node, NodeField::elements),
                        );
                    }
                }
                if *trailing_comma {
                    self.comma();
                }
                out!(self, "]");
            }

            Node::SpreadElement(SpreadElement {
                metadata: _,
                argument,
            }) => {
                out_token!(self, node, "...");
                argument.visit(ctx, self, Some(Path::new(node, NodeField::argument)));
            }

            Node::NewExpression(NewExpression {
                metadata: _,
                callee,
                type_arguments,
                arguments,
            }) => {
                out_token!(self, node, "new ");
                self.print_child(
                    ctx,
                    Some(*callee),
                    Path::new(node, NodeField::callee),
                    ChildPos::Left,
                );
                if let Some(type_arguments) = type_arguments {
                    type_arguments.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_arguments)),
                    );
                }
                out!(self, "(");
                for (i, arg) in arguments.iter().enumerate() {
                    if i > 0 {
                        self.comma();
                    }
                    self.print_comma_expression(ctx, *arg, Path::new(node, NodeField::arguments));
                }
                out!(self, ")");
            }
            Node::YieldExpression(YieldExpression {
                metadata: _,
                argument,
                delegate,
            }) => {
                out_token!(self, node, "yield");
                if *delegate {
                    out!(self, "*");
                    self.space(ForceSpace::No);
                } else if argument.is_some() {
                    out!(self, " ");
                }
                self.print_child(
                    ctx,
                    *argument,
                    Path::new(node, NodeField::argument),
                    ChildPos::Right,
                );
            }
            Node::AwaitExpression(AwaitExpression {
                metadata: _,
                argument,
            }) => {
                out!(self, "await ");
                self.print_child(
                    ctx,
                    Some(*argument),
                    Path::new(node, NodeField::argument),
                    ChildPos::Right,
                );
            }

            Node::ImportExpression(ImportExpression {
                metadata: _,
                source,
                attributes,
            }) => {
                out_token!(self, node, "import(");
                source.visit(ctx, self, Some(Path::new(node, NodeField::source)));
                if let Some(attributes) = attributes {
                    out!(self, ",");
                    self.space(ForceSpace::No);
                    attributes.visit(ctx, self, Some(Path::new(node, NodeField::attributes)));
                }
                out!(self, ")");
            }

            Node::CallExpression(CallExpression {
                metadata: _,
                callee,
                type_arguments,
                arguments,
            }) => {
                self.print_child(
                    ctx,
                    Some(*callee),
                    Path::new(node, NodeField::callee),
                    ChildPos::Left,
                );
                if let Some(type_arguments) = type_arguments {
                    type_arguments.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_arguments)),
                    );
                }
                out!(self, "(");
                for (i, arg) in arguments.iter().enumerate() {
                    if i > 0 {
                        self.comma();
                    }
                    self.print_child(
                        ctx,
                        Some(*arg),
                        Path::new(node, NodeField::arguments),
                        ChildPos::Anywhere,
                    );
                }
                out!(self, ")");
            }
            Node::OptionalCallExpression(OptionalCallExpression {
                metadata: _,
                callee,
                type_arguments,
                arguments,
                optional,
            }) => {
                self.print_child(
                    ctx,
                    Some(*callee),
                    Path::new(node, NodeField::callee),
                    ChildPos::Left,
                );
                if let Some(type_arguments) = type_arguments {
                    type_arguments.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_arguments)),
                    );
                }
                out!(self, "{}(", if *optional { "?." } else { "" });
                for (i, arg) in arguments.iter().enumerate() {
                    if i > 0 {
                        self.comma();
                    }
                    self.print_child(
                        ctx,
                        Some(*arg),
                        Path::new(node, NodeField::arguments),
                        ChildPos::Anywhere,
                    );
                }
                out!(self, ")");
            }

            Node::AssignmentExpression(AssignmentExpression {
                metadata: _,
                operator,
                left,
                right,
            }) => {
                self.print_child(
                    ctx,
                    Some(*left),
                    Path::new(node, NodeField::left),
                    ChildPos::Left,
                );
                self.space(ForceSpace::No);
                out!(self, "{}", operator.as_str());
                self.space(ForceSpace::No);
                self.print_child(
                    ctx,
                    Some(*right),
                    Path::new(node, NodeField::right),
                    ChildPos::Right,
                );
            }
            Node::UnaryExpression(UnaryExpression {
                metadata: _,
                operator,
                argument,
                prefix,
            }) => {
                let ident = operator.as_str().chars().next().unwrap().is_alphabetic();
                if *prefix {
                    out!(self, "{}", operator.as_str());
                    if ident {
                        out!(self, " ");
                    }
                    self.print_child(
                        ctx,
                        Some(*argument),
                        Path::new(node, NodeField::argument),
                        ChildPos::Right,
                    );
                } else {
                    self.print_child(
                        ctx,
                        Some(*argument),
                        Path::new(node, NodeField::argument),
                        ChildPos::Left,
                    );
                    if ident {
                        out!(self, " ");
                    }
                    out!(self, "{}", operator.as_str());
                }
            }
            Node::UpdateExpression(UpdateExpression {
                metadata: _,
                operator,
                argument,
                prefix,
            }) => {
                if *prefix {
                    out!(self, "{}", operator.as_str());
                    self.print_child(
                        ctx,
                        Some(*argument),
                        Path::new(node, NodeField::argument),
                        ChildPos::Right,
                    );
                } else {
                    self.print_child(
                        ctx,
                        Some(*argument),
                        Path::new(node, NodeField::argument),
                        ChildPos::Left,
                    );
                    out!(self, "{}", operator.as_str());
                }
            }
            Node::MemberExpression(MemberExpression {
                metadata: _,
                object,
                property,
                computed,
            }) => {
                match object {
                    Node::NumericLiteral(NumericLiteral { value, .. }) => {
                        // Account for possible `50..toString()`.
                        let string = convert::number_to_string(*value);
                        // If there is an `e` or a decimal point, no need for an extra `.`.
                        let suffix = string.find::<&[char]>(&['E', 'e', '.']).map_or(".", |_| "");
                        out_token!(self, node, "{}{}", string, suffix);
                    }
                    _ => {
                        self.print_child(
                            ctx,
                            Some(*object),
                            Path::new(node, NodeField::object),
                            ChildPos::Left,
                        );
                    }
                }
                if *computed {
                    out!(self, "[");
                } else {
                    out!(self, ".");
                }
                self.print_child(
                    ctx,
                    Some(*property),
                    Path::new(node, NodeField::property),
                    ChildPos::Right,
                );
                if *computed {
                    out!(self, "]");
                }
            }
            Node::OptionalMemberExpression(OptionalMemberExpression {
                metadata: _,
                object,
                property,
                computed,
                optional,
            }) => {
                self.print_child(
                    ctx,
                    Some(*object),
                    Path::new(node, NodeField::object),
                    ChildPos::Left,
                );
                if *computed {
                    out!(self, "{}[", if *optional { "?." } else { "" });
                } else {
                    out!(self, "{}.", if *optional { "?" } else { "" });
                }
                self.print_child(
                    ctx,
                    Some(*property),
                    Path::new(node, NodeField::property),
                    ChildPos::Right,
                );
                if *computed {
                    out!(self, "]");
                }
            }

            Node::BinaryExpression(BinaryExpression {
                metadata: _,
                left,
                right,
                operator,
            }) => {
                let ident = operator.as_str().chars().next().unwrap().is_alphabetic();
                self.print_child(
                    ctx,
                    Some(*left),
                    Path::new(node, NodeField::left),
                    ChildPos::Left,
                );
                self.space(if ident {
                    ForceSpace::Yes
                } else {
                    ForceSpace::No
                });
                out!(self, "{}", operator.as_str());
                self.space(if ident {
                    ForceSpace::Yes
                } else {
                    ForceSpace::No
                });
                self.print_child(
                    ctx,
                    Some(*right),
                    Path::new(node, NodeField::right),
                    ChildPos::Right,
                );
            }

            Node::Directive(Directive { metadata: _, value }) => {
                value.visit(ctx, self, Some(Path::new(node, NodeField::value)));
            }
            Node::DirectiveLiteral(DirectiveLiteral { metadata: _, .. }) => {
                unimplemented!("No escaping for directive literals");
            }

            Node::ConditionalExpression(ConditionalExpression {
                metadata: _,
                test,
                consequent,
                alternate,
            }) => {
                self.print_child(
                    ctx,
                    Some(*test),
                    Path::new(node, NodeField::test),
                    ChildPos::Left,
                );
                self.space(ForceSpace::No);
                out!(self, "?");
                self.space(ForceSpace::No);
                self.print_child(
                    ctx,
                    Some(*consequent),
                    Path::new(node, NodeField::consequent),
                    ChildPos::Anywhere,
                );
                self.space(ForceSpace::No);
                out!(self, ":");
                self.space(ForceSpace::No);
                self.print_child(
                    ctx,
                    Some(*alternate),
                    Path::new(node, NodeField::alternate),
                    ChildPos::Right,
                );
            }

            Node::Identifier(Identifier {
                metadata: _,
                name,
                type_annotation,
                optional,
            }) => {
                self.add_segment(node);
                self.write_utf8(ctx.str(*name).as_ref());
                self.annotate_identifier(ctx, node);
                if *optional {
                    out!(self, "?");
                }
                if let Some(type_annotation) = type_annotation {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    type_annotation.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_annotation)),
                    );
                }
            }
            Node::PrivateName(PrivateName { metadata: _, id }) => {
                out_token!(self, node, "#");
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
            }
            Node::MetaProperty(MetaProperty {
                metadata: _,
                meta,
                property,
            }) => {
                meta.visit(ctx, self, Some(Path::new(node, NodeField::meta)));
                out!(self, ".");
                property.visit(ctx, self, Some(Path::new(node, NodeField::property)));
            }

            Node::CatchClause(CatchClause {
                metadata: _,
                param,
                body,
            }) => {
                self.space(ForceSpace::No);
                out_token!(self, node, "catch");
                if let Some(param) = param {
                    self.space(ForceSpace::No);
                    out!(self, "(");
                    param.visit(ctx, self, Some(Path::new(node, NodeField::param)));
                    out!(self, ")");
                }
                self.visit_stmt_or_block(
                    ctx,
                    *body,
                    ForceBlock::Yes,
                    Path::new(node, NodeField::body),
                );
            }

            Node::VariableDeclaration(VariableDeclaration {
                metadata: _,
                kind,
                declarations,
            }) => {
                out_token!(self, node, "{} ", kind.as_str());
                for (i, decl) in declarations.iter().enumerate() {
                    if i > 0 {
                        self.comma();
                    }
                    decl.visit(ctx, self, Some(Path::new(node, NodeField::declarations)));
                }
            }
            Node::VariableDeclarator(VariableDeclarator {
                metadata: _,
                init,
                id,
            }) => {
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                if let Some(init) = init {
                    out!(
                        self,
                        "{}",
                        match self.pretty {
                            Pretty::Yes => " = ",
                            Pretty::No => "=",
                        }
                    );
                    init.visit(ctx, self, Some(Path::new(node, NodeField::init)));
                }
            }

            Node::TemplateLiteral(TemplateLiteral {
                metadata: _,
                quasis,
                expressions,
            }) => {
                out_token!(self, node, "`");
                let mut it_expr = expressions.iter();
                for quasi in quasis {
                    if let Node::TemplateElement(TemplateElement {
                        metadata: _,
                        raw,
                        tail: _,
                        cooked: _,
                    }) = quasi
                    {
                        let mut buf = [0u8; 4];
                        for char in ctx.str(*raw).chars() {
                            if char == '\n' {
                                self.force_newline_without_indent();
                                continue;
                            }
                            self.write_char(char, &mut buf);
                        }
                        if let Some(expr) = it_expr.next() {
                            out!(self, "${{");
                            expr.visit(ctx, self, Some(Path::new(node, NodeField::expressions)));
                            out!(self, "}}");
                        }
                    }
                }
                out!(self, "`");
            }
            Node::TaggedTemplateExpression(TaggedTemplateExpression {
                metadata: _,
                tag,
                quasi,
            }) => {
                self.print_child(
                    ctx,
                    Some(*tag),
                    Path::new(node, NodeField::tag),
                    ChildPos::Left,
                );
                self.print_child(
                    ctx,
                    Some(*quasi),
                    Path::new(node, NodeField::quasi),
                    ChildPos::Right,
                );
            }
            Node::TemplateElement(_) => {
                unreachable!("TemplateElement is handled in TemplateLiteral case");
            }

            Node::Property(Property {
                metadata: _,
                key,
                value,
                kind,
                computed,
                method,
                shorthand,
            }) => {
                let mut need_sep = false;
                if *kind != PropertyKind::Init {
                    out_token!(self, node, "{}", kind.as_str());
                    need_sep = true;
                } else if *method {
                    match value {
                        Node::FunctionExpression(FunctionExpression {
                            metadata: _,
                            generator,
                            is_async,
                            ..
                        }) => {
                            if *is_async {
                                out!(self, "async");
                                need_sep = true;
                            }
                            if *generator {
                                out!(self, "*");
                                need_sep = false;
                                self.space(ForceSpace::No);
                            }
                        }
                        _ => unreachable!(),
                    };
                }
                if *computed {
                    if need_sep {
                        self.space(ForceSpace::No);
                    }
                    need_sep = false;
                    out!(self, "[");
                }
                if need_sep {
                    out!(self, " ");
                }
                if *shorthand {
                    value.visit(ctx, self, None);
                } else {
                    key.visit(ctx, self, None);
                }
                if *computed {
                    out!(self, "]");
                }
                if *shorthand {
                    return;
                }
                if *kind != PropertyKind::Init || *method {
                    match value {
                        Node::FunctionExpression(FunctionExpression {
                            metadata: _,
                            // Name is handled by the property key.
                            id: _,
                            params,
                            body,
                            return_type,
                            predicate,
                            type_parameters,
                            // Handled above.
                            generator: _,
                            is_async: _,
                        }) => {
                            self.visit_func_params_body(
                                ctx,
                                params,
                                *type_parameters,
                                *return_type,
                                *predicate,
                                *body,
                                *value,
                            );
                        }
                        _ => unreachable!(),
                    };
                } else {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    value.visit(ctx, self, Some(Path::new(node, NodeField::value)));
                }
            }

            Node::LogicalExpression(LogicalExpression {
                metadata: _,
                left,
                right,
                operator,
            }) => {
                self.print_child(
                    ctx,
                    Some(*left),
                    Path::new(node, NodeField::left),
                    ChildPos::Left,
                );
                self.space(ForceSpace::No);
                out!(self, "{}", operator.as_str());
                self.space(ForceSpace::No);
                self.print_child(
                    ctx,
                    Some(*right),
                    Path::new(node, NodeField::right),
                    ChildPos::Right,
                );
            }

            Node::ClassExpression(ClassExpression {
                metadata: _,
                id,
                type_parameters,
                super_class,
                super_type_parameters,
                implements,
                decorators,
                body,
            })
            | Node::ClassDeclaration(ClassDeclaration {
                metadata: _,
                id,
                type_parameters,
                super_class,
                super_type_parameters,
                implements,
                decorators,
                body,
            }) => {
                if !decorators.is_empty() {
                    for decorator in decorators {
                        decorator.visit(ctx, self, Some(Path::new(node, NodeField::decorators)));
                        self.force_newline();
                    }
                    out!(self, "class");
                } else {
                    out_token!(self, node, "class");
                }
                if let Some(id) = id {
                    self.space(ForceSpace::Yes);
                    id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                }
                if let Some(type_parameters) = type_parameters {
                    type_parameters.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_parameters)),
                    );
                }
                if let Some(super_class) = super_class {
                    out!(self, " extends ");
                    super_class.visit(ctx, self, Some(Path::new(node, NodeField::super_class)));
                }
                if let Some(super_type_parameters) = super_type_parameters {
                    super_type_parameters.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::super_type_parameters)),
                    );
                }
                if !implements.is_empty() {
                    out!(self, " implements ");
                    for (i, implement) in implements.iter().enumerate() {
                        if i > 0 {
                            self.comma();
                        }
                        implement.visit(ctx, self, Some(Path::new(node, NodeField::implements)));
                    }
                }

                self.space(ForceSpace::No);
                body.visit(ctx, self, Some(Path::new(node, NodeField::body)));
            }

            Node::ClassBody(ClassBody { metadata: _, body }) => {
                if body.is_empty() {
                    out!(self, "{{}}");
                } else {
                    out!(self, "{{");
                    self.inc_indent();
                    self.newline();
                    for prop in body {
                        prop.visit(ctx, self, Some(Path::new(node, NodeField::body)));
                        self.newline();
                    }
                    out!(self, "}}");
                    self.dec_indent();
                    self.newline();
                }
            }
            Node::ClassProperty(ClassProperty {
                metadata: _,
                key,
                value,
                computed,
                is_static,
                declare,
                optional,
                variance,
                type_annotation,
            }) => {
                if let Some(variance) = variance {
                    variance.visit(ctx, self, Some(Path::new(node, NodeField::variance)));
                }
                if *is_static {
                    out!(self, "static ");
                }
                if *declare {
                    out!(self, "declare ");
                }
                if *computed {
                    out!(self, "[");
                }
                key.visit(ctx, self, Some(Path::new(node, NodeField::key)));
                if *computed {
                    out!(self, "]");
                }
                if *optional {
                    out!(self, "?");
                }
                if let Some(type_annotation) = type_annotation {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    type_annotation.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_annotation)),
                    );
                }
                if let Some(value) = value {
                    self.space(ForceSpace::No);
                    out!(self, "=");
                    self.space(ForceSpace::No);
                    value.visit(ctx, self, Some(Path::new(node, NodeField::value)));
                }
                out!(self, ";");
            }
            Node::ClassPrivateProperty(ClassPrivateProperty {
                metadata: _,
                key,
                value,
                is_static,
                declare,
                optional,
                variance,
                type_annotation,
            }) => {
                if let Some(variance) = variance {
                    variance.visit(ctx, self, Some(Path::new(node, NodeField::variance)));
                }
                if *is_static {
                    out!(self, "static ");
                }
                if *declare {
                    out!(self, "static ");
                }
                out!(self, "#");
                key.visit(ctx, self, Some(Path::new(node, NodeField::key)));
                if *optional {
                    out!(self, "?");
                }
                if let Some(type_annotation) = type_annotation {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    type_annotation.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_annotation)),
                    );
                }
                self.space(ForceSpace::No);
                if let Some(value) = value {
                    out!(self, "=");
                    self.space(ForceSpace::No);
                    value.visit(ctx, self, Some(Path::new(node, NodeField::value)));
                }
                out!(self, ";");
            }
            Node::MethodDefinition(MethodDefinition {
                metadata: _,
                key,
                value,
                kind,
                computed,
                is_static,
            }) => {
                let (is_async, generator, params, body, return_type, predicate, type_parameters) =
                    match value {
                        Node::FunctionExpression(FunctionExpression {
                            metadata: _,
                            id: _,
                            generator,
                            is_async,
                            params,
                            body,
                            return_type,
                            predicate,
                            type_parameters,
                        }) => (
                            *is_async,
                            *generator,
                            params,
                            body,
                            return_type,
                            predicate,
                            type_parameters,
                        ),
                        _ => {
                            unreachable!("Invalid method value");
                        }
                    };
                if *is_static {
                    out!(self, "static ");
                }
                if is_async {
                    out!(self, "async ");
                }
                if generator {
                    out!(self, "*");
                }
                match *kind {
                    MethodDefinitionKind::Method => {}
                    MethodDefinitionKind::Constructor => {
                        // Will be handled by key output.
                    }
                    MethodDefinitionKind::Get => {
                        out!(self, "get ");
                    }
                    MethodDefinitionKind::Set => {
                        out!(self, "set ");
                    }
                };
                if *computed {
                    out!(self, "[");
                }
                key.visit(ctx, self, Some(Path::new(node, NodeField::key)));
                if *computed {
                    out!(self, "]");
                }
                self.visit_func_params_body(
                    ctx,
                    params,
                    *type_parameters,
                    *return_type,
                    *predicate,
                    *body,
                    node,
                );
            }

            Node::ImportDeclaration(ImportDeclaration {
                metadata: _,
                specifiers,
                source,
                assertions,
                import_kind,
            }) => {
                out_token!(self, node, "import ");
                if *import_kind != ImportKind::Value {
                    out!(self, "{} ", import_kind.as_str());
                }
                let mut has_named_specs = false;
                for (i, spec) in specifiers.iter().enumerate() {
                    if i > 0 {
                        self.comma();
                    }
                    if let Node::ImportSpecifier(_) = spec {
                        if !has_named_specs {
                            has_named_specs = true;
                            out!(self, "{{");
                        }
                    }
                    spec.visit(ctx, self, Some(Path::new(node, NodeField::specifiers)));
                }
                if !specifiers.is_empty() {
                    if has_named_specs {
                        out!(self, "}}");
                        self.space(ForceSpace::No);
                    } else {
                        out!(self, " ");
                    }
                    out!(self, "from ");
                }
                source.visit(ctx, self, Some(Path::new(node, NodeField::source)));
                if let Some(assertions) = assertions {
                    if !assertions.is_empty() {
                        out!(self, " assert {{");
                        for (i, attribute) in assertions.iter().enumerate() {
                            if i > 0 {
                                self.comma();
                            }
                            attribute.visit(
                                ctx,
                                self,
                                Some(Path::new(node, NodeField::assertions)),
                            );
                        }
                        out!(self, "}}");
                    }
                }
            }
            Node::ImportSpecifier(ImportSpecifier {
                metadata: _,
                imported,
                local,
                import_kind,
            }) => {
                if *import_kind != ImportKind::Value {
                    out!(self, "{} ", import_kind.as_str());
                }
                imported.visit(ctx, self, Some(Path::new(node, NodeField::imported)));
                out!(self, " as ");
                local.visit(ctx, self, Some(Path::new(node, NodeField::local)));
            }
            Node::ImportDefaultSpecifier(ImportDefaultSpecifier { metadata: _, local }) => {
                local.visit(ctx, self, Some(Path::new(node, NodeField::local)));
            }
            Node::ImportNamespaceSpecifier(ImportNamespaceSpecifier { metadata: _, local }) => {
                out!(self, "* as ");
                local.visit(ctx, self, Some(Path::new(node, NodeField::local)));
            }
            Node::ImportAttribute(ImportAttribute {
                metadata: _,
                key,
                value,
            }) => {
                key.visit(ctx, self, Some(Path::new(node, NodeField::key)));
                out!(self, ":");
                self.space(ForceSpace::No);
                value.visit(ctx, self, Some(Path::new(node, NodeField::value)));
            }

            Node::ExportNamedDeclaration(ExportNamedDeclaration {
                metadata: _,
                declaration,
                specifiers,
                source,
                export_kind,
            }) => {
                out_token!(self, node, "export ");
                if let Some(declaration) = declaration {
                    declaration.visit(ctx, self, Some(Path::new(node, NodeField::declaration)));
                } else {
                    if *export_kind != ExportKind::Value {
                        out!(self, "{} ", export_kind.as_str());
                    }
                    out!(self, "{{");
                    for (i, spec) in specifiers.iter().enumerate() {
                        if i > 0 {
                            self.comma();
                        }
                        spec.visit(ctx, self, Some(Path::new(node, NodeField::specifiers)));
                    }
                    out!(self, "}}");
                    if let Some(source) = source {
                        out!(self, " from ");
                        source.visit(ctx, self, Some(Path::new(node, NodeField::source)));
                    }
                }
            }
            Node::ExportSpecifier(ExportSpecifier {
                metadata: _,
                exported,
                local,
            }) => {
                local.visit(ctx, self, Some(Path::new(node, NodeField::local)));
                out!(self, " as ");
                exported.visit(ctx, self, Some(Path::new(node, NodeField::exported)));
            }
            Node::ExportNamespaceSpecifier(ExportNamespaceSpecifier {
                metadata: _,
                exported,
            }) => {
                out!(self, "* as ");
                exported.visit(ctx, self, Some(Path::new(node, NodeField::exported)));
            }
            Node::ExportDefaultDeclaration(ExportDefaultDeclaration {
                metadata: _,
                declaration,
            }) => {
                out_token!(self, node, "export default ");
                declaration.visit(ctx, self, Some(Path::new(node, NodeField::declaration)));
            }
            Node::ExportAllDeclaration(ExportAllDeclaration {
                metadata: _,
                source,
                export_kind,
            }) => {
                out_token!(self, node, "export ");
                if *export_kind != ExportKind::Value {
                    out!(self, "{} ", export_kind.as_str());
                }
                out!(self, "* from ");
                source.visit(ctx, self, Some(Path::new(node, NodeField::source)));
            }

            Node::ObjectPattern(ObjectPattern {
                metadata: _,
                properties,
                type_annotation,
            }) => {
                self.visit_props(ctx, properties, Path::new(node, NodeField::properties));
                if let Some(type_annotation) = type_annotation {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    type_annotation.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_annotation)),
                    );
                }
            }
            Node::ArrayPattern(ArrayPattern {
                metadata: _,
                elements,
                type_annotation,
            }) => {
                out!(self, "[");
                for (i, elem) in elements.iter().enumerate() {
                    if i > 0 {
                        self.comma();
                    }
                    elem.visit(ctx, self, Some(Path::new(node, NodeField::elements)));
                }
                out!(self, "]");
                if let Some(type_annotation) = type_annotation {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    type_annotation.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_annotation)),
                    );
                }
            }
            Node::RestElement(RestElement {
                metadata: _,
                argument,
            }) => {
                out!(self, "...");
                argument.visit(ctx, self, Some(Path::new(node, NodeField::argument)));
            }
            Node::AssignmentPattern(AssignmentPattern {
                metadata: _,
                left,
                right,
            }) => {
                left.visit(ctx, self, Some(Path::new(node, NodeField::left)));
                self.space(ForceSpace::No);
                out!(self, "=");
                self.space(ForceSpace::No);
                right.visit(ctx, self, Some(Path::new(node, NodeField::right)));
            }

            Node::JSXIdentifier(JSXIdentifier { metadata: _, name }) => {
                out_token!(self, node, "{}", ctx.str(*name));
            }
            Node::JSXMemberExpression(JSXMemberExpression {
                metadata: _,
                object,
                property,
            }) => {
                object.visit(ctx, self, Some(Path::new(node, NodeField::object)));
                out!(self, ".");
                property.visit(ctx, self, Some(Path::new(node, NodeField::property)));
            }
            Node::JSXNamespacedName(JSXNamespacedName {
                metadata: _,
                namespace,
                name,
            }) => {
                namespace.visit(ctx, self, Some(Path::new(node, NodeField::namespace)));
                out!(self, ":");
                name.visit(ctx, self, Some(Path::new(node, NodeField::name)));
            }
            Node::JSXEmptyExpression(_) => {}
            Node::JSXExpressionContainer(JSXExpressionContainer {
                metadata: _,
                expression,
            }) => {
                out!(self, "{{");
                expression.visit(ctx, self, Some(Path::new(node, NodeField::expression)));
                out!(self, "}}");
            }
            Node::JSXSpreadChild(JSXSpreadChild {
                metadata: _,
                expression,
            }) => {
                out!(self, "{{...");
                expression.visit(ctx, self, Some(Path::new(node, NodeField::expression)));
                out!(self, "}}");
            }
            Node::JSXOpeningElement(JSXOpeningElement {
                metadata: _,
                name,
                attributes,
                self_closing,
            }) => {
                out!(self, "<");
                name.visit(ctx, self, Some(Path::new(node, NodeField::name)));
                for attr in attributes {
                    self.space(ForceSpace::Yes);
                    attr.visit(ctx, self, Some(Path::new(node, NodeField::attributes)));
                }
                if *self_closing {
                    out!(self, " />");
                } else {
                    out!(self, ">");
                }
            }
            Node::JSXClosingElement(JSXClosingElement { metadata: _, name }) => {
                out!(self, "</");
                name.visit(ctx, self, Some(Path::new(node, NodeField::name)));
                out!(self, ">");
            }
            Node::JSXAttribute(JSXAttribute {
                metadata: _,
                name,
                value,
            }) => {
                name.visit(ctx, self, Some(Path::new(node, NodeField::name)));
                if let Some(value) = value {
                    out!(self, "=");
                    value.visit(ctx, self, Some(Path::new(node, NodeField::value)));
                }
            }
            Node::JSXSpreadAttribute(JSXSpreadAttribute {
                metadata: _,
                argument,
            }) => {
                out!(self, "{{...");
                argument.visit(ctx, self, Some(Path::new(node, NodeField::argument)));
                out!(self, "}}");
            }
            Node::JSXStringLiteral(JSXStringLiteral {
                metadata: _,
                value: _,
                raw,
            }) => {
                let mut buf = [0u8; 4];
                for char in ctx.str(*raw).chars() {
                    if char == '\n' {
                        self.force_newline_without_indent();
                        continue;
                    }
                    self.write_char(char, &mut buf);
                }
            }
            Node::JSXText(JSXText {
                metadata: _,
                value: _,
                raw,
            }) => {
                let mut buf = [0u8; 4];
                for char in ctx.str(*raw).chars() {
                    if char == '\n' {
                        self.force_newline_without_indent();
                        continue;
                    }
                    self.write_char(char, &mut buf);
                }
            }
            Node::JSXElement(JSXElement {
                metadata: _,
                opening_element,
                children,
                closing_element,
            }) => {
                opening_element.visit(ctx, self, Some(Path::new(node, NodeField::opening_element)));
                if let Some(closing_element) = closing_element {
                    for child in children {
                        child.visit(ctx, self, Some(Path::new(node, NodeField::children)));
                    }
                    closing_element.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::closing_element)),
                    );
                }
            }
            Node::JSXFragment(JSXFragment {
                metadata: _,
                opening_fragment,
                children,
                closing_fragment,
            }) => {
                opening_fragment.visit(
                    ctx,
                    self,
                    Some(Path::new(node, NodeField::opening_fragment)),
                );
                for child in children {
                    child.visit(ctx, self, Some(Path::new(node, NodeField::children)));
                }
                closing_fragment.visit(
                    ctx,
                    self,
                    Some(Path::new(node, NodeField::closing_fragment)),
                );
            }
            Node::JSXOpeningFragment(_) => {
                out_token!(self, node, "<>");
            }
            Node::JSXClosingFragment(_) => {
                out_token!(self, node, "</>");
            }

            Node::ExistsTypeAnnotation(_) => {
                out_token!(self, node, "*");
            }
            Node::EmptyTypeAnnotation(_) => {
                out_token!(self, node, "empty");
            }
            Node::StringTypeAnnotation(_) => {
                out_token!(self, node, "string");
            }
            Node::NumberTypeAnnotation(_) => {
                out_token!(self, node, "number");
            }
            Node::StringLiteralTypeAnnotation(StringLiteralTypeAnnotation {
                metadata: _,
                value,
                raw,
            }) => {
                let quote = raw.str[0] as u8 as char;
                out_token!(self, node, "{}", quote);
                self.print_escaped_string_literal(value, quote);
                out!(self, "{}", quote);
            }
            Node::NumberLiteralTypeAnnotation(NumberLiteralTypeAnnotation {
                metadata: _,
                value,
                ..
            }) => {
                out_token!(self, node, "{}", convert::number_to_string(*value));
            }
            Node::BooleanTypeAnnotation(_) => {
                out_token!(self, node, "boolean");
            }
            Node::BooleanLiteralTypeAnnotation(BooleanLiteralTypeAnnotation {
                metadata: _,
                value,
                ..
            }) => {
                out_token!(self, node, "{}", if *value { "true" } else { "false" });
            }
            Node::NullLiteralTypeAnnotation(_) => {
                out_token!(self, node, "null");
            }
            Node::SymbolTypeAnnotation(_) => {
                out_token!(self, node, "symbol");
            }
            Node::AnyTypeAnnotation(_) => {
                out_token!(self, node, "any");
            }
            Node::MixedTypeAnnotation(_) => {
                out_token!(self, node, "mixed");
            }
            Node::VoidTypeAnnotation(_) => {
                out_token!(self, node, "void");
            }
            Node::FunctionTypeAnnotation(FunctionTypeAnnotation {
                metadata: _,
                params,
                this,
                return_type,
                rest,
                type_parameters,
            }) => {
                if let Some(type_parameters) = type_parameters {
                    type_parameters.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_parameters)),
                    );
                }
                out!(self, "(");
                let mut need_comma = false;
                if let Some(this) = this {
                    match this {
                        Node::FunctionTypeParam(FunctionTypeParam {
                            metadata: _,
                            type_annotation,
                            ..
                        }) => {
                            out!(self, "this:");
                            self.space(ForceSpace::No);
                            type_annotation.visit(
                                ctx,
                                self,
                                Some(Path::new(node, NodeField::type_annotation)),
                            );
                        }
                        _ => {
                            unimplemented!("Malformed AST: Need to handle error");
                        }
                    }
                    this.visit(ctx, self, Some(Path::new(node, NodeField::this)));
                    need_comma = true;
                }
                for param in params.iter() {
                    if need_comma {
                        self.comma();
                    }
                    param.visit(ctx, self, Some(Path::new(node, NodeField::param)));
                    need_comma = true;
                }
                if let Some(rest) = rest {
                    if need_comma {
                        self.comma();
                    }
                    out!(self, "...");
                    rest.visit(ctx, self, Some(Path::new(node, NodeField::rest)));
                }
                out!(self, ")");
                if self.pretty == Pretty::Yes {
                    out!(self, " => ");
                } else {
                    out!(self, "=>");
                }
                return_type.visit(ctx, self, Some(Path::new(node, NodeField::return_type)));
            }
            Node::FunctionTypeParam(FunctionTypeParam {
                metadata: _,
                name,
                type_annotation,
                optional,
            }) => {
                if let Some(name) = name {
                    name.visit(ctx, self, Some(Path::new(node, NodeField::name)));
                    if *optional {
                        out!(self, "?");
                    }
                    out!(self, ":");
                    self.space(ForceSpace::No);
                }
                type_annotation.visit(ctx, self, Some(Path::new(node, NodeField::type_annotation)));
            }
            Node::NullableTypeAnnotation(NullableTypeAnnotation {
                metadata: _,
                type_annotation,
            }) => {
                out!(self, "?");
                self.print_child(
                    ctx,
                    Some(type_annotation),
                    Path::new(node, NodeField::type_annotation),
                    ChildPos::Right,
                );
            }
            Node::QualifiedTypeIdentifier(QualifiedTypeIdentifier {
                metadata: _,
                qualification,
                id,
            }) => {
                qualification.visit(ctx, self, Some(Path::new(node, NodeField::qualification)));
                out!(self, ".");
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
            }
            Node::TypeofTypeAnnotation(TypeofTypeAnnotation {
                metadata: _,
                argument,
            }) => {
                out!(self, "typeof ");
                argument.visit(ctx, self, Some(Path::new(node, NodeField::argument)));
            }
            Node::TupleTypeAnnotation(TupleTypeAnnotation { metadata: _, types }) => {
                out!(self, "[");
                for (i, ty) in types.iter().enumerate() {
                    if i > 0 {
                        self.comma();
                    }
                    ty.visit(ctx, self, Some(Path::new(node, NodeField::types)));
                }
                out!(self, "]");
            }
            Node::ArrayTypeAnnotation(ArrayTypeAnnotation {
                metadata: _,
                element_type,
            }) => {
                element_type.visit(ctx, self, Some(Path::new(node, NodeField::element_type)));
                out!(self, "[]");
            }
            Node::UnionTypeAnnotation(UnionTypeAnnotation { metadata: _, types }) => {
                for (i, ty) in types.iter().enumerate() {
                    if i > 0 {
                        self.space(ForceSpace::No);
                        out!(self, "|");
                        self.space(ForceSpace::No);
                    }
                    self.print_child(
                        ctx,
                        Some(*ty),
                        Path::new(node, NodeField::types),
                        ChildPos::Anywhere,
                    );
                }
            }
            Node::IntersectionTypeAnnotation(IntersectionTypeAnnotation { metadata: _, types }) => {
                for (i, ty) in types.iter().enumerate() {
                    if i > 0 {
                        self.space(ForceSpace::No);
                        out!(self, "&");
                        self.space(ForceSpace::No);
                    }
                    self.print_child(
                        ctx,
                        Some(*ty),
                        Path::new(node, NodeField::types),
                        ChildPos::Anywhere,
                    );
                }
            }
            Node::GenericTypeAnnotation(GenericTypeAnnotation {
                metadata: _,
                id,
                type_parameters,
            }) => {
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                if let Some(type_parameters) = type_parameters {
                    type_parameters.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_parameters)),
                    );
                }
            }
            Node::IndexedAccessType(IndexedAccessType {
                metadata: _,
                object_type,
                index_type,
            }) => {
                object_type.visit(ctx, self, Some(Path::new(node, NodeField::object_type)));
                out!(self, "[");
                index_type.visit(ctx, self, Some(Path::new(node, NodeField::index_type)));
                out!(self, "]");
            }
            Node::OptionalIndexedAccessType(OptionalIndexedAccessType {
                metadata: _,
                object_type,
                index_type,
                optional,
            }) => {
                object_type.visit(ctx, self, Some(Path::new(node, NodeField::object_type)));
                out!(self, "{}[", if *optional { "?." } else { "" });
                index_type.visit(ctx, self, Some(Path::new(node, NodeField::index_type)));
                out!(self, "]");
            }
            Node::InterfaceTypeAnnotation(InterfaceTypeAnnotation {
                metadata: _,
                extends,
                body,
            }) => {
                out!(self, "interface");
                if !extends.is_empty() {
                    out!(self, " extends ");
                    for (i, extend) in extends.iter().enumerate() {
                        if i > 0 {
                            self.comma();
                        }
                        extend.visit(ctx, self, Some(Path::new(node, NodeField::extends)));
                    }
                } else {
                    self.space(ForceSpace::No);
                }
                if let Some(body) = body {
                    body.visit(ctx, self, Some(Path::new(node, NodeField::body)));
                }
            }

            Node::TypeAlias(TypeAlias {
                metadata: _,
                id,
                type_parameters,
                right,
            })
            | Node::DeclareTypeAlias(DeclareTypeAlias {
                metadata: _,
                id,
                type_parameters,
                right,
            }) => {
                if matches!(&node, Node::DeclareTypeAlias(_)) {
                    out_token!(self, node, "declare type");
                } else {
                    out_token!(self, node, "type ");
                }
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                if let Some(type_parameters) = type_parameters {
                    type_parameters.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_parameters)),
                    );
                }
                if self.pretty == Pretty::Yes {
                    out!(self, " = ");
                } else {
                    out!(self, "=");
                }
                right.visit(ctx, self, Some(Path::new(node, NodeField::right)));
            }
            Node::OpaqueType(OpaqueType {
                metadata: _,
                id,
                type_parameters,
                impltype,
                supertype,
            }) => {
                out_token!(self, node, "opaque type ");
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                if let Some(type_parameters) = type_parameters {
                    type_parameters.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_parameters)),
                    );
                }
                if let Some(supertype) = supertype {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    supertype.visit(ctx, self, Some(Path::new(node, NodeField::supertype)));
                }
                if self.pretty == Pretty::Yes {
                    out!(self, " = ");
                } else {
                    out!(self, "=");
                }
                impltype.visit(ctx, self, Some(Path::new(node, NodeField::impltype)));
            }
            Node::InterfaceDeclaration(InterfaceDeclaration {
                metadata: _,
                id,
                type_parameters,
                extends,
                body,
            })
            | Node::DeclareInterface(DeclareInterface {
                metadata: _,
                id,
                type_parameters,
                extends,
                body,
            }) => {
                self.visit_interface(
                    ctx,
                    if matches!(node, Node::InterfaceDeclaration(_)) {
                        "interface"
                    } else {
                        "declare interface"
                    },
                    *id,
                    *type_parameters,
                    extends,
                    *body,
                    node,
                );
            }
            Node::DeclareOpaqueType(DeclareOpaqueType {
                metadata: _,
                id,
                type_parameters,
                impltype,
                supertype,
            }) => {
                out_token!(self, node, "opaque type ");
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                if let Some(type_parameters) = type_parameters {
                    type_parameters.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_parameters)),
                    );
                }
                if let Some(supertype) = supertype {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    supertype.visit(ctx, self, Some(Path::new(node, NodeField::supertype)));
                }
                if self.pretty == Pretty::Yes {
                    out!(self, " = ");
                } else {
                    out!(self, "=");
                }
                if let Some(impltype) = impltype {
                    impltype.visit(ctx, self, Some(Path::new(node, NodeField::impltype)));
                }
            }
            Node::DeclareClass(DeclareClass {
                metadata: _,
                id,
                type_parameters,
                extends,
                implements,
                mixins,
                body,
            }) => {
                out_token!(self, node, "declare class ");
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                if let Some(type_parameters) = type_parameters {
                    type_parameters.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_parameters)),
                    );
                }
                if !extends.is_empty() {
                    out!(self, " extends ");
                    for (i, extend) in extends.iter().enumerate() {
                        if i > 0 {
                            self.comma();
                        }
                        extend.visit(ctx, self, Some(Path::new(node, NodeField::extends)));
                    }
                }
                if !mixins.is_empty() {
                    out!(self, " mixins ");
                    for (i, mixin) in mixins.iter().enumerate() {
                        if i > 0 {
                            self.comma();
                        }
                        mixin.visit(ctx, self, Some(Path::new(node, NodeField::mixins)));
                    }
                }
                if !implements.is_empty() {
                    out!(self, " implements ");
                    for (i, implement) in implements.iter().enumerate() {
                        if i > 0 {
                            self.comma();
                        }
                        implement.visit(ctx, self, Some(Path::new(node, NodeField::implements)));
                    }
                }
                self.space(ForceSpace::No);
                body.visit(ctx, self, Some(Path::new(node, NodeField::body)));
            }
            Node::DeclareFunction(DeclareFunction {
                metadata: _,
                id,
                predicate,
            }) => {
                // This AST type uses the Identifier/TypeAnnotation
                // pairing to put a name on a function header-looking construct,
                // so we have to do some deep matching to get it to come out right.
                out_token!(self, node, "declare function ");
                match id {
                    Node::Identifier(Identifier {
                        metadata: _,
                        name,
                        type_annotation,
                        ..
                    }) => {
                        out!(self, "{}", &ctx.str(*name));
                        match type_annotation {
                            Some(Node::TypeAnnotation(TypeAnnotation {
                                metadata: _,
                                type_annotation:
                                    Node::FunctionTypeAnnotation(FunctionTypeAnnotation {
                                        metadata: _,
                                        params,
                                        this,
                                        return_type,
                                        rest,
                                        type_parameters,
                                    }),
                            })) => {
                                self.visit_func_type_params(
                                    ctx,
                                    params,
                                    *this,
                                    *rest,
                                    *type_parameters,
                                    node,
                                );
                                out!(self, ":");
                                self.space(ForceSpace::No);
                                return_type.visit(
                                    ctx,
                                    self,
                                    Some(Path::new(node, NodeField::return_type)),
                                );
                            }
                            _ => {
                                unimplemented!("Malformed AST: Need to handle error");
                            }
                        }
                        if let Some(predicate) = predicate {
                            self.space(ForceSpace::No);
                            predicate.visit(ctx, self, Some(Path::new(node, NodeField::predicate)));
                        }
                    }
                    _ => {
                        unimplemented!("Malformed AST: Need to handle error");
                    }
                }
            }
            Node::DeclareVariable(DeclareVariable { metadata: _, id }) => {
                if let Some(path) = path {
                    if !matches!(path.parent, Node::DeclareExportDeclaration(_)) {
                        out!(self, "declare ");
                    }
                }
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
            }
            Node::DeclareExportDeclaration(DeclareExportDeclaration {
                metadata: _,
                declaration,
                specifiers,
                source,
                default,
            }) => {
                out_token!(self, node, "declare export ");
                if *default {
                    out!(self, "default ");
                }
                if let Some(declaration) = declaration {
                    declaration.visit(ctx, self, Some(Path::new(node, NodeField::declaration)));
                } else {
                    out!(self, "{{");
                    for (i, spec) in specifiers.iter().enumerate() {
                        if i > 0 {
                            self.comma();
                        }
                        spec.visit(ctx, self, Some(Path::new(node, NodeField::specifiers)));
                    }
                    out!(self, "}}");
                    if let Some(source) = source {
                        out!(self, " from ");
                        source.visit(ctx, self, Some(Path::new(node, NodeField::source)));
                    }
                }
            }
            Node::DeclareExportAllDeclaration(DeclareExportAllDeclaration {
                metadata: _,
                source,
            }) => {
                out_token!(self, node, "declare export * from ");
                source.visit(ctx, self, Some(Path::new(node, NodeField::source)));
            }
            Node::DeclareModule(DeclareModule {
                metadata: _,
                id,
                body,
                ..
            }) => {
                out!(self, "declare module ");
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                self.space(ForceSpace::No);
                body.visit(ctx, self, Some(Path::new(node, NodeField::body)));
            }
            Node::DeclareModuleExports(DeclareModuleExports {
                metadata: _,
                type_annotation,
            }) => {
                out!(self, "declare module.exports:");
                self.space(ForceSpace::No);
                type_annotation.visit(ctx, self, Some(Path::new(node, NodeField::type_annotation)));
            }

            Node::InterfaceExtends(InterfaceExtends {
                metadata: _,
                id,
                type_parameters,
            })
            | Node::ClassImplements(ClassImplements {
                metadata: _,
                id,
                type_parameters,
            }) => {
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                if let Some(type_parameters) = type_parameters {
                    type_parameters.visit(
                        ctx,
                        self,
                        Some(Path::new(node, NodeField::type_parameters)),
                    );
                }
            }

            Node::TypeAnnotation(TypeAnnotation {
                metadata: _,
                type_annotation,
            }) => {
                type_annotation.visit(ctx, self, Some(Path::new(node, NodeField::type_annotation)));
            }
            Node::ObjectTypeAnnotation(ObjectTypeAnnotation {
                metadata: _,
                properties,
                indexers,
                call_properties,
                internal_slots,
                inexact,
                exact,
            }) => {
                out!(self, "{}", if *exact { "{|" } else { "{" });
                self.inc_indent();
                self.newline();

                let mut need_comma = false;

                for prop in properties {
                    if need_comma {
                        self.comma();
                    }
                    prop.visit(ctx, self, Some(Path::new(node, NodeField::properties)));
                    self.newline();
                    need_comma = true;
                }
                for prop in indexers {
                    if need_comma {
                        self.comma();
                    }
                    prop.visit(ctx, self, Some(Path::new(node, NodeField::indexers)));
                    self.newline();
                    need_comma = true;
                }
                for prop in call_properties {
                    if need_comma {
                        self.comma();
                    }
                    prop.visit(ctx, self, Some(Path::new(node, NodeField::call_properties)));
                    self.newline();
                    need_comma = true;
                }
                for prop in internal_slots {
                    if need_comma {
                        self.comma();
                    }
                    prop.visit(ctx, self, Some(Path::new(node, NodeField::internal_slots)));
                    self.newline();
                    need_comma = true;
                }

                if *inexact {
                    if need_comma {
                        self.comma();
                    }
                    out!(self, "...");
                }

                self.dec_indent();
                self.newline();
                out!(self, "{}", if *exact { "|}" } else { "}" });
            }
            Node::ObjectTypeProperty(ObjectTypeProperty {
                metadata: _,
                key,
                value,
                method,
                optional,
                is_static,
                proto,
                variance,
                ..
            }) => {
                if let Some(variance) = variance {
                    variance.visit(ctx, self, Some(Path::new(node, NodeField::variance)));
                }
                if *is_static {
                    out!(self, "static ");
                }
                if *proto {
                    out!(self, "proto ");
                }
                key.visit(ctx, self, Some(Path::new(node, NodeField::key)));
                if *optional {
                    out!(self, "?");
                }
                if *method {
                    match value {
                        Node::FunctionTypeAnnotation(FunctionTypeAnnotation {
                            metadata: _,
                            params,
                            this,
                            return_type,
                            rest,
                            type_parameters,
                        }) => {
                            self.visit_func_type_params(
                                ctx,
                                params,
                                *this,
                                *rest,
                                *type_parameters,
                                node,
                            );
                            out!(self, ":");
                            self.space(ForceSpace::No);
                            return_type.visit(
                                ctx,
                                self,
                                Some(Path::new(node, NodeField::return_type)),
                            );
                        }
                        _ => {
                            unimplemented!("Malformed AST: Need to handle error");
                        }
                    }
                } else {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    value.visit(ctx, self, Some(Path::new(node, NodeField::value)));
                }
            }
            Node::ObjectTypeSpreadProperty(ObjectTypeSpreadProperty {
                metadata: _,
                argument,
            }) => {
                out!(self, "...");
                argument.visit(ctx, self, Some(Path::new(node, NodeField::argument)));
            }
            Node::ObjectTypeInternalSlot(ObjectTypeInternalSlot {
                metadata: _,
                id,
                value,
                optional,
                is_static,
                method,
            }) => {
                if *is_static {
                    out!(self, "static ");
                }
                out!(self, "[[");
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                if *optional {
                    out!(self, "?");
                }
                out!(self, "]]");
                if *method {
                    match value {
                        Node::FunctionTypeAnnotation(FunctionTypeAnnotation {
                            metadata: _,
                            params,
                            this,
                            return_type,
                            rest,
                            type_parameters,
                        }) => {
                            self.visit_func_type_params(
                                ctx,
                                params,
                                *this,
                                *rest,
                                *type_parameters,
                                node,
                            );
                            out!(self, ":");
                            self.space(ForceSpace::No);
                            return_type.visit(
                                ctx,
                                self,
                                Some(Path::new(node, NodeField::return_type)),
                            );
                        }
                        _ => {
                            unimplemented!("Malformed AST: Need to handle error");
                        }
                    }
                } else {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    value.visit(ctx, self, Some(Path::new(node, NodeField::value)));
                }
            }
            Node::ObjectTypeCallProperty(ObjectTypeCallProperty {
                metadata: _,
                value,
                is_static,
            }) => {
                if *is_static {
                    out!(self, "static ");
                }
                match value {
                    Node::FunctionTypeAnnotation(FunctionTypeAnnotation {
                        metadata: _,
                        params,
                        this,
                        return_type,
                        rest,
                        type_parameters,
                    }) => {
                        self.visit_func_type_params(
                            ctx,
                            params,
                            *this,
                            *rest,
                            *type_parameters,
                            node,
                        );
                        out!(self, ":");
                        self.space(ForceSpace::No);
                        return_type.visit(ctx, self, Some(Path::new(node, NodeField::return_type)));
                    }
                    _ => {
                        unimplemented!("Malformed AST: Need to handle error");
                    }
                }
            }
            Node::ObjectTypeIndexer(ObjectTypeIndexer {
                metadata: _,
                id,
                key,
                value,
                is_static,
                variance,
            }) => {
                if *is_static {
                    out!(self, "static ");
                }
                if let Some(variance) = variance {
                    variance.visit(ctx, self, Some(Path::new(node, NodeField::variance)));
                }
                out!(self, "[");
                if let Some(id) = id {
                    id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                    out!(self, ":");
                    self.space(ForceSpace::No);
                }
                key.visit(ctx, self, Some(Path::new(node, NodeField::key)));
                out!(self, "]");
                out!(self, ":");
                self.space(ForceSpace::No);
                value.visit(ctx, self, Some(Path::new(node, NodeField::value)));
            }
            Node::Variance(Variance { metadata: _, kind }) => {
                out_token!(
                    self,
                    node,
                    "{}",
                    match ctx.str(*kind) {
                        "plus" => "+",
                        "minus" => "-",
                        _ => unimplemented!("Malformed variance"),
                    }
                )
            }

            Node::TypeParameterDeclaration(TypeParameterDeclaration {
                metadata: _,
                params,
            })
            | Node::TypeParameterInstantiation(TypeParameterInstantiation {
                metadata: _,
                params,
            }) => {
                out!(self, "<");
                for (i, param) in params.iter().enumerate() {
                    if i > 0 {
                        self.comma();
                    }
                    param.visit(ctx, self, Some(Path::new(node, NodeField::params)));
                }
                out!(self, ">");
            }
            Node::TypeParameter(TypeParameter {
                metadata: _,
                name,
                bound,
                variance,
                default,
            }) => {
                if let Some(variance) = variance {
                    variance.visit(ctx, self, Some(Path::new(node, NodeField::variance)));
                }
                out!(self, "{}", ctx.str(*name));
                if let Some(bound) = bound {
                    out!(self, ":");
                    self.space(ForceSpace::No);
                    bound.visit(ctx, self, Some(Path::new(node, NodeField::bound)));
                }
                if let Some(default) = default {
                    out!(self, "=");
                    self.space(ForceSpace::No);
                    default.visit(ctx, self, Some(Path::new(node, NodeField::default)));
                }
            }
            Node::TypeCastExpression(TypeCastExpression {
                metadata: _,
                expression,
                type_annotation,
            }) => {
                // Type casts are required to have pathheses.
                out!(self, "(");
                self.print_child(
                    ctx,
                    Some(*expression),
                    Path::new(node, NodeField::expression),
                    ChildPos::Left,
                );
                out!(self, ":");
                self.space(ForceSpace::No);
                self.print_child(
                    ctx,
                    Some(*type_annotation),
                    Path::new(node, NodeField::type_annotation),
                    ChildPos::Right,
                );
                out!(self, ")");
            }
            Node::InferredPredicate(_) => {
                out_token!(self, node, "%checks");
            }
            Node::DeclaredPredicate(DeclaredPredicate { metadata: _, value }) => {
                out_token!(self, node, "%checks(");
                value.visit(ctx, self, Some(Path::new(node, NodeField::value)));
                out!(self, ")");
            }

            Node::EnumDeclaration(EnumDeclaration {
                metadata: _,
                id,
                body,
            }) => {
                out_token!(self, node, "enum ");
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                body.visit(ctx, self, Some(Path::new(node, NodeField::body)));
            }
            Node::EnumStringBody(EnumStringBody {
                metadata: _,
                members,
                explicit_type,
                has_unknown_members,
            }) => {
                self.visit_enum_body(
                    ctx,
                    "string",
                    members,
                    *explicit_type,
                    *has_unknown_members,
                    node,
                );
            }
            Node::EnumNumberBody(EnumNumberBody {
                metadata: _,
                members,
                explicit_type,
                has_unknown_members,
            }) => {
                self.visit_enum_body(
                    ctx,
                    "number",
                    members,
                    *explicit_type,
                    *has_unknown_members,
                    node,
                );
            }
            Node::EnumBooleanBody(EnumBooleanBody {
                metadata: _,
                members,
                explicit_type,
                has_unknown_members,
            }) => {
                self.visit_enum_body(
                    ctx,
                    "boolean",
                    members,
                    *explicit_type,
                    *has_unknown_members,
                    node,
                );
            }
            Node::EnumSymbolBody(EnumSymbolBody {
                metadata: _,
                members,
                has_unknown_members,
            }) => {
                self.visit_enum_body(ctx, "symbol", members, true, *has_unknown_members, node);
            }
            Node::EnumDefaultedMember(EnumDefaultedMember { metadata: _, id }) => {
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
            }
            Node::EnumStringMember(EnumStringMember {
                metadata: _,
                id,
                init,
            })
            | Node::EnumNumberMember(EnumNumberMember {
                metadata: _,
                id,
                init,
            })
            | Node::EnumBooleanMember(EnumBooleanMember {
                metadata: _,
                id,
                init,
            }) => {
                id.visit(ctx, self, Some(Path::new(node, NodeField::id)));
                out!(
                    self,
                    "{}",
                    match self.pretty {
                        Pretty::Yes => " = ",
                        Pretty::No => "=",
                    }
                );
                init.visit(ctx, self, Some(Path::new(node, NodeField::init)));
            }

            _ => {
                unimplemented!("Cannot generate node kind: {}", node.name());
            }
        };
    }