feat(prettier): add the basics of comment printing (#1313)

This commit is contained in:
Boshen 2023-11-14 20:32:03 +08:00 committed by GitHub
parent 4967ca2cdb
commit 5f316626f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 134 additions and 15 deletions

View file

@ -37,6 +37,16 @@ pub enum CommentKind {
MultiLine, 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 { impl Comment {
pub fn new(end: u32, kind: CommentKind) -> Self { pub fn new(end: u32, kind: CommentKind) -> Self {
Self { kind, end } Self { kind, end }
@ -46,12 +56,16 @@ impl Comment {
self.end self.end
} }
pub fn kind(&self) -> CommentKind {
self.kind
}
pub fn is_single_line(self) -> bool { pub fn is_single_line(self) -> bool {
matches!(self.kind, CommentKind::SingleLine) self.kind.is_single_line()
} }
pub fn is_multi_line(self) -> bool { pub fn is_multi_line(self) -> bool {
matches!(self.kind, CommentKind::MultiLine) self.kind.is_multi_line()
} }
} }

View file

@ -52,6 +52,7 @@ impl FormatRunner {
let allocator = Allocator::default(); let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap(); let source_type = SourceType::from_path(path).unwrap();
let ret = Parser::new(&allocator, &source_text, source_type).parse(); 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);
} }
} }

View file

@ -19,6 +19,7 @@ doctest = false
oxc_allocator = { workspace = true } oxc_allocator = { workspace = true }
oxc_ast = { workspace = true } oxc_ast = { workspace = true }
oxc_syntax = { workspace = true } oxc_syntax = { workspace = true }
oxc_span = { workspace = true }
[dev-dependencies] [dev-dependencies]
oxc_parser = { workspace = true } oxc_parser = { workspace = true }

View file

@ -18,6 +18,7 @@ fn main() {
let allocator = Allocator::default(); let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap(); let source_type = SourceType::from_path(path).unwrap();
let ret = Parser::new(&allocator, &source_text, source_type).parse(); 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}"); println!("{output}");
} }

View file

@ -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<Doc<'a>> {
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)
}
}

View file

@ -47,15 +47,36 @@ impl<'a> Doc<'a> {
} }
} }
#[derive(Clone, Copy)]
#[allow(unused)]
pub enum Separator {
Softline,
Hardline,
}
/// Doc Builder /// Doc Builder
impl<'a> Prettier<'a> { impl<'a> Prettier<'a> {
#[inline] #[inline]
pub fn vec<T>(&self) -> Vec<'a, T> { pub(crate) fn vec<T>(&self) -> Vec<'a, T> {
Vec::new_in(self.allocator) Vec::new_in(self.allocator)
} }
#[inline] #[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()) 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>>) -> 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)
}
} }

View file

@ -30,6 +30,9 @@ impl<'a> Format<'a> for Program<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
let mut parts = p.vec(); let mut parts = p.vec();
parts.extend(self.body.iter().map(|stmt| stmt.format(p))); 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) Doc::Array(parts)
} }
} }

View file

@ -2,13 +2,19 @@
//! //!
//! A port of <https://github.com/prettier/prettier> //! A port of <https://github.com/prettier/prettier>
mod comment;
mod doc; mod doc;
mod format; mod format;
mod macros; mod macros;
mod printer; mod printer;
use std::{
iter::{Peekable, Rev},
vec,
};
use oxc_allocator::Allocator; use oxc_allocator::Allocator;
use oxc_ast::ast::Program; use oxc_ast::{ast::Program, CommentKind, Trivias};
use crate::{format::Format, printer::Printer}; use crate::{format::Format, printer::Printer};
@ -32,12 +38,23 @@ impl Default for PrettierOptions {
pub struct Prettier<'a> { pub struct Prettier<'a> {
allocator: &'a Allocator, allocator: &'a Allocator,
source_text: &'a str,
options: PrettierOptions, options: PrettierOptions,
/// A stack of comments that will be carefully placed in the right places.
trivias: Peekable<Rev<vec::IntoIter<(u32, u32, CommentKind)>>>,
} }
impl<'a> Prettier<'a> { impl<'a> Prettier<'a> {
pub fn new(allocator: &'a Allocator, options: PrettierOptions) -> Self { pub fn new(
Self { allocator, options } 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 { pub fn build(mut self, program: &Program<'a>) -> String {

View file

@ -39,7 +39,7 @@ impl<'a> JSDocBuilder<'a> {
fn find_jsdoc_comment(&self, span: Span) -> Option<&'a str> { fn find_jsdoc_comment(&self, span: Span) -> Option<&'a str> {
let (start, comment) = self.trivias.comments().range(..span.start).next()?; let (start, comment) = self.trivias.comments().range(..span.start).next()?;
if comment.is_single_line() { if comment.kind().is_single_line() {
return None; return None;
} }

View file

@ -23,10 +23,14 @@ fn bench_prettier(criterion: &mut Criterion) {
b.iter(|| { b.iter(|| {
let allocator1 = Allocator::default(); let allocator1 = Allocator::default();
let allocator2 = Allocator::default(); let allocator2 = Allocator::default();
let program = Parser::new(&allocator1, source_text, SourceType::default()) let ret = Parser::new(&allocator1, source_text, SourceType::default()).parse();
.parse() let _ = Prettier::new(
.program; &allocator2,
let _ = Prettier::new(&allocator2, PrettierOptions::default()).build(&program); source_text,
ret.trivias,
PrettierOptions::default(),
)
.build(&ret.program);
}); });
}, },
); );

View file

@ -146,6 +146,7 @@ printWidth: 80
let allocator = Allocator::default(); let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap(); let source_type = SourceType::from_path(path).unwrap();
let ret = Parser::new(&allocator, source_text, source_type).parse(); 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)
} }
} }