diff --git a/crates/oxc_ast/src/trivia.rs b/crates/oxc_ast/src/trivia.rs index 857fe1e46..26620c42a 100644 --- a/crates/oxc_ast/src/trivia.rs +++ b/crates/oxc_ast/src/trivia.rs @@ -37,6 +37,16 @@ pub enum CommentKind { MultiLine, } +impl CommentKind { + pub fn is_single_line(self) -> bool { + matches!(self, Self::SingleLine) + } + + pub fn is_multi_line(self) -> bool { + matches!(self, Self::MultiLine) + } +} + impl Comment { pub fn new(end: u32, kind: CommentKind) -> Self { Self { kind, end } @@ -46,12 +56,16 @@ impl Comment { self.end } + pub fn kind(&self) -> CommentKind { + self.kind + } + pub fn is_single_line(self) -> bool { - matches!(self.kind, CommentKind::SingleLine) + self.kind.is_single_line() } pub fn is_multi_line(self) -> bool { - matches!(self.kind, CommentKind::MultiLine) + self.kind.is_multi_line() } } diff --git a/crates/oxc_cli/src/format/mod.rs b/crates/oxc_cli/src/format/mod.rs index ba6d6757f..64a217ca9 100644 --- a/crates/oxc_cli/src/format/mod.rs +++ b/crates/oxc_cli/src/format/mod.rs @@ -52,6 +52,7 @@ impl FormatRunner { let allocator = Allocator::default(); let source_type = SourceType::from_path(path).unwrap(); let ret = Parser::new(&allocator, &source_text, source_type).parse(); - let _ = Prettier::new(&allocator, PrettierOptions::default()).build(&ret.program); + let _ = Prettier::new(&allocator, &source_text, ret.trivias, PrettierOptions::default()) + .build(&ret.program); } } diff --git a/crates/oxc_prettier/Cargo.toml b/crates/oxc_prettier/Cargo.toml index 9c45ca4a5..679866fbc 100644 --- a/crates/oxc_prettier/Cargo.toml +++ b/crates/oxc_prettier/Cargo.toml @@ -19,6 +19,7 @@ doctest = false oxc_allocator = { workspace = true } oxc_ast = { workspace = true } oxc_syntax = { workspace = true } +oxc_span = { workspace = true } [dev-dependencies] oxc_parser = { workspace = true } diff --git a/crates/oxc_prettier/examples/prettier.rs b/crates/oxc_prettier/examples/prettier.rs index 5163d8f07..5742e3bac 100644 --- a/crates/oxc_prettier/examples/prettier.rs +++ b/crates/oxc_prettier/examples/prettier.rs @@ -18,6 +18,7 @@ fn main() { let allocator = Allocator::default(); let source_type = SourceType::from_path(path).unwrap(); let ret = Parser::new(&allocator, &source_text, source_type).parse(); - let output = Prettier::new(&allocator, PrettierOptions::default()).build(&ret.program); + let output = Prettier::new(&allocator, &source_text, ret.trivias, PrettierOptions::default()) + .build(&ret.program); println!("{output}"); } diff --git a/crates/oxc_prettier/src/comment.rs b/crates/oxc_prettier/src/comment.rs new file mode 100644 index 000000000..9f118357a --- /dev/null +++ b/crates/oxc_prettier/src/comment.rs @@ -0,0 +1,56 @@ +//! Comment helpers + +use oxc_ast::CommentKind; +use oxc_span::Span; + +use crate::{ + doc::{Doc, Separator}, + Prettier, +}; + +#[derive(Clone, Copy)] +#[allow(unused)] +pub enum CommentFlags { + /// Check comment is a leading comment + Leading, + /// Check comment is a trailing comment + Trailing, + /// Check comment is a dangling comment + Dangling, + /// Check comment is a block comment + Block, + /// Check comment is a line comment + Line, + /// Check comment is a `prettier-ignore` comment + PrettierIgnore, + /// Check comment is the first attached comment + First, + /// Check comment is the last attached comment + Last, +} + +impl<'a> Prettier<'a> { + #[allow(unused)] + pub(crate) fn has_comment(_span: Span, _flags: CommentFlags) -> bool { + false + } + + pub(crate) fn print_dangling_comments(&mut self, range: Span) -> Option> { + let mut parts = vec![]; + while let Some((start, end, kind)) = self.trivias.peek().copied() { + if end <= range.end { + parts.push(self.print_comment(start, end, kind)); + self.trivias.next(); + } else { + break; + } + } + (!parts.is_empty()).then(|| self.join(Separator::Hardline, parts)) + } + + fn print_comment(&self, start: u32, end: u32, kind: CommentKind) -> Doc<'a> { + let end_offset = if kind.is_multi_line() { 2 } else { 0 }; + let comment = Span::new(start - 2, end + end_offset).source_text(self.source_text); + Doc::Str(comment) + } +} diff --git a/crates/oxc_prettier/src/doc.rs b/crates/oxc_prettier/src/doc.rs index 289cf16d4..9c17bfb1e 100644 --- a/crates/oxc_prettier/src/doc.rs +++ b/crates/oxc_prettier/src/doc.rs @@ -47,15 +47,36 @@ impl<'a> Doc<'a> { } } +#[derive(Clone, Copy)] +#[allow(unused)] +pub enum Separator { + Softline, + Hardline, +} + /// Doc Builder impl<'a> Prettier<'a> { #[inline] - pub fn vec(&self) -> Vec<'a, T> { + pub(crate) fn vec(&self) -> Vec<'a, T> { Vec::new_in(self.allocator) } #[inline] - pub fn str(&self, s: &str) -> Doc<'a> { + pub(crate) fn str(&self, s: &str) -> Doc<'a> { Doc::Str(String::from_str_in(s, self.allocator).into_bump_str()) } + + pub(crate) fn join(&self, separator: Separator, docs: std::vec::Vec>) -> Doc<'a> { + let mut parts = self.vec(); + for (i, doc) in docs.into_iter().enumerate() { + if i != 0 { + parts.push(match separator { + Separator::Softline => Doc::Softline, + Separator::Hardline => Doc::Hardline, + }); + } + parts.push(doc); + } + Doc::Array(parts) + } } diff --git a/crates/oxc_prettier/src/format.rs b/crates/oxc_prettier/src/format.rs index 9ebfaacd0..ea75576af 100644 --- a/crates/oxc_prettier/src/format.rs +++ b/crates/oxc_prettier/src/format.rs @@ -30,6 +30,9 @@ impl<'a> Format<'a> for Program<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { let mut parts = p.vec(); parts.extend(self.body.iter().map(|stmt| stmt.format(p))); + if let Some(doc) = p.print_dangling_comments(self.span) { + parts.push(doc); + } Doc::Array(parts) } } diff --git a/crates/oxc_prettier/src/lib.rs b/crates/oxc_prettier/src/lib.rs index 76b8cc343..c353e3b8e 100644 --- a/crates/oxc_prettier/src/lib.rs +++ b/crates/oxc_prettier/src/lib.rs @@ -2,13 +2,19 @@ //! //! A port of +mod comment; mod doc; mod format; mod macros; mod printer; +use std::{ + iter::{Peekable, Rev}, + vec, +}; + use oxc_allocator::Allocator; -use oxc_ast::ast::Program; +use oxc_ast::{ast::Program, CommentKind, Trivias}; use crate::{format::Format, printer::Printer}; @@ -32,12 +38,23 @@ impl Default for PrettierOptions { pub struct Prettier<'a> { allocator: &'a Allocator, + source_text: &'a str, + options: PrettierOptions, + + /// A stack of comments that will be carefully placed in the right places. + trivias: Peekable>>, } impl<'a> Prettier<'a> { - pub fn new(allocator: &'a Allocator, options: PrettierOptions) -> Self { - Self { allocator, options } + pub fn new( + allocator: &'a Allocator, + source_text: &'a str, + trivias: Trivias, + options: PrettierOptions, + ) -> Self { + let trivias = trivias.into_iter().rev().peekable(); + Self { allocator, source_text, options, trivias } } pub fn build(mut self, program: &Program<'a>) -> String { diff --git a/crates/oxc_semantic/src/jsdoc/builder.rs b/crates/oxc_semantic/src/jsdoc/builder.rs index bc57e3378..7b291e13d 100644 --- a/crates/oxc_semantic/src/jsdoc/builder.rs +++ b/crates/oxc_semantic/src/jsdoc/builder.rs @@ -39,7 +39,7 @@ impl<'a> JSDocBuilder<'a> { fn find_jsdoc_comment(&self, span: Span) -> Option<&'a str> { let (start, comment) = self.trivias.comments().range(..span.start).next()?; - if comment.is_single_line() { + if comment.kind().is_single_line() { return None; } diff --git a/tasks/benchmark/benches/prettier.rs b/tasks/benchmark/benches/prettier.rs index dd39c3aa4..61e65f281 100644 --- a/tasks/benchmark/benches/prettier.rs +++ b/tasks/benchmark/benches/prettier.rs @@ -23,10 +23,14 @@ fn bench_prettier(criterion: &mut Criterion) { b.iter(|| { let allocator1 = Allocator::default(); let allocator2 = Allocator::default(); - let program = Parser::new(&allocator1, source_text, SourceType::default()) - .parse() - .program; - let _ = Prettier::new(&allocator2, PrettierOptions::default()).build(&program); + let ret = Parser::new(&allocator1, source_text, SourceType::default()).parse(); + let _ = Prettier::new( + &allocator2, + source_text, + ret.trivias, + PrettierOptions::default(), + ) + .build(&ret.program); }); }, ); diff --git a/tasks/prettier_conformance/src/lib.rs b/tasks/prettier_conformance/src/lib.rs index d82c64b62..911e8ea03 100644 --- a/tasks/prettier_conformance/src/lib.rs +++ b/tasks/prettier_conformance/src/lib.rs @@ -146,6 +146,7 @@ printWidth: 80 let allocator = Allocator::default(); let source_type = SourceType::from_path(path).unwrap(); let ret = Parser::new(&allocator, source_text, source_type).parse(); - Prettier::new(&allocator, PrettierOptions::default()).build(&ret.program) + Prettier::new(&allocator, source_text, ret.trivias, PrettierOptions::default()) + .build(&ret.program) } }