mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(linter/eslint-plugin-react): Implement no-set-state (#3975)
Rule Detail: [link](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-set-state.md)
This commit is contained in:
parent
1852ea4fd2
commit
10a3c9a052
3 changed files with 209 additions and 0 deletions
|
|
@ -217,6 +217,7 @@ mod react {
|
|||
pub mod no_find_dom_node;
|
||||
pub mod no_is_mounted;
|
||||
pub mod no_render_return_value;
|
||||
pub mod no_set_state;
|
||||
pub mod no_string_refs;
|
||||
pub mod no_unescaped_entities;
|
||||
pub mod no_unknown_property;
|
||||
|
|
@ -687,6 +688,7 @@ oxc_macros::declare_all_lint_rules! {
|
|||
react::no_direct_mutation_state,
|
||||
react::no_find_dom_node,
|
||||
react::no_render_return_value,
|
||||
react::no_set_state,
|
||||
react::no_string_refs,
|
||||
react::no_unescaped_entities,
|
||||
react::no_is_mounted,
|
||||
|
|
|
|||
165
crates/oxc_linter/src/rules/react/no_set_state.rs
Normal file
165
crates/oxc_linter/src/rules/react/no_set_state.rs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
use oxc_ast::{ast::Expression, AstKind};
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_span::{GetSpan, Span};
|
||||
|
||||
use crate::{
|
||||
context::LintContext,
|
||||
rule::Rule,
|
||||
utils::{get_parent_es5_component, get_parent_es6_component},
|
||||
AstNode,
|
||||
};
|
||||
|
||||
fn no_set_state_diagnostic(span0: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("eslint-plugin-react(no-set-state): Do not use setState").with_label(span0)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct NoSetState;
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
///
|
||||
/// Disallow the usage of `this.setState` in React components.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// When using an architecture that separates your application state from your UI components
|
||||
/// (e.g. Flux), it may be desirable to forbid the use of local component state. This rule is
|
||||
/// especially helpful in read-only applications (that don't use forms), since local component
|
||||
/// state should rarely be necessary in such cases.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```javascript
|
||||
/// var Hello = createReactClass({
|
||||
/// getInitialState: function() {
|
||||
/// return {
|
||||
/// name: this.props.name
|
||||
/// };
|
||||
/// },
|
||||
/// handleClick: function() {
|
||||
/// this.setState({
|
||||
/// name: this.props.name.toUpperCase()
|
||||
/// });
|
||||
/// },
|
||||
/// render: function() {
|
||||
/// return <div onClick={this.handleClick.bind(this)}>Hello {this.state.name}</div>;
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
NoSetState,
|
||||
style,
|
||||
);
|
||||
|
||||
impl Rule for NoSetState {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
let AstKind::CallExpression(call_expr) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(member_expr) = call_expr.callee.as_member_expression() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(member_expr.object(), Expression::ThisExpression(_))
|
||||
|| !member_expr.static_property_name().is_some_and(|str| str == "setState")
|
||||
|| !(get_parent_es5_component(node, ctx).is_some()
|
||||
|| get_parent_es6_component(ctx).is_some())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.diagnostic(no_set_state_diagnostic(call_expr.callee.span()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec![
|
||||
"
|
||||
var Hello = function() {
|
||||
this.setState({})
|
||||
};
|
||||
",
|
||||
"
|
||||
var Hello = createReactClass({
|
||||
render: function() {
|
||||
return <div>Hello {this.props.name}</div>;
|
||||
}
|
||||
});
|
||||
",
|
||||
"
|
||||
var Hello = createReactClass({
|
||||
componentDidUpdate: function() {
|
||||
someNonMemberFunction(arg);
|
||||
this.someHandler = this.setState;
|
||||
},
|
||||
render: function() {
|
||||
return <div>Hello {this.props.name}</div>;
|
||||
}
|
||||
});
|
||||
",
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
"
|
||||
var Hello = createReactClass({
|
||||
componentDidUpdate: function() {
|
||||
this.setState({
|
||||
name: this.props.name.toUpperCase()
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return <div>Hello {this.state.name}</div>;
|
||||
}
|
||||
});
|
||||
",
|
||||
"
|
||||
var Hello = createReactClass({
|
||||
someMethod: function() {
|
||||
this.setState({
|
||||
name: this.props.name.toUpperCase()
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return <div onClick={this.someMethod.bind(this)}>Hello {this.state.name}</div>;
|
||||
}
|
||||
});
|
||||
",
|
||||
"
|
||||
class Hello extends React.Component {
|
||||
someMethod() {
|
||||
this.setState({
|
||||
name: this.props.name.toUpperCase()
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return <div onClick={this.someMethod.bind(this)}>Hello {this.state.name}</div>;
|
||||
}
|
||||
};
|
||||
",
|
||||
"
|
||||
class Hello extends React.Component {
|
||||
someMethod = () => {
|
||||
this.setState({
|
||||
name: this.props.name.toUpperCase()
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return <div onClick={this.someMethod.bind(this)}>Hello {this.state.name}</div>;
|
||||
}
|
||||
};
|
||||
",
|
||||
"
|
||||
class Hello extends React.Component {
|
||||
render() {
|
||||
return <div onMouseEnter={() => this.setState({dropdownIndex: index})} />;
|
||||
}
|
||||
};
|
||||
",
|
||||
];
|
||||
|
||||
Tester::new(NoSetState::NAME, pass, fail).test_and_snapshot();
|
||||
}
|
||||
42
crates/oxc_linter/src/snapshots/no_set_state.snap
Normal file
42
crates/oxc_linter/src/snapshots/no_set_state.snap
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
---
|
||||
⚠ eslint-plugin-react(no-set-state): Do not use setState
|
||||
╭─[no_set_state.tsx:4:16]
|
||||
3 │ componentDidUpdate: function() {
|
||||
4 │ this.setState({
|
||||
· ─────────────
|
||||
5 │ name: this.props.name.toUpperCase()
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-react(no-set-state): Do not use setState
|
||||
╭─[no_set_state.tsx:4:16]
|
||||
3 │ someMethod: function() {
|
||||
4 │ this.setState({
|
||||
· ─────────────
|
||||
5 │ name: this.props.name.toUpperCase()
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-react(no-set-state): Do not use setState
|
||||
╭─[no_set_state.tsx:4:16]
|
||||
3 │ someMethod() {
|
||||
4 │ this.setState({
|
||||
· ─────────────
|
||||
5 │ name: this.props.name.toUpperCase()
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-react(no-set-state): Do not use setState
|
||||
╭─[no_set_state.tsx:4:16]
|
||||
3 │ someMethod = () => {
|
||||
4 │ this.setState({
|
||||
· ─────────────
|
||||
5 │ name: this.props.name.toUpperCase()
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-react(no-set-state): Do not use setState
|
||||
╭─[no_set_state.tsx:4:48]
|
||||
3 │ render() {
|
||||
4 │ return <div onMouseEnter={() => this.setState({dropdownIndex: index})} />;
|
||||
· ─────────────
|
||||
5 │ }
|
||||
╰────
|
||||
Loading…
Reference in a new issue