mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(transformer): Start on function_name transform. (#1510)
Co-authored-by: Boshen <boshenc@gmail.com>
This commit is contained in:
parent
4e05d1809f
commit
6cbc5dd75b
8 changed files with 223 additions and 3 deletions
|
|
@ -445,6 +445,10 @@ impl<'a> PropertyKey<'a> {
|
||||||
self.static_name().is_some_and(|n| n == name)
|
self.static_name().is_some_and(|n| n == name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_identifier(&self) -> bool {
|
||||||
|
matches!(self, Self::PrivateIdentifier(_) | Self::Identifier(_))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_private_identifier(&self) -> bool {
|
pub fn is_private_identifier(&self) -> bool {
|
||||||
matches!(self, Self::PrivateIdentifier(_))
|
matches!(self, Self::PrivateIdentifier(_))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ oxc_semantic = { workspace = true }
|
||||||
oxc_diagnostics = { workspace = true }
|
oxc_diagnostics = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
phf = { workspace = true, features = ["macros"] }
|
phf = { workspace = true, features = ["macros"] }
|
||||||
|
|
||||||
|
|
|
||||||
172
crates/oxc_transformer/src/es2015/function_name.rs
Normal file
172
crates/oxc_transformer/src/es2015/function_name.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
// use lazy_static::lazy_static;
|
||||||
|
use oxc_ast::{ast::*, AstBuilder, Visit};
|
||||||
|
use oxc_span::{Atom, Span};
|
||||||
|
use oxc_syntax::operator::AssignmentOperator;
|
||||||
|
use oxc_syntax::unicode_id_start::is_id_continue;
|
||||||
|
// use regex::Regex;
|
||||||
|
|
||||||
|
use crate::context::TransformerCtx;
|
||||||
|
use crate::options::TransformOptions;
|
||||||
|
use crate::utils::is_valid_identifier;
|
||||||
|
|
||||||
|
/// ES2015: Function Name
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// * <https://babel.dev/docs/babel-plugin-transform-function-name>
|
||||||
|
/// * <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-function-name>
|
||||||
|
pub struct FunctionName<'a> {
|
||||||
|
_ast: Rc<AstBuilder<'a>>,
|
||||||
|
ctx: TransformerCtx<'a>,
|
||||||
|
unicode_escapes: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FunctionName<'a> {
|
||||||
|
pub fn new(
|
||||||
|
_ast: &Rc<AstBuilder<'a>>,
|
||||||
|
_ctx: &TransformerCtx<'a>,
|
||||||
|
_options: &TransformOptions,
|
||||||
|
) -> Option<Self> {
|
||||||
|
// Disabled for now
|
||||||
|
None
|
||||||
|
// (options.target < TransformTarget::ES2015 || options.function_name).then(|| Self {
|
||||||
|
// ast,
|
||||||
|
// ctx,
|
||||||
|
// // TODO hook up the plugin
|
||||||
|
// unicode_escapes: true,
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_assignment_expression(&mut self, expr: &mut AssignmentExpression<'a>) {
|
||||||
|
if expr.right.is_function() && matches!(expr.operator, AssignmentOperator::Assign) {
|
||||||
|
if let AssignmentTarget::SimpleAssignmentTarget(
|
||||||
|
SimpleAssignmentTarget::AssignmentTargetIdentifier(target),
|
||||||
|
) = &expr.left
|
||||||
|
{
|
||||||
|
if let Some(id) =
|
||||||
|
create_valid_identifier(target.span, target.name.clone(), self.unicode_escapes)
|
||||||
|
{
|
||||||
|
self.transform_expression(&mut expr.right, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_object_expression(&mut self, expr: &mut ObjectExpression<'a>) {
|
||||||
|
for property_kind in expr.properties.iter_mut() {
|
||||||
|
if let ObjectPropertyKind::ObjectProperty(property) = property_kind {
|
||||||
|
if property.computed || !property.value.is_function() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = match &property.key {
|
||||||
|
PropertyKey::Identifier(ident) => create_valid_identifier(
|
||||||
|
ident.span,
|
||||||
|
ident.name.clone(),
|
||||||
|
self.unicode_escapes,
|
||||||
|
),
|
||||||
|
PropertyKey::PrivateIdentifier(ident) => create_valid_identifier(
|
||||||
|
ident.span,
|
||||||
|
ident.name.clone(),
|
||||||
|
self.unicode_escapes,
|
||||||
|
),
|
||||||
|
PropertyKey::Expression(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(id) = id {
|
||||||
|
self.transform_expression(&mut property.value, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_variable_declarator(&mut self, decl: &mut VariableDeclarator<'a>) {
|
||||||
|
let Some(init) = &mut decl.init else { return };
|
||||||
|
|
||||||
|
if let BindingPatternKind::BindingIdentifier(ident) = &decl.id.kind {
|
||||||
|
// Create a new ID instead of cloning to avoid local binding/refs
|
||||||
|
if let Some(id) =
|
||||||
|
create_valid_identifier(ident.span, ident.name.clone(), self.unicode_escapes)
|
||||||
|
{
|
||||||
|
self.transform_expression(init, id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal only
|
||||||
|
fn transform_expression(&mut self, expr: &mut Expression<'a>, mut id: BindingIdentifier) {
|
||||||
|
// function () {} -> function name() {}
|
||||||
|
if let Expression::FunctionExpression(func) = expr {
|
||||||
|
let scopes = self.ctx.scopes();
|
||||||
|
let mut count = 0;
|
||||||
|
|
||||||
|
// let mut finder = IdentFinder { id, found: 0 };
|
||||||
|
// finder.visit_expression(expr);
|
||||||
|
|
||||||
|
// Check for nested params/vars of the same name
|
||||||
|
for scope in scopes.descendants() {
|
||||||
|
for binding in scopes.get_bindings(scope) {
|
||||||
|
if binding.0 == &id.name {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're shadowing, change the name
|
||||||
|
if count > 0 {
|
||||||
|
id.name = Atom::from(format!("{}{}", id.name, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
if func.id.is_none() {
|
||||||
|
func.id = Some(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IdentFinder {
|
||||||
|
id: BindingIdentifier,
|
||||||
|
found: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Visit<'a> for IdentFinder {
|
||||||
|
fn visit_binding_identifier(&mut self, ident: &BindingIdentifier) {
|
||||||
|
if ident.name == self.id.name {
|
||||||
|
self.found += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/babel/babel/blob/main/packages/babel-helper-function-name/src/index.ts
|
||||||
|
// https://github.com/babel/babel/blob/main/packages/babel-types/src/converters/toBindingIdentifierName.ts#L3
|
||||||
|
// https://github.com/babel/babel/blob/main/packages/babel-types/src/converters/toIdentifier.ts#L4
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
|
fn create_valid_identifier(
|
||||||
|
span: Span,
|
||||||
|
atom: Atom,
|
||||||
|
_unicode_escapes: bool,
|
||||||
|
) -> Option<BindingIdentifier> {
|
||||||
|
// NOTE: this regex fails to compile on Rust
|
||||||
|
// lazy_static! {
|
||||||
|
// static ref UNICODE_NAME: Regex = Regex::new(r"(?u)[\u{D800}-\u{DFFF}]").unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if !unicode_escapes && UNICODE_NAME.is_match(atom.as_str()) {
|
||||||
|
// return None;
|
||||||
|
// }
|
||||||
|
|
||||||
|
let id = Atom::from(
|
||||||
|
atom.chars().map(|c| if is_id_continue(c) { c } else { '-' }).collect::<String>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let id = if id == "" {
|
||||||
|
Atom::from("_")
|
||||||
|
} else if id == "eval" || id == "arguments" || id == "null" || !is_valid_identifier(&id, true) {
|
||||||
|
Atom::from(format!("_{id}"))
|
||||||
|
} else {
|
||||||
|
atom
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(BindingIdentifier::new(span, id))
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
mod function_name;
|
||||||
mod shorthand_properties;
|
mod shorthand_properties;
|
||||||
mod template_literals;
|
mod template_literals;
|
||||||
|
|
||||||
|
pub use function_name::FunctionName;
|
||||||
pub use shorthand_properties::ShorthandProperties;
|
pub use shorthand_properties::ShorthandProperties;
|
||||||
pub use template_literals::TemplateLiterals;
|
pub use template_literals::TemplateLiterals;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ use oxc_semantic::Semantic;
|
||||||
use oxc_span::SourceType;
|
use oxc_span::SourceType;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::TransformerCtx, es2015::ShorthandProperties, es2016::ExponentiationOperator,
|
context::TransformerCtx, es2015::*, es2016::ExponentiationOperator,
|
||||||
es2019::OptionalCatchBinding, es2020::NullishCoalescingOperator,
|
es2019::OptionalCatchBinding, es2020::NullishCoalescingOperator,
|
||||||
es2021::LogicalAssignmentOperators, es2022::ClassStaticBlock, es3::PropertyLiteral,
|
es2021::LogicalAssignmentOperators, es2022::ClassStaticBlock, es3::PropertyLiteral,
|
||||||
react_jsx::ReactJsx, regexp::RegexpFlags, typescript::TypeScript, utils::CreateVars,
|
react_jsx::ReactJsx, regexp::RegexpFlags, typescript::TypeScript, utils::CreateVars,
|
||||||
|
|
@ -62,6 +62,7 @@ pub struct Transformer<'a> {
|
||||||
// es2016
|
// es2016
|
||||||
es2016_exponentiation_operator: Option<ExponentiationOperator<'a>>,
|
es2016_exponentiation_operator: Option<ExponentiationOperator<'a>>,
|
||||||
// es2015
|
// es2015
|
||||||
|
es2015_function_name: Option<FunctionName<'a>>,
|
||||||
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>>,
|
es3_property_literal: Option<PropertyLiteral<'a>>,
|
||||||
|
|
@ -86,14 +87,22 @@ impl<'a> Transformer<'a> {
|
||||||
// TODO: pass verbatim_module_syntax from user config
|
// TODO: pass verbatim_module_syntax from user config
|
||||||
typescript: source_type.is_typescript().then(|| TypeScript::new(Rc::clone(&ast), ctx.clone(), false)),
|
typescript: source_type.is_typescript().then(|| TypeScript::new(Rc::clone(&ast), ctx.clone(), false)),
|
||||||
regexp_flags: RegexpFlags::new(Rc::clone(&ast), &options),
|
regexp_flags: RegexpFlags::new(Rc::clone(&ast), &options),
|
||||||
|
// es2022
|
||||||
es2022_class_static_block: es2022::ClassStaticBlock::new(Rc::clone(&ast), &options),
|
es2022_class_static_block: es2022::ClassStaticBlock::new(Rc::clone(&ast), &options),
|
||||||
|
// es2021
|
||||||
es2021_logical_assignment_operators: LogicalAssignmentOperators::new(Rc::clone(&ast), ctx.clone(), &options),
|
es2021_logical_assignment_operators: LogicalAssignmentOperators::new(Rc::clone(&ast), ctx.clone(), &options),
|
||||||
|
// es2020
|
||||||
es2020_nullish_coalescing_operators: NullishCoalescingOperator::new(Rc::clone(&ast), ctx.clone(), &options),
|
es2020_nullish_coalescing_operators: NullishCoalescingOperator::new(Rc::clone(&ast), ctx.clone(), &options),
|
||||||
|
// es2019
|
||||||
es2019_optional_catch_binding: OptionalCatchBinding::new(Rc::clone(&ast), &options),
|
es2019_optional_catch_binding: OptionalCatchBinding::new(Rc::clone(&ast), &options),
|
||||||
|
// es2016
|
||||||
es2016_exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ast), ctx.clone(), &options),
|
es2016_exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ast), ctx.clone(), &options),
|
||||||
|
// es2015
|
||||||
|
es2015_function_name: FunctionName::new(&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),
|
// other
|
||||||
|
es3_property_literal: PropertyLiteral::new(Rc::clone(&ast), &options),
|
||||||
react_jsx: ReactJsx::new(Rc::clone(&ast), ctx.clone(), options)
|
react_jsx: ReactJsx::new(Rc::clone(&ast), ctx.clone(), options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -129,6 +138,13 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
|
||||||
self.react_jsx.as_mut().map(|t| t.add_react_jsx_runtime_imports(program));
|
self.react_jsx.as_mut().map(|t| t.add_react_jsx_runtime_imports(program));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_assignment_expression(&mut self, expr: &mut AssignmentExpression<'a>) {
|
||||||
|
self.es2015_function_name.as_mut().map(|t| t.transform_assignment_expression(expr));
|
||||||
|
|
||||||
|
self.visit_assignment_target(&mut expr.left);
|
||||||
|
self.visit_expression(&mut expr.right);
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_statements(&mut self, stmts: &mut oxc_allocator::Vec<'a, Statement<'a>>) {
|
fn visit_statements(&mut self, stmts: &mut oxc_allocator::Vec<'a, Statement<'a>>) {
|
||||||
for stmt in stmts.iter_mut() {
|
for stmt in stmts.iter_mut() {
|
||||||
self.visit_statement(stmt);
|
self.visit_statement(stmt);
|
||||||
|
|
@ -171,12 +187,21 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
|
||||||
self.visit_statements(&mut clause.body.body);
|
self.visit_statements(&mut clause.body.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_object_expression(&mut self, expr: &mut ObjectExpression<'a>) {
|
||||||
|
self.es2015_function_name.as_mut().map(|t| t.transform_object_expression(expr));
|
||||||
|
|
||||||
|
for property in expr.properties.iter_mut() {
|
||||||
|
self.visit_object_property_kind(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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.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);
|
||||||
|
|
||||||
if let Some(init) = &mut prop.init {
|
if let Some(init) = &mut prop.init {
|
||||||
self.visit_expression(init);
|
self.visit_expression(init);
|
||||||
}
|
}
|
||||||
|
|
@ -192,11 +217,23 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
|
||||||
|
|
||||||
fn visit_formal_parameters(&mut self, params: &mut FormalParameters<'a>) {
|
fn visit_formal_parameters(&mut self, params: &mut FormalParameters<'a>) {
|
||||||
self.typescript.as_mut().map(|t| t.transform_formal_parameters(params));
|
self.typescript.as_mut().map(|t| t.transform_formal_parameters(params));
|
||||||
|
|
||||||
for param in params.items.iter_mut() {
|
for param in params.items.iter_mut() {
|
||||||
self.visit_formal_parameter(param);
|
self.visit_formal_parameter(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(rest) = &mut params.rest {
|
if let Some(rest) = &mut params.rest {
|
||||||
self.visit_rest_element(rest);
|
self.visit_rest_element(rest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_variable_declarator(&mut self, declarator: &mut VariableDeclarator<'a>) {
|
||||||
|
self.es2015_function_name.as_mut().map(|t| t.transform_variable_declarator(declarator));
|
||||||
|
|
||||||
|
self.visit_binding_pattern(&mut declarator.id);
|
||||||
|
|
||||||
|
if let Some(init) = &mut declarator.init {
|
||||||
|
self.visit_expression(init);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ pub struct TransformOptions {
|
||||||
// es2016
|
// es2016
|
||||||
pub exponentiation_operator: bool,
|
pub exponentiation_operator: bool,
|
||||||
// es2015
|
// es2015
|
||||||
|
pub function_name: bool,
|
||||||
pub shorthand_properties: bool,
|
pub shorthand_properties: bool,
|
||||||
pub sticky_regex: bool,
|
pub sticky_regex: bool,
|
||||||
pub template_literals: bool,
|
pub template_literals: bool,
|
||||||
|
|
|
||||||
4
justfile
4
justfile
|
|
@ -51,6 +51,10 @@ check:
|
||||||
test:
|
test:
|
||||||
cargo test
|
cargo test
|
||||||
|
|
||||||
|
test-transform:
|
||||||
|
cargo run -p oxc_transform_conformance
|
||||||
|
cargo run -p oxc_transform_conformance -- --exec
|
||||||
|
|
||||||
# Lint the whole project
|
# Lint the whole project
|
||||||
lint:
|
lint:
|
||||||
cargo lint -- --deny warnings
|
cargo lint -- --deny warnings
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ pub trait TestCase {
|
||||||
.map(get_options::<ReactJsxOptions>),
|
.map(get_options::<ReactJsxOptions>),
|
||||||
assumptions: options.assumptions,
|
assumptions: options.assumptions,
|
||||||
class_static_block: options.get_plugin("transform-class-static-block").is_some(),
|
class_static_block: options.get_plugin("transform-class-static-block").is_some(),
|
||||||
|
function_name: options.get_plugin("transform-function-name").is_some(),
|
||||||
logical_assignment_operators: options
|
logical_assignment_operators: options
|
||||||
.get_plugin("transform-logical-assignment-operators")
|
.get_plugin("transform-logical-assignment-operators")
|
||||||
.is_some(),
|
.is_some(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue