fix(linter/no-direct-mutation-state): false positive when class is declared inside a CallExpression (#3294)

fixes #3290
This commit is contained in:
Boshen 2024-05-15 14:07:01 +00:00
parent 8ff1ffba74
commit e12323f4f9

View file

@ -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(),
));
}
}
if matches!(parent.kind(), AstKind::Class(_)) {
break;
}
}
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));
}
}
}
_ => {}
}
}
(is_constructor && !is_call_expression) || !is_component
}
#[test]
@ -199,15 +203,11 @@ fn test() {
use crate::tester::Tester;
let pass = vec![
(
"var Hello = createReactClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});",
None,
),
(
"
var Hello = createReactClass({
render: function() {
@ -217,16 +217,10 @@ fn test() {
}
});
",
None,
),
(
"
var Hello = 'foo';
module.exports = {};
",
None,
),
(
"
class Hello {
getFoo() {
@ -235,9 +229,6 @@ fn test() {
}
}
",
None,
),
(
"
class Hello extends React.Component {
constructor() {
@ -245,9 +236,6 @@ fn test() {
}
}
",
None,
),
(
"
class Hello extends React.Component {
constructor() {
@ -255,9 +243,6 @@ fn test() {
}
}
",
None,
),
(
"
class OneComponent extends Component {
constructor() {
@ -271,12 +256,21 @@ 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#"
var Hello = createReactClass({
@ -297,9 +291,6 @@ fn test() {
}
});
"#,
None,
),
(
"
var Hello = createReactClass({
render: function() {
@ -308,9 +299,6 @@ fn test() {
}
});
",
None,
),
(
r#"
var Hello = createReactClass({
render: function() {
@ -319,9 +307,6 @@ fn test() {
}
});
"#,
None,
),
(
r#"
var Hello = createReactClass({
render: function() {
@ -330,9 +315,6 @@ fn test() {
}
});
"#,
None,
),
(
r#"
var Hello = createReactClass({
render: function() {
@ -342,9 +324,6 @@ fn test() {
}
});
"#,
None,
),
(
r#"
class Hello extends React.Component {
constructor() {
@ -355,9 +334,6 @@ fn test() {
}
}
"#,
None,
),
(
r#"
class Hello extends React.Component {
constructor(props) {
@ -368,9 +344,6 @@ fn test() {
}
}
"#,
None,
),
(
r#"
class Hello extends React.Component {
componentWillMount() {
@ -378,9 +351,6 @@ fn test() {
}
}
"#,
None,
),
(
r#"
class Hello extends React.Component {
componentDidMount() {
@ -388,9 +358,6 @@ fn test() {
}
}
"#,
None,
),
(
r#"
class Hello extends React.Component {
componentWillReceiveProps() {
@ -398,9 +365,6 @@ fn test() {
}
}
"#,
None,
),
(
r#"
class Hello extends React.Component {
shouldComponentUpdate() {
@ -408,9 +372,6 @@ fn test() {
}
}
"#,
None,
),
(
r#"
class Hello extends React.Component {
componentWillUpdate() {
@ -418,9 +379,6 @@ fn test() {
}
}
"#,
None,
),
(
r#"
class Hello extends React.Component {
componentDidUpdate() {
@ -428,9 +386,6 @@ fn test() {
}
}
"#,
None,
),
(
r#"
class Hello extends React.Component {
componentWillUnmount() {
@ -438,8 +393,6 @@ fn test() {
}
}
"#,
None,
),
];
Tester::new(NoDirectMutationState::NAME, pass, fail).test_and_snapshot();