diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index fb2acbf448568..49799079e3e85 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -46,9 +46,9 @@ language-tags = { workspace = true } mime_guess = { workspace = true } url = { workspace = true } -rust-lapper = "1.1.0" -once_cell = "1.19.0" -memchr = "2.7.1" +rust-lapper = "1.1.0" +once_cell = "1.19.0" +memchr = "2.7.1" json-strip-comments = "1.0.1" [dev-dependencies] diff --git a/crates/oxc_transformer/src/es2019/json_strings.rs b/crates/oxc_transformer/src/es2019/json_strings.rs new file mode 100644 index 0000000000000..b4d223fd0a7ff --- /dev/null +++ b/crates/oxc_transformer/src/es2019/json_strings.rs @@ -0,0 +1,56 @@ +use oxc_ast::ast::*; +use oxc_span::Atom; +use oxc_syntax::identifier::{LS, PS}; + +use crate::options::{TransformOptions, TransformTarget}; + +/// ES2019: Json Strings +/// +/// References: +/// * +/// * +pub struct JsonStrings; + +impl JsonStrings { + pub fn new(options: &TransformOptions) -> Option { + (options.target < TransformTarget::ES2019 || options.json_strings).then(|| Self {}) + } + + // Allow `U+2028` and `U+2029` in string literals + // + // + fn normalize_str(str: &str) -> Option { + if !str.contains(LS) && !str.contains(PS) { + return None; + } + let mut buf = String::new(); + let mut is_escaped = false; + for c in str.chars() { + match (is_escaped, c) { + (false, LS) => buf.push_str("\\u2028"), + (false, PS) => buf.push_str("\\u2029"), + _ => buf.push(c), + } + is_escaped = !is_escaped && matches!(c, '\\'); + } + Some(buf.into()) + } + + #[allow(clippy::unused_self)] + // TODO oxc_codegen currently prints json strings correctly, + // but we need a way to turn off this behaviour from codegen + // and do the transformation here. + pub fn transform_string_literal(&mut self, _literal: &mut StringLiteral) { + // let str = &self.ctx.semantic().source_text()[literal.span.start as usize + 1..literal.span.end as usize - 1]; + // if let Some(value) = Self::normalize_str(str) { + // literal.value = value; + // } + } + + #[allow(clippy::unused_self)] + pub fn transform_directive(&mut self, directive: &mut Directive) { + if let Some(value) = Self::normalize_str(directive.directive.as_str()) { + directive.directive = value; + } + } +} diff --git a/crates/oxc_transformer/src/es2019/mod.rs b/crates/oxc_transformer/src/es2019/mod.rs index 6b7b65c77fafb..4330b086186d6 100644 --- a/crates/oxc_transformer/src/es2019/mod.rs +++ b/crates/oxc_transformer/src/es2019/mod.rs @@ -1,3 +1,4 @@ +mod json_strings; mod optional_catch_binding; - +pub use json_strings::JsonStrings; pub use optional_catch_binding::OptionalCatchBinding; diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 1a58c3c9e714d..24d41af086dc9 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -35,10 +35,18 @@ use oxc_span::SourceType; use proposals::Decorators; use crate::{ - context::TransformerCtx, es2015::*, es2016::ExponentiationOperator, - es2019::OptionalCatchBinding, es2020::NullishCoalescingOperator, - es2021::LogicalAssignmentOperators, es2022::ClassStaticBlock, es3::PropertyLiteral, - react_jsx::ReactJsx, regexp::RegexpFlags, typescript::TypeScript, utils::CreateVars, + context::TransformerCtx, + es2015::*, + es2016::ExponentiationOperator, + es2019::{JsonStrings, OptionalCatchBinding}, + es2020::NullishCoalescingOperator, + es2021::LogicalAssignmentOperators, + es2022::ClassStaticBlock, + es3::PropertyLiteral, + react_jsx::ReactJsx, + regexp::RegexpFlags, + typescript::TypeScript, + utils::CreateVars, }; pub use crate::{ @@ -64,6 +72,7 @@ pub struct Transformer<'a> { // es2020 es2020_nullish_coalescing_operators: Option>, // es2019 + es2019_json_strings: Option, es2019_optional_catch_binding: Option>, // es2016 es2016_exponentiation_operator: Option>, @@ -105,6 +114,7 @@ impl<'a> Transformer<'a> { // es2020 es2020_nullish_coalescing_operators: NullishCoalescingOperator::new(Rc::clone(&ast), ctx.clone(), &options), // es2019 + es2019_json_strings: JsonStrings::new(&options), es2019_optional_catch_binding: OptionalCatchBinding::new(Rc::clone(&ast), &options), // es2016 es2016_exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ast), ctx.clone(), &options), @@ -291,4 +301,16 @@ impl<'a> VisitMut<'a> for Transformer<'a> { self.leave_node(kind); } + + fn visit_directive(&mut self, directive: &mut Directive) { + self.es2019_json_strings + .as_mut() + .map(|t: &mut JsonStrings| t.transform_directive(directive)); + } + + fn visit_string_literal(&mut self, lit: &mut StringLiteral) { + self.es2019_json_strings + .as_mut() + .map(|t: &mut JsonStrings| t.transform_string_literal(lit)); + } } diff --git a/crates/oxc_transformer/src/options.rs b/crates/oxc_transformer/src/options.rs index 1ade2f5bbbb3f..e658cefe3a74f 100644 --- a/crates/oxc_transformer/src/options.rs +++ b/crates/oxc_transformer/src/options.rs @@ -22,6 +22,7 @@ pub struct TransformOptions { pub nullish_coalescing_operator: Option, // es2019 pub optional_catch_binding: bool, + pub json_strings: bool, // es2016 pub exponentiation_operator: bool, // es2015 diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index ee7a6cd46a3c1..c787b2bb8f781 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,8 +1,9 @@ -Passed: 327/1369 +Passed: 329/1369 # All Passed: * babel-plugin-transform-numeric-separator * babel-plugin-transform-optional-catch-binding +* babel-plugin-transform-json-strings * babel-plugin-transform-shorthand-properties * babel-plugin-transform-sticky-regex * babel-plugin-transform-instanceof @@ -607,10 +608,6 @@ Passed: 327/1369 * transparent-expr-wrappers/ts-as-member-expression/input.ts * transparent-expr-wrappers/ts-parenthesized-expression-member-call/input.ts -# babel-plugin-transform-json-strings (2/4) -* json-strings/directive-line-separator/input.js -* json-strings/directive-paragraph-separator/input.js - # babel-plugin-transform-async-generator-functions (0/22) * async-generators/class-method/input.js * async-generators/class-private-method/input.js diff --git a/tasks/transform_conformance/src/test_case.rs b/tasks/transform_conformance/src/test_case.rs index 9edccc9654c7e..4e60ec7071552 100644 --- a/tasks/transform_conformance/src/test_case.rs +++ b/tasks/transform_conformance/src/test_case.rs @@ -111,6 +111,7 @@ pub trait TestCase { nullish_coalescing_operator: options .get_plugin("transform-nullish-coalescing-operator") .map(get_options::), + json_strings: options.get_plugin("transform-json-strings").is_some(), optional_catch_binding: options .get_plugin("transform-optional-catch-binding") .is_some(),