mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +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(());
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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(" */");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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`],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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