Skip to content

Commit

Permalink
feat(linter/eslint-plugin-react): Implement prefer-es6-class
Browse files Browse the repository at this point in the history
  • Loading branch information
jelly committed Jun 25, 2024
1 parent fafe67c commit 75abe5b
Show file tree
Hide file tree
Showing 3 changed files with 223 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 @@ -218,6 +218,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 @@ -686,6 +687,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
185 changes: 185 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,185 @@
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::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): Component 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): Component 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.span));
}
} else if is_es6_component(node) {
let AstKind::Class(class_expr) = node.kind() else {
return;
};
ctx.diagnostic(unexpected_es6_class_diagnostic(class_expr.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();
}
36 changes: 36 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,36 @@
---
source: crates/oxc_linter/src/tester.rs
---
eslint-plugin-react(prefer-es6-class): Component should use es6 class instead of createClass
╭─[prefer_es_6_class.tsx:2:25]
1 │
2 │ ╭─▶ var Hello = createReactClass({
3 │ │ displayName: 'Hello',
4 │ │ render: function() {
5 │ │ return <div>Hello {this.props.name}</div>;
6 │ │ }
7 │ ╰─▶ });
8
╰────

eslint-plugin-react(prefer-es6-class): Component should use es6 class instead of createClass
╭─[prefer_es_6_class.tsx:2:25]
1 │
2 │ ╭─▶ var Hello = createReactClass({
3 │ │ render: function() {
4 │ │ return <div>Hello {this.props.name}</div>;
5 │ │ }
6 │ ╰─▶ });
7
╰────

eslint-plugin-react(prefer-es6-class): Component should use createClass instead of es6 class
╭─[prefer_es_6_class.tsx:2:13]
1 │
2 │ ╭─▶ class Hello extends React.Component {
3 │ │ render() {
4 │ │ return <div>Hello {this.props.name}</div>;
5 │ │ }
6 │ ╰─▶ }
7
╰────

0 comments on commit 75abe5b

Please sign in to comment.