diff --git a/crates/oxc_ast/src/ast/comment.rs b/crates/oxc_ast/src/ast/comment.rs index e142fec8e98ff..7bf3d20fe72a8 100644 --- a/crates/oxc_ast/src/ast/comment.rs +++ b/crates/oxc_ast/src/ast/comment.rs @@ -49,15 +49,14 @@ pub enum CommentAnnotation { #[default] None = 0, - /// `/** jsdoc */` - /// - Jsdoc = 1, - /// Legal Comment /// e.g. `/* @license */`, `/* @preserve */`, or starts with `//!` or `/*!`. - /// /// - Legal = 2, + Legal = 1, + + /// `/** jsdoc */` + /// + Jsdoc = 2, /// `/* #__PURE__ */` /// diff --git a/crates/oxc_codegen/src/comment.rs b/crates/oxc_codegen/src/comment.rs index f9bef059aaec7..26475fc969623 100644 --- a/crates/oxc_codegen/src/comment.rs +++ b/crates/oxc_codegen/src/comment.rs @@ -1,7 +1,6 @@ -use oxc_span::{GetSpan, Span}; use rustc_hash::FxHashMap; -use oxc_ast::{Comment, CommentKind, ast::Argument}; +use oxc_ast::{Comment, CommentKind}; use oxc_syntax::identifier::is_line_terminator; use crate::{Codegen, LegalComment}; @@ -10,7 +9,12 @@ pub type CommentsMap = FxHashMap>; impl Codegen<'_> { pub(crate) fn build_comments(&mut self, comments: &[Comment]) { - self.comments.reserve(comments.len()); + if !self.options.comments + && self.options.legal_comments.is_none() + && !self.options.annotation_comments + { + return; + } let move_legal_comments = { let legal_comments = &self.options.legal_comments; matches!( @@ -23,10 +27,25 @@ impl Codegen<'_> { if comment.is_pure() || comment.is_no_side_effects() { continue; } - if comment.is_legal() && move_legal_comments { - self.legal_comments.push(*comment); + let mut add = false; + if comment.is_legal() { + if move_legal_comments { + self.legal_comments.push(*comment); + } else if self.options.print_legal_comment() { + add = true; + } + } else if comment.is_leading() { + if comment.is_annotation() { + if self.options.print_annotation_comment() { + add = true; + } + } else if self.options.print_normal_comment() { + add = true; + } + } + if add { + self.comments.entry(comment.attached_to).or_default().push(*comment); } - self.comments.entry(comment.attached_to).or_default().push(*comment); } } @@ -34,80 +53,31 @@ impl Codegen<'_> { self.comments.contains_key(&start) } - pub(crate) fn contains_comment_in_call_like_expression( - &self, - span: Span, - arguments: &[Argument<'_>], - ) -> (bool, bool) { - let has_comment_before_right_paren = - self.print_annotation_comment && span.end > 0 && self.has_comment(span.end - 1); - - let has_comment = has_comment_before_right_paren - || self.print_annotation_comment - && arguments.iter().any(|item| self.has_comment(item.span().start)); - - (has_comment, has_comment_before_right_paren) - } - - /// Whether to keep leading comments. - fn should_keep_leading_comment(comment: &Comment) -> bool { - comment.preceded_by_newline && comment.is_annotation() - } - pub(crate) fn print_leading_comments(&mut self, start: u32) { - if !self.print_any_comment { - return; + if let Some(comments) = self.comments.remove(&start) { + self.print_comments(&comments); } - let Some(comments) = self.comments.remove(&start) else { - return; - }; - let comments = - comments.into_iter().filter(Self::should_keep_leading_comment).collect::>(); - self.print_comments(&comments); } pub(crate) fn get_statement_comments(&mut self, start: u32) -> Option> { - let comments = self.comments.remove(&start)?; - - let mut leading_comments = vec![]; - - for comment in comments { - if comment.is_legal() { - match &self.options.legal_comments { - LegalComment::None if self.options.comments => { - leading_comments.push(comment); - continue; - } - LegalComment::Inline => { - leading_comments.push(comment); - continue; - } - LegalComment::Eof | LegalComment::Linked(_) | LegalComment::External => { - /* noop, handled by `build_comments`. */ - continue; - } - LegalComment::None => {} - } - } - if Self::should_keep_leading_comment(&comment) { - leading_comments.push(comment); - } + if self.comments.is_empty() { + return None; } - - Some(leading_comments) + self.comments.remove(&start) } /// A statement comment also includes legal comments #[inline] pub(crate) fn print_statement_comments(&mut self, start: u32) { - if self.print_any_comment { - if let Some(comments) = self.get_statement_comments(start) { - self.print_comments(&comments); - } + if let Some(comments) = self.get_statement_comments(start) { + self.print_comments(&comments); } } pub(crate) fn print_expr_comments(&mut self, start: u32) -> bool { + if self.comments.is_empty() { + return false; + } let Some(comments) = self.comments.remove(&start) else { return false }; for comment in &comments { diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 4b4fce99a92b8..2da3f496a0d58 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -49,13 +49,8 @@ impl Gen for Program<'_> { if let Some(hashbang) = &self.hashbang { hashbang.print(p, ctx); } - for directive in &self.directives { - directive.print(p, ctx); - } - for stmt in &self.body { - stmt.print(p, ctx); - p.print_semicolon_if_needed(); - } + p.print_directives_and_statements(&self.directives, &self.body, ctx); + p.print_semicolon_if_needed(); // Print trailing statement comments. p.print_statement_comments(self.span.end); } @@ -142,7 +137,7 @@ impl Gen for Statement<'_> { } Self::FunctionDeclaration(decl) => { p.print_statement_comments(decl.span.start); - if decl.pure && p.print_annotation_comment { + if decl.pure && p.options.print_annotation_comment() { p.print_indent(); p.print_str(NO_SIDE_EFFECTS_NEW_LINE_COMMENT); } @@ -742,27 +737,19 @@ impl Gen for Function<'_> { impl Gen for FunctionBody<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { let span_end = self.span.end; - let comments_at_end = if p.print_any_comment && span_end > 0 { - p.get_statement_comments(span_end - 1) - } else { - None - }; - let is_empty = if self.is_empty() { - comments_at_end.is_none() || comments_at_end.as_ref().is_some_and(Vec::is_empty) + let comments_at_end = + if span_end > 0 { p.get_statement_comments(span_end - 1) } else { None }; + let single_line = if self.is_empty() { + comments_at_end.as_ref().is_none_or(|comments| comments.iter().all(|c| c.is_block())) } else { false }; - p.print_curly_braces(self.span, is_empty, |p| { - for directive in &self.directives { - directive.print(p, ctx); - } - for stmt in &self.statements { - p.print_semicolon_if_needed(); - stmt.print(p, ctx); - } + p.print_curly_braces(self.span, single_line, |p| { + p.print_directives_and_statements(&self.directives, &self.statements, ctx); // Print trailing statement comments. if let Some(comments) = comments_at_end { p.print_comments(&comments); + p.print_next_indent_as_space = false; } }); p.needs_semicolon = false; @@ -953,7 +940,7 @@ impl Gen for ExportNamedDeclaration<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { p.print_statement_comments(self.span.start); if let Some(Declaration::FunctionDeclaration(func)) = &self.declaration { - if func.pure && p.print_annotation_comment { + if func.pure && p.options.print_annotation_comment() { p.print_str(NO_SIDE_EFFECTS_NEW_LINE_COMMENT); } } @@ -1100,7 +1087,7 @@ impl Gen for ExportDefaultDeclaration<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { p.print_statement_comments(self.span.start); if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = &self.declaration { - if func.pure && p.print_annotation_comment { + if func.pure && p.options.print_annotation_comment() { p.print_str(NO_SIDE_EFFECTS_NEW_LINE_COMMENT); } } @@ -1149,13 +1136,13 @@ impl GenExpr for Expression<'_> { Self::ArrayExpression(expr) => expr.print(p, ctx), Self::ObjectExpression(expr) => expr.print_expr(p, precedence, ctx), Self::FunctionExpression(func) => { - if func.pure && p.print_annotation_comment { + if func.pure && p.options.print_annotation_comment() { p.print_str(NO_SIDE_EFFECTS_COMMENT); } func.print(p, ctx); } Self::ArrowFunctionExpression(func) => { - if func.pure && p.print_annotation_comment { + if func.pure && p.options.print_annotation_comment() { p.print_str(NO_SIDE_EFFECTS_COMMENT); } func.print_expr(p, precedence, ctx); @@ -1398,7 +1385,7 @@ impl GenExpr for CallExpression<'_> { let is_statement = p.start_of_stmt == p.code_len(); let is_export_default = p.start_of_default_export == p.code_len(); let mut wrap = precedence >= Precedence::New || ctx.intersects(Context::FORBID_CALL); - let pure = self.pure && p.print_annotation_comment; + let pure = self.pure && p.options.print_annotation_comment(); if precedence >= Precedence::Postfix && pure { wrap = true; } @@ -1420,22 +1407,7 @@ impl GenExpr for CallExpression<'_> { if let Some(type_parameters) = &self.type_arguments { type_parameters.print(p, ctx); } - p.print_ascii_byte(b'('); - - let (has_comment, has_comment_before_right_paren) = - p.contains_comment_in_call_like_expression(self.span, self.arguments.as_slice()); - if has_comment { - p.indent(); - p.print_list_with_comments(self.arguments.as_slice(), ctx); - // Handle `/* comment */);` - if !has_comment_before_right_paren || !p.print_expr_comments(self.span.end - 1) { - p.print_soft_newline(); - } - p.dedent(); - } else { - p.print_list(&self.arguments, ctx); - } - p.print_ascii_byte(b')'); + p.print_arguments(self.span, &self.arguments, ctx); p.add_source_mapping_end(self.span); }); } @@ -2167,7 +2139,7 @@ impl GenExpr for ChainExpression<'_> { impl GenExpr for NewExpression<'_> { fn gen_expr(&self, p: &mut Codegen, precedence: Precedence, ctx: Context) { let mut wrap = precedence >= self.precedence(); - let pure = self.pure && p.print_annotation_comment; + let pure = self.pure && p.options.print_annotation_comment(); if precedence >= Precedence::Postfix && pure { wrap = true; } @@ -2184,22 +2156,7 @@ impl GenExpr for NewExpression<'_> { // Omit the "()" when minifying, but only when safe to do so if !p.options.minify || !self.arguments.is_empty() || precedence >= Precedence::Postfix { - p.print_ascii_byte(b'('); - let (has_comment, has_comment_before_right_paren) = p - .contains_comment_in_call_like_expression(self.span, self.arguments.as_slice()); - if has_comment { - p.indent(); - p.print_list_with_comments(self.arguments.as_slice(), ctx); - // Handle `/* comment */);` - if !has_comment_before_right_paren || !p.print_expr_comments(self.span.end - 1) - { - p.print_soft_newline(); - } - p.dedent(); - } else { - p.print_list(&self.arguments, ctx); - } - p.print_ascii_byte(b')'); + p.print_arguments(self.span, &self.arguments, ctx); } }); } @@ -3624,13 +3581,7 @@ impl Gen for TSModuleBlock<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { let is_empty = self.directives.is_empty() && self.body.is_empty(); p.print_curly_braces(self.span, is_empty, |p| { - for directive in &self.directives { - directive.print(p, ctx); - } - for stmt in &self.body { - p.print_semicolon_if_needed(); - stmt.print(p, ctx); - } + p.print_directives_and_statements(&self.directives, &self.body, ctx); }); p.needs_semicolon = false; } @@ -3809,21 +3760,7 @@ impl GenExpr for V8IntrinsicExpression<'_> { p.add_source_mapping(self.span); p.print_ascii_byte(b'%'); self.name.print(p, Context::empty()); - p.print_ascii_byte(b'('); - let (has_comment, has_comment_before_right_paren) = - p.contains_comment_in_call_like_expression(self.span, self.arguments.as_slice()); - if has_comment { - p.indent(); - p.print_list_with_comments(self.arguments.as_slice(), ctx); - // Handle `/* comment */);` - if !has_comment_before_right_paren || !p.print_expr_comments(self.span.end - 1) { - p.print_soft_newline(); - } - p.dedent(); - } else { - p.print_list(&self.arguments, ctx); - } - p.print_ascii_byte(b')'); + p.print_arguments(self.span, &self.arguments, ctx); p.add_source_mapping_end(self.span); }); } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index 842ed8a22e41a..02e211a9a3356 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -15,10 +15,7 @@ mod str; use std::borrow::Cow; -use oxc_ast::ast::{ - Argument, BindingIdentifier, BlockStatement, Comment, Expression, IdentifierReference, Program, - Statement, -}; +use oxc_ast::ast::*; use oxc_data_structures::{code_buffer::CodeBuffer, stack::Stack}; use oxc_semantic::Scoping; use oxc_span::{GetSpan, SPAN, Span}; @@ -107,11 +104,6 @@ pub struct Codegen<'a> { /// Fast path for [CodegenOptions::single_quote] quote: Quote, - /// Fast path for if print comments - print_any_comment: bool, - print_legal_comment: bool, - print_annotation_comment: bool, - // Builders comments: CommentsMap, @@ -146,9 +138,6 @@ impl<'a> Codegen<'a> { #[must_use] pub fn new() -> Self { let options = CodegenOptions::default(); - let print_any_comment = options.print_any_comment(); - let print_legal_comment = options.print_legal_comment(); - let print_annotation_comment = options.print_annotation_comment(); Self { options, source_text: None, @@ -167,9 +156,6 @@ impl<'a> Codegen<'a> { is_jsx: false, indent: 0, quote: Quote::Double, - print_any_comment, - print_legal_comment, - print_annotation_comment, comments: CommentsMap::default(), legal_comments: vec![], sourcemap_builder: None, @@ -180,9 +166,6 @@ impl<'a> Codegen<'a> { #[must_use] pub fn with_options(mut self, options: CodegenOptions) -> Self { self.quote = if options.single_quote { Quote::Single } else { Quote::Double }; - self.print_any_comment = options.print_any_comment(); - self.print_legal_comment = options.print_legal_comment(); - self.print_annotation_comment = options.print_annotation_comment(); self.options = options; self } @@ -211,14 +194,7 @@ impl<'a> Codegen<'a> { self.quote = if self.options.single_quote { Quote::Single } else { Quote::Double }; self.source_text = Some(program.source_text); self.code.reserve(program.source_text.len()); - - if self.print_any_comment { - if program.comments.is_empty() { - self.print_any_comment = false; - } else { - self.build_comments(&program.comments); - } - } + self.build_comments(&program.comments); if let Some(path) = &self.options.source_map_path { self.sourcemap_builder = Some(SourcemapBuilder::new(path, program.source_text)); } @@ -469,6 +445,44 @@ impl<'a> Codegen<'a> { self.needs_semicolon = false; } + fn print_directives_and_statements( + &mut self, + directives: &[Directive<'_>], + stmts: &[Statement<'_>], + ctx: Context, + ) { + for directive in directives { + directive.print(self, ctx); + } + let Some((first, rest)) = stmts.split_first() else { + return; + }; + + // Ensure first string literal is not a directive. + let mut first_needs_parens = false; + if directives.is_empty() && !self.options.minify { + if let Statement::ExpressionStatement(s) = first { + let s = s.expression.without_parentheses(); + if matches!(s, Expression::StringLiteral(_)) { + first_needs_parens = true; + self.print_ascii_byte(b'('); + s.print_expr(self, Precedence::Lowest, ctx); + self.print_ascii_byte(b')'); + self.print_semicolon_after_statement(); + } + } + } + + if !first_needs_parens { + first.print(self, ctx); + } + + for stmt in rest { + self.print_semicolon_if_needed(); + stmt.print(self, ctx); + } + } + #[inline] fn print_list(&mut self, items: &[T], ctx: Context) { let Some((first, rest)) = items.split_first() else { @@ -482,6 +496,44 @@ impl<'a> Codegen<'a> { } } + #[inline] + fn print_expressions(&mut self, items: &[T], precedence: Precedence, ctx: Context) { + let Some((first, rest)) = items.split_first() else { + return; + }; + first.print_expr(self, precedence, ctx); + for item in rest { + self.print_comma(); + self.print_soft_space(); + item.print_expr(self, precedence, ctx); + } + } + + fn print_arguments(&mut self, span: Span, arguments: &[Argument<'_>], ctx: Context) { + self.print_ascii_byte(b'('); + + let has_comment_before_right_paren = span.end > 0 && self.has_comment(span.end - 1); + + let has_comment = has_comment_before_right_paren + || arguments.iter().any(|item| self.has_comment(item.span().start)); + + if has_comment { + self.indent(); + self.print_list_with_comments(arguments, ctx); + // Handle `/* comment */);` + if !has_comment_before_right_paren + || (span.end > 0 && !self.print_expr_comments(span.end - 1)) + { + self.print_soft_newline(); + } + self.dedent(); + self.print_indent(); + } else { + self.print_list(arguments, ctx); + } + self.print_ascii_byte(b')'); + } + fn print_list_with_comments(&mut self, items: &[Argument<'_>], ctx: Context) { let Some((first, rest)) = items.split_first() else { return; @@ -505,19 +557,6 @@ impl<'a> Codegen<'a> { } } - #[inline] - fn print_expressions(&mut self, items: &[T], precedence: Precedence, ctx: Context) { - let Some((first, rest)) = items.split_first() else { - return; - }; - first.print_expr(self, precedence, ctx); - for item in rest { - self.print_comma(); - self.print_soft_space(); - item.print_expr(self, precedence, ctx); - } - } - fn get_identifier_reference_name(&self, reference: &IdentifierReference<'a>) -> &'a str { if let Some(scoping) = &self.scoping { if let Some(reference_id) = reference.reference_id.get() { diff --git a/crates/oxc_codegen/src/options.rs b/crates/oxc_codegen/src/options.rs index 9f1b1f5be5eca..fd0ac0bf18d6e 100644 --- a/crates/oxc_codegen/src/options.rs +++ b/crates/oxc_codegen/src/options.rs @@ -13,19 +13,30 @@ pub struct CodegenOptions { /// Default is `false`. pub minify: bool, - /// Print all comments? + /// Print normal comments? + /// + /// At present, only some leading comments are preserved. + /// + /// Does not control legal and annotation comments. /// /// Default is `true`. pub comments: bool, - /// Print annotation comments, e.g. `/* #__PURE__ */` and `/* #__NO_SIDE_EFFECTS__ */`. + /// Print annotation comments. + /// + /// * jsdoc: `/** jsdoc */` + /// * pure: `/* #__PURE__ */` and `/* #__NO_SIDE_EFFECTS__ */` + /// * webpack: `/* webpackChunkName */` + /// * vite: `/* @vite-ignore */` + /// * coverage: `v8 ignore`, `c8 ignore`, `node:coverage`, `istanbul ignore` /// /// Default is `true`. pub annotation_comments: bool, /// Print legal comments. /// - /// + /// * starts with `//!` or `/*!`. + /// * contains `/* @license */` or `/* @preserve */` /// /// Default is [LegalComment::Inline]. pub legal_comments: LegalComment, @@ -64,16 +75,19 @@ impl CodegenOptions { } } - pub(crate) fn print_any_comment(&self) -> bool { + #[inline] + pub(crate) fn print_normal_comment(&self) -> bool { self.comments } + #[inline] pub(crate) fn print_legal_comment(&self) -> bool { - self.comments || !self.legal_comments.is_none() + self.legal_comments.is_inline() } + #[inline] pub(crate) fn print_annotation_comment(&self) -> bool { - self.comments || self.annotation_comments + self.annotation_comments } } diff --git a/crates/oxc_codegen/tests/integration/comments.rs b/crates/oxc_codegen/tests/integration/comments.rs index 43c6ad1aa7bf7..e96ac48e8b787 100644 --- a/crates/oxc_codegen/tests/integration/comments.rs +++ b/crates/oxc_codegen/tests/integration/comments.rs @@ -457,3 +457,64 @@ delete /* @__PURE__ */ (() => {})();", snapshot("pure_comments", &cases); } } + +pub mod options { + use oxc_codegen::{CodegenOptions, LegalComment}; + + use crate::codegen_options; + + #[test] + fn test() { + let code = " +//! Top Legal Comment +function foo() { + /** JSDoc Comment */ + function bar() { + /* #__PURE__ */ x(); + } + function baz() { + //! Function Legal Comment + } + x(/* Normal Comment */); + x(/** Call Expression Annotation Comment */ token); +}"; + + for comments in [true, false] { + for annotation in [true, false] { + for legal in [LegalComment::Inline, LegalComment::Eof, LegalComment::None] { + let options = CodegenOptions { + comments, + annotation_comments: annotation, + legal_comments: legal.clone(), + ..CodegenOptions::default() + }; + let printed = codegen_options(code, &options).code; + + if comments { + assert!(printed.contains("Normal Comment")); + } else { + assert!(!printed.contains("Normal Comment")); + } + + if annotation { + assert!(printed.contains("JSDoc Comment")); + assert!(printed.contains("__PURE__")); + assert!(printed.contains("Call Expression Annotation Comment")); + } else { + assert!(!printed.contains("JSDoc Comment")); + assert!(!printed.contains("__PURE__")); + assert!(!printed.contains("Call Expression Annotation Comment")); + } + + if legal.is_none() { + assert!(!printed.contains("Top Legal Comment")); + assert!(!printed.contains("Function Legal Comment")); + } else { + assert!(printed.contains("Top Legal Comment")); + assert!(printed.contains("Function Legal Comment")); + } + } + } + } + } +} diff --git a/crates/oxc_codegen/tests/integration/main.rs b/crates/oxc_codegen/tests/integration/main.rs index 03da1970a92b3..55b849c172c27 100644 --- a/crates/oxc_codegen/tests/integration/main.rs +++ b/crates/oxc_codegen/tests/integration/main.rs @@ -10,10 +10,12 @@ use oxc_codegen::{Codegen, CodegenOptions, CodegenReturn}; use oxc_parser::Parser; use oxc_span::SourceType; +#[track_caller] pub fn codegen(source_text: &str) -> String { codegen_options(source_text, &CodegenOptions::default()).code } +#[track_caller] pub fn codegen_options(source_text: &str, options: &CodegenOptions) -> CodegenReturn { let allocator = Allocator::default(); let source_type = SourceType::ts(); @@ -23,10 +25,12 @@ pub fn codegen_options(source_text: &str, options: &CodegenOptions) -> CodegenRe Codegen::new().with_options(options).build(&ret.program) } +#[track_caller] pub fn snapshot(name: &str, cases: &[&str]) { snapshot_options(name, cases, &CodegenOptions::default()); } +#[track_caller] pub fn snapshot_options(name: &str, cases: &[&str], options: &CodegenOptions) { use std::fmt::Write; diff --git a/crates/oxc_codegen/tests/integration/snapshots/jsodc.snap b/crates/oxc_codegen/tests/integration/snapshots/jsodc.snap index 22ae3612397cc..1fd5cd51d6d6a 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/jsodc.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/jsodc.snap @@ -172,6 +172,7 @@ this.Book = function(title) { /** The title of the book. */ this.title = title; }; +// https://github.com/oxc-project/oxc/issues/6006 export enum DefinitionKind { /** * Definition is a referenced variable. diff --git a/crates/oxc_codegen/tests/integration/snapshots/legal_eof_comments.snap b/crates/oxc_codegen/tests/integration/snapshots/legal_eof_comments.snap index 5949430f9a903..882b8431b32ce 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/legal_eof_comments.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/legal_eof_comments.snap @@ -100,10 +100,10 @@ function foo() { ---------- function foo() { (() => { - /** - * @preserve - */ - })(); + /** + * @preserve + */ +})(); /** * @preserve */ diff --git a/crates/oxc_codegen/tests/integration/snapshots/legal_inline_comments.snap b/crates/oxc_codegen/tests/integration/snapshots/legal_inline_comments.snap index 5b0e6df19c524..cf21f7bf039a7 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/legal_inline_comments.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/legal_inline_comments.snap @@ -92,10 +92,10 @@ function foo() { ---------- function foo() { (() => { - /** - * @preserve - */ - })(); + /** + * @preserve + */ +})(); /** * @preserve */ diff --git a/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap b/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap index d5f0f6b6d48d5..4de133c25a30f 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap @@ -85,10 +85,10 @@ function foo() { ---------- function foo() { (() => { - /** - * @preserve - */ - })(); + /** + * @preserve + */ +})(); /** * @preserve */ diff --git a/crates/oxc_codegen/tests/integration/snapshots/ts.snap b/crates/oxc_codegen/tests/integration/snapshots/ts.snap index 952a5584e00dc..5ecabfcf3cb25 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/ts.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/ts.snap @@ -307,9 +307,9 @@ import 'module-name'; import {} from "mod"; export let name1, name2; export const name3 = 1, name4 = 2; -export function functionName() {} +export function functionName() {/* … */} export class ClassName {} -export function* generatorFunctionName() {} +export function* generatorFunctionName() {/* … */} export const { name5, name2: bar } = o; export const [name6, name7] = array; export { name8, name81 }; diff --git a/crates/oxc_codegen/tests/integration/ts.rs b/crates/oxc_codegen/tests/integration/ts.rs index 5f571e3dcdf11..6b0ff75024e81 100644 --- a/crates/oxc_codegen/tests/integration/ts.rs +++ b/crates/oxc_codegen/tests/integration/ts.rs @@ -121,9 +121,5 @@ export import b = require("b"); ]; snapshot("ts", &cases); - snapshot_options( - "minify", - &cases, - &CodegenOptions { minify: true, ..CodegenOptions::default() }, - ); + snapshot_options("minify", &cases, &CodegenOptions::minify()); } diff --git a/crates/oxc_isolated_declarations/tests/snapshots/async-function.snap b/crates/oxc_isolated_declarations/tests/snapshots/async-function.snap index 6483f14c6221a..392e6cd3214ad 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/async-function.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/async-function.snap @@ -5,11 +5,14 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/async-function.ts ``` ==================== .D.TS ==================== +// Correct declare function asyncFunctionGood(): Promise; declare const asyncFunctionGoo2: () => Promise; declare class AsyncClassGood { method(): number; } +// Need to explicit return type for async functions +// Incorrect declare function asyncFunction(); declare const asyncFunction2: unknown; declare class AsyncClassBad { diff --git a/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap b/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap index e5769c27b5e7e..8b747b28e51ad 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap @@ -5,11 +5,13 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/function-parameters. ``` ==================== .D.TS ==================== +// Correct export declare function fnDeclGood(p?: T, rParam?: string): void; export declare function fnDeclGood2(p?: T, rParam?: number): void; export declare function fooGood([a, b]?: any[]): number; export declare const fooGood2: ({ a, b }?: object) => number; export declare function fooGood3({ a, b: [{ c }] }: object): void; +// Incorrect export declare function fnDeclBad(p: T, rParam: T, r2: T): void; export declare function fnDeclBad2(p: T, r2: T): void; export declare function fnDeclBad3(p: T, rParam?: T, r2: T): void; diff --git a/crates/oxc_isolated_declarations/tests/snapshots/function-signatures.snap b/crates/oxc_isolated_declarations/tests/snapshots/function-signatures.snap index c7998e7d279a1..98d20a093c95a 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/function-signatures.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/function-signatures.snap @@ -5,7 +5,10 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/function-signatures. ``` ==================== .D.TS ==================== +// All of these are valid function signatures under isolatedDeclarations export declare function A(): void; export declare function B(): (() => void) | undefined; +// There should be no declaration for the implementation signature, just the +// two overloads. export declare function C(x: string): void; export declare function C(x: number): void; diff --git a/crates/oxc_isolated_declarations/tests/snapshots/generator.snap b/crates/oxc_isolated_declarations/tests/snapshots/generator.snap index e6c2845ff49dc..7d4fa47293246 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/generator.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/generator.snap @@ -5,10 +5,13 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/generator.ts ``` ==================== .D.TS ==================== +// Correct declare function generatorGood(): Generator; declare class GeneratorClassGood { method(): Generator; } +// Need to explicit return type for async functions +// Incorrect declare function generatorBad(); declare class GeneratorClassBad { method(); diff --git a/crates/oxc_isolated_declarations/tests/snapshots/infer-expression.snap b/crates/oxc_isolated_declarations/tests/snapshots/infer-expression.snap index 3c344b8f6522f..33cc7b0957387 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/infer-expression.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/infer-expression.snap @@ -5,12 +5,17 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/infer-expression.ts ``` ==================== .D.TS ==================== +// Correct +// ParenthesizedExpression declare const n: number; declare const s: string; declare const t: string; declare const b: boolean; +// UnaryExpression declare let unaryA: number; declare const unaryB = -12n; +// Incorrect +// UnaryExpression declare const unaryC: unknown; declare const unaryD: unknown; declare const unaryE: {}; diff --git a/crates/oxc_isolated_declarations/tests/snapshots/infer-return-type.snap b/crates/oxc_isolated_declarations/tests/snapshots/infer-return-type.snap index 4c3bec1334d98..cadb34224cbf3 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/infer-return-type.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/infer-return-type.snap @@ -6,10 +6,14 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/infer-return-type.ts ==================== .D.TS ==================== declare function foo(): number; +// inferred type is number declare function bar(): number | undefined; +// inferred type is number | undefined declare function baz(); +// We can't infer return type if there are multiple return statements with different types declare function qux(): string; declare function quux(): string; +// Inferred type is string declare function returnFunctionOrNothing(): (() => number) | undefined; diff --git a/crates/oxc_isolated_declarations/tests/snapshots/set-get-accessor.snap b/crates/oxc_isolated_declarations/tests/snapshots/set-get-accessor.snap index d01e3a42106f9..0892ab614acc7 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/set-get-accessor.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/set-get-accessor.snap @@ -5,6 +5,7 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/set-get-accessor.ts ``` ==================== .D.TS ==================== +// Correct declare class Cls { get a(): number; set a(value); @@ -16,6 +17,7 @@ declare class Cls { private accessor e; private static accessor f; } +// Incorrect declare class ClsBad { get a(); set a(v); diff --git a/crates/oxc_isolated_declarations/tests/snapshots/signatures.snap b/crates/oxc_isolated_declarations/tests/snapshots/signatures.snap index 2bed4c93bd4a2..723ee1fd341d7 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/signatures.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/signatures.snap @@ -24,6 +24,7 @@ export interface I { set value(_: string); get value(): string; } +// Do nothing export interface Ref< T = any, S = T diff --git a/crates/oxc_transformer_plugins/tests/integrations/replace_global_defines.rs b/crates/oxc_transformer_plugins/tests/integrations/replace_global_defines.rs index 046c1cb6ce79b..b8bab9e5ea105 100644 --- a/crates/oxc_transformer_plugins/tests/integrations/replace_global_defines.rs +++ b/crates/oxc_transformer_plugins/tests/integrations/replace_global_defines.rs @@ -190,7 +190,9 @@ fn this_expr() { // This code should be the same as above (() => { ok( this, this.foo, this.foo.bar, this.foo.baz, this.bar,) })(); ", - "ok(1, 2, 3, 2 .baz, 1 .bar);", + " + // This code should be the same as above + ok(1, 2, 3, 2 .baz, 1 .bar);", config.clone(), ); diff --git a/tasks/coverage/snapshots/codegen_babel.snap b/tasks/coverage/snapshots/codegen_babel.snap index d0bb1a9b33065..39814065e5fcc 100644 --- a/tasks/coverage/snapshots/codegen_babel.snap +++ b/tasks/coverage/snapshots/codegen_babel.snap @@ -2,4 +2,8 @@ commit: 578ac4df codegen_babel Summary: AST Parsed : 2322/2322 (100.00%) -Positive Passed: 2322/2322 (100.00%) +Positive Passed: 2320/2322 (99.91%) +Normal: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/uncategorised/47/input.js + +Normal: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/uncategorised/48/input.js + diff --git a/tasks/coverage/snapshots/minifier_babel.snap b/tasks/coverage/snapshots/minifier_babel.snap index 55e0f7c345e88..3bc383c4bc820 100644 --- a/tasks/coverage/snapshots/minifier_babel.snap +++ b/tasks/coverage/snapshots/minifier_babel.snap @@ -2,4 +2,8 @@ commit: 578ac4df minifier_babel Summary: AST Parsed : 1732/1732 (100.00%) -Positive Passed: 1732/1732 (100.00%) +Positive Passed: 1730/1732 (99.88%) +Compress: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/uncategorised/47/input.js + +Compress: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/uncategorised/48/input.js + diff --git a/tasks/coverage/snapshots/transformer_babel.snap b/tasks/coverage/snapshots/transformer_babel.snap index 4f3728db3b3a1..9a6c411a84527 100644 --- a/tasks/coverage/snapshots/transformer_babel.snap +++ b/tasks/coverage/snapshots/transformer_babel.snap @@ -2,4 +2,8 @@ commit: 578ac4df transformer_babel Summary: AST Parsed : 2322/2322 (100.00%) -Positive Passed: 2322/2322 (100.00%) +Positive Passed: 2320/2322 (99.91%) +Mismatch: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/uncategorised/47/input.js + +Mismatch: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/uncategorised/48/input.js + diff --git a/tasks/coverage/snapshots/transpile.snap b/tasks/coverage/snapshots/transpile.snap index 97dc87ac21be7..35421ba6a55d5 100644 --- a/tasks/coverage/snapshots/transpile.snap +++ b/tasks/coverage/snapshots/transpile.snap @@ -386,6 +386,7 @@ export class InClassMethodBad { o(array: T = [], rParam: string): void {} } ; +// https://github.com/microsoft/TypeScript/issues/60976 class Bar {} export class ClsWithRequiredInitializedParameter { constructor(private arr: Bar = new Bar(), private bool: boolean) {} diff --git a/tasks/coverage/src/driver.rs b/tasks/coverage/src/driver.rs index a5762ab080c69..de14bd0bb110b 100644 --- a/tasks/coverage/src/driver.rs +++ b/tasks/coverage/src/driver.rs @@ -62,8 +62,13 @@ impl CompilerInterface for Driver { } fn codegen_options(&self) -> Option { - self.codegen - .then(|| CodegenOptions { minify: self.remove_whitespace, ..CodegenOptions::default() }) + self.codegen.then(|| { + if self.remove_whitespace { + CodegenOptions::minify() + } else { + CodegenOptions::default() + } + }) } fn handle_errors(&mut self, errors: Vec) { diff --git a/tasks/coverage/src/typescript/transpile_runner.rs b/tasks/coverage/src/typescript/transpile_runner.rs index 2f051650d8c98..a7346e8614f38 100644 --- a/tasks/coverage/src/typescript/transpile_runner.rs +++ b/tasks/coverage/src/typescript/transpile_runner.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; use oxc::{ allocator::Allocator, - codegen::Codegen, + codegen::{Codegen, CodegenOptions}, diagnostics::OxcDiagnostic, isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions}, parser::Parser, @@ -181,6 +181,9 @@ fn transpile(path: &Path, source_text: &str) -> (String, Vec) { let ret = IsolatedDeclarations::new(&allocator, IsolatedDeclarationsOptions { strip_internal: true }) .build(&ret.program); - let printed = Codegen::new().build(&ret.program).code; + let printed = Codegen::new() + .with_options(CodegenOptions { comments: false, ..CodegenOptions::default() }) + .build(&ret.program) + .code; (printed, ret.errors) } diff --git a/tasks/transform_conformance/src/driver.rs b/tasks/transform_conformance/src/driver.rs index da3fe75178f95..50b0a74b3b293 100644 --- a/tasks/transform_conformance/src/driver.rs +++ b/tasks/transform_conformance/src/driver.rs @@ -68,6 +68,8 @@ impl CompilerInterface for Driver { self.errors.extend(errors); } } + // Clear comments to avoid pure annotation comments that cause mismatch. + program.comments.clear(); ControlFlow::Continue(()) } }