diff --git a/crates/oxc_prettier/src/doc.rs b/crates/oxc_prettier/src/doc.rs index 8f8a7067a..ef09b6d09 100644 --- a/crates/oxc_prettier/src/doc.rs +++ b/crates/oxc_prettier/src/doc.rs @@ -56,7 +56,7 @@ impl<'a> Prettier<'a> { } #[inline] - pub(crate) fn alloc(&self, doc: Doc<'a>) -> Box<'a, Doc<'a>> { + pub(crate) fn boxed(&self, doc: Doc<'a>) -> Box<'a, Doc<'a>> { Box(self.allocator.alloc(doc)) } diff --git a/crates/oxc_prettier/src/format/block.rs b/crates/oxc_prettier/src/format/block.rs index 3ef2d54bd..85859f554 100644 --- a/crates/oxc_prettier/src/format/block.rs +++ b/crates/oxc_prettier/src/format/block.rs @@ -1,5 +1,5 @@ use oxc_allocator::Vec; -use oxc_ast::ast::*; +use oxc_ast::{ast::*, AstKind}; use crate::{doc::Doc, format::array, hardline, indent, ss, Prettier}; @@ -15,6 +15,20 @@ pub(super) fn print_block<'a>( if let Some(doc) = print_block_body(p, stmts, directives, true, false) { parts.push(indent![p, hardline!(), doc]); parts.push(hardline!()); + } else { + let parent = p.parent_kind(); + if !(matches!( + parent, + AstKind::FunctionBody(_) + | AstKind::ArrowExpression(_) + | AstKind::Function(_) + | AstKind::ForStatement(_) + | AstKind::WhileStatement(_) + | AstKind::DoWhileStatement(_) + ) || matches!(p.current_kind(), AstKind::StaticBlock(_))) + { + parts.push(hardline!()); + } } parts.push(ss!("}")); Doc::Array(parts) diff --git a/crates/oxc_prettier/src/format/mod.rs b/crates/oxc_prettier/src/format/mod.rs index 0937b5f96..e3f866769 100644 --- a/crates/oxc_prettier/src/format/mod.rs +++ b/crates/oxc_prettier/src/format/mod.rs @@ -5,12 +5,6 @@ #![allow(unused_variables)] -use std::borrow::Cow; - -use oxc_allocator::{Box, Vec}; -use oxc_ast::ast::*; -use oxc_span::GetSpan; - mod array; mod arrow_function; mod binaryish; @@ -25,10 +19,16 @@ mod statement; mod string; mod ternary; +use std::borrow::Cow; + +use oxc_allocator::{Box, Vec}; +use oxc_ast::{ast::*, AstKind}; +use oxc_span::GetSpan; + use crate::{ array, doc::{Doc, Separator}, - format, group, hardline, indent, softline, ss, string, Prettier, + format, group, hardline, indent, softline, ss, string, wrap, Prettier, }; use self::{ @@ -53,6 +53,7 @@ where impl<'a> Format<'a> for Program<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { + p.enter_node(AstKind::Program(p.alloc(self))); let mut parts = p.vec(); if let Some(hashbang) = &self.hashbang { parts.push(hashbang.format(p)); @@ -65,6 +66,7 @@ impl<'a> Format<'a> for Program<'a> { { parts.push(doc); } + p.leave_node(); Doc::Array(parts) } } @@ -155,42 +157,44 @@ impl<'a> Format<'a> for IfStatement<'a> { impl<'a> Format<'a> for BlockStatement<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - block::print_block(p, &self.body, None) + wrap!(p, self, BlockStatement, { block::print_block(p, &self.body, None) }) } } impl<'a> Format<'a> for ForStatement<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let mut parts = p.vec(); + wrap!(p, self, ForStatement, { + let mut parts = p.vec(); - parts.push(ss!("for (")); + parts.push(ss!("for (")); - let mut parts_head = p.vec(); + let mut parts_head = p.vec(); - if let Some(init) = &self.init { - parts_head.push(format!(p, init)); - } - parts_head.push(ss!(";")); - parts_head.push(Doc::Line); - if let Some(init) = &self.test { - parts_head.push(format!(p, init)); - } - parts_head.push(ss!(";")); - parts_head.push(Doc::Line); - if let Some(init) = &self.update { - parts_head.push(format!(p, init)); - } + if let Some(init) = &self.init { + parts_head.push(format!(p, init)); + } + parts_head.push(ss!(";")); + parts_head.push(Doc::Line); + if let Some(init) = &self.test { + parts_head.push(format!(p, init)); + } + parts_head.push(ss!(";")); + parts_head.push(Doc::Line); + if let Some(init) = &self.update { + parts_head.push(format!(p, init)); + } - let parts_head = indent!(p, group!(p, Doc::Array(parts_head))); + let parts_head = indent!(p, group!(p, Doc::Array(parts_head))); - parts.push(group!(p, parts_head)); + parts.push(group!(p, parts_head)); - parts.push(ss!(")")); + parts.push(ss!(")")); - let body = format!(p, self.body); - parts.push(adjust_clause(p, &self.body, body, false)); + let body = format!(p, self.body); + parts.push(adjust_clause(p, &self.body, body, false)); - Doc::Group(parts) + Doc::Group(parts) + }) } } @@ -255,43 +259,47 @@ impl<'a> Format<'a> for ForStatementLeft<'a> { impl<'a> Format<'a> for WhileStatement<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let mut parts = p.vec(); + wrap!(p, self, WhileStatement, { + let mut parts = p.vec(); - parts.push(ss!("while (")); - parts.push(group!(p, indent!(p, softline!(), format!(p, self.test)), softline!())); - parts.push(ss!(")")); + parts.push(ss!("while (")); + parts.push(group!(p, indent!(p, softline!(), format!(p, self.test)), softline!())); + parts.push(ss!(")")); - let body = format!(p, self.body); - parts.push(adjust_clause(p, &self.body, body, false)); + let body = format!(p, self.body); + parts.push(adjust_clause(p, &self.body, body, false)); - Doc::Group(parts) + Doc::Group(parts) + }) } } impl<'a> Format<'a> for DoWhileStatement<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let mut parts = p.vec(); + wrap!(p, self, DoWhileStatement, { + let mut parts = p.vec(); - let clause = format!(p, self.body); - let clause = adjust_clause(p, &self.body, clause, false); - let do_body = group!(p, ss!("do"), clause); + let clause = format!(p, self.body); + let clause = adjust_clause(p, &self.body, clause, false); + let do_body = group!(p, ss!("do"), clause); - parts.push(do_body); + parts.push(do_body); - if matches!(self.body, Statement::BlockStatement(_)) { - parts.push(ss!(" ")); - } else { - parts.push(hardline!()); - } + if matches!(self.body, Statement::BlockStatement(_)) { + parts.push(ss!(" ")); + } else { + parts.push(hardline!()); + } - parts.push(ss!("while (")); - parts.push(group!(p, indent!(p, softline!(), format!(p, self.test)), softline!())); - parts.push(ss!(")")); - if p.options.semi { - parts.push(ss!(";")); - } + parts.push(ss!("while (")); + parts.push(group!(p, indent!(p, softline!(), format!(p, self.test)), softline!())); + parts.push(ss!(")")); + if p.options.semi { + parts.push(ss!(";")); + } - Doc::Array(parts) + Doc::Array(parts) + }) } } @@ -402,6 +410,7 @@ impl<'a> Format<'a> for LabeledStatement<'a> { impl<'a> Format<'a> for TryStatement<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { + p.enter_node(AstKind::TryStatement(p.alloc(self))); let mut parts = p.vec(); parts.push(ss!("try ")); parts.push(format!(p, self.block)); @@ -413,6 +422,7 @@ impl<'a> Format<'a> for TryStatement<'a> { parts.push(ss!(" finally ")); parts.push(format!(p, finalizer)); } + p.leave_node(); Doc::Array(parts) } } @@ -854,13 +864,15 @@ impl<'a> Format<'a> for VariableDeclarator<'a> { impl<'a> Format<'a> for Function<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - function::print_function(p, self, None) + wrap!(p, self, Function, { function::print_function(p, self, None) }) } } impl<'a> Format<'a> for FunctionBody<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - block::print_block(p, &self.statements, Some(&self.directives)) + wrap!(p, self, FunctionBody, { + block::print_block(p, &self.statements, Some(&self.directives)) + }) } } @@ -1325,11 +1337,13 @@ impl<'a> Format<'a> for ObjectProperty<'a> { } if method { if let Expression::FunctionExpression(func_expr) = &self.value { - parts.push(function::print_function( - p, - func_expr, - Some(self.key.span().source_text(p.source_text)), - )); + wrap!(p, func_expr, Function, { + parts.push(function::print_function( + p, + func_expr, + Some(self.key.span().source_text(p.source_text)), + )); + }); } } else { parts.push(format!(p, self.key)); @@ -1367,7 +1381,7 @@ impl<'a> Format<'a> for PropertyKey<'a> { impl<'a> Format<'a> for ArrowExpression<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - arrow_function::print_arrow_function(p, self) + wrap!(p, self, ArrowExpression, { arrow_function::print_arrow_function(p, self) }) } } @@ -1815,7 +1829,9 @@ impl<'a> Format<'a> for JSXFragment<'a> { impl<'a> Format<'a> for StaticBlock<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - array![p, ss!("static "), block::print_block(p, &self.body, None)] + wrap!(p, self, StaticBlock, { + array![p, ss!("static "), block::print_block(p, &self.body, None)] + }) } } diff --git a/crates/oxc_prettier/src/lib.rs b/crates/oxc_prettier/src/lib.rs index 937ab65d2..7542babb8 100644 --- a/crates/oxc_prettier/src/lib.rs +++ b/crates/oxc_prettier/src/lib.rs @@ -13,12 +13,12 @@ mod printer; use std::{iter::Peekable, vec}; -use doc::Doc; use oxc_allocator::Allocator; -use oxc_ast::{ast::Program, CommentKind, Trivias}; +use oxc_ast::{ast::Program, AstKind, CommentKind, Trivias}; + +use crate::{doc::Doc, format::Format, printer::Printer}; pub use crate::options::{ArrowParens, EndOfLine, PrettierOptions, QuoteProps, TrailingComma}; -use crate::{format::Format, printer::Printer}; pub struct Prettier<'a> { allocator: &'a Allocator, @@ -29,6 +29,10 @@ pub struct Prettier<'a> { /// A stack of comments that will be carefully placed in the right places. trivias: Peekable>, + + /// The stack of AST Nodes + /// See + nodes: Vec>, } impl<'a> Prettier<'a> { @@ -38,8 +42,13 @@ impl<'a> Prettier<'a> { trivias: Trivias, options: PrettierOptions, ) -> Self { - let trivias = trivias.into_iter().peekable(); - Self { allocator, source_text, options, trivias } + Self { + allocator, + source_text, + options, + trivias: trivias.into_iter().peekable(), + nodes: vec![], + } } pub fn build(mut self, program: &Program<'a>) -> String { @@ -51,21 +60,46 @@ impl<'a> Prettier<'a> { program.format(&mut self) } - pub(crate) fn should_print_es5_comma(&self) -> bool { + fn enter_node(&mut self, kind: AstKind<'a>) { + self.nodes.push(kind); + } + + fn leave_node(&mut self) { + self.nodes.pop(); + } + + fn current_kind(&self) -> AstKind<'a> { + self.nodes[self.nodes.len() - 1] + } + + fn parent_kind(&self) -> AstKind<'a> { + self.nodes[self.nodes.len() - 2] + } + + /// A hack for erasing the lifetime requirement. + #[allow(clippy::unused_self)] + fn alloc(&self, t: &T) -> &'a T { + // SAFETY: + // This should be safe as long as `src` is an reference from the allocator. + // But honestly, I'm not really sure if this is safe. + unsafe { std::mem::transmute(t) } + } + + fn should_print_es5_comma(&self) -> bool { self.should_print_comma_impl(false) } #[allow(unused)] - pub(crate) fn should_print_all_comma(&self) -> bool { + fn should_print_all_comma(&self) -> bool { self.should_print_comma_impl(true) } - pub(crate) fn should_print_comma_impl(&self, level_all: bool) -> bool { + fn should_print_comma_impl(&self, level_all: bool) -> bool { let trailing_comma = self.options.trailing_comma; trailing_comma.is_all() || (trailing_comma.is_es5() && !level_all) } - pub(crate) fn is_next_line_empty(&self, end: u32) -> bool { + fn is_next_line_empty(&self, end: u32) -> bool { self.source_text[end as usize..].chars().nth(1).is_some_and(|c| c == '\n') } } diff --git a/crates/oxc_prettier/src/macros.rs b/crates/oxc_prettier/src/macros.rs index 636e695cb..ab85f855e 100644 --- a/crates/oxc_prettier/src/macros.rs +++ b/crates/oxc_prettier/src/macros.rs @@ -85,6 +85,16 @@ macro_rules! group { #[macro_export] macro_rules! if_break { ($p:ident, $s:expr) => {{ - Doc::IfBreak($p.alloc(Doc::Str($s))) + Doc::IfBreak($p.boxed(Doc::Str($s))) + }}; +} + +#[macro_export] +macro_rules! wrap { + ($p:ident, $self:expr, $kind:ident, $block:block) => {{ + $p.enter_node(AstKind::$kind($p.alloc($self))); + let doc = $block; + $p.leave_node(); + doc }}; } diff --git a/tasks/prettier_conformance/prettier.snap.md b/tasks/prettier_conformance/prettier.snap.md index 49b68733c..e1deb515e 100644 --- a/tasks/prettier_conformance/prettier.snap.md +++ b/tasks/prettier_conformance/prettier.snap.md @@ -1,4 +1,4 @@ -Compatibility: 105/838 (12.53%) +Compatibility: 108/838 (12.89%) # Failed @@ -616,9 +616,6 @@ Compatibility: 105/838 (12.53%) ### line-suffix-boundary * line-suffix-boundary/boundary.js -### logical-assignment -* logical-assignment/logical-assignment.js - ### logical_expressions * logical_expressions/issue-7024.js * logical_expressions/logical_expression_operators.js @@ -658,7 +655,6 @@ Compatibility: 105/838 (12.53%) ### module-blocks * module-blocks/comments.js * module-blocks/module-blocks.js -* module-blocks/non-module-blocks.js * module-blocks/range.js * module-blocks/worker.js @@ -765,9 +761,6 @@ Compatibility: 105/838 (12.53%) * objects/assignment-expression/object-property.js * objects/assignment-expression/object-value.js -### optional-catch-binding -* optional-catch-binding/optional_catch_binding.js - ### optional-chaining * optional-chaining/chaining.js * optional-chaining/comments.js