diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs
index 0d464e11e..fa7273d05 100644
--- a/crates/oxc_linter/src/rules.rs
+++ b/crates/oxc_linter/src/rules.rs
@@ -133,6 +133,7 @@ mod react {
pub mod no_dangerously_set_inner_html;
pub mod no_find_dom_node;
pub mod no_render_return_value;
+ pub mod no_string_refs;
pub mod no_unescaped_entities;
}
@@ -266,6 +267,7 @@ oxc_macros::declare_all_lint_rules! {
react::no_dangerously_set_inner_html,
react::no_find_dom_node,
react::no_render_return_value,
+ react::no_string_refs,
import::named,
import::no_cycle,
import::no_self_import,
diff --git a/crates/oxc_linter/src/rules/react/no_string_refs.rs b/crates/oxc_linter/src/rules/react/no_string_refs.rs
new file mode 100644
index 000000000..3c1560e38
--- /dev/null
+++ b/crates/oxc_linter/src/rules/react/no_string_refs.rs
@@ -0,0 +1,265 @@
+use oxc_ast::ast::{CallExpression, JSXAttributeItem, JSXAttributeName};
+use oxc_ast::{
+ ast::{Expression, JSXAttributeValue, JSXExpression, JSXExpressionContainer, MemberExpression},
+ AstKind,
+};
+use oxc_diagnostics::{
+ miette::{self, Diagnostic},
+ thiserror::Error,
+};
+use oxc_macros::declare_oxc_lint;
+use oxc_span::Span;
+
+use crate::{context::LintContext, rule::Rule, AstNode};
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("eslint-plugin-react(no-string-refs):")]
+#[diagnostic(
+ severity(warning),
+ help("Using string literals in ref attributes is deprecated. Use a callback instead.")
+)]
+struct NoStringRefsDiagnostic(#[label] pub Span);
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("eslint-plugin-react(no-string-refs):")]
+#[diagnostic(severity(warning), help("Using this.refs is deprecated."))]
+struct NoThisRefsDiagnostic(#[label] pub Span);
+
+#[derive(Debug, Default, Clone)]
+pub struct NoStringRefs {
+ /// When set to `true`, it will give a warning when using template literals for refs.
+ pub no_template_literals: bool,
+}
+
+declare_oxc_lint!(
+ /// ### What it does
+ ///
+ /// This rule disallows using string references in JSX `ref` attributes.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// String refs are considered legacy in the React documentation. Callback refs are preferred.
+ ///
+ /// ### Example
+ /// ```javascript
+ /// // Bad
+ /// var Hello = createReactClass({
+ /// componentDidMount: function() {
+ /// var component = this.refs.hello;
+ /// // ...do something with component
+ /// },
+ /// render: function() {
+ /// return
Hello, world.
;
+ /// },
+ /// });
+ ///
+ /// // Good
+ /// var Hello = createReactClass({
+ /// componentDidMount: function() {
+ /// var component = this.hello;
+ /// // ...do something with component
+ /// },
+ /// render: function() {
+ /// return this.hello = c}>Hello, world.
;
+ /// },
+ /// });
+ /// ```
+ NoStringRefs,
+ correctness
+);
+
+impl Rule for NoStringRefs {
+ fn from_configuration(value: serde_json::Value) -> Self {
+ Self {
+ no_template_literals: value.get(0).and_then(|x| x.get("noTemplateLiterals")).map_or(
+ false,
+ |x| match x {
+ serde_json::Value::Bool(b) => *b,
+ _ => false,
+ },
+ ),
+ }
+ }
+ fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
+ if let AstKind::JSXAttributeItem(JSXAttributeItem::Attribute(attribute)) = node.kind() {
+ let Some(value) = &attribute.0.value else {
+ return;
+ };
+ if let JSXAttributeName::Identifier(iden) = &attribute.0.name {
+ if iden.name.as_str() != "ref" {
+ return;
+ };
+ }
+ match value {
+ JSXAttributeValue::StringLiteral(s) => {
+ ctx.diagnostic(NoStringRefsDiagnostic(s.span));
+ }
+ JSXAttributeValue::ExpressionContainer(JSXExpressionContainer {
+ expression: JSXExpression::Expression(expr),
+ ..
+ }) => match expr {
+ Expression::TemplateLiteral(s) if self.no_template_literals => {
+ ctx.diagnostic(NoStringRefsDiagnostic(s.span));
+ }
+ Expression::StringLiteral(s) => {
+ ctx.diagnostic(NoStringRefsDiagnostic(s.span));
+ }
+ _ => return,
+ },
+ _ => return,
+ }
+ }
+
+ if let AstKind::MemberExpression(MemberExpression::StaticMemberExpression(expr)) =
+ node.kind()
+ {
+ if let (&Expression::ThisExpression(_), "refs") =
+ (&expr.object, expr.property.name.as_str())
+ {
+ for node_id in ctx.nodes().ancestors(node.id()).skip(1) {
+ let parent = ctx.nodes().get_node(node_id);
+ if let AstKind::CallExpression(CallExpression {
+ callee: Expression::Identifier(iden),
+ ..
+ }) = parent.kind()
+ {
+ if iden.name.as_str() == "createReactClass" {
+ ctx.diagnostic(NoThisRefsDiagnostic(expr.span));
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#[test]
+fn test() {
+ use crate::tester::Tester;
+
+ let pass = vec![
+ (
+ "
+ var Hello = blah({
+ componentDidMount: function() {
+ var component = this.refs.hello;
+ },
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ None,
+ ),
+ (
+ "
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ var component = this.hello;
+ },
+ render: function() {
+ return this.hello = c}>Hello {this.props.name}
;
+ }
+ });
+ ",
+ None,
+ ),
+ (
+ "
+ var Hello = createReactClass({
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ None,
+ ),
+ (
+ "
+ var Hello = createReactClass({
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ None,
+ ),
+ ];
+
+ let fail = vec![
+ (
+ "
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ var component = this.refs.hello;
+ },
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ None,
+ ),
+ (
+ "
+ var Hello = createReactClass({
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ None,
+ ),
+ (
+ "
+ var Hello = createReactClass({
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ None,
+ ),
+ (
+ "
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ var component = this.refs.hello;
+ },
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ None,
+ ),
+ (
+ "
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ var component = this.refs.hello;
+ },
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ Some(serde_json::json!([{ "noTemplateLiterals": true }])),
+ ),
+ (
+ "
+ var Hello = createReactClass({
+ componentDidMount: function() {
+ var component = this.refs.hello;
+ },
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ Some(serde_json::json!([{ "noTemplateLiterals": true }])),
+ ),
+ ];
+
+ Tester::new(NoStringRefs::NAME, pass, fail).test_and_snapshot();
+}
diff --git a/crates/oxc_linter/src/snapshots/no_string_refs.snap b/crates/oxc_linter/src/snapshots/no_string_refs.snap
new file mode 100644
index 000000000..08cdbe974
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_string_refs.snap
@@ -0,0 +1,86 @@
+---
+source: crates/oxc_linter/src/tester.rs
+expression: no_string_refs
+---
+ ⚠ eslint-plugin-react(no-string-refs):
+ ╭─[no_string_refs.tsx:3:1]
+ 3 │ componentDidMount: function() {
+ 4 │ var component = this.refs.hello;
+ · ─────────
+ 5 │ },
+ ╰────
+ help: Using this.refs is deprecated.
+
+ ⚠ eslint-plugin-react(no-string-refs):
+ ╭─[no_string_refs.tsx:3:1]
+ 3 │ render: function() {
+ 4 │ return Hello {this.props.name}
;
+ · ───────
+ 5 │ }
+ ╰────
+ help: Using string literals in ref attributes is deprecated. Use a callback instead.
+
+ ⚠ eslint-plugin-react(no-string-refs):
+ ╭─[no_string_refs.tsx:3:1]
+ 3 │ render: function() {
+ 4 │ return Hello {this.props.name}
;
+ · ───────
+ 5 │ }
+ ╰────
+ help: Using string literals in ref attributes is deprecated. Use a callback instead.
+
+ ⚠ eslint-plugin-react(no-string-refs):
+ ╭─[no_string_refs.tsx:3:1]
+ 3 │ componentDidMount: function() {
+ 4 │ var component = this.refs.hello;
+ · ─────────
+ 5 │ },
+ ╰────
+ help: Using this.refs is deprecated.
+
+ ⚠ eslint-plugin-react(no-string-refs):
+ ╭─[no_string_refs.tsx:6:1]
+ 6 │ render: function() {
+ 7 │ return Hello {this.props.name}
;
+ · ───────
+ 8 │ }
+ ╰────
+ help: Using string literals in ref attributes is deprecated. Use a callback instead.
+
+ ⚠ eslint-plugin-react(no-string-refs):
+ ╭─[no_string_refs.tsx:3:1]
+ 3 │ componentDidMount: function() {
+ 4 │ var component = this.refs.hello;
+ · ─────────
+ 5 │ },
+ ╰────
+ help: Using this.refs is deprecated.
+
+ ⚠ eslint-plugin-react(no-string-refs):
+ ╭─[no_string_refs.tsx:6:1]
+ 6 │ render: function() {
+ 7 │ return Hello {this.props.name}
;
+ · ───────
+ 8 │ }
+ ╰────
+ help: Using string literals in ref attributes is deprecated. Use a callback instead.
+
+ ⚠ eslint-plugin-react(no-string-refs):
+ ╭─[no_string_refs.tsx:3:1]
+ 3 │ componentDidMount: function() {
+ 4 │ var component = this.refs.hello;
+ · ─────────
+ 5 │ },
+ ╰────
+ help: Using this.refs is deprecated.
+
+ ⚠ eslint-plugin-react(no-string-refs):
+ ╭─[no_string_refs.tsx:6:1]
+ 6 │ render: function() {
+ 7 │ return Hello {this.props.name}
;
+ · ───────────────
+ 8 │ }
+ ╰────
+ help: Using string literals in ref attributes is deprecated. Use a callback instead.
+
+