mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 12:29:28 +00:00
[pylint
] Implement potential-index-error
(PLE0643
) (#9545)
## Summary add `potential-index-error` rule (`PLE0643`) See: #970 ## Test Plan `cargo test`
This commit is contained in:
parent
866bea60a5
commit
49a445a23d
8 changed files with 131 additions and 0 deletions
9
crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/pylint/potential_index_error.py
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
print([1, 2, 3][3]) # PLE0643
|
||||||
|
print([1, 2, 3][-4]) # PLE0643
|
||||||
|
print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643
|
||||||
|
print([1, 2, 3][-999999999999999999999999999999999999999999]) # PLE0643
|
||||||
|
|
||||||
|
print([1, 2, 3][2]) # OK
|
||||||
|
print([1, 2, 3][0]) # OK
|
||||||
|
print([1, 2, 3][-3]) # OK
|
||||||
|
print([1, 2, 3][3:]) # OK
|
|
@ -125,6 +125,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::SliceCopy) {
|
if checker.enabled(Rule::SliceCopy) {
|
||||||
refurb::rules::slice_copy(checker, subscript);
|
refurb::rules::slice_copy(checker, subscript);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::PotentialIndexError) {
|
||||||
|
pylint::rules::potential_index_error(checker, value, slice);
|
||||||
|
}
|
||||||
|
|
||||||
pandas_vet::rules::subscript(checker, value, expr);
|
pandas_vet::rules::subscript(checker, value, expr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,6 +229,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Pylint, "E0307") => (RuleGroup::Stable, rules::pylint::rules::InvalidStrReturnType),
|
(Pylint, "E0307") => (RuleGroup::Stable, rules::pylint::rules::InvalidStrReturnType),
|
||||||
(Pylint, "E0604") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllObject),
|
(Pylint, "E0604") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllObject),
|
||||||
(Pylint, "E0605") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllFormat),
|
(Pylint, "E0605") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllFormat),
|
||||||
|
(Pylint, "E0643") => (RuleGroup::Preview, rules::pylint::rules::PotentialIndexError),
|
||||||
(Pylint, "E0704") => (RuleGroup::Preview, rules::pylint::rules::MisplacedBareRaise),
|
(Pylint, "E0704") => (RuleGroup::Preview, rules::pylint::rules::MisplacedBareRaise),
|
||||||
(Pylint, "E1132") => (RuleGroup::Preview, rules::pylint::rules::RepeatedKeywordArgument),
|
(Pylint, "E1132") => (RuleGroup::Preview, rules::pylint::rules::RepeatedKeywordArgument),
|
||||||
(Pylint, "E1142") => (RuleGroup::Stable, rules::pylint::rules::AwaitOutsideAsync),
|
(Pylint, "E1142") => (RuleGroup::Stable, rules::pylint::rules::AwaitOutsideAsync),
|
||||||
|
|
|
@ -167,6 +167,7 @@ mod tests {
|
||||||
#[test_case(Rule::NoClassmethodDecorator, Path::new("no_method_decorator.py"))]
|
#[test_case(Rule::NoClassmethodDecorator, Path::new("no_method_decorator.py"))]
|
||||||
#[test_case(Rule::UnnecessaryDunderCall, Path::new("unnecessary_dunder_call.py"))]
|
#[test_case(Rule::UnnecessaryDunderCall, Path::new("unnecessary_dunder_call.py"))]
|
||||||
#[test_case(Rule::NoStaticmethodDecorator, Path::new("no_method_decorator.py"))]
|
#[test_case(Rule::NoStaticmethodDecorator, Path::new("no_method_decorator.py"))]
|
||||||
|
#[test_case(Rule::PotentialIndexError, Path::new("potential_index_error.py"))]
|
||||||
#[test_case(Rule::SuperWithoutBrackets, Path::new("super_without_brackets.py"))]
|
#[test_case(Rule::SuperWithoutBrackets, Path::new("super_without_brackets.py"))]
|
||||||
#[test_case(
|
#[test_case(
|
||||||
Rule::UnnecessaryDictIndexLookup,
|
Rule::UnnecessaryDictIndexLookup,
|
||||||
|
|
|
@ -42,6 +42,7 @@ pub(crate) use no_self_use::*;
|
||||||
pub(crate) use non_ascii_module_import::*;
|
pub(crate) use non_ascii_module_import::*;
|
||||||
pub(crate) use non_ascii_name::*;
|
pub(crate) use non_ascii_name::*;
|
||||||
pub(crate) use nonlocal_without_binding::*;
|
pub(crate) use nonlocal_without_binding::*;
|
||||||
|
pub(crate) use potential_index_error::*;
|
||||||
pub(crate) use property_with_parameters::*;
|
pub(crate) use property_with_parameters::*;
|
||||||
pub(crate) use redefined_argument_from_local::*;
|
pub(crate) use redefined_argument_from_local::*;
|
||||||
pub(crate) use redefined_loop_name::*;
|
pub(crate) use redefined_loop_name::*;
|
||||||
|
@ -124,6 +125,7 @@ mod no_self_use;
|
||||||
mod non_ascii_module_import;
|
mod non_ascii_module_import;
|
||||||
mod non_ascii_name;
|
mod non_ascii_name;
|
||||||
mod nonlocal_without_binding;
|
mod nonlocal_without_binding;
|
||||||
|
mod potential_index_error;
|
||||||
mod property_with_parameters;
|
mod property_with_parameters;
|
||||||
mod redefined_argument_from_local;
|
mod redefined_argument_from_local;
|
||||||
mod redefined_loop_name;
|
mod redefined_loop_name;
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::{self as ast, Expr};
|
||||||
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for hard-coded sequence accesses that are known to be out of bounds.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Attempting to access a sequence with an out-of-bounds index will cause an
|
||||||
|
/// `IndexError` to be raised at runtime. When the sequence and index are
|
||||||
|
/// defined statically (e.g., subscripts on `list` and `tuple` literals, with
|
||||||
|
/// integer indexes), such errors can be detected ahead of time.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// print([0, 1, 2][3])
|
||||||
|
/// ```
|
||||||
|
#[violation]
|
||||||
|
pub struct PotentialIndexError;
|
||||||
|
|
||||||
|
impl Violation for PotentialIndexError {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!("Potential IndexError")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PLE0643
|
||||||
|
pub(crate) fn potential_index_error(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||||
|
// Determine the length of the sequence.
|
||||||
|
let length = match value {
|
||||||
|
Expr::Tuple(ast::ExprTuple { elts, .. }) | Expr::List(ast::ExprList { elts, .. }) => {
|
||||||
|
match i64::try_from(elts.len()) {
|
||||||
|
Ok(length) => length,
|
||||||
|
Err(_) => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine the index value.
|
||||||
|
let index = match slice {
|
||||||
|
Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||||
|
value: ast::Number::Int(number_value),
|
||||||
|
..
|
||||||
|
}) => number_value.as_i64(),
|
||||||
|
Expr::UnaryOp(ast::ExprUnaryOp {
|
||||||
|
op: ast::UnaryOp::USub,
|
||||||
|
operand,
|
||||||
|
..
|
||||||
|
}) => match operand.as_ref() {
|
||||||
|
Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||||
|
value: ast::Number::Int(number_value),
|
||||||
|
..
|
||||||
|
}) => number_value.as_i64().map(|number| -number),
|
||||||
|
_ => return,
|
||||||
|
},
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emit a diagnostic if the index is out of bounds. If the index can't be represented as an
|
||||||
|
// `i64`, but the length _can_, then the index is definitely out of bounds.
|
||||||
|
if index.map_or(true, |index| index >= length || index < -length) {
|
||||||
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(PotentialIndexError, slice.range()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||||
|
---
|
||||||
|
potential_index_error.py:1:17: PLE0643 Potential IndexError
|
||||||
|
|
|
||||||
|
1 | print([1, 2, 3][3]) # PLE0643
|
||||||
|
| ^ PLE0643
|
||||||
|
2 | print([1, 2, 3][-4]) # PLE0643
|
||||||
|
3 | print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643
|
||||||
|
|
|
||||||
|
|
||||||
|
potential_index_error.py:2:17: PLE0643 Potential IndexError
|
||||||
|
|
|
||||||
|
1 | print([1, 2, 3][3]) # PLE0643
|
||||||
|
2 | print([1, 2, 3][-4]) # PLE0643
|
||||||
|
| ^^ PLE0643
|
||||||
|
3 | print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643
|
||||||
|
4 | print([1, 2, 3][-999999999999999999999999999999999999999999]) # PLE0643
|
||||||
|
|
|
||||||
|
|
||||||
|
potential_index_error.py:3:17: PLE0643 Potential IndexError
|
||||||
|
|
|
||||||
|
1 | print([1, 2, 3][3]) # PLE0643
|
||||||
|
2 | print([1, 2, 3][-4]) # PLE0643
|
||||||
|
3 | print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLE0643
|
||||||
|
4 | print([1, 2, 3][-999999999999999999999999999999999999999999]) # PLE0643
|
||||||
|
|
|
||||||
|
|
||||||
|
potential_index_error.py:4:17: PLE0643 Potential IndexError
|
||||||
|
|
|
||||||
|
2 | print([1, 2, 3][-4]) # PLE0643
|
||||||
|
3 | print([1, 2, 3][999999999999999999999999999999999999999999]) # PLE0643
|
||||||
|
4 | print([1, 2, 3][-999999999999999999999999999999999999999999]) # PLE0643
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLE0643
|
||||||
|
5 |
|
||||||
|
6 | print([1, 2, 3][2]) # OK
|
||||||
|
|
|
||||||
|
|
||||||
|
|
2
ruff.schema.json
generated
2
ruff.schema.json
generated
|
@ -3129,6 +3129,8 @@
|
||||||
"PLE060",
|
"PLE060",
|
||||||
"PLE0604",
|
"PLE0604",
|
||||||
"PLE0605",
|
"PLE0605",
|
||||||
|
"PLE064",
|
||||||
|
"PLE0643",
|
||||||
"PLE07",
|
"PLE07",
|
||||||
"PLE070",
|
"PLE070",
|
||||||
"PLE0704",
|
"PLE0704",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue