diff --git a/apps/oxlint/src-js/generated/deserialize.js b/apps/oxlint/src-js/generated/deserialize.js index eeb50e6b2d993..5d2f24a3bcf08 100644 --- a/apps/oxlint/src-js/generated/deserialize.js +++ b/apps/oxlint/src-js/generated/deserialize.js @@ -5651,6 +5651,8 @@ function deserializeCommentKind(pos) { return "Line"; case 1: return "Block"; + case 2: + return "Block"; default: throw Error(`Unexpected discriminant ${uint8[pos]} for CommentKind`); } diff --git a/crates/oxc_ast/src/ast/comment.rs b/crates/oxc_ast/src/ast/comment.rs index 541d1c3223b68..7a9846955fd86 100644 --- a/crates/oxc_ast/src/ast/comment.rs +++ b/crates/oxc_ast/src/ast/comment.rs @@ -17,6 +17,9 @@ pub enum CommentKind { Line = 0, /// Block comment Block = 1, + /// Multiline block comment (contains line breaks) + #[estree(rename = "Block")] + MultilineBlock = 2, } /// Information about a comment's position relative to a token. @@ -172,7 +175,9 @@ impl Comment { pub fn content_span(&self) -> Span { match self.kind { CommentKind::Line => Span::new(self.span.start + 2, self.span.end), - CommentKind::Block => Span::new(self.span.start + 2, self.span.end - 2), + CommentKind::Block | CommentKind::MultilineBlock => { + Span::new(self.span.start + 2, self.span.end - 2) + } } } @@ -185,7 +190,13 @@ impl Comment { /// Returns `true` if this is a block comment. #[inline] pub fn is_block(self) -> bool { - self.kind == CommentKind::Block + matches!(self.kind, CommentKind::Block | CommentKind::MultilineBlock) + } + + /// Returns `true` if this is a multi-line block comment. + #[inline] + pub fn is_multiline_block(self) -> bool { + self.kind == CommentKind::MultilineBlock } /// Returns `true` if this comment is before a token. diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index 427e169d561b0..ce933322421b6 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -3234,6 +3234,7 @@ impl ESTree for CommentKind { match self { Self::Line => JsonSafeString("Line").serialize(serializer), Self::Block => JsonSafeString("Block").serialize(serializer), + Self::MultilineBlock => JsonSafeString("Block").serialize(serializer), } } } diff --git a/crates/oxc_codegen/src/comment.rs b/crates/oxc_codegen/src/comment.rs index 289e5ed7a29ab..ef584036541f9 100644 --- a/crates/oxc_codegen/src/comment.rs +++ b/crates/oxc_codegen/src/comment.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use rustc_hash::{FxHashMap, FxHashSet}; use oxc_ast::{Comment, CommentKind, ast::Program}; -use oxc_syntax::line_terminator::{LineTerminatorSplitter, is_line_terminator}; +use oxc_syntax::line_terminator::LineTerminatorSplitter; use crate::{Codegen, LegalComment, options::CommentOptions}; @@ -128,10 +128,10 @@ impl Codegen<'_> { }; let comment_source = comment.span.source_text(source_text); match comment.kind { - CommentKind::Line => { + CommentKind::Line | CommentKind::Block => { self.print_str_escaping_script_close_tag(comment_source); } - CommentKind::Block => { + CommentKind::MultilineBlock => { for line in LineTerminatorSplitter::new(comment_source) { if !line.starts_with("/*") { self.print_indent(); @@ -163,7 +163,7 @@ impl Codegen<'_> { let source_text = program.source_text; for comment in program.comments.iter().filter(|c| c.is_legal()) { let mut text = Cow::Borrowed(comment.span.source_text(source_text)); - if comment.is_block() && text.contains(is_line_terminator) { + if comment.is_multiline_block() { let mut buffer = String::with_capacity(text.len()); // Print block comments with our own indentation. for line in LineTerminatorSplitter::new(&text) { diff --git a/crates/oxc_formatter/src/formatter/trivia.rs b/crates/oxc_formatter/src/formatter/trivia.rs index bc376f9d49155..ad8dafb89747b 100644 --- a/crates/oxc_formatter/src/formatter/trivia.rs +++ b/crates/oxc_formatter/src/formatter/trivia.rs @@ -117,28 +117,31 @@ impl<'a> Format<'a> for FormatLeadingComments<'a> { write!(f, comment); match comment.kind { - CommentKind::Block => match f.source_text().lines_after(comment.span.end) { - 0 => { - let should_nestle = - leading_comments_iter.peek().is_some_and(|next_comment| { - should_nestle_adjacent_doc_comments( - comment, - next_comment, - f.source_text(), - ) - }); - - write!(f, [maybe_space(!should_nestle)]); - } - 1 => { - if f.source_text().get_lines_before(comment.span, f.comments()) == 0 { - write!(f, [soft_line_break_or_space()]); - } else { - write!(f, [hard_line_break()]); + CommentKind::Block | CommentKind::MultilineBlock => { + match f.source_text().lines_after(comment.span.end) { + 0 => { + let should_nestle = + leading_comments_iter.peek().is_some_and(|next_comment| { + should_nestle_adjacent_doc_comments( + comment, + next_comment, + f.source_text(), + ) + }); + + write!(f, [maybe_space(!should_nestle)]); + } + 1 => { + if f.source_text().get_lines_before(comment.span, f.comments()) == 0 + { + write!(f, [soft_line_break_or_space()]); + } else { + write!(f, [hard_line_break()]); + } } + _ => write!(f, [empty_line()]), } - _ => write!(f, [empty_line()]), - }, + } CommentKind::Line => match f.source_text().lines_after(comment.span.end) { 0 | 1 => write!(f, [hard_line_break()]), _ => write!(f, [empty_line()]), diff --git a/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs b/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs index 2d4dd5de51140..7a78bdab0ace4 100644 --- a/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs +++ b/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs @@ -1,6 +1,5 @@ use cow_utils::CowUtils; use lazy_regex::Regex; -use oxc_ast::CommentKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; @@ -193,9 +192,7 @@ impl Rule for BanTsComment { if let Some(captures) = find_ts_comment_directive(raw, comm.is_line()) { // safe to unwrap, if capture success, it can always capture one of the four directives let (directive, description) = (captures.0, captures.1); - if CommentKind::Block == comm.kind - && (directive == "check" || directive == "nocheck") - { + if comm.is_block() && (directive == "check" || directive == "nocheck") { continue; } diff --git a/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs b/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs index d835632c98546..f17120a1bf3c5 100644 --- a/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs +++ b/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs @@ -174,7 +174,7 @@ fn check_member(member: &TSSignature, node: &AstNode<'_>, ctx: &LintContext<'_>) let single_line_comment: String = format!("//{comment}\n"); comments_vec.push(single_line_comment); } - CommentKind::Block => { + CommentKind::Block | CommentKind::MultilineBlock => { let multi_line_comment: String = format!("/*{comment}*/\n"); comments_vec.push(multi_line_comment); } diff --git a/crates/oxc_napi/src/lib.rs b/crates/oxc_napi/src/lib.rs index 6fe70318a5300..750ad1f4c963a 100644 --- a/crates/oxc_napi/src/lib.rs +++ b/crates/oxc_napi/src/lib.rs @@ -33,7 +33,7 @@ pub fn convert_utf8_to_utf16( Comment { r#type: match comment.kind { CommentKind::Line => String::from("Line"), - CommentKind::Block => String::from("Block"), + CommentKind::Block | CommentKind::MultilineBlock => String::from("Block"), }, value, start: span.start, diff --git a/crates/oxc_parser/src/lexer/comment.rs b/crates/oxc_parser/src/lexer/comment.rs index c2b7a140d9088..93bb886547781 100644 --- a/crates/oxc_parser/src/lexer/comment.rs +++ b/crates/oxc_parser/src/lexer/comment.rs @@ -1,5 +1,6 @@ use memchr::memmem::Finder; +use oxc_ast::CommentKind; use oxc_syntax::line_terminator::is_line_terminator; use crate::diagnostics; @@ -82,9 +83,13 @@ impl<'a> Lexer<'a> { /// Section 12.4 Multi Line Comment pub(super) fn skip_multi_line_comment(&mut self) -> Kind { // If `is_on_new_line` is already set, go directly to faster search which only looks for `*/` - if self.token.is_on_new_line() { - return self.skip_multi_line_comment_after_line_break(self.source.position()); - } + // We need to identify if comment contains line breaks or not + // (`CommentKind::Block` or `CommentKind::MultilineBlock`). + // So we have to use the loop below for the first line of the comment even if + // `Token`'s `is_on_new_line` flag is already set. + // If the loop finds a line break before end of the comment, we then switch to + // the faster `skip_multi_line_comment_after_line_break` which searches + // for the end of the comment using `memchr`. byte_search! { lexer: self, @@ -149,6 +154,7 @@ impl<'a> Lexer<'a> { self.trivia_builder.add_block_comment( self.token.start(), self.offset(), + CommentKind::Block, self.source.whole(), ); Kind::Skip @@ -170,6 +176,7 @@ impl<'a> Lexer<'a> { self.trivia_builder.add_block_comment( self.token.start(), self.offset(), + CommentKind::MultilineBlock, self.source.whole(), ); Kind::Skip diff --git a/crates/oxc_parser/src/lexer/trivia_builder.rs b/crates/oxc_parser/src/lexer/trivia_builder.rs index fb29d2dba23b7..a68ae1e4dd8c8 100644 --- a/crates/oxc_parser/src/lexer/trivia_builder.rs +++ b/crates/oxc_parser/src/lexer/trivia_builder.rs @@ -59,8 +59,14 @@ impl TriviaBuilder { self.add_comment(Comment::new(start, end, CommentKind::Line), source_text); } - pub fn add_block_comment(&mut self, start: u32, end: u32, source_text: &str) { - self.add_comment(Comment::new(start, end, CommentKind::Block), source_text); + pub fn add_block_comment( + &mut self, + start: u32, + end: u32, + kind: CommentKind, + source_text: &str, + ) { + self.add_comment(Comment::new(start, end, kind), source_text); } // For block comments only. This function is not called after line comments because the lexer skips @@ -425,7 +431,7 @@ token /* Trailing 1 */ let expected = vec![ Comment { span: Span::new(1, 13), - kind: CommentKind::Block, + kind: CommentKind::MultilineBlock, position: CommentPosition::Leading, attached_to: 28, newlines: CommentNewlines::Leading | CommentNewlines::Trailing, @@ -433,7 +439,7 @@ token /* Trailing 1 */ }, Comment { span: Span::new(14, 26), - kind: CommentKind::Block, + kind: CommentKind::MultilineBlock, position: CommentPosition::Leading, attached_to: 28, newlines: CommentNewlines::Leading | CommentNewlines::Trailing, diff --git a/napi/parser/generated/deserialize/js.js b/napi/parser/generated/deserialize/js.js index 665d39f7cfe51..a55851ca70bfb 100644 --- a/napi/parser/generated/deserialize/js.js +++ b/napi/parser/generated/deserialize/js.js @@ -4210,6 +4210,8 @@ function deserializeCommentKind(pos) { return "Line"; case 1: return "Block"; + case 2: + return "Block"; default: throw Error(`Unexpected discriminant ${uint8[pos]} for CommentKind`); } diff --git a/napi/parser/generated/deserialize/js_parent.js b/napi/parser/generated/deserialize/js_parent.js index c719d95a3e564..b5e417456deac 100644 --- a/napi/parser/generated/deserialize/js_parent.js +++ b/napi/parser/generated/deserialize/js_parent.js @@ -4943,6 +4943,8 @@ function deserializeCommentKind(pos) { return "Line"; case 1: return "Block"; + case 2: + return "Block"; default: throw Error(`Unexpected discriminant ${uint8[pos]} for CommentKind`); } diff --git a/napi/parser/generated/deserialize/js_range.js b/napi/parser/generated/deserialize/js_range.js index 7b946532e948a..0ee4434619a8d 100644 --- a/napi/parser/generated/deserialize/js_range.js +++ b/napi/parser/generated/deserialize/js_range.js @@ -4656,6 +4656,8 @@ function deserializeCommentKind(pos) { return "Line"; case 1: return "Block"; + case 2: + return "Block"; default: throw Error(`Unexpected discriminant ${uint8[pos]} for CommentKind`); } diff --git a/napi/parser/generated/deserialize/js_range_parent.js b/napi/parser/generated/deserialize/js_range_parent.js index c9547ede082a3..fada9f950a0be 100644 --- a/napi/parser/generated/deserialize/js_range_parent.js +++ b/napi/parser/generated/deserialize/js_range_parent.js @@ -5190,6 +5190,8 @@ function deserializeCommentKind(pos) { return "Line"; case 1: return "Block"; + case 2: + return "Block"; default: throw Error(`Unexpected discriminant ${uint8[pos]} for CommentKind`); } diff --git a/napi/parser/generated/deserialize/ts.js b/napi/parser/generated/deserialize/ts.js index 5e1eda6a9ba7b..982a94b7060b7 100644 --- a/napi/parser/generated/deserialize/ts.js +++ b/napi/parser/generated/deserialize/ts.js @@ -4463,6 +4463,8 @@ function deserializeCommentKind(pos) { return "Line"; case 1: return "Block"; + case 2: + return "Block"; default: throw Error(`Unexpected discriminant ${uint8[pos]} for CommentKind`); } diff --git a/napi/parser/generated/deserialize/ts_parent.js b/napi/parser/generated/deserialize/ts_parent.js index 778184ba59eae..49ac6cf63f6eb 100644 --- a/napi/parser/generated/deserialize/ts_parent.js +++ b/napi/parser/generated/deserialize/ts_parent.js @@ -5204,6 +5204,8 @@ function deserializeCommentKind(pos) { return "Line"; case 1: return "Block"; + case 2: + return "Block"; default: throw Error(`Unexpected discriminant ${uint8[pos]} for CommentKind`); } diff --git a/napi/parser/generated/deserialize/ts_range.js b/napi/parser/generated/deserialize/ts_range.js index 1a6083087f5bf..6a39bfa15a011 100644 --- a/napi/parser/generated/deserialize/ts_range.js +++ b/napi/parser/generated/deserialize/ts_range.js @@ -4908,6 +4908,8 @@ function deserializeCommentKind(pos) { return "Line"; case 1: return "Block"; + case 2: + return "Block"; default: throw Error(`Unexpected discriminant ${uint8[pos]} for CommentKind`); } diff --git a/napi/parser/generated/deserialize/ts_range_parent.js b/napi/parser/generated/deserialize/ts_range_parent.js index 4f3edd4da410a..145dfb2a649ae 100644 --- a/napi/parser/generated/deserialize/ts_range_parent.js +++ b/napi/parser/generated/deserialize/ts_range_parent.js @@ -5458,6 +5458,8 @@ function deserializeCommentKind(pos) { return "Line"; case 1: return "Block"; + case 2: + return "Block"; default: throw Error(`Unexpected discriminant ${uint8[pos]} for CommentKind`); } diff --git a/napi/parser/generated/lazy/constructors.js b/napi/parser/generated/lazy/constructors.js index d9dc7c1b75d30..38698a956e038 100644 --- a/napi/parser/generated/lazy/constructors.js +++ b/napi/parser/generated/lazy/constructors.js @@ -11743,6 +11743,8 @@ function constructCommentKind(pos, ast) { return "Line"; case 1: return "Block"; + case 2: + return "Block"; default: throw new Error(`Unexpected discriminant ${ast.buffer[pos]} for CommentKind`); }