Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(codegen): print linked and external legal comments #7060

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/oxc_codegen/examples/sourcemap.rs
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> {
return Ok(());
}

let CodegenReturn { code, map } = CodeGenerator::new()
let CodegenReturn { code, map, .. } = CodeGenerator::new()
.with_options(CodegenOptions {
source_map_path: Some(path.to_path_buf()),
..CodegenOptions::default()
72 changes: 46 additions & 26 deletions crates/oxc_codegen/src/comment.rs
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ use rustc_hash::FxHashMap;
use oxc_ast::{Comment, CommentKind};
use oxc_syntax::identifier::is_line_terminator;

use crate::Codegen;
use crate::{Codegen, LegalComment};

static ANNOTATION_MATCHER: Lazy<DoubleArrayAhoCorasick<usize>> = Lazy::new(|| {
let patterns = vec!["#__NO_SIDE_EFFECTS__", "@__NO_SIDE_EFFECTS__", "@__PURE__", "#__PURE__"];
@@ -49,18 +49,7 @@ impl<'a> Codegen<'a> {
ANNOTATION_MATCHER.find_iter(comment_content).count() != 0
}

fn is_legal_comment(&self, comment: &Comment) -> bool {
if self.options.comments {
if self.options.legal_comments.is_inline() || self.options.legal_comments.is_none() {
return comment.is_legal(self.source_text);
}
} else if self.options.legal_comments.is_inline() {
return comment.is_legal(self.source_text);
}
false
}

/// Weather to keep leading comments.
/// Whether to keep leading comments.
fn is_leading_comments(&self, comment: &Comment) -> bool {
comment.preceded_by_newline
&& (comment.is_jsdoc(self.source_text)
@@ -89,11 +78,36 @@ impl<'a> Codegen<'a> {
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) || self.is_legal_comment(comment)
});
self.print_comments(start, &comments, unused_comments);

let mut leading_comments = vec![];
let mut unused_comments = vec![];

for comment in comments {
if self.is_leading_comments(&comment) {
leading_comments.push(comment);
continue;
}
if comment.is_legal(self.source_text) {
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 => {
self.legal_comments.push(comment);
continue;
}
LegalComment::None => {}
}
}
unused_comments.push(comment);
}

self.print_comments(start, &leading_comments, unused_comments);
}

pub(crate) fn print_annotation_comments(&mut self, node_start: u32) {
@@ -148,15 +162,21 @@ impl<'a> Codegen<'a> {
}
}

pub(crate) fn try_print_eof_legal_comments(&mut self, comments: &[Comment]) {
if !self.options.legal_comments.is_eof() {
return;
}
for c in comments {
if c.is_legal(self.source_text) {
self.print_comment(c);
self.print_hard_newline();
pub(crate) fn try_print_eof_legal_comments(&mut self) {
match self.options.legal_comments.clone() {
LegalComment::Eof => {
let comments = self.legal_comments.drain(..).collect::<Vec<_>>();
for c in comments {
self.print_comment(&c);
self.print_hard_newline();
}
}
LegalComment::Linked(path) => {
self.print_str("/*! For license information please see ");
self.print_str(&path);
self.print_str(" */");
}
_ => {}
}
}

35 changes: 21 additions & 14 deletions crates/oxc_codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ mod sourcemap_builder;
use std::borrow::Cow;

use oxc_ast::ast::{
BindingIdentifier, BlockStatement, Expression, IdentifierReference, Program, Statement,
BindingIdentifier, BlockStatement, Comment, Expression, IdentifierReference, Program, Statement,
};
use oxc_mangler::Mangler;
use oxc_span::{GetSpan, Span};
@@ -40,6 +40,7 @@ pub use crate::{
pub type CodeGenerator<'a> = Codegen<'a>;

/// Output from [`Codegen::build`]
#[non_exhaustive]
pub struct CodegenReturn {
/// The generated source code.
pub code: String,
@@ -48,6 +49,9 @@ pub struct CodegenReturn {
///
/// You must set [`CodegenOptions::source_map_path`] for this to be [`Some`].
pub map: Option<oxc_sourcemap::SourceMap>,

/// All the legal comments returned from [LegalComment::Linked] or [LegalComment::External].
pub legal_comments: Vec<Comment>,
}

/// A code generator for printing JavaScript and TypeScript code.
@@ -74,8 +78,6 @@ pub struct Codegen<'a> {
/// Original source code of the AST
source_text: &'a str,

comments: CommentsMap,

mangler: Option<Mangler>,

/// Output Code
@@ -96,6 +98,17 @@ pub struct Codegen<'a> {
start_of_stmt: usize,
start_of_arrow_expr: usize,
start_of_default_export: usize,

/// Track the current indentation level
indent: u32,

/// Fast path for [CodegenOptions::single_quote]
quote: u8,

// Builders
comments: CommentsMap,

legal_comments: Vec<Comment>,
/// Start of comment that needs to be moved to the before VariableDeclarator
///
/// For example:
@@ -110,13 +123,6 @@ pub struct Codegen<'a> {
/// ```
start_of_annotation_comment: Option<u32>,

/// Track the current indentation level
indent: u32,

/// Fast path for [CodegenOptions::single_quote]
quote: u8,

// Builders
sourcemap_builder: Option<SourcemapBuilder>,
}

@@ -148,8 +154,6 @@ impl<'a> Codegen<'a> {
Self {
options: CodegenOptions::default(),
source_text: "",
comments: CommentsMap::default(),
start_of_annotation_comment: None,
mangler: None,
code: CodeBuffer::default(),
needs_semicolon: false,
@@ -164,6 +168,9 @@ impl<'a> Codegen<'a> {
start_of_default_export: 0,
indent: 0,
quote: b'"',
comments: CommentsMap::default(),
start_of_annotation_comment: None,
legal_comments: vec![],
sourcemap_builder: None,
}
}
@@ -198,10 +205,10 @@ impl<'a> Codegen<'a> {
self.sourcemap_builder = Some(SourcemapBuilder::new(path, program.source_text));
}
program.print(&mut self, Context::default());
self.try_print_eof_legal_comments(&program.comments);
self.try_print_eof_legal_comments();
let code = self.code.into_string();
let map = self.sourcemap_builder.map(SourcemapBuilder::into_sourcemap);
CodegenReturn { code, map }
CodegenReturn { code, map, legal_comments: self.legal_comments }
}

/// Turn what's been built so far into a string. Like [`build`],
18 changes: 9 additions & 9 deletions crates/oxc_codegen/src/options.rs
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ use std::path::PathBuf;
/// Legal comment
///
/// <https://esbuild.github.io/api/#legal-comments>
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub enum LegalComment {
/// Do not preserve any legal comments (default).
#[default]
@@ -12,26 +12,26 @@ pub enum LegalComment {
Inline,
/// Move all legal comments to the end of the file.
Eof,
/// Move all legal comments to a .LEGAL.txt file and link to them with a comment.
Linked,
/// Return all legal comments and link then to them with a comment to the provided string.
Linked(String),
/// Move all legal comments to a .LEGAL.txt file but to not link to them.
External,
}

impl LegalComment {
/// Is None.
pub fn is_none(self) -> bool {
self == Self::None
pub fn is_none(&self) -> bool {
*self == Self::None
}

/// Is inline mode.
pub fn is_inline(self) -> bool {
self == Self::Inline
pub fn is_inline(&self) -> bool {
*self == Self::Inline
}

/// Is EOF mode.
pub fn is_eof(self) -> bool {
self == Self::Eof
pub fn is_eof(&self) -> bool {
*self == Self::Eof
}
}

21 changes: 20 additions & 1 deletion crates/oxc_codegen/tests/integration/legal_comments.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use oxc_codegen::{CodegenOptions, LegalComment};

use crate::{snapshot, snapshot_options};
use crate::{codegen_options, snapshot, snapshot_options};

fn cases() -> Vec<&'static str> {
vec![
@@ -21,3 +21,22 @@ fn legal_eof_comment() {
let options = CodegenOptions { legal_comments: LegalComment::Eof, ..Default::default() };
snapshot_options("legal_eof_comments", &cases(), &options);
}

#[test]
fn legal_linked_comment() {
let options = CodegenOptions {
legal_comments: LegalComment::Linked(String::from("test.js")),
..Default::default()
};
snapshot_options("legal_linked_comments", &cases(), &options);
}

#[test]
fn legal_external_comment() {
let options = CodegenOptions { legal_comments: LegalComment::External, ..Default::default() };
let code = "/* @license */\n/* @preserve */\nfoo;\n";
let ret = codegen_options(code, &options);
assert_eq!(ret.code, "foo;\n");
assert_eq!(ret.legal_comments[0].span.source_text(code), " @license ");
assert_eq!(ret.legal_comments[1].span.source_text(code), " @preserve ");
}
10 changes: 5 additions & 5 deletions crates/oxc_codegen/tests/integration/main.rs
Original file line number Diff line number Diff line change
@@ -8,21 +8,21 @@ pub mod ts;
pub mod unit;

use oxc_allocator::Allocator;
use oxc_codegen::{CodeGenerator, CodegenOptions};
use oxc_codegen::{CodeGenerator, CodegenOptions, CodegenReturn};
use oxc_parser::Parser;
use oxc_span::SourceType;

pub fn codegen(source_text: &str) -> String {
codegen_options(source_text, &CodegenOptions::default())
codegen_options(source_text, &CodegenOptions::default()).code
}

pub fn codegen_options(source_text: &str, options: &CodegenOptions) -> String {
pub fn codegen_options(source_text: &str, options: &CodegenOptions) -> CodegenReturn {
let allocator = Allocator::default();
let source_type = SourceType::ts();
let ret = Parser::new(&allocator, source_text, source_type).parse();
let mut options = options.clone();
options.single_quote = true;
CodeGenerator::new().with_options(options).build(&ret.program).code
CodeGenerator::new().with_options(options).build(&ret.program)
}

pub fn snapshot(name: &str, cases: &[&str]) {
@@ -33,7 +33,7 @@ pub fn snapshot_options(name: &str, cases: &[&str], options: &CodegenOptions) {
use std::fmt::Write;

let snapshot = cases.iter().enumerate().fold(String::new(), |mut w, (i, case)| {
let result = codegen_options(case, options);
let result = codegen_options(case, options).code;
write!(w, "########## {i}\n{case}\n----------\n{result}\n",).unwrap();
w
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
source: crates/oxc_codegen/tests/integration/main.rs
---
########## 0
/* @license */
/* @license */
foo;bar;
----------
foo;
bar;
/*! For license information please see test.js */
########## 1
/* @license */
/* @preserve */
foo;bar;
----------
foo;
bar;
/*! For license information please see test.js */
########## 2
/* @license */
//! KEEP
foo;bar;
----------
foo;
bar;
/*! For license information please see test.js */
########## 3
/* @license */
/*! KEEP */
foo;bar;
----------
foo;
bar;
/*! For license information please see test.js */