Compare commits

...

5 Commits

Author SHA1 Message Date
Charlie Marsh
89afc9db74 Bump version to 0.0.128 2022-11-18 18:50:03 -05:00
Charlie Marsh
0f34cdb7a3 Enable customization of autofixable error codes (#811) 2022-11-18 18:49:13 -05:00
Charlie Marsh
437b6f23b9 Remove warn_on checks (#812) 2022-11-18 18:48:24 -05:00
Charlie Marsh
0fe2b15676 Change NotInTest to NotIsTest 2022-11-18 18:23:40 -05:00
Harutaka Kawamura
e81efa5a3d Implement a --show-source setting (#698) 2022-11-18 14:02:29 -05:00
44 changed files with 477 additions and 320 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.127
rev: v0.0.128
hooks:
- id: ruff

28
Cargo.lock generated
View File

@@ -49,6 +49,16 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7"
[[package]]
name = "annotate-snippets"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3b9d411ecbaf79885c6df4d75fff75858d5995ff25385657a28af47e82f9c36"
dependencies = [
"unicode-width",
"yansi-term",
]
[[package]]
name = "anyhow"
version = "1.0.66"
@@ -417,7 +427,7 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5b5db619f3556839cb2223ae86ff3f9a09da2c5013be42bc9af08c9589bf70c"
dependencies = [
"annotate-snippets",
"annotate-snippets 0.6.1",
]
[[package]]
@@ -930,7 +940,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.127-dev.0"
version = "0.0.128-dev.0"
dependencies = [
"anyhow",
"clap 4.0.22",
@@ -2238,8 +2248,9 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.127"
version = "0.0.128"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
"assert_cmd",
"atty",
@@ -2287,7 +2298,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.127"
version = "0.0.128"
dependencies = [
"anyhow",
"clap 4.0.22",
@@ -3312,3 +3323,12 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "yansi-term"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
dependencies = [
"winapi 0.3.9",
]

View File

@@ -6,13 +6,14 @@ members = [
[package]
name = "ruff"
version = "0.0.127"
version = "0.0.128"
edition = "2021"
[lib]
name = "ruff"
[dependencies]
annotate-snippets = { version = "0.9.1", features = ["color"] }
anyhow = { version = "1.0.66" }
atty = { version = "0.2.14" }
bincode = { version = "1.3.3" }

View File

@@ -122,7 +122,7 @@ default configuration is equivalent to:
[tool.ruff]
line-length = 88
# Enable Flake's "E" and "F" codes by default.
# Enable Pyflakes `E` and `F` codes by default.
select = ["E", "F"]
ignore = []
@@ -157,18 +157,24 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
target-version = "py310"
```
As an example, the following would configure Ruff to (1) avoid checking for line-length
violations (`E501`) and (2) ignore unused import rules in `__init__.py` files:
As an example, the following would configure Ruff to: (1) avoid checking for line-length
violations (`E501`); (2), always autofix, but never remove unused imports (`F401`); and (3) ignore
import-at-top-of-file errors (`E402`) in `__init__.py` files:
```toml
[tool.ruff]
# Enable Pyflakes and pycodestyle rules.
select = ["E", "F"]
# Never enforce `E501`.
# Never enforce `E501` (line length violations).
ignore = ["E501"]
# Ignore `F401` violations in any `__init__.py` file, and in `path/to/file.py`.
per-file-ignores = {"__init__.py" = ["F401"], "path/to/file.py" = ["F401"]}
# Always autofix, but never try to fix `F401` (unused imports).
fix = true
unfixable = ["F401"]
# Ignore `E402` (import violations in any `__init__.py` file, and in `path/to/file.py`.
per-file-ignores = {"__init__.py" = ["E402"], "path/to/file.py" = ["E402"]}
```
Plugin configurations should be expressed as subsections, e.g.:
@@ -191,7 +197,7 @@ ruff path/to/code/ --select F401 --select F403
See `ruff --help` for more:
```shell
ruff: An extremely fast Python linter.
Ruff: An extremely fast Python linter.
Usage: ruff [OPTIONS] <FILES>...
@@ -227,14 +233,20 @@ Options:
List of paths, used to exclude files and/or directories from checks
--extend-exclude <EXTEND_EXCLUDE>
Like --exclude, but adds additional files and directories on top of the excluded ones
--fixable <FIXABLE>
List of error codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
--unfixable <UNFIXABLE>
List of error codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
--per-file-ignores <PER_FILE_IGNORES>
List of mappings from file pattern to code to exclude
--format <FORMAT>
Output serialization format for error messages [default: text] [possible values: text, json]
--show-source
Show violations with source code
--show-files
See the files ruff will be run against with the current settings
See the files Ruff will be run against with the current settings
--show-settings
See ruff's settings
See Ruff's settings
--add-noqa
Enable automatic additions of noqa directives to failing lines
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
@@ -244,7 +256,7 @@ Options:
--line-length <LINE_LENGTH>
Set the line-length for length-associated checks and automatic formatting
--max-complexity <MAX_COMPLEXITY>
Set the maximum cyclomatic complexity for complexity-associated checks
Max McCabe complexity allowed for a function
--stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin
-h, --help

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.127"
version = "0.0.128"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.127"
version = "0.0.128"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.127-dev.0"
version = "0.0.128-dev.0"
edition = "2021"
[lib]

View File

@@ -243,22 +243,25 @@ mod tests {
fn it_converts_empty() -> Result<()> {
let actual = convert(&HashMap::from([]), None)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
extend_ignore: None,
extend_select: None,
fix: None,
fixable: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
@@ -279,22 +282,25 @@ mod tests {
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
line_length: Some(100),
src: None,
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
extend_ignore: None,
extend_select: None,
fix: None,
fixable: None,
ignore: Some(vec![]),
line_length: Some(100),
per_file_ignores: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
@@ -315,22 +321,25 @@ mod tests {
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
line_length: Some(100),
src: None,
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
extend_ignore: None,
extend_select: None,
fix: None,
fixable: None,
ignore: Some(vec![]),
line_length: Some(100),
per_file_ignores: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
@@ -351,22 +360,25 @@ mod tests {
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
extend_ignore: None,
extend_select: None,
fix: None,
fixable: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
@@ -387,22 +399,25 @@ mod tests {
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
extend_ignore: None,
extend_select: None,
fix: None,
fixable: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: Some(flake8_quotes::settings::Options {
@@ -431,11 +446,16 @@ mod tests {
Some(vec![Plugin::Flake8Docstrings]),
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
extend_ignore: None,
extend_select: None,
fix: None,
fixable: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
select: Some(vec![
CheckCodePrefix::D100,
CheckCodePrefix::D101,
@@ -476,12 +496,10 @@ mod tests {
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
@@ -502,23 +520,26 @@ mod tests {
None,
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
extend_ignore: None,
extend_select: None,
fix: None,
fixable: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::Q,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: Some(flake8_quotes::settings::Options {

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.127"
version = "0.0.128"
edition = "2021"
[dependencies]

View File

@@ -152,10 +152,10 @@ impl<'a> Checker<'a> {
/// Return `true` if a patch should be generated under the given autofix
/// `Mode`.
pub fn patch(&self) -> bool {
pub fn patch(&self, code: &CheckCode) -> bool {
// TODO(charlie): We can't fix errors in f-strings until RustPython adds
// location data.
self.autofix.patch() && self.in_f_string.is_none()
self.autofix.patch() && self.in_f_string.is_none() && self.settings.fixable.contains(code)
}
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
@@ -1256,7 +1256,7 @@ where
args,
keywords,
self.locator,
self.patch(),
self.patch(&CheckCode::C400),
Range::from_located(expr),
) {
self.add_check(check);
@@ -1270,7 +1270,7 @@ where
args,
keywords,
self.locator,
self.patch(),
self.patch(&CheckCode::C401),
Range::from_located(expr),
) {
self.add_check(check);
@@ -1284,7 +1284,7 @@ where
args,
keywords,
self.locator,
self.patch(),
self.patch(&CheckCode::C402),
Range::from_located(expr),
) {
self.add_check(check);
@@ -1299,7 +1299,7 @@ where
args,
keywords,
self.locator,
self.patch(),
self.patch(&CheckCode::C403),
Range::from_located(expr),
)
{
@@ -1315,7 +1315,7 @@ where
args,
keywords,
self.locator,
self.patch(),
self.patch(&CheckCode::C404),
Range::from_located(expr),
)
{
@@ -1330,7 +1330,7 @@ where
args,
keywords,
self.locator,
self.patch(),
self.patch(&CheckCode::C405),
Range::from_located(expr),
) {
self.add_check(check);
@@ -1344,7 +1344,7 @@ where
args,
keywords,
self.locator,
self.patch(),
self.patch(&CheckCode::C406),
Range::from_located(expr),
) {
self.add_check(check);
@@ -1358,7 +1358,7 @@ where
args,
keywords,
self.locator,
self.patch(),
self.patch(&CheckCode::C408),
Range::from_located(expr),
) {
self.add_check(check);
@@ -1372,7 +1372,7 @@ where
func,
args,
self.locator,
self.patch(),
self.patch(&CheckCode::C409),
Range::from_located(expr),
)
{
@@ -1387,7 +1387,7 @@ where
func,
args,
self.locator,
self.patch(),
self.patch(&CheckCode::C410),
Range::from_located(expr),
)
{
@@ -1401,7 +1401,7 @@ where
func,
args,
self.locator,
self.patch(),
self.patch(&CheckCode::C411),
Range::from_located(expr),
) {
self.add_check(check);
@@ -1415,7 +1415,7 @@ where
func,
args,
self.locator,
self.patch(),
self.patch(&CheckCode::C413),
Range::from_located(expr),
)
{
@@ -1667,7 +1667,7 @@ where
elt,
generators,
self.locator,
self.patch(),
self.patch(&CheckCode::C416),
Range::from_located(expr),
) {
self.add_check(check);
@@ -2628,7 +2628,7 @@ impl<'a> Checker<'a> {
let child = self.parents[defined_by];
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
let fix = if self.patch() {
let fix = if self.patch(&CheckCode::F401) {
let deleted: Vec<&Stmt> = self
.deletions
.iter()

View File

@@ -73,7 +73,7 @@ pub fn check_lines(
end_location: Location::new(lineno + 1, line_length + 1),
},
);
if autofix.patch() {
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::deletion(
Location::new(lineno + 1, 0),
Location::new(lineno + 1, line_length + 1),
@@ -195,7 +195,7 @@ pub fn check_lines(
end_location: Location::new(row + 1, end),
},
);
if autofix.patch() {
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::deletion(
Location::new(row + 1, start),
Location::new(row + 1, lines[row].chars().count()),
@@ -223,7 +223,7 @@ pub fn check_lines(
end_location: Location::new(row + 1, end),
},
);
if autofix.patch() {
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
if valid_codes.is_empty() {
check.amend(Fix::deletion(
Location::new(row + 1, start),

View File

@@ -36,7 +36,7 @@ pub fn check_tokens(
// RUF001, RUF002, RUF003
if enforce_ambiguous_unicode_character {
if matches!(tok, Tok::String { .. } | Tok::Comment) {
for check in rules::checks::ambiguous_unicode_character(
checks.extend(rules::checks::ambiguous_unicode_character(
locator,
start,
end,
@@ -49,12 +49,9 @@ pub fn check_tokens(
} else {
Context::Comment
},
autofix.patch(),
) {
if settings.enabled.contains(check.kind.code()) {
checks.push(check);
}
}
settings,
autofix,
));
}
}

View File

@@ -1,19 +1,16 @@
use std::fmt;
use std::path::PathBuf;
use clap::{command, Parser};
use fnv::FnvHashMap;
use log::warn;
use regex::Regex;
use crate::checks_gen::CheckCodePrefix;
use crate::logging::LogLevel;
use crate::printer::SerializationFormat;
use crate::settings::configuration::Configuration;
use crate::settings::types::{PatternPrefixPair, PerFileIgnore, PythonVersion};
#[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")]
#[command(author, about = "Ruff: An extremely fast Python linter.")]
#[command(version)]
pub struct Cli {
#[arg(required = true)]
@@ -66,16 +63,27 @@ pub struct Cli {
/// excluded ones.
#[arg(long, value_delimiter = ',')]
pub extend_exclude: Vec<String>,
/// List of error codes to treat as eligible for autofix. Only applicable
/// when autofix itself is enabled (e.g., via `--fix`).
#[arg(long, value_delimiter = ',')]
pub fixable: Vec<CheckCodePrefix>,
/// List of error codes to treat as ineligible for autofix. Only applicable
/// when autofix itself is enabled (e.g., via `--fix`).
#[arg(long, value_delimiter = ',')]
pub unfixable: Vec<CheckCodePrefix>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')]
pub per_file_ignores: Vec<PatternPrefixPair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
pub format: SerializationFormat,
/// See the files ruff will be run against with the current settings.
/// Show violations with source code.
#[arg(long)]
pub show_source: bool,
/// See the files Ruff will be run against with the current settings.
#[arg(long)]
pub show_files: bool,
/// See ruff's settings.
/// See Ruff's settings.
#[arg(long)]
pub show_settings: bool,
/// Enable automatic additions of noqa directives to failing lines.
@@ -134,64 +142,6 @@ pub fn extract_log_level(cli: &Cli) -> LogLevel {
}
}
pub enum Warnable {
Select,
ExtendSelect,
}
impl fmt::Display for Warnable {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Warnable::Select => fmt.write_str("--select"),
Warnable::ExtendSelect => fmt.write_str("--extend-select"),
}
}
}
/// Warn the user if they attempt to enable a code that won't be respected.
pub fn warn_on(
flag: Warnable,
codes: &[CheckCodePrefix],
cli_ignore: &[CheckCodePrefix],
cli_extend_ignore: &[CheckCodePrefix],
pyproject_configuration: &Configuration,
pyproject_path: Option<&PathBuf>,
) {
for code in codes {
if !cli_ignore.is_empty() {
if cli_ignore.contains(code) {
warn!("{code:?} was passed to {flag}, but ignored via --ignore")
}
} else if pyproject_configuration.ignore.contains(code) {
if let Some(path) = pyproject_path {
warn!(
"{code:?} was passed to {flag}, but ignored by the `ignore` field in {}",
path.to_string_lossy()
)
} else {
warn!("{code:?} was passed to {flag}, but ignored by the default `ignore` field",)
}
}
if !cli_extend_ignore.is_empty() {
if cli_extend_ignore.contains(code) {
warn!("{code:?} was passed to {flag}, but ignored via --extend-ignore")
}
} else if pyproject_configuration.extend_ignore.contains(code) {
if let Some(path) = pyproject_path {
warn!(
"{code:?} was passed to {flag}, but ignored by the `extend_ignore` field in {}",
path.to_string_lossy()
)
} else {
warn!(
"{code:?} was passed to {flag}, but ignored by the default `extend_ignore` \
field"
)
}
}
}
}
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
pub fn collect_per_file_ignores(
pairs: Vec<PatternPrefixPair>,

View File

@@ -36,6 +36,7 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
)
}
/// B011
pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option<&Expr>) {
if let ExprKind::Constant {
value: Constant::Bool(false),
@@ -43,7 +44,7 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option
} = &test.node
{
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
if checker.patch() {
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {
if let Ok(content) = generator.generate() {

View File

@@ -54,7 +54,7 @@ fn duplicate_handler_exceptions<'a>(
),
Range::from_located(expr),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// TODO(charlie): If we have a single element, remove the tuple.
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&type_pattern(unique_elts), 0) {

View File

@@ -20,6 +20,7 @@ fn attribute(value: &Expr, attr: &str) -> Expr {
)
}
/// B009
pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let ExprKind::Name { id, .. } = &func.node {
if id == "getattr" {
@@ -32,7 +33,7 @@ pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) {
let mut check =
Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr));
if checker.patch() {
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&attribute(obj, value), 0) {
if let Ok(content) = generator.generate() {

View File

@@ -65,7 +65,7 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body:
CheckKind::UnusedLoopControlVariable(name.to_string()),
Range::from_located(expr),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Prefix the variable name with an underscore.
check.amend(Fix::replacement(
format!("_{name}"),

View File

@@ -16,7 +16,7 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
checker.settings.enabled.contains(&CheckCode::T203),
Range::from_located(expr),
) {
if checker.patch() {
if checker.patch(check.kind.code()) {
let context = checker.binding_context();
if matches!(
checker.parents[context.defined_by].node,

View File

@@ -91,7 +91,7 @@ pub fn check_imports(
if has_leading_content || has_trailing_content {
let mut check = Check::new(CheckKind::UnsortedImports, range);
if autofix.patch() {
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
let mut content = String::new();
if has_leading_content {
content.push('\n');
@@ -119,7 +119,7 @@ pub fn check_imports(
let actual = dedent(&locator.slice_source_code_range(&range));
if actual != expected {
let mut check = Check::new(CheckKind::UnsortedImports, range);
if autofix.patch() {
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::replacement(
indent(&expected, &indentation),
range.location,

View File

@@ -22,7 +22,7 @@ use crate::check_tokens::check_tokens;
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
use crate::code_gen::SourceGenerator;
use crate::directives::Directives;
use crate::message::Message;
use crate::message::{Message, Source};
use crate::noqa::add_noqa;
use crate::settings::Settings;
use crate::source_code_locator::SourceCodeLocator;
@@ -176,7 +176,15 @@ pub fn lint_stdin(
// Convert to messages.
Ok(checks
.into_iter()
.map(|check| Message::from_check(path.to_string_lossy().to_string(), check))
.map(|check| {
let filename = path.to_string_lossy().to_string();
let source = if settings.show_source {
Some(Source::from_check(&check, &locator))
} else {
None
};
Message::from_check(check, filename, source)
})
.collect())
}
@@ -233,7 +241,15 @@ pub fn lint_path(
// Convert to messages.
let messages: Vec<Message> = checks
.into_iter()
.map(|check| Message::from_check(path.to_string_lossy().to_string(), check))
.map(|check| {
let filename = path.to_string_lossy().to_string();
let source = if settings.show_source {
Some(Source::from_check(&check, &locator))
} else {
None
};
Message::from_check(check, filename, source)
})
.collect();
#[cfg(not(target_family = "wasm"))]
cache::set(path, &metadata, settings, autofix, &messages, mode);

View File

@@ -7,7 +7,7 @@ use std::time::Instant;
#[cfg(not(target_family = "wasm"))]
use ::ruff::cache;
use ::ruff::checks::{CheckCode, CheckKind};
use ::ruff::cli::{collect_per_file_ignores, extract_log_level, warn_on, Cli, Warnable};
use ::ruff::cli::{collect_per_file_ignores, extract_log_level, Cli};
use ::ruff::fs::iter_python_files;
use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin};
use ::ruff::logging::{set_up_logging, LogLevel};
@@ -117,6 +117,7 @@ fn run_once(
location: Default::default(),
end_location: Default::default(),
filename: path.to_string_lossy().to_string(),
source: None,
}]
} else {
error!("Failed to check {}: {message}", path.to_string_lossy());
@@ -239,25 +240,9 @@ fn inner_main() -> Result<ExitCode> {
collect_per_file_ignores(cli.per_file_ignores, project_root.as_ref());
}
if !cli.select.is_empty() {
warn_on(
Warnable::Select,
&cli.select,
&cli.ignore,
&cli.extend_ignore,
&configuration,
pyproject.as_ref(),
);
configuration.select = cli.select;
}
if !cli.extend_select.is_empty() {
warn_on(
Warnable::ExtendSelect,
&cli.extend_select,
&cli.ignore,
&cli.extend_ignore,
&configuration,
pyproject.as_ref(),
);
configuration.extend_select = cli.extend_select;
}
if !cli.ignore.is_empty() {
@@ -266,6 +251,12 @@ fn inner_main() -> Result<ExitCode> {
if !cli.extend_ignore.is_empty() {
configuration.extend_ignore = cli.extend_ignore;
}
if !cli.fixable.is_empty() {
configuration.fixable = cli.fixable;
}
if !cli.unfixable.is_empty() {
configuration.unfixable = cli.unfixable;
}
if let Some(line_length) = cli.line_length {
configuration.line_length = line_length;
}
@@ -281,6 +272,9 @@ fn inner_main() -> Result<ExitCode> {
if let Some(fix) = fix {
configuration.fix = fix;
}
if cli.show_source {
configuration.show_source = true;
}
if cli.show_settings && cli.show_files {
eprintln!("Error: specify --show-settings or show-files (not both).");

View File

@@ -2,12 +2,16 @@ use std::cmp::Ordering;
use std::fmt;
use std::path::Path;
use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
use colored::Colorize;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::fs::relativize_path;
use crate::source_code_locator::SourceCodeLocator;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Message {
@@ -16,16 +20,18 @@ pub struct Message {
pub location: Location,
pub end_location: Location,
pub filename: String,
pub source: Option<Source>,
}
impl Message {
pub fn from_check(filename: String, check: Check) -> Self {
pub fn from_check(check: Check, filename: String, source: Option<Source>) -> Self {
Self {
kind: check.kind,
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
location: Location::new(check.location.row(), check.location.column() + 1),
end_location: Location::new(check.end_location.row(), check.end_location.column() + 1),
filename,
source,
}
}
}
@@ -48,8 +54,7 @@ impl PartialOrd for Message {
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
let label = format!(
"{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&self.filename)).white().bold(),
":".cyan(),
@@ -58,7 +63,71 @@ impl fmt::Display for Message {
self.location.column(),
":".cyan(),
self.kind.code().as_ref().red().bold(),
self.kind.body()
)
self.kind.body(),
);
match &self.source {
None => write!(f, "{}", label),
Some(source) => {
let snippet = Snippet {
title: Some(Annotation {
label: Some(&label),
annotation_type: AnnotationType::Error,
// The ID (error number) is already encoded in the `label`.
id: None,
}),
footer: vec![],
slices: vec![Slice {
source: &source.contents,
line_start: self.location.row(),
annotations: vec![SourceAnnotation {
label: self.kind.code().as_ref(),
annotation_type: AnnotationType::Error,
range: source.range,
}],
// The origin (file name, line number, and column number) is already encoded
// in the `label`.
origin: None,
fold: false,
}],
opt: FormatOptions {
color: true,
..Default::default()
},
};
// `split_once(' ')` strips "error: " from `message`.
let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once(' ').unwrap();
write!(f, "{}", message)
}
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Source {
pub contents: String,
pub range: (usize, usize),
}
impl Source {
pub fn from_check(check: &Check, locator: &SourceCodeLocator) -> Self {
let source = locator.slice_source_code_range(&Range {
location: Location::new(check.location.row(), 0),
end_location: Location::new(check.end_location.row() + 1, 0),
});
let num_chars_in_range = locator
.slice_source_code_range(&Range {
location: check.location,
end_location: check.end_location,
})
.chars()
.count();
Source {
contents: source.to_string(),
range: (
check.location.column(),
check.location.column() + num_chars_in_range,
),
}
}
}

View File

@@ -62,7 +62,7 @@ pub fn literal_comparisons(
CheckKind::NoneComparison(RejectedCmpop::Eq),
Range::from_located(comparator),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Dummy replacement
check.amend(Fix::dummy(expr.location));
bad_ops.insert(0, Cmpop::Is);
@@ -74,7 +74,7 @@ pub fn literal_comparisons(
CheckKind::NoneComparison(RejectedCmpop::NotEq),
Range::from_located(comparator),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(0, Cmpop::IsNot);
}
@@ -93,7 +93,7 @@ pub fn literal_comparisons(
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
Range::from_located(comparator),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(0, Cmpop::Is);
}
@@ -104,7 +104,7 @@ pub fn literal_comparisons(
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
Range::from_located(comparator),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(0, Cmpop::IsNot);
}
@@ -129,7 +129,7 @@ pub fn literal_comparisons(
CheckKind::NoneComparison(RejectedCmpop::Eq),
Range::from_located(comparator),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(idx, Cmpop::Is);
}
@@ -140,7 +140,7 @@ pub fn literal_comparisons(
CheckKind::NoneComparison(RejectedCmpop::NotEq),
Range::from_located(comparator),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(idx, Cmpop::IsNot);
}
@@ -159,7 +159,7 @@ pub fn literal_comparisons(
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
Range::from_located(comparator),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(idx, Cmpop::Is);
}
@@ -170,7 +170,7 @@ pub fn literal_comparisons(
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
Range::from_located(comparator),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::dummy(expr.location));
bad_ops.insert(idx, Cmpop::IsNot);
}
@@ -226,7 +226,7 @@ pub fn not_tests(
if check_not_in {
let mut check =
Check::new(CheckKind::NotInTest, Range::from_located(operand));
if checker.patch() && should_fix {
if checker.patch(check.kind.code()) && should_fix {
if let Some(content) = compare(left, &[Cmpop::NotIn], comparators) {
check.amend(Fix::replacement(
content,
@@ -241,8 +241,8 @@ pub fn not_tests(
Cmpop::Is => {
if check_not_is {
let mut check =
Check::new(CheckKind::NotInTest, Range::from_located(operand));
if checker.patch() && should_fix {
Check::new(CheckKind::NotIsTest, Range::from_located(operand));
if checker.patch(check.kind.code()) && should_fix {
if let Some(content) = compare(left, &[Cmpop::IsNot], comparators) {
check.amend(Fix::replacement(
content,

View File

@@ -179,7 +179,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
CheckKind::NoBlankLineBeforeFunction(blank_lines_before),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Delete the blank line before the docstring.
check.amend(Fix::deletion(
Location::new(docstring.location.row() - blank_lines_before, 0),
@@ -220,7 +220,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
CheckKind::NoBlankLineAfterFunction(blank_lines_after),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Delete the blank line after the docstring.
check.amend(Fix::deletion(
Location::new(docstring.end_location.unwrap().row() + 1, 0),
@@ -269,7 +269,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
CheckKind::NoBlankLineBeforeClass(blank_lines_before),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Delete the blank line before the class.
check.amend(Fix::deletion(
Location::new(docstring.location.row() - blank_lines_before, 0),
@@ -285,7 +285,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
CheckKind::OneBlankLineBeforeClass(blank_lines_before),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Insert one blank line before the class.
check.amend(Fix::replacement(
"\n".to_string(),
@@ -322,7 +322,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
CheckKind::OneBlankLineAfterClass(blank_lines_after),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Insert a blank line before the class (replacing any existing lines).
check.amend(Fix::replacement(
"\n".to_string(),
@@ -364,7 +364,7 @@ pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) {
CheckKind::BlankLineAfterSummary,
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Insert one blank line after the summary (replacing any existing lines).
check.amend(Fix::replacement(
"\n".to_string(),
@@ -425,7 +425,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
end_location: Location::new(docstring.location.row() + i, 0),
},
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
helpers::clean(&docstring_indent),
Location::new(docstring.location.row() + i, 0),
@@ -475,7 +475,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
end_location: Location::new(docstring.location.row() + i, 0),
},
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
helpers::clean(&docstring_indent),
Location::new(docstring.location.row() + i, 0),
@@ -499,7 +499,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
end_location: Location::new(docstring.location.row() + i, 0),
},
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
helpers::clean(&docstring_indent),
Location::new(docstring.location.row() + i, 0),
@@ -537,7 +537,7 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
CheckKind::NewLineAfterLastParagraph,
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Insert a newline just before the end-quote(s).
let content = format!(
"\n{}",
@@ -580,7 +580,7 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
CheckKind::NoSurroundingWhitespace,
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
if let Some(first_line) = checker
.locator
.slice_source_code_range(&Range::from_located(docstring))
@@ -916,7 +916,7 @@ fn blanks_and_section_underline(
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Add a dashed line (of the appropriate length) under the section header.
let content = format!(
"{}{}\n",
@@ -950,7 +950,7 @@ fn blanks_and_section_underline(
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Add a dashed line (of the appropriate length) under the section header.
let content = format!(
"{}{}\n",
@@ -972,7 +972,7 @@ fn blanks_and_section_underline(
),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Delete any blank lines between the header and content.
check.amend(Fix::deletion(
Location::new(docstring.location.row() + context.original_index + 1, 0),
@@ -995,7 +995,7 @@ fn blanks_and_section_underline(
CheckKind::SectionUnderlineAfterName(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Delete any blank lines between the header and the underline.
check.amend(Fix::deletion(
Location::new(docstring.location.row() + context.original_index + 1, 0),
@@ -1026,7 +1026,7 @@ fn blanks_and_section_underline(
),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Replace the existing underline with a line of the appropriate length.
let content = format!(
"{}{}\n",
@@ -1064,7 +1064,7 @@ fn blanks_and_section_underline(
CheckKind::SectionUnderlineNotOverIndented(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Replace the existing indentation with whitespace of the appropriate length.
check.amend(Fix::replacement(
helpers::clean(&indentation),
@@ -1113,7 +1113,7 @@ fn blanks_and_section_underline(
),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Delete any blank lines between the header and content.
check.amend(Fix::deletion(
Location::new(
@@ -1172,7 +1172,7 @@ fn common_section(
CheckKind::CapitalizeSectionName(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Replace the section title with the capitalized variant. This requires
// locating the start and end of the section name.
if let Some(index) = context.line.find(&context.section_name) {
@@ -1205,7 +1205,7 @@ fn common_section(
CheckKind::SectionNotOverIndented(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Replace the existing indentation with whitespace of the appropriate length.
check.amend(Fix::replacement(
helpers::clean(&indentation),
@@ -1232,7 +1232,7 @@ fn common_section(
CheckKind::BlankLineAfterLastSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Add a newline after the section.
check.amend(Fix::insertion(
"\n".to_string(),
@@ -1253,7 +1253,7 @@ fn common_section(
CheckKind::BlankLineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Add a newline after the section.
check.amend(Fix::insertion(
"\n".to_string(),
@@ -1277,7 +1277,7 @@ fn common_section(
CheckKind::BlankLineBeforeSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Add a blank line before the section.
check.amend(Fix::insertion(
"\n".to_string(),
@@ -1444,7 +1444,7 @@ fn numpy_section(checker: &mut Checker, definition: &Definition, context: &Secti
CheckKind::NewLineAfterSectionName(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Delete the suffix. This requires locating the end of the section name.
if let Some(index) = context.line.find(&context.section_name) {
// Map from bytes to characters.
@@ -1493,7 +1493,7 @@ fn google_section(checker: &mut Checker, definition: &Definition, context: &Sect
CheckKind::SectionNameEndsInColon(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
// Replace the suffix. This requires locating the end of the section name.
if let Some(index) = context.line.find(&context.section_name) {
// Map from bytes to characters.

View File

@@ -43,7 +43,7 @@ pub fn invalid_literal_comparison(
&& (is_constant_non_singleton(left) || is_constant_non_singleton(right))
{
let mut check = Check::new(CheckKind::IsLiteral, location);
if checker.patch() {
if checker.patch(check.kind.code()) {
match fix_invalid_literal_comparison(
checker.locator,
Range {

View File

@@ -28,7 +28,7 @@ fn match_not_implemented(expr: &Expr) -> Option<&Expr> {
pub fn raise_not_implemented(checker: &mut Checker, expr: &Expr) {
if let Some(expr) = match_not_implemented(expr) {
let mut check = Check::new(CheckKind::RaiseNotImplemented, Range::from_located(expr));
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
"NotImplementedError".to_string(),
expr.location,

View File

@@ -220,7 +220,7 @@ pub fn convert_typed_dict_functional_to_class(
CheckKind::ConvertTypedDictFunctionalToClass,
Range::from_located(stmt),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
match convert_to_functional_class(stmt, class_name, body, total_keyword) {
Ok(fix) => check.amend(fix),
Err(err) => error!("Failed to convert TypedDict: {}", err),

View File

@@ -37,7 +37,7 @@ pub fn deprecated_unittest_alias(checker: &mut Checker, expr: &Expr) {
CheckKind::DeprecatedUnittestAlias(attr.to_string(), target.to_string()),
Range::from_located(expr),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
format!("self.{}", target),
expr.location,

View File

@@ -17,7 +17,7 @@ pub fn super_call_with_parameters(checker: &mut Checker, expr: &Expr, func: &Exp
.map(|index| checker.parents[*index])
.collect();
if let Some(mut check) = checks::super_args(scope, &parents, expr, func, args) {
if checker.patch() {
if checker.patch(check.kind.code()) {
if let Some(fix) = pyupgrade::fixes::remove_super_arguments(checker.locator, expr) {
check.amend(fix);
}

View File

@@ -9,7 +9,7 @@ use crate::pyupgrade::checks;
/// U003
pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let Some(mut check) = checks::type_of_primitive(func, args, Range::from_located(expr)) {
if checker.patch() {
if checker.patch(check.kind.code()) {
if let CheckKind::TypeOfPrimitive(primitive) = &check.kind {
check.amend(Fix::replacement(
primitive.builtin(),

View File

@@ -3,7 +3,7 @@ use rustpython_ast::{Constant, Expr, ExprKind, Keyword};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::checks::{Check, CheckCode, CheckKind};
use crate::source_code_locator::SourceCodeLocator;
const UTF8_LITERALS: &[&str] = &["utf-8", "utf8", "utf_8", "u8", "utf", "cp65001"];
@@ -124,13 +124,16 @@ pub fn unnecessary_encode_utf8(
expr,
variable,
checker.locator,
checker.patch(),
checker.patch(&CheckCode::U012),
));
} else {
// "unicode text©".encode("utf-8")
if let Some(check) =
delete_default_encode_arg_or_kwarg(expr, args, kwargs, checker.patch())
{
if let Some(check) = delete_default_encode_arg_or_kwarg(
expr,
args,
kwargs,
checker.patch(&CheckCode::U012),
) {
checker.add_check(check);
}
}
@@ -139,9 +142,12 @@ pub fn unnecessary_encode_utf8(
// f"foo{bar}".encode(*args, **kwargs)
ExprKind::JoinedStr { .. } => {
if is_default_encode(args, kwargs) {
if let Some(check) =
delete_default_encode_arg_or_kwarg(expr, args, kwargs, checker.patch())
{
if let Some(check) = delete_default_encode_arg_or_kwarg(
expr,
args,
kwargs,
checker.patch(&CheckCode::U012),
) {
checker.add_check(check);
}
}

View File

@@ -56,7 +56,7 @@ pub fn unnecessary_future_import(checker: &mut Checker, stmt: &Stmt, names: &[Lo
),
Range::from_located(stmt),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
let context = checker.binding_context();
let deleted: Vec<&Stmt> = checker
.deletions

View File

@@ -11,7 +11,7 @@ pub fn unnecessary_lru_cache_params(checker: &mut Checker, decorator_list: &[Exp
&checker.from_imports,
&checker.import_aliases,
) {
if checker.patch() {
if checker.patch(check.kind.code()) {
if let Some(fix) =
fixes::remove_unnecessary_lru_cache_params(checker.locator, &check.location)
{

View File

@@ -12,7 +12,7 @@ pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr, id: &str) {
CheckKind::UsePEP585Annotation(replacement.to_string()),
Range::from_located(expr),
);
if checker.patch() {
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
replacement.to_lowercase(),
expr.location,

View File

@@ -47,7 +47,7 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
let call_path = dealias_call_path(collect_call_paths(value), &checker.import_aliases);
if checker.match_typing_call_path(&call_path, "Optional") {
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
if checker.patch() {
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&optional(slice), 0) {
if let Ok(content) = generator.generate() {
@@ -62,7 +62,7 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
checker.add_check(check);
} else if checker.match_typing_call_path(&call_path, "Union") {
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
if checker.patch() {
if checker.patch(check.kind.code()) {
match &slice.node {
ExprKind::Slice { .. } => {
// Invalid type annotation.

View File

@@ -11,7 +11,7 @@ pub fn useless_metaclass_type(checker: &mut Checker, stmt: &Stmt, value: &Expr,
if let Some(mut check) =
checks::useless_metaclass_type(targets, value, Range::from_located(stmt))
{
if checker.patch() {
if checker.patch(check.kind.code()) {
let context = checker.binding_context();
let deleted: Vec<&Stmt> = checker
.deletions

View File

@@ -14,7 +14,7 @@ pub fn useless_object_inheritance(
) {
let scope = checker.current_scope();
if let Some(mut check) = checks::useless_object_inheritance(name, bases, scope) {
if checker.patch() {
if checker.patch(check.kind.code()) {
if let Some(fix) = pyupgrade::fixes::remove_class_def_base(
checker.locator,
&stmt.location,

View File

@@ -3,10 +3,10 @@ use once_cell::sync::Lazy;
use rustpython_ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::autofix::{fixer, Fix};
use crate::checks::CheckKind;
use crate::source_code_locator::SourceCodeLocator;
use crate::Check;
use crate::{Check, Settings};
/// See: https://github.com/microsoft/vscode/blob/095ddabc52b82498ee7f718a34f9dd11d59099a8/src/vs/base/common/strings.ts#L1094
static CONFUSABLES: Lazy<FnvHashMap<u32, u32>> = Lazy::new(|| {
@@ -1606,7 +1606,8 @@ pub fn ambiguous_unicode_character(
start: &Location,
end: &Location,
context: Context,
fix: bool,
settings: &Settings,
autofix: &fixer::Mode,
) -> Vec<Check> {
let mut checks = vec![];
@@ -1645,14 +1646,16 @@ pub fn ambiguous_unicode_character(
end_location,
},
);
if fix {
check.amend(Fix::replacement(
representant.to_string(),
location,
end_location,
));
if settings.enabled.contains(check.kind.code()) {
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::replacement(
representant.to_string(),
location,
end_location,
));
}
checks.push(check);
}
checks.push(check);
}
}

View File

@@ -25,12 +25,15 @@ pub struct Configuration {
pub extend_ignore: Vec<CheckCodePrefix>,
pub extend_select: Vec<CheckCodePrefix>,
pub fix: bool,
pub fixable: Vec<CheckCodePrefix>,
pub ignore: Vec<CheckCodePrefix>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub select: Vec<CheckCodePrefix>,
pub show_source: bool,
pub src: Vec<PathBuf>,
pub target_version: PythonVersion,
pub unfixable: Vec<CheckCodePrefix>,
// Plugins
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_bugbear: flake8_bugbear::settings::Settings,
@@ -121,6 +124,28 @@ impl Configuration {
.unwrap_or_else(|| vec![CheckCodePrefix::E, CheckCodePrefix::F]),
extend_select: options.extend_select.unwrap_or_default(),
fix: options.fix.unwrap_or_default(),
fixable: options.fixable.unwrap_or_else(|| {
// TODO(charlie): Autogenerate this list.
vec![
CheckCodePrefix::A,
CheckCodePrefix::B,
CheckCodePrefix::BLE,
CheckCodePrefix::C,
CheckCodePrefix::D,
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::I,
CheckCodePrefix::M,
CheckCodePrefix::N,
CheckCodePrefix::Q,
CheckCodePrefix::S,
CheckCodePrefix::T,
CheckCodePrefix::U,
CheckCodePrefix::W,
CheckCodePrefix::YTT,
]
}),
unfixable: options.unfixable.unwrap_or_default(),
ignore: options.ignore.unwrap_or_default(),
line_length: options.line_length.unwrap_or(88),
per_file_ignores: options
@@ -134,6 +159,7 @@ impl Configuration {
.collect()
})
.unwrap_or_default(),
show_source: options.show_source.unwrap_or_default(),
// Plugins
flake8_annotations: options
.flake8_annotations

View File

@@ -30,8 +30,10 @@ pub struct Settings {
pub enabled: FnvHashSet<CheckCode>,
pub exclude: Vec<FilePattern>,
pub extend_exclude: Vec<FilePattern>,
pub fixable: FnvHashSet<CheckCode>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub show_source: bool,
pub src: Vec<PathBuf>,
pub target_version: PythonVersion,
// Plugins
@@ -49,13 +51,20 @@ impl Settings {
Self {
dummy_variable_rgx: config.dummy_variable_rgx,
enabled: resolve_codes(
&config.select,
&config.extend_select,
&config.ignore,
&config.extend_ignore,
&config
.select
.into_iter()
.chain(config.extend_select.into_iter())
.collect::<Vec<_>>(),
&config
.ignore
.into_iter()
.chain(config.extend_ignore.into_iter())
.collect::<Vec<_>>(),
),
exclude: config.exclude,
extend_exclude: config.extend_exclude,
fixable: resolve_codes(&config.fixable, &config.unfixable),
flake8_annotations: config.flake8_annotations,
flake8_bugbear: config.flake8_bugbear,
flake8_quotes: config.flake8_quotes,
@@ -67,13 +76,15 @@ impl Settings {
per_file_ignores: config.per_file_ignores,
src: config.src,
target_version: config.target_version,
show_source: config.show_source,
}
}
pub fn for_rule(check_code: CheckCode) -> Self {
Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FnvHashSet::from_iter([check_code]),
enabled: FnvHashSet::from_iter([check_code.clone()]),
fixable: FnvHashSet::from_iter([check_code]),
exclude: Default::default(),
extend_exclude: Default::default(),
line_length: 88,
@@ -87,13 +98,15 @@ impl Settings {
isort: Default::default(),
mccabe: Default::default(),
pep8_naming: Default::default(),
show_source: Default::default(),
}
}
pub fn for_rules(check_codes: Vec<CheckCode>) -> Self {
Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FnvHashSet::from_iter(check_codes),
enabled: FnvHashSet::from_iter(check_codes.clone()),
fixable: FnvHashSet::from_iter(check_codes),
exclude: Default::default(),
extend_exclude: Default::default(),
line_length: 88,
@@ -107,6 +120,7 @@ impl Settings {
isort: Default::default(),
mccabe: Default::default(),
pep8_naming: Default::default(),
show_source: Default::default(),
}
}
}
@@ -118,10 +132,14 @@ impl Hash for Settings {
for value in self.enabled.iter() {
value.hash(state);
}
for value in self.fixable.iter() {
value.hash(state);
}
self.line_length.hash(state);
for value in self.per_file_ignores.iter() {
value.hash(state);
}
self.show_source.hash(state);
self.target_version.hash(state);
// Add plugin properties in alphabetical order.
self.flake8_annotations.hash(state);
@@ -136,12 +154,7 @@ impl Hash for Settings {
/// Given a set of selected and ignored prefixes, resolve the set of enabled
/// error codes.
fn resolve_codes(
select: &[CheckCodePrefix],
extend_select: &[CheckCodePrefix],
ignore: &[CheckCodePrefix],
extend_ignore: &[CheckCodePrefix],
) -> FnvHashSet<CheckCode> {
fn resolve_codes(select: &[CheckCodePrefix], ignore: &[CheckCodePrefix]) -> FnvHashSet<CheckCode> {
let mut codes: FnvHashSet<CheckCode> = FnvHashSet::default();
for specificity in [
PrefixSpecificity::Category,
@@ -154,11 +167,6 @@ fn resolve_codes(
codes.extend(prefix.codes());
}
}
for prefix in extend_select {
if prefix.specificity() == specificity {
codes.extend(prefix.codes());
}
}
for prefix in ignore {
if prefix.specificity() == specificity {
for code in prefix.codes() {
@@ -166,13 +174,6 @@ fn resolve_codes(
}
}
}
for prefix in extend_ignore {
if prefix.specificity() == specificity {
for code in prefix.codes() {
codes.remove(&code);
}
}
}
}
codes
}
@@ -187,19 +188,19 @@ mod tests {
#[test]
fn resolver() {
let actual = resolve_codes(&[CheckCodePrefix::W], &[], &[], &[]);
let actual = resolve_codes(&[CheckCodePrefix::W], &[]);
let expected = FnvHashSet::from_iter([CheckCode::W292, CheckCode::W605]);
assert_eq!(actual, expected);
let actual = resolve_codes(&[CheckCodePrefix::W6], &[], &[], &[]);
let actual = resolve_codes(&[CheckCodePrefix::W6], &[]);
let expected = FnvHashSet::from_iter([CheckCode::W605]);
assert_eq!(actual, expected);
let actual = resolve_codes(&[CheckCodePrefix::W], &[], &[CheckCodePrefix::W292], &[]);
let actual = resolve_codes(&[CheckCodePrefix::W], &[CheckCodePrefix::W292]);
let expected = FnvHashSet::from_iter([CheckCode::W605]);
assert_eq!(actual, expected);
let actual = resolve_codes(&[CheckCodePrefix::W605], &[], &[CheckCodePrefix::W605], &[]);
let actual = resolve_codes(&[CheckCodePrefix::W605], &[CheckCodePrefix::W605]);
let expected = FnvHashSet::from_iter([]);
assert_eq!(actual, expected);
}

View File

@@ -19,11 +19,14 @@ pub struct Options {
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
pub extend_select: Option<Vec<CheckCodePrefix>>,
pub fix: Option<bool>,
pub fixable: Option<Vec<CheckCodePrefix>>,
pub ignore: Option<Vec<CheckCodePrefix>>,
pub line_length: Option<usize>,
pub select: Option<Vec<CheckCodePrefix>>,
pub show_source: Option<bool>,
pub src: Option<Vec<String>>,
pub target_version: Option<PythonVersion>,
pub unfixable: Option<Vec<CheckCodePrefix>>,
// Plugins
pub flake8_annotations: Option<flake8_annotations::settings::Options>,
pub flake8_bugbear: Option<flake8_bugbear::settings::Options>,

View File

@@ -134,18 +134,21 @@ mod tests {
pyproject.tool,
Some(Tools {
ruff: Some(Options {
line_length: None,
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
select: None,
extend_select: None,
ignore: None,
extend_ignore: None,
extend_select: None,
fix: None,
fixable: None,
ignore: None,
line_length: None,
per_file_ignores: None,
dummy_variable_rgx: None,
select: None,
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
@@ -168,18 +171,21 @@ line-length = 79
pyproject.tool,
Some(Tools {
ruff: Some(Options {
line_length: Some(79),
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
select: None,
extend_select: None,
ignore: None,
extend_ignore: None,
extend_select: None,
fix: None,
fixable: None,
ignore: None,
line_length: Some(79),
per_file_ignores: None,
dummy_variable_rgx: None,
select: None,
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
@@ -210,10 +216,13 @@ exclude = ["foo.py"]
extend_select: None,
ignore: None,
extend_ignore: None,
fixable: None,
unfixable: None,
per_file_ignores: None,
dummy_variable_rgx: None,
src: None,
target_version: None,
show_source: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
@@ -236,18 +245,21 @@ select = ["E501"]
pyproject.tool,
Some(Tools {
ruff: Some(Options {
line_length: None,
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
select: Some(vec![CheckCodePrefix::E501]),
extend_select: None,
ignore: None,
extend_ignore: None,
extend_select: None,
fix: None,
fixable: None,
ignore: None,
line_length: None,
per_file_ignores: None,
dummy_variable_rgx: None,
select: Some(vec![CheckCodePrefix::E501]),
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
@@ -271,18 +283,21 @@ ignore = ["E501"]
pyproject.tool,
Some(Tools {
ruff: Some(Options {
line_length: None,
fix: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
select: None,
extend_select: Some(vec![CheckCodePrefix::M001]),
ignore: Some(vec![CheckCodePrefix::E501]),
extend_ignore: None,
extend_select: Some(vec![CheckCodePrefix::M001]),
fix: None,
fixable: None,
ignore: Some(vec![CheckCodePrefix::E501]),
line_length: None,
per_file_ignores: None,
dummy_variable_rgx: None,
select: None,
show_source: None,
src: None,
target_version: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_quotes: None,
@@ -357,6 +372,8 @@ other-attribute = 1
extend_select: None,
ignore: None,
extend_ignore: None,
fixable: None,
unfixable: None,
per_file_ignores: Some(FnvHashMap::from_iter([(
"__init__.py".to_string(),
vec![CheckCodePrefix::F401]
@@ -364,6 +381,7 @@ other-attribute = 1
dummy_variable_rgx: None,
src: None,
target_version: None,
show_source: None,
flake8_annotations: None,
flake8_bugbear: Some(flake8_bugbear::settings::Options {
extend_immutable_calls: Some(vec![

View File

@@ -43,12 +43,15 @@ pub struct UserConfiguration {
pub extend_ignore: Vec<CheckCodePrefix>,
pub extend_select: Vec<CheckCodePrefix>,
pub fix: bool,
pub fixable: Vec<CheckCodePrefix>,
pub ignore: Vec<CheckCodePrefix>,
pub line_length: usize,
pub per_file_ignores: Vec<(Exclusion, Vec<CheckCode>)>,
pub select: Vec<CheckCodePrefix>,
pub show_source: bool,
pub src: Vec<PathBuf>,
pub target_version: PythonVersion,
pub unfixable: Vec<CheckCodePrefix>,
// Plugins
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings,
@@ -81,6 +84,8 @@ impl UserConfiguration {
extend_ignore: configuration.extend_ignore,
extend_select: configuration.extend_select,
fix: configuration.fix,
fixable: configuration.fixable,
unfixable: configuration.unfixable,
ignore: configuration.ignore,
line_length: configuration.line_length,
per_file_ignores: configuration
@@ -96,6 +101,7 @@ impl UserConfiguration {
select: configuration.select,
src: configuration.src,
target_version: configuration.target_version,
show_source: configuration.show_source,
flake8_annotations: configuration.flake8_annotations,
flake8_quotes: configuration.flake8_quotes,
flake8_tidy_imports: configuration.flake8_tidy_imports,

View File

@@ -2,7 +2,7 @@
source: src/linter.rs
expression: checks
---
- kind: NotInTest
- kind: NotIsTest
location:
row: 2
column: 7
@@ -19,7 +19,7 @@ expression: checks
row: 2
column: 13
applied: false
- kind: NotInTest
- kind: NotIsTest
location:
row: 5
column: 7
@@ -36,7 +36,7 @@ expression: checks
row: 5
column: 15
applied: false
- kind: NotInTest
- kind: NotIsTest
location:
row: 8
column: 7

View File

@@ -78,3 +78,15 @@ fn test_stdin_autofix_when_no_issues_should_still_print_contents() -> Result<()>
);
Ok(())
}
#[test]
fn test_show_source() -> Result<()> {
let mut cmd = Command::cargo_bin(crate_name!())?;
let output = cmd
.args(["-", "--show-source"])
.write_stdin("l = 1")
.assert()
.failure();
assert!(str::from_utf8(&output.get_output().stdout)?.contains("l = 1"));
Ok(())
}