Make unused variable pattern configurable (#265)

This commit is contained in:
Harutaka Kawamura 2022-09-24 23:43:39 +09:00 committed by GitHub
parent d77979429c
commit dce86e065b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 116 additions and 10 deletions

View File

@ -10,13 +10,13 @@ except ValueError as e:
print(e)
def f():
def f1():
x = 1
y = 2
z = x + y
def g():
def f2():
foo = (1, 2)
(a, b) = (1, 2)
@ -26,6 +26,12 @@ def g():
(x, y) = baz = bar
def h():
def f3():
locals()
x = 1
def f4():
_ = 1
__ = 1
_discarded = 1

View File

@ -1,6 +1,7 @@
use std::collections::BTreeSet;
use itertools::izip;
use regex::Regex;
use rustpython_parser::ast::{
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword,
Location, Stmt, StmtKind, Unaryop,
@ -71,7 +72,11 @@ pub fn check_not_tests(
}
/// Check UnusedVariable compliance.
pub fn check_unused_variables(scope: &Scope, locator: &dyn CheckLocator) -> Vec<Check> {
pub fn check_unused_variables(
scope: &Scope,
locator: &dyn CheckLocator,
dummy_variable_rgx: &Regex,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
if matches!(
@ -82,13 +87,12 @@ pub fn check_unused_variables(scope: &Scope, locator: &dyn CheckLocator) -> Vec<
}
for (name, binding) in scope.values.iter() {
// TODO(charlie): Ignore if using `locals`.
if binding.used.is_none()
&& name != "_"
&& matches!(binding.kind, BindingKind::Assignment)
&& !dummy_variable_rgx.is_match(name)
&& name != "__tracebackhide__"
&& name != "__traceback_info__"
&& name != "__traceback_supplement__"
&& matches!(binding.kind, BindingKind::Assignment)
{
checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),

View File

@ -1415,8 +1415,11 @@ impl<'a> Checker<'a> {
fn check_deferred_assignments(&mut self) {
if self.settings.select.contains(&CheckCode::F841) {
while let Some(index) = self.deferred_assignments.pop() {
self.checks
.extend(checks::check_unused_variables(&self.scopes[index], self));
self.checks.extend(checks::check_unused_variables(
&self.scopes[index],
self,
&self.settings.dummy_variable_rgx,
));
}
}
}

View File

@ -182,6 +182,8 @@ pub fn check_lines(
mod tests {
use std::collections::BTreeSet;
use regex::Regex;
use super::check_lines;
use super::*;
@ -198,6 +200,7 @@ mod tests {
exclude: vec![],
extend_exclude: vec![],
select: BTreeSet::from_iter(vec![CheckCode::E501]),
dummy_variable_rgx: Regex::new(r"^_+").unwrap(),
};
check_lines(
&mut checks,

View File

@ -119,6 +119,7 @@ mod tests {
use std::path::Path;
use anyhow::Result;
use regex::Regex;
use rustpython_parser::lexer;
use rustpython_parser::lexer::LexResult;
@ -596,6 +597,21 @@ mod tests {
Ok(())
}
#[test]
fn f841_dummy_variable_rgx() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F841.py"),
&settings::Settings {
dummy_variable_rgx: Regex::new(r"^z$").unwrap(),
..settings::Settings::for_rule(CheckCode::F841)
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn f901() -> Result<()> {
let mut checks = check_path(

View File

@ -1,5 +1,6 @@
extern crate core;
use regex::Regex;
use std::io;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
@ -85,6 +86,9 @@ struct Cli {
/// Enable automatic additions of noqa directives to failing lines.
#[clap(long, action)]
add_noqa: bool,
/// Regular expression matching the name of dummy variables.
#[clap(long)]
dummy_variable_rgx: Option<Regex>,
}
#[cfg(feature = "update-informer")]
@ -265,6 +269,9 @@ fn inner_main() -> Result<ExitCode> {
if !cli.extend_ignore.is_empty() {
settings.ignore(&cli.extend_ignore);
}
if let Some(dummy_variable_rgx) = cli.dummy_variable_rgx {
settings.dummy_variable_rgx = dummy_variable_rgx;
}
if cli.show_settings && cli.show_files {
eprintln!("Error: specify --show-settings or show-files (not both).");

View File

@ -30,6 +30,7 @@ pub struct Config {
pub extend_exclude: Option<Vec<String>>,
pub select: Option<Vec<CheckCode>>,
pub ignore: Option<Vec<CheckCode>>,
pub dummy_variable_rgx: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
@ -130,6 +131,7 @@ mod tests {
extend_exclude: None,
select: None,
ignore: None,
dummy_variable_rgx: None,
})
})
);
@ -150,6 +152,7 @@ line-length = 79
extend_exclude: None,
select: None,
ignore: None,
dummy_variable_rgx: None,
})
})
);
@ -170,6 +173,7 @@ exclude = ["foo.py"]
extend_exclude: None,
select: None,
ignore: None,
dummy_variable_rgx: None,
})
})
);
@ -190,6 +194,7 @@ select = ["E501"]
extend_exclude: None,
select: Some(vec![CheckCode::E501]),
ignore: None,
dummy_variable_rgx: None,
})
})
);
@ -210,6 +215,7 @@ ignore = ["E501"]
extend_exclude: None,
select: None,
ignore: Some(vec![CheckCode::E501]),
dummy_variable_rgx: None,
})
})
);
@ -274,6 +280,7 @@ other-attribute = 1
]),
select: None,
ignore: None,
dummy_variable_rgx: None,
}
);

View File

@ -2,9 +2,10 @@ use std::collections::BTreeSet;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use anyhow::Result;
use anyhow::{anyhow, Result};
use glob::Pattern;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::checks::{CheckCode, DEFAULT_CHECK_CODES};
use crate::fs;
@ -43,6 +44,7 @@ pub struct Settings {
pub exclude: Vec<FilePattern>,
pub extend_exclude: Vec<FilePattern>,
pub select: BTreeSet<CheckCode>,
pub dummy_variable_rgx: Regex,
}
impl Settings {
@ -54,6 +56,7 @@ impl Settings {
exclude: vec![],
extend_exclude: vec![],
select: BTreeSet::from([check_code]),
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
}
}
@ -65,6 +68,7 @@ impl Settings {
exclude: vec![],
extend_exclude: vec![],
select: BTreeSet::from_iter(check_codes),
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
}
}
}
@ -72,6 +76,7 @@ impl Settings {
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
self.line_length.hash(state);
self.dummy_variable_rgx.as_str().hash(state);
for value in self.select.iter() {
value.hash(state);
}
@ -102,6 +107,9 @@ static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
]
});
static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
Lazy::new(|| Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap());
impl Settings {
pub fn from_pyproject(
pyproject: Option<PathBuf>,
@ -133,6 +141,11 @@ impl Settings {
} else {
BTreeSet::from_iter(DEFAULT_CHECK_CODES)
},
dummy_variable_rgx: match config.dummy_variable_rgx {
Some(pattern) => Regex::new(&pattern)
.map_err(|e| anyhow!("Invalid dummy-variable-rgx value: {e}"))?,
None => DEFAULT_DUMMY_VARIABLE_RGX.clone(),
},
pyproject,
project_root,
};

View File

@ -0,0 +1,47 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnusedVariable: e
location:
row: 3
column: 1
fix: ~
- kind:
UnusedVariable: foo
location:
row: 20
column: 5
fix: ~
- kind:
UnusedVariable: a
location:
row: 21
column: 6
fix: ~
- kind:
UnusedVariable: b
location:
row: 21
column: 9
fix: ~
- kind:
UnusedVariable: _
location:
row: 35
column: 5
fix: ~
- kind:
UnusedVariable: __
location:
row: 36
column: 5
fix: ~
- kind:
UnusedVariable: _discarded
location:
row: 37
column: 5
fix: ~