diff --git a/crates/oxc_linter/src/rules/react/no_direct_mutation_state.rs b/crates/oxc_linter/src/rules/react/no_direct_mutation_state.rs index ac4392c927a0..1bec62aa023f 100644 --- a/crates/oxc_linter/src/rules/react/no_direct_mutation_state.rs +++ b/crates/oxc_linter/src/rules/react/no_direct_mutation_state.rs @@ -84,6 +84,44 @@ declare_oxc_lint!( correctness ); +impl Rule for NoDirectMutationState { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::AssignmentExpression(assignment_expr) => { + if should_ignore_component(node, ctx) { + return; + } + + if let Some(assignment) = assignment_expr.left.as_simple_assignment_target() { + if let Some(outer_member_expression) = get_outer_member_expression(assignment) { + if is_state_member_expression(outer_member_expression) { + ctx.diagnostic(no_direct_mutation_state_diagnostic( + assignment_expr.left.span(), + )); + } + } + } + } + + AstKind::UpdateExpression(update_expr) => { + if should_ignore_component(node, ctx) { + return; + } + + if let Some(outer_member_expression) = + get_outer_member_expression(&update_expr.argument) + { + if is_state_member_expression(outer_member_expression) { + ctx.diagnostic(no_direct_mutation_state_diagnostic(update_expr.span)); + } + } + } + + _ => {} + } + } +} + // check current node is this.state.xx fn is_state_member_expression(expression: &StaticMemberExpression<'_>) -> bool { if let Expression::ThisExpression(_) = &expression.object { @@ -133,9 +171,9 @@ fn get_static_member_expression_obj<'a, 'b>( } fn should_ignore_component<'a, 'b>(node: &'b AstNode<'a>, ctx: &'b LintContext<'a>) -> bool { - let mut is_constructor: bool = false; - let mut is_call_expression_node: bool = false; - let mut is_component: bool = false; + let mut is_constructor = false; + let mut is_call_expression = false; + let mut is_component = false; for parent in ctx.nodes().iter_parents(node.id()) { if let AstKind::MethodDefinition(method_def) = parent.kind() { @@ -144,54 +182,20 @@ fn should_ignore_component<'a, 'b>(node: &'b AstNode<'a>, ctx: &'b LintContext<' } } - if let AstKind::CallExpression(_) = parent.kind() { - is_call_expression_node = true; + if matches!(parent.kind(), AstKind::CallExpression(_)) { + is_call_expression = true; } if is_es6_component(parent) || is_es5_component(parent) { is_component = true; } - } - - is_constructor && !is_call_expression_node || !is_component -} - -impl Rule for NoDirectMutationState { - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - match node.kind() { - AstKind::AssignmentExpression(assignment_expr) => { - if should_ignore_component(node, ctx) { - return; - } - - if let Some(assignment) = assignment_expr.left.as_simple_assignment_target() { - if let Some(outer_member_expression) = get_outer_member_expression(assignment) { - if is_state_member_expression(outer_member_expression) { - ctx.diagnostic(no_direct_mutation_state_diagnostic( - assignment_expr.left.span(), - )); - } - } - } - } - AstKind::UpdateExpression(update_expr) => { - if should_ignore_component(node, ctx) { - return; - } - - if let Some(outer_member_expression) = - get_outer_member_expression(&update_expr.argument) - { - if is_state_member_expression(outer_member_expression) { - ctx.diagnostic(no_direct_mutation_state_diagnostic(update_expr.span)); - } - } - } - - _ => {} + if matches!(parent.kind(), AstKind::Class(_)) { + break; } } + + (is_constructor && !is_call_expression) || !is_component } #[test] @@ -199,16 +203,12 @@ fn test() { use crate::tester::Tester; let pass = vec![ - ( - "var Hello = createReactClass({ + "var Hello = createReactClass({ render: function() { return
Hello {this.props.name}
; } });", - None, - ), - ( - " + " var Hello = createReactClass({ render: function() { var obj = {state: {}}; @@ -217,17 +217,11 @@ fn test() { } }); ", - None, - ), - ( - " + " var Hello = 'foo'; module.exports = {}; ", - None, - ), - ( - " + " class Hello { getFoo() { this.state.foo = 'bar' @@ -235,30 +229,21 @@ fn test() { } } ", - None, - ), - ( - " + " class Hello extends React.Component { constructor() { this.state.foo = 'bar' } } ", - None, - ), - ( - " + " class Hello extends React.Component { constructor() { this.state.foo = 1; } } ", - None, - ), - ( - " + " class OneComponent extends Component { constructor() { super(); @@ -271,13 +256,22 @@ fn test() { } } ", - None, - ), + " + describe('Component spec', () => { + it('should apply default props on rerender', () => { + class Outer extends Component { + constructor() { + super(); + this.state = { i: 1 }; + } + } + }); + }); +", ]; let fail = vec![ - ( - r#" + r#" var Hello = createReactClass({ componentWillMount() { @@ -297,10 +291,7 @@ fn test() { } }); "#, - None, - ), - ( - " + " var Hello = createReactClass({ render: function() { this.state.foo++; @@ -308,10 +299,7 @@ fn test() { } }); ", - None, - ), - ( - r#" + r#" var Hello = createReactClass({ render: function() { this.state.person.name= "bar" @@ -319,10 +307,7 @@ fn test() { } }); "#, - None, - ), - ( - r#" + r#" var Hello = createReactClass({ render: function() { this.state.person.name.first = "bar" @@ -330,10 +315,7 @@ fn test() { } }); "#, - None, - ), - ( - r#" + r#" var Hello = createReactClass({ render: function() { this.state.person.name.first = "bar" @@ -342,10 +324,7 @@ fn test() { } }); "#, - None, - ), - ( - r#" + r#" class Hello extends React.Component { constructor() { someFn() @@ -355,10 +334,7 @@ fn test() { } } "#, - None, - ), - ( - r#" + r#" class Hello extends React.Component { constructor(props) { super(props) @@ -368,78 +344,55 @@ fn test() { } } "#, - None, - ), - ( - r#" + r#" class Hello extends React.Component { componentWillMount() { this.state.foo = "bar" } } "#, - None, - ), - ( - r#" + r#" class Hello extends React.Component { componentDidMount() { this.state.foo = "bar" } } "#, - None, - ), - ( - r#" + r#" class Hello extends React.Component { componentWillReceiveProps() { this.state.foo = "bar" } } "#, - None, - ), - ( - r#" + r#" class Hello extends React.Component { shouldComponentUpdate() { this.state.foo = "bar" } } "#, - None, - ), - ( - r#" + r#" class Hello extends React.Component { componentWillUpdate() { this.state.foo = "bar" } } "#, - None, - ), - ( - r#" + r#" class Hello extends React.Component { componentDidUpdate() { this.state.foo = "bar" } } "#, - None, - ), - ( - r#" + r#" class Hello extends React.Component { componentWillUnmount() { this.state.foo = "bar" } } "#, - None, - ), ]; Tester::new(NoDirectMutationState::NAME, pass, fail).test_and_snapshot();