feat(transformer): Start on function_name transform. (#1510)

Co-authored-by: Boshen <boshenc@gmail.com>
This commit is contained in:
Miles Johnson 2023-12-03 21:35:44 -08:00 committed by GitHub
parent 4e05d1809f
commit 6cbc5dd75b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 223 additions and 3 deletions

View file

@ -445,6 +445,10 @@ impl<'a> PropertyKey<'a> {
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 {
matches!(self, Self::PrivateIdentifier(_))
}

View file

@ -27,7 +27,6 @@ oxc_semantic = { workspace = true }
oxc_diagnostics = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true, features = ["derive"] }
phf = { workspace = true, features = ["macros"] }

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

View file

@ -1,5 +1,7 @@
mod function_name;
mod shorthand_properties;
mod template_literals;
pub use function_name::FunctionName;
pub use shorthand_properties::ShorthandProperties;
pub use template_literals::TemplateLiterals;

View file

@ -33,7 +33,7 @@ use oxc_semantic::Semantic;
use oxc_span::SourceType;
use crate::{
context::TransformerCtx, es2015::ShorthandProperties, es2016::ExponentiationOperator,
context::TransformerCtx, es2015::*, es2016::ExponentiationOperator,
es2019::OptionalCatchBinding, es2020::NullishCoalescingOperator,
es2021::LogicalAssignmentOperators, es2022::ClassStaticBlock, es3::PropertyLiteral,
react_jsx::ReactJsx, regexp::RegexpFlags, typescript::TypeScript, utils::CreateVars,
@ -62,6 +62,7 @@ pub struct Transformer<'a> {
// es2016
es2016_exponentiation_operator: Option<ExponentiationOperator<'a>>,
// es2015
es2015_function_name: Option<FunctionName<'a>>,
es2015_shorthand_properties: Option<ShorthandProperties<'a>>,
es2015_template_literals: Option<TemplateLiterals<'a>>,
es3_property_literal: Option<PropertyLiteral<'a>>,
@ -86,14 +87,22 @@ impl<'a> Transformer<'a> {
// TODO: pass verbatim_module_syntax from user config
typescript: source_type.is_typescript().then(|| TypeScript::new(Rc::clone(&ast), ctx.clone(), false)),
regexp_flags: RegexpFlags::new(Rc::clone(&ast), &options),
// es2022
es2022_class_static_block: es2022::ClassStaticBlock::new(Rc::clone(&ast), &options),
// es2021
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),
// es2019
es2019_optional_catch_binding: OptionalCatchBinding::new(Rc::clone(&ast), &options),
// es2016
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_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)
}
}
@ -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));
}
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>>) {
for stmt in stmts.iter_mut() {
self.visit_statement(stmt);
@ -171,12 +187,21 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
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>) {
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_expression(&mut prop.value);
if let Some(init) = &mut prop.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>) {
self.typescript.as_mut().map(|t| t.transform_formal_parameters(params));
for param in params.items.iter_mut() {
self.visit_formal_parameter(param);
}
if let Some(rest) = &mut params.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);
}
}
}

View file

@ -20,6 +20,7 @@ pub struct TransformOptions {
// es2016
pub exponentiation_operator: bool,
// es2015
pub function_name: bool,
pub shorthand_properties: bool,
pub sticky_regex: bool,
pub template_literals: bool,

View file

@ -51,6 +51,10 @@ check:
test:
cargo test
test-transform:
cargo run -p oxc_transform_conformance
cargo run -p oxc_transform_conformance -- --exec
# Lint the whole project
lint:
cargo lint -- --deny warnings

View file

@ -94,6 +94,7 @@ pub trait TestCase {
.map(get_options::<ReactJsxOptions>),
assumptions: options.assumptions,
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
.get_plugin("transform-logical-assignment-operators")
.is_some(),