diff --git a/crates/oxc/src/compiler.rs b/crates/oxc/src/compiler.rs index a0c30aea749204..8e789d05b36f78 100644 --- a/crates/oxc/src/compiler.rs +++ b/crates/oxc/src/compiler.rs @@ -2,7 +2,7 @@ use std::{mem, ops::ControlFlow, path::Path}; use oxc_allocator::Allocator; use oxc_ast::ast::Program; -use oxc_codegen::{CodeGenerator, CodegenOptions, CodegenReturn, CommentOptions}; +use oxc_codegen::{CodeGenerator, CodegenOptions, CodegenReturn}; use oxc_diagnostics::OxcDiagnostic; use oxc_isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions}; use oxc_mangler::{MangleOptions, Mangler}; @@ -283,11 +283,7 @@ pub trait CompilerInterface { mangler: Option, options: CodegenOptions, ) -> CodegenReturn { - let comment_options = CommentOptions { preserve_annotate_comments: true }; - let mut codegen = CodeGenerator::new() - .with_options(options) - .with_mangler(mangler) - .enable_comment(program, comment_options); + let mut codegen = CodeGenerator::new().with_options(options).with_mangler(mangler); if self.enable_sourcemap() { codegen = codegen .enable_source_map(source_path.to_string_lossy().as_ref(), program.source_text); diff --git a/crates/oxc_codegen/examples/codegen.rs b/crates/oxc_codegen/examples/codegen.rs index 895a7f3eeb41d5..3edfcfbaabf378 100644 --- a/crates/oxc_codegen/examples/codegen.rs +++ b/crates/oxc_codegen/examples/codegen.rs @@ -2,7 +2,7 @@ use std::{env, path::Path}; use oxc_allocator::Allocator; -use oxc_codegen::{CodeGenerator, CodegenOptions, CommentOptions}; +use oxc_codegen::{CodeGenerator, CodegenOptions}; use oxc_parser::{Parser, ParserReturn}; use oxc_span::SourceType; use pico_args::Arguments; @@ -61,7 +61,6 @@ fn parse<'a>( fn codegen(ret: &ParserReturn<'_>, minify: bool) -> String { CodeGenerator::new() - .enable_comment(&ret.program, CommentOptions { preserve_annotate_comments: true }) .with_options(CodegenOptions { minify, ..CodegenOptions::default() }) .build(&ret.program) .code diff --git a/crates/oxc_codegen/src/comment.rs b/crates/oxc_codegen/src/comment.rs index 389b0ad55ad5d3..d24f4c4b943161 100644 --- a/crates/oxc_codegen/src/comment.rs +++ b/crates/oxc_codegen/src/comment.rs @@ -16,10 +16,6 @@ static ANNOTATION_MATCHER: Lazy> = Lazy::new(|| { pub(crate) type CommentsMap = FxHashMap>; impl<'a> Codegen<'a> { - pub(crate) fn preserve_annotate_comments(&self) -> bool { - self.comment_options.preserve_annotate_comments && !self.options.minify - } - pub(crate) fn build_comments(&mut self, comments: &[Comment]) { for comment in comments { self.comments.entry(comment.attached_to).or_default().push(*comment); @@ -31,35 +27,33 @@ impl<'a> Codegen<'a> { } pub(crate) fn has_annotation_comment(&self, start: u32) -> bool { - if !self.preserve_annotate_comments() { + if !self.options.print_annotation_comments() { return false; } - let Some(source_text) = self.source_text else { return false }; self.comments.get(&start).is_some_and(|comments| { - comments.iter().any(|comment| Self::is_annotation_comment(comment, source_text)) + comments.iter().any(|comment| self.is_annotation_comment(comment)) }) } pub(crate) fn has_non_annotation_comment(&self, start: u32) -> bool { - if !self.preserve_annotate_comments() { + if !self.options.print_annotation_comments() { return self.has_comment(start); } - let Some(source_text) = self.source_text else { return false }; self.comments.get(&start).is_some_and(|comments| { - comments.iter().any(|comment| !Self::is_annotation_comment(comment, source_text)) + comments.iter().any(|comment| !self.is_annotation_comment(comment)) }) } /// Weather to keep leading comments. - fn is_leading_comments(comment: &Comment, source_text: &str) -> bool { - (comment.is_jsdoc(source_text) || (comment.is_line() && Self::is_annotation_comment(comment, source_text))) + fn is_leading_comments(&self, comment: &Comment) -> bool { + (comment.is_jsdoc(self.source_text) || (comment.is_line() && self.is_annotation_comment(comment))) && comment.preceded_by_newline // webpack comment `/*****/` - && !comment.span.source_text(source_text).chars().all(|c| c == '*') + && !comment.span.source_text(self.source_text).chars().all(|c| c == '*') } - fn print_comment(&mut self, comment: &Comment, source_text: &str) { - let comment_source = comment.real_span().source_text(source_text); + fn print_comment(&mut self, comment: &Comment) { + let comment_source = comment.real_span().source_text(self.source_text); match comment.kind { CommentKind::Line => { self.print_str(comment_source); @@ -84,14 +78,12 @@ impl<'a> Codegen<'a> { if self.options.minify { return; } - let Some(source_text) = self.source_text else { return }; let Some(comments) = self.comments.remove(&start) else { return; }; - let (comments, unused_comments): (Vec<_>, Vec<_>) = comments - .into_iter() - .partition(|comment| Self::is_leading_comments(comment, source_text)); + let (comments, unused_comments): (Vec<_>, Vec<_>) = + comments.into_iter().partition(|comment| self.is_leading_comments(comment)); if comments.first().is_some_and(|c| c.preceded_by_newline) { // Skip printing newline if this comment is already on a newline. @@ -107,7 +99,7 @@ impl<'a> Codegen<'a> { self.print_indent(); } - self.print_comment(comment, source_text); + self.print_comment(comment); } if comments.last().is_some_and(|c| c.is_line() || c.followed_by_newline) { @@ -120,32 +112,31 @@ impl<'a> Codegen<'a> { } } - fn is_annotation_comment(comment: &Comment, source_text: &str) -> bool { - let comment_content = comment.span.source_text(source_text); + fn is_annotation_comment(&self, comment: &Comment) -> bool { + let comment_content = comment.span.source_text(self.source_text); ANNOTATION_MATCHER.find_iter(comment_content).count() != 0 } pub(crate) fn print_annotation_comments(&mut self, node_start: u32) { - if !self.preserve_annotate_comments() { + if !self.options.print_annotation_comments() { return; } // If there is has annotation comments awaiting move to here, print them. let start = self.start_of_annotation_comment.take().unwrap_or(node_start); - let Some(source_text) = self.source_text else { return }; let Some(comments) = self.comments.remove(&start) else { return }; for comment in comments { - if !Self::is_annotation_comment(&comment, source_text) { + if !self.is_annotation_comment(&comment) { continue; } if comment.is_line() { self.print_str("/*"); - self.print_str(comment.span.source_text(source_text)); + self.print_str(comment.span.source_text(self.source_text)); self.print_str("*/"); } else { - self.print_str(comment.real_span().source_text(source_text)); + self.print_str(comment.real_span().source_text(self.source_text)); } self.print_hard_space(); } @@ -155,12 +146,10 @@ impl<'a> Codegen<'a> { if self.options.minify { return false; } - let Some(source_text) = self.source_text else { return false }; let Some(comments) = self.comments.remove(&start) else { return false }; - let (annotation_comments, comments): (Vec<_>, Vec<_>) = comments - .into_iter() - .partition(|comment| Self::is_annotation_comment(comment, source_text)); + let (annotation_comments, comments): (Vec<_>, Vec<_>) = + comments.into_iter().partition(|comment| self.is_annotation_comment(comment)); if !annotation_comments.is_empty() { self.comments.insert(start, annotation_comments); @@ -169,7 +158,7 @@ impl<'a> Codegen<'a> { for comment in &comments { self.print_hard_newline(); self.print_indent(); - self.print_comment(comment, source_text); + self.print_comment(comment); } if comments.is_empty() { diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index a815a3c01880a8..4dd193eb2a9781 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, ops::Not}; +use std::ops::Not; use cow_utils::CowUtils; #[allow(clippy::wildcard_imports)] @@ -558,7 +558,7 @@ impl<'a> Gen for VariableDeclaration<'a> { p.print_str("declare "); } - if p.preserve_annotate_comments() + if p.options.print_annotation_comments() && p.start_of_annotation_comment.is_none() && matches!(self.kind, VariableDeclarationKind::Const) && matches!(self.declarations.first(), Some(VariableDeclarator { init: Some(init), .. }) if init.is_function()) @@ -825,7 +825,7 @@ impl<'a> Gen for ExportNamedDeclaration<'a> { p.add_source_mapping(self.span.start); p.print_indent(); - if p.preserve_annotate_comments() { + if p.options.print_annotation_comments() { match &self.declaration { Some(Declaration::FunctionDeclaration(_)) => { p.print_annotation_comments(self.span.start); @@ -1195,10 +1195,7 @@ impl<'a> Gen for RegExpLiteral<'a> { fn gen(&self, p: &mut Codegen, _ctx: Context) { p.add_source_mapping(self.span.start); let last = p.peek_nth(0); - let pattern_text = p.source_text.map_or_else( - || Cow::Owned(self.regex.pattern.to_string()), - |src| self.regex.pattern.source_text(src), - ); + let pattern_text = self.regex.pattern.source_text(p.source_text); // Avoid forming a single-line comment or " = Codegen<'a>; -#[derive(Default, Clone, Copy)] +#[derive(Clone, Copy)] pub struct CodegenOptions { /// Use single quotes instead of double quotes. /// @@ -46,47 +46,49 @@ pub struct CodegenOptions { /// /// Default is `false`. pub minify: bool, + + /// Print comments? + /// + /// Default is `true`. + pub comments: bool, + + /// Print annotation comments, e.g. `/* #__PURE__ */` and `/* #__NO_SIDE_EFFECTS__ */`. + /// + /// Only takes into effect when `comments` is false. + /// + /// Default is `false`. + pub annotation_comments: bool, +} + +impl Default for CodegenOptions { + fn default() -> Self { + Self { single_quote: false, minify: false, comments: true, annotation_comments: false } + } } -#[derive(Default, Clone, Copy)] -pub struct CommentOptions { - /// Enable preserve annotate comments, like `/* #__PURE__ */` and `/* #__NO_SIDE_EFFECTS__ */`. - pub preserve_annotate_comments: bool, +impl CodegenOptions { + fn print_annotation_comments(self) -> bool { + !self.minify && (self.comments || self.annotation_comments) + } } /// Output from [`Codegen::build`] pub struct CodegenReturn { /// The generated source code. pub code: String, + /// The source map from the input source code to the generated source code. - /// - /// You must use [`Codegen::enable_source_map`] for this to be [`Some`]. pub map: Option, } pub struct Codegen<'a> { - options: CodegenOptions, - comment_options: CommentOptions, + pub(crate) options: CodegenOptions, /// Original source code of the AST - source_text: Option<&'a str>, + source_text: &'a str, comments: CommentsMap, - /// Start of comment that needs to be moved to the before VariableDeclarator - /// - /// For example: - /// ```js - /// /* @__NO_SIDE_EFFECTS__ */ export const a = function() { - /// }, b = 10000; - /// ``` - /// Should be generated as: - /// ```js - /// export const /* @__NO_SIDE_EFFECTS__ */ a = function() { - /// }, b = 10000; - /// ``` - start_of_annotation_comment: Option, - mangler: Option, /// Output Code @@ -107,6 +109,19 @@ pub struct Codegen<'a> { start_of_stmt: usize, start_of_arrow_expr: usize, start_of_default_export: usize, + /// Start of comment that needs to be moved to the before VariableDeclarator + /// + /// For example: + /// ```js + /// /* @__NO_SIDE_EFFECTS__ */ export const a = function() { + /// }, b = 10000; + /// ``` + /// Should be generated as: + /// ```js + /// export const /* @__NO_SIDE_EFFECTS__ */ a = function() { + /// }, b = 10000; + /// ``` + start_of_annotation_comment: Option, /// Track the current indentation level indent: u32, @@ -142,8 +157,7 @@ impl<'a> Codegen<'a> { pub fn new() -> Self { Self { options: CodegenOptions::default(), - comment_options: CommentOptions::default(), - source_text: None, + source_text: "", comments: CommentsMap::default(), start_of_annotation_comment: None, mangler: None, @@ -164,44 +178,13 @@ impl<'a> Codegen<'a> { } } - /// Initialize the output code buffer to reduce memory reallocation. - /// Minification will reduce by at least half of the original size. - #[must_use] - pub fn with_capacity(mut self, source_text_len: usize) -> Self { - let capacity = if self.options.minify { source_text_len / 2 } else { source_text_len }; - // ensure space for at least `capacity` additional bytes without clobbering existing - // allocations. - self.code.reserve(capacity); - self - } - #[must_use] pub fn with_options(mut self, options: CodegenOptions) -> Self { - self.options = options; self.quote = if options.single_quote { b'\'' } else { b'"' }; + self.options = options; self } - /// Adds the source text of the original AST. - /// - /// The source code will be used with comments or for improving the generated output. It also - /// pre-allocates memory for the output code using [`Codegen::with_capacity`]. Note that if you - /// use this method alongside your own call to [`Codegen::with_capacity`], the larger of the - /// two will be used. - #[must_use] - pub fn with_source_text(mut self, source_text: &'a str) -> Self { - self.source_text = Some(source_text); - self.with_capacity(source_text.len()) - } - - /// Also sets the [Self::with_source_text] - #[must_use] - pub fn enable_comment(mut self, program: &Program<'a>, options: CommentOptions) -> Self { - self.comment_options = options; - self.build_comments(&program.comments); - self.with_source_text(program.source_text) - } - #[must_use] pub fn enable_source_map(mut self, source_name: &str, source_text: &str) -> Self { let mut sourcemap_builder = SourcemapBuilder::default(); @@ -217,7 +200,14 @@ impl<'a> Codegen<'a> { } #[must_use] - pub fn build(mut self, program: &Program<'_>) -> CodegenReturn { + pub fn build(mut self, program: &Program<'a>) -> CodegenReturn { + self.quote = if self.options.single_quote { b'\'' } else { b'"' }; + self.source_text = program.source_text; + self.code.reserve(program.source_text.len()); + if self.options.print_annotation_comments() { + self.build_comments(&program.comments); + } + program.print(&mut self, Context::default()); let code = self.into_source_text(); let map = self.sourcemap_builder.map(SourcemapBuilder::into_sourcemap); @@ -227,7 +217,6 @@ impl<'a> Codegen<'a> { #[must_use] pub fn into_source_text(&mut self) -> String { // SAFETY: criteria of `from_utf8_unchecked` are met. - unsafe { String::from_utf8_unchecked(std::mem::take(&mut self.code)) } } diff --git a/crates/oxc_codegen/tests/integration/main.rs b/crates/oxc_codegen/tests/integration/main.rs index 21e6e641aaa759..b2c5c4f9f15c1b 100644 --- a/crates/oxc_codegen/tests/integration/main.rs +++ b/crates/oxc_codegen/tests/integration/main.rs @@ -7,7 +7,7 @@ pub mod ts; pub mod unit; use oxc_allocator::Allocator; -use oxc_codegen::{CodeGenerator, CodegenOptions, CommentOptions}; +use oxc_codegen::{CodeGenerator, CodegenOptions}; use oxc_parser::Parser; use oxc_span::SourceType; @@ -17,7 +17,6 @@ pub fn codegen(source_text: &str) -> String { let ret = Parser::new(&allocator, source_text, source_type).parse(); CodeGenerator::new() .with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() }) - .enable_comment(&ret.program, CommentOptions { preserve_annotate_comments: true }) .build(&ret.program) .code } diff --git a/crates/oxc_codegen/tests/integration/tester.rs b/crates/oxc_codegen/tests/integration/tester.rs index e6460fd82af459..5f8d8a39931719 100644 --- a/crates/oxc_codegen/tests/integration/tester.rs +++ b/crates/oxc_codegen/tests/integration/tester.rs @@ -1,5 +1,5 @@ use oxc_allocator::Allocator; -use oxc_codegen::{CodeGenerator, CodegenOptions, CommentOptions}; +use oxc_codegen::{CodeGenerator, CodegenOptions}; use oxc_parser::Parser; use oxc_span::SourceType; @@ -7,10 +7,7 @@ pub fn test(source_text: &str, expected: &str) { let source_type = SourceType::jsx(); let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); - let result = CodeGenerator::new() - .enable_comment(&ret.program, CommentOptions { preserve_annotate_comments: true }) - .build(&ret.program) - .code; + let result = CodeGenerator::new().build(&ret.program).code; assert_eq!(result, expected, "\nfor source: {source_text:?}"); } diff --git a/crates/oxc_isolated_declarations/examples/isolated_declarations.rs b/crates/oxc_isolated_declarations/examples/isolated_declarations.rs index 2074fcb19d820e..ce354f662ae42d 100644 --- a/crates/oxc_isolated_declarations/examples/isolated_declarations.rs +++ b/crates/oxc_isolated_declarations/examples/isolated_declarations.rs @@ -2,7 +2,7 @@ use std::{env, path::Path}; use oxc_allocator::Allocator; -use oxc_codegen::{CodeGenerator, CommentOptions}; +use oxc_codegen::CodeGenerator; use oxc_isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions}; use oxc_parser::Parser; use oxc_span::SourceType; @@ -35,10 +35,7 @@ fn main() { let id_ret = IsolatedDeclarations::new(&allocator, IsolatedDeclarationsOptions { strip_internal: true }) .build(&ret.program); - let printed = CodeGenerator::new() - .enable_comment(&ret.program, CommentOptions { preserve_annotate_comments: false }) - .build(&id_ret.program) - .code; + let printed = CodeGenerator::new().build(&id_ret.program).code; println!("Dts Emit:\n"); println!("{printed}\n"); diff --git a/crates/oxc_isolated_declarations/tests/mod.rs b/crates/oxc_isolated_declarations/tests/mod.rs index dc0f18e6ca9e79..7c3d2b184cc0b2 100644 --- a/crates/oxc_isolated_declarations/tests/mod.rs +++ b/crates/oxc_isolated_declarations/tests/mod.rs @@ -3,7 +3,7 @@ mod deno; use std::{fs, path::Path, sync::Arc}; use oxc_allocator::Allocator; -use oxc_codegen::{CodeGenerator, CommentOptions}; +use oxc_codegen::CodeGenerator; use oxc_isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions}; use oxc_parser::Parser; use oxc_span::SourceType; @@ -16,10 +16,7 @@ fn transform(path: &Path, source_text: &str) -> String { let id_ret = IsolatedDeclarations::new(&allocator, IsolatedDeclarationsOptions { strip_internal: true }) .build(&parser_ret.program); - let code = CodeGenerator::new() - .enable_comment(&parser_ret.program, CommentOptions { preserve_annotate_comments: false }) - .build(&id_ret.program) - .code; + let code = CodeGenerator::new().build(&id_ret.program).code; let mut snapshot = format!("```\n==================== .D.TS ====================\n\n{code}\n\n"); diff --git a/crates/oxc_linter/src/fixer/mod.rs b/crates/oxc_linter/src/fixer/mod.rs index 2c51081a2719e2..df16517e394dd3 100644 --- a/crates/oxc_linter/src/fixer/mod.rs +++ b/crates/oxc_linter/src/fixer/mod.rs @@ -170,7 +170,6 @@ impl<'c, 'a: 'c> RuleFixer<'c, 'a> { #[allow(clippy::unused_self)] pub fn codegen(self) -> CodeGenerator<'a> { CodeGenerator::new() - .with_source_text(self.source_text()) .with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() }) } diff --git a/napi/minify/src/lib.rs b/napi/minify/src/lib.rs index 550785a5e69a0f..61156f360f351d 100644 --- a/napi/minify/src/lib.rs +++ b/napi/minify/src/lib.rs @@ -22,7 +22,6 @@ pub fn minify(filename: String, source_text: String) -> String { Codegen::new() .with_options(CodegenOptions { minify: true, ..CodegenOptions::default() }) .with_mangler(mangler) - .with_capacity(source_text.len()) .build(&program) .code } diff --git a/napi/transform/src/isolated_declaration.rs b/napi/transform/src/isolated_declaration.rs index 72867b82008587..89645cebcfea0c 100644 --- a/napi/transform/src/isolated_declaration.rs +++ b/napi/transform/src/isolated_declaration.rs @@ -4,7 +4,7 @@ use napi_derive::napi; use oxc::{ allocator::Allocator, - codegen::{CodeGenerator, CommentOptions}, + codegen::CodeGenerator, isolated_declarations::IsolatedDeclarations, napi::{ isolated_declarations::{IsolatedDeclarationsOptions, IsolatedDeclarationsResult}, @@ -39,8 +39,7 @@ pub fn isolated_declaration( ) .build(&ret.program); - let mut codegen = CodeGenerator::new() - .enable_comment(&ret.program, CommentOptions { preserve_annotate_comments: false }); + let mut codegen = CodeGenerator::new(); if options.sourcemap == Some(true) { codegen = codegen.enable_source_map(&filename, &source_text); } diff --git a/tasks/transform_conformance/src/driver.rs b/tasks/transform_conformance/src/driver.rs index 107328276d8f28..2a595d8d74bd43 100644 --- a/tasks/transform_conformance/src/driver.rs +++ b/tasks/transform_conformance/src/driver.rs @@ -2,9 +2,8 @@ use std::{mem, ops::ControlFlow, path::Path}; use oxc::{ ast::ast::Program, - codegen::{CodeGenerator, CodegenOptions, CodegenReturn}, + codegen::{CodegenOptions, CodegenReturn}, diagnostics::OxcDiagnostic, - mangler::Mangler, span::SourceType, transformer::{TransformOptions, TransformerReturn}, CompilerInterface, @@ -23,6 +22,10 @@ impl CompilerInterface for Driver { Some(self.options.clone()) } + fn codegen_options(&self) -> Option { + Some(CodegenOptions { comments: false, ..CodegenOptions::default() }) + } + fn check_semantic_error(&self) -> bool { false } @@ -55,17 +58,6 @@ impl CompilerInterface for Driver { } ControlFlow::Continue(()) } - - // Disable comments - fn codegen( - &self, - program: &Program<'_>, - _source_path: &Path, - mangler: Option, - options: CodegenOptions, - ) -> CodegenReturn { - CodeGenerator::new().with_options(options).with_mangler(mangler).build(program) - } } impl Driver { diff --git a/tasks/transform_conformance/src/test_case.rs b/tasks/transform_conformance/src/test_case.rs index 84b89b48e36cf4..baab1fa7980a11 100644 --- a/tasks/transform_conformance/src/test_case.rs +++ b/tasks/transform_conformance/src/test_case.rs @@ -6,7 +6,7 @@ use std::{ use cow_utils::CowUtils; use oxc::{ allocator::Allocator, - codegen::CodeGenerator, + codegen::{CodeGenerator, CodegenOptions}, diagnostics::{Error, NamedSource, OxcDiagnostic}, parser::Parser, span::{SourceType, VALID_EXTENSIONS}, @@ -300,11 +300,10 @@ impl TestCase for ConformanceTestCase { // Get expected code by parsing the source text, so we can get the same code generated result. let ret = Parser::new(&allocator, &output, source_type).parse(); CodeGenerator::new() - // .enable_comment( - // &output, - // ret.trivias, - // CommentOptions { preserve_annotate_comments: true }, - // ) + .with_options(CodegenOptions { + comments: false, + ..CodegenOptions::default() + }) .build(&ret.program) .code }, @@ -396,7 +395,10 @@ impl ExecTestCase { let source_text = fs::read_to_string(&target_path).unwrap(); let source_type = SourceType::from_path(&target_path).unwrap(); let transformed_ret = Parser::new(&allocator, &source_text, source_type).parse(); - let result = CodeGenerator::new().build(&transformed_ret.program).code; + let result = CodeGenerator::new() + .with_options(CodegenOptions { comments: false, ..CodegenOptions::default() }) + .build(&transformed_ret.program) + .code; fs::write(&target_path, result).unwrap(); target_path }