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:
IWANABETHATGUY 2023-11-21 23:07:43 +08:00 committed by GitHub
parent 8934ddb590
commit f66e4d8ac3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 173 additions and 8 deletions

1
Cargo.lock generated
View file

@ -1920,6 +1920,7 @@ dependencies = [
"oxc_semantic", "oxc_semantic",
"oxc_span", "oxc_span",
"oxc_syntax", "oxc_syntax",
"phf",
"serde", "serde",
] ]

View file

@ -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 }

View file

@ -0,0 +1,2 @@
mod property_literals;
pub use property_literals::PropertyLiteral;

View 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);
}
}
}
}

View file

@ -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);

View file

@ -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,

View file

@ -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())
}

View file

@ -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

View file

@ -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

View file

@ -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");