refactor(codegen)!: remove CommentOptions API (#6451)

This commit is contained in:
Boshen 2024-10-11 13:53:28 +00:00
parent 3677ef81e9
commit 7645e5c34b
14 changed files with 100 additions and 149 deletions

View file

@ -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<Mangler>,
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);

View file

@ -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

View file

@ -16,10 +16,6 @@ static ANNOTATION_MATCHER: Lazy<DoubleArrayAhoCorasick<usize>> = Lazy::new(|| {
pub(crate) type CommentsMap = FxHashMap</* attached_to */ u32, Vec<Comment>>;
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() {

View file

@ -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 "</script" sequence
if Some('/') == last
|| (Some('<') == last && pattern_text.cow_to_lowercase().starts_with("script"))

View file

@ -35,7 +35,7 @@ pub use crate::{
/// Code generator without whitespace removal.
pub type CodeGenerator<'a> = 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,
}
#[derive(Default, Clone, Copy)]
pub struct CommentOptions {
/// Enable preserve annotate comments, like `/* #__PURE__ */` and `/* #__NO_SIDE_EFFECTS__ */`.
pub preserve_annotate_comments: bool,
impl Default for CodegenOptions {
fn default() -> Self {
Self { single_quote: false, minify: false, comments: true, annotation_comments: false }
}
}
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<oxc_sourcemap::SourceMap>,
}
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<u32>,
mangler: Option<Mangler>,
/// 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<u32>,
/// 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)) }
}

View file

@ -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
}

View file

@ -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:?}");
}

View file

@ -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");

View file

@ -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");

View file

@ -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() })
}

View file

@ -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
}

View file

@ -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);
}

View file

@ -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<CodegenOptions> {
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<Mangler>,
options: CodegenOptions,
) -> CodegenReturn {
CodeGenerator::new().with_options(options).with_mangler(mangler).build(program)
}
}
impl Driver {

View file

@ -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
}