feat(linter): eslint/guard-for-in (#2746)

Rule details: https://eslint.org/docs/latest/rules/guard-for-in

---------

Co-authored-by: j.buendia <j.buendia>
This commit is contained in:
Jose 2024-03-18 12:36:26 +01:00 committed by GitHub
parent 95ac265504
commit 22c84c546a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 147 additions and 0 deletions

View file

@ -43,6 +43,7 @@ mod eslint {
pub mod eqeqeq;
pub mod for_direction;
pub mod getter_return;
pub mod guard_for_in;
pub mod max_lines;
pub mod no_array_constructor;
pub mod no_async_promise_executor;
@ -360,6 +361,7 @@ oxc_macros::declare_all_lint_rules! {
eslint::eqeqeq,
eslint::for_direction,
eslint::getter_return,
eslint::guard_for_in,
eslint::max_lines,
eslint::no_ternary,
eslint::no_this_before_super,

View file

@ -0,0 +1,100 @@
use oxc_ast::ast::Statement;
use oxc_ast::AstKind;
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use crate::{context::LintContext, rule::Rule, AstNode};
#[derive(Debug, Error, Diagnostic)]
#[error("eslint(guard-for-in): Require `for-in` loops to include an `if` statement")]
#[diagnostic(severity(warning), help("The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype."))]
struct GuardForInDiagnostic(#[label] pub Span);
#[derive(Debug, Default, Clone)]
pub struct GuardForIn;
declare_oxc_lint!(
/// ### What it does
/// This rule is aimed at preventing unexpected behavior that could arise from using a for in loop without filtering the results in the loop. As such, it will warn when for in loops do not filter their results with an if statement.
///
/// ### Why is this bad?
///
///
/// ### Example
/// ```javascript
/// for (key in foo) {
// doSomething(key);
// }
/// ```
GuardForIn,
style
);
impl Rule for GuardForIn {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::ForInStatement(for_in_statement) = node.kind() {
match &for_in_statement.body {
Statement::EmptyStatement(_) | Statement::IfStatement(_) => return,
Statement::BlockStatement(block_body) if block_body.body.is_empty() => return,
Statement::BlockStatement(block_body)
if block_body.body.len() == 1
&& matches!(block_body.body[0], Statement::IfStatement(_)) =>
{
return
}
Statement::BlockStatement(block_body) if block_body.body.len() >= 1 => {
let block_statement = &block_body.body[0];
if let Statement::IfStatement(i) = block_statement {
if let Statement::ContinueStatement(_) = &i.consequent {
return;
}
if let Statement::BlockStatement(consequent_block) = &i.consequent {
if consequent_block.body.len() == 1
&& matches!(
&consequent_block.body[0],
Statement::ContinueStatement(_)
)
{
return;
}
}
}
}
_ => {}
}
ctx.diagnostic(GuardForInDiagnostic(Span::new(
for_in_statement.span.start,
for_in_statement.span.end,
)));
}
}
}
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec![
"for (var x in o);",
"for (var x in o) {}",
"for (var x in o) if (x) f();",
"for (var x in o) { if (x) { f(); } }",
"for (var x in o) { if (x) continue; f(); }",
"for (var x in o) { if (x) { continue; } f(); }",
];
let fail = vec![
"for (var x in o) { if (x) { f(); continue; } g(); }",
"for (var x in o) { if (x) { continue; f(); } g(); }",
"for (var x in o) { if (x) { f(); } g(); }",
"for (var x in o) { if (x) f(); g(); }",
"for (var x in o) { foo() }",
"for (var x in o) foo();",
];
Tester::new(GuardForIn::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,45 @@
---
source: crates/oxc_linter/src/tester.rs
expression: guard_for_in
---
⚠ eslint(guard-for-in): Require `for-in` loops to include an `if` statement
╭─[guard_for_in.tsx:1:1]
1 │ for (var x in o) { if (x) { f(); continue; } g(); }
· ───────────────────────────────────────────────────
╰────
help: The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.
⚠ eslint(guard-for-in): Require `for-in` loops to include an `if` statement
╭─[guard_for_in.tsx:1:1]
1 │ for (var x in o) { if (x) { continue; f(); } g(); }
· ───────────────────────────────────────────────────
╰────
help: The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.
⚠ eslint(guard-for-in): Require `for-in` loops to include an `if` statement
╭─[guard_for_in.tsx:1:1]
1 │ for (var x in o) { if (x) { f(); } g(); }
· ─────────────────────────────────────────
╰────
help: The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.
⚠ eslint(guard-for-in): Require `for-in` loops to include an `if` statement
╭─[guard_for_in.tsx:1:1]
1 │ for (var x in o) { if (x) f(); g(); }
· ─────────────────────────────────────
╰────
help: The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.
⚠ eslint(guard-for-in): Require `for-in` loops to include an `if` statement
╭─[guard_for_in.tsx:1:1]
1 │ for (var x in o) { foo() }
· ──────────────────────────
╰────
help: The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.
⚠ eslint(guard-for-in): Require `for-in` loops to include an `if` statement
╭─[guard_for_in.tsx:1:1]
1 │ for (var x in o) foo();
· ───────────────────────
╰────
help: The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.