Skip to content

Commit

Permalink
[flake8-pie] Implement unnecessary-range-start (PIE808) (#6690)
Browse files Browse the repository at this point in the history
  • Loading branch information
harupy authored Aug 19, 2023
1 parent 17af12e commit a489b96
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 0 deletions.
13 changes: 13 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_pie/PIE808.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# PIE808
range(0, 10)

# OK
range(x, 10)
range(-15, 10)
range(10)
range(0)
range(0, 10, x)
range(0, 10, 1)
range(0, 10, step=1)
range(start=0, stop=10)
range(0, stop=10)
3 changes: 3 additions & 0 deletions crates/ruff/src/checkers/ast/analyze/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::UnnecessaryDictKwargs) {
flake8_pie::rules::unnecessary_dict_kwargs(checker, expr, keywords);
}
if checker.enabled(Rule::UnnecessaryRangeStart) {
flake8_pie::rules::unnecessary_range_start(checker, call);
}
if checker.enabled(Rule::ExecBuiltin) {
flake8_bandit::rules::exec_used(checker, func);
}
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Pie, "800") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessarySpread),
(Flake8Pie, "804") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessaryDictKwargs),
(Flake8Pie, "807") => (RuleGroup::Unspecified, rules::flake8_pie::rules::ReimplementedListBuiltin),
(Flake8Pie, "808") => (RuleGroup::Unspecified, rules::flake8_pie::rules::UnnecessaryRangeStart),
(Flake8Pie, "810") => (RuleGroup::Unspecified, rules::flake8_pie::rules::MultipleStartsEndsWith),

// flake8-commas
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/rules/flake8_pie/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod tests {
#[test_case(Rule::DuplicateClassFieldDefinition, Path::new("PIE794.py"))]
#[test_case(Rule::UnnecessaryDictKwargs, Path::new("PIE804.py"))]
#[test_case(Rule::MultipleStartsEndsWith, Path::new("PIE810.py"))]
#[test_case(Rule::UnnecessaryRangeStart, Path::new("PIE808.py"))]
#[test_case(Rule::UnnecessaryPass, Path::new("PIE790.py"))]
#[test_case(Rule::UnnecessarySpread, Path::new("PIE800.py"))]
#[test_case(Rule::ReimplementedListBuiltin, Path::new("PIE807.py"))]
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff/src/rules/flake8_pie/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub(crate) use no_unnecessary_pass::*;
pub(crate) use non_unique_enums::*;
pub(crate) use reimplemented_list_builtin::*;
pub(crate) use unnecessary_dict_kwargs::*;
pub(crate) use unnecessary_range_start::*;
pub(crate) use unnecessary_spread::*;

mod duplicate_class_field_definition;
Expand All @@ -12,4 +13,5 @@ mod no_unnecessary_pass;
mod non_unique_enums;
mod reimplemented_list_builtin;
mod unnecessary_dict_kwargs;
mod unnecessary_range_start;
mod unnecessary_spread;
95 changes: 95 additions & 0 deletions crates/ruff/src/rules/flake8_pie/rules/unnecessary_range_start.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use num_bigint::BigInt;

use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::{AlwaysAutofixableViolation, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Constant, Expr, Ranged};

use crate::autofix::edits::{remove_argument, Parentheses};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;

/// ## What it does
/// Checks for `range` calls with an unnecessary `start` argument.
///
/// ## Why is this bad?
/// `range(0, x)` is equivalent to `range(x)`, as `0` is the default value for
/// the `start` argument. Omitting the `start` argument makes the code more
/// concise and idiomatic.
///
/// ## Example
/// ```python
/// range(0, 3)
/// ```
///
/// Use instead:
/// ```python
/// range(3)
/// ```
///
/// ## References
/// - [Python documentation: `range`](https://docs.python.org/3/library/stdtypes.html#range)
#[violation]
pub struct UnnecessaryRangeStart;

impl AlwaysAutofixableViolation for UnnecessaryRangeStart {
#[derive_message_formats]
fn message(&self) -> String {
format!("Unnecessary `start` argument in `range`")
}

fn autofix_title(&self) -> String {
format!("Remove `start` argument")
}
}

/// PIE808
pub(crate) fn unnecessary_range_start(checker: &mut Checker, call: &ast::ExprCall) {
// Verify that the call is to the `range` builtin.
let Expr::Name(ast::ExprName { id, .. }) = call.func.as_ref() else {
return;
};
if id != "range" {
return;
};
if !checker.semantic().is_builtin("range") {
return;
};

// `range` doesn't accept keyword arguments.
if !call.arguments.keywords.is_empty() {
return;
}

// Verify that the call has exactly two arguments (no `step`).
let [start, _] = call.arguments.args.as_slice() else {
return;
};

// Verify that the `start` argument is the literal `0`.
let Expr::Constant(ast::ExprConstant {
value: Constant::Int(value),
..
}) = start
else {
return;
};
if *value != BigInt::from(0) {
return;
};

let mut diagnostic = Diagnostic::new(UnnecessaryRangeStart, start.range());
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
remove_argument(
&start,
&call.arguments,
Parentheses::Preserve,
checker.locator(),
checker.source_type,
)
.map(Fix::automatic)
});
}
checker.diagnostics.push(diagnostic);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
source: crates/ruff/src/rules/flake8_pie/mod.rs
---
PIE808.py:2:7: PIE808 [*] Unnecessary `start` argument in `range`
|
1 | # PIE808
2 | range(0, 10)
| ^ PIE808
3 |
4 | # OK
|
= help: Remove `start` argument

Fix
1 1 | # PIE808
2 |-range(0, 10)
2 |+range(10)
3 3 |
4 4 | # OK
5 5 | range(x, 10)


1 change: 1 addition & 0 deletions ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a489b96

Please sign in to comment.