Skip to content

Commit

Permalink
feat(linter/eslint-plugin-react): Implement prefer-es6-class (#3812)
Browse files Browse the repository at this point in the history
Rule Detail:

[link](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md)

---------

Co-authored-by: Boshen <boshenc@gmail.com>
  • Loading branch information
jelly and Boshen committed Jun 26, 2024
1 parent bbe8336 commit 5f84500
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ mod react {
pub mod no_string_refs;
pub mod no_unescaped_entities;
pub mod no_unknown_property;
pub mod prefer_es6_class;
pub mod react_in_jsx_scope;
pub mod require_render_return;
pub mod rules_of_hooks;
Expand Down Expand Up @@ -690,6 +691,7 @@ oxc_macros::declare_all_lint_rules! {
react::no_unescaped_entities,
react::no_is_mounted,
react::no_unknown_property,
react::prefer_es6_class,
react::require_render_return,
react::rules_of_hooks,
react::void_dom_elements_no_children,
Expand Down
188 changes: 188 additions & 0 deletions crates/oxc_linter/src/rules/react/prefer_es6_class.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};

use crate::{
context::LintContext,
rule::Rule,
utils::{is_es5_component, is_es6_component},
AstNode,
};

fn unexpected_es6_class_diagnostic(span0: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("eslint-plugin-react(prefer-es6-class): Components should use createClass instead of ES6 class.")
.with_label(span0)
}

fn expected_es6_class_diagnostic(span0: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("eslint-plugin-react(prefer-es6-class): Components should use es6 class instead of createClass.")
.with_label(span0)
}

#[derive(Debug, Default, Clone)]
pub struct PreferEs6Class {
prefer_es6_class_option: PreferES6ClassOptionType,
}

declare_oxc_lint!(
/// ### What it does
///
/// React offers you two ways to create traditional components: using the ES5
/// create-react-class module or the new ES6 class system.
///
/// ### Why is this bad?
///
/// This rule enforces a consistent React class style.
///
/// ### Example
/// ```javascript
/// var Hello = createReactClass({
/// render: function() {
/// return <div>Hello {this.props.name}</div>;
/// }
/// });
/// ```
PreferEs6Class,
style,
);

impl Rule for PreferEs6Class {
fn from_configuration(value: serde_json::Value) -> Self {
let obj = value.get(0);

Self {
prefer_es6_class_option: obj
.and_then(serde_json::Value::as_str)
.map(PreferES6ClassOptionType::from)
.unwrap_or_default(),
}
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if matches!(self.prefer_es6_class_option, PreferES6ClassOptionType::Always) {
if is_es5_component(node) {
let AstKind::CallExpression(call_expr) = node.kind() else {
return;
};
ctx.diagnostic(expected_es6_class_diagnostic(call_expr.callee.span()));
}
} else if is_es6_component(node) {
let AstKind::Class(class_expr) = node.kind() else {
return;
};
ctx.diagnostic(unexpected_es6_class_diagnostic(
class_expr.id.as_ref().map_or(class_expr.span, |id| id.span),
));
}
}
}

#[derive(Debug, Default, Clone)]
enum PreferES6ClassOptionType {
#[default]
Always,
Never,
}

impl PreferES6ClassOptionType {
pub fn from(raw: &str) -> Self {
match raw {
"always" => Self::Always,
_ => Self::Never,
}
}
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
(
r"
class Hello extends React.Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
Hello.displayName = 'Hello'
",
None,
),
(
r"
export default class Hello extends React.Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
Hello.displayName = 'Hello'
",
None,
),
(
r"
var Hello = 'foo';
module.exports = {};
",
None,
),
(
r"
var Hello = createReactClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
",
Some(serde_json::json!(["never"])),
),
(
r"
class Hello extends React.Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
",
Some(serde_json::json!(["always"])),
),
];

let fail = vec![
(
r"
var Hello = createReactClass({
displayName: 'Hello',
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
",
None,
),
(
r"
var Hello = createReactClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
",
Some(serde_json::json!(["always"])),
),
(
r"
class Hello extends React.Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
",
Some(serde_json::json!(["never"])),
),
];

Tester::new(PreferEs6Class::NAME, pass, fail).test_and_snapshot();
}
26 changes: 26 additions & 0 deletions crates/oxc_linter/src/snapshots/prefer_es_6_class.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
source: crates/oxc_linter/src/tester.rs
---
eslint-plugin-react(prefer-es6-class): Components should use es6 class instead of createClass.
╭─[prefer_es_6_class.tsx:2:25]
1 │
2 │ var Hello = createReactClass({
· ────────────────
3 │ displayName: 'Hello',
╰────

eslint-plugin-react(prefer-es6-class): Components should use es6 class instead of createClass.
╭─[prefer_es_6_class.tsx:2:25]
1 │
2 │ var Hello = createReactClass({
· ────────────────
3 │ render: function() {
╰────

eslint-plugin-react(prefer-es6-class): Components should use createClass instead of ES6 class.
╭─[prefer_es_6_class.tsx:2:19]
1 │
2 │ class Hello extends React.Component {
· ─────
3 │ render() {
╰────

0 comments on commit 5f84500

Please sign in to comment.