mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(transformer): add transform property-literal plugin (#1458)
1. Add `transform-property-literal-plugin` 2. Passing 2 testcases. --------- Co-authored-by: Wenzhe Wang <mysteryven@gmail.com>
This commit is contained in:
parent
8934ddb590
commit
f66e4d8ac3
10 changed files with 173 additions and 8 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1920,6 +1920,7 @@ dependencies = [
|
||||||
"oxc_semantic",
|
"oxc_semantic",
|
||||||
"oxc_span",
|
"oxc_span",
|
||||||
"oxc_syntax",
|
"oxc_syntax",
|
||||||
|
"phf",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@ oxc_syntax = { workspace = true }
|
||||||
oxc_semantic = { workspace = true }
|
oxc_semantic = { workspace = true }
|
||||||
oxc_diagnostics = { workspace = true }
|
oxc_diagnostics = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
phf = { workspace = true, features = ["macros"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
oxc_parser = { workspace = true }
|
oxc_parser = { workspace = true }
|
||||||
|
|
|
||||||
2
crates/oxc_transformer/src/es3/mod.rs
Normal file
2
crates/oxc_transformer/src/es3/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
mod property_literals;
|
||||||
|
pub use property_literals::PropertyLiteral;
|
||||||
34
crates/oxc_transformer/src/es3/property_literals.rs
Normal file
34
crates/oxc_transformer/src/es3/property_literals.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
use oxc_ast::{ast::*, AstBuilder};
|
||||||
|
use oxc_span::SPAN;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::utils::is_valid_es3_identifier;
|
||||||
|
use crate::{TransformOptions, TransformTarget};
|
||||||
|
|
||||||
|
/// ES3: PropertyLiteral
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// * <https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-property-literals/src/index.js>
|
||||||
|
pub struct PropertyLiteral<'a> {
|
||||||
|
ast: Rc<AstBuilder<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PropertyLiteral<'a> {
|
||||||
|
pub fn new(ast: Rc<AstBuilder<'a>>, options: &TransformOptions) -> Option<Self> {
|
||||||
|
(options.target <= TransformTarget::ES3 || options.property_literals).then(|| Self { ast })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_object_property<'b>(&mut self, expr: &'b mut ObjectProperty<'a>) {
|
||||||
|
if expr.computed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let PropertyKey::Identifier(ident) = &expr.key {
|
||||||
|
if !is_valid_es3_identifier(&ident.name) {
|
||||||
|
let string_lit = self
|
||||||
|
.ast
|
||||||
|
.literal_string_expression(StringLiteral::new(SPAN, ident.name.clone()));
|
||||||
|
expr.key = PropertyKey::Expression(string_lit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ mod es2019;
|
||||||
mod es2020;
|
mod es2020;
|
||||||
mod es2021;
|
mod es2021;
|
||||||
mod es2022;
|
mod es2022;
|
||||||
|
mod es3;
|
||||||
mod options;
|
mod options;
|
||||||
mod react_jsx;
|
mod react_jsx;
|
||||||
mod regexp;
|
mod regexp;
|
||||||
|
|
@ -34,8 +35,8 @@ use oxc_span::SourceType;
|
||||||
use crate::{
|
use crate::{
|
||||||
context::TransformerCtx, es2015::ShorthandProperties, es2016::ExponentiationOperator,
|
context::TransformerCtx, es2015::ShorthandProperties, es2016::ExponentiationOperator,
|
||||||
es2019::OptionalCatchBinding, es2020::NullishCoalescingOperator,
|
es2019::OptionalCatchBinding, es2020::NullishCoalescingOperator,
|
||||||
es2021::LogicalAssignmentOperators, es2022::ClassStaticBlock, react_jsx::ReactJsx,
|
es2021::LogicalAssignmentOperators, es2022::ClassStaticBlock, es3::PropertyLiteral,
|
||||||
regexp::RegexpFlags, typescript::TypeScript, utils::CreateVars,
|
react_jsx::ReactJsx, regexp::RegexpFlags, typescript::TypeScript, utils::CreateVars,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
|
|
@ -63,6 +64,7 @@ pub struct Transformer<'a> {
|
||||||
// es2015
|
// es2015
|
||||||
es2015_shorthand_properties: Option<ShorthandProperties<'a>>,
|
es2015_shorthand_properties: Option<ShorthandProperties<'a>>,
|
||||||
es2015_template_literals: Option<TemplateLiterals<'a>>,
|
es2015_template_literals: Option<TemplateLiterals<'a>>,
|
||||||
|
es3_property_literal: Option<PropertyLiteral<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Transformer<'a> {
|
impl<'a> Transformer<'a> {
|
||||||
|
|
@ -91,6 +93,7 @@ impl<'a> Transformer<'a> {
|
||||||
es2016_exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ast), ctx.clone(), &options),
|
es2016_exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ast), ctx.clone(), &options),
|
||||||
es2015_shorthand_properties: ShorthandProperties::new(Rc::clone(&ast), &options),
|
es2015_shorthand_properties: ShorthandProperties::new(Rc::clone(&ast), &options),
|
||||||
es2015_template_literals: TemplateLiterals::new(Rc::clone(&ast), &options),
|
es2015_template_literals: TemplateLiterals::new(Rc::clone(&ast), &options),
|
||||||
|
es3_property_literal: PropertyLiteral::new(Rc::clone(&ast ), &options),
|
||||||
react_jsx: options.react_jsx.map(|options| ReactJsx::new(Rc::clone(&ast), ctx.clone(), options)),
|
react_jsx: options.react_jsx.map(|options| ReactJsx::new(Rc::clone(&ast), ctx.clone(), options)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,6 +163,7 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
|
||||||
|
|
||||||
fn visit_object_property(&mut self, prop: &mut ObjectProperty<'a>) {
|
fn visit_object_property(&mut self, prop: &mut ObjectProperty<'a>) {
|
||||||
self.es2015_shorthand_properties.as_mut().map(|t| t.transform_object_property(prop));
|
self.es2015_shorthand_properties.as_mut().map(|t| t.transform_object_property(prop));
|
||||||
|
self.es3_property_literal.as_mut().map(|t| t.transform_object_property(prop));
|
||||||
|
|
||||||
self.visit_property_key(&mut prop.key);
|
self.visit_property_key(&mut prop.key);
|
||||||
self.visit_expression(&mut prop.value);
|
self.visit_expression(&mut prop.value);
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,13 @@ pub struct TransformOptions {
|
||||||
pub shorthand_properties: bool,
|
pub shorthand_properties: bool,
|
||||||
pub sticky_regex: bool,
|
pub sticky_regex: bool,
|
||||||
pub template_literals: bool,
|
pub template_literals: bool,
|
||||||
|
pub property_literals: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See <https://www.typescriptlang.org/tsconfig#target>
|
/// See <https://www.typescriptlang.org/tsconfig#target>
|
||||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum TransformTarget {
|
pub enum TransformTarget {
|
||||||
|
ES3,
|
||||||
ES5,
|
ES5,
|
||||||
ES2015,
|
ES2015,
|
||||||
ES2016,
|
ES2016,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ use std::mem;
|
||||||
|
|
||||||
use oxc_allocator::Vec;
|
use oxc_allocator::Vec;
|
||||||
use oxc_ast::ast::*;
|
use oxc_ast::ast::*;
|
||||||
use oxc_span::Span;
|
use oxc_span::{Atom, Span};
|
||||||
|
use oxc_syntax::unicode_id_start::{is_id_continue, is_id_start};
|
||||||
|
|
||||||
use crate::context::TransformerCtx;
|
use crate::context::TransformerCtx;
|
||||||
|
|
||||||
|
|
@ -50,3 +51,120 @@ pub trait CreateVars<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const RESERVED_WORDS_ES3_ONLY: phf::Set<&str> = phf::phf_set![
|
||||||
|
"abstract",
|
||||||
|
"boolean",
|
||||||
|
"byte",
|
||||||
|
"char",
|
||||||
|
"double",
|
||||||
|
"enum",
|
||||||
|
"final",
|
||||||
|
"float",
|
||||||
|
"goto",
|
||||||
|
"implements",
|
||||||
|
"int",
|
||||||
|
"interface",
|
||||||
|
"long",
|
||||||
|
"native",
|
||||||
|
"package",
|
||||||
|
"private",
|
||||||
|
"protected",
|
||||||
|
"public",
|
||||||
|
"short",
|
||||||
|
"static",
|
||||||
|
"synchronized",
|
||||||
|
"throws",
|
||||||
|
"transient",
|
||||||
|
"volatile",
|
||||||
|
];
|
||||||
|
|
||||||
|
const RESERVED_WORD_STRICT: phf::Set<&str> = phf::phf_set![
|
||||||
|
"implements",
|
||||||
|
"interface",
|
||||||
|
"let",
|
||||||
|
"package",
|
||||||
|
"private",
|
||||||
|
"protected",
|
||||||
|
"public",
|
||||||
|
"static",
|
||||||
|
"yield",
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const KEYWORDS: phf::Set<&str> = phf::phf_set![
|
||||||
|
"break",
|
||||||
|
"case",
|
||||||
|
"catch",
|
||||||
|
"continue",
|
||||||
|
"debugger",
|
||||||
|
"default",
|
||||||
|
"do",
|
||||||
|
"else",
|
||||||
|
"finally",
|
||||||
|
"for",
|
||||||
|
"function",
|
||||||
|
"if",
|
||||||
|
"return",
|
||||||
|
"switch",
|
||||||
|
"throw",
|
||||||
|
"try",
|
||||||
|
"var",
|
||||||
|
"const",
|
||||||
|
"while",
|
||||||
|
"with",
|
||||||
|
"new",
|
||||||
|
"this",
|
||||||
|
"super",
|
||||||
|
"class",
|
||||||
|
"extends",
|
||||||
|
"export",
|
||||||
|
"import",
|
||||||
|
"null",
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
"in",
|
||||||
|
"instanceof",
|
||||||
|
"typeof",
|
||||||
|
"void",
|
||||||
|
"delete",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// https://github.com/babel/babel/blob/ff3481746a830e0e94626de4c4cb075ea5f2f5dc/packages/babel-helper-validator-identifier/src/identifier.ts#L85-L109
|
||||||
|
pub fn is_identifier_name(name: &Atom) -> bool {
|
||||||
|
let string = name.as_str();
|
||||||
|
if string.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let mut is_first = true;
|
||||||
|
for ch in string.chars() {
|
||||||
|
if is_first {
|
||||||
|
is_first = false;
|
||||||
|
if !is_id_start(ch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if !is_id_continue(ch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_valid_identifier(name: &Atom, reserved: bool) -> bool {
|
||||||
|
if reserved && (KEYWORDS.contains(name.as_str()) || is_strict_reserved_word(name, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
is_identifier_name(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_strict_reserved_word(name: &Atom, in_module: bool) -> bool {
|
||||||
|
is_reserved_word(name, in_module) || RESERVED_WORD_STRICT.contains(name.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_reserved_word(name: &Atom, in_module: bool) -> bool {
|
||||||
|
(in_module && name.as_str() == "await") || name.as_str() == "enum"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isValidES3Identifier.ts#L35
|
||||||
|
pub fn is_valid_es3_identifier(name: &Atom) -> bool {
|
||||||
|
is_valid_identifier(name, true) && !RESERVED_WORDS_ES3_ONLY.contains(name.as_str())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Passed: 262/1080
|
Passed: 265/1081
|
||||||
|
|
||||||
# All Passed:
|
# All Passed:
|
||||||
* babel-plugin-transform-numeric-separator
|
* babel-plugin-transform-numeric-separator
|
||||||
|
|
@ -6,6 +6,7 @@ Passed: 262/1080
|
||||||
* babel-plugin-transform-json-strings
|
* babel-plugin-transform-json-strings
|
||||||
* babel-plugin-transform-shorthand-properties
|
* babel-plugin-transform-shorthand-properties
|
||||||
* babel-plugin-transform-sticky-regex
|
* babel-plugin-transform-sticky-regex
|
||||||
|
* babel-plugin-transform-property-literals
|
||||||
|
|
||||||
|
|
||||||
# babel-plugin-transform-unicode-sets-regex (0/4)
|
# babel-plugin-transform-unicode-sets-regex (0/4)
|
||||||
|
|
@ -843,15 +844,13 @@ Passed: 262/1080
|
||||||
* regression/11061/input.mjs
|
* regression/11061/input.mjs
|
||||||
* variable-declaration/non-null-in-optional-chain/input.ts
|
* variable-declaration/non-null-in-optional-chain/input.ts
|
||||||
|
|
||||||
# babel-plugin-transform-react-jsx (137/156)
|
# babel-plugin-transform-react-jsx (139/156)
|
||||||
* autoImport/after-polyfills-compiled-to-cjs/input.mjs
|
* autoImport/after-polyfills-compiled-to-cjs/input.mjs
|
||||||
* autoImport/complicated-scope-module/input.js
|
* autoImport/complicated-scope-module/input.js
|
||||||
* react/arrow-functions/input.js
|
* react/arrow-functions/input.js
|
||||||
* react/optimisation.react.constant-elements/input.js
|
* react/optimisation.react.constant-elements/input.js
|
||||||
* react/should-add-quotes-es3/input.js
|
|
||||||
* react-automatic/arrow-functions/input.js
|
* react-automatic/arrow-functions/input.js
|
||||||
* react-automatic/optimisation.react.constant-elements/input.js
|
* react-automatic/optimisation.react.constant-elements/input.js
|
||||||
* react-automatic/should-add-quotes-es3/input.js
|
|
||||||
* react-automatic/should-handle-attributed-elements/input.js
|
* react-automatic/should-handle-attributed-elements/input.js
|
||||||
* react-automatic/should-throw-when-filter-is-specified/input.js
|
* react-automatic/should-throw-when-filter-is-specified/input.js
|
||||||
* regression/issue-12478-automatic/input.js
|
* regression/issue-12478-automatic/input.js
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,8 @@ const CASES: &[&str] = &[
|
||||||
"babel-plugin-transform-sticky-regex",
|
"babel-plugin-transform-sticky-regex",
|
||||||
"babel-plugin-transform-unicode-regex",
|
"babel-plugin-transform-unicode-regex",
|
||||||
"babel-plugin-transform-template-literals",
|
"babel-plugin-transform-template-literals",
|
||||||
|
// ES3
|
||||||
|
"babel-plugin-transform-property-literals",
|
||||||
// TypeScript
|
// TypeScript
|
||||||
"babel-plugin-transform-typescript",
|
"babel-plugin-transform-typescript",
|
||||||
// React
|
// React
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ pub trait TestCase {
|
||||||
shorthand_properties: options.get_plugin("transform-shorthand-properties").is_some(),
|
shorthand_properties: options.get_plugin("transform-shorthand-properties").is_some(),
|
||||||
sticky_regex: options.get_plugin("transform-sticky-regex").is_some(),
|
sticky_regex: options.get_plugin("transform-sticky-regex").is_some(),
|
||||||
template_literals: options.get_plugin("transform-template-literals").is_some(),
|
template_literals: options.get_plugin("transform-template-literals").is_some(),
|
||||||
|
property_literals: options.get_plugin("transform-property-literals").is_some(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +256,7 @@ impl TestCase for ConformanceTestCase {
|
||||||
println!("Input:\n");
|
println!("Input:\n");
|
||||||
println!("{input}\n");
|
println!("{input}\n");
|
||||||
println!("Options:");
|
println!("Options:");
|
||||||
println!("{transform_options:?}\n");
|
println!("{transform_options:#?}\n");
|
||||||
if babel_options.throws.is_some() {
|
if babel_options.throws.is_some() {
|
||||||
println!("Expected Errors:\n");
|
println!("Expected Errors:\n");
|
||||||
println!("{output}\n");
|
println!("{output}\n");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue