Skip to content

Commit

Permalink
feat(oxc_codegen): preserve annotate comment (#3465)
Browse files Browse the repository at this point in the history
1. Copy tests from
https://github.com/evanw/esbuild/blob/efa3dd2d8e895f7f9a9bef0d588560bbae7d776e/internal/bundler_tests/bundler_dce_test.go#L3833-L3971
2. Add option to preserve annotate comment like `/* #__NO_SIDE_EFFECTS__
*/` and `/* #__PURE__ */`
  • Loading branch information
IWANABETHATGUY authored May 30, 2024
1 parent ec041a0 commit 0cdb45a
Show file tree
Hide file tree
Showing 22 changed files with 574 additions and 142 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ cfg-if = "1.0.0"
schemars = "0.8.21"
oxc-browserslist = "0.16.1"
criterion2 = { version = "0.9.0", default-features = false }
daachorse = { version = "1.0.0" }

[workspace.metadata.cargo-shear]
ignored = ["napi", "oxc_traverse"]
Expand Down
4 changes: 3 additions & 1 deletion crates/oxc_codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ oxc_allocator = { workspace = true }
oxc_syntax = { workspace = true }
oxc_sourcemap = { workspace = true }
bitflags = { workspace = true }

once_cell = { workspace = true }
daachorse = { workspace = true }
rustc-hash = { workspace = true }
[dev-dependencies]
oxc_parser = { workspace = true }
base64 = { workspace = true }
8 changes: 5 additions & 3 deletions crates/oxc_codegen/examples/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ fn main() -> std::io::Result<()> {
println!("Original:");
println!("{source_text}");

let options = CodegenOptions { enable_source_map: false, enable_typescript: true };
let options =
CodegenOptions { enable_source_map: false, enable_typescript: true, ..Default::default() };
let printed =
Codegen::<false>::new("", &source_text, options.clone()).build(&ret.program).source_text;
Codegen::<false>::new("", &source_text, options, None).build(&ret.program).source_text;
println!("Printed:");
println!("{printed}");

let ret = Parser::new(&allocator, &printed, source_type).parse();
let minified = Codegen::<true>::new("", &source_text, options).build(&ret.program).source_text;
let minified =
Codegen::<true>::new("", &source_text, options, None).build(&ret.program).source_text;
println!("Minified:");
println!("{minified}");

Expand Down
5 changes: 3 additions & 2 deletions crates/oxc_codegen/examples/sourcemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ fn main() -> std::io::Result<()> {
return Ok(());
}

let codegen_options = CodegenOptions { enable_source_map: true, enable_typescript: true };
let codegen_options =
CodegenOptions { enable_source_map: true, enable_typescript: true, ..Default::default() };

let CodegenReturn { source_text, source_map } =
Codegen::<false>::new(path.to_string_lossy().as_ref(), &source_text, codegen_options)
Codegen::<false>::new(path.to_string_lossy().as_ref(), &source_text, codegen_options, None)
.build(&ret.program);

if let Some(source_map) = source_map {
Expand Down
70 changes: 70 additions & 0 deletions crates/oxc_codegen/src/annotation_comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use std::usize;

use daachorse::DoubleArrayAhoCorasick;
use once_cell::sync::Lazy;
use oxc_ast::{Comment, CommentKind};

use crate::Codegen;
static MATCHER: Lazy<DoubleArrayAhoCorasick<usize>> = Lazy::new(|| {
let patterns = vec!["#__NO_SIDE_EFFECTS__", "@__NO_SIDE_EFFECTS__", "@__PURE__", "#__PURE__"];

DoubleArrayAhoCorasick::new(patterns).unwrap()
});

pub fn get_leading_annotate_comment<const MINIFY: bool>(
node_start: u32,
codegen: &mut Codegen<{ MINIFY }>,
) -> Option<(u32, Comment)> {
let maybe_leading_comment = codegen.try_get_leading_comment(node_start);
let (comment_start, comment) = maybe_leading_comment?;
let real_end = match comment.kind {
CommentKind::SingleLine => comment.end,
CommentKind::MultiLine => comment.end + 2,
};
let source_code = codegen.source_code;
let content_between = &source_code[real_end as usize..node_start as usize];
// Used for VariableDeclaration (Rollup only respects "const" and only for the first one)
if content_between.chars().all(|ch| ch.is_ascii_whitespace()) {
let comment_content = &source_code[*comment_start as usize..comment.end as usize];
if MATCHER.find_iter(&comment_content).next().is_some() {
return Some((*comment_start, *comment));
}
None
} else {
None
}
}

pub fn print_comment<const MINIFY: bool>(
comment_start: u32,
comment: Comment,
p: &mut Codegen<{ MINIFY }>,
) {
match comment.kind {
CommentKind::SingleLine => {
p.print_str("//");
p.print_range_of_source_code(comment_start as usize..comment.end as usize);
p.print_soft_newline();
p.print_indent();
}
CommentKind::MultiLine => {
p.print_str("/*");
p.print_range_of_source_code(comment_start as usize..comment.end as usize);
p.print_str("*/");
p.print_soft_space();
}
}
}

pub fn gen_comment<const MINIFY: bool>(node_start: u32, codegen: &mut Codegen<{ MINIFY }>) {
if !codegen.options.preserve_annotate_comments {
return;
}
if let Some((comment_start, comment)) = codegen.try_take_moved_comment(node_start) {
print_comment::<MINIFY>(comment_start, comment, codegen);
}
let maybe_leading_annotate_comment = get_leading_annotate_comment(node_start, codegen);
if let Some((comment_start, comment)) = maybe_leading_annotate_comment {
print_comment::<MINIFY>(comment_start, comment, codegen);
}
}
61 changes: 58 additions & 3 deletions crates/oxc_codegen/src/gen.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::annotation_comment::{gen_comment, get_leading_annotate_comment};
use oxc_allocator::{Box, Vec};
#[allow(clippy::wildcard_imports)]
use oxc_ast::ast::*;
Expand Down Expand Up @@ -29,6 +30,11 @@ where
}
}

/// the [GenComment] trait only generate annotate comments like `/* @__PURE__ */` and `/* @__NO_SIDE_EFFECTS__ */`.
pub trait GenComment<const MINIFY: bool> {
fn gen_comment(&self, _p: &mut Codegen<{ MINIFY }>, _ctx: Context) {}
}

impl<'a, const MINIFY: bool, T> GenExpr<MINIFY> for Box<'a, T>
where
T: GenExpr<MINIFY>,
Expand Down Expand Up @@ -611,10 +617,24 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for VariableDeclaration<'a> {
if p.options.enable_typescript && self.modifiers.contains(ModifierKind::Declare) {
p.print_str(b"declare ");
}

if p.options.preserve_annotate_comments
&& matches!(self.kind, VariableDeclarationKind::Const)
{
if let Some(declarator) = self.declarations.first() {
if let Some(ref init) = declarator.init {
if let Some(leading_annotate_comment) =
get_leading_annotate_comment(self.span.start, p)
{
p.move_comment(init.span().start, leading_annotate_comment);
}
}
}
}
p.print_str(match self.kind {
VariableDeclarationKind::Const => b"const",
VariableDeclarationKind::Let => b"let",
VariableDeclarationKind::Var => b"var",
VariableDeclarationKind::Const => "const",
VariableDeclarationKind::Let => "let",
VariableDeclarationKind::Var => "var",
});
if !self.declarations.is_empty() {
p.print_hard_space();
Expand All @@ -637,6 +657,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for VariableDeclarator<'a> {
impl<'a, const MINIFY: bool> Gen<MINIFY> for Function<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.add_source_mapping(self.span.start);
self.gen_comment(p, ctx);
if !p.options.enable_typescript && self.is_typescript_syntax() {
return;
}
Expand Down Expand Up @@ -847,6 +868,27 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ExportNamedDeclaration<'a> {
if !p.options.enable_typescript && self.is_typescript_syntax() {
return;
}
if p.options.preserve_annotate_comments {
match &self.declaration {
Some(Declaration::FunctionDeclaration(_)) => {
gen_comment(self.span.start, p);
}
Some(Declaration::VariableDeclaration(var_decl))
if matches!(var_decl.kind, VariableDeclarationKind::Const) =>
{
if let Some(declarator) = var_decl.declarations.first() {
if let Some(ref init) = declarator.init {
if let Some(leading_annotate_comment) =
get_leading_annotate_comment(self.span.start, p)
{
p.move_comment(init.span().start, leading_annotate_comment);
}
}
}
}
_ => {}
};
}
p.print_str(b"export ");
if p.options.enable_typescript && self.export_kind.is_type() {
p.print_str(b"type ");
Expand Down Expand Up @@ -1009,6 +1051,18 @@ impl<'a, const MINIFY: bool> GenExpr<MINIFY> for Expression<'a> {
}
}

impl<const MINIFY: bool> GenComment<MINIFY> for ArrowFunctionExpression<'_> {
fn gen_comment(&self, codegen: &mut Codegen<{ MINIFY }>, _ctx: Context) {
gen_comment(self.span.start, codegen);
}
}

impl<const MINIFY: bool> GenComment<MINIFY> for Function<'_> {
fn gen_comment(&self, codegen: &mut Codegen<{ MINIFY }>, _ctx: Context) {
gen_comment(self.span.start, codegen);
}
}

impl<'a, const MINIFY: bool> GenExpr<MINIFY> for TSAsExpression<'a> {
fn gen_expr(&self, p: &mut Codegen<{ MINIFY }>, precedence: Precedence, ctx: Context) {
if p.options.enable_typescript {
Expand Down Expand Up @@ -1538,6 +1592,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for PropertyKey<'a> {
impl<'a, const MINIFY: bool> GenExpr<MINIFY> for ArrowFunctionExpression<'a> {
fn gen_expr(&self, p: &mut Codegen<{ MINIFY }>, precedence: Precedence, ctx: Context) {
p.wrap(precedence > Precedence::Assign, |p| {
self.gen_comment(p, ctx);
if self.r#async {
p.add_source_mapping(self.span.start);
p.print_str(b"async");
Expand Down
Loading

0 comments on commit 0cdb45a

Please sign in to comment.