unsupported/juno/crates/juno/src/gen_js.rs (3,762 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ use crate::{ ast::*, sema::{DeclKind, Resolution, SemContext}, }; use juno_support::{convert, source_manager::SourceLoc}; use sourcemap::{RawToken, SourceMap, SourceMapBuilder}; use std::{ fmt, io::{self, BufWriter, Write}, }; /// Whether to pretty-print the generated JS. /// Does not do full formatting of the source, but does add indentation and /// some extra spaces to make source more readable. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Pretty { No, Yes, } /// Generate JS for `root` and print it to `out`. /// FIXME: This currently only returns an empty SourceMap. pub fn generate<W: Write>( out: W, ctx: &mut Context, root: &NodeRc, pretty: Pretty, annotation: Annotation, ) -> io::Result<SourceMap> { let gc = GCLock::new(ctx); GenJS::gen_root(out, &gc, root.node(&gc), pretty, annotation) } /// Associativity direction. #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Assoc { /// Left to right associativity. Ltr, /// Right to left associativity. Rtl, } mod precedence { use crate::ast::{BinaryExpressionOperator, LogicalExpressionOperator}; pub type Precedence = u32; pub const ALWAYS_PAREN: Precedence = 0; pub const SEQ: Precedence = 1; pub const ARROW: Precedence = 2; pub const YIELD: Precedence = 3; pub const ASSIGN: Precedence = 4; pub const COND: Precedence = 5; pub const BIN_START: Precedence = 6; pub const UNARY: Precedence = 26; pub const POST_UPDATE: Precedence = 27; pub const TAGGED_TEMPLATE: Precedence = 28; pub const NEW_NO_ARGS: Precedence = 29; pub const MEMBER: Precedence = 30; pub const PRIMARY: Precedence = 31; pub const TOP: Precedence = 32; pub const UNION_TYPE: Precedence = 1; pub const INTERSECTION_TYPE: Precedence = 2; pub fn get_binary_precedence(op: BinaryExpressionOperator) -> Precedence { use BinaryExpressionOperator::*; (match op { Exp => 12, Mult => 11, Mod => 11, Div => 11, Plus => 10, Minus => 10, LShift => 9, RShift => 9, RShift3 => 9, Less => 8, Greater => 8, LessEquals => 8, GreaterEquals => 8, LooseEquals => 7, LooseNotEquals => 7, StrictEquals => 7, StrictNotEquals => 7, BitAnd => 6, BitXor => 5, BitOr => 4, In => 8, Instanceof => 8, }) + BIN_START } pub fn get_logical_precedence(op: LogicalExpressionOperator) -> Precedence { use LogicalExpressionOperator::*; (match op { And => 3, Or => 2, NullishCoalesce => 1, }) + BIN_START } } /// Child position for the purpose of determining whether the child needs parens. #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum ChildPos { Left, Anywhere, Right, } /// Whether parens are needed around something. #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum NeedParens { /// No pathheses needed. No, /// Pathheses required. Yes, /// A space character is sufficient to distinguish. /// Used in unary operations, e.g. Space, } impl From<bool> for NeedParens { fn from(x: bool) -> NeedParens { if x { NeedParens::Yes } else { NeedParens::No } } } /// Whether to force a space when adding a space in JS generation. #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum ForceSpace { No, Yes, } /// Whether to force the statements to be emitted inside a new block `{ }`. #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum ForceBlock { No, Yes, } pub enum Annotation<'s> { No, Sem(&'s SemContext), } /// Generator for output JS. Walks the AST to output real JS. struct GenJS<'s, W: Write> { /// Where to write the generated JS. out: BufWriter<W>, /// How to annotate the generated source. annotation: Annotation<'s>, /// Whether to pretty print the output JS. pretty: Pretty, /// Size of the indentation step. /// May be configurable in the future. indent_step: usize, /// Current indentation level, used in pretty mode. indent: usize, /// Current position of the writer. position: SourceLoc, /// Raw token tracking the most recent node. cur_token: Option<RawToken>, /// Build a source map as we go along. sourcemap: SourceMapBuilder, /// Some(err) if an error has occurred when writing, else None. error: Option<io::Error>, } /// Print to the output stream if no errors have been seen so far. /// `$gen_js` is a mutable reference to the GenJS struct. /// `$arg` arguments follow the format pattern used by `format!`. /// The output must be ASCII and contain no newlines. macro_rules! out { ($gen_js:expr, $($arg:tt)*) => {{ $gen_js.write_ascii(format_args!($($arg)*)); }} } /// Emit a source mapping token at this point with the given node, and call `out!()`. /// Call this macro instead of `out!()` directly if emitting the start of an AST node. macro_rules! out_token { ($gen_js:expr, $node:expr, $($arg:tt)*) => {{ $gen_js.add_segment($node); out!($gen_js, $($arg)*); }} } impl<W: Write> GenJS<'_, W> { /// Generate JS for `root` and flush the output. /// If at any point, JS generation resulted in an error, return `Err(err)`, /// otherwise return `Ok(())`. fn gen_root<'s, 'gc>( writer: W, ctx: &'gc GCLock, root: &'gc Node<'gc>, pretty: Pretty, annotation: Annotation<'s>, ) -> io::Result<SourceMap> { let mut gen_js = GenJS { out: BufWriter::new(writer), annotation, pretty, indent_step: 2, indent: 0, position: SourceLoc { line: 1, col: 1 }, cur_token: None, // FIXME: Pass in file name here. sourcemap: SourceMapBuilder::new(None), error: None, }; root.visit(ctx, &mut gen_js, None); gen_js.force_newline(); gen_js.flush_cur_token(); match gen_js.error { None => gen_js .out .flush() .and(Ok(gen_js.sourcemap.into_sourcemap())), Some(err) => Err(err), } } /// Write to the `out` writer if we haven't seen any errors. /// If we have seen any errors, do nothing. /// Used via the `out!` macro. /// The output must be ASCII and contain no newlines. fn write_ascii(&mut self, args: fmt::Arguments<'_>) { if self.error.is_none() { let buf = format!("{}", args); debug_assert!(buf.is_ascii(), "Output must be ASCII"); debug_assert!(!buf.contains('\n'), "Output must have no newlines"); if let Err(e) = self.out.write_all(buf.as_bytes()) { self.error = Some(e); } self.position.col += buf.len() as u32; } } /// Write a single unicode character to the `out` writer if we haven't seen any errors. /// Character must not be a newline. /// Use `dst` as a temporary buffer. /// If we have seen any errors, do nothing. fn write_char(&mut self, ch: char, dst: &mut [u8]) { debug_assert!(ch != '\n', "Output must not contain newlines"); if self.error.is_none() { if let Err(e) = self.out.write_all(ch.encode_utf8(dst).as_bytes()) { self.error = Some(e); } self.position.col += 1; } } /// Write unicode to the `out` writer if we haven't seen any errors. /// If we have seen any errors, do nothing. /// The output must contain no newlines. fn write_utf8(&mut self, s: &str) { debug_assert!( !s.chars().any(|c| c == '\n'), "Output must not contain newlines" ); if self.error.is_none() { if let Err(e) = self.out.write_all(s.as_bytes()) { self.error = Some(e); } } self.position.col += s.chars().count() as u32; } /// Generate the JS for each node kind. 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()); } }; } /// Increase the indent level. fn inc_indent(&mut self) { self.indent += self.indent_step; } /// Decrease the indent level. fn dec_indent(&mut self) { self.indent -= self.indent_step; } /// Print a ',', with a trailing space in pretty mode. fn comma(&mut self) { out!( self, "{}", match self.pretty { Pretty::No => ",", Pretty::Yes => ", ", } ) } /// Print a ' ' if forced by ForceSpace::Yes or pretty mode. fn space(&mut self, force: ForceSpace) { if self.pretty == Pretty::Yes || force == ForceSpace::Yes { out!(self, " "); } } /// Print a newline and indent if pretty. fn newline(&mut self) { if self.pretty == Pretty::Yes { self.force_newline(); } } /// Print a newline and indent. fn force_newline(&mut self) { self.force_newline_without_indent(); out!(self, "{:indent$}", "", indent = self.indent as usize); } /// Print a newline without any indent after. fn force_newline_without_indent(&mut self) { if self.error.is_none() { if let Err(e) = self.out.write(&[b'\n']) { self.error = Some(e); } } self.position.line += 1; self.position.col = 1; } /// Print the child of a `path` node at the position `child_pos`. fn print_child<'gc>( &mut self, ctx: &'gc GCLock, child: Option<&'gc Node<'gc>>, path: Path<'gc>, child_pos: ChildPos, ) { if let Some(child) = child { self.print_parens( ctx, child, path, self.need_parens(ctx, path, child, child_pos), ); } } /// Print one expression in a sequence separated by comma. It needs parens /// if its precedence is <= comma. fn print_comma_expression<'gc>( &mut self, ctx: &'gc GCLock, child: &'gc Node<'gc>, path: Path<'gc>, ) { self.print_parens( ctx, child, path, NeedParens::from(self.get_precedence(child).0 <= precedence::SEQ), ) } fn print_parens<'gc>( &mut self, ctx: &'gc GCLock, child: &'gc Node<'gc>, path: Path<'gc>, need_parens: NeedParens, ) { if need_parens == NeedParens::Yes { out!(self, "("); } else if need_parens == NeedParens::Space { out!(self, " "); } child.visit(ctx, self, Some(path)); if need_parens == NeedParens::Yes { out!(self, ")"); } } fn print_escaped_string_literal(&mut self, value: &NodeString, esc: char) { for &c in &value.str { if c <= u8::MAX as u16 { match char::from(c as u8) { '\\' => { out!(self, "\\\\"); continue; } '\x08' => { out!(self, "\\b"); continue; } '\x0c' => { out!(self, "\\f"); continue; } '\n' => { out!(self, "\\n"); continue; } '\r' => { out!(self, "\\r"); continue; } '\t' => { out!(self, "\\t"); continue; } '\x0b' => { out!(self, "\\v"); continue; } _ => {} }; } if c == esc as u16 { out!(self, "\\"); } if (0x20..=0x7f).contains(&c) { // Printable. out!(self, "{}", char::from(c as u8)); } else { out!(self, "\\u{:04x}", c); } } } fn visit_props<'gc>(&mut self, ctx: &'gc GCLock, props: &[&'gc Node<'gc>], path: Path<'gc>) { out!(self, "{{"); for (i, prop) in props.iter().enumerate() { if i > 0 { self.comma(); } prop.visit(ctx, self, Some(path)); } out!(self, "}}"); } #[allow(clippy::too_many_arguments)] fn visit_func_params_body<'gc>( &mut self, ctx: &'gc GCLock, params: &[&'gc Node<'gc>], type_parameters: Option<&'gc Node<'gc>>, return_type: Option<&'gc Node<'gc>>, predicate: Option<&'gc Node<'gc>>, body: &'gc Node<'gc>, node: &'gc Node<'gc>, ) { if let Some(type_parameters) = type_parameters { type_parameters.visit(ctx, self, Some(Path::new(node, NodeField::type_parameters))); } out!(self, "("); for (i, param) in params.iter().enumerate() { if i > 0 { self.comma(); } param.visit(ctx, self, Some(Path::new(node, NodeField::param))); } out!(self, ")"); if let Some(return_type) = return_type { out!(self, ":"); self.space(ForceSpace::No); return_type.visit(ctx, self, Some(Path::new(node, NodeField::return_type))); } if let Some(predicate) = predicate { self.space(ForceSpace::Yes); predicate.visit(ctx, self, Some(Path::new(node, NodeField::predicate))); } self.space(ForceSpace::No); body.visit(ctx, self, Some(Path::new(node, NodeField::body))); } fn visit_func_type_params<'gc>( &mut self, ctx: &'gc GCLock, params: &[&'gc Node<'gc>], this: Option<&'gc Node<'gc>>, rest: Option<&'gc Node<'gc>>, type_parameters: Option<&'gc Node<'gc>>, node: &'gc Node<'gc>, ) { 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, ")"); } #[allow(clippy::too_many_arguments)] fn visit_interface<'gc>( &mut self, ctx: &'gc GCLock, decl: &str, id: &'gc Node<'gc>, type_parameters: Option<&'gc Node<'gc>>, extends: &[&'gc Node<'gc>], body: &'gc Node<'gc>, node: &'gc Node<'gc>, ) { out!(self, "{} ", decl); 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))); } self.space(ForceSpace::No); 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))); } self.space(ForceSpace::No); } body.visit(ctx, self, Some(Path::new(node, NodeField::body))); } /// Generate the body of a Flow enum with type `kind`. fn visit_enum_body<'gc>( &mut self, ctx: &'gc GCLock, kind: &str, members: &[&'gc Node<'gc>], explicit_type: bool, has_unknown_members: bool, node: &'gc Node<'gc>, ) { if explicit_type { out!(self, ":"); self.space(ForceSpace::No); out!(self, "{}", kind); } out!(self, "{{"); self.inc_indent(); self.newline(); for (i, member) in members.iter().enumerate() { if i > 0 { self.comma(); self.newline(); } member.visit(ctx, self, Some(Path::new(node, NodeField::members))); } if has_unknown_members { if !members.is_empty() { self.comma(); self.newline(); } out!(self, "..."); } self.dec_indent(); self.newline(); out!(self, "}}"); } /// Visit a statement node which is the body of a loop or a clause in an if. /// It could be a block statement. /// Return true if block fn visit_stmt_or_block<'gc>( &mut self, ctx: &'gc GCLock, node: &'gc Node<'gc>, force_block: ForceBlock, path: Path<'gc>, ) -> bool { if let Node::BlockStatement(BlockStatement { metadata: _, body }) = &node { if body.is_empty() { self.space(ForceSpace::No); out!(self, "{{}}"); return true; } self.space(ForceSpace::No); 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, "}}"); return true; } if force_block == ForceBlock::Yes { self.space(ForceSpace::No); out!(self, "{{"); self.inc_indent(); self.newline(); self.visit_stmt_in_block(ctx, node, path); self.dec_indent(); self.newline(); out!(self, "}}"); self.newline(); true } else { self.inc_indent(); self.newline(); self.visit_stmt_in_block(ctx, node, path); self.dec_indent(); false } } fn visit_stmt_list<'gc>(&mut self, ctx: &'gc GCLock, list: &[&'gc Node<'gc>], path: Path<'gc>) { for (i, stmt) in list.iter().enumerate() { if i > 0 { self.newline(); } self.visit_stmt_in_block(ctx, *stmt, path); } } fn visit_stmt_in_block<'gc>( &mut self, ctx: &'gc GCLock, stmt: &'gc Node<'gc>, path: Path<'gc>, ) { stmt.visit(ctx, self, Some(path)); if !stmt_skip_semi(ctx, Some(stmt)) { out!(self, ";"); } } /// Return the precedence and associativity of `node`. fn get_precedence(&self, node: &Node<'_>) -> (precedence::Precedence, Assoc) { // Precedence order taken from // https://github.com/facebook/flow/blob/master/src/parser_utils/output/js_layout_generator.ml use precedence::*; match &node { Node::Identifier(_) | Node::NullLiteral(_) | Node::BooleanLiteral(_) | Node::StringLiteral(_) | Node::NumericLiteral(_) | Node::RegExpLiteral(_) | Node::ThisExpression(_) | Node::Super(_) | Node::ArrayExpression(_) | Node::ObjectExpression(_) | Node::ObjectPattern(_) | Node::FunctionExpression(_) | Node::ClassExpression(_) | Node::TemplateLiteral(_) | Node::JSXElement(_) | Node::JSXFragment(_) | Node::TypeCastExpression(_) => (PRIMARY, Assoc::Ltr), Node::MemberExpression(_) | Node::OptionalMemberExpression(_) | Node::MetaProperty(_) | Node::CallExpression(_) | Node::OptionalCallExpression(_) => (MEMBER, Assoc::Ltr), Node::NewExpression(NewExpression { metadata: _, arguments, .. }) => { // `new foo()` has higher precedence than `new foo`. In pretty mode we // always append the `()`, but otherwise we must check the number of args. if self.pretty == Pretty::Yes || !arguments.is_empty() { (MEMBER, Assoc::Ltr) } else { (NEW_NO_ARGS, Assoc::Ltr) } } Node::TaggedTemplateExpression(_) | Node::ImportExpression(_) => { (TAGGED_TEMPLATE, Assoc::Ltr) } Node::UpdateExpression(UpdateExpression { metadata: _, prefix, .. }) => { if *prefix { (POST_UPDATE, Assoc::Ltr) } else { (UNARY, Assoc::Rtl) } } Node::UnaryExpression(_) => (UNARY, Assoc::Rtl), Node::BinaryExpression(BinaryExpression { metadata: _, operator, .. }) => (get_binary_precedence(*operator), Assoc::Ltr), Node::LogicalExpression(LogicalExpression { metadata: _, operator, .. }) => (get_logical_precedence(*operator), Assoc::Ltr), Node::ConditionalExpression(_) => (COND, Assoc::Rtl), Node::AssignmentExpression(_) => (ASSIGN, Assoc::Rtl), Node::ArrowFunctionExpression(_) => (ARROW, Assoc::Ltr), Node::YieldExpression(_) => (YIELD, Assoc::Ltr), Node::SequenceExpression(_) => (SEQ, Assoc::Rtl), Node::ExistsTypeAnnotation(_) | Node::EmptyTypeAnnotation(_) | Node::StringTypeAnnotation(_) | Node::NumberTypeAnnotation(_) | Node::StringLiteralTypeAnnotation(_) | Node::NumberLiteralTypeAnnotation(_) | Node::BooleanTypeAnnotation(_) | Node::BooleanLiteralTypeAnnotation(_) | Node::NullLiteralTypeAnnotation(_) | Node::SymbolTypeAnnotation(_) | Node::AnyTypeAnnotation(_) | Node::MixedTypeAnnotation(_) | Node::VoidTypeAnnotation(_) => (PRIMARY, Assoc::Ltr), Node::NullableTypeAnnotation(_) => (UNARY, Assoc::Ltr), Node::UnionTypeAnnotation(_) => (UNION_TYPE, Assoc::Ltr), Node::IntersectionTypeAnnotation(_) => (INTERSECTION_TYPE, Assoc::Ltr), _ => (ALWAYS_PAREN, Assoc::Ltr), } } /// Return whether pathheses are needed around the `child` node, /// which is situated at `child_pos` position in relation to its `path`. fn need_parens<'gc>( &self, ctx: &'gc GCLock, path: Path<'gc>, child: &'gc Node<'gc>, child_pos: ChildPos, ) -> NeedParens { #[allow(clippy::if_same_then_else)] if matches!(path.parent, Node::ArrowFunctionExpression(_)) { // (x) => ({x: 10}) needs parens to avoid confusing it with a block and a // labelled statement. if child_pos == ChildPos::Right && matches!(child, Node::ObjectExpression(_)) { return NeedParens::Yes; } } else if matches!(path.parent, Node::ForStatement(_)) { // for((a in b);..;..) needs parens to avoid confusing it with for(a in b). return NeedParens::from(match &child { Node::BinaryExpression(BinaryExpression { metadata: _, operator, .. }) => *operator == BinaryExpressionOperator::In, _ => false, }); } else if matches!(path.parent, Node::NewExpression(_)) { // `new(fn())` needs parens to avoid confusing it with `new fn()`. // Need to check the entire subtree to ensure there isn't a call anywhere in it, // because if there is, it would take precedence and terminate the `new` early. // As an example, see the difference between // `new(foo().bar)` (which gets `bar` on `foo()`) // and // `new foo().bar` (which gets `bar` on `new foo()`) if child_pos == ChildPos::Left && contains_call(ctx, child) { return NeedParens::Yes; } } else if matches!(path.parent, Node::ExpressionStatement(_)) { // Expression statement like (function () {} + 1) needs parens. return NeedParens::from(self.root_starts_with(ctx, child, |kind| -> bool { matches!( kind, Node::FunctionExpression(_) | Node::ClassExpression(_) | Node::ObjectExpression(_) | Node::ObjectPattern(_) ) })); } else if (is_unary_op(path.parent, UnaryExpressionOperator::Minus) && self.root_starts_with(ctx, child, check_minus)) || (is_unary_op(path.parent, UnaryExpressionOperator::Plus) && self.root_starts_with(ctx, child, check_plus)) || (child_pos == ChildPos::Right && is_binary_op(path.parent, BinaryExpressionOperator::Minus) && self.root_starts_with(ctx, child, check_minus)) || (child_pos == ChildPos::Right && is_binary_op(path.parent, BinaryExpressionOperator::Plus) && self.root_starts_with(ctx, child, check_plus)) { // -(-x) or -(--x) or -(-5) // +(+x) or +(++x) // a-(-x) or a-(--x) or a-(-5) // a+(+x) or a+(++x) return if self.pretty == Pretty::Yes { NeedParens::Yes } else { NeedParens::Space }; } else if matches!( path.parent, Node::MemberExpression(_) | Node::CallExpression(_) ) && matches!( child, Node::OptionalMemberExpression(_) | Node::OptionalCallExpression(_) ) && child_pos == ChildPos::Left { // When optional chains are terminated by non-optional member/calls, // we need the left hand side to be pathhesized. // Avoids confusing `(a?.b).c` with `a?.b.c`. return NeedParens::Yes; } else if (check_and_or(path.parent) && check_nullish(child)) || (check_nullish(path.parent) && check_and_or(child)) { // Nullish coalescing always requires parens when mixed with any // other logical operations. return NeedParens::Yes; } else if matches!( path.parent, Node::CallExpression(_) | Node::OptionalCallExpression(_) ) && matches!(child, Node::SpreadElement(_)) { // It's illegal to place parens around spread arguments. return NeedParens::No; } let (child_prec, _child_assoc) = self.get_precedence(child); if child_prec == precedence::ALWAYS_PAREN { return NeedParens::Yes; } let (path_prec, path_assoc) = self.get_precedence(path.parent); if child_prec < path_prec { // Child is definitely a danger. return NeedParens::Yes; } if child_prec > path_prec { // Definitely cool. return NeedParens::No; } // Equal precedence, so associativity (rtl/ltr) is what matters. if child_pos == ChildPos::Anywhere { // Child could be anywhere, so always paren. return NeedParens::Yes; } if child_prec == precedence::TOP { // Both precedences are safe. return NeedParens::No; } // Check if child is on the dangerous side. NeedParens::from(if path_assoc == Assoc::Rtl { child_pos == ChildPos::Left } else { child_pos == ChildPos::Right }) } fn root_starts_with<'gc, F: Fn(&'gc Node<'gc>) -> bool>( &self, ctx: &'gc GCLock, expr: &'gc Node<'gc>, pred: F, ) -> bool { self.expr_starts_with(ctx, expr, None, pred) } fn expr_starts_with<'gc, F: Fn(&'gc Node<'gc>) -> bool>( &self, ctx: &'gc GCLock, expr: &'gc Node<'gc>, path: Option<Path<'gc>>, pred: F, ) -> bool { if let Some(path) = path { if self.need_parens(ctx, path, expr, ChildPos::Left) == NeedParens::Yes { return false; } } if pred(expr) { return true; } // Ensure the recursive calls are the last things to run, // hopefully the compiler makes this into a loop. match expr { Node::CallExpression(CallExpression { metadata: _, callee, .. }) => { self.expr_starts_with(ctx, *callee, Some(Path::new(expr, NodeField::callee)), pred) } Node::OptionalCallExpression(OptionalCallExpression { metadata: _, callee, .. }) => { self.expr_starts_with(ctx, *callee, Some(Path::new(expr, NodeField::callee)), pred) } Node::BinaryExpression(BinaryExpression { metadata: _, left, .. }) => self.expr_starts_with(ctx, *left, Some(Path::new(expr, NodeField::left)), pred), Node::LogicalExpression(LogicalExpression { metadata: _, left, .. }) => self.expr_starts_with(ctx, *left, Some(Path::new(expr, NodeField::left)), pred), Node::ConditionalExpression(ConditionalExpression { metadata: _, test, .. }) => self.expr_starts_with(ctx, *test, Some(Path::new(expr, NodeField::test)), pred), Node::AssignmentExpression(AssignmentExpression { metadata: _, left, .. }) => self.expr_starts_with(ctx, *left, Some(Path::new(expr, NodeField::left)), pred), Node::UpdateExpression(UpdateExpression { metadata: _, prefix, argument, .. }) => { !*prefix && self.expr_starts_with( ctx, *argument, Some(Path::new(expr, NodeField::argument)), pred, ) } Node::UnaryExpression(UnaryExpression { metadata: _, prefix, argument, .. }) => { !*prefix && self.expr_starts_with( ctx, *argument, Some(Path::new(expr, NodeField::argument)), pred, ) } Node::MemberExpression(MemberExpression { metadata: _, object, .. }) | Node::OptionalMemberExpression(OptionalMemberExpression { metadata: _, object, .. }) => { self.expr_starts_with(ctx, *object, Some(Path::new(expr, NodeField::object)), pred) } Node::TaggedTemplateExpression(TaggedTemplateExpression { metadata: _, tag, .. }) => self.expr_starts_with(ctx, *tag, Some(Path::new(expr, NodeField::tag)), pred), _ => false, } } /// Adds the current location as a segment pointing to the start of `node`. fn add_segment(&mut self, node: &Node) { // Convert from 1-indexed to 0-indexed as expected by source map. // Use `wrapping_sub` in case the line/col are invalid (0) to ensure // the overflow goes to `u32::MAX`. let new_token = Some(RawToken { dst_line: self.position.line.wrapping_sub(1), dst_col: self.position.col.wrapping_sub(1), src_line: node.range().start.line.wrapping_sub(1), src_col: node.range().start.col.wrapping_sub(1), src_id: 0, name_id: !0, }); self.flush_cur_token(); self.cur_token = new_token; } /// Add the `cur_token` to the sourcemap and set `cur_token` to `None`. fn flush_cur_token(&mut self) { if let Some(cur) = self.cur_token { self.sourcemap.add_raw( cur.dst_line, cur.dst_col, cur.src_line, cur.src_col, if cur.src_id != !0 { Some(cur.src_id) } else { None }, if cur.name_id != !0 { Some(cur.name_id) } else { None }, ); } self.cur_token = None; } /// Add an "@" and some information tagging an identifier with its declaration ID. fn annotate_identifier(&mut self, lock: &GCLock, node: &Node) { if let Annotation::Sem(sem) = &self.annotation { match sem.ident_decl(&NodeRc::from_node(lock, node)) { Some(Resolution::Decl(decl_id)) => { match sem.decl(decl_id).kind { DeclKind::Let | DeclKind::Const | DeclKind::Class | DeclKind::Import | DeclKind::ES5Catch | DeclKind::FunctionExprName | DeclKind::ScopedFunction | DeclKind::Var | DeclKind::Parameter => { out!(self, "@D{}", decl_id); } DeclKind::GlobalProperty => { out!(self, "@global"); } DeclKind::UndeclaredGlobalProperty => { out!(self, "@uglobal"); } }; } Some(Resolution::Unresolvable) => { out!(self, "@unresolvable"); } _ => {} } }; } } impl<'gc, W: Write> Visitor<'gc> for GenJS<'_, W> { fn call(&mut self, ctx: &'gc GCLock, node: &'gc Node<'gc>, path: Option<Path<'gc>>) { self.gen_node(ctx, node, path); } } fn is_unary_op(node: &Node, op: UnaryExpressionOperator) -> bool { match node { Node::UnaryExpression(UnaryExpression { metadata: _, operator, .. }) => *operator == op, _ => false, } } fn is_update_prefix(node: &Node, op: UpdateExpressionOperator) -> bool { match node { Node::UpdateExpression(UpdateExpression { metadata: _, prefix, operator, .. }) => *prefix && *operator == op, _ => false, } } fn is_negative_number(node: &Node) -> bool { match node { Node::NumericLiteral(NumericLiteral { metadata: _, value, .. }) => *value < 0f64, _ => false, } } fn is_binary_op(node: &Node, op: BinaryExpressionOperator) -> bool { match node { Node::BinaryExpression(BinaryExpression { metadata: _, operator, .. }) => *operator == op, _ => false, } } fn is_if_without_else(node: &Node) -> bool { match node { Node::IfStatement(IfStatement { metadata: _, alternate, .. }) => alternate.is_none(), _ => false, } } fn check_plus(node: &Node) -> bool { is_unary_op(node, UnaryExpressionOperator::Plus) || is_update_prefix(node, UpdateExpressionOperator::Increment) } fn check_minus(node: &Node) -> bool { is_unary_op(node, UnaryExpressionOperator::Minus) || is_update_prefix(node, UpdateExpressionOperator::Decrement) || is_negative_number(node) } fn check_and_or(node: &Node) -> bool { matches!( node, Node::LogicalExpression(LogicalExpression { metadata: _, operator: LogicalExpressionOperator::And | LogicalExpressionOperator::Or, .. }) ) } fn check_nullish(node: &Node) -> bool { matches!( node, Node::LogicalExpression(LogicalExpression { metadata: _, operator: LogicalExpressionOperator::NullishCoalesce, .. }) ) } /// Whether to skip the semicolon at the end of `node`. /// Block statements don't need semicolons at the end, but other statements which contain /// statements don't need them either. /// For example: /// ```js /// if (x) /// y(); /// ``` /// The semicolon will be emitted as part of emitting `y()`, which is an `ExpressionStatement`, /// so the `IfStatement` does not need to emit a semicolon. fn stmt_skip_semi<'gc>(ctx: &'gc GCLock, node: Option<&'gc Node<'gc>>) -> bool { match node { Some(node) => match &node { Node::BlockStatement(_) | Node::FunctionDeclaration(_) | Node::WhileStatement(_) | Node::ForStatement(_) | Node::ForInStatement(_) | Node::ForOfStatement(_) | Node::IfStatement(_) | Node::WithStatement(_) => true, Node::SwitchStatement(_) => true, Node::LabeledStatement(LabeledStatement { body, .. }) => { stmt_skip_semi(ctx, Some(*body)) } Node::TryStatement(TryStatement { metadata: _, finalizer, handler, .. }) => stmt_skip_semi(ctx, finalizer.or(*handler)), Node::CatchClause(CatchClause { body, .. }) => stmt_skip_semi(ctx, Some(*body)), Node::ClassDeclaration(_) => true, Node::ExportDefaultDeclaration(ExportDefaultDeclaration { metadata: _, declaration, }) => stmt_skip_semi(ctx, Some(*declaration)), Node::ExportNamedDeclaration(ExportNamedDeclaration { metadata: _, declaration, .. }) => stmt_skip_semi(ctx, *declaration), Node::EnumDeclaration(_) => true, _ => false, }, None => false, } } /// Return true if `node` contains a `CallExpression`. fn contains_call<'gc>(gc: &'gc GCLock, node: &'gc Node<'gc>) -> bool { struct CallFinder { found: bool, } impl<'gc> Visitor<'gc> for CallFinder { fn call(&mut self, gc: &'gc GCLock, node: &'gc Node<'gc>, _path: Option<Path<'gc>>) { match node { Node::CallExpression(_) | Node::OptionalCallExpression(OptionalCallExpression { optional: false, .. }) => { self.found = true; } _ => { node.visit_children(gc, self); } }; } } let mut finder = CallFinder { found: false }; node.visit(gc, &mut finder, None); finder.found }