Compare commits

...

34 Commits

Author SHA1 Message Date
Charlie Marsh
66975876b2 Bump version to 0.0.135 2022-11-22 19:21:53 -05:00
Charlie Marsh
7316b120ba Log errors in add_noqa and autoformat calls (#881) 2022-11-22 19:21:36 -05:00
Charlie Marsh
fec887e481 Apply a limit to the number of fix iterations (#882) 2022-11-22 19:21:31 -05:00
Charlie Marsh
bdd32c0850 Enforce most pedantic lints on CI (#878) 2022-11-22 18:55:57 -05:00
Charlie Marsh
10dcd5fd0a Remove unused imports 2022-11-22 18:45:24 -05:00
Charlie Marsh
b922e6ecc8 Fix clippy::unnecessary_wraps (pedantic) (#880)
https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps
2022-11-22 18:25:30 -05:00
Charlie Marsh
f59799e0c4 Remove lingering empty lines 2022-11-22 17:54:24 -05:00
Charlie Marsh
814ddeb7ea Remove always-inline (#879) 2022-11-22 17:13:25 -05:00
Charlie Marsh
9315b9f459 Remove Mode from various internal checkers (#877) 2022-11-22 16:57:47 -05:00
Charlie Marsh
113b5a10bf Return Vec<Check> from check_tokens (#876) 2022-11-22 16:43:32 -05:00
Charlie Marsh
fe77fb70a1 Apply autofixes iteratively until code is stabilized (#875) 2022-11-22 16:37:52 -05:00
Charlie Marsh
c3f6170503 Update README with list of projects (#874) 2022-11-22 14:28:02 -05:00
Anders Kaseorg
a46160f0e2 Fix clippy::unreadable_literal (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
9a66cf2ffb Fix clippy::uninlined_format_args (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
3205473612 Ignore clippy::struct_excessive_bools (pedantic)
“consider using a state machine or refactoring bools into two-variant
enums”

https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
0bb8b14ae1 Fix clippy::single_match_else (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
9cf4621071 Fix clippy::redundant_else (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
bbc9ed1b21 Fix clippy::redundant_closure_for_method_calls (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
517ca2604a Fix clippy::needless_pass_by_value (pedantic)
“this argument is passed by value, but not consumed in the function
body”

https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
348ff509c0 Fix clippy::mut_mut (pedantic)
“this expression mutably borrows a mutable reference. Consider
reborrowing”

https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
fb545551f8 Fix clippy::match_bool (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#match_bool

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
3b33a431d6 Fix clippy::map_unwrap_or (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
bc95690725 Fix clippy::manual_string_new (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
9dc788d089 Fix clippy::let_underscore_drop (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_drop

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
15f63494a7 Fix clippy::items_after_statements (pedantic)
“adding items after statements is confusing, since items exist from
the start of the scope”

https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
68668c20e8 Fix clippy::if_not_else (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
9462335371 Fix clippy::from_iter_instead_of_collect (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
3dd6522b4f Fix clippy::explicit_iter_loop (pedantic)
“it is more concise to loop over references to containers instead of
using explicit iteration methods”

https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
6b935121a7 Fix clippy::explicit_deref_methods (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
8d9d9b3204 Fix clippy::documentation_markdown (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
0a506eff34 Fix clippy::cloned_instead_of_copied (pedantic)
“used `cloned` where `copied` could be used instead”

https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
7c7489c1dd Ignore clippy::cast_possible_truncation (pedantic)
“casting `usize` to `u8` may truncate the value”

https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
ae90eccb7f Fix clippy::cast_lossless (pedantic)
“casting `bool` to `u8` is more cleanly stated with `u8::from(_)`”

https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
7a61edbe46 Fix clippy::default-trait-access (pedantic) (#867) 2022-11-21 21:00:38 -05:00
194 changed files with 1457 additions and 1701 deletions

View File

@@ -82,7 +82,7 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic
cargo_test:
name: "cargo test"

View File

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

6
Cargo.lock generated
View File

@@ -670,7 +670,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.134-dev.0"
version = "0.0.135-dev.0"
dependencies = [
"anyhow",
"clap 4.0.22",
@@ -1768,7 +1768,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.134"
version = "0.0.135"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1818,7 +1818,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.134"
version = "0.0.135"
dependencies = [
"anyhow",
"clap 4.0.22",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.134"
version = "0.0.135"
edition = "2021"
[lib]

View File

@@ -32,12 +32,14 @@ of plugins), [`isort`](https://pypi.org/project/isort/), [`pydocstyle`](https://
and [`autoflake`](https://pypi.org/project/autoflake/) all while executing tens or hundreds of times
faster than any individual tool.
(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to
automatically convert your existing configuration.)
Ruff is actively developed and used in major open-source projects like:
Ruff is actively developed and used in major open-source projects
like [FastAPI](https://github.com/tiangolo/fastapi), [Zulip](https://github.com/zulip/zulip),
[pydantic](https://github.com/pydantic/pydantic), and [Saleor](https://github.com/saleor/saleor).
- [FastAPI](https://github.com/tiangolo/fastapi)
- [Bokeh](https://github.com/bokeh/bokeh)
- [Zulip](https://github.com/zulip/zulip)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Saleor](https://github.com/saleor/saleor)
- [Hatch](https://github.com/pypa/hatch)
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
@@ -105,7 +107,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.116
rev: v0.0.135
hooks:
- id: ruff
```

View File

@@ -10,7 +10,7 @@ fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| {
let rope = Rope::from_str(black_box(&contents));
rope.line_to_char(black_box(4));
})
});
});
}

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ pub fn convert(
plugins: Option<Vec<Plugin>>,
) -> Result<Pyproject> {
// Extract all referenced check code prefixes, to power plugin inference.
let mut referenced_codes: BTreeSet<CheckCodePrefix> = Default::default();
let mut referenced_codes: BTreeSet<CheckCodePrefix> = BTreeSet::default();
for (key, value) in flake8 {
if let Some(value) = value {
match key.as_str() {
@@ -70,13 +70,13 @@ pub fn convert(
.unwrap_or_default();
// Parse each supported option.
let mut options: Options = Default::default();
let mut flake8_annotations: flake8_annotations::settings::Options = Default::default();
let mut flake8_bugbear: flake8_bugbear::settings::Options = Default::default();
let mut flake8_quotes: flake8_quotes::settings::Options = Default::default();
let mut flake8_tidy_imports: flake8_tidy_imports::settings::Options = Default::default();
let mut mccabe: mccabe::settings::Options = Default::default();
let mut pep8_naming: pep8_naming::settings::Options = Default::default();
let mut options = Options::default();
let mut flake8_annotations = flake8_annotations::settings::Options::default();
let mut flake8_bugbear = flake8_bugbear::settings::Options::default();
let mut flake8_quotes = flake8_quotes::settings::Options::default();
let mut flake8_tidy_imports = flake8_tidy_imports::settings::Options::default();
let mut mccabe = mccabe::settings::Options::default();
let mut pep8_naming = pep8_naming::settings::Options::default();
for (key, value) in flake8 {
if let Some(value) = value {
match key.as_str() {
@@ -110,7 +110,7 @@ pub fn convert(
match parser::parse_files_to_codes_mapping(value.as_ref()) {
Ok(per_file_ignores) => {
options.per_file_ignores =
Some(parser::collect_per_file_ignores(per_file_ignores))
Some(parser::collect_per_file_ignores(per_file_ignores));
}
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
@@ -181,7 +181,7 @@ pub fn convert(
"ban-relative-imports" | "ban_relative_imports" => match value.trim() {
"true" => flake8_tidy_imports.ban_relative_imports = Some(Strictness::All),
"parents" => {
flake8_tidy_imports.ban_relative_imports = Some(Strictness::Parents)
flake8_tidy_imports.ban_relative_imports = Some(Strictness::Parents);
}
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
@@ -203,22 +203,22 @@ pub fn convert(
// Deduplicate and sort.
options.select = Some(Vec::from_iter(select));
options.ignore = Some(Vec::from_iter(ignore));
if flake8_annotations != Default::default() {
if flake8_annotations != flake8_annotations::settings::Options::default() {
options.flake8_annotations = Some(flake8_annotations);
}
if flake8_bugbear != Default::default() {
if flake8_bugbear != flake8_bugbear::settings::Options::default() {
options.flake8_bugbear = Some(flake8_bugbear);
}
if flake8_quotes != Default::default() {
if flake8_quotes != flake8_quotes::settings::Options::default() {
options.flake8_quotes = Some(flake8_quotes);
}
if flake8_tidy_imports != Default::default() {
if flake8_tidy_imports != flake8_tidy_imports::settings::Options::default() {
options.flake8_tidy_imports = Some(flake8_tidy_imports);
}
if mccabe != Default::default() {
if mccabe != mccabe::settings::Options::default() {
options.mccabe = Some(mccabe);
}
if pep8_naming != Default::default() {
if pep8_naming != pep8_naming::settings::Options::default() {
options.pep8_naming = Some(pep8_naming);
}

View File

@@ -1,4 +1,15 @@
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
pub mod converter;
mod parser;

View File

@@ -1,4 +1,16 @@
//! Utility to generate Ruff's pyproject.toml section from a Flake8 INI file.
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use std::path::PathBuf;

View File

@@ -31,7 +31,7 @@ pub fn parse_prefix_codes(value: &str) -> Vec<CheckCodePrefix> {
pub fn parse_strings(value: &str) -> Vec<String> {
COMMA_SEPARATED_LIST_RE
.split(value)
.map(|part| part.trim())
.map(str::trim)
.filter(|part| !part.is_empty())
.map(String::from)
.collect()
@@ -92,7 +92,7 @@ impl State {
});
}
}
Err(_) => eprintln!("Skipping unrecognized prefix: {}", code),
Err(_) => eprintln!("Skipping unrecognized prefix: {code}"),
}
}
codes
@@ -129,14 +129,14 @@ fn tokenize_files_to_codes_mapping(value: &str) -> Vec<Token> {
}
tokens.push(Token {
token_name: TokenType::Eof,
src: "".to_string(),
src: String::new(),
});
tokens
}
/// Parse a 'files-to-codes' mapping, mimicking Flake8's internal logic.
///
/// See: https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45
/// See: <https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45>
pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair>> {
if value.trim().is_empty() {
return Ok(vec![]);

View File

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

View File

@@ -24,7 +24,7 @@ pub struct Cli {
pub fn main(cli: &Cli) -> Result<()> {
// Build up a map from prefix to matching CheckCodes.
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = Default::default();
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = BTreeMap::default();
for check_code in CheckCode::iter() {
let as_ref: String = check_code.as_ref().to_string();
let prefix_len = as_ref
@@ -106,7 +106,7 @@ pub fn main(cli: &Cli) -> Result<()> {
2 => "Tens",
1 => "Hundreds",
0 => "Category",
_ => panic!("Invalid prefix: {}", prefix),
_ => panic!("Invalid prefix: {prefix}"),
};
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => PrefixSpecificity::{},",
@@ -132,10 +132,10 @@ pub fn main(cli: &Cli) -> Result<()> {
// Write the output to `src/checks_gen.rs` (or stdout).
if cli.dry_run {
println!("{}", output);
println!("{output}");
} else {
let mut f = OpenOptions::new().write(true).truncate(true).open(FILE)?;
write!(f, "{}", output)?;
write!(f, "{output}")?;
}
Ok(())

View File

@@ -61,7 +61,7 @@ pub fn main(cli: &Cli) -> Result<()> {
}
if cli.dry_run {
print!("{}", output);
print!("{output}");
} else {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
@@ -84,9 +84,9 @@ pub fn main(cli: &Cli) -> Result<()> {
// Write the prefix, new contents, and suffix.
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
write!(f, "{}\n\n", prefix)?;
write!(f, "{}", output)?;
write!(f, "{}", suffix)?;
write!(f, "{prefix}\n\n")?;
write!(f, "{output}")?;
write!(f, "{suffix}")?;
}
Ok(())

View File

@@ -19,7 +19,7 @@ pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
let mut generator = SourceGenerator::new();
generator.unparse_suite(&python_ast)?;
generator.unparse_suite(&python_ast);
println!("{}", generator.generate()?);
Ok(())
}

View File

@@ -1,3 +1,16 @@
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
pub mod generate_check_code_prefix;
pub mod generate_rules_table;
pub mod generate_source_code;

View File

@@ -1,3 +1,16 @@
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use anyhow::Result;
use clap::{Parser, Subcommand};
use ruff_dev::{

View File

@@ -17,6 +17,6 @@ pub struct Cli {
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
println!("{:#?}", python_ast);
println!("{python_ast:#?}");
Ok(())
}

View File

@@ -1,4 +1,4 @@
//! Print the LibCST CST for a given Python file.
//! Print the `LibCST` CST for a given Python file.
use std::fs;
use std::path::PathBuf;
@@ -17,7 +17,7 @@ pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
match libcst_native::parse_module(&contents, None) {
Ok(python_cst) => {
println!("{:#?}", python_cst);
println!("{python_cst:#?}");
Ok(())
}
Err(_) => Err(anyhow::anyhow!("Failed to parse CST")),

View File

@@ -17,7 +17,7 @@ pub struct Cli {
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
for (_, tok, _) in lexer::make_tokenizer(&contents).flatten() {
println!("{:#?}", tok);
println!("{tok:#?}");
}
Ok(())
}

View File

@@ -6,7 +6,6 @@ use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location,
use crate::ast::types::Range;
use crate::SourceCodeLocator;
#[inline(always)]
fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
match &expr.node {
ExprKind::Call { func, .. } => {
@@ -24,7 +23,6 @@ fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
}
/// Convert an `Expr` to its call path (like `List`, or `typing.List`).
#[inline(always)]
pub fn compose_call_path(expr: &Expr) -> Option<String> {
let segments = collect_call_paths(expr);
if segments.is_empty() {
@@ -35,7 +33,6 @@ pub fn compose_call_path(expr: &Expr) -> Option<String> {
}
/// Convert an `Expr` to its call path segments (like ["typing", "List"]).
#[inline(always)]
pub fn collect_call_paths(expr: &Expr) -> Vec<&str> {
let mut segments = vec![];
collect_call_path_inner(expr, &mut segments);
@@ -121,10 +118,9 @@ pub fn match_call_path(
// `Match`).
if num_segments == 0 {
module.is_empty()
|| from_imports
.get(module)
.map(|imports| imports.contains(member) || imports.contains("*"))
.unwrap_or(false)
|| from_imports.get(module).map_or(false, |imports| {
imports.contains(member) || imports.contains("*")
})
} else {
let components: Vec<&str> = module.split('.').collect();
@@ -148,8 +144,7 @@ pub fn match_call_path(
let member = components[cut];
if from_imports
.get(&module.as_str())
.map(|imports| imports.contains(member))
.unwrap_or(false)
.map_or(false, |imports| imports.contains(member))
{
return true;
}

View File

@@ -4,8 +4,6 @@ use crate::ast::types::{BindingKind, Scope};
/// Extract the names bound to a given __all__ assignment.
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
let mut names: Vec<String> = vec![];
fn add_to_names(names: &mut Vec<String>, elts: &[Expr]) {
for elt in elts {
if let ExprKind::Constant {
@@ -13,11 +11,13 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
..
} = &elt.node
{
names.push(value.to_string())
names.push(value.to_string());
}
}
}
let mut names: Vec<String> = vec![];
// Grab the existing bound __all__ values.
if let StmtKind::AugAssign { .. } = &stmt.node {
if let Some(binding) = scope.values.get("__all__") {
@@ -35,7 +35,7 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
} {
match &value.node {
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
add_to_names(&mut names, elts)
add_to_names(&mut names, elts);
}
ExprKind::BinOp { left, right, .. } => {
let mut current_left = left;

View File

@@ -249,7 +249,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
visitor.visit_stmt(stmt);
}
for excepthandler in handlers {
visitor.visit_excepthandler(excepthandler)
visitor.visit_excepthandler(excepthandler);
}
for stmt in orelse {
visitor.visit_stmt(stmt);
@@ -447,7 +447,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
pub fn walk_constant<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, constant: &'a Constant) {
if let Constant::Tuple(constants) = constant {
for constant in constants {
visitor.visit_constant(constant)
visitor.visit_constant(constant);
}
}
}
@@ -577,7 +577,6 @@ pub fn walk_pattern<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, pattern: &'a P
}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_expr_context<'a, V: Visitor<'a> + ?Sized>(
visitor: &mut V,
expr_context: &'a ExprContext,
@@ -585,21 +584,16 @@ pub fn walk_expr_context<'a, V: Visitor<'a> + ?Sized>(
}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_boolop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, boolop: &'a Boolop) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_operator<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, operator: &'a Operator) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_unaryop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, unaryop: &'a Unaryop) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_cmpop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, cmpop: &'a Cmpop) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_alias<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, alias: &'a Alias) {}

View File

@@ -10,8 +10,6 @@ use crate::autofix::{Fix, Patch};
use crate::checks::Check;
use crate::source_code_locator::SourceCodeLocator;
// TODO(charlie): The model here is awkward because `Apply` is only relevant at
// higher levels in the execution flow.
#[derive(Hash)]
pub enum Mode {
Generate,
@@ -19,55 +17,55 @@ pub enum Mode {
None,
}
impl Mode {
/// Return `true` if a patch should be generated under the given `Mode`.
pub fn patch(&self) -> bool {
match &self {
Mode::Generate => true,
Mode::Apply => true,
Mode::None => false,
impl From<bool> for Mode {
fn from(value: bool) -> Self {
if value {
Mode::Apply
} else {
Mode::None
}
}
}
impl From<bool> for Mode {
fn from(value: bool) -> Self {
impl From<&Mode> for bool {
fn from(value: &Mode) -> Self {
match value {
true => Mode::Apply,
false => Mode::None,
Mode::Generate | Mode::Apply => true,
Mode::None => false,
}
}
}
/// Auto-fix errors in a file, and write the fixed source code to disk.
pub fn fix_file<'a>(
checks: &'a mut [Check],
checks: &'a [Check],
locator: &'a SourceCodeLocator<'a>,
) -> Option<Cow<'a, str>> {
) -> Option<(Cow<'a, str>, usize)> {
if checks.iter().all(|check| check.fix.is_none()) {
return None;
}
Some(apply_fixes(
checks.iter_mut().filter_map(|check| check.fix.as_mut()),
checks.iter().filter_map(|check| check.fix.as_ref()),
locator,
))
}
/// Apply a series of fixes.
fn apply_fixes<'a>(
fixes: impl Iterator<Item = &'a mut Fix>,
fixes: impl Iterator<Item = &'a Fix>,
locator: &'a SourceCodeLocator<'a>,
) -> Cow<'a, str> {
) -> (Cow<'a, str>, usize) {
let mut output = RopeBuilder::new();
let mut last_pos: Location = Location::new(1, 0);
let mut applied: BTreeSet<&Patch> = BTreeSet::default();
let mut num_fixed: usize = 0;
for fix in fixes.sorted_by_key(|fix| fix.patch.location) {
// If we already applied an identical fix as part of another correction, skip
// any re-application.
if applied.contains(&fix.patch) {
fix.applied = true;
num_fixed += 1;
continue;
}
@@ -90,19 +88,18 @@ fn apply_fixes<'a>(
// Track that the fix was applied.
last_pos = fix.patch.end_location;
applied.insert(&fix.patch);
fix.applied = true;
num_fixed += 1;
}
// Add the remaining content.
let slice = locator.slice_source_code_at(last_pos);
output.append(&slice);
Cow::from(output.finish())
(Cow::from(output.finish()), num_fixed)
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use rustpython_parser::ast::Location;
use crate::autofix::fixer::apply_fixes;
@@ -110,115 +107,117 @@ mod tests {
use crate::SourceCodeLocator;
#[test]
fn empty_file() -> Result<()> {
let mut fixes = vec![];
let locator = SourceCodeLocator::new("");
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "";
assert_eq!(actual, expected);
Ok(())
fn empty_file() {
let fixes = vec![];
let locator = SourceCodeLocator::new(r#""#);
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
assert_eq!(contents, "");
assert_eq!(fixed, 0);
}
#[test]
fn apply_single_replacement() -> Result<()> {
let mut fixes = vec![Fix {
fn apply_single_replacement() {
let fixes = vec![Fix {
patch: Patch {
content: "Bar".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 14),
},
applied: false,
}];
let locator = SourceCodeLocator::new(
"class A(object):
...
",
r#"
class A(object):
...
"#
.trim(),
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "class A(Bar):
...
";
assert_eq!(actual, expected);
Ok(())
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
assert_eq!(
contents,
r#"
class A(Bar):
...
"#
.trim(),
);
assert_eq!(fixed, 1);
}
#[test]
fn apply_single_removal() -> Result<()> {
let mut fixes = vec![Fix {
fn apply_single_removal() {
let fixes = vec![Fix {
patch: Patch {
content: "".to_string(),
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),
},
applied: false,
}];
let locator = SourceCodeLocator::new(
"class A(object):
...
",
r#"
class A(object):
...
"#
.trim(),
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "class A:
...
";
assert_eq!(actual, expected);
Ok(())
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
assert_eq!(
contents,
r#"
class A:
...
"#
.trim()
);
assert_eq!(fixed, 1);
}
#[test]
fn apply_double_removal() -> Result<()> {
let mut fixes = vec![
fn apply_double_removal() {
let fixes = vec![
Fix {
patch: Patch {
content: "".to_string(),
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 16),
},
applied: false,
},
Fix {
patch: Patch {
content: "".to_string(),
content: String::new(),
location: Location::new(1, 16),
end_location: Location::new(1, 23),
},
applied: false,
},
];
let locator = SourceCodeLocator::new(
"class A(object, object):
...
",
r#"
class A(object, object):
...
"#
.trim(),
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
let expected = "class A:
...
";
assert_eq!(actual, expected);
Ok(())
assert_eq!(
contents,
r#"
class A:
...
"#
.trim()
);
assert_eq!(fixed, 2);
}
#[test]
fn ignore_overlapping_fixes() -> Result<()> {
let mut fixes = vec![
fn ignore_overlapping_fixes() {
let fixes = vec![
Fix {
patch: Patch {
content: "".to_string(),
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),
},
applied: false,
},
Fix {
patch: Patch {
@@ -226,22 +225,24 @@ mod tests {
location: Location::new(1, 9),
end_location: Location::new(1, 11),
},
applied: false,
},
];
let locator = SourceCodeLocator::new(
"class A(object):
r#"
class A(object):
...
",
"#
.trim(),
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "class A:
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
assert_eq!(
contents,
r#"
class A:
...
";
assert_eq!(actual, expected);
Ok(())
"#
.trim(),
);
assert_eq!(fixed, 1);
}
}

View File

@@ -14,18 +14,16 @@ pub struct Patch {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fix {
pub patch: Patch,
pub applied: bool,
}
impl Fix {
pub fn deletion(start: Location, end: Location) -> Self {
Self {
patch: Patch {
content: "".to_string(),
content: String::new(),
location: start,
end_location: end,
},
applied: false,
}
}
@@ -36,7 +34,6 @@ impl Fix {
location: start,
end_location: end,
},
applied: false,
}
}
@@ -47,18 +44,16 @@ impl Fix {
location: at,
end_location: at,
},
applied: false,
}
}
pub fn dummy(location: Location) -> Self {
Self {
patch: Patch {
content: "".to_string(),
content: String::new(),
location,
end_location: location,
},
applied: false,
}
}
}

View File

@@ -63,9 +63,10 @@ impl Mode {
impl From<bool> for Mode {
fn from(value: bool) -> Self {
match value {
true => Mode::ReadWrite,
false => Mode::None,
if value {
Mode::ReadWrite
} else {
Mode::None
}
}
}
@@ -177,6 +178,6 @@ pub fn set(
cache_key(path, settings, autofix),
&bincode::serialize(&check_result).unwrap(),
) {
error!("Failed to write to cache: {e:?}")
error!("Failed to write to cache: {e:?}");
}
}

View File

@@ -1,7 +1,6 @@
//! Lint rules based on AST traversal.
use std::collections::BTreeMap;
use std::ops::Deref;
use std::path::Path;
use itertools::Itertools;
@@ -19,11 +18,11 @@ use crate::ast::helpers::{
use crate::ast::operations::extract_all_names;
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
Binding, BindingContext, BindingKind, ClassScope, ImportKind, Range, Scope, ScopeKind,
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, ImportKind, Range, Scope,
ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{helpers, operations, visitor};
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
@@ -42,10 +41,11 @@ use crate::{
const GLOBAL_SCOPE_INDEX: usize = 0;
#[allow(clippy::struct_excessive_bools)]
pub struct Checker<'a> {
// Input data.
path: &'a Path,
autofix: &'a fixer::Mode,
autofix: bool,
pub(crate) settings: &'a Settings,
pub(crate) locator: &'a SourceCodeLocator<'a>,
// Computed checks.
@@ -86,7 +86,7 @@ pub struct Checker<'a> {
impl<'a> Checker<'a> {
pub fn new(
settings: &'a Settings,
autofix: &'a fixer::Mode,
autofix: bool,
path: &'a Path,
locator: &'a SourceCodeLocator,
) -> Checker<'a> {
@@ -95,40 +95,39 @@ impl<'a> Checker<'a> {
autofix,
path,
locator,
checks: Default::default(),
definitions: Default::default(),
deletions: Default::default(),
from_imports: Default::default(),
import_aliases: Default::default(),
parents: Default::default(),
parent_stack: Default::default(),
scopes: Default::default(),
scope_stack: Default::default(),
dead_scopes: Default::default(),
deferred_string_annotations: Default::default(),
deferred_annotations: Default::default(),
deferred_functions: Default::default(),
deferred_lambdas: Default::default(),
deferred_assignments: Default::default(),
checks: vec![],
definitions: vec![],
deletions: FxHashSet::default(),
from_imports: FxHashMap::default(),
import_aliases: FxHashMap::default(),
parents: vec![],
parent_stack: vec![],
scopes: vec![],
scope_stack: vec![],
dead_scopes: vec![],
deferred_string_annotations: vec![],
deferred_annotations: vec![],
deferred_functions: vec![],
deferred_lambdas: vec![],
deferred_assignments: vec![],
// Internal, derivative state.
visible_scope: VisibleScope {
modifier: Modifier::Module,
visibility: module_visibility(path),
},
in_f_string: Default::default(),
in_annotation: Default::default(),
in_deferred_string_annotation: Default::default(),
in_literal: Default::default(),
in_subscript: Default::default(),
seen_import_boundary: Default::default(),
in_f_string: None,
in_annotation: false,
in_deferred_string_annotation: false,
in_literal: false,
in_subscript: false,
seen_import_boundary: false,
futures_allowed: true,
annotations_future_enabled: Default::default(),
except_handlers: Default::default(),
annotations_future_enabled: false,
except_handlers: vec![],
}
}
/// Add a `Check` to the `Checker`.
#[inline(always)]
pub(crate) fn add_check(&mut self, check: Check) {
// If we're in an f-string, override the location. RustPython doesn't produce
// reliable locations for expressions within f-strings, so we use the
@@ -145,7 +144,6 @@ impl<'a> Checker<'a> {
}
/// Add multiple `Check` items to the `Checker`.
#[inline(always)]
pub(crate) fn add_checks(&mut self, checks: impl Iterator<Item = Check>) {
for check in checks {
self.add_check(check);
@@ -157,7 +155,7 @@ impl<'a> Checker<'a> {
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.settings.fixable.contains(code)
self.autofix && self.in_f_string.is_none() && self.settings.fixable.contains(code)
}
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
@@ -536,20 +534,20 @@ where
self.check_builtin_shadowing(name, Range::from_located(stmt), false);
for expr in bases {
self.visit_expr(expr)
self.visit_expr(expr);
}
for keyword in keywords {
self.visit_keyword(keyword)
self.visit_keyword(keyword);
}
for expr in decorator_list {
self.visit_expr(expr)
self.visit_expr(expr);
}
self.push_scope(Scope::new(ScopeKind::Class(ClassScope {
name,
bases,
keywords,
decorator_list,
})))
})));
}
StmtKind::Import { names } => {
if self.settings.enabled.contains(&CheckCode::E402) {
@@ -578,7 +576,7 @@ where
used: None,
range: Range::from_located(stmt),
},
)
);
} else {
if let Some(asname) = &alias.node.asname {
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
@@ -603,8 +601,7 @@ where
.node
.asname
.as_ref()
.map(|asname| asname == &alias.node.name)
.unwrap_or(false)
.map_or(false, |asname| asname == &alias.node.name)
{
Some((
self.scopes[*(self
@@ -619,7 +616,7 @@ where
},
range: Range::from_located(stmt),
},
)
);
}
if let Some(asname) = &alias.node.asname {
@@ -695,7 +692,7 @@ where
.iter()
.filter(|alias| alias.node.asname.is_none())
.map(|alias| alias.node.name.as_str()),
)
);
}
for alias in names {
if let Some(asname) = &alias.node.asname {
@@ -744,7 +741,7 @@ where
}
if self.settings.enabled.contains(&CheckCode::F407) {
if !ALL_FEATURE_NAMES.contains(&alias.node.name.deref()) {
if !ALL_FEATURE_NAMES.contains(&&*alias.node.name) {
self.add_check(Check::new(
CheckKind::FutureFeatureNotDefined(alias.node.name.to_string()),
Range::from_located(stmt),
@@ -807,7 +804,7 @@ where
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
let full_name = match module {
None => alias.node.name.to_string(),
Some(parent) => format!("{}.{}", parent, alias.node.name),
Some(parent) => format!("{parent}.{}", alias.node.name),
};
self.add_binding(
name,
@@ -823,8 +820,7 @@ where
.node
.asname
.as_ref()
.map(|asname| asname == &alias.node.name)
.unwrap_or(false)
.map_or(false, |asname| asname == &alias.node.name)
{
Some((
self.scopes[*(self
@@ -839,7 +835,7 @@ where
},
range: Range::from_located(stmt),
},
)
);
}
if self.settings.enabled.contains(&CheckCode::I252) {
@@ -940,7 +936,7 @@ where
self,
stmt,
test,
msg.as_ref().map(|expr| expr.deref()),
msg.as_ref().map(|expr| &**expr),
);
}
if self.settings.enabled.contains(&CheckCode::S101) {
@@ -983,7 +979,7 @@ where
StmtKind::Assign { targets, value, .. } => {
if self.settings.enabled.contains(&CheckCode::E731) {
if let [target] = &targets[..] {
pycodestyle::plugins::do_not_assign_lambda(self, target, value, stmt)
pycodestyle::plugins::do_not_assign_lambda(self, target, value, stmt);
}
}
if self.settings.enabled.contains(&CheckCode::U001) {
@@ -1020,7 +1016,7 @@ where
StmtKind::Delete { .. } => {}
StmtKind::Expr { value, .. } => {
if self.settings.enabled.contains(&CheckCode::B015) {
flake8_bugbear::plugins::useless_comparison(self, value)
flake8_bugbear::plugins::useless_comparison(self, value);
}
}
_ => {}
@@ -1085,7 +1081,7 @@ where
}
self.except_handlers.pop();
for excepthandler in handlers {
self.visit_excepthandler(excepthandler)
self.visit_excepthandler(excepthandler);
}
for stmt in orelse {
self.visit_stmt(stmt);
@@ -1624,7 +1620,7 @@ where
comparators,
check_none_comparisons,
check_true_false_comparisons,
)
);
}
if self.settings.enabled.contains(&CheckCode::F632) {
@@ -1721,7 +1717,7 @@ where
for expr in &args.defaults {
self.visit_expr(expr);
}
self.push_scope(Scope::new(ScopeKind::Lambda))
self.push_scope(Scope::new(ScopeKind::Lambda));
}
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
if self.settings.enabled.contains(&CheckCode::C416) {
@@ -1736,10 +1732,10 @@ where
self.add_check(check);
};
}
self.push_scope(Scope::new(ScopeKind::Generator))
self.push_scope(Scope::new(ScopeKind::Generator));
}
ExprKind::GeneratorExp { .. } | ExprKind::DictComp { .. } => {
self.push_scope(Scope::new(ScopeKind::Generator))
self.push_scope(Scope::new(ScopeKind::Generator));
}
_ => {}
};
@@ -1898,7 +1894,7 @@ where
error!(
"Found non-ExprKind::Tuple argument to PEP 593 \
Annotation."
)
);
}
}
}
@@ -2018,10 +2014,10 @@ where
.extend(pyflakes::checks::duplicate_arguments(arguments));
}
if self.settings.enabled.contains(&CheckCode::B006) {
flake8_bugbear::plugins::mutable_argument_default(self, arguments)
flake8_bugbear::plugins::mutable_argument_default(self, arguments);
}
if self.settings.enabled.contains(&CheckCode::B008) {
flake8_bugbear::plugins::function_call_argument_default(self, arguments)
flake8_bugbear::plugins::function_call_argument_default(self, arguments);
}
// flake8-boolean-trap
@@ -2161,7 +2157,7 @@ impl<'a> Checker<'a> {
builtin,
Binding {
kind: BindingKind::Builtin,
range: Default::default(),
range: Range::default(),
used: None,
},
);
@@ -2171,7 +2167,7 @@ impl<'a> Checker<'a> {
builtin,
Binding {
kind: BindingKind::Builtin,
range: Default::default(),
range: Range::default(),
used: None,
},
);
@@ -2193,7 +2189,7 @@ impl<'a> Checker<'a> {
pub fn binding_context(&self) -> BindingContext {
let mut rev = self.parent_stack.iter().rev().fuse();
let defined_by = *rev.next().expect("Expected to bind within a statement.");
let defined_in = rev.next().cloned();
let defined_in = rev.next().copied();
BindingContext {
defined_by,
defined_in,
@@ -2314,7 +2310,7 @@ impl<'a> Checker<'a> {
self.add_check(Check::new(
CheckKind::UndefinedName(id.clone()),
Range::from_located(expr),
))
));
}
}
}
@@ -2341,23 +2337,24 @@ impl<'a> Checker<'a> {
.current_scope()
.values
.get(id)
.map(|binding| matches!(binding.kind, BindingKind::Global))
.unwrap_or(false)
.map_or(false, |binding| matches!(binding.kind, BindingKind::Global))
{
pep8_naming::plugins::non_lowercase_variable_in_function(self, expr, parent, id)
pep8_naming::plugins::non_lowercase_variable_in_function(
self, expr, parent, id,
);
}
}
}
if self.settings.enabled.contains(&CheckCode::N815) {
if matches!(self.current_scope().kind, ScopeKind::Class(..)) {
pep8_naming::plugins::mixed_case_variable_in_class_scope(self, expr, parent, id)
pep8_naming::plugins::mixed_case_variable_in_class_scope(self, expr, parent, id);
}
}
if self.settings.enabled.contains(&CheckCode::N816) {
if matches!(self.current_scope().kind, ScopeKind::Module) {
pep8_naming::plugins::mixed_case_variable_in_global_scope(self, expr, parent, id)
pep8_naming::plugins::mixed_case_variable_in_global_scope(self, expr, parent, id);
}
}
@@ -2453,7 +2450,7 @@ impl<'a> Checker<'a> {
self.add_check(Check::new(
CheckKind::UndefinedName(id.to_string()),
Range::from_located(expr),
))
));
}
}
}
@@ -2523,7 +2520,7 @@ impl<'a> Checker<'a> {
self.parent_stack = parents;
self.scope_stack = scopes;
self.visible_scope = visibility;
self.push_scope(Scope::new(ScopeKind::Function(Default::default())));
self.push_scope(Scope::new(ScopeKind::Function(FunctionScope::default())));
match &stmt.node {
StmtKind::FunctionDef { body, args, .. }
@@ -2588,9 +2585,7 @@ impl<'a> Checker<'a> {
let all_binding: Option<&Binding> = scope.values.get("__all__");
let all_names: Option<Vec<&str>> =
all_binding.and_then(|binding| match &binding.kind {
BindingKind::Export(names) => {
Some(names.iter().map(|name| name.as_str()).collect())
}
BindingKind::Export(names) => Some(names.iter().map(String::as_str).collect()),
_ => None,
});
@@ -2648,7 +2643,7 @@ impl<'a> Checker<'a> {
let mut unused: BTreeMap<(ImportKind, usize, Option<usize>), Vec<&str>> =
BTreeMap::new();
for (name, binding) in scope.values.iter() {
for (name, binding) in &scope.values {
if !matches!(
binding.kind,
BindingKind::Importation(..)
@@ -2892,7 +2887,7 @@ pub fn check_ast(
python_ast: &Suite,
locator: &SourceCodeLocator,
settings: &Settings,
autofix: &fixer::Mode,
autofix: bool,
path: &Path,
) -> Vec<Check> {
let mut checker = Checker::new(settings, autofix, path, locator);

View File

@@ -4,7 +4,6 @@ use nohash_hasher::IntSet;
use rustpython_parser::ast::Suite;
use crate::ast::visitor::Visitor;
use crate::autofix::fixer;
use crate::checks::Check;
use crate::isort;
use crate::isort::track::ImportTracker;
@@ -15,12 +14,12 @@ fn check_import_blocks(
tracker: ImportTracker,
locator: &SourceCodeLocator,
settings: &Settings,
autofix: &fixer::Mode,
autofix: bool,
) -> Vec<Check> {
let mut checks = vec![];
for block in tracker.into_iter() {
if !block.is_empty() {
if let Some(check) = isort::plugins::check_imports(block, locator, settings, autofix) {
if let Some(check) = isort::plugins::check_imports(&block, locator, settings, autofix) {
checks.push(check);
}
}
@@ -33,7 +32,7 @@ pub fn check_imports(
locator: &SourceCodeLocator,
exclusions: &IntSet<usize>,
settings: &Settings,
autofix: &fixer::Mode,
autofix: bool,
) -> Vec<Check> {
let mut tracker = ImportTracker::new(exclusions);
for stmt in python_ast {

View File

@@ -6,7 +6,7 @@ use regex::Regex;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::{fixer, Fix};
use crate::autofix::Fix;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::noqa;
use crate::noqa::Directive;
@@ -37,7 +37,7 @@ pub fn check_lines(
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
settings: &Settings,
autofix: &fixer::Mode,
autofix: bool,
) {
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::U009);
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
@@ -73,7 +73,7 @@ pub fn check_lines(
end_location: Location::new(lineno + 1, line_length + 1),
},
);
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
if autofix && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::deletion(
Location::new(lineno + 1, 0),
Location::new(lineno + 1, line_length + 1),
@@ -101,7 +101,7 @@ pub fn check_lines(
match noqa {
(Directive::All(..), matches) => {
matches.push(check.kind.code().as_ref());
ignored.push(index)
ignored.push(index);
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&check.kind.code().as_ref()) {
@@ -195,7 +195,7 @@ pub fn check_lines(
end_location: Location::new(row + 1, end),
},
);
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
if autofix && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
@@ -208,10 +208,10 @@ pub fn check_lines(
let mut invalid_codes = vec![];
let mut valid_codes = vec![];
for code in codes {
if !matches.contains(&code) {
invalid_codes.push(code.to_string());
} else {
if matches.contains(&code) {
valid_codes.push(code.to_string());
} else {
invalid_codes.push(code.to_string());
}
}
@@ -223,7 +223,7 @@ pub fn check_lines(
end_location: Location::new(row + 1, end),
},
);
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
if autofix && settings.fixable.contains(check.kind.code()) {
if valid_codes.is_empty() {
check.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
@@ -257,14 +257,13 @@ mod tests {
use nohash_hasher::IntMap;
use super::check_lines;
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::settings::Settings;
#[test]
fn e501_non_ascii_char() {
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
let noqa_line_for: IntMap<usize, usize> = Default::default();
let noqa_line_for: IntMap<usize, usize> = IntMap::default();
let check_with_max_line_length = |line_length: usize| {
let mut checks: Vec<Check> = vec![];
check_lines(
@@ -275,7 +274,7 @@ mod tests {
line_length,
..Settings::for_rule(CheckCode::E501)
},
&fixer::Mode::Generate,
true,
);
checks
};

View File

@@ -2,7 +2,6 @@
use rustpython_parser::lexer::{LexResult, Tok};
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::lex::docstring_detection::StateMachine;
use crate::rules::checks::Context;
@@ -10,12 +9,13 @@ use crate::source_code_locator::SourceCodeLocator;
use crate::{flake8_quotes, pycodestyle, rules, Settings};
pub fn check_tokens(
checks: &mut Vec<Check>,
locator: &SourceCodeLocator,
tokens: &[LexResult],
settings: &Settings,
autofix: &fixer::Mode,
) {
autofix: bool,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
let enforce_ambiguous_unicode_character = settings.enabled.contains(&CheckCode::RUF001)
|| settings.enabled.contains(&CheckCode::RUF002)
|| settings.enabled.contains(&CheckCode::RUF003);
@@ -25,7 +25,7 @@ pub fn check_tokens(
|| settings.enabled.contains(&CheckCode::Q003);
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
let mut state_machine: StateMachine = Default::default();
let mut state_machine = StateMachine::default();
for &(start, ref tok, end) in tokens.iter().flatten() {
let is_docstring = if enforce_ambiguous_unicode_character || enforce_quotes {
state_machine.consume(tok)
@@ -81,4 +81,6 @@ pub fn check_tokens(
}
}
}
checks
}

View File

@@ -613,7 +613,7 @@ impl CheckCode {
}
}
/// A placeholder representation of the CheckKind for the check.
/// A placeholder representation of the `CheckKind` for the check.
pub fn kind(&self) -> CheckKind {
match self {
// pycodestyle errors
@@ -1334,13 +1334,13 @@ impl CheckKind {
match self {
// pycodestyle errors
CheckKind::AmbiguousClassName(name) => {
format!("Ambiguous class name: `{}`", name)
format!("Ambiguous class name: `{name}`")
}
CheckKind::AmbiguousFunctionName(name) => {
format!("Ambiguous function name: `{}`", name)
format!("Ambiguous function name: `{name}`")
}
CheckKind::AmbiguousVariableName(name) => {
format!("Ambiguous variable name: `{}`", name)
format!("Ambiguous variable name: `{name}`")
}
CheckKind::AssertTuple => {
"Assert test is a non-empty tuple, which is always `True`".to_string()
@@ -1383,7 +1383,7 @@ impl CheckKind {
CheckKind::ImportStarUsage(name, sources) => {
let sources = sources
.iter()
.map(|source| format!("`{}`", source))
.map(|source| format!("`{source}`"))
.join(", ");
format!("`{name}` may be undefined, or defined from star imports: {sources}")
}
@@ -1421,24 +1421,18 @@ impl CheckKind {
CheckKind::ExpressionsInStarAssignment => {
"Too many expressions in star-unpacking assignment".to_string()
}
CheckKind::TrueFalseComparison(value, op) => match *value {
true => match op {
RejectedCmpop::Eq => {
"Comparison to `True` should be `cond is True`".to_string()
}
RejectedCmpop::NotEq => {
"Comparison to `True` should be `cond is not True`".to_string()
}
},
false => match op {
RejectedCmpop::Eq => {
"Comparison to `False` should be `cond is False`".to_string()
}
RejectedCmpop::NotEq => {
"Comparison to `False` should be `cond is not False`".to_string()
}
},
},
CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq) => {
"Comparison to `True` should be `cond is True`".to_string()
}
CheckKind::TrueFalseComparison(true, RejectedCmpop::NotEq) => {
"Comparison to `True` should be `cond is not True`".to_string()
}
CheckKind::TrueFalseComparison(false, RejectedCmpop::Eq) => {
"Comparison to `False` should be `cond is False`".to_string()
}
CheckKind::TrueFalseComparison(false, RejectedCmpop::NotEq) => {
"Comparison to `False` should be `cond is not False`".to_string()
}
CheckKind::TwoStarredExpressions => "Two starred expressions in assignment".to_string(),
CheckKind::TypeComparison => "Do not compare types, use `isinstance()`".to_string(),
CheckKind::UndefinedExport(name) => {
@@ -2161,13 +2155,12 @@ impl Check {
mod tests {
use std::str::FromStr;
use anyhow::Result;
use strum::IntoEnumIterator;
use crate::checks::CheckCode;
#[test]
fn check_code_serialization() -> Result<()> {
fn check_code_serialization() {
for check_code in CheckCode::iter() {
assert!(
CheckCode::from_str(check_code.as_ref()).is_ok(),
@@ -2175,6 +2168,5 @@ mod tests {
check_code
);
}
Ok(())
}
}

View File

@@ -1,4 +1,4 @@
//! File automatically generated by examples/generate_check_code_prefix.rs.
//! File automatically generated by `examples/generate_check_code_prefix.rs`.
use serde::{Deserialize, Serialize};
use strum_macros::EnumString;

View File

@@ -13,6 +13,7 @@ use crate::settings::types::{PatternPrefixPair, PerFileIgnore, PythonVersion};
#[derive(Debug, Parser)]
#[command(author, about = "Ruff: An extremely fast Python linter.")]
#[command(version)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cli {
#[arg(required = true)]
pub files: Vec<PathBuf>,

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,8 @@ use crate::{Settings, SourceCodeLocator};
bitflags! {
pub struct Flags: u32 {
const NOQA = 0b00000001;
const ISORT = 0b00000010;
const NOQA = 0b0000_0001;
const ISORT = 0b0000_0010;
}
}
@@ -44,12 +44,12 @@ pub fn extract_directives(
noqa_line_for: if flags.contains(Flags::NOQA) {
extract_noqa_line_for(lxr)
} else {
Default::default()
IntMap::default()
},
isort_exclusions: if flags.contains(Flags::ISORT) {
extract_isort_exclusions(lxr, locator)
} else {
Default::default()
IntSet::default()
},
}
}
@@ -113,7 +113,7 @@ pub fn extract_isort_exclusions(lxr: &[LexResult], locator: &SourceCodeLocator)
#[cfg(test)]
mod tests {
use anyhow::Result;
use nohash_hasher::IntMap;
use rustpython_parser::lexer;
use rustpython_parser::lexer::LexResult;
@@ -121,8 +121,8 @@ mod tests {
use crate::directives::extract_noqa_line_for;
#[test]
fn extraction() -> Result<()> {
let empty: IntMap<usize, usize> = Default::default();
fn extraction() {
let empty: IntMap<usize, usize> = IntMap::default();
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
@@ -200,7 +200,5 @@ z = x + 1",
extract_noqa_line_for(&lxr),
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
);
Ok(())
}
}

View File

@@ -7,7 +7,6 @@ mod tests {
use anyhow::Result;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::{flake8_annotations, Settings};
@@ -31,7 +30,7 @@ mod tests {
CheckCode::ANN401,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -57,7 +56,7 @@ mod tests {
CheckCode::ANN102,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -83,7 +82,7 @@ mod tests {
CheckCode::ANN206,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -109,7 +108,7 @@ mod tests {
CheckCode::ANN206,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -129,7 +128,7 @@ mod tests {
},
..Settings::for_rules(vec![CheckCode::ANN401])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);

View File

@@ -1,5 +1,3 @@
use std::ops::Deref;
use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
@@ -25,16 +23,14 @@ where
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// No recurse.
}
StmtKind::Return { value } => {
self.returns.push(value.as_ref().map(|expr| expr.deref()))
}
StmtKind::Return { value } => self.returns.push(value.as_ref().map(|expr| &**expr)),
_ => visitor::walk_stmt(self, stmt),
}
}
}
fn is_none_returning(body: &[Stmt]) -> bool {
let mut visitor: ReturnStatementVisitor = Default::default();
let mut visitor = ReturnStatementVisitor::default();
for stmt in body {
visitor.visit_stmt(stmt);
}

View File

@@ -21,6 +21,7 @@ pub struct Options {
}
#[derive(Debug, Hash, Default)]
#[allow(clippy::struct_excessive_bools)]
pub struct Settings {
pub mypy_init_return: bool,
pub suppress_dummy_args: bool,
@@ -29,6 +30,7 @@ pub struct Settings {
}
impl Settings {
#[allow(clippy::needless_pass_by_value)]
pub fn from_options(options: Options) -> Self {
Self {
mypy_init_return: options.mypy_init_return.unwrap_or_default(),

View File

@@ -51,7 +51,7 @@ pub fn check_boolean_default_value_in_function_definition(
checker: &mut Checker,
arguments: &Arguments,
) {
for arg in arguments.defaults.iter() {
for arg in &arguments.defaults {
add_if_boolean(
checker,
arg,

View File

@@ -7,7 +7,6 @@ mod tests {
use anyhow::Result;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::{flake8_bugbear, Settings};
@@ -26,7 +25,7 @@ mod tests {
},
..Settings::for_rules(vec![CheckCode::B008])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -17,8 +17,7 @@ fn is_abc_class(
.node
.arg
.as_ref()
.map(|a| a == "metaclass")
.unwrap_or(false)
.map_or(false, |a| a == "metaclass")
&& match_module_member(
&keyword.node.value,
"abc",

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::autofix::Fix;
@@ -8,16 +8,16 @@ use crate::code_gen::SourceGenerator;
fn assertion_error(msg: Option<&Expr>) -> Stmt {
Stmt::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
StmtKind::Raise {
exc: Some(Box::new(Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Call {
func: Box::new(Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Name {
id: "AssertionError".to_string(),
ctx: ExprContext::Load,
@@ -46,14 +46,13 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
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() {
check.amend(Fix::replacement(
content,
stmt.location,
stmt.end_location.unwrap(),
));
}
generator.unparse_stmt(&assertion_error(msg));
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
stmt.location,
stmt.end_location.unwrap(),
));
}
}
checker.add_check(check);

View File

@@ -1,7 +1,9 @@
use std::collections::BTreeSet;
use itertools::Itertools;
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Stmt};
use rustpython_ast::{
Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location, Stmt,
};
use crate::ast::helpers;
use crate::ast::types::Range;
@@ -12,8 +14,8 @@ use crate::code_gen::SourceGenerator;
fn type_pattern(elts: Vec<&Expr>) -> Expr {
Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Tuple {
elts: elts.into_iter().cloned().collect(),
ctx: ExprContext::Load,
@@ -26,9 +28,9 @@ fn duplicate_handler_exceptions<'a>(
expr: &'a Expr,
elts: &'a [Expr],
) -> BTreeSet<Vec<&'a str>> {
let mut seen: BTreeSet<Vec<&str>> = Default::default();
let mut duplicates: BTreeSet<Vec<&str>> = Default::default();
let mut unique_elts: Vec<&Expr> = Default::default();
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
let mut unique_elts: Vec<&Expr> = Vec::default();
for type_ in elts {
let call_path = helpers::collect_call_paths(type_);
if !call_path.is_empty() {
@@ -57,14 +59,13 @@ fn duplicate_handler_exceptions<'a>(
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) {
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
))
}
generator.unparse_expr(&type_pattern(unique_elts), 0);
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
}
checker.add_check(check);
@@ -75,8 +76,8 @@ fn duplicate_handler_exceptions<'a>(
}
pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Excepthandler]) {
let mut seen: BTreeSet<Vec<&str>> = Default::default();
let mut duplicates: BTreeSet<Vec<&str>> = Default::default();
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
for handler in handlers {
match &handler.node {
ExcepthandlerKind::ExceptHandler { type_, .. } => {

View File

@@ -60,9 +60,9 @@ where
self.checks.push((
CheckKind::FunctionCallArgumentDefault(compose_call_path(expr)),
Range::from_located(expr),
))
));
}
visitor::walk_expr(self, expr)
visitor::walk_expr(self, expr);
}
ExprKind::Lambda { .. } => {}
_ => visitor::walk_expr(self, expr),

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind};
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location};
use crate::ast::types::Range;
use crate::autofix::Fix;
@@ -10,8 +10,8 @@ use crate::python::keyword::KWLIST;
fn attribute(value: &Expr, attr: &str) -> Expr {
Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Attribute {
value: Box::new(value.clone()),
attr: attr.to_string(),
@@ -35,14 +35,13 @@ pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr));
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() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
generator.unparse_expr(&attribute(obj, value), 0);
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
}
checker.add_check(check);

View File

@@ -31,7 +31,7 @@ where
}
ExprKind::Lambda { args, body } => {
visitor::walk_expr(self, body);
for arg in args.args.iter() {
for arg in &args.args {
self.names.remove(arg.node.arg.as_str());
}
}

View File

@@ -64,22 +64,21 @@ pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[E
);
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(elt, 0) {
if let Ok(content) = generator.generate() {
match match_tuple_range(handler, checker.locator) {
Ok(range) => {
check.amend(Fix::replacement(
content,
range.location,
range.end_location,
));
}
Err(e) => error!("Failed to locate parentheses: {}", e),
generator.unparse_expr(elt, 0);
if let Ok(content) = generator.generate() {
match match_tuple_range(handler, checker.locator) {
Ok(range) => {
check.amend(Fix::replacement(
content,
range.location,
range.end_location,
));
}
Err(e) => error!("Failed to locate parentheses: {}", e),
}
}
}
checker.add_check(check)
checker.add_check(check);
}
}
}

View File

@@ -1,4 +1,6 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use anyhow::Result;
use log::error;
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::autofix::Fix;
@@ -8,14 +10,14 @@ use crate::code_gen::SourceGenerator;
use crate::python::identifiers::IDENTIFIER_REGEX;
use crate::python::keyword::KWLIST;
fn assignment(obj: &Expr, name: &str, value: &Expr) -> Option<String> {
fn assignment(obj: &Expr, name: &str, value: &Expr) -> Result<String> {
let stmt = Stmt::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
StmtKind::Assign {
targets: vec![Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Attribute {
value: Box::new(obj.clone()),
attr: name.to_string(),
@@ -27,10 +29,8 @@ fn assignment(obj: &Expr, name: &str, value: &Expr) -> Option<String> {
},
);
let mut generator = SourceGenerator::new();
match generator.unparse_stmt(&stmt) {
Ok(()) => generator.generate().ok(),
Err(_) => None,
}
generator.unparse_stmt(&stmt);
generator.generate().map_err(std::convert::Into::into)
}
/// B010
@@ -47,13 +47,14 @@ pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
let mut check =
Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
if checker.patch(check.kind.code()) {
if let Some(content) = assignment(obj, name, value) {
check.amend(Fix::replacement(
match assignment(obj, name, value) {
Ok(content) => check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
)),
Err(e) => error!("Failed to fix invalid comparison: {}", e),
};
}
checker.add_check(check);
}

View File

@@ -12,7 +12,7 @@ pub fn unary_prefix_increment(checker: &mut Checker, expr: &Expr, op: &Unaryop,
checker.add_check(Check::new(
CheckKind::UnaryPrefixIncrement,
Range::from_located(expr),
))
));
}
}
}

View File

@@ -17,7 +17,7 @@ struct NameFinder<'a> {
impl NameFinder<'_> {
fn new() -> Self {
NameFinder {
names: Default::default(),
names: FxHashMap::default(),
}
}
}
@@ -71,7 +71,7 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body:
format!("_{name}"),
expr.location,
expr.end_location.unwrap(),
))
));
}
checker.add_check(check);
}

View File

@@ -1,3 +1,4 @@
#[derive(Clone, Copy)]
pub enum ShadowingType {
Variable,
Argument,

View File

@@ -390,11 +390,6 @@ pub fn unnecessary_double_cast_or_process(
args: &[Expr],
location: Range,
) -> Option<Check> {
let outer = function_name(func)?;
if !["list", "tuple", "set", "reversed", "sorted"].contains(&outer) {
return None;
}
fn new_check(inner: &str, outer: &str, location: Range) -> Check {
Check::new(
CheckKind::UnnecessaryDoubleCastOrProcess(inner.to_string(), outer.to_string()),
@@ -402,6 +397,11 @@ pub fn unnecessary_double_cast_or_process(
)
}
let outer = function_name(func)?;
if !["list", "tuple", "set", "reversed", "sorted"].contains(&outer) {
return None;
}
if let ExprKind::Call { func, .. } = &args.first()?.node {
let inner = function_name(func)?;
// Ex) set(tuple(...))

View File

@@ -1,9 +1,9 @@
use anyhow::Result;
use libcst_native::{
Arg, AssignEqual, Call, Codegen, Dict, DictComp, DictElement, Element, Expr, Expression,
LeftCurlyBrace, LeftParen, LeftSquareBracket, List, ListComp, Name, ParenthesizableWhitespace,
RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace,
Tuple,
Arg, AssignEqual, Call, Codegen, CodegenState, Dict, DictComp, DictElement, Element, Expr,
Expression, LeftCurlyBrace, LeftParen, LeftSquareBracket, List, ListComp, Name,
ParenthesizableWhitespace, RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp,
SimpleString, SimpleWhitespace, Tuple,
};
use crate::ast::types::Range;
@@ -60,7 +60,7 @@ pub fn fix_unnecessary_generator_list(
rpar: generator_exp.rpar.clone(),
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -103,7 +103,7 @@ pub fn fix_unnecessary_generator_set(
rpar: generator_exp.rpar.clone(),
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -163,13 +163,13 @@ pub fn fix_unnecessary_generator_dict(
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
whitespace_before_colon: Default::default(),
lpar: vec![],
rpar: vec![],
whitespace_before_colon: ParenthesizableWhitespace::default(),
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" ")),
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -211,7 +211,7 @@ pub fn fix_unnecessary_list_comprehension_set(
rpar: list_comp.rpar.clone(),
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -269,7 +269,7 @@ pub fn fix_unnecessary_list_comprehension_dict(
rpar: list_comp.rpar.clone(),
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -312,12 +312,12 @@ pub fn fix_unnecessary_literal_set(
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}));
}
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -363,7 +363,7 @@ pub fn fix_unnecessary_literal_dict(
key: key.clone(),
value: value.clone(),
comma: comma.clone(),
whitespace_before_colon: Default::default(),
whitespace_before_colon: ParenthesizableWhitespace::default(),
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
SimpleWhitespace(" "),
),
@@ -385,11 +385,11 @@ pub fn fix_unnecessary_literal_dict(
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -422,28 +422,28 @@ pub fn fix_unnecessary_collection_call(
match name.value {
"tuple" => {
body.value = Expression::Tuple(Box::new(Tuple {
elements: Default::default(),
lpar: vec![Default::default()],
rpar: vec![Default::default()],
elements: vec![],
lpar: vec![LeftParen::default()],
rpar: vec![RightParen::default()],
}));
}
"list" => {
body.value = Expression::List(Box::new(List {
elements: Default::default(),
lbracket: Default::default(),
rbracket: Default::default(),
lpar: Default::default(),
rpar: Default::default(),
elements: vec![],
lbracket: LeftSquareBracket::default(),
rbracket: RightSquareBracket::default(),
lpar: vec![],
rpar: vec![],
}));
}
"dict" => {
if call.args.is_empty() {
body.value = Expression::Dict(Box::new(Dict {
elements: Default::default(),
lbrace: Default::default(),
rbrace: Default::default(),
lpar: Default::default(),
rpar: Default::default(),
elements: vec![],
lbrace: LeftCurlyBrace::default(),
rbrace: RightCurlyBrace::default(),
lpar: vec![],
rpar: vec![],
}));
} else {
// Quote each argument.
@@ -465,12 +465,12 @@ pub fn fix_unnecessary_collection_call(
.map(|(i, arg)| DictElement::Simple {
key: Expression::SimpleString(Box::new(SimpleString {
value: &arena[i],
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
})),
value: arg.value.clone(),
comma: arg.comma.clone(),
whitespace_before_colon: Default::default(),
whitespace_before_colon: ParenthesizableWhitespace::default(),
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
SimpleWhitespace(" "),
),
@@ -490,8 +490,8 @@ pub fn fix_unnecessary_collection_call(
.whitespace_after_arg
.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}));
}
}
@@ -502,7 +502,7 @@ pub fn fix_unnecessary_collection_call(
}
};
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -558,7 +558,7 @@ pub fn fix_unnecessary_literal_within_tuple_call(
}],
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -612,11 +612,11 @@ pub fn fix_unnecessary_literal_within_list_call(
rbracket: RightSquareBracket {
whitespace_before: whitespace_before.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -640,7 +640,7 @@ pub fn fix_unnecessary_list_call(
body.value = arg.value.clone();
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -695,22 +695,22 @@ pub fn fix_unnecessary_call_around_sorted(
args.push(Arg {
value: Expression::Name(Box::new(Name {
value: "True",
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
})),
keyword: Some(Name {
value: "reverse",
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}),
equal: Some(AssignEqual {
whitespace_before: Default::default(),
whitespace_after: Default::default(),
whitespace_before: ParenthesizableWhitespace::default(),
whitespace_after: ParenthesizableWhitespace::default(),
}),
comma: Default::default(),
star: Default::default(),
whitespace_after_star: Default::default(),
whitespace_after_arg: Default::default(),
comma: None,
star: "",
whitespace_after_star: ParenthesizableWhitespace::default(),
whitespace_after_arg: ParenthesizableWhitespace::default(),
});
args
};
@@ -722,11 +722,11 @@ pub fn fix_unnecessary_call_around_sorted(
rpar: inner_call.rpar.clone(),
whitespace_after_func: inner_call.whitespace_after_func.clone(),
whitespace_before_args: inner_call.whitespace_before_args.clone(),
}))
}));
}
}
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -750,45 +750,45 @@ pub fn fix_unnecessary_comprehension(
body.value = Expression::Call(Box::new(Call {
func: Box::new(Expression::Name(Box::new(Name {
value: "list",
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}))),
args: vec![Arg {
value: inner.for_in.iter.clone(),
keyword: Default::default(),
equal: Default::default(),
comma: Default::default(),
star: Default::default(),
whitespace_after_star: Default::default(),
whitespace_after_arg: Default::default(),
keyword: None,
equal: None,
comma: None,
star: "",
whitespace_after_star: ParenthesizableWhitespace::default(),
whitespace_after_arg: ParenthesizableWhitespace::default(),
}],
lpar: Default::default(),
rpar: Default::default(),
whitespace_after_func: Default::default(),
whitespace_before_args: Default::default(),
}))
lpar: vec![],
rpar: vec![],
whitespace_after_func: ParenthesizableWhitespace::default(),
whitespace_before_args: ParenthesizableWhitespace::default(),
}));
}
Expression::SetComp(inner) => {
body.value = Expression::Call(Box::new(Call {
func: Box::new(Expression::Name(Box::new(Name {
value: "set",
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}))),
args: vec![Arg {
value: inner.for_in.iter.clone(),
keyword: Default::default(),
equal: Default::default(),
comma: Default::default(),
star: Default::default(),
whitespace_after_star: Default::default(),
whitespace_after_arg: Default::default(),
keyword: None,
equal: None,
comma: None,
star: "",
whitespace_after_star: ParenthesizableWhitespace::default(),
whitespace_after_arg: ParenthesizableWhitespace::default(),
}],
lpar: Default::default(),
rpar: Default::default(),
whitespace_after_func: Default::default(),
whitespace_before_args: Default::default(),
}))
lpar: vec![],
rpar: vec![],
whitespace_after_func: ParenthesizableWhitespace::default(),
whitespace_before_args: ParenthesizableWhitespace::default(),
}));
}
_ => {
return Err(anyhow::anyhow!(
@@ -797,7 +797,7 @@ pub fn fix_unnecessary_comprehension(
}
}
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(

View File

@@ -35,7 +35,7 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
if fix.patch.content.is_empty() || fix.patch.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix)
check.amend(fix);
}
Err(e) => error!("Failed to remove print call: {}", e),
}

View File

@@ -74,13 +74,13 @@ pub fn quotes(
return None;
}
return Some(Check::new(
Some(Check::new(
CheckKind::BadQuotesDocstring(settings.docstring_quotes.clone()),
Range {
location: start,
end_location: end,
},
));
))
} else if is_multiline {
// If our string is or contains a known good string, ignore it.
if raw_text.contains(good_multiline(&settings.multiline_quotes)) {
@@ -92,13 +92,13 @@ pub fn quotes(
return None;
}
return Some(Check::new(
Some(Check::new(
CheckKind::BadQuotesMultilineString(settings.multiline_quotes.clone()),
Range {
location: start,
end_location: end,
},
));
))
} else {
let string_contents = &raw_text[1..raw_text.len() - 1];
@@ -131,7 +131,7 @@ pub fn quotes(
},
));
}
}
None
None
}
}

View File

@@ -8,7 +8,6 @@ mod tests {
use anyhow::Result;
use test_case::test_case;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::flake8_quotes::settings::Quote;
use crate::linter::test_path;
@@ -39,7 +38,7 @@ mod tests {
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
@@ -71,7 +70,7 @@ mod tests {
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
@@ -108,7 +107,7 @@ mod tests {
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
@@ -145,7 +144,7 @@ mod tests {
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -7,7 +7,6 @@ mod tests {
use anyhow::Result;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::flake8_tidy_imports::settings::Strictness;
use crate::linter::test_path;
@@ -23,7 +22,7 @@ mod tests {
},
..Settings::for_rules(vec![CheckCode::I252])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -40,7 +39,7 @@ mod tests {
},
..Settings::for_rules(vec![CheckCode::I252])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);

View File

@@ -1,7 +1,6 @@
use std::borrow::Cow;
use std::fs::File;
use std::io::{BufReader, Read};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
@@ -102,8 +101,8 @@ pub fn iter_python_files<'a>(
true
}
}
Err(_) => {
debug!("Ignored path due to error in parsing: {:?}", path);
Err(e) => {
debug!("Ignored path due to error in parsing: {:?}: {}", path, e);
true
}
}
@@ -155,7 +154,7 @@ pub(crate) fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
/// Convert an absolute path to be relative to the current working directory.
pub(crate) fn relativize_path(path: &Path) -> Cow<str> {
if let Ok(path) = path.strip_prefix(path_dedot::CWD.deref()) {
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
return path.to_string_lossy();
}
path.to_string_lossy()

View File

@@ -21,7 +21,7 @@ pub fn categorize(
known_third_party: &BTreeSet<String>,
extra_standard_library: &BTreeSet<String>,
) -> ImportType {
if level.map(|level| *level > 0).unwrap_or(false) {
if level.map_or(false, |level| *level > 0) {
ImportType::LocalFolder
} else if known_first_party.contains(module_base) {
ImportType::FirstParty

View File

@@ -128,7 +128,7 @@ fn annotate_imports<'a>(
asname: alias.node.asname.as_ref(),
atop: alias_atop,
inline: alias_inline,
})
});
}
annotated.push(AnnotatedImport::ImportFrom {
@@ -146,7 +146,7 @@ fn annotate_imports<'a>(
}
fn normalize_imports(imports: Vec<AnnotatedImport>) -> ImportBlock {
let mut block: ImportBlock = Default::default();
let mut block = ImportBlock::default();
for import in imports {
match import {
AnnotatedImport::Import {
@@ -204,7 +204,7 @@ fn normalize_imports(imports: Vec<AnnotatedImport>) -> ImportBlock {
entry.inline.push(comment.value);
}
} else {
let entry = &mut block
let entry = block
.import_from_as
.entry((
ImportFromData { module, level },
@@ -274,7 +274,7 @@ fn categorize_imports<'a>(
known_third_party: &BTreeSet<String>,
extra_standard_library: &BTreeSet<String>,
) -> BTreeMap<ImportType, ImportBlock<'a>> {
let mut block_by_type: BTreeMap<ImportType, ImportBlock> = Default::default();
let mut block_by_type: BTreeMap<ImportType, ImportBlock> = BTreeMap::default();
// Categorize `StmtKind::Import`.
for (alias, comments) in block.import {
let import_type = categorize(
@@ -327,7 +327,7 @@ fn categorize_imports<'a>(
}
fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
let mut ordered: OrderedImportBlock = Default::default();
let mut ordered = OrderedImportBlock::default();
// Sort `StmtKind::Import`.
ordered.import.extend(
@@ -354,12 +354,12 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
(
CommentSet {
atop: comments.atop,
inline: Default::default(),
inline: vec![],
},
FxHashMap::from_iter([(
alias,
CommentSet {
atop: Default::default(),
atop: vec![],
inline: comments.inline,
},
)]),
@@ -428,22 +428,22 @@ pub fn format_imports(
let import_block = sort_imports(import_block);
// Add a blank line between every section.
if !is_first_block {
output.append("\n");
} else {
if is_first_block {
is_first_block = false;
} else {
output.append("\n");
}
let mut is_first_statement = true;
// Format `StmtKind::Import` statements.
for (alias, comments) in import_block.import.iter() {
for (alias, comments) in &import_block.import {
output.append(&format::format_import(alias, comments, is_first_statement));
is_first_statement = false;
}
// Format `StmtKind::ImportFrom` statements.
for (import_from, comments, aliases) in import_block.import_from.iter() {
for (import_from, comments, aliases) in &import_block.import_from {
output.append(&format::format_import_from(
import_from,
comments,
@@ -464,7 +464,6 @@ mod tests {
use anyhow::Result;
use test_case::test_case;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::Settings;
@@ -501,7 +500,7 @@ mod tests {
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
..Settings::for_rule(CheckCode::I001)
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -4,7 +4,7 @@ use textwrap::{dedent, indent};
use crate::ast::helpers::{match_leading_content, match_trailing_content};
use crate::ast::types::Range;
use crate::ast::whitespace::leading_space;
use crate::autofix::{fixer, Fix};
use crate::autofix::Fix;
use crate::checks::CheckKind;
use crate::isort::{comments, format_imports};
use crate::{Check, Settings, SourceCodeLocator};
@@ -30,13 +30,13 @@ fn extract_indentation(body: &[&Stmt], locator: &SourceCodeLocator) -> String {
/// I001
pub fn check_imports(
body: Vec<&Stmt>,
body: &[&Stmt],
locator: &SourceCodeLocator,
settings: &Settings,
autofix: &fixer::Mode,
autofix: bool,
) -> Option<Check> {
let range = extract_range(&body);
let indentation = extract_indentation(&body, locator);
let range = extract_range(body);
let indentation = extract_indentation(body, locator);
// Extract comments. Take care to grab any inline comments from the last line.
let comments = comments::collect_comments(
@@ -53,7 +53,7 @@ pub fn check_imports(
// Generate the sorted import block.
let expected = format_imports(
&body,
body,
comments,
settings.line_length - indentation.len(),
&settings.src,
@@ -64,7 +64,7 @@ pub fn check_imports(
if has_leading_content || has_trailing_content {
let mut check = Check::new(CheckKind::UnsortedImports, range);
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
if autofix && settings.fixable.contains(check.kind.code()) {
let mut content = String::new();
if has_leading_content {
content.push('\n');
@@ -90,9 +90,11 @@ pub fn check_imports(
end_location: Location::new(range.end_location.row() + 1, 0),
};
let actual = dedent(&locator.slice_source_code_range(&range));
if actual != expected {
if actual == expected {
None
} else {
let mut check = Check::new(CheckKind::UnsortedImports, range);
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
if autofix && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::replacement(
indent(&expected, &indentation),
range.location,
@@ -100,8 +102,6 @@ pub fn check_imports(
));
}
Some(check)
} else {
None
}
}
}

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 8
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 6
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 26
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 5
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 15
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 5
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 3
column: 0
applied: false

View File

@@ -18,7 +18,6 @@ expression: checks
end_location:
row: 3
column: 0
applied: false
- kind: UnsortedImports
location:
row: 5
@@ -35,5 +34,4 @@ expression: checks
end_location:
row: 7
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 13
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 5
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 12
column: 0
applied: false

View File

@@ -18,7 +18,6 @@ expression: checks
end_location:
row: 4
column: 0
applied: false
- kind: UnsortedImports
location:
row: 5
@@ -35,5 +34,4 @@ expression: checks
end_location:
row: 7
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 3
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 6
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 4
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 5
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 5
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 11
column: 0
applied: false

View File

@@ -18,5 +18,4 @@ expression: checks
end_location:
row: 27
column: 0
applied: false

View File

@@ -18,7 +18,6 @@ expression: checks
end_location:
row: 3
column: 0
applied: false
- kind: UnsortedImports
location:
row: 5
@@ -35,5 +34,4 @@ expression: checks
end_location:
row: 7
column: 0
applied: false

View File

@@ -1,4 +1,4 @@
/// See: https://github.com/PyCQA/isort/blob/12cc5fbd67eebf92eb2213b03c07b138ae1fb448/isort/sorting.py#L13
/// See: <https://github.com/PyCQA/isort/blob/12cc5fbd67eebf92eb2213b03c07b138ae1fb448/isort/sorting.py#L13>
use crate::python::string;
#[derive(PartialOrd, Ord, PartialEq, Eq)]
@@ -23,12 +23,7 @@ pub fn member_key<'a>(
if name.len() > 1 && string::is_upper(name) {
// Ex) `CONSTANT`
Prefix::Constants
} else if name
.chars()
.next()
.map(|char| char.is_uppercase())
.unwrap_or(false)
{
} else if name.chars().next().map_or(false, char::is_uppercase) {
// Ex) `Class`
Prefix::Classes
} else {

View File

@@ -142,7 +142,7 @@ where
finalbody,
} => {
for excepthandler in handlers {
self.visit_excepthandler(excepthandler)
self.visit_excepthandler(excepthandler);
}
for stmt in body {

View File

@@ -1,6 +1,6 @@
//! Extract docstrings via tokenization.
//!
//! See: https://github.com/zheller/flake8-quotes/blob/ef0d9a90249a080e460b70ab62bf4b65e5aa5816/flake8_quotes/docstring_detection.py#L29
//! See: <https://github.com/zheller/flake8-quotes/blob/ef0d9a90249a080e460b70ab62bf4b65e5aa5816/flake8_quotes/docstring_detection.py#L29>
//!
//! TODO(charlie): Consolidate with the existing AST-based docstring extraction.

View File

@@ -1,15 +1,26 @@
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use std::path::Path;
use anyhow::Result;
use log::debug;
use rustpython_helpers::tokenize;
use rustpython_parser::lexer::LexResult;
use settings::{pyproject, Settings};
use crate::autofix::fixer::Mode;
use crate::checks::Check;
use crate::linter::{check_path, tokenize};
use crate::linter::check_path;
use crate::settings::configuration::Configuration;
use crate::source_code_locator::SourceCodeLocator;
@@ -54,6 +65,7 @@ mod pyflakes;
mod python;
mod pyupgrade;
mod rules;
mod rustpython_helpers;
pub mod settings;
pub mod source_code_locator;
#[cfg(feature = "update-informer")]
@@ -100,7 +112,7 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
&locator,
&directives,
&settings,
&if autofix { Mode::Generate } else { Mode::None },
autofix,
)?;
Ok(checks)

View File

@@ -1,16 +1,13 @@
use std::fs::write;
use std::io;
use std::io::Write;
use std::ops::AddAssign;
use std::path::Path;
use anyhow::Result;
#[cfg(not(target_family = "wasm"))]
use log::debug;
use rustpython_ast::{Mod, Suite};
use rustpython_parser::error::ParseError;
use rustpython_parser::lexer::LexResult;
use rustpython_parser::parser::Mode;
use rustpython_parser::{lexer, parser};
use crate::ast::types::Range;
use crate::autofix::fixer;
@@ -26,32 +23,29 @@ use crate::message::{Message, Source};
use crate::noqa::add_noqa;
use crate::settings::Settings;
use crate::source_code_locator::SourceCodeLocator;
use crate::{cache, directives, fs};
use crate::{cache, directives, fs, rustpython_helpers};
/// Collect tokens up to and including the first error.
pub(crate) fn tokenize(contents: &str) -> Vec<LexResult> {
let mut tokens: Vec<LexResult> = vec![];
for tok in lexer::make_tokenizer(contents) {
let is_err = tok.is_err();
tokens.push(tok);
if is_err {
break;
}
#[derive(Debug, Default)]
pub struct Diagnostics {
pub messages: Vec<Message>,
pub fixed: usize,
}
impl Diagnostics {
pub fn new(messages: Vec<Message>) -> Self {
Self { messages, fixed: 0 }
}
tokens
}
/// Parse a full Python program from its tokens.
pub(crate) fn parse_program_tokens(
lxr: Vec<LexResult>,
source_path: &str,
) -> Result<Suite, ParseError> {
parser::parse_tokens(lxr, Mode::Module, source_path).map(|top| match top {
Mod::Module { body, .. } => body,
_ => unreachable!(),
})
impl AddAssign for Diagnostics {
fn add_assign(&mut self, other: Self) {
self.messages.extend(other.messages);
self.fixed += other.fixed;
}
}
/// Generate a list of `Check` violations from the source code contents at the
/// given `Path`.
pub(crate) fn check_path(
path: &Path,
contents: &str,
@@ -59,7 +53,7 @@ pub(crate) fn check_path(
locator: &SourceCodeLocator,
directives: &Directives,
settings: &Settings,
autofix: &fixer::Mode,
autofix: bool,
) -> Result<Vec<Check>> {
// Aggregate all checks.
let mut checks: Vec<Check> = vec![];
@@ -70,7 +64,7 @@ pub(crate) fn check_path(
.iter()
.any(|check_code| matches!(check_code.lint_source(), LintSource::Tokens));
if use_tokens {
check_tokens(&mut checks, locator, &tokens, settings, autofix);
checks.extend(check_tokens(locator, &tokens, settings, autofix));
}
// Run the AST-based checks.
@@ -83,7 +77,7 @@ pub(crate) fn check_path(
.iter()
.any(|check_code| matches!(check_code.lint_source(), LintSource::Imports));
if use_ast || use_imports {
match parse_program_tokens(tokens, "<filename>") {
match rustpython_helpers::parse_program_tokens(tokens, "<filename>") {
Ok(python_ast) => {
if use_ast {
checks.extend(check_ast(&python_ast, locator, settings, autofix, path));
@@ -106,7 +100,7 @@ pub(crate) fn check_path(
location: parse_error.location,
end_location: parse_error.location,
},
))
));
}
}
}
@@ -135,132 +129,108 @@ pub(crate) fn check_path(
Ok(checks)
}
pub fn lint_stdin(
path: &Path,
stdin: &str,
settings: &Settings,
autofix: &fixer::Mode,
) -> Result<Vec<Message>> {
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(stdin);
// Initialize the SourceCodeLocator (which computes offsets lazily).
let locator = SourceCodeLocator::new(stdin);
// Extract the `# noqa` and `# isort: skip` directives from the source.
let directives = directives::extract_directives(
&tokens,
&locator,
directives::Flags::from_settings(settings),
);
// Generate checks.
let mut checks = check_path(
path,
stdin,
tokens,
&locator,
&directives,
settings,
autofix,
)?;
// Apply autofix, write results to stdout.
if matches!(autofix, fixer::Mode::Apply) {
match fix_file(&mut checks, &locator) {
None => io::stdout().write_all(stdin.as_bytes()),
Some(contents) => io::stdout().write_all(contents.as_bytes()),
}?;
}
// Convert to messages.
Ok(checks
.into_iter()
.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())
}
const MAX_ITERATIONS: usize = 100;
/// Lint the source code at the given `Path`.
pub fn lint_path(
path: &Path,
settings: &Settings,
mode: &cache::Mode,
autofix: &fixer::Mode,
) -> Result<Vec<Message>> {
) -> Result<Diagnostics> {
let metadata = path.metadata()?;
// Check the cache.
if let Some(messages) = cache::get(path, &metadata, settings, autofix, mode) {
debug!("Cache hit for: {}", path.to_string_lossy());
return Ok(messages);
return Ok(Diagnostics::new(messages));
}
// Read the file from disk.
let contents = fs::read_file(path)?;
let mut contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(&contents);
// Track the number of fixed errors across iterations.
let mut fixed = 0;
// Initialize the SourceCodeLocator (which computes offsets lazily).
let locator = SourceCodeLocator::new(&contents);
// As an escape hatch, bail after 100 iterations.
let mut iterations = 0;
// Determine the noqa and isort exclusions.
let directives = directives::extract_directives(
&tokens,
&locator,
directives::Flags::from_settings(settings),
);
// Continuously autofix until the source code stabilizes.
let messages = loop {
// Tokenize once.
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
// Generate checks.
let mut checks = check_path(
path,
&contents,
tokens,
&locator,
&directives,
settings,
autofix,
)?;
// Initialize the SourceCodeLocator (which computes offsets lazily).
let locator = SourceCodeLocator::new(&contents);
// Apply autofix.
if matches!(autofix, fixer::Mode::Apply) {
if let Some(fixed_contents) = fix_file(&mut checks, &locator) {
write(path, fixed_contents.as_ref())?;
// Determine the noqa and isort exclusions.
let directives = directives::extract_directives(
&tokens,
&locator,
directives::Flags::from_settings(settings),
);
// Generate checks.
let checks = check_path(
path,
&contents,
tokens,
&locator,
&directives,
settings,
autofix.into(),
)?;
// Apply autofix.
if matches!(autofix, fixer::Mode::Apply) && iterations < MAX_ITERATIONS {
if let Some((fixed_contents, applied)) = fix_file(&checks, &locator) {
// Count the number of fixed errors.
fixed += applied;
// Store the fixed contents.
contents = fixed_contents.to_string();
// Increment the iteration count.
iterations += 1;
// Re-run the linter pass (by avoiding the break).
continue;
}
}
// Convert to messages.
let filename = path.to_string_lossy().to_string();
break checks
.into_iter()
.map(|check| {
let source = if settings.show_source {
Some(Source::from_check(&check, &locator))
} else {
None
};
Message::from_check(check, filename.clone(), source)
})
.collect::<Vec<_>>();
};
// Convert to messages.
let messages: Vec<Message> = checks
.into_iter()
.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();
// Re-populate the cache.
cache::set(path, &metadata, settings, autofix, &messages, mode);
Ok(messages)
// If we applied any fixes, write the contents back to disk.
if fixed > 0 {
write(path, &contents)?;
}
Ok(Diagnostics { messages, fixed })
}
/// Add any missing `#noqa` pragmas to the source code at the given `Path`.
pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
// Read the file from disk.
let contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(&contents);
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
// Initialize the SourceCodeLocator (which computes offsets lazily).
let locator = SourceCodeLocator::new(&contents);
@@ -280,32 +250,115 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
&locator,
&directives,
settings,
&fixer::Mode::None,
false,
)?;
add_noqa(&checks, &contents, &directives.noqa_line_for, path)
}
/// Apply autoformatting to the source code at the given `Path`.
pub fn autoformat_path(path: &Path) -> Result<()> {
// Read the file from disk.
let contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(&contents);
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
// Generate the AST.
let python_ast = parse_program_tokens(tokens, "<filename>")?;
let mut generator: SourceGenerator = Default::default();
generator.unparse_suite(&python_ast)?;
let python_ast = rustpython_helpers::parse_program_tokens(tokens, "<filename>")?;
let mut generator = SourceGenerator::default();
generator.unparse_suite(&python_ast);
write(path, generator.generate()?)?;
Ok(())
}
/// Generate a list of `Check` violations from source code content derived from
/// stdin.
pub fn lint_stdin(
path: &Path,
stdin: &str,
settings: &Settings,
autofix: &fixer::Mode,
) -> Result<Diagnostics> {
// Read the file from disk.
let mut contents = stdin.to_string();
// Track the number of fixed errors across iterations.
let mut fixed = 0;
// As an escape hatch, bail after 100 iterations.
let mut iterations = 0;
let messages = loop {
// Tokenize once.
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
// Initialize the SourceCodeLocator (which computes offsets lazily).
let locator = SourceCodeLocator::new(&contents);
// Extract the `# noqa` and `# isort: skip` directives from the source.
let directives = directives::extract_directives(
&tokens,
&locator,
directives::Flags::from_settings(settings),
);
// Generate checks.
let checks = check_path(
path,
&contents,
tokens,
&locator,
&directives,
settings,
autofix.into(),
)?;
// Apply autofix.
if matches!(autofix, fixer::Mode::Apply) && iterations < MAX_ITERATIONS {
if let Some((fixed_contents, applied)) = fix_file(&checks, &locator) {
// Count the number of fixed errors.
fixed += applied;
// Store the fixed contents.
contents = fixed_contents.to_string();
// Increment the iteration count.
iterations += 1;
// Re-run the linter pass (by avoiding the break).
continue;
}
}
// Convert to messages.
let filename = path.to_string_lossy().to_string();
break checks
.into_iter()
.map(|check| {
let source = if settings.show_source {
Some(Source::from_check(&check, &locator))
} else {
None
};
Message::from_check(check, filename.clone(), source)
})
.collect();
};
// Write the fixed contents to stdout.
if matches!(autofix, fixer::Mode::Apply) {
io::stdout().write_all(contents.as_bytes())?;
}
Ok(Diagnostics { messages, fixed })
}
#[cfg(test)]
pub fn test_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
pub fn test_path(path: &Path, settings: &Settings, autofix: bool) -> Result<Vec<Check>> {
let contents = fs::read_file(path)?;
let tokens: Vec<LexResult> = tokenize(&contents);
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
let locator = SourceCodeLocator::new(&contents);
let directives = directives::extract_directives(
&tokens,
@@ -332,7 +385,6 @@ mod tests {
use regex::Regex;
use test_case::test_case;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::settings;
@@ -552,7 +604,7 @@ mod tests {
let mut checks = test_path(
Path::new("./resources/test/fixtures").join(path).as_path(),
&settings::Settings::for_rule(check_code),
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
@@ -567,7 +619,7 @@ mod tests {
dummy_variable_rgx: Regex::new(r"^z$").unwrap(),
..settings::Settings::for_rule(CheckCode::F841)
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -579,7 +631,7 @@ mod tests {
let mut checks = test_path(
Path::new("./resources/test/fixtures/M001.py"),
&settings::Settings::for_rules(vec![CheckCode::M001, CheckCode::E501, CheckCode::F841]),
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -591,7 +643,7 @@ mod tests {
let mut checks = test_path(
Path::new("./resources/test/fixtures/__init__.py"),
&settings::Settings::for_rules(vec![CheckCode::F821, CheckCode::F822]),
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -603,7 +655,7 @@ mod tests {
let mut checks = test_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings::for_rules(vec![CheckCode::F401, CheckCode::F821]),
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);

View File

@@ -53,12 +53,12 @@ pub fn set_up_logging(level: &LogLevel) -> Result<()> {
record.target(),
record.level(),
message
))
));
})
.level(level.level_filter())
.chain(std::io::stdout())
.apply()
.map_err(|e| e.into())
.apply()?;
Ok(())
}
#[cfg(test)]

View File

@@ -1,3 +1,16 @@
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::process::ExitCode;
@@ -25,12 +38,14 @@ use log::{debug, error};
use notify::{raw_watcher, RecursiveMode, Watcher};
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
use ruff::linter::Diagnostics;
use rustpython_ast::Location;
use walkdir::DirEntry;
/// Shim that calls par_iter except for wasm because there's no wasm support in
/// rayon yet (there is a shim to be used for the web, but it requires js
/// cooperation) Unfortunately, ParallelIterator does not implement Iterator so
/// the signatures diverge
/// Shim that calls `par_iter` except for wasm because there's no wasm support
/// in rayon yet (there is a shim to be used for the web, but it requires js
/// cooperation) Unfortunately, `ParallelIterator` does not implement `Iterator`
/// so the signatures diverge
#[cfg(not(target_family = "wasm"))]
fn par_iter<T: Sync>(iterable: &Vec<T>) -> impl ParallelIterator<Item = &T> {
iterable.par_iter()
@@ -70,19 +85,14 @@ fn read_from_stdin() -> Result<String> {
Ok(buffer)
}
fn run_once_stdin(settings: &Settings, filename: &Path, autofix: bool) -> Result<Vec<Message>> {
fn run_once_stdin(settings: &Settings, filename: &Path, autofix: bool) -> Result<Diagnostics> {
let stdin = read_from_stdin()?;
let mut messages = lint_stdin(filename, &stdin, settings, &autofix.into())?;
messages.sort_unstable();
Ok(messages)
let mut diagnostics = lint_stdin(filename, &stdin, settings, &autofix.into())?;
diagnostics.messages.sort_unstable();
Ok(diagnostics)
}
fn run_once(
files: &[PathBuf],
settings: &Settings,
cache: bool,
autofix: bool,
) -> Result<Vec<Message>> {
fn run_once(files: &[PathBuf], settings: &Settings, cache: bool, autofix: bool) -> Diagnostics {
// Collect all the files to check.
let start = Instant::now();
let paths: Vec<Result<DirEntry, walkdir::Error>> = files
@@ -93,7 +103,7 @@ fn run_once(
debug!("Identified files to lint in: {:?}", duration);
let start = Instant::now();
let mut messages: Vec<Message> = par_iter(&paths)
let mut diagnostics: Diagnostics = par_iter(&paths)
.map(|entry| {
match entry {
Ok(entry) => {
@@ -110,63 +120,67 @@ fn run_once(
.unwrap_or_else(|(path, message)| {
if let Some(path) = path {
if settings.enabled.contains(&CheckCode::E902) {
vec![Message {
Diagnostics::new(vec![Message {
kind: CheckKind::IOError(message),
fixed: false,
location: Default::default(),
end_location: Default::default(),
location: Location::default(),
end_location: Location::default(),
filename: path.to_string_lossy().to_string(),
source: None,
}]
}])
} else {
error!("Failed to check {}: {message}", path.to_string_lossy());
vec![]
Diagnostics::default()
}
} else {
error!("{message}");
vec![]
Diagnostics::default()
}
})
})
.flatten()
.collect();
.reduce(Diagnostics::default, |mut acc, item| {
acc += item;
acc
});
messages.sort_unstable();
diagnostics.messages.sort_unstable();
let duration = start.elapsed();
debug!("Checked files in: {:?}", duration);
Ok(messages)
diagnostics
}
fn add_noqa(files: &[PathBuf], settings: &Settings) -> Result<usize> {
fn add_noqa(files: &[PathBuf], settings: &Settings) -> usize {
// Collect all the files to check.
let start = Instant::now();
let paths: Vec<Result<DirEntry, walkdir::Error>> = files
let paths: Vec<DirEntry> = files
.iter()
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
.flatten()
.collect();
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);
let start = Instant::now();
let modifications: usize = par_iter(&paths)
.map(|entry| match entry {
Ok(entry) => {
let path = entry.path();
add_noqa_to_path(path, settings)
.filter_map(|entry| {
let path = entry.path();
match add_noqa_to_path(path, settings) {
Ok(count) => Some(count),
Err(e) => {
error!("Failed to add noqa to {}: {e}", path.to_string_lossy());
None
}
}
Err(_) => Ok(0),
})
.flatten()
.sum();
let duration = start.elapsed();
debug!("Added noqa to files in: {:?}", duration);
Ok(modifications)
modifications
}
fn autoformat(files: &[PathBuf], settings: &Settings) -> Result<usize> {
fn autoformat(files: &[PathBuf], settings: &Settings) -> usize {
// Collect all the files to format.
let start = Instant::now();
let paths: Vec<DirEntry> = files
@@ -179,17 +193,22 @@ fn autoformat(files: &[PathBuf], settings: &Settings) -> Result<usize> {
let start = Instant::now();
let modifications = par_iter(&paths)
.map(|entry| {
.filter_map(|entry| {
let path = entry.path();
autoformat_path(path)
match autoformat_path(path) {
Ok(()) => Some(()),
Err(e) => {
error!("Failed to autoformat {}: {e}", path.to_string_lossy());
None
}
}
})
.flatten()
.count();
let duration = start.elapsed();
debug!("Auto-formatted files in: {:?}", duration);
Ok(modifications)
modifications
}
fn inner_main() -> Result<ExitCode> {
@@ -322,7 +341,7 @@ fn inner_main() -> Result<ExitCode> {
printer.clear_screen()?;
printer.write_to_user("Starting linter in watch mode...\n");
let messages = run_once(&cli.files, &settings, cache_enabled, false)?;
let messages = run_once(&cli.files, &settings, cache_enabled, false);
printer.write_continuously(&messages)?;
// Configure the file watcher.
@@ -340,7 +359,7 @@ fn inner_main() -> Result<ExitCode> {
printer.clear_screen()?;
printer.write_to_user("File change detected...\n");
let messages = run_once(&cli.files, &settings, cache_enabled, false)?;
let messages = run_once(&cli.files, &settings, cache_enabled, false);
printer.write_continuously(&messages)?;
}
}
@@ -349,12 +368,12 @@ fn inner_main() -> Result<ExitCode> {
}
}
} else if cli.add_noqa {
let modifications = add_noqa(&cli.files, &settings)?;
let modifications = add_noqa(&cli.files, &settings);
if modifications > 0 && log_level >= LogLevel::Default {
println!("Added {modifications} noqa directives.");
}
} else if cli.autoformat {
let modifications = autoformat(&cli.files, &settings)?;
let modifications = autoformat(&cli.files, &settings);
if modifications > 0 && log_level >= LogLevel::Default {
println!("Formatted {modifications} files.");
}
@@ -362,28 +381,28 @@ fn inner_main() -> Result<ExitCode> {
let is_stdin = cli.files == vec![PathBuf::from("-")];
// Generate lint violations.
let messages = if is_stdin {
let diagnostics = if is_stdin {
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
let path = Path::new(&filename);
run_once_stdin(&settings, path, fix_enabled)?
} else {
run_once(&cli.files, &settings, cache_enabled, fix_enabled)?
run_once(&cli.files, &settings, cache_enabled, fix_enabled)
};
// Always try to print violations (the printer itself may suppress output),
// unless we're writing fixes via stdin (in which case, the transformed
// source code goes to stdout).
if !(is_stdin && fix_enabled) {
printer.write_once(&messages)?;
printer.write_once(&diagnostics)?;
}
// Check for updates if we're in a non-silent log level.
#[cfg(feature = "update-informer")]
if !is_stdin && log_level >= LogLevel::Default && atty::is(atty::Stream::Stdout) {
let _ = updates::check_for_updates();
drop(updates::check_for_updates());
}
if messages.iter().any(|message| !message.fixed) && !cli.exit_zero {
if !diagnostics.messages.is_empty() && !cli.exit_zero {
return Ok(ExitCode::FAILURE);
}
}
@@ -395,7 +414,7 @@ fn main() -> ExitCode {
match inner_main() {
Ok(code) => code,
Err(err) => {
eprintln!("{} {:?}", "error".red().bold(), err);
eprintln!("{} {err:?}", "error".red().bold());
ExitCode::FAILURE
}
}

View File

@@ -8,7 +8,6 @@ mod tests {
use anyhow::Result;
use test_case::test_case;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::{mccabe, Settings};
@@ -17,14 +16,14 @@ mod tests {
#[test_case(3)]
#[test_case(10)]
fn max_complexity_zero(max_complexity: usize) -> Result<()> {
let snapshot = format!("max_complexity_{}", max_complexity);
let snapshot = format!("max_complexity_{max_complexity}");
let mut checks = test_path(
Path::new("./resources/test/fixtures/C901.py"),
&Settings {
mccabe: mccabe::settings::Settings { max_complexity },
..Settings::for_rules(vec![CheckCode::C901])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -14,6 +14,7 @@ pub struct Settings {
}
impl Settings {
#[allow(clippy::needless_pass_by_value)]
pub fn from_options(options: Options) -> Self {
Self {
max_complexity: options.max_complexity.unwrap_or_default(),

View File

@@ -16,7 +16,6 @@ use crate::source_code_locator::SourceCodeLocator;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Message {
pub kind: CheckKind,
pub fixed: bool,
pub location: Location,
pub end_location: Location,
pub filename: String,
@@ -27,7 +26,6 @@ impl Message {
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,
@@ -66,7 +64,7 @@ impl fmt::Display for Message {
self.kind.body(),
);
match &self.source {
None => write!(f, "{}", label),
None => write!(f, "{label}"),
Some(source) => {
let snippet = Snippet {
title: Some(Annotation {
@@ -91,13 +89,13 @@ impl fmt::Display for Message {
}],
opt: FormatOptions {
color: true,
..Default::default()
..FormatOptions::default()
},
};
// `split_once(' ')` strips "error: " from `message`.
let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once(' ').unwrap();
write!(f, "{}", message)
write!(f, "{message}")
}
}
}

View File

@@ -35,7 +35,7 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
noqa.end(),
SPLIT_COMMA_REGEX
.split(codes.as_str())
.map(|code| code.trim())
.map(str::trim)
.filter(|code| !code.is_empty())
.collect(),
),
@@ -57,7 +57,7 @@ pub fn add_noqa(
noqa_line_for: &IntMap<usize, usize>,
path: &Path,
) -> Result<usize> {
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for)?;
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for);
fs::write(path, output)?;
Ok(count)
}
@@ -66,7 +66,7 @@ fn add_noqa_inner(
checks: &[Check],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
) -> Result<(usize, String)> {
) -> (usize, String) {
let lines: Vec<&str> = contents.lines().collect();
let mut matches_by_line: BTreeMap<usize, BTreeSet<&CheckCode>> = BTreeMap::new();
for lineno in 0..lines.len() {
@@ -89,7 +89,7 @@ fn add_noqa_inner(
}
let mut count: usize = 0;
let mut output = "".to_string();
let mut output = String::new();
for (lineno, line) in lines.iter().enumerate() {
match matches_by_line.get(&lineno) {
None => {
@@ -107,7 +107,7 @@ fn add_noqa_inner(
output.push_str("# noqa: ");
}
};
let codes: Vec<&str> = codes.iter().map(|code| code.as_ref()).collect();
let codes: Vec<&str> = codes.iter().map(AsRef::as_ref).collect();
output.push_str(&codes.join(", "));
output.push('\n');
count += 1;
@@ -115,12 +115,13 @@ fn add_noqa_inner(
}
}
Ok((count, output))
(count, output)
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
@@ -142,11 +143,11 @@ mod tests {
}
#[test]
fn modification() -> Result<()> {
fn modification() {
let checks = vec![];
let contents = "x = 1";
let noqa_line_for = Default::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
assert_eq!(count, 0);
assert_eq!(output.trim(), contents.trim());
@@ -158,8 +159,8 @@ mod tests {
},
)];
let contents = "x = 1";
let noqa_line_for = Default::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: F841".trim());
@@ -180,8 +181,8 @@ mod tests {
),
];
let contents = "x = 1 # noqa: E741";
let noqa_line_for = Default::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
@@ -202,11 +203,9 @@ mod tests {
),
];
let contents = "x = 1 # noqa";
let noqa_line_for = Default::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
Ok(())
}
}

View File

@@ -11,13 +11,7 @@ use crate::python::string::{self};
/// N801
pub fn invalid_class_name(class_def: &Stmt, name: &str) -> Option<Check> {
let stripped = name.strip_prefix('_').unwrap_or(name);
if !stripped
.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false)
|| stripped.contains('_')
{
if !stripped.chars().next().map_or(false, char::is_uppercase) || stripped.contains('_') {
return Some(Check::new(
CheckKind::InvalidClassName(name.to_string()),
Range::from_located(class_def),

View File

@@ -78,7 +78,7 @@ pub fn is_mixed_case(name: &str) -> bool {
.unwrap_or(name)
.chars()
.next()
.map_or_else(|| false, |c| c.is_lowercase())
.map_or_else(|| false, char::is_lowercase)
}
pub fn is_acronym(name: &str, asname: &str) -> bool {

View File

@@ -5,8 +5,8 @@ use rustpython_parser::ast::Location;
use serde::Serialize;
use crate::checks::{CheckCode, CheckKind};
use crate::linter::Diagnostics;
use crate::logging::LogLevel;
use crate::message::Message;
use crate::tell_user;
#[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Debug)]
@@ -20,10 +20,9 @@ struct ExpandedMessage<'a> {
kind: &'a CheckKind,
code: &'a CheckCode,
message: String,
fixed: bool,
location: Location,
end_location: Location,
filename: &'a String,
filename: &'a str,
}
pub struct Printer<'a> {
@@ -42,14 +41,13 @@ impl<'a> Printer<'a> {
}
}
pub fn write_once(&self, messages: &[Message]) -> Result<()> {
pub fn write_once(&self, diagnostics: &Diagnostics) -> Result<()> {
if matches!(self.log_level, LogLevel::Silent) {
return Ok(());
}
let (fixed, outstanding): (Vec<&Message>, Vec<&Message>) =
messages.iter().partition(|message| message.fixed);
let num_fixable = outstanding
let num_fixable = diagnostics
.messages
.iter()
.filter(|message| message.kind.fixable())
.count();
@@ -59,41 +57,41 @@ impl<'a> Printer<'a> {
println!(
"{}",
serde_json::to_string_pretty(
&messages
&diagnostics
.messages
.iter()
.map(|message| ExpandedMessage {
kind: &message.kind,
code: message.kind.code(),
message: message.kind.body(),
fixed: message.fixed,
location: message.location,
end_location: message.end_location,
filename: &message.filename,
})
.collect::<Vec<_>>()
)?
)
);
}
SerializationFormat::Text => {
if self.log_level >= &LogLevel::Default {
if !fixed.is_empty() {
if diagnostics.fixed > 0 {
println!(
"Found {} error(s) ({} fixed).",
outstanding.len(),
fixed.len()
)
} else if !outstanding.is_empty() {
println!("Found {} error(s).", outstanding.len())
diagnostics.messages.len(),
diagnostics.fixed,
);
} else if !diagnostics.messages.is_empty() {
println!("Found {} error(s).", diagnostics.messages.len());
}
}
for message in outstanding {
println!("{}", message)
for message in &diagnostics.messages {
println!("{message}");
}
if self.log_level >= &LogLevel::Default {
if num_fixable > 0 {
println!("{num_fixable} potentially fixable with the --fix option.")
println!("{num_fixable} potentially fixable with the --fix option.");
}
}
}
@@ -102,7 +100,7 @@ impl<'a> Printer<'a> {
Ok(())
}
pub fn write_continuously(&self, messages: &[Message]) -> Result<()> {
pub fn write_continuously(&self, diagnostics: &Diagnostics) -> Result<()> {
if matches!(self.log_level, LogLevel::Silent) {
return Ok(());
}
@@ -110,16 +108,16 @@ impl<'a> Printer<'a> {
if self.log_level >= &LogLevel::Default {
tell_user!(
"Found {} error(s). Watching for file changes.",
messages.len(),
diagnostics.messages.len()
);
}
if !messages.is_empty() {
if !diagnostics.messages.is_empty() {
if self.log_level >= &LogLevel::Default {
println!();
}
for message in messages {
println!("{}", message)
for message in &diagnostics.messages {
println!("{message}");
}
}

View File

@@ -157,7 +157,7 @@ pub fn invalid_escape_sequence(
location,
end_location,
},
))
));
}
}
}

View File

@@ -15,8 +15,8 @@ use crate::code_gen::SourceGenerator;
fn compare(left: &Expr, ops: &[Cmpop], comparators: &[Expr]) -> Option<String> {
let cmp = Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Compare {
left: Box::new(left.clone()),
ops: ops.to_vec(),
@@ -24,10 +24,9 @@ fn compare(left: &Expr, ops: &[Cmpop], comparators: &[Expr]) -> Option<String> {
},
);
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&cmp, 0) {
if let Ok(content) = generator.generate() {
return Some(content);
}
generator.unparse_expr(&cmp, 0);
if let Ok(content) = generator.generate() {
return Some(content);
}
None
}
@@ -268,15 +267,15 @@ pub fn not_tests(
fn function(name: &str, args: &Arguments, body: &Expr) -> Result<String> {
let body = Stmt::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
StmtKind::Return {
value: Some(Box::new(body.clone())),
},
);
let func = Stmt::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
StmtKind::FunctionDef {
name: name.to_string(),
args: Box::new(args.clone()),
@@ -287,8 +286,8 @@ fn function(name: &str, args: &Arguments, body: &Expr) -> Result<String> {
},
);
let mut generator = SourceGenerator::new();
generator.unparse_stmt(&func)?;
generator.generate().map_err(|e| e.into())
generator.unparse_stmt(&func);
Ok(generator.generate()?)
}
/// E731

View File

@@ -532,7 +532,7 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
let content = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
if let Some(last_line) = content.lines().last().map(|line| line.trim()) {
if let Some(last_line) = content.lines().last().map(str::trim) {
if last_line != "\"\"\"" && last_line != "'''" {
let mut check = Check::new(
CheckKind::NewLineAfterLastParagraph,
@@ -587,7 +587,7 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
.map(|line| line.to_lowercase())
.map(str::to_lowercase)
{
for pattern in constants::TRIPLE_QUOTE_PREFIXES
.iter()
@@ -633,7 +633,7 @@ pub fn multi_line_summary_start(checker: &mut Checker, definition: &Definition)
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
.map(|line| line.to_lowercase())
.map(str::to_lowercase)
{
if constants::TRIPLE_QUOTE_PREFIXES.contains(&first_line.as_str()) {
if checker.settings.enabled.contains(&CheckCode::D212) {
@@ -669,7 +669,7 @@ pub fn triple_quotes(checker: &mut Checker, definition: &Definition) {
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
.map(|line| line.to_lowercase())
.map(str::to_lowercase)
{
let starts_with_triple = if string.contains("\"\"\"") {
first_line.starts_with("'''")
@@ -945,51 +945,7 @@ fn blanks_and_section_underline(
.chars()
.all(|char| char.is_whitespace() || char == '-');
if !dash_line_found {
if checker.settings.enabled.contains(&CheckCode::D407) {
let mut check = Check::new(
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch(check.kind.code()) {
// Add a dashed line (of the appropriate length) under the section header.
let content = format!(
"{}{}\n",
whitespace::clean(&whitespace::indentation(checker, docstring)),
"-".repeat(context.section_name.len())
);
check.amend(Fix::insertion(
content,
Location::new(docstring.location.row() + context.original_index + 1, 0),
));
}
checker.add_check(check);
}
if blank_lines_after_header > 0 {
if checker.settings.enabled.contains(&CheckCode::D412) {
let mut check = Check::new(
CheckKind::NoBlankLinesBetweenHeaderAndContent(
context.section_name.to_string(),
),
Range::from_located(docstring),
);
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),
Location::new(
docstring.location.row()
+ context.original_index
+ 1
+ blank_lines_after_header,
0,
),
));
}
checker.add_check(check);
}
}
} else {
if dash_line_found {
if blank_lines_after_header > 0 {
if checker.settings.enabled.contains(&CheckCode::D408) {
let mut check = Check::new(
@@ -1146,6 +1102,50 @@ fn blanks_and_section_underline(
));
}
}
} else {
if checker.settings.enabled.contains(&CheckCode::D407) {
let mut check = Check::new(
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch(check.kind.code()) {
// Add a dashed line (of the appropriate length) under the section header.
let content = format!(
"{}{}\n",
whitespace::clean(&whitespace::indentation(checker, docstring)),
"-".repeat(context.section_name.len())
);
check.amend(Fix::insertion(
content,
Location::new(docstring.location.row() + context.original_index + 1, 0),
));
}
checker.add_check(check);
}
if blank_lines_after_header > 0 {
if checker.settings.enabled.contains(&CheckCode::D412) {
let mut check = Check::new(
CheckKind::NoBlankLinesBetweenHeaderAndContent(
context.section_name.to_string(),
),
Range::from_located(docstring),
);
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),
Location::new(
docstring.location.row()
+ context.original_index
+ 1
+ blank_lines_after_header,
0,
),
));
}
checker.add_check(check);
}
}
}
}
@@ -1190,7 +1190,7 @@ fn common_section(
docstring.location.row() + context.original_index,
section_name_start + section_name_length,
),
))
));
}
}
checker.add_check(check);
@@ -1224,8 +1224,7 @@ fn common_section(
if context
.following_lines
.last()
.map(|line| !line.trim().is_empty())
.unwrap_or(true)
.map_or(true, |line| !line.trim().is_empty())
{
if context.is_last_section {
if checker.settings.enabled.contains(&CheckCode::D413) {
@@ -1285,7 +1284,7 @@ fn common_section(
Location::new(docstring.location.row() + context.original_index, 0),
));
}
checker.add_check(check)
checker.add_check(check);
}
}
@@ -1326,7 +1325,7 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
}
// Look for arguments that weren't included in the docstring.
let mut missing_args: BTreeSet<&str> = Default::default();
let mut missing_args: BTreeSet<&str> = BTreeSet::default();
for arg in all_arguments {
let arg_name = arg.node.arg.as_str();
if arg_name.starts_with('_') {
@@ -1363,12 +1362,7 @@ fn args_section(checker: &mut Checker, definition: &Definition, context: &Sectio
.trim()
.lines()
{
if line
.chars()
.next()
.map(|char| char.is_whitespace())
.unwrap_or(true)
{
if line.chars().next().map_or(true, char::is_whitespace) {
// This is a continuation of documentation for the last
// parameter because it does start with whitespace.
if let Some(current) = args_sections.last_mut() {
@@ -1385,13 +1379,16 @@ fn args_section(checker: &mut Checker, definition: &Definition, context: &Sectio
checker,
definition,
// Collect the list of arguments documented in the docstring.
&FxHashSet::from_iter(args_sections.iter().filter_map(|section| {
match GOOGLE_ARGS_REGEX.captures(section.as_str()) {
Some(caps) => caps.get(1).map(|arg_name| arg_name.as_str()),
None => None,
}
})),
)
&args_sections
.iter()
.filter_map(
|section| match GOOGLE_ARGS_REGEX.captures(section.as_str()) {
Some(caps) => caps.get(1).map(|arg_name| arg_name.as_str()),
None => None,
},
)
.collect(),
);
}
fn parameters_section(checker: &mut Checker, definition: &Definition, context: &SectionContext) {
@@ -1461,7 +1458,7 @@ fn numpy_section(checker: &mut Checker, definition: &Definition, context: &Secti
));
}
}
checker.add_check(check)
checker.add_check(check);
}
}

View File

@@ -63,7 +63,7 @@ pub fn unused_variables(scope: &Scope, dummy_variable_rgx: &Regex) -> Vec<Check>
return checks;
}
for (&name, binding) in scope.values.iter() {
for (&name, binding) in &scope.values {
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Assignment)
&& !dummy_variable_rgx.is_match(name)
@@ -164,7 +164,7 @@ pub fn repeated_keys(
checks.push(Check::new(
CheckKind::MultiValueRepeatedKeyLiteral,
Range::from_located(k2),
))
));
}
}
(Some(DictionaryKey::Variable(v1)), Some(DictionaryKey::Variable(v2))) => {
@@ -172,7 +172,7 @@ pub fn repeated_keys(
checks.push(Check::new(
CheckKind::MultiValueRepeatedKeyVariable((*v2).to_string()),
Range::from_located(k2),
))
));
}
}
_ => {}
@@ -239,13 +239,13 @@ pub fn break_outside_loop(stmt: &Stmt, parents: &[&Stmt], parent_stack: &[usize]
}
}
if !allowed {
if allowed {
None
} else {
Some(Check::new(
CheckKind::BreakOutsideLoop,
Range::from_located(stmt),
))
} else {
None
}
}
@@ -279,12 +279,12 @@ pub fn continue_outside_loop(
}
}
if !allowed {
if allowed {
None
} else {
Some(Check::new(
CheckKind::ContinueOutsideLoop,
Range::from_located(stmt),
))
} else {
None
}
}

Some files were not shown because too many files have changed in this diff Show More