Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1822b57ed5 | ||
|
|
c679570041 | ||
|
|
edcb3a7217 | ||
|
|
6e43dc7270 | ||
|
|
570d0864f2 | ||
|
|
d22e96916c | ||
|
|
043d31dcdf | ||
|
|
1392e4cced | ||
|
|
59ee89a091 | ||
|
|
6a7c3728ee |
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.196
|
||||
rev: v0.0.198
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -750,7 +750,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.196-dev.0"
|
||||
version = "0.0.198-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1878,7 +1878,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.196"
|
||||
version = "0.0.198"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1945,7 +1945,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.196"
|
||||
version = "0.0.198"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1966,7 +1966,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.196"
|
||||
version = "0.0.198"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.196"
|
||||
version = "0.0.198"
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
@@ -45,7 +45,7 @@ path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
|
||||
ruff_macros = { version = "0.0.196", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.198", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
|
||||
|
||||
37
README.md
37
README.md
@@ -167,7 +167,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.196'
|
||||
rev: 'v0.0.198'
|
||||
hooks:
|
||||
- id: ruff
|
||||
# Respect `exclude` and `extend-exclude` settings.
|
||||
@@ -225,8 +225,8 @@ max-complexity = 10
|
||||
```
|
||||
|
||||
As an example, the following would configure Ruff to: (1) avoid checking for line-length
|
||||
violations (`E501`); (2), always autofix, but never remove unused imports (`F401`); and (3) ignore
|
||||
import-at-top-of-file errors (`E402`) in `__init__.py` files:
|
||||
violations (`E501`); (2) never remove unused imports (`F401`); and (3) ignore import-at-top-of-file
|
||||
errors (`E402`) in `__init__.py` files:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
@@ -236,8 +236,7 @@ select = ["E", "F"]
|
||||
# Never enforce `E501` (line length violations).
|
||||
ignore = ["E501"]
|
||||
|
||||
# Always autofix, but never try to fix `F401` (unused imports).
|
||||
fix = true
|
||||
# Never try to fix `F401` (unused imports).
|
||||
unfixable = ["F401"]
|
||||
|
||||
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
|
||||
@@ -257,6 +256,17 @@ select = ["E", "F", "Q"]
|
||||
docstring-quotes = "double"
|
||||
```
|
||||
|
||||
Ruff mirrors Flake8's error code system, in which each error code consists of a one-to-three letter
|
||||
prefix, followed by three digits (e.g., `F401`). The prefix indicates that "source" of the error
|
||||
code (e.g., `F` for Pyflakes, `E` for `pycodestyle`, `ANN` for `flake8-annotations`). The set of
|
||||
enabled errors is determined by the `select` and `ignore` options, which support both the full
|
||||
error code (e.g., `F401`) and the prefix (e.g., `F`).
|
||||
|
||||
As a special-case, Ruff also supports the `ALL` error code, which enables all error codes. Note that
|
||||
some of the `pydocstyle` error codes are conflicting (e.g., `D203` and `D211`) as they represent
|
||||
alternative docstring formats. Enabling `ALL` without further configuration may result in suboptimal
|
||||
behavior, especially for the `pydocstyle` plugin.
|
||||
|
||||
As an alternative to `pyproject.toml`, Ruff will also respect a `ruff.toml` file, which implements
|
||||
an equivalent schema (though the `[tool.ruff]` hierarchy can be omitted). For example, the above
|
||||
`pyproject.toml` described above would be represented via the following `ruff.toml`:
|
||||
@@ -984,6 +994,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
|
||||
| RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
|
||||
| RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
|
||||
| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | |
|
||||
| RUF004 | KeywordArgumentBeforeStarArgument | Keyword argument `...` must come after starred arguments | |
|
||||
| RUF100 | UnusedNOQA | Unused blanket `noqa` directive | 🛠 |
|
||||
|
||||
<!-- End auto-generated sections. -->
|
||||
@@ -1368,6 +1379,22 @@ src = ["src", "tests"]
|
||||
known-first-party = ["my_module1", "my_module2"]
|
||||
```
|
||||
|
||||
### Does Ruff support Jupyter Notebooks?
|
||||
|
||||
Ruff is integrated into [nbQA](https://github.com/nbQA-dev/nbQA), a tool for running linters and
|
||||
code formatters over Jupyter Notebooks.
|
||||
|
||||
After installing `ruff` and `nbqa`, you can run Ruff over a notebook like so:
|
||||
|
||||
```shell
|
||||
> nbqa ruff Untitled.ipynb
|
||||
Untitled.ipynb:cell_1:2:5: F841 Local variable `x` is assigned to but never used
|
||||
Untitled.ipynb:cell_2:1:1: E402 Module level import not at top of file
|
||||
Untitled.ipynb:cell_2:1:8: F401 `os` imported but unused
|
||||
Found 3 error(s).
|
||||
1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
### Does Ruff support NumPy- or Google-style docstrings?
|
||||
|
||||
Yes! To enable a specific docstring convention, start by enabling all `pydocstyle` error codes, and
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.196"
|
||||
version = "0.0.198"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.196"
|
||||
version = "0.0.198"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.196-dev.0"
|
||||
version = "0.0.198-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -4,11 +4,12 @@ use anyhow::Result;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::flake8_quotes::settings::Quote;
|
||||
use ruff::flake8_tidy_imports::settings::Strictness;
|
||||
use ruff::pydocstyle::settings::Convention;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
use ruff::{
|
||||
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_quotes, flake8_tidy_imports, mccabe,
|
||||
pep8_naming,
|
||||
pep8_naming, pydocstyle,
|
||||
};
|
||||
|
||||
use crate::black::Black;
|
||||
@@ -91,6 +92,7 @@ pub fn convert(
|
||||
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();
|
||||
let mut pydocstyle = pydocstyle::settings::Options::default();
|
||||
for (key, value) in flake8 {
|
||||
if let Some(value) = value {
|
||||
match key.as_str() {
|
||||
@@ -200,9 +202,12 @@ pub fn convert(
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
// flake8-docstrings
|
||||
"docstring-convention" => {
|
||||
// No-op (handled above).
|
||||
}
|
||||
"docstring-convention" => match value.trim() {
|
||||
"google" => pydocstyle.convention = Some(Convention::Google),
|
||||
"numpy" => pydocstyle.convention = Some(Convention::Numpy),
|
||||
"pep257" | "all" => pydocstyle.convention = None,
|
||||
_ => eprintln!("Unexpected '{key}' value: {value}"),
|
||||
},
|
||||
// mccabe
|
||||
"max-complexity" | "max_complexity" => match value.clone().parse::<usize>() {
|
||||
Ok(max_complexity) => mccabe.max_complexity = Some(max_complexity),
|
||||
@@ -247,6 +252,9 @@ pub fn convert(
|
||||
if pep8_naming != pep8_naming::settings::Options::default() {
|
||||
options.pep8_naming = Some(pep8_naming);
|
||||
}
|
||||
if pydocstyle != pydocstyle::settings::Options::default() {
|
||||
options.pydocstyle = Some(pydocstyle);
|
||||
}
|
||||
|
||||
// Extract any settings from the existing `pyproject.toml`.
|
||||
if let Some(black) = black {
|
||||
@@ -271,9 +279,10 @@ mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::flake8_quotes;
|
||||
use ruff::pydocstyle::settings::Convention;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
use ruff::{flake8_quotes, pydocstyle};
|
||||
|
||||
use crate::converter::convert;
|
||||
use crate::plugin::Plugin;
|
||||
@@ -659,7 +668,9 @@ mod tests {
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pydocstyle: None,
|
||||
pydocstyle: Some(pydocstyle::settings::Options {
|
||||
convention: Some(Convention::Numpy),
|
||||
}),
|
||||
pyupgrade: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
13
resources/test/fixtures/ruff/RUF004.py
vendored
Normal file
13
resources/test/fixtures/ruff/RUF004.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
def f(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
a = (1, 2)
|
||||
b = (3, 4)
|
||||
c = (5, 6)
|
||||
|
||||
f(a, b)
|
||||
f(a, kw=b)
|
||||
f(*a, kw=b)
|
||||
f(kw=a, *b)
|
||||
f(kw=a, *b, *c)
|
||||
@@ -377,6 +377,7 @@
|
||||
"A001",
|
||||
"A002",
|
||||
"A003",
|
||||
"ALL",
|
||||
"ANN",
|
||||
"ANN0",
|
||||
"ANN00",
|
||||
@@ -805,6 +806,7 @@
|
||||
"RUF001",
|
||||
"RUF002",
|
||||
"RUF003",
|
||||
"RUF004",
|
||||
"RUF1",
|
||||
"RUF10",
|
||||
"RUF100",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.196"
|
||||
version = "0.0.198"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -13,6 +13,8 @@ use itertools::Itertools;
|
||||
use ruff::checks::{CheckCode, PREFIX_REDIRECTS};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
const ALL: &str = "ALL";
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
@@ -34,9 +36,15 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
let code_suffix_len = code_str.len() - code_prefix_len;
|
||||
for i in 0..=code_suffix_len {
|
||||
let prefix = code_str[..code_prefix_len + i].to_string();
|
||||
let entry = prefix_to_codes.entry(prefix).or_default();
|
||||
entry.insert(check_code.clone());
|
||||
prefix_to_codes
|
||||
.entry(prefix)
|
||||
.or_default()
|
||||
.insert(check_code.clone());
|
||||
}
|
||||
prefix_to_codes
|
||||
.entry(ALL.to_string())
|
||||
.or_default()
|
||||
.insert(check_code.clone());
|
||||
}
|
||||
|
||||
// Add any prefix aliases (e.g., "U" to "UP").
|
||||
@@ -79,6 +87,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.derive("Eq")
|
||||
.derive("PartialOrd")
|
||||
.derive("Ord")
|
||||
.push_variant(Variant::new("None"))
|
||||
.push_variant(Variant::new("Zero"))
|
||||
.push_variant(Variant::new("One"))
|
||||
.push_variant(Variant::new("Two"))
|
||||
@@ -129,14 +138,18 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.line("#[allow(clippy::match_same_arms)]")
|
||||
.line("match self {");
|
||||
for prefix in prefix_to_codes.keys() {
|
||||
let num_numeric = prefix.chars().filter(|char| char.is_numeric()).count();
|
||||
let specificity = match num_numeric {
|
||||
0 => "Zero",
|
||||
1 => "One",
|
||||
2 => "Two",
|
||||
3 => "Three",
|
||||
4 => "Four",
|
||||
_ => panic!("Invalid prefix: {prefix}"),
|
||||
let specificity = if prefix == "ALL" {
|
||||
"None"
|
||||
} else {
|
||||
let num_numeric = prefix.chars().filter(|char| char.is_numeric()).count();
|
||||
match num_numeric {
|
||||
0 => "Zero",
|
||||
1 => "One",
|
||||
2 => "Two",
|
||||
3 => "Three",
|
||||
4 => "Four",
|
||||
_ => panic!("Invalid prefix: {prefix}"),
|
||||
}
|
||||
};
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => SuffixLength::{specificity},"
|
||||
|
||||
@@ -5,7 +5,9 @@ use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use ruff::code_gen::SourceGenerator;
|
||||
use ruff::source_code_generator::SourceCodeGenerator;
|
||||
use ruff::source_code_locator::SourceCodeLocator;
|
||||
use ruff::source_code_style::SourceCodeStyleDetector;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
#[derive(Args)]
|
||||
@@ -18,7 +20,9 @@ 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())?;
|
||||
let mut generator = SourceGenerator::new();
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_suite(&python_ast);
|
||||
println!("{}", generator.generate()?);
|
||||
Ok(())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.196"
|
||||
version = "0.0.198"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -33,14 +33,16 @@ use crate::python::typing::SubscriptKind;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::vendored::cformat::{CFormatError, CFormatErrorType};
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::vendor::cformat::{CFormatError, CFormatErrorType};
|
||||
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
|
||||
use crate::{
|
||||
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
|
||||
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez,
|
||||
flake8_debugger, flake8_errmsg, flake8_import_conventions, flake8_print, flake8_return,
|
||||
flake8_simplify, flake8_tidy_imports, flake8_unused_arguments, mccabe, noqa, pandas_vet,
|
||||
pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, visibility,
|
||||
pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||
visibility,
|
||||
};
|
||||
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
@@ -56,6 +58,7 @@ pub struct Checker<'a> {
|
||||
pub(crate) settings: &'a Settings,
|
||||
pub(crate) noqa_line_for: &'a IntMap<usize, usize>,
|
||||
pub(crate) locator: &'a SourceCodeLocator<'a>,
|
||||
pub(crate) style: &'a SourceCodeStyleDetector<'a>,
|
||||
// Computed checks.
|
||||
checks: Vec<Check>,
|
||||
// Function and class definition tracking (e.g., for docstring enforcement).
|
||||
@@ -107,6 +110,7 @@ impl<'a> Checker<'a> {
|
||||
noqa: flags::Noqa,
|
||||
path: &'a Path,
|
||||
locator: &'a SourceCodeLocator,
|
||||
style: &'a SourceCodeStyleDetector,
|
||||
) -> Checker<'a> {
|
||||
Checker {
|
||||
settings,
|
||||
@@ -115,6 +119,7 @@ impl<'a> Checker<'a> {
|
||||
noqa,
|
||||
path,
|
||||
locator,
|
||||
style,
|
||||
checks: vec![],
|
||||
definitions: vec![],
|
||||
deletions: FxHashSet::default(),
|
||||
@@ -1642,27 +1647,34 @@ where
|
||||
}
|
||||
|
||||
// pyupgrade
|
||||
if self.settings.enabled.contains(&CheckCode::UP003) {
|
||||
pyupgrade::plugins::type_of_primitive(self, expr, func, args);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP005) {
|
||||
pyupgrade::plugins::deprecated_unittest_alias(self, func);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP008) {
|
||||
pyupgrade::plugins::super_call_with_parameters(self, expr, func, args);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP012) {
|
||||
pyupgrade::plugins::unnecessary_encode_utf8(self, expr, func, args, keywords);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP015) {
|
||||
pyupgrade::plugins::redundant_open_modes(self, expr);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP016) {
|
||||
pyupgrade::plugins::remove_six_compat(self, expr);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP018) {
|
||||
pyupgrade::plugins::native_literals(self, expr, func, args, keywords);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP020) {
|
||||
pyupgrade::plugins::open_alias(self, expr, func);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP021) {
|
||||
pyupgrade::plugins::replace_universal_newlines(self, expr, keywords);
|
||||
}
|
||||
|
||||
// flake8-super
|
||||
if self.settings.enabled.contains(&CheckCode::UP008) {
|
||||
pyupgrade::plugins::super_call_with_parameters(self, expr, func, args);
|
||||
}
|
||||
|
||||
// flake8-print
|
||||
if self.settings.enabled.contains(&CheckCode::T201)
|
||||
|| self.settings.enabled.contains(&CheckCode::T203)
|
||||
@@ -1725,7 +1737,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C401) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_set(
|
||||
expr,
|
||||
@@ -1739,7 +1750,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C402) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_dict(
|
||||
expr,
|
||||
@@ -1753,7 +1763,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C403) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_comprehension_set(
|
||||
@@ -1769,7 +1778,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C404) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_list_comprehension_dict(
|
||||
@@ -1785,7 +1793,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C405) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_set(
|
||||
expr,
|
||||
@@ -1799,7 +1806,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C406) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_dict(
|
||||
expr,
|
||||
@@ -1813,7 +1819,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C408) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_collection_call(
|
||||
expr,
|
||||
@@ -1827,7 +1832,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C409) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_literal_within_tuple_call(
|
||||
@@ -1842,7 +1846,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C410) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_literal_within_list_call(
|
||||
@@ -1857,7 +1860,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C411) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_list_call(
|
||||
expr,
|
||||
@@ -1870,7 +1872,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C413) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_call_around_sorted(
|
||||
@@ -1885,7 +1886,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C414) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_double_cast_or_process(
|
||||
@@ -1897,7 +1897,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C415) {
|
||||
if let Some(check) =
|
||||
flake8_comprehensions::checks::unnecessary_subscript_reversal(
|
||||
@@ -1909,7 +1908,6 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C417) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_map(
|
||||
func,
|
||||
@@ -1920,19 +1918,6 @@ where
|
||||
};
|
||||
}
|
||||
|
||||
// pyupgrade
|
||||
if self.settings.enabled.contains(&CheckCode::UP003) {
|
||||
pyupgrade::plugins::type_of_primitive(self, expr, func, args);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::UP015) {
|
||||
pyupgrade::plugins::redundant_open_modes(self, expr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::UP020) {
|
||||
pyupgrade::plugins::open_alias(self, expr, func);
|
||||
}
|
||||
|
||||
// flake8-boolean-trap
|
||||
if self.settings.enabled.contains(&CheckCode::FBT003) {
|
||||
flake8_boolean_trap::plugins::check_boolean_positional_value_in_function_call(
|
||||
@@ -1963,7 +1948,6 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::PD002) {
|
||||
self.add_checks(pandas_vet::checks::inplace_argument(keywords).into_iter());
|
||||
}
|
||||
|
||||
for (code, name) in vec![
|
||||
(CheckCode::PD003, "isnull"),
|
||||
(CheckCode::PD004, "notnull"),
|
||||
@@ -1980,7 +1964,6 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::PD015) {
|
||||
if let Some(check) = pandas_vet::checks::use_of_pd_merge(func) {
|
||||
self.add_check(check);
|
||||
@@ -2074,6 +2057,14 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::PLR1722) {
|
||||
pylint::plugins::use_sys_exit(self, func);
|
||||
}
|
||||
|
||||
// ruff
|
||||
if self.settings.enabled.contains(&CheckCode::RUF004) {
|
||||
self.add_checks(
|
||||
ruff::checks::keyword_argument_before_star_argument(args, keywords)
|
||||
.into_iter(),
|
||||
);
|
||||
}
|
||||
}
|
||||
ExprKind::Dict { keys, .. } => {
|
||||
let check_repeated_literals = self.settings.enabled.contains(&CheckCode::F601);
|
||||
@@ -3926,16 +3917,26 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_ast(
|
||||
python_ast: &Suite,
|
||||
locator: &SourceCodeLocator,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
noqa: flags::Noqa,
|
||||
path: &Path,
|
||||
) -> Vec<Check> {
|
||||
let mut checker = Checker::new(settings, noqa_line_for, autofix, noqa, path, locator);
|
||||
let mut checker = Checker::new(
|
||||
settings,
|
||||
noqa_line_for,
|
||||
autofix,
|
||||
noqa,
|
||||
path,
|
||||
locator,
|
||||
stylist,
|
||||
);
|
||||
checker.push_scope(Scope::new(ScopeKind::Module));
|
||||
checker.bind_builtins();
|
||||
|
||||
|
||||
@@ -328,6 +328,7 @@ pub enum CheckCode {
|
||||
RUF001,
|
||||
RUF002,
|
||||
RUF003,
|
||||
RUF004,
|
||||
RUF100,
|
||||
// pygrep-hooks
|
||||
PGH001,
|
||||
@@ -955,6 +956,7 @@ pub enum CheckKind {
|
||||
AmbiguousUnicodeCharacterString(char, char),
|
||||
AmbiguousUnicodeCharacterDocstring(char, char),
|
||||
AmbiguousUnicodeCharacterComment(char, char),
|
||||
KeywordArgumentBeforeStarArgument(String),
|
||||
UnusedNOQA(Option<UnusedCodes>),
|
||||
// flake8-datetimez
|
||||
CallDatetimeWithoutTzinfo,
|
||||
@@ -1360,6 +1362,7 @@ impl CheckCode {
|
||||
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
|
||||
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
|
||||
CheckCode::RUF003 => CheckKind::AmbiguousUnicodeCharacterComment('𝐁', 'B'),
|
||||
CheckCode::RUF004 => CheckKind::KeywordArgumentBeforeStarArgument("...".to_string()),
|
||||
CheckCode::RUF100 => CheckKind::UnusedNOQA(None),
|
||||
}
|
||||
}
|
||||
@@ -1612,6 +1615,7 @@ impl CheckCode {
|
||||
CheckCode::RUF001 => CheckCategory::Ruff,
|
||||
CheckCode::RUF002 => CheckCategory::Ruff,
|
||||
CheckCode::RUF003 => CheckCategory::Ruff,
|
||||
CheckCode::RUF004 => CheckCategory::Ruff,
|
||||
CheckCode::RUF100 => CheckCategory::Ruff,
|
||||
CheckCode::S101 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::S102 => CheckCategory::Flake8Bandit,
|
||||
@@ -1980,6 +1984,7 @@ impl CheckKind {
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
|
||||
CheckKind::AmbiguousUnicodeCharacterComment(..) => &CheckCode::RUF003,
|
||||
CheckKind::KeywordArgumentBeforeStarArgument(..) => &CheckCode::RUF004,
|
||||
CheckKind::UnusedNOQA(..) => &CheckCode::RUF100,
|
||||
}
|
||||
}
|
||||
@@ -2882,6 +2887,9 @@ impl CheckKind {
|
||||
'{representant}'?)"
|
||||
)
|
||||
}
|
||||
CheckKind::KeywordArgumentBeforeStarArgument(name) => {
|
||||
format!("Keyword argument `{name}` must come after starred arguments")
|
||||
}
|
||||
CheckKind::UnusedNOQA(codes) => match codes {
|
||||
None => "Unused blanket `noqa` directive".to_string(),
|
||||
Some(codes) => {
|
||||
|
||||
@@ -28,6 +28,7 @@ pub enum CheckCodePrefix {
|
||||
A001,
|
||||
A002,
|
||||
A003,
|
||||
ALL,
|
||||
ANN,
|
||||
ANN0,
|
||||
ANN00,
|
||||
@@ -456,6 +457,7 @@ pub enum CheckCodePrefix {
|
||||
RUF001,
|
||||
RUF002,
|
||||
RUF003,
|
||||
RUF004,
|
||||
RUF1,
|
||||
RUF10,
|
||||
RUF100,
|
||||
@@ -558,6 +560,7 @@ pub enum CheckCodePrefix {
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SuffixLength {
|
||||
None,
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
@@ -575,6 +578,297 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::A001 => vec![CheckCode::A001],
|
||||
CheckCodePrefix::A002 => vec![CheckCode::A002],
|
||||
CheckCodePrefix::A003 => vec![CheckCode::A003],
|
||||
CheckCodePrefix::ALL => vec![
|
||||
CheckCode::E401,
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E721,
|
||||
CheckCode::E722,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
CheckCode::W292,
|
||||
CheckCode::W605,
|
||||
CheckCode::F401,
|
||||
CheckCode::F402,
|
||||
CheckCode::F403,
|
||||
CheckCode::F404,
|
||||
CheckCode::F405,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F501,
|
||||
CheckCode::F502,
|
||||
CheckCode::F503,
|
||||
CheckCode::F504,
|
||||
CheckCode::F505,
|
||||
CheckCode::F506,
|
||||
CheckCode::F507,
|
||||
CheckCode::F508,
|
||||
CheckCode::F509,
|
||||
CheckCode::F521,
|
||||
CheckCode::F522,
|
||||
CheckCode::F523,
|
||||
CheckCode::F524,
|
||||
CheckCode::F525,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
CheckCode::F621,
|
||||
CheckCode::F622,
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
CheckCode::F701,
|
||||
CheckCode::F702,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F811,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F842,
|
||||
CheckCode::F901,
|
||||
CheckCode::PLC0414,
|
||||
CheckCode::PLC2201,
|
||||
CheckCode::PLC3002,
|
||||
CheckCode::PLE0117,
|
||||
CheckCode::PLE0118,
|
||||
CheckCode::PLE1142,
|
||||
CheckCode::PLR0206,
|
||||
CheckCode::PLR0402,
|
||||
CheckCode::PLR1701,
|
||||
CheckCode::PLR1722,
|
||||
CheckCode::PLW0120,
|
||||
CheckCode::PLW0602,
|
||||
CheckCode::A001,
|
||||
CheckCode::A002,
|
||||
CheckCode::A003,
|
||||
CheckCode::B002,
|
||||
CheckCode::B003,
|
||||
CheckCode::B004,
|
||||
CheckCode::B005,
|
||||
CheckCode::B006,
|
||||
CheckCode::B007,
|
||||
CheckCode::B008,
|
||||
CheckCode::B009,
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B012,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
CheckCode::B016,
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
CheckCode::B019,
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B023,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
CheckCode::B904,
|
||||
CheckCode::B905,
|
||||
CheckCode::BLE001,
|
||||
CheckCode::C400,
|
||||
CheckCode::C401,
|
||||
CheckCode::C402,
|
||||
CheckCode::C403,
|
||||
CheckCode::C404,
|
||||
CheckCode::C405,
|
||||
CheckCode::C406,
|
||||
CheckCode::C408,
|
||||
CheckCode::C409,
|
||||
CheckCode::C410,
|
||||
CheckCode::C411,
|
||||
CheckCode::C413,
|
||||
CheckCode::C414,
|
||||
CheckCode::C415,
|
||||
CheckCode::C416,
|
||||
CheckCode::C417,
|
||||
CheckCode::T100,
|
||||
CheckCode::C901,
|
||||
CheckCode::TID252,
|
||||
CheckCode::RET501,
|
||||
CheckCode::RET502,
|
||||
CheckCode::RET503,
|
||||
CheckCode::RET504,
|
||||
CheckCode::RET505,
|
||||
CheckCode::RET506,
|
||||
CheckCode::RET507,
|
||||
CheckCode::RET508,
|
||||
CheckCode::T201,
|
||||
CheckCode::T203,
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
CheckCode::ANN001,
|
||||
CheckCode::ANN002,
|
||||
CheckCode::ANN003,
|
||||
CheckCode::ANN101,
|
||||
CheckCode::ANN102,
|
||||
CheckCode::ANN201,
|
||||
CheckCode::ANN202,
|
||||
CheckCode::ANN204,
|
||||
CheckCode::ANN205,
|
||||
CheckCode::ANN206,
|
||||
CheckCode::ANN401,
|
||||
CheckCode::YTT101,
|
||||
CheckCode::YTT102,
|
||||
CheckCode::YTT103,
|
||||
CheckCode::YTT201,
|
||||
CheckCode::YTT202,
|
||||
CheckCode::YTT203,
|
||||
CheckCode::YTT204,
|
||||
CheckCode::YTT301,
|
||||
CheckCode::YTT302,
|
||||
CheckCode::YTT303,
|
||||
CheckCode::SIM118,
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
CheckCode::UP004,
|
||||
CheckCode::UP005,
|
||||
CheckCode::UP006,
|
||||
CheckCode::UP007,
|
||||
CheckCode::UP008,
|
||||
CheckCode::UP009,
|
||||
CheckCode::UP010,
|
||||
CheckCode::UP011,
|
||||
CheckCode::UP012,
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
CheckCode::UP018,
|
||||
CheckCode::UP019,
|
||||
CheckCode::UP020,
|
||||
CheckCode::UP021,
|
||||
CheckCode::D100,
|
||||
CheckCode::D101,
|
||||
CheckCode::D102,
|
||||
CheckCode::D103,
|
||||
CheckCode::D104,
|
||||
CheckCode::D105,
|
||||
CheckCode::D106,
|
||||
CheckCode::D107,
|
||||
CheckCode::D200,
|
||||
CheckCode::D201,
|
||||
CheckCode::D202,
|
||||
CheckCode::D203,
|
||||
CheckCode::D204,
|
||||
CheckCode::D205,
|
||||
CheckCode::D206,
|
||||
CheckCode::D207,
|
||||
CheckCode::D208,
|
||||
CheckCode::D209,
|
||||
CheckCode::D210,
|
||||
CheckCode::D211,
|
||||
CheckCode::D212,
|
||||
CheckCode::D213,
|
||||
CheckCode::D214,
|
||||
CheckCode::D215,
|
||||
CheckCode::D300,
|
||||
CheckCode::D301,
|
||||
CheckCode::D400,
|
||||
CheckCode::D402,
|
||||
CheckCode::D403,
|
||||
CheckCode::D404,
|
||||
CheckCode::D405,
|
||||
CheckCode::D406,
|
||||
CheckCode::D407,
|
||||
CheckCode::D408,
|
||||
CheckCode::D409,
|
||||
CheckCode::D410,
|
||||
CheckCode::D411,
|
||||
CheckCode::D412,
|
||||
CheckCode::D413,
|
||||
CheckCode::D414,
|
||||
CheckCode::D415,
|
||||
CheckCode::D416,
|
||||
CheckCode::D417,
|
||||
CheckCode::D418,
|
||||
CheckCode::D419,
|
||||
CheckCode::N801,
|
||||
CheckCode::N802,
|
||||
CheckCode::N803,
|
||||
CheckCode::N804,
|
||||
CheckCode::N805,
|
||||
CheckCode::N806,
|
||||
CheckCode::N807,
|
||||
CheckCode::N811,
|
||||
CheckCode::N812,
|
||||
CheckCode::N813,
|
||||
CheckCode::N814,
|
||||
CheckCode::N815,
|
||||
CheckCode::N816,
|
||||
CheckCode::N817,
|
||||
CheckCode::N818,
|
||||
CheckCode::I001,
|
||||
CheckCode::ERA001,
|
||||
CheckCode::S101,
|
||||
CheckCode::S102,
|
||||
CheckCode::S104,
|
||||
CheckCode::S105,
|
||||
CheckCode::S106,
|
||||
CheckCode::S107,
|
||||
CheckCode::FBT001,
|
||||
CheckCode::FBT002,
|
||||
CheckCode::FBT003,
|
||||
CheckCode::ARG001,
|
||||
CheckCode::ARG002,
|
||||
CheckCode::ARG003,
|
||||
CheckCode::ARG004,
|
||||
CheckCode::ARG005,
|
||||
CheckCode::ICN001,
|
||||
CheckCode::DTZ001,
|
||||
CheckCode::DTZ002,
|
||||
CheckCode::DTZ003,
|
||||
CheckCode::DTZ004,
|
||||
CheckCode::DTZ005,
|
||||
CheckCode::DTZ006,
|
||||
CheckCode::DTZ007,
|
||||
CheckCode::DTZ011,
|
||||
CheckCode::DTZ012,
|
||||
CheckCode::RUF001,
|
||||
CheckCode::RUF002,
|
||||
CheckCode::RUF003,
|
||||
CheckCode::RUF004,
|
||||
CheckCode::RUF100,
|
||||
CheckCode::PGH001,
|
||||
CheckCode::PGH002,
|
||||
CheckCode::PGH003,
|
||||
CheckCode::PD002,
|
||||
CheckCode::PD003,
|
||||
CheckCode::PD004,
|
||||
CheckCode::PD007,
|
||||
CheckCode::PD008,
|
||||
CheckCode::PD009,
|
||||
CheckCode::PD010,
|
||||
CheckCode::PD011,
|
||||
CheckCode::PD012,
|
||||
CheckCode::PD013,
|
||||
CheckCode::PD015,
|
||||
CheckCode::PD901,
|
||||
CheckCode::EM101,
|
||||
CheckCode::EM102,
|
||||
CheckCode::EM103,
|
||||
],
|
||||
CheckCodePrefix::ANN => vec![
|
||||
CheckCode::ANN001,
|
||||
CheckCode::ANN002,
|
||||
@@ -2025,13 +2319,25 @@ impl CheckCodePrefix {
|
||||
CheckCode::RUF001,
|
||||
CheckCode::RUF002,
|
||||
CheckCode::RUF003,
|
||||
CheckCode::RUF004,
|
||||
CheckCode::RUF100,
|
||||
],
|
||||
CheckCodePrefix::RUF0 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
|
||||
CheckCodePrefix::RUF00 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
|
||||
CheckCodePrefix::RUF0 => vec![
|
||||
CheckCode::RUF001,
|
||||
CheckCode::RUF002,
|
||||
CheckCode::RUF003,
|
||||
CheckCode::RUF004,
|
||||
],
|
||||
CheckCodePrefix::RUF00 => vec![
|
||||
CheckCode::RUF001,
|
||||
CheckCode::RUF002,
|
||||
CheckCode::RUF003,
|
||||
CheckCode::RUF004,
|
||||
],
|
||||
CheckCodePrefix::RUF001 => vec![CheckCode::RUF001],
|
||||
CheckCodePrefix::RUF002 => vec![CheckCode::RUF002],
|
||||
CheckCodePrefix::RUF003 => vec![CheckCode::RUF003],
|
||||
CheckCodePrefix::RUF004 => vec![CheckCode::RUF004],
|
||||
CheckCodePrefix::RUF1 => vec![CheckCode::RUF100],
|
||||
CheckCodePrefix::RUF10 => vec![CheckCode::RUF100],
|
||||
CheckCodePrefix::RUF100 => vec![CheckCode::RUF100],
|
||||
@@ -2478,6 +2784,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::A001 => SuffixLength::Three,
|
||||
CheckCodePrefix::A002 => SuffixLength::Three,
|
||||
CheckCodePrefix::A003 => SuffixLength::Three,
|
||||
CheckCodePrefix::ALL => SuffixLength::None,
|
||||
CheckCodePrefix::ANN => SuffixLength::Zero,
|
||||
CheckCodePrefix::ANN0 => SuffixLength::One,
|
||||
CheckCodePrefix::ANN00 => SuffixLength::Two,
|
||||
@@ -2906,6 +3213,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::RUF001 => SuffixLength::Three,
|
||||
CheckCodePrefix::RUF002 => SuffixLength::Three,
|
||||
CheckCodePrefix::RUF003 => SuffixLength::Three,
|
||||
CheckCodePrefix::RUF004 => SuffixLength::Three,
|
||||
CheckCodePrefix::RUF1 => SuffixLength::One,
|
||||
CheckCodePrefix::RUF10 => SuffixLength::Two,
|
||||
CheckCodePrefix::RUF100 => SuffixLength::Three,
|
||||
@@ -3010,6 +3318,7 @@ impl CheckCodePrefix {
|
||||
|
||||
pub const CATEGORIES: &[CheckCodePrefix] = &[
|
||||
CheckCodePrefix::A,
|
||||
CheckCodePrefix::ALL,
|
||||
CheckCodePrefix::ANN,
|
||||
CheckCodePrefix::ARG,
|
||||
CheckCodePrefix::B,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
|
||||
fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
Stmt::new(
|
||||
@@ -47,7 +47,8 @@ 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();
|
||||
let mut generator =
|
||||
SourceCodeGenerator::new(checker.style.indentation(), checker.style.quote());
|
||||
generator.unparse_stmt(&assertion_error(msg));
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
|
||||
fn type_pattern(elts: Vec<&Expr>) -> Expr {
|
||||
Expr::new(
|
||||
@@ -54,7 +54,8 @@ fn duplicate_handler_exceptions<'a>(
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator =
|
||||
SourceCodeGenerator::new(checker.style.indentation(), checker.style.quote());
|
||||
if unique_elts.len() == 1 {
|
||||
generator.unparse_expr(unique_elts[0], 0);
|
||||
} else {
|
||||
|
||||
@@ -4,9 +4,9 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
|
||||
fn attribute(value: &Expr, attr: &str) -> Expr {
|
||||
Expr::new(
|
||||
@@ -46,7 +46,8 @@ pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
|
||||
|
||||
let mut check = Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator =
|
||||
SourceCodeGenerator::new(checker.style.indentation(), checker.style.quote());
|
||||
generator.unparse_expr(&attribute(obj, value), 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
|
||||
/// B013
|
||||
pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[Excepthandler]) {
|
||||
@@ -23,7 +23,8 @@ pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[E
|
||||
Range::from_located(type_),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator =
|
||||
SourceCodeGenerator::new(checker.style.indentation(), checker.style.quote());
|
||||
generator.unparse_expr(elt, 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
|
||||
@@ -6,11 +6,17 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
fn assignment(obj: &Expr, name: &str, value: &Expr) -> Result<String> {
|
||||
fn assignment(
|
||||
obj: &Expr,
|
||||
name: &str,
|
||||
value: &Expr,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<String> {
|
||||
let stmt = Stmt::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
@@ -28,7 +34,7 @@ fn assignment(obj: &Expr, name: &str, value: &Expr) -> Result<String> {
|
||||
type_comment: None,
|
||||
},
|
||||
);
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_stmt(&stmt);
|
||||
generator.generate().map_err(std::convert::Into::into)
|
||||
}
|
||||
@@ -63,7 +69,7 @@ pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
|
||||
if expr == child.as_ref() {
|
||||
let mut check = Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
match assignment(obj, name, value) {
|
||||
match assignment(obj, name, value, checker.style) {
|
||||
Ok(content) => check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
|
||||
@@ -25,7 +25,7 @@ expression: checks
|
||||
row: 10
|
||||
column: 12
|
||||
fix:
|
||||
content: "raise AssertionError('message')"
|
||||
content: "raise AssertionError(\"message\")"
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
|
||||
@@ -24,7 +24,6 @@ mod checkers;
|
||||
pub mod checks;
|
||||
pub mod checks_gen;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
mod cst;
|
||||
mod directives;
|
||||
mod docstrings;
|
||||
@@ -60,7 +59,7 @@ mod pandas_vet;
|
||||
pub mod pep8_naming;
|
||||
pub mod printer;
|
||||
mod pycodestyle;
|
||||
mod pydocstyle;
|
||||
pub mod pydocstyle;
|
||||
mod pyflakes;
|
||||
mod pygrep_hooks;
|
||||
mod pylint;
|
||||
@@ -70,8 +69,10 @@ pub mod resolver;
|
||||
mod ruff;
|
||||
mod rustpython_helpers;
|
||||
pub mod settings;
|
||||
pub mod source_code_generator;
|
||||
pub mod source_code_locator;
|
||||
mod vendored;
|
||||
pub mod source_code_style;
|
||||
mod vendor;
|
||||
pub mod visibility;
|
||||
|
||||
cfg_if! {
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::rustpython_helpers::tokenize;
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::{flags, pyproject, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::{directives, packages, resolver};
|
||||
|
||||
/// Load the relevant `Settings` for a given `Path`.
|
||||
@@ -38,9 +39,12 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(contents, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
@@ -55,6 +59,7 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
&settings,
|
||||
autofix.into(),
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::settings::configuration::Configuration;
|
||||
use crate::settings::options::Options;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const TYPES: &'static str = r#"
|
||||
@@ -69,9 +70,12 @@ pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(contents, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(&tokens, &locator, directives::Flags::empty());
|
||||
|
||||
@@ -82,6 +86,7 @@ pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
|
||||
contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
&settings,
|
||||
flags::Autofix::Enabled,
|
||||
|
||||
@@ -17,12 +17,13 @@ use crate::checkers::lines::check_lines;
|
||||
use crate::checkers::noqa::check_noqa;
|
||||
use crate::checkers::tokens::check_tokens;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::directives::Directives;
|
||||
use crate::message::{Message, Source};
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::{cache, directives, fs, rustpython_helpers};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -53,6 +54,7 @@ pub(crate) fn check_path(
|
||||
contents: &str,
|
||||
tokens: Vec<LexResult>,
|
||||
locator: &SourceCodeLocator,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
directives: &Directives,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
@@ -89,6 +91,7 @@ pub(crate) fn check_path(
|
||||
checks.extend(check_ast(
|
||||
&python_ast,
|
||||
locator,
|
||||
stylist,
|
||||
&directives.noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
@@ -216,9 +219,12 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
@@ -233,6 +239,7 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
settings,
|
||||
flags::Autofix::Disabled,
|
||||
@@ -259,9 +266,15 @@ pub fn autoformat_path(path: &Path, settings: &Settings) -> Result<()> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
|
||||
// Generate the AST.
|
||||
let python_ast = rustpython_helpers::parse_program_tokens(tokens, "<filename>")?;
|
||||
let mut generator = SourceGenerator::default();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_suite(&python_ast);
|
||||
write(path, generator.generate()?)?;
|
||||
|
||||
@@ -318,9 +331,12 @@ fn lint(
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
@@ -335,6 +351,7 @@ fn lint(
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
settings,
|
||||
autofix.into(),
|
||||
@@ -381,6 +398,7 @@ pub fn test_path(path: &Path, settings: &Settings) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
@@ -392,6 +410,7 @@ pub fn test_path(path: &Path, settings: &Settings) -> Result<Vec<Check>> {
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
settings,
|
||||
flags::Autofix::Enabled,
|
||||
|
||||
@@ -14,6 +14,7 @@ mod tests {
|
||||
use crate::linter::check_path;
|
||||
use crate::settings::flags;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::{directives, rustpython_helpers, settings};
|
||||
|
||||
fn check_code(contents: &str, expected: &[CheckCode]) -> Result<()> {
|
||||
@@ -21,6 +22,7 @@ mod tests {
|
||||
let settings = settings::Settings::for_rules(CheckCodePrefix::PD.codes());
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
@@ -32,6 +34,7 @@ mod tests {
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
&settings,
|
||||
flags::Autofix::Enabled,
|
||||
|
||||
@@ -12,9 +12,15 @@ use crate::ast::whitespace::leading_space;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind, RejectedCmpop};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
fn compare(left: &Expr, ops: &[Cmpop], comparators: &[Expr]) -> Option<String> {
|
||||
fn compare(
|
||||
left: &Expr,
|
||||
ops: &[Cmpop],
|
||||
comparators: &[Expr],
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Option<String> {
|
||||
let cmp = Expr::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
@@ -24,7 +30,7 @@ fn compare(left: &Expr, ops: &[Cmpop], comparators: &[Expr]) -> Option<String> {
|
||||
comparators: comparators.to_vec(),
|
||||
},
|
||||
);
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_expr(&cmp, 0);
|
||||
generator.generate().ok()
|
||||
}
|
||||
@@ -194,7 +200,7 @@ pub fn literal_comparisons(
|
||||
.map(|(idx, op)| bad_ops.get(&idx).unwrap_or(op))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(content) = compare(left, &ops, comparators) {
|
||||
if let Some(content) = compare(left, &ops, comparators, checker.style) {
|
||||
for check in &mut checks {
|
||||
check.amend(Fix::replacement(
|
||||
content.to_string(),
|
||||
@@ -233,7 +239,9 @@ pub fn not_tests(
|
||||
let mut check =
|
||||
Check::new(CheckKind::NotInTest, Range::from_located(operand));
|
||||
if checker.patch(check.kind.code()) && should_fix {
|
||||
if let Some(content) = compare(left, &[Cmpop::NotIn], comparators) {
|
||||
if let Some(content) =
|
||||
compare(left, &[Cmpop::NotIn], comparators, checker.style)
|
||||
{
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
@@ -249,7 +257,9 @@ pub fn not_tests(
|
||||
let mut check =
|
||||
Check::new(CheckKind::NotIsTest, Range::from_located(operand));
|
||||
if checker.patch(check.kind.code()) && should_fix {
|
||||
if let Some(content) = compare(left, &[Cmpop::IsNot], comparators) {
|
||||
if let Some(content) =
|
||||
compare(left, &[Cmpop::IsNot], comparators, checker.style)
|
||||
{
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
@@ -267,7 +277,12 @@ pub fn not_tests(
|
||||
}
|
||||
}
|
||||
|
||||
fn function(name: &str, args: &Arguments, body: &Expr) -> Result<String> {
|
||||
fn function(
|
||||
name: &str,
|
||||
args: &Arguments,
|
||||
body: &Expr,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<String> {
|
||||
let body = Stmt::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
@@ -287,7 +302,7 @@ fn function(name: &str, args: &Arguments, body: &Expr) -> Result<String> {
|
||||
type_comment: None,
|
||||
},
|
||||
);
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_stmt(&func);
|
||||
Ok(generator.generate()?)
|
||||
}
|
||||
@@ -301,7 +316,7 @@ pub fn do_not_assign_lambda(checker: &mut Checker, target: &Expr, value: &Expr,
|
||||
if !match_leading_content(stmt, checker.locator)
|
||||
&& !match_trailing_content(stmt, checker.locator)
|
||||
{
|
||||
match function(id, args, body) {
|
||||
match function(id, args, body, checker.style) {
|
||||
Ok(content) => {
|
||||
let first_line = checker.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(stmt.location.row(), 0),
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::str::FromStr;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::vendored::cformat::{
|
||||
use crate::vendor::cformat::{
|
||||
CFormatError, CFormatPart, CFormatQuantity, CFormatSpec, CFormatString,
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::fmt;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::vendored::format::{
|
||||
use crate::vendor::format::{
|
||||
FieldName, FieldType, FormatParseError, FormatPart, FormatString, FromTemplate,
|
||||
};
|
||||
|
||||
@@ -82,7 +82,7 @@ impl TryFrom<&str> for FormatSummary {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::vendored::format::FromTemplate;
|
||||
use crate::vendor::format::FromTemplate;
|
||||
|
||||
#[test]
|
||||
fn test_format_summary() {
|
||||
|
||||
@@ -20,6 +20,7 @@ mod tests {
|
||||
use crate::linter::{check_path, test_path};
|
||||
use crate::settings::flags;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::{directives, rustpython_helpers, settings};
|
||||
|
||||
#[test_case(CheckCode::F401, Path::new("F401_0.py"); "F401_0")]
|
||||
@@ -171,6 +172,7 @@ mod tests {
|
||||
let settings = settings::Settings::for_rules(CheckCodePrefix::F.codes());
|
||||
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let stylist = SourceCodeStyleDetector::from_contents(&contents, &locator);
|
||||
let directives = directives::extract_directives(
|
||||
&tokens,
|
||||
&locator,
|
||||
@@ -182,6 +184,7 @@ mod tests {
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&directives,
|
||||
&settings,
|
||||
flags::Autofix::Enabled,
|
||||
|
||||
@@ -7,9 +7,10 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
/// Return the typename, args, keywords and mother class
|
||||
fn match_named_tuple_assign<'a>(
|
||||
@@ -163,8 +164,9 @@ fn convert_to_class(
|
||||
typename: &str,
|
||||
body: Vec<Stmt>,
|
||||
base_class: &ExprKind,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Fix> {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_stmt(&create_class_def_stmt(typename, body, base_class));
|
||||
let content = generator.generate()?;
|
||||
Ok(Fix::replacement(
|
||||
@@ -194,7 +196,7 @@ pub fn convert_named_tuple_functional_to_class(
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
match convert_to_class(stmt, typename, properties, base_class) {
|
||||
match convert_to_class(stmt, typename, properties, base_class, checker.style) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(err) => error!("Failed to convert `NamedTuple`: {err}"),
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
/// Return the class name, arguments, keywords and base class for a `TypedDict`
|
||||
/// assignment.
|
||||
@@ -196,8 +197,9 @@ fn convert_to_class(
|
||||
body: Vec<Stmt>,
|
||||
total_keyword: Option<KeywordData>,
|
||||
base_class: &ExprKind,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Fix> {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_stmt(&create_class_def_stmt(
|
||||
class_name,
|
||||
body,
|
||||
@@ -236,7 +238,14 @@ pub fn convert_typed_dict_functional_to_class(
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
match convert_to_class(stmt, class_name, body, total_keyword, base_class) {
|
||||
match convert_to_class(
|
||||
stmt,
|
||||
class_name,
|
||||
body,
|
||||
total_keyword,
|
||||
base_class,
|
||||
checker.style,
|
||||
) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(err) => error!("Failed to convert TypedDict: {err}"),
|
||||
};
|
||||
|
||||
@@ -7,7 +7,8 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
/// Return `true` if the `Expr` is a reference to `${module}.${any}`.
|
||||
@@ -87,13 +88,14 @@ fn replace_call_on_arg_by_arg_attribute(
|
||||
arg: &Expr,
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Check> {
|
||||
let attribute = ExprKind::Attribute {
|
||||
value: Box::new(arg.clone()),
|
||||
attr: attr.to_string(),
|
||||
ctx: ExprContext::Load,
|
||||
};
|
||||
replace_by_expr_kind(attribute, expr, patch)
|
||||
replace_by_expr_kind(attribute, expr, patch, stylist)
|
||||
}
|
||||
|
||||
// `func(arg, **args)` => `arg.method(**args)`
|
||||
@@ -102,6 +104,7 @@ fn replace_call_on_arg_by_arg_method_call(
|
||||
args: &[Expr],
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Option<Check>> {
|
||||
if args.is_empty() {
|
||||
bail!("Expected at least one argument");
|
||||
@@ -119,7 +122,7 @@ fn replace_call_on_arg_by_arg_method_call(
|
||||
.collect(),
|
||||
keywords: vec![],
|
||||
};
|
||||
let expr = replace_by_expr_kind(call, expr, patch)?;
|
||||
let expr = replace_by_expr_kind(call, expr, patch, stylist)?;
|
||||
Ok(Some(expr))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -127,10 +130,15 @@ fn replace_call_on_arg_by_arg_method_call(
|
||||
}
|
||||
|
||||
// `expr` => `Expr(expr_kind)`
|
||||
fn replace_by_expr_kind(node: ExprKind, expr: &Expr, patch: bool) -> Result<Check> {
|
||||
fn replace_by_expr_kind(
|
||||
node: ExprKind,
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Check> {
|
||||
let mut check = Check::new(CheckKind::RemoveSixCompat, Range::from_located(expr));
|
||||
if patch {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_expr(&create_expr(node), 0);
|
||||
let content = generator.generate()?;
|
||||
check.amend(Fix::replacement(
|
||||
@@ -142,10 +150,15 @@ fn replace_by_expr_kind(node: ExprKind, expr: &Expr, patch: bool) -> Result<Chec
|
||||
Ok(check)
|
||||
}
|
||||
|
||||
fn replace_by_stmt_kind(node: StmtKind, expr: &Expr, patch: bool) -> Result<Check> {
|
||||
fn replace_by_stmt_kind(
|
||||
node: StmtKind,
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Check> {
|
||||
let mut check = Check::new(CheckKind::RemoveSixCompat, Range::from_located(expr));
|
||||
if patch {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(stylist.indentation(), stylist.quote());
|
||||
generator.unparse_stmt(&create_stmt(node));
|
||||
let content = generator.generate()?;
|
||||
check.amend(Fix::replacement(
|
||||
@@ -163,12 +176,13 @@ fn replace_by_raise_from(
|
||||
cause: Option<ExprKind>,
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Check> {
|
||||
let stmt_kind = StmtKind::Raise {
|
||||
exc: exc.map(|exc| Box::new(create_expr(exc))),
|
||||
cause: cause.map(|cause| Box::new(create_expr(cause))),
|
||||
};
|
||||
replace_by_stmt_kind(stmt_kind, expr, patch)
|
||||
replace_by_stmt_kind(stmt_kind, expr, patch, stylist)
|
||||
}
|
||||
|
||||
fn replace_by_index_on_arg(
|
||||
@@ -176,16 +190,22 @@ fn replace_by_index_on_arg(
|
||||
index: &ExprKind,
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Check> {
|
||||
let index = ExprKind::Subscript {
|
||||
value: Box::new(create_expr(arg.node.clone())),
|
||||
slice: Box::new(create_expr(index.clone())),
|
||||
ctx: ExprContext::Load,
|
||||
};
|
||||
replace_by_expr_kind(index, expr, patch)
|
||||
replace_by_expr_kind(index, expr, patch, stylist)
|
||||
}
|
||||
|
||||
fn handle_reraise(args: &[Expr], expr: &Expr, patch: bool) -> Result<Option<Check>> {
|
||||
fn handle_reraise(
|
||||
args: &[Expr],
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Result<Option<Check>> {
|
||||
if let [_, exc, tb] = args {
|
||||
let check = replace_by_raise_from(
|
||||
Some(ExprKind::Call {
|
||||
@@ -200,6 +220,7 @@ fn handle_reraise(args: &[Expr], expr: &Expr, patch: bool) -> Result<Option<Chec
|
||||
None,
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?;
|
||||
Ok(Some(check))
|
||||
} else if let [arg] = args {
|
||||
@@ -208,7 +229,7 @@ fn handle_reraise(args: &[Expr], expr: &Expr, patch: bool) -> Result<Option<Chec
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "sys" && attr == "exc_info" {
|
||||
let check = replace_by_raise_from(None, None, expr, patch)?;
|
||||
let check = replace_by_raise_from(None, None, expr, patch, stylist)?;
|
||||
return Ok(Some(check));
|
||||
};
|
||||
};
|
||||
@@ -227,6 +248,7 @@ fn handle_func(
|
||||
keywords: &[Keyword],
|
||||
expr: &Expr,
|
||||
patch: bool,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
locator: &SourceCodeLocator,
|
||||
) -> Result<Option<Check>> {
|
||||
let func_name = match &func.node {
|
||||
@@ -241,72 +263,82 @@ fn handle_func(
|
||||
("ensure_str", [arg], []) => replace_by_str_literal(arg, false, expr, patch, locator),
|
||||
("ensure_text", [arg], []) => replace_by_str_literal(arg, false, expr, patch, locator),
|
||||
("iteritems", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("items", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("items", args, expr, patch, stylist)?
|
||||
}
|
||||
("viewitems", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("items", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("items", args, expr, patch, stylist)?
|
||||
}
|
||||
("iterkeys", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("keys", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("keys", args, expr, patch, stylist)?
|
||||
}
|
||||
("viewkeys", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("keys", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("keys", args, expr, patch, stylist)?
|
||||
}
|
||||
("itervalues", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("values", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("values", args, expr, patch, stylist)?
|
||||
}
|
||||
("viewvalues", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("values", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("values", args, expr, patch, stylist)?
|
||||
}
|
||||
("get_method_function", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__func__", arg, expr, patch,
|
||||
"__func__", arg, expr, patch, stylist,
|
||||
)?),
|
||||
("get_method_self", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__self__", arg, expr, patch,
|
||||
"__self__", arg, expr, patch, stylist,
|
||||
)?),
|
||||
("get_function_closure", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__closure__",
|
||||
arg,
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("get_function_code", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__code__", arg, expr, patch,
|
||||
"__code__", arg, expr, patch, stylist,
|
||||
)?),
|
||||
("get_function_defaults", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__defaults__",
|
||||
arg,
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("get_function_globals", [arg], []) => Some(replace_call_on_arg_by_arg_attribute(
|
||||
"__globals__",
|
||||
arg,
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("create_unbound_method", [arg, _], _) => Some(replace_by_expr_kind(
|
||||
arg.node.clone(),
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("get_unbound_function", [arg], []) => Some(replace_by_expr_kind(
|
||||
arg.node.clone(),
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("create_unbound_method", [arg, _], _) => {
|
||||
Some(replace_by_expr_kind(arg.node.clone(), expr, patch)?)
|
||||
}
|
||||
("get_unbound_function", [arg], []) => {
|
||||
Some(replace_by_expr_kind(arg.node.clone(), expr, patch)?)
|
||||
}
|
||||
("assertCountEqual", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("assertCountEqual", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("assertCountEqual", args, expr, patch, stylist)?
|
||||
}
|
||||
("assertRaisesRegex", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("assertRaisesRegex", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("assertRaisesRegex", args, expr, patch, stylist)?
|
||||
}
|
||||
("assertRegex", args, []) => {
|
||||
replace_call_on_arg_by_arg_method_call("assertRegex", args, expr, patch)?
|
||||
replace_call_on_arg_by_arg_method_call("assertRegex", args, expr, patch, stylist)?
|
||||
}
|
||||
("raise_from", [exc, cause], []) => Some(replace_by_raise_from(
|
||||
Some(exc.node.clone()),
|
||||
Some(cause.node.clone()),
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("reraise", args, []) => handle_reraise(args, expr, patch)?,
|
||||
("reraise", args, []) => handle_reraise(args, expr, patch, stylist)?,
|
||||
("byte2int", [arg], []) => Some(replace_by_index_on_arg(
|
||||
arg,
|
||||
&ExprKind::Constant {
|
||||
@@ -315,10 +347,15 @@ fn handle_func(
|
||||
},
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("indexbytes", [arg, index], []) => Some(replace_by_index_on_arg(
|
||||
arg,
|
||||
&index.node,
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
("indexbytes", [arg, index], []) => {
|
||||
Some(replace_by_index_on_arg(arg, &index.node, expr, patch)?)
|
||||
}
|
||||
("int2byte", [arg], []) => Some(replace_by_expr_kind(
|
||||
ExprKind::Call {
|
||||
func: Box::new(create_expr(ExprKind::Name {
|
||||
@@ -333,6 +370,7 @@ fn handle_func(
|
||||
},
|
||||
expr,
|
||||
patch,
|
||||
stylist,
|
||||
)?),
|
||||
_ => None,
|
||||
};
|
||||
@@ -382,6 +420,7 @@ fn handle_next_on_six_dict(expr: &Expr, patch: bool, checker: &Checker) -> Resul
|
||||
},
|
||||
arg,
|
||||
patch,
|
||||
checker.style,
|
||||
) {
|
||||
Ok(check) => Ok(Some(check)),
|
||||
Err(err) => Err(err),
|
||||
@@ -409,7 +448,15 @@ pub fn remove_six_compat(checker: &mut Checker, expr: &Expr) {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
} => match handle_func(func, args, keywords, expr, patch, checker.locator) {
|
||||
} => match handle_func(
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
expr,
|
||||
patch,
|
||||
checker.style,
|
||||
checker.locator,
|
||||
) {
|
||||
Ok(check) => check,
|
||||
Err(err) => {
|
||||
error!("Failed to remove `six` reference: {err}");
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
|
||||
fn optional(expr: &Expr) -> Expr {
|
||||
Expr::new(
|
||||
@@ -65,7 +65,8 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
|
||||
if checker.match_typing_call_path(&call_path, "Optional") {
|
||||
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
|
||||
if checker.patch(check.kind.code()) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator =
|
||||
SourceCodeGenerator::new(checker.style.indentation(), checker.style.quote());
|
||||
generator.unparse_expr(&optional(slice), 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
@@ -84,7 +85,10 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
|
||||
// Invalid type annotation.
|
||||
}
|
||||
ExprKind::Tuple { elts, .. } => {
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(
|
||||
checker.style.indentation(),
|
||||
checker.style.quote(),
|
||||
);
|
||||
generator.unparse_expr(&union(elts), 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
@@ -96,7 +100,10 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
|
||||
}
|
||||
_ => {
|
||||
// Single argument.
|
||||
let mut generator = SourceGenerator::new();
|
||||
let mut generator = SourceCodeGenerator::new(
|
||||
checker.style.indentation(),
|
||||
checker.style.quote(),
|
||||
);
|
||||
generator.unparse_expr(slice, 0);
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix::replacement(
|
||||
|
||||
@@ -75,7 +75,7 @@ expression: checks
|
||||
row: 17
|
||||
column: 46
|
||||
fix:
|
||||
content: "class MyType5(TypedDict):\n a: 'hello'"
|
||||
content: "class MyType5(TypedDict):\n a: \"hello\""
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
@@ -91,7 +91,7 @@ expression: checks
|
||||
row: 18
|
||||
column: 41
|
||||
fix:
|
||||
content: "class MyType6(TypedDict):\n a: 'hello'"
|
||||
content: "class MyType6(TypedDict):\n a: \"hello\""
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
@@ -139,7 +139,7 @@ expression: checks
|
||||
row: 30
|
||||
column: 59
|
||||
fix:
|
||||
content: "class MyType10(TypedDict):\n key: Literal['value']"
|
||||
content: "class MyType10(TypedDict):\n key: Literal[\"value\"]"
|
||||
location:
|
||||
row: 30
|
||||
column: 0
|
||||
|
||||
@@ -27,7 +27,7 @@ expression: checks
|
||||
row: 12
|
||||
column: 1
|
||||
fix:
|
||||
content: "class NT2(NamedTuple):\n a: int\n b: str = 'foo'\n c: list[bool] = [True]"
|
||||
content: "class NT2(NamedTuple):\n a: int\n b: str = \"foo\"\n c: list[bool] = [True]"
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_ast::{Expr, ExprKind, Keyword, KeywordData, Location};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
@@ -1680,3 +1680,25 @@ pub fn ambiguous_unicode_character(
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
/// RUF004
|
||||
pub fn keyword_argument_before_star_argument(args: &[Expr], keywords: &[Keyword]) -> Vec<Check> {
|
||||
let mut checks = vec![];
|
||||
if let Some(arg) = args
|
||||
.iter()
|
||||
.find(|arg| matches!(arg.node, ExprKind::Starred { .. }))
|
||||
{
|
||||
for keyword in keywords {
|
||||
if keyword.location < arg.location {
|
||||
let KeywordData { arg, .. } = &keyword.node;
|
||||
if let Some(arg) = arg {
|
||||
checks.push(Check::new(
|
||||
CheckKind::KeywordArgumentBeforeStarArgument(arg.to_string()),
|
||||
Range::from_located(keyword),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checks
|
||||
}
|
||||
|
||||
@@ -8,10 +8,24 @@ mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashSet;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
#[test_case(CheckCode::RUF004, Path::new("RUF004.py"); "RUF004")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/ruff")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confusables() -> Result<()> {
|
||||
|
||||
23
src/ruff/snapshots/ruff__ruff__tests__RUF004_RUF004.py.snap
Normal file
23
src/ruff/snapshots/ruff__ruff__tests__RUF004_RUF004.py.snap
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/ruff/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
KeywordArgumentBeforeStarArgument: kw
|
||||
location:
|
||||
row: 12
|
||||
column: 2
|
||||
end_location:
|
||||
row: 12
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
KeywordArgumentBeforeStarArgument: kw
|
||||
location:
|
||||
row: 13
|
||||
column: 2
|
||||
end_location:
|
||||
row: 13
|
||||
column: 6
|
||||
fix: ~
|
||||
|
||||
@@ -379,6 +379,7 @@ fn resolve_codes<'a>(specs: impl Iterator<Item = CheckCodeSpec<'a>>) -> FxHashSe
|
||||
let mut codes: FxHashSet<CheckCode> = FxHashSet::default();
|
||||
for spec in specs {
|
||||
for specificity in [
|
||||
SuffixLength::None,
|
||||
SuffixLength::Zero,
|
||||
SuffixLength::One,
|
||||
SuffixLength::Two,
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
//! Generate Python source code from an abstract syntax tree (AST).
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Suite, Withitem};
|
||||
use rustpython_common::str;
|
||||
use rustpython_parser::ast::{
|
||||
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, ConversionFlag, Expr, ExprKind,
|
||||
Operator, Stmt, StmtKind,
|
||||
};
|
||||
|
||||
use crate::source_code_style::{Indentation, Quote};
|
||||
use crate::vendor::{bytes, str};
|
||||
|
||||
mod precedence {
|
||||
macro_rules! precedence {
|
||||
($($op:ident,)*) => {
|
||||
@@ -27,25 +32,27 @@ mod precedence {
|
||||
pub const EXPR: u8 = BOR;
|
||||
}
|
||||
|
||||
pub struct SourceGenerator {
|
||||
pub struct SourceCodeGenerator<'a> {
|
||||
/// The indentation style to use.
|
||||
indent: &'a Indentation,
|
||||
/// The quote style to use for string literals.
|
||||
quote: &'a Quote,
|
||||
buffer: Vec<u8>,
|
||||
indentation: usize,
|
||||
new_lines: usize,
|
||||
indent_depth: usize,
|
||||
num_newlines: usize,
|
||||
initial: bool,
|
||||
}
|
||||
|
||||
impl Default for SourceGenerator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceGenerator {
|
||||
pub fn new() -> Self {
|
||||
SourceGenerator {
|
||||
impl<'a> SourceCodeGenerator<'a> {
|
||||
pub fn new(indent: &'a Indentation, quote: &'a Quote) -> Self {
|
||||
SourceCodeGenerator {
|
||||
// Style preferences.
|
||||
indent,
|
||||
quote,
|
||||
// Internal state.
|
||||
buffer: vec![],
|
||||
indentation: 0,
|
||||
new_lines: 0,
|
||||
indent_depth: 0,
|
||||
num_newlines: 0,
|
||||
initial: true,
|
||||
}
|
||||
}
|
||||
@@ -56,30 +63,30 @@ impl SourceGenerator {
|
||||
|
||||
fn newline(&mut self) {
|
||||
if !self.initial {
|
||||
self.new_lines = std::cmp::max(self.new_lines, 1);
|
||||
self.num_newlines = std::cmp::max(self.num_newlines, 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn newlines(&mut self, extra: usize) {
|
||||
if !self.initial {
|
||||
self.new_lines = std::cmp::max(self.new_lines, 1 + extra);
|
||||
self.num_newlines = std::cmp::max(self.num_newlines, 1 + extra);
|
||||
}
|
||||
}
|
||||
|
||||
fn body<U>(&mut self, stmts: &[Stmt<U>]) {
|
||||
self.indentation += 1;
|
||||
self.indent_depth += 1;
|
||||
for stmt in stmts {
|
||||
self.unparse_stmt(stmt);
|
||||
}
|
||||
self.indentation -= 1;
|
||||
self.indent_depth -= 1;
|
||||
}
|
||||
|
||||
fn p(&mut self, s: &str) {
|
||||
if self.new_lines > 0 {
|
||||
for _ in 0..self.new_lines {
|
||||
if self.num_newlines > 0 {
|
||||
for _ in 0..self.num_newlines {
|
||||
self.buffer.extend("\n".as_bytes());
|
||||
}
|
||||
self.new_lines = 0;
|
||||
self.num_newlines = 0;
|
||||
}
|
||||
self.buffer.extend(s.as_bytes());
|
||||
}
|
||||
@@ -108,7 +115,7 @@ impl SourceGenerator {
|
||||
macro_rules! statement {
|
||||
($body:block) => {{
|
||||
self.newline();
|
||||
self.p(&" ".repeat(self.indentation));
|
||||
self.p(&self.indent.deref().repeat(self.indent_depth));
|
||||
$body
|
||||
self.initial = false;
|
||||
}};
|
||||
@@ -123,7 +130,7 @@ impl SourceGenerator {
|
||||
..
|
||||
} => {
|
||||
// TODO(charlie): Handle decorators.
|
||||
self.newlines(if self.indentation == 0 { 2 } else { 1 });
|
||||
self.newlines(if self.indent_depth == 0 { 2 } else { 1 });
|
||||
statement!({
|
||||
self.p("def ");
|
||||
self.p(name);
|
||||
@@ -137,7 +144,7 @@ impl SourceGenerator {
|
||||
self.p(":");
|
||||
});
|
||||
self.body(body);
|
||||
if self.indentation == 0 {
|
||||
if self.indent_depth == 0 {
|
||||
self.newlines(2);
|
||||
}
|
||||
}
|
||||
@@ -149,7 +156,7 @@ impl SourceGenerator {
|
||||
..
|
||||
} => {
|
||||
// TODO(charlie): Handle decorators.
|
||||
self.newlines(if self.indentation == 0 { 2 } else { 1 });
|
||||
self.newlines(if self.indent_depth == 0 { 2 } else { 1 });
|
||||
statement!({
|
||||
self.p("async def ");
|
||||
self.p(name);
|
||||
@@ -163,7 +170,7 @@ impl SourceGenerator {
|
||||
self.p(":");
|
||||
});
|
||||
self.body(body);
|
||||
if self.indentation == 0 {
|
||||
if self.indent_depth == 0 {
|
||||
self.newlines(2);
|
||||
}
|
||||
}
|
||||
@@ -175,7 +182,7 @@ impl SourceGenerator {
|
||||
..
|
||||
} => {
|
||||
// TODO(charlie): Handle decorators.
|
||||
self.newlines(if self.indentation == 0 { 2 } else { 1 });
|
||||
self.newlines(if self.indent_depth == 0 { 2 } else { 1 });
|
||||
statement!({
|
||||
self.p("class ");
|
||||
self.p(name);
|
||||
@@ -200,7 +207,7 @@ impl SourceGenerator {
|
||||
self.p(":");
|
||||
});
|
||||
self.body(body);
|
||||
if self.indentation == 0 {
|
||||
if self.indent_depth == 0 {
|
||||
self.newlines(2);
|
||||
}
|
||||
}
|
||||
@@ -788,6 +795,12 @@ impl SourceGenerator {
|
||||
{
|
||||
self.p(&value.to_string().replace("inf", inf_str));
|
||||
}
|
||||
Constant::Bytes(b) => {
|
||||
self.p(&bytes::repr(b, self.quote.into()));
|
||||
}
|
||||
Constant::Str(s) => {
|
||||
self.p(&format!("{}", str::repr(s, self.quote.into())));
|
||||
}
|
||||
_ => self.p(&format!("{value}")),
|
||||
}
|
||||
}
|
||||
@@ -931,7 +944,7 @@ impl SourceGenerator {
|
||||
}
|
||||
|
||||
fn unparse_formatted<U>(&mut self, val: &Expr<U>, conversion: usize, spec: Option<&Expr<U>>) {
|
||||
let mut generator = SourceGenerator::default();
|
||||
let mut generator = SourceCodeGenerator::new(self.indent, self.quote);
|
||||
generator.unparse_expr(val, precedence::TEST + 1);
|
||||
let brace = if generator.buffer.starts_with("{".as_bytes()) {
|
||||
// put a space to avoid escaping the bracket
|
||||
@@ -987,10 +1000,10 @@ impl SourceGenerator {
|
||||
self.unparse_fstring_body(values, is_spec);
|
||||
} else {
|
||||
self.p("f");
|
||||
let mut generator = SourceGenerator::default();
|
||||
let mut generator = SourceCodeGenerator::new(self.indent, self.quote);
|
||||
generator.unparse_fstring_body(values, is_spec);
|
||||
let body = std::str::from_utf8(&generator.buffer).unwrap();
|
||||
self.p(&format!("{}", str::repr(body)));
|
||||
self.p(&format!("{}", str::repr(body, self.quote.into())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1010,3 +1023,139 @@ impl SourceGenerator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::{Indentation, Quote};
|
||||
|
||||
fn round_trip(contents: &str) -> Result<String> {
|
||||
let indentation = Indentation::default();
|
||||
let quote = Quote::default();
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let mut generator = SourceCodeGenerator::new(&indentation, "e);
|
||||
generator.unparse_stmt(stmt);
|
||||
generator.generate().map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
fn round_trip_with(indentation: &Indentation, quote: &Quote, contents: &str) -> Result<String> {
|
||||
let program = parser::parse_program(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let mut generator = SourceCodeGenerator::new(indentation, quote);
|
||||
generator.unparse_stmt(stmt);
|
||||
generator.generate().map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quote() -> Result<()> {
|
||||
assert_eq!(round_trip(r#""hello""#)?, r#""hello""#);
|
||||
assert_eq!(round_trip(r#"'hello'"#)?, r#""hello""#);
|
||||
assert_eq!(round_trip(r#"u'hello'"#)?, r#"u"hello""#);
|
||||
assert_eq!(round_trip(r#"r'hello'"#)?, r#""hello""#);
|
||||
assert_eq!(round_trip(r#"b'hello'"#)?, r#"b"hello""#);
|
||||
assert_eq!(round_trip(r#"("abc" "def" "ghi")"#)?, r#""abcdefghi""#);
|
||||
assert_eq!(round_trip(r#""he\"llo""#)?, r#"'he"llo'"#);
|
||||
assert_eq!(round_trip(r#"f'abc{"def"}{1}'"#)?, r#"f'abc{"def"}{1}'"#);
|
||||
assert_eq!(round_trip(r#"f"abc{'def'}{1}""#)?, r#"f'abc{"def"}{1}'"#);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indent() -> Result<()> {
|
||||
assert_eq!(
|
||||
round_trip(
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim(),
|
||||
)?,
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_quote() -> Result<()> {
|
||||
assert_eq!(
|
||||
round_trip_with(&Indentation::default(), &Quote::Double, r#""hello""#)?,
|
||||
r#""hello""#
|
||||
);
|
||||
assert_eq!(
|
||||
round_trip_with(&Indentation::default(), &Quote::Single, r#""hello""#)?,
|
||||
r#"'hello'"#
|
||||
);
|
||||
assert_eq!(
|
||||
round_trip_with(&Indentation::default(), &Quote::Double, r#"'hello'"#)?,
|
||||
r#""hello""#
|
||||
);
|
||||
assert_eq!(
|
||||
round_trip_with(&Indentation::default(), &Quote::Single, r#"'hello'"#)?,
|
||||
r#"'hello'"#
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_indent() -> Result<()> {
|
||||
assert_eq!(
|
||||
round_trip_with(
|
||||
&Indentation::new(" ".to_string()),
|
||||
&Quote::default(),
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim(),
|
||||
)?,
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
assert_eq!(
|
||||
round_trip_with(
|
||||
&Indentation::new(" ".to_string()),
|
||||
&Quote::default(),
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim(),
|
||||
)?,
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
assert_eq!(
|
||||
round_trip_with(
|
||||
&Indentation::new("\t".to_string()),
|
||||
&Quote::default(),
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim(),
|
||||
)?,
|
||||
r#"
|
||||
if True:
|
||||
pass
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
200
src/source_code_style.rs
Normal file
200
src/source_code_style.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
//! Detect code style from Python source code.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use once_cell::unsync::OnceCell;
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::pydocstyle::helpers::leading_quote;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::vendor;
|
||||
|
||||
pub struct SourceCodeStyleDetector<'a> {
|
||||
contents: &'a str,
|
||||
locator: &'a SourceCodeLocator<'a>,
|
||||
indentation: OnceCell<Indentation>,
|
||||
quote: OnceCell<Quote>,
|
||||
}
|
||||
|
||||
impl<'a> SourceCodeStyleDetector<'a> {
|
||||
pub fn indentation(&'a self) -> &'a Indentation {
|
||||
self.indentation
|
||||
.get_or_init(|| detect_indentation(self.contents, self.locator).unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn quote(&'a self) -> &'a Quote {
|
||||
self.quote
|
||||
.get_or_init(|| detect_quote(self.contents, self.locator).unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn from_contents(contents: &'a str, locator: &'a SourceCodeLocator<'a>) -> Self {
|
||||
Self {
|
||||
contents,
|
||||
locator,
|
||||
indentation: OnceCell::default(),
|
||||
quote: OnceCell::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The quotation style used in Python source code.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Quote {
|
||||
Single,
|
||||
Double,
|
||||
}
|
||||
|
||||
impl Default for Quote {
|
||||
fn default() -> Self {
|
||||
Quote::Double
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Quote> for vendor::str::Quote {
|
||||
fn from(val: &Quote) -> Self {
|
||||
match val {
|
||||
Quote::Single => vendor::str::Quote::Single,
|
||||
Quote::Double => vendor::str::Quote::Double,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The indentation style used in Python source code.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Indentation(String);
|
||||
|
||||
impl Indentation {
|
||||
pub fn new(indentation: String) -> Self {
|
||||
Self(indentation)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Indentation {
|
||||
fn default() -> Self {
|
||||
Indentation(" ".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Indentation {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect the indentation style of the given tokens.
|
||||
fn detect_indentation(contents: &str, locator: &SourceCodeLocator) -> Option<Indentation> {
|
||||
for (_start, tok, end) in lexer::make_tokenizer(contents).flatten() {
|
||||
if let Tok::Indent { .. } = tok {
|
||||
let start = Location::new(end.row(), 0);
|
||||
let whitespace = locator.slice_source_code_range(&Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
});
|
||||
return Some(Indentation(whitespace.to_string()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Detect the quotation style of the given tokens.
|
||||
fn detect_quote(contents: &str, locator: &SourceCodeLocator) -> Option<Quote> {
|
||||
for (start, tok, end) in lexer::make_tokenizer(contents).flatten() {
|
||||
if let Tok::String { .. } = tok {
|
||||
let content = locator.slice_source_code_range(&Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
});
|
||||
if let Some(pattern) = leading_quote(&content) {
|
||||
if pattern.contains('\'') {
|
||||
return Some(Quote::Single);
|
||||
} else if pattern.contains('"') {
|
||||
return Some(Quote::Double);
|
||||
}
|
||||
unreachable!("Expected string to start with a valid quote prefix")
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::source_code_style::{detect_indentation, detect_quote, Indentation, Quote};
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
#[test]
|
||||
fn indentation() {
|
||||
let contents = r#"x = 1"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_indentation(contents, &locator), None);
|
||||
|
||||
let contents = r#"
|
||||
if True:
|
||||
pass
|
||||
"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
detect_indentation(contents, &locator),
|
||||
Some(Indentation(" ".to_string()))
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
if True:
|
||||
pass
|
||||
"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
detect_indentation(contents, &locator),
|
||||
Some(Indentation(" ".to_string()))
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
if True:
|
||||
pass
|
||||
"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(
|
||||
detect_indentation(contents, &locator),
|
||||
Some(Indentation("\t".to_string()))
|
||||
);
|
||||
|
||||
// TODO(charlie): Should non-significant whitespace be detected?
|
||||
let contents = r#"
|
||||
x = (
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
)
|
||||
"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_indentation(contents, &locator), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quote() {
|
||||
let contents = r#"x = 1"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_quote(contents, &locator), None);
|
||||
|
||||
let contents = r#"x = '1'"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_quote(contents, &locator), Some(Quote::Single));
|
||||
|
||||
let contents = r#"x = "1""#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_quote(contents, &locator), Some(Quote::Double));
|
||||
|
||||
let contents = r#"
|
||||
def f():
|
||||
"""Docstring."""
|
||||
pass
|
||||
"#;
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
assert_eq!(detect_quote(contents, &locator), Some(Quote::Double));
|
||||
}
|
||||
}
|
||||
68
src/vendor/bytes.rs
vendored
Normal file
68
src/vendor/bytes.rs
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
//! Vendored from [bytes.rs in rustpython-common](https://github.com/RustPython/RustPython/blob/1d8269fb729c91fc56064e975172d3a11bd62d07/common/src/bytes.rs).
|
||||
//! The only changes we make are to remove dead code and make the default quote
|
||||
//! type configurable.
|
||||
|
||||
use crate::vendor;
|
||||
use crate::vendor::str::Quote;
|
||||
|
||||
pub fn repr(b: &[u8], quote: Quote) -> String {
|
||||
repr_with(b, &[], "", quote)
|
||||
}
|
||||
|
||||
pub fn repr_with(b: &[u8], prefixes: &[&str], suffix: &str, quote: Quote) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut out_len = 0usize;
|
||||
let mut squote = 0;
|
||||
let mut dquote = 0;
|
||||
|
||||
for &ch in b {
|
||||
let incr = match ch {
|
||||
b'\'' => {
|
||||
squote += 1;
|
||||
1
|
||||
}
|
||||
b'"' => {
|
||||
dquote += 1;
|
||||
1
|
||||
}
|
||||
b'\\' | b'\t' | b'\r' | b'\n' => 2,
|
||||
0x20..=0x7e => 1,
|
||||
_ => 4, // \xHH
|
||||
};
|
||||
// TODO: OverflowError
|
||||
out_len = out_len.checked_add(incr).unwrap();
|
||||
}
|
||||
|
||||
let (quote, num_escaped_quotes) = vendor::str::choose_quotes_for_repr(squote, dquote, quote);
|
||||
// we'll be adding backslashes in front of the existing inner quotes
|
||||
out_len += num_escaped_quotes;
|
||||
|
||||
// 3 is for b prefix + outer quotes
|
||||
out_len += 3 + prefixes.iter().map(|s| s.len()).sum::<usize>() + suffix.len();
|
||||
|
||||
let mut res = String::with_capacity(out_len);
|
||||
res.extend(prefixes.iter().copied());
|
||||
res.push('b');
|
||||
res.push(quote);
|
||||
for &ch in b {
|
||||
match ch {
|
||||
b'\t' => res.push_str("\\t"),
|
||||
b'\n' => res.push_str("\\n"),
|
||||
b'\r' => res.push_str("\\r"),
|
||||
// printable ascii range
|
||||
0x20..=0x7e => {
|
||||
let ch = ch as char;
|
||||
if ch == quote || ch == '\\' {
|
||||
res.push('\\');
|
||||
}
|
||||
res.push(ch);
|
||||
}
|
||||
_ => write!(res, "\\x{ch:02x}").unwrap(),
|
||||
}
|
||||
}
|
||||
res.push(quote);
|
||||
res.push_str(suffix);
|
||||
|
||||
res
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Implementation of Printf-Style string formatting
|
||||
//! as per the [Python Docs](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting).
|
||||
//! Vendored from [cformat.rs in rustpython-vm](https://github.com/RustPython/RustPython/blob/f54b5556e28256763c5506813ea977c9e1445af0/vm/src/cformat.rs).
|
||||
//! The only changes we make are to remove dead code and code involving the vm.
|
||||
//! The only changes we make are to remove dead code and code involving the VM.
|
||||
use std::fmt;
|
||||
use std::iter::{Enumerate, Peekable};
|
||||
use std::str::FromStr;
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Vendored from [format.rs in rustpython-vm](https://github.com/RustPython/RustPython/blob/f54b5556e28256763c5506813ea977c9e1445af0/vm/src/format.rs).
|
||||
//! The only changes we make are to remove dead code and code involving the vm.
|
||||
//! The only changes we make are to remove dead code and code involving the VM.
|
||||
use itertools::{Itertools, PeekingNext};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
2
src/vendored/mod.rs → src/vendor/mod.rs
vendored
2
src/vendored/mod.rs → src/vendor/mod.rs
vendored
@@ -1,2 +1,4 @@
|
||||
pub mod bytes;
|
||||
pub mod cformat;
|
||||
pub mod format;
|
||||
pub mod str;
|
||||
182
src/vendor/str.rs
vendored
Normal file
182
src/vendor/str.rs
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
//! Vendored from [str.rs in rustpython-common](https://github.com/RustPython/RustPython/blob/1d8269fb729c91fc56064e975172d3a11bd62d07/common/src/str.rs).
|
||||
//! The only changes we make are to remove dead code and make the default quote
|
||||
//! type configurable.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use once_cell::unsync::OnceCell;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Quote {
|
||||
Single,
|
||||
Double,
|
||||
}
|
||||
|
||||
/// Get a Display-able type that formats to the python `repr()` of the string
|
||||
/// value.
|
||||
#[inline]
|
||||
pub fn repr(s: &str, quote: Quote) -> Repr<'_> {
|
||||
Repr {
|
||||
s,
|
||||
quote,
|
||||
info: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct ReprOverflowError;
|
||||
|
||||
impl fmt::Display for ReprOverflowError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("string is too long to generate repr")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct ReprInfo {
|
||||
dquoted: bool,
|
||||
out_len: usize,
|
||||
}
|
||||
|
||||
impl ReprInfo {
|
||||
fn get(s: &str, quote: Quote) -> Result<Self, ReprOverflowError> {
|
||||
let mut out_len = 0usize;
|
||||
let mut squote = 0;
|
||||
let mut dquote = 0;
|
||||
|
||||
for ch in s.chars() {
|
||||
let incr = match ch {
|
||||
'\'' => {
|
||||
squote += 1;
|
||||
1
|
||||
}
|
||||
'"' => {
|
||||
dquote += 1;
|
||||
1
|
||||
}
|
||||
'\\' | '\t' | '\r' | '\n' => 2,
|
||||
ch if ch < ' ' || ch as u32 == 0x7f => 4, // \xHH
|
||||
ch if ch.is_ascii() => 1,
|
||||
ch if rustpython_common::char::is_printable(ch) => {
|
||||
// max = std::cmp::max(ch, max);
|
||||
ch.len_utf8()
|
||||
}
|
||||
ch if (ch as u32) < 0x100 => 4, // \xHH
|
||||
ch if (ch as u32) < 0x10000 => 6, // \uHHHH
|
||||
_ => 10, // \uHHHHHHHH
|
||||
};
|
||||
out_len += incr;
|
||||
if out_len > std::isize::MAX as usize {
|
||||
return Err(ReprOverflowError);
|
||||
}
|
||||
}
|
||||
|
||||
let (quote, num_escaped_quotes) = choose_quotes_for_repr(squote, dquote, quote);
|
||||
// we'll be adding backslashes in front of the existing inner quotes
|
||||
out_len += num_escaped_quotes;
|
||||
|
||||
// start and ending quotes
|
||||
out_len += 2;
|
||||
|
||||
let dquoted = quote == '"';
|
||||
|
||||
Ok(ReprInfo { dquoted, out_len })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Repr<'a> {
|
||||
s: &'a str,
|
||||
// the quote type we prefer to use
|
||||
quote: Quote,
|
||||
// the tuple is dquouted, out_len
|
||||
info: OnceCell<Result<ReprInfo, ReprOverflowError>>,
|
||||
}
|
||||
|
||||
impl Repr<'_> {
|
||||
fn get_info(&self) -> Result<ReprInfo, ReprOverflowError> {
|
||||
*self.info.get_or_init(|| ReprInfo::get(self.s, self.quote))
|
||||
}
|
||||
|
||||
fn _fmt<W: fmt::Write>(&self, repr: &mut W, info: ReprInfo) -> fmt::Result {
|
||||
let s = self.s;
|
||||
let in_len = s.len();
|
||||
let ReprInfo { dquoted, out_len } = info;
|
||||
|
||||
let quote = if dquoted { '"' } else { '\'' };
|
||||
// if we don't need to escape anything we can just copy
|
||||
let unchanged = out_len == in_len;
|
||||
|
||||
repr.write_char(quote)?;
|
||||
if unchanged {
|
||||
repr.write_str(s)?;
|
||||
} else {
|
||||
for ch in s.chars() {
|
||||
match ch {
|
||||
'\n' => repr.write_str("\\n"),
|
||||
'\t' => repr.write_str("\\t"),
|
||||
'\r' => repr.write_str("\\r"),
|
||||
// these 2 branches *would* be handled below, but we shouldn't have to do a
|
||||
// unicodedata lookup just for ascii characters
|
||||
'\x20'..='\x7e' => {
|
||||
// printable ascii range
|
||||
if ch == quote || ch == '\\' {
|
||||
repr.write_char('\\')?;
|
||||
}
|
||||
repr.write_char(ch)
|
||||
}
|
||||
ch if ch.is_ascii() => {
|
||||
write!(repr, "\\x{:02x}", ch as u8)
|
||||
}
|
||||
ch if rustpython_common::char::is_printable(ch) => repr.write_char(ch),
|
||||
'\0'..='\u{ff}' => {
|
||||
write!(repr, "\\x{:02x}", ch as u32)
|
||||
}
|
||||
'\0'..='\u{ffff}' => {
|
||||
write!(repr, "\\u{:04x}", ch as u32)
|
||||
}
|
||||
_ => {
|
||||
write!(repr, "\\U{:08x}", ch as u32)
|
||||
}
|
||||
}?;
|
||||
}
|
||||
}
|
||||
repr.write_char(quote)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Repr<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let info = self.get_info().unwrap();
|
||||
self._fmt(f, info)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the outer quotes to use and the number of quotes that need to be
|
||||
/// escaped.
|
||||
pub(crate) fn choose_quotes_for_repr(
|
||||
num_squotes: usize,
|
||||
num_dquotes: usize,
|
||||
quote: Quote,
|
||||
) -> (char, usize) {
|
||||
match quote {
|
||||
Quote::Single => {
|
||||
// always use squote unless we have squotes but no dquotes
|
||||
let use_dquote = num_squotes > 0 && num_dquotes == 0;
|
||||
if use_dquote {
|
||||
('"', num_dquotes)
|
||||
} else {
|
||||
('\'', num_squotes)
|
||||
}
|
||||
}
|
||||
Quote::Double => {
|
||||
// always use dquote unless we have dquotes but no squotes
|
||||
let use_squote = num_dquotes > 0 && num_squotes == 0;
|
||||
if use_squote {
|
||||
('\'', num_squotes)
|
||||
} else {
|
||||
('"', num_dquotes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user