oxc_transformer/jsx/
jsx_self.rs

1//! React JSX Self
2//!
3//! This plugin adds `__self` attribute to JSX elements.
4//!
5//! > This plugin is included in `preset-react`.
6//!
7//! ## Example
8//!
9//! Input:
10//! ```js
11//! <div>foo</div>;
12//! <Bar>foo</Bar>;
13//! <>foo</>;
14//! ```
15//!
16//! Output:
17//! ```js
18//! <div __self={this}>foo</div>;
19//! <Bar __self={this}>foo</Bar>;
20//! <>foo</>;
21//! ```
22//!
23//! ## Implementation
24//!
25//! Implementation based on [@babel/plugin-transform-react-jsx-self](https://babeljs.io/docs/babel-plugin-transform-react-jsx-self).
26//!
27//! ## References:
28//!
29//! * Babel plugin implementation: <https://github.com/babel/babel/blob/v7.26.2/packages/babel-plugin-transform-react-jsx-self/src/index.ts>
30
31use oxc_ast::ast::*;
32use oxc_diagnostics::OxcDiagnostic;
33use oxc_span::{SPAN, Span};
34use oxc_traverse::{Ancestor, Traverse};
35
36use crate::{
37    context::{TransformCtx, TraverseCtx},
38    state::TransformState,
39};
40
41const SELF: &str = "__self";
42
43pub struct JsxSelf<'a, 'ctx> {
44    ctx: &'ctx TransformCtx<'a>,
45}
46
47impl<'a, 'ctx> JsxSelf<'a, 'ctx> {
48    pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
49        Self { ctx }
50    }
51}
52
53impl<'a> Traverse<'a, TransformState<'a>> for JsxSelf<'a, '_> {
54    fn enter_jsx_opening_element(
55        &mut self,
56        elem: &mut JSXOpeningElement<'a>,
57        ctx: &mut TraverseCtx<'a>,
58    ) {
59        self.add_self_this_attribute(elem, ctx);
60    }
61}
62
63impl<'a> JsxSelf<'a, '_> {
64    pub fn report_error(&self, span: Span) {
65        let error = OxcDiagnostic::warn("Duplicate __self prop found.").with_label(span);
66        self.ctx.error(error);
67    }
68
69    fn is_inside_constructor(ctx: &TraverseCtx<'a>) -> bool {
70        for scope_id in ctx.ancestor_scopes() {
71            let flags = ctx.scoping().scope_flags(scope_id);
72            if flags.is_block() || flags.is_arrow() {
73                continue;
74            }
75            return flags.is_constructor();
76        }
77        unreachable!(); // Always hit `Program` and exit before loop ends
78    }
79
80    fn has_no_super_class(ctx: &TraverseCtx<'a>) -> bool {
81        for ancestor in ctx.ancestors() {
82            if let Ancestor::ClassBody(class) = ancestor {
83                return class.super_class().is_none();
84            }
85        }
86        true
87    }
88
89    pub fn get_object_property_kind_for_jsx_plugin(
90        ctx: &TraverseCtx<'a>,
91    ) -> ObjectPropertyKind<'a> {
92        let kind = PropertyKind::Init;
93        let key = ctx.ast.property_key_static_identifier(SPAN, SELF);
94        let value = ctx.ast.expression_this(SPAN);
95        ctx.ast.object_property_kind_object_property(SPAN, kind, key, value, false, false, false)
96    }
97
98    pub fn can_add_self_attribute(ctx: &TraverseCtx<'a>) -> bool {
99        !Self::is_inside_constructor(ctx) || Self::has_no_super_class(ctx)
100    }
101
102    /// `<div __self={this} />`
103    ///       ^^^^^^^^^^^^^
104    fn add_self_this_attribute(&self, elem: &mut JSXOpeningElement<'a>, ctx: &TraverseCtx<'a>) {
105        // Check if `__self` attribute already exists
106        for item in &elem.attributes {
107            if let JSXAttributeItem::Attribute(attribute) = item
108                && let JSXAttributeName::Identifier(ident) = &attribute.name
109                && ident.name == SELF
110            {
111                self.report_error(ident.span);
112                return;
113            }
114        }
115
116        let name = ctx.ast.jsx_attribute_name_identifier(SPAN, SELF);
117        let value = {
118            let jsx_expr = JSXExpression::from(ctx.ast.expression_this(SPAN));
119            ctx.ast.jsx_attribute_value_expression_container(SPAN, jsx_expr)
120        };
121        let attribute = ctx.ast.jsx_attribute_item_attribute(SPAN, name, Some(value));
122        elem.attributes.push(attribute);
123    }
124}