From 413973df663c850a16161e18d62c7ccebf2f74df Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sat, 2 Nov 2024 04:08:14 +0000 Subject: [PATCH] feat(codegen): print linked and external legal comment (#7059) part of #7050 --- crates/oxc_codegen/examples/sourcemap.rs | 2 +- crates/oxc_codegen/src/comment.rs | 72 ++++++++++++------- crates/oxc_codegen/src/lib.rs | 35 +++++---- crates/oxc_codegen/src/options.rs | 18 ++--- .../tests/integration/legal_comments.rs | 21 +++++- crates/oxc_codegen/tests/integration/main.rs | 10 +-- .../snapshots/legal_linked_comments.snap | 35 +++++++++ 7 files changed, 137 insertions(+), 56 deletions(-) create mode 100644 crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap diff --git a/crates/oxc_codegen/examples/sourcemap.rs b/crates/oxc_codegen/examples/sourcemap.rs index 78b16aafc1629..e3d60072ddbf2 100644 --- a/crates/oxc_codegen/examples/sourcemap.rs +++ b/crates/oxc_codegen/examples/sourcemap.rs @@ -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() diff --git a/crates/oxc_codegen/src/comment.rs b/crates/oxc_codegen/src/comment.rs index 8179f31c64840..d204c8dd88490 100644 --- a/crates/oxc_codegen/src/comment.rs +++ b/crates/oxc_codegen/src/comment.rs @@ -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> = 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::>(); + 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(" */"); } + _ => {} } } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index 320123de33b91..b55933a8de0d7 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -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, + + /// All the legal comments returned from [LegalComment::Linked] or [LegalComment::External]. + pub legal_comments: Vec, } /// 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, /// 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, /// 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, - /// Track the current indentation level - indent: u32, - - /// Fast path for [CodegenOptions::single_quote] - quote: u8, - - // Builders sourcemap_builder: Option, } @@ -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`], diff --git a/crates/oxc_codegen/src/options.rs b/crates/oxc_codegen/src/options.rs index 64b6ee73f8866..14d65796c43b5 100644 --- a/crates/oxc_codegen/src/options.rs +++ b/crates/oxc_codegen/src/options.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; /// Legal comment /// /// -#[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 } } diff --git a/crates/oxc_codegen/tests/integration/legal_comments.rs b/crates/oxc_codegen/tests/integration/legal_comments.rs index e1abc8897f004..bfee378412974 100644 --- a/crates/oxc_codegen/tests/integration/legal_comments.rs +++ b/crates/oxc_codegen/tests/integration/legal_comments.rs @@ -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 "); +} diff --git a/crates/oxc_codegen/tests/integration/main.rs b/crates/oxc_codegen/tests/integration/main.rs index e5b46d2eb4ca6..8071ead7c5a13 100644 --- a/crates/oxc_codegen/tests/integration/main.rs +++ b/crates/oxc_codegen/tests/integration/main.rs @@ -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 }); diff --git a/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap b/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap new file mode 100644 index 0000000000000..432167befeaad --- /dev/null +++ b/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap @@ -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 */