mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 20:32:10 +00:00
feat(prettier): add the basics of comment printing (#1313)
This commit is contained in:
parent
4967ca2cdb
commit
5f316626f6
11 changed files with 134 additions and 15 deletions
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
crates/oxc_prettier/src/comment.rs
Normal file
56
crates/oxc_prettier/src/comment.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue