Skip to content

Commit

Permalink
feat(codegen): print linked and external legal comment (#7059)
Browse files Browse the repository at this point in the history
part of #7050
  • Loading branch information
Boshen committed Nov 2, 2024
1 parent 38d1f78 commit 413973d
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 56 deletions.
2 changes: 1 addition & 1 deletion crates/oxc_codegen/examples/sourcemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
72 changes: 46 additions & 26 deletions crates/oxc_codegen/src/comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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__"];
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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(" */");
}
_ => {}
}
}

Expand Down
35 changes: 21 additions & 14 deletions crates/oxc_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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,
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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>,
}

Expand Down Expand Up @@ -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,
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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`],
Expand Down
18 changes: 9 additions & 9 deletions crates/oxc_codegen/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
}
}

Expand Down
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![
Expand All @@ -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
Expand Up @@ -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]) {
Expand All @@ -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
});
Expand Down
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 */

0 comments on commit 413973d

Please sign in to comment.