mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(codegen): print linked and external legal comment (#7059)
part of #7050
This commit is contained in:
parent
38d1f78754
commit
413973df66
7 changed files with 137 additions and 56 deletions
|
|
@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let CodegenReturn { code, map } = CodeGenerator::new()
|
let CodegenReturn { code, map, .. } = CodeGenerator::new()
|
||||||
.with_options(CodegenOptions {
|
.with_options(CodegenOptions {
|
||||||
source_map_path: Some(path.to_path_buf()),
|
source_map_path: Some(path.to_path_buf()),
|
||||||
..CodegenOptions::default()
|
..CodegenOptions::default()
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use rustc_hash::FxHashMap;
|
||||||
use oxc_ast::{Comment, CommentKind};
|
use oxc_ast::{Comment, CommentKind};
|
||||||
use oxc_syntax::identifier::is_line_terminator;
|
use oxc_syntax::identifier::is_line_terminator;
|
||||||
|
|
||||||
use crate::Codegen;
|
use crate::{Codegen, LegalComment};
|
||||||
|
|
||||||
static ANNOTATION_MATCHER: Lazy<DoubleArrayAhoCorasick<usize>> = Lazy::new(|| {
|
static ANNOTATION_MATCHER: Lazy<DoubleArrayAhoCorasick<usize>> = Lazy::new(|| {
|
||||||
let patterns = vec!["#__NO_SIDE_EFFECTS__", "@__NO_SIDE_EFFECTS__", "@__PURE__", "#__PURE__"];
|
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
|
ANNOTATION_MATCHER.find_iter(comment_content).count() != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_legal_comment(&self, comment: &Comment) -> bool {
|
/// Whether to keep leading comments.
|
||||||
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.
|
|
||||||
fn is_leading_comments(&self, comment: &Comment) -> bool {
|
fn is_leading_comments(&self, comment: &Comment) -> bool {
|
||||||
comment.preceded_by_newline
|
comment.preceded_by_newline
|
||||||
&& (comment.is_jsdoc(self.source_text)
|
&& (comment.is_jsdoc(self.source_text)
|
||||||
|
|
@ -89,11 +78,36 @@ impl<'a> Codegen<'a> {
|
||||||
let Some(comments) = self.comments.remove(&start) else {
|
let Some(comments) = self.comments.remove(&start) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let (comments, unused_comments): (Vec<_>, Vec<_>) =
|
|
||||||
comments.into_iter().partition(|comment| {
|
let mut leading_comments = vec![];
|
||||||
self.is_leading_comments(comment) || self.is_legal_comment(comment)
|
let mut unused_comments = vec![];
|
||||||
});
|
|
||||||
self.print_comments(start, &comments, unused_comments);
|
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) {
|
pub(crate) fn print_annotation_comments(&mut self, node_start: u32) {
|
||||||
|
|
@ -148,16 +162,22 @@ impl<'a> Codegen<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_print_eof_legal_comments(&mut self, comments: &[Comment]) {
|
pub(crate) fn try_print_eof_legal_comments(&mut self) {
|
||||||
if !self.options.legal_comments.is_eof() {
|
match self.options.legal_comments.clone() {
|
||||||
return;
|
LegalComment::Eof => {
|
||||||
}
|
let comments = self.legal_comments.drain(..).collect::<Vec<_>>();
|
||||||
for c in comments {
|
for c in comments {
|
||||||
if c.is_legal(self.source_text) {
|
self.print_comment(&c);
|
||||||
self.print_comment(c);
|
|
||||||
self.print_hard_newline();
|
self.print_hard_newline();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LegalComment::Linked(path) => {
|
||||||
|
self.print_str("/*! For license information please see ");
|
||||||
|
self.print_str(&path);
|
||||||
|
self.print_str(" */");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_comments(&mut self, start: u32, comments: &[Comment], unused_comments: Vec<Comment>) {
|
fn print_comments(&mut self, start: u32, comments: &[Comment], unused_comments: Vec<Comment>) {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ mod sourcemap_builder;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use oxc_ast::ast::{
|
use oxc_ast::ast::{
|
||||||
BindingIdentifier, BlockStatement, Expression, IdentifierReference, Program, Statement,
|
BindingIdentifier, BlockStatement, Comment, Expression, IdentifierReference, Program, Statement,
|
||||||
};
|
};
|
||||||
use oxc_mangler::Mangler;
|
use oxc_mangler::Mangler;
|
||||||
use oxc_span::{GetSpan, Span};
|
use oxc_span::{GetSpan, Span};
|
||||||
|
|
@ -40,6 +40,7 @@ pub use crate::{
|
||||||
pub type CodeGenerator<'a> = Codegen<'a>;
|
pub type CodeGenerator<'a> = Codegen<'a>;
|
||||||
|
|
||||||
/// Output from [`Codegen::build`]
|
/// Output from [`Codegen::build`]
|
||||||
|
#[non_exhaustive]
|
||||||
pub struct CodegenReturn {
|
pub struct CodegenReturn {
|
||||||
/// The generated source code.
|
/// The generated source code.
|
||||||
pub code: String,
|
pub code: String,
|
||||||
|
|
@ -48,6 +49,9 @@ pub struct CodegenReturn {
|
||||||
///
|
///
|
||||||
/// You must set [`CodegenOptions::source_map_path`] for this to be [`Some`].
|
/// You must set [`CodegenOptions::source_map_path`] for this to be [`Some`].
|
||||||
pub map: Option<oxc_sourcemap::SourceMap>,
|
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.
|
/// A code generator for printing JavaScript and TypeScript code.
|
||||||
|
|
@ -74,8 +78,6 @@ pub struct Codegen<'a> {
|
||||||
/// Original source code of the AST
|
/// Original source code of the AST
|
||||||
source_text: &'a str,
|
source_text: &'a str,
|
||||||
|
|
||||||
comments: CommentsMap,
|
|
||||||
|
|
||||||
mangler: Option<Mangler>,
|
mangler: Option<Mangler>,
|
||||||
|
|
||||||
/// Output Code
|
/// Output Code
|
||||||
|
|
@ -96,6 +98,17 @@ pub struct Codegen<'a> {
|
||||||
start_of_stmt: usize,
|
start_of_stmt: usize,
|
||||||
start_of_arrow_expr: usize,
|
start_of_arrow_expr: usize,
|
||||||
start_of_default_export: 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
|
/// Start of comment that needs to be moved to the before VariableDeclarator
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
|
|
@ -110,13 +123,6 @@ pub struct Codegen<'a> {
|
||||||
/// ```
|
/// ```
|
||||||
start_of_annotation_comment: Option<u32>,
|
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>,
|
sourcemap_builder: Option<SourcemapBuilder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,8 +154,6 @@ impl<'a> Codegen<'a> {
|
||||||
Self {
|
Self {
|
||||||
options: CodegenOptions::default(),
|
options: CodegenOptions::default(),
|
||||||
source_text: "",
|
source_text: "",
|
||||||
comments: CommentsMap::default(),
|
|
||||||
start_of_annotation_comment: None,
|
|
||||||
mangler: None,
|
mangler: None,
|
||||||
code: CodeBuffer::default(),
|
code: CodeBuffer::default(),
|
||||||
needs_semicolon: false,
|
needs_semicolon: false,
|
||||||
|
|
@ -164,6 +168,9 @@ impl<'a> Codegen<'a> {
|
||||||
start_of_default_export: 0,
|
start_of_default_export: 0,
|
||||||
indent: 0,
|
indent: 0,
|
||||||
quote: b'"',
|
quote: b'"',
|
||||||
|
comments: CommentsMap::default(),
|
||||||
|
start_of_annotation_comment: None,
|
||||||
|
legal_comments: vec![],
|
||||||
sourcemap_builder: None,
|
sourcemap_builder: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -198,10 +205,10 @@ impl<'a> Codegen<'a> {
|
||||||
self.sourcemap_builder = Some(SourcemapBuilder::new(path, program.source_text));
|
self.sourcemap_builder = Some(SourcemapBuilder::new(path, program.source_text));
|
||||||
}
|
}
|
||||||
program.print(&mut self, Context::default());
|
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 code = self.code.into_string();
|
||||||
let map = self.sourcemap_builder.map(SourcemapBuilder::into_sourcemap);
|
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`],
|
/// Turn what's been built so far into a string. Like [`build`],
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::path::PathBuf;
|
||||||
/// Legal comment
|
/// Legal comment
|
||||||
///
|
///
|
||||||
/// <https://esbuild.github.io/api/#legal-comments>
|
/// <https://esbuild.github.io/api/#legal-comments>
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||||
pub enum LegalComment {
|
pub enum LegalComment {
|
||||||
/// Do not preserve any legal comments (default).
|
/// Do not preserve any legal comments (default).
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -12,26 +12,26 @@ pub enum LegalComment {
|
||||||
Inline,
|
Inline,
|
||||||
/// Move all legal comments to the end of the file.
|
/// Move all legal comments to the end of the file.
|
||||||
Eof,
|
Eof,
|
||||||
/// Move all legal comments to a .LEGAL.txt file and link to them with a comment.
|
/// Return all legal comments and link then to them with a comment to the provided string.
|
||||||
Linked,
|
Linked(String),
|
||||||
/// Move all legal comments to a .LEGAL.txt file but to not link to them.
|
/// Move all legal comments to a .LEGAL.txt file but to not link to them.
|
||||||
External,
|
External,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LegalComment {
|
impl LegalComment {
|
||||||
/// Is None.
|
/// Is None.
|
||||||
pub fn is_none(self) -> bool {
|
pub fn is_none(&self) -> bool {
|
||||||
self == Self::None
|
*self == Self::None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is inline mode.
|
/// Is inline mode.
|
||||||
pub fn is_inline(self) -> bool {
|
pub fn is_inline(&self) -> bool {
|
||||||
self == Self::Inline
|
*self == Self::Inline
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is EOF mode.
|
/// Is EOF mode.
|
||||||
pub fn is_eof(self) -> bool {
|
pub fn is_eof(&self) -> bool {
|
||||||
self == Self::Eof
|
*self == Self::Eof
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use oxc_codegen::{CodegenOptions, LegalComment};
|
use oxc_codegen::{CodegenOptions, LegalComment};
|
||||||
|
|
||||||
use crate::{snapshot, snapshot_options};
|
use crate::{codegen_options, snapshot, snapshot_options};
|
||||||
|
|
||||||
fn cases() -> Vec<&'static str> {
|
fn cases() -> Vec<&'static str> {
|
||||||
vec![
|
vec![
|
||||||
|
|
@ -21,3 +21,22 @@ fn legal_eof_comment() {
|
||||||
let options = CodegenOptions { legal_comments: LegalComment::Eof, ..Default::default() };
|
let options = CodegenOptions { legal_comments: LegalComment::Eof, ..Default::default() };
|
||||||
snapshot_options("legal_eof_comments", &cases(), &options);
|
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 ");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,21 @@ pub mod ts;
|
||||||
pub mod unit;
|
pub mod unit;
|
||||||
|
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
use oxc_codegen::{CodeGenerator, CodegenOptions};
|
use oxc_codegen::{CodeGenerator, CodegenOptions, CodegenReturn};
|
||||||
use oxc_parser::Parser;
|
use oxc_parser::Parser;
|
||||||
use oxc_span::SourceType;
|
use oxc_span::SourceType;
|
||||||
|
|
||||||
pub fn codegen(source_text: &str) -> String {
|
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 allocator = Allocator::default();
|
||||||
let source_type = SourceType::ts();
|
let source_type = SourceType::ts();
|
||||||
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
||||||
let mut options = options.clone();
|
let mut options = options.clone();
|
||||||
options.single_quote = true;
|
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]) {
|
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;
|
use std::fmt::Write;
|
||||||
|
|
||||||
let snapshot = cases.iter().enumerate().fold(String::new(), |mut w, (i, case)| {
|
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();
|
write!(w, "########## {i}\n{case}\n----------\n{result}\n",).unwrap();
|
||||||
w
|
w
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
Loading…
Reference in a new issue