mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
fix(linter/no-direct-mutation-state): false positive when class is declared inside a CallExpression (#3294)
fixes #3290
This commit is contained in:
parent
8ff1ffba74
commit
e12323f4f9
1 changed files with 80 additions and 127 deletions
|
|
@ -84,6 +84,44 @@ declare_oxc_lint!(
|
||||||
correctness
|
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
|
// check current node is this.state.xx
|
||||||
fn is_state_member_expression(expression: &StaticMemberExpression<'_>) -> bool {
|
fn is_state_member_expression(expression: &StaticMemberExpression<'_>) -> bool {
|
||||||
if let Expression::ThisExpression(_) = &expression.object {
|
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 {
|
fn should_ignore_component<'a, 'b>(node: &'b AstNode<'a>, ctx: &'b LintContext<'a>) -> bool {
|
||||||
let mut is_constructor: bool = false;
|
let mut is_constructor = false;
|
||||||
let mut is_call_expression_node: bool = false;
|
let mut is_call_expression = false;
|
||||||
let mut is_component: bool = false;
|
let mut is_component = false;
|
||||||
|
|
||||||
for parent in ctx.nodes().iter_parents(node.id()) {
|
for parent in ctx.nodes().iter_parents(node.id()) {
|
||||||
if let AstKind::MethodDefinition(method_def) = parent.kind() {
|
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() {
|
if matches!(parent.kind(), AstKind::CallExpression(_)) {
|
||||||
is_call_expression_node = true;
|
is_call_expression = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_es6_component(parent) || is_es5_component(parent) {
|
if is_es6_component(parent) || is_es5_component(parent) {
|
||||||
is_component = true;
|
is_component = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is_constructor && !is_call_expression_node || !is_component
|
if matches!(parent.kind(), AstKind::Class(_)) {
|
||||||
}
|
break;
|
||||||
|
|
||||||
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) => {
|
(is_constructor && !is_call_expression) || !is_component
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -199,15 +203,11 @@ fn test() {
|
||||||
use crate::tester::Tester;
|
use crate::tester::Tester;
|
||||||
|
|
||||||
let pass = vec![
|
let pass = vec![
|
||||||
(
|
|
||||||
"var Hello = createReactClass({
|
"var Hello = createReactClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return <div>Hello {this.props.name}</div>;
|
return <div>Hello {this.props.name}</div>;
|
||||||
}
|
}
|
||||||
});",
|
});",
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"
|
"
|
||||||
var Hello = createReactClass({
|
var Hello = createReactClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
@ -217,16 +217,10 @@ fn test() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
",
|
",
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"
|
"
|
||||||
var Hello = 'foo';
|
var Hello = 'foo';
|
||||||
module.exports = {};
|
module.exports = {};
|
||||||
",
|
",
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"
|
"
|
||||||
class Hello {
|
class Hello {
|
||||||
getFoo() {
|
getFoo() {
|
||||||
|
|
@ -235,9 +229,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"
|
"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -245,9 +236,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"
|
"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -255,9 +243,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"
|
"
|
||||||
class OneComponent extends Component {
|
class OneComponent extends Component {
|
||||||
constructor() {
|
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![
|
let fail = vec![
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
var Hello = createReactClass({
|
var Hello = createReactClass({
|
||||||
|
|
||||||
|
|
@ -297,9 +291,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"
|
"
|
||||||
var Hello = createReactClass({
|
var Hello = createReactClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
@ -308,9 +299,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
",
|
",
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
var Hello = createReactClass({
|
var Hello = createReactClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
@ -319,9 +307,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
var Hello = createReactClass({
|
var Hello = createReactClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
@ -330,9 +315,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
var Hello = createReactClass({
|
var Hello = createReactClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
@ -342,9 +324,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -355,9 +334,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|
@ -368,9 +344,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
|
@ -378,9 +351,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
|
@ -388,9 +358,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
componentWillReceiveProps() {
|
componentWillReceiveProps() {
|
||||||
|
|
@ -398,9 +365,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
shouldComponentUpdate() {
|
shouldComponentUpdate() {
|
||||||
|
|
@ -408,9 +372,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
componentWillUpdate() {
|
componentWillUpdate() {
|
||||||
|
|
@ -418,9 +379,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
|
|
@ -428,9 +386,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
r#"
|
r#"
|
||||||
class Hello extends React.Component {
|
class Hello extends React.Component {
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
|
@ -438,8 +393,6 @@ fn test() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
None,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
Tester::new(NoDirectMutationState::NAME, pass, fail).test_and_snapshot();
|
Tester::new(NoDirectMutationState::NAME, pass, fail).test_and_snapshot();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue