Compare commits

...

10 Commits

Author SHA1 Message Date
Charlie Marsh
16c5ac1e91 Bump version to 0.0.106 2022-11-07 15:32:54 -05:00
Charlie Marsh
da39cddccb Include function and argument names in ANN checks (#648) 2022-11-07 15:27:03 -05:00
Charlie Marsh
de6435f41d Add a flake8-to-ruff mention (#644) 2022-11-07 10:59:47 -05:00
Charlie Marsh
589ae48f8c Update --help 2022-11-07 10:41:04 -05:00
Harutaka Kawamura
35cc3094b5 Implement B005 (#643) 2022-11-07 10:10:59 -05:00
Charlie Marsh
07c9fc55f6 Add fix option to pyproject.toml (#639) 2022-11-07 09:46:21 -05:00
Harutaka Kawamura
7773e9728b Implement B004 (#638) 2022-11-07 09:25:09 -05:00
Charlie Marsh
8fc435bad9 Fix --ignore for ANN101 and ANN102 (#637) 2022-11-07 09:03:09 -05:00
Charlie Marsh
dd20b23576 Infer plugins based on per-file-ignores, ignores, etc. (#632) 2022-11-06 22:39:06 -05:00
Charlie Marsh
da8ee1df3e Add TODO in flake8_annotations 2022-11-06 21:24:46 -05:00
31 changed files with 558 additions and 175 deletions

View File

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

6
Cargo.lock generated
View File

@@ -920,7 +920,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.105-dev.0"
version = "0.0.106-dev.0"
dependencies = [
"anyhow",
"clap 4.0.15",
@@ -2221,7 +2221,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.105"
version = "0.0.106"
dependencies = [
"anyhow",
"assert_cmd",
@@ -2266,7 +2266,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.105"
version = "0.0.106"
dependencies = [
"anyhow",
"clap 4.0.15",

View File

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

View File

@@ -15,15 +15,15 @@ An extremely fast Python linter, written in Rust.
<i>Linting the CPython codebase from scratch.</i>
</p>
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🤝 Python 3.10 compatibility
- 🛠️ `pyproject.toml` support
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache support
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix)-inspired autofix support (e.g., automatically remove unused imports)
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support, for continuous file monitoring
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) ([`pydocstyle`](https://pypi.org/project/pydocstyle/))
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🤝 Python 3.10 compatibility
- 🛠️ `pyproject.toml` support
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 `--fix` support, for automatic error correction (e.g., automatically remove unused imports)
- 👀 `--watch` support, for continuous file monitoring
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) ([`pydocstyle`](https://pypi.org/project/pydocstyle/))
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety
@@ -31,6 +31,9 @@ of plugins), [`pydocstyle`](https://pypi.org/project/pydocstyle/), [`yesqa`](htt
and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) and [`autoflake`](https://pypi.org/project/autoflake/)
all while executing tens or hundreds of times faster than any individual tool.
(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to
automatically convert your existing configuration.)
Ruff is actively developed and used in major open-source projects
like [Zulip](https://github.com/zulip/zulip), [pydantic](https://github.com/pydantic/pydantic),
and [Saleor](https://github.com/saleor/saleor).
@@ -94,7 +97,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.105
rev: v0.0.106
hooks:
- id: ruff
```
@@ -200,7 +203,7 @@ Options:
Exit with status code "0", even upon detecting errors
-w, --watch
Run in watch mode by re-running whenever files change
-f, --fix
--fix
Attempt to automatically fix lint errors
-n, --no-cache
Disable cache reads
@@ -482,6 +485,8 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
| ---- | ---- | ------- | --- |
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
| B003 | AssignmentToOsEnviron | Assigning to `os.environ` doesn't clear the environment. | |
| B004 | UnreliableCallableCheck | Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results. | |
| B005 | StripWithMultiCharacters | Using `.strip()` with multi-character strings is misleading the reader. | |
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
@@ -530,16 +535,16 @@ For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ANN001 | MissingTypeFunctionArgument | Missing type annotation for function argument | |
| ANN002 | MissingTypeArgs | Missing type annotation for `*args` | |
| ANN003 | MissingTypeKwargs | Missing type annotation for `**kwargs` | |
| ANN101 | MissingTypeSelf | Missing type annotation for `self` in method | |
| ANN102 | MissingTypeCls | Missing type annotation for `cls` in classmethod | |
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function | |
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function | |
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method | |
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod | |
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod | |
| ANN001 | MissingTypeFunctionArgument | Missing type annotation for function argument `...` | |
| ANN002 | MissingTypeArgs | Missing type annotation for `*...` | |
| ANN003 | MissingTypeKwargs | Missing type annotation for `**...` | |
| ANN101 | MissingTypeSelf | Missing type annotation for `...` in method | |
| ANN102 | MissingTypeCls | Missing type annotation for `...` in classmethod | |
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function `...` | |
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function `...` | |
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method `...` | |
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod `...` | |
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
### Ruff-specific rules
@@ -606,6 +611,9 @@ stylistic lint rules that are obviated by autoformatting.
### How does Ruff compare to Flake8?
(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to
automatically convert your existing configuration.)
Ruff can be used as a (near) drop-in replacement for Flake8 when used (1) without or with a small
number of plugins, (2) alongside Black, and (3) on Python 3 code.
@@ -625,7 +633,7 @@ including:
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (17/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (11/34)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
@@ -648,7 +656,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (17/32)
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (11/34).

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
use std::collections::{BTreeSet, HashMap};
use anyhow::Result;
use ruff::checks_gen::CheckCodePrefix;
use ruff::flake8_quotes::settings::Quote;
use ruff::settings::options::Options;
use ruff::settings::pyproject::Pyproject;
@@ -13,6 +14,29 @@ pub fn convert(
flake8: &HashMap<String, Option<String>>,
plugins: Option<Vec<Plugin>>,
) -> Result<Pyproject> {
// Extract all referenced check code prefixes, to power plugin inference.
let mut referenced_codes: BTreeSet<CheckCodePrefix> = Default::default();
for (key, value) in flake8 {
if let Some(value) = value {
match key.as_str() {
"select" | "ignore" | "extend-select" | "extend_select" | "extend-ignore"
| "extend_ignore" => {
referenced_codes.extend(parser::parse_prefix_codes(value.as_ref()));
}
"per-file-ignores" | "per_file_ignores" => {
if let Ok(per_file_ignores) =
parser::parse_files_to_codes_mapping(value.as_ref())
{
for (_, codes) in parser::collect_per_file_ignores(per_file_ignores) {
referenced_codes.extend(codes);
}
}
}
_ => {}
}
}
}
// Check if the user has specified a `select`. If not, we'll add our own
// default `select`, and populate it based on user plugins.
let mut select = flake8
@@ -25,7 +49,12 @@ pub fn convert(
.unwrap_or_else(|| {
plugin::resolve_select(
flake8,
&plugins.unwrap_or_else(|| plugin::infer_plugins(flake8)),
&plugins.unwrap_or_else(|| {
plugin::infer_plugins_from_options(flake8)
.into_iter()
.chain(plugin::infer_plugins_from_codes(&referenced_codes))
.collect()
}),
)
});
let mut ignore = flake8
@@ -52,6 +81,7 @@ pub fn convert(
},
"select" => {
// No-op (handled above).
select.extend(parser::parse_prefix_codes(value.as_ref()));
}
"ignore" => {
// No-op (handled above).

View File

@@ -37,6 +37,20 @@ impl FromStr for Plugin {
}
impl Plugin {
pub fn default(&self) -> CheckCodePrefix {
match self {
Plugin::Flake8Bugbear => CheckCodePrefix::B,
Plugin::Flake8Builtins => CheckCodePrefix::A,
Plugin::Flake8Comprehensions => CheckCodePrefix::C,
Plugin::Flake8Docstrings => CheckCodePrefix::D,
Plugin::Flake8Print => CheckCodePrefix::T,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
Plugin::PEP8Naming => CheckCodePrefix::N,
Plugin::Pyupgrade => CheckCodePrefix::U,
}
}
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
match self {
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
@@ -239,8 +253,11 @@ impl DocstringConvention {
}
}
/// Infer the enabled plugins based on user-provided settings.
pub fn infer_plugins(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
/// Infer the enabled plugins based on user-provided options.
///
/// For example, if the user specified a `mypy-init-return` setting, we should
/// infer that `flake8-annotations` is active.
pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
let mut plugins = BTreeSet::new();
for key in flake8.keys() {
match key.as_str() {
@@ -306,6 +323,38 @@ pub fn infer_plugins(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
Vec::from_iter(plugins)
}
/// Infer the enabled plugins based on the referenced prefixes.
///
/// For example, if the user ignores `ANN101`, we should infer that
/// `flake8-annotations` is active.
pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin> {
[
Plugin::Flake8Bugbear,
Plugin::Flake8Builtins,
Plugin::Flake8Comprehensions,
Plugin::Flake8Docstrings,
Plugin::Flake8Print,
Plugin::Flake8Quotes,
Plugin::Flake8Annotations,
Plugin::PEP8Naming,
Plugin::Pyupgrade,
]
.into_iter()
.filter(|plugin| {
for prefix in codes {
if prefix
.codes()
.iter()
.any(|code| plugin.default().codes().contains(code))
{
return true;
}
}
false
})
.collect()
}
/// Resolve the set of enabled `CheckCodePrefix` values for the given plugins.
pub fn resolve_select(
flake8: &HashMap<String, Option<String>>,
@@ -316,9 +365,7 @@ pub fn resolve_select(
// Add prefix codes for every plugin.
for plugin in plugins {
for prefix in plugin.select(flake8) {
select.insert(prefix);
}
select.extend(plugin.select(flake8));
}
select
@@ -328,18 +375,18 @@ pub fn resolve_select(
mod tests {
use std::collections::HashMap;
use crate::plugin::{infer_plugins, Plugin};
use crate::plugin::{infer_plugins_from_options, Plugin};
#[test]
fn it_infers_plugins() {
let actual = infer_plugins(&HashMap::from([(
let actual = infer_plugins_from_options(&HashMap::from([(
"inline-quotes".to_string(),
Some("single".to_string()),
)]));
let expected = vec![Plugin::Flake8Quotes];
assert_eq!(actual, expected);
let actual = infer_plugins(&HashMap::from([(
let actual = infer_plugins_from_options(&HashMap::from([(
"staticmethod-decorators".to_string(),
Some("[]".to_string()),
)]));

12
resources/test/fixtures/B004.py vendored Normal file
View File

@@ -0,0 +1,12 @@
def this_is_a_bug():
o = object()
if hasattr(o, "__call__"):
print("Ooh, callable! Or is it?")
if getattr(o, "__call__", False):
print("Ooh, callable! Or is it?")
def this_is_fine():
o = object()
if callable(o):
print("Ooh, this is actually callable.")

26
resources/test/fixtures/B005.py vendored Normal file
View File

@@ -0,0 +1,26 @@
s = "qwe"
s.strip(s) # no warning
s.strip("we") # no warning
s.strip(".facebook.com") # warning
s.strip("e") # no warning
s.strip("\n\t ") # no warning
s.strip(r"\n\t ") # warning
s.lstrip(s) # no warning
s.lstrip("we") # no warning
s.lstrip(".facebook.com") # warning
s.lstrip("e") # no warning
s.lstrip("\n\t ") # no warning
s.lstrip(r"\n\t ") # warning
s.rstrip(s) # no warning
s.rstrip("we") # warning
s.rstrip(".facebook.com") # warning
s.rstrip("e") # no warning
s.rstrip("\n\t ") # no warning
s.rstrip(r"\n\t ") # warning
from somewhere import other_type, strip
strip("we") # no warning
other_type().lstrip() # no warning
other_type().rstrip(["a", "b", "c"]) # no warning
other_type().strip("a", "b") # no warning

View File

@@ -1,3 +1,5 @@
from typing import Type
# Error
def foo(a, b):
pass
@@ -31,3 +33,23 @@ def foo(a: int, b: int) -> int:
# OK
def foo() -> int:
pass
class Foo:
# OK
def foo(self: "Foo", a: int, b: int) -> int:
pass
# ANN101
def foo(self, a: int, b: int) -> int:
pass
# OK
@classmethod
def foo(cls: Type["Foo"], a: int, b: int) -> int:
pass
# ANN102
@classmethod
def foo(cls, a: int, b: int) -> int:
pass

View File

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

View File

@@ -1042,6 +1042,13 @@ where
flake8_print::plugins::print_call(self, expr, func);
}
if self.settings.enabled.contains(&CheckCode::B004) {
flake8_bugbear::plugins::unreliable_callable_check(self, expr, func, args);
}
if self.settings.enabled.contains(&CheckCode::B005) {
flake8_bugbear::plugins::strip_with_multi_characters(self, expr, func, args);
}
// flake8-comprehensions
if self.settings.enabled.contains(&CheckCode::C400) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_list(

View File

@@ -79,6 +79,8 @@ pub enum CheckCode {
// flake8-bugbear
B002,
B003,
B004,
B005,
B006,
B007,
B008,
@@ -337,6 +339,8 @@ pub enum CheckKind {
// flake8-bugbear
UnaryPrefixIncrement,
AssignmentToOsEnviron,
UnreliableCallableCheck,
StripWithMultiCharacters,
MutableArgumentDefault,
UnusedLoopControlVariable(String),
FunctionCallArgumentDefault,
@@ -374,16 +378,16 @@ pub enum CheckKind {
BadQuotesDocstring(Quote),
AvoidQuoteEscape,
// flake8-annotations
MissingTypeFunctionArgument,
MissingTypeArgs,
MissingTypeKwargs,
MissingTypeSelf,
MissingTypeCls,
MissingReturnTypePublicFunction,
MissingReturnTypePrivateFunction,
MissingReturnTypeMagicMethod,
MissingReturnTypeStaticMethod,
MissingReturnTypeClassMethod,
MissingTypeFunctionArgument(String),
MissingTypeArgs(String),
MissingTypeKwargs(String),
MissingTypeSelf(String),
MissingTypeCls(String),
MissingReturnTypePublicFunction(String),
MissingReturnTypePrivateFunction(String),
MissingReturnTypeMagicMethod(String),
MissingReturnTypeStaticMethod(String),
MissingReturnTypeClassMethod(String),
// pyupgrade
TypeOfPrimitive(Primitive),
UnnecessaryAbspath,
@@ -541,6 +545,8 @@ impl CheckCode {
// flake8-bugbear
CheckCode::B002 => CheckKind::UnaryPrefixIncrement,
CheckCode::B003 => CheckKind::AssignmentToOsEnviron,
CheckCode::B004 => CheckKind::UnreliableCallableCheck,
CheckCode::B005 => CheckKind::StripWithMultiCharacters,
CheckCode::B006 => CheckKind::MutableArgumentDefault,
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault,
@@ -593,16 +599,16 @@ impl CheckCode {
CheckCode::Q002 => CheckKind::BadQuotesDocstring(Quote::Double),
CheckCode::Q003 => CheckKind::AvoidQuoteEscape,
// flake8-annotations
CheckCode::ANN001 => CheckKind::MissingTypeFunctionArgument,
CheckCode::ANN002 => CheckKind::MissingTypeArgs,
CheckCode::ANN003 => CheckKind::MissingTypeKwargs,
CheckCode::ANN101 => CheckKind::MissingTypeSelf,
CheckCode::ANN102 => CheckKind::MissingTypeCls,
CheckCode::ANN201 => CheckKind::MissingReturnTypePublicFunction,
CheckCode::ANN202 => CheckKind::MissingReturnTypePrivateFunction,
CheckCode::ANN204 => CheckKind::MissingReturnTypeMagicMethod,
CheckCode::ANN205 => CheckKind::MissingReturnTypeStaticMethod,
CheckCode::ANN206 => CheckKind::MissingReturnTypeClassMethod,
CheckCode::ANN001 => CheckKind::MissingTypeFunctionArgument("...".to_string()),
CheckCode::ANN002 => CheckKind::MissingTypeArgs("...".to_string()),
CheckCode::ANN003 => CheckKind::MissingTypeKwargs("...".to_string()),
CheckCode::ANN101 => CheckKind::MissingTypeSelf("...".to_string()),
CheckCode::ANN102 => CheckKind::MissingTypeCls("...".to_string()),
CheckCode::ANN201 => CheckKind::MissingReturnTypePublicFunction("...".to_string()),
CheckCode::ANN202 => CheckKind::MissingReturnTypePrivateFunction("...".to_string()),
CheckCode::ANN204 => CheckKind::MissingReturnTypeMagicMethod("...".to_string()),
CheckCode::ANN205 => CheckKind::MissingReturnTypeStaticMethod("...".to_string()),
CheckCode::ANN206 => CheckKind::MissingReturnTypeClassMethod("...".to_string()),
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
@@ -752,6 +758,8 @@ impl CheckCode {
CheckCode::A003 => CheckCategory::Flake8Builtins,
CheckCode::B002 => CheckCategory::Flake8Bugbear,
CheckCode::B003 => CheckCategory::Flake8Bugbear,
CheckCode::B004 => CheckCategory::Flake8Bugbear,
CheckCode::B005 => CheckCategory::Flake8Bugbear,
CheckCode::B006 => CheckCategory::Flake8Bugbear,
CheckCode::B007 => CheckCategory::Flake8Bugbear,
CheckCode::B008 => CheckCategory::Flake8Bugbear,
@@ -927,6 +935,8 @@ impl CheckKind {
// flake8-bugbear
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
CheckKind::UnreliableCallableCheck => &CheckCode::B004,
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
CheckKind::MutableArgumentDefault => &CheckCode::B006,
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
CheckKind::FunctionCallArgumentDefault => &CheckCode::B008,
@@ -964,16 +974,16 @@ impl CheckKind {
CheckKind::BadQuotesDocstring(_) => &CheckCode::Q002,
CheckKind::AvoidQuoteEscape => &CheckCode::Q003,
// flake8-annotations
CheckKind::MissingTypeFunctionArgument => &CheckCode::ANN001,
CheckKind::MissingTypeArgs => &CheckCode::ANN002,
CheckKind::MissingTypeKwargs => &CheckCode::ANN003,
CheckKind::MissingTypeSelf => &CheckCode::ANN101,
CheckKind::MissingTypeCls => &CheckCode::ANN102,
CheckKind::MissingReturnTypePublicFunction => &CheckCode::ANN201,
CheckKind::MissingReturnTypePrivateFunction => &CheckCode::ANN202,
CheckKind::MissingReturnTypeMagicMethod => &CheckCode::ANN204,
CheckKind::MissingReturnTypeStaticMethod => &CheckCode::ANN205,
CheckKind::MissingReturnTypeClassMethod => &CheckCode::ANN206,
CheckKind::MissingTypeFunctionArgument(_) => &CheckCode::ANN001,
CheckKind::MissingTypeArgs(_) => &CheckCode::ANN002,
CheckKind::MissingTypeKwargs(_) => &CheckCode::ANN003,
CheckKind::MissingTypeSelf(_) => &CheckCode::ANN101,
CheckKind::MissingTypeCls(_) => &CheckCode::ANN102,
CheckKind::MissingReturnTypePublicFunction(_) => &CheckCode::ANN201,
CheckKind::MissingReturnTypePrivateFunction(_) => &CheckCode::ANN202,
CheckKind::MissingReturnTypeMagicMethod(_) => &CheckCode::ANN204,
CheckKind::MissingReturnTypeStaticMethod(_) => &CheckCode::ANN205,
CheckKind::MissingReturnTypeClassMethod(_) => &CheckCode::ANN206,
// pyupgrade
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
@@ -1211,6 +1221,13 @@ impl CheckKind {
CheckKind::AssignmentToOsEnviron => {
"Assigning to `os.environ` doesn't clear the environment.".to_string()
}
CheckKind::UnreliableCallableCheck => " Using `hasattr(x, '__call__')` to test if x \
is callable is unreliable. Use `callable(x)` \
for consistent results."
.to_string(),
CheckKind::StripWithMultiCharacters => "Using `.strip()` with multi-character strings \
is misleading the reader."
.to_string(),
CheckKind::MutableArgumentDefault => {
"Do not use mutable data structures for argument defaults.".to_string()
}
@@ -1360,31 +1377,33 @@ impl CheckKind {
"Change outer quotes to avoid escaping inner quotes".to_string()
}
// flake8-annotations
CheckKind::MissingTypeFunctionArgument => {
"Missing type annotation for function argument".to_string()
CheckKind::MissingTypeFunctionArgument(name) => {
format!("Missing type annotation for function argument `{name}`")
}
CheckKind::MissingTypeArgs => "Missing type annotation for `*args`".to_string(),
CheckKind::MissingTypeKwargs => "Missing type annotation for `**kwargs`".to_string(),
CheckKind::MissingTypeSelf => {
"Missing type annotation for `self` in method".to_string()
CheckKind::MissingTypeArgs(name) => format!("Missing type annotation for `*{name}`"),
CheckKind::MissingTypeKwargs(name) => {
format!("Missing type annotation for `**{name}`")
}
CheckKind::MissingTypeCls => {
"Missing type annotation for `cls` in classmethod".to_string()
CheckKind::MissingTypeSelf(name) => {
format!("Missing type annotation for `{name}` in method")
}
CheckKind::MissingReturnTypePublicFunction => {
"Missing return type annotation for public function".to_string()
CheckKind::MissingTypeCls(name) => {
format!("Missing type annotation for `{name}` in classmethod")
}
CheckKind::MissingReturnTypePrivateFunction => {
"Missing return type annotation for private function".to_string()
CheckKind::MissingReturnTypePublicFunction(name) => {
format!("Missing return type annotation for public function `{name}`")
}
CheckKind::MissingReturnTypeMagicMethod => {
"Missing return type annotation for magic method".to_string()
CheckKind::MissingReturnTypePrivateFunction(name) => {
format!("Missing return type annotation for private function `{name}`")
}
CheckKind::MissingReturnTypeStaticMethod => {
"Missing return type annotation for staticmethod".to_string()
CheckKind::MissingReturnTypeMagicMethod(name) => {
format!("Missing return type annotation for magic method `{name}`")
}
CheckKind::MissingReturnTypeClassMethod => {
"Missing return type annotation for classmethod".to_string()
CheckKind::MissingReturnTypeStaticMethod(name) => {
format!("Missing return type annotation for staticmethod `{name}`")
}
CheckKind::MissingReturnTypeClassMethod(name) => {
format!("Missing return type annotation for classmethod `{name}`")
}
// pyupgrade
CheckKind::TypeOfPrimitive(primitive) => {

View File

@@ -35,6 +35,8 @@ pub enum CheckCodePrefix {
B00,
B002,
B003,
B004,
B005,
B006,
B007,
B008,
@@ -317,6 +319,8 @@ impl CheckCodePrefix {
CheckCodePrefix::B => vec![
CheckCode::B002,
CheckCode::B003,
CheckCode::B004,
CheckCode::B005,
CheckCode::B006,
CheckCode::B007,
CheckCode::B008,
@@ -332,6 +336,8 @@ impl CheckCodePrefix {
CheckCodePrefix::B0 => vec![
CheckCode::B002,
CheckCode::B003,
CheckCode::B004,
CheckCode::B005,
CheckCode::B006,
CheckCode::B007,
CheckCode::B008,
@@ -347,12 +353,16 @@ impl CheckCodePrefix {
CheckCodePrefix::B00 => vec![
CheckCode::B002,
CheckCode::B003,
CheckCode::B004,
CheckCode::B005,
CheckCode::B006,
CheckCode::B007,
CheckCode::B008,
],
CheckCodePrefix::B002 => vec![CheckCode::B002],
CheckCodePrefix::B003 => vec![CheckCode::B003],
CheckCodePrefix::B004 => vec![CheckCode::B004],
CheckCodePrefix::B005 => vec![CheckCode::B005],
CheckCodePrefix::B006 => vec![CheckCode::B006],
CheckCodePrefix::B007 => vec![CheckCode::B007],
CheckCodePrefix::B008 => vec![CheckCode::B008],
@@ -1010,6 +1020,8 @@ impl CheckCodePrefix {
CheckCodePrefix::B00 => PrefixSpecificity::Tens,
CheckCodePrefix::B002 => PrefixSpecificity::Explicit,
CheckCodePrefix::B003 => PrefixSpecificity::Explicit,
CheckCodePrefix::B004 => PrefixSpecificity::Explicit,
CheckCodePrefix::B005 => PrefixSpecificity::Explicit,
CheckCodePrefix::B006 => PrefixSpecificity::Explicit,
CheckCodePrefix::B007 => PrefixSpecificity::Explicit,
CheckCodePrefix::B008 => PrefixSpecificity::Explicit,

View File

@@ -38,8 +38,10 @@ pub struct Cli {
#[arg(short, long)]
pub watch: bool,
/// Attempt to automatically fix lint errors.
#[arg(short, long)]
pub fix: bool,
#[arg(long, overrides_with("no_fix"))]
fix: bool,
#[clap(long, overrides_with("fix"), hide = true)]
no_fix: bool,
/// Disable cache reads.
#[arg(short, long)]
pub no_cache: bool,
@@ -94,6 +96,22 @@ pub struct Cli {
pub stdin_filename: Option<String>,
}
impl Cli {
// See: https://github.com/clap-rs/clap/issues/3146
pub fn fix(&self) -> Option<bool> {
resolve_bool_arg(self.fix, self.no_fix)
}
}
fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
match (yes, no) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(..) => unreachable!("Clap should make this impossible"),
}
}
/// Map the CLI settings to a `LogLevel`.
pub fn extract_log_level(cli: &Cli) -> LogLevel {
if cli.silent {
@@ -165,7 +183,7 @@ pub fn warn_on(
}
}
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
pub fn collect_per_file_ignores(
pairs: Vec<PatternPrefixPair>,
project_root: &Option<PathBuf>,

View File

@@ -29,9 +29,9 @@ where
}
}
fn is_none_returning(stmt: &Stmt) -> bool {
fn is_none_returning(body: &[Stmt]) -> bool {
let mut visitor: ReturnStatementVisitor = Default::default();
for stmt in match_body(stmt) {
for stmt in body {
visitor.visit_stmt(stmt);
}
for expr in visitor.returns.into_iter().flatten() {
@@ -48,39 +48,38 @@ fn is_none_returning(stmt: &Stmt) -> bool {
true
}
fn match_args(stmt: &Stmt) -> &Arguments {
fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, &Option<Box<Expr>>, &Vec<Stmt>) {
match &stmt.node {
StmtKind::FunctionDef { args, .. } | StmtKind::AsyncFunctionDef { args, .. } => args,
_ => panic!("Found non-FunctionDef in match_args"),
}
}
fn match_body(stmt: &Stmt) -> &Vec<Stmt> {
match &stmt.node {
StmtKind::FunctionDef { body, .. } | StmtKind::AsyncFunctionDef { body, .. } => body,
_ => panic!("Found non-FunctionDef in match_body"),
}
}
fn match_returns(stmt: &Stmt) -> &Option<Box<Expr>> {
match &stmt.node {
StmtKind::FunctionDef { returns, .. } | StmtKind::AsyncFunctionDef { returns, .. } => {
returns
StmtKind::FunctionDef {
name,
args,
returns,
body,
..
}
_ => panic!("Found non-FunctionDef in match_returns"),
| StmtKind::AsyncFunctionDef {
name,
args,
returns,
body,
..
} => (name, args, returns, body),
_ => panic!("Found non-FunctionDef in match_name"),
}
}
/// Generate flake8-annotation checks for a given `Definition`.
pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &Visibility) {
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
// We could adhere more closely to `flake8-annotations` by defining public
// vs. secret vs. protected.
match &definition.kind {
DefinitionKind::Module => {}
DefinitionKind::Package => {}
DefinitionKind::Class(_) => {}
DefinitionKind::NestedClass(_) => {}
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
let args = match_args(stmt);
let returns = match_returns(stmt);
let (name, args, returns, body) = match_function_def(stmt);
// ANN001
for arg in args
@@ -95,7 +94,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
{
if checker.settings.enabled.contains(&CheckCode::ANN001) {
checker.add_check(Check::new(
CheckKind::MissingTypeFunctionArgument,
CheckKind::MissingTypeFunctionArgument(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
@@ -111,7 +110,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
{
if checker.settings.enabled.contains(&CheckCode::ANN002) {
checker.add_check(Check::new(
CheckKind::MissingTypeArgs,
CheckKind::MissingTypeArgs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
@@ -127,7 +126,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
{
if checker.settings.enabled.contains(&CheckCode::ANN003) {
checker.add_check(Check::new(
CheckKind::MissingTypeKwargs,
CheckKind::MissingTypeKwargs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
@@ -140,7 +139,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
// Allow omission of return annotation in `__init__` functions, if the function
// only returns `None` (explicitly or implicitly).
if checker.settings.flake8_annotations.suppress_none_returning
&& is_none_returning(stmt)
&& is_none_returning(body)
{
return;
}
@@ -149,7 +148,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
Visibility::Public => {
if checker.settings.enabled.contains(&CheckCode::ANN201) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction,
CheckKind::MissingReturnTypePublicFunction(name.to_string()),
Range::from_located(stmt),
));
}
@@ -157,7 +156,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
Visibility::Private => {
if checker.settings.enabled.contains(&CheckCode::ANN202) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction,
CheckKind::MissingReturnTypePrivateFunction(name.to_string()),
Range::from_located(stmt),
));
}
@@ -166,8 +165,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
}
}
DefinitionKind::Method(stmt) => {
let args = match_args(stmt);
let returns = match_returns(stmt);
let (name, args, returns, body) = match_function_def(stmt);
let mut has_any_typed_arg = false;
// ANN001
@@ -187,7 +185,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
{
if checker.settings.enabled.contains(&CheckCode::ANN001) {
checker.add_check(Check::new(
CheckKind::MissingTypeFunctionArgument,
CheckKind::MissingTypeFunctionArgument(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
@@ -205,7 +203,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
{
if checker.settings.enabled.contains(&CheckCode::ANN002) {
checker.add_check(Check::new(
CheckKind::MissingTypeArgs,
CheckKind::MissingTypeArgs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
@@ -223,7 +221,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
{
if checker.settings.enabled.contains(&CheckCode::ANN003) {
checker.add_check(Check::new(
CheckKind::MissingTypeKwargs,
CheckKind::MissingTypeKwargs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
@@ -238,16 +236,16 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if let Some(arg) = args.args.first() {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN101) {
if checker.settings.enabled.contains(&CheckCode::ANN102) {
checker.add_check(Check::new(
CheckKind::MissingTypeCls,
CheckKind::MissingTypeCls(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
} else {
if checker.settings.enabled.contains(&CheckCode::ANN102) {
if checker.settings.enabled.contains(&CheckCode::ANN101) {
checker.add_check(Check::new(
CheckKind::MissingTypeSelf,
CheckKind::MissingTypeSelf(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
@@ -261,7 +259,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
// Allow omission of return annotation in `__init__` functions, if the function
// only returns `None` (explicitly or implicitly).
if checker.settings.flake8_annotations.suppress_none_returning
&& is_none_returning(stmt)
&& is_none_returning(body)
{
return;
}
@@ -269,21 +267,21 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if visibility::is_classmethod(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN206) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeClassMethod,
CheckKind::MissingReturnTypeClassMethod(name.to_string()),
Range::from_located(stmt),
));
}
} else if visibility::is_staticmethod(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN205) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeStaticMethod,
CheckKind::MissingReturnTypeStaticMethod(name.to_string()),
Range::from_located(stmt),
));
}
} else if visibility::is_magic(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN204) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeMagicMethod,
CheckKind::MissingReturnTypeMagicMethod(name.to_string()),
Range::from_located(stmt),
));
}
@@ -295,7 +293,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
&& has_any_typed_arg)
{
checker.add_check(Check::new(
CheckKind::MissingReturnTypeMagicMethod,
CheckKind::MissingReturnTypeMagicMethod(name.to_string()),
Range::from_located(stmt),
));
}
@@ -305,7 +303,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
Visibility::Public => {
if checker.settings.enabled.contains(&CheckCode::ANN201) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction,
CheckKind::MissingReturnTypePublicFunction(name.to_string()),
Range::from_located(stmt),
));
}
@@ -313,7 +311,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
Visibility::Private => {
if checker.settings.enabled.contains(&CheckCode::ANN202) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction,
CheckKind::MissingReturnTypePrivateFunction(name.to_string()),
Range::from_located(stmt),
));
}

View File

@@ -2,68 +2,94 @@
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind: MissingReturnTypePublicFunction
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 2
row: 4
column: 0
end_location:
row: 7
row: 9
column: 0
fix: ~
- kind: MissingTypeFunctionArgument
- kind:
MissingTypeFunctionArgument: a
location:
row: 2
row: 4
column: 8
end_location:
row: 2
row: 4
column: 9
fix: ~
- kind: MissingTypeFunctionArgument
- kind:
MissingTypeFunctionArgument: b
location:
row: 2
row: 4
column: 11
end_location:
row: 2
row: 4
column: 12
fix: ~
- kind: MissingReturnTypePublicFunction
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 7
row: 9
column: 0
end_location:
row: 12
row: 14
column: 0
fix: ~
- kind: MissingTypeFunctionArgument
- kind:
MissingTypeFunctionArgument: b
location:
row: 7
row: 9
column: 16
end_location:
row: 7
row: 9
column: 17
fix: ~
- kind: MissingTypeFunctionArgument
- kind:
MissingTypeFunctionArgument: b
location:
row: 12
row: 14
column: 16
end_location:
row: 12
row: 14
column: 17
fix: ~
- kind: MissingReturnTypePublicFunction
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 17
row: 19
column: 0
end_location:
row: 22
row: 24
column: 0
fix: ~
- kind: MissingReturnTypePublicFunction
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 22
row: 24
column: 0
end_location:
row: 27
row: 29
column: 0
fix: ~
- kind:
MissingTypeSelf: self
location:
row: 44
column: 12
end_location:
row: 44
column: 16
fix: ~
- kind:
MissingTypeCls: cls
location:
row: 54
column: 12
end_location:
row: 54
column: 15
fix: ~

View File

@@ -2,7 +2,8 @@
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind: MissingReturnTypeMagicMethod
- kind:
MissingReturnTypeMagicMethod: __init__
location:
row: 5
column: 4
@@ -10,7 +11,8 @@ expression: checks
row: 10
column: 0
fix: ~
- kind: MissingReturnTypeMagicMethod
- kind:
MissingReturnTypeMagicMethod: __init__
location:
row: 11
column: 4
@@ -18,7 +20,8 @@ expression: checks
row: 16
column: 0
fix: ~
- kind: MissingReturnTypePrivateFunction
- kind:
MissingReturnTypePrivateFunction: __init__
location:
row: 40
column: 0

View File

@@ -2,7 +2,8 @@
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind: MissingReturnTypePublicFunction
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 45
column: 0
@@ -10,7 +11,8 @@ expression: checks
row: 50
column: 0
fix: ~
- kind: MissingReturnTypePublicFunction
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 50
column: 0

View File

@@ -6,7 +6,9 @@ pub use duplicate_exceptions::{duplicate_exceptions, duplicate_handler_exception
pub use function_call_argument_default::function_call_argument_default;
pub use mutable_argument_default::mutable_argument_default;
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
pub use strip_with_multi_characters::strip_with_multi_characters;
pub use unary_prefix_increment::unary_prefix_increment;
pub use unreliable_callable_check::unreliable_callable_check;
pub use unused_loop_control_variable::unused_loop_control_variable;
pub use useless_comparison::useless_comparison;
pub use useless_expression::useless_expression;
@@ -19,7 +21,9 @@ mod duplicate_exceptions;
mod function_call_argument_default;
mod mutable_argument_default;
mod redundant_tuple_in_exception_handler;
mod strip_with_multi_characters;
mod unary_prefix_increment;
mod unreliable_callable_check;
mod unused_loop_control_variable;
mod useless_comparison;
mod useless_expression;

View File

@@ -0,0 +1,28 @@
use itertools::Itertools;
use rustpython_ast::{Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
/// B005
pub fn strip_with_multi_characters(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let ExprKind::Attribute { attr, .. } = &func.node {
if attr == "strip" || attr == "lstrip" || attr == "rstrip" {
if args.len() == 1 {
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &args[0].node
{
if value.len() > 1 && value.chars().unique().count() != value.len() {
checker.add_check(Check::new(
CheckKind::StripWithMultiCharacters,
Range::from_located(expr),
));
}
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
use rustpython_ast::{Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
/// B004
pub fn unreliable_callable_check(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let ExprKind::Name { id, .. } = &func.node {
if id == "getattr" || id == "hasattr" {
if args.len() >= 2 {
if let ExprKind::Constant {
value: Constant::Str(s),
..
} = &args[1].node
{
if s == "__call__" {
checker.add_check(Check::new(
CheckKind::UnreliableCallableCheck,
Range::from_located(expr),
));
}
}
}
}
}
}

View File

@@ -294,6 +294,8 @@ mod tests {
#[test_case(CheckCode::A003, Path::new("A003.py"); "A003")]
#[test_case(CheckCode::B002, Path::new("B002.py"); "B002")]
#[test_case(CheckCode::B003, Path::new("B003.py"); "B003")]
#[test_case(CheckCode::B004, Path::new("B004.py"); "B004")]
#[test_case(CheckCode::B005, Path::new("B005.py"); "B005")]
#[test_case(CheckCode::B006, Path::new("B006_B008.py"); "B006")]
#[test_case(CheckCode::B007, Path::new("B007.py"); "B007")]
#[test_case(CheckCode::B008, Path::new("B006_B008.py"); "B008")]

View File

@@ -220,7 +220,9 @@ fn autoformat(files: &[PathBuf], settings: &Settings) -> Result<usize> {
}
fn inner_main() -> Result<ExitCode> {
// Extract command-line arguments.
let cli = Cli::parse();
let fix = cli.fix();
let log_level = extract_log_level(&cli);
set_up_logging(&log_level)?;
@@ -239,7 +241,7 @@ fn inner_main() -> Result<ExitCode> {
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
// Parse the settings from the pyproject.toml and command-line arguments.
// Reconcile configuration from pyproject.toml and command-line arguments.
let exclude: Vec<FilePattern> = cli
.exclude
.iter()
@@ -296,6 +298,9 @@ fn inner_main() -> Result<ExitCode> {
if let Some(dummy_variable_rgx) = cli.dummy_variable_rgx {
configuration.dummy_variable_rgx = dummy_variable_rgx;
}
if let Some(fix) = fix {
configuration.fix = fix;
}
if cli.show_settings && cli.show_files {
eprintln!("Error: specify --show-settings or show-files (not both).");
@@ -306,6 +311,8 @@ fn inner_main() -> Result<ExitCode> {
return Ok(ExitCode::SUCCESS);
}
// Extract settings for internal use.
let autofix = configuration.fix;
let settings = Settings::from_configuration(configuration);
if cli.show_files {
@@ -318,7 +325,7 @@ fn inner_main() -> Result<ExitCode> {
let printer = Printer::new(&cli.format, &log_level);
if cli.watch {
if cli.fix {
if autofix {
eprintln!("Warning: --fix is not enabled in watch mode.");
}
@@ -381,15 +388,15 @@ fn inner_main() -> Result<ExitCode> {
let messages = if is_stdin {
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
let path = Path::new(&filename);
run_once_stdin(&settings, path, cli.fix)?
run_once_stdin(&settings, path, autofix)?
} else {
run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?
run_once(&cli.files, &settings, !cli.no_cache, autofix)?
};
// Always try to print violations (the printer itself may suppress output),
// unless we're writing fixes via stdin (in which case, the transformed
// source code goes to stdout).
if !(is_stdin && cli.fix) {
if !(is_stdin && autofix) {
printer.write_once(&messages)?;
}

View File

@@ -20,6 +20,7 @@ pub struct Configuration {
pub extend_exclude: Vec<FilePattern>,
pub extend_ignore: Vec<CheckCodePrefix>,
pub extend_select: Vec<CheckCodePrefix>,
pub fix: bool,
pub ignore: Vec<CheckCodePrefix>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
@@ -91,6 +92,7 @@ impl Configuration {
.select
.unwrap_or_else(|| vec![CheckCodePrefix::E, CheckCodePrefix::F]),
extend_select: options.extend_select.unwrap_or_default(),
fix: options.fix.unwrap_or_default(),
ignore: options.ignore.unwrap_or_default(),
line_length: options.line_length.unwrap_or(88),
per_file_ignores: options

View File

@@ -11,15 +11,16 @@ use crate::{flake8_annotations, flake8_quotes, pep8_naming};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
pub line_length: Option<usize>,
pub dummy_variable_rgx: Option<String>,
pub exclude: Option<Vec<String>>,
pub extend_exclude: Option<Vec<String>>,
pub select: Option<Vec<CheckCodePrefix>>,
pub extend_select: Option<Vec<CheckCodePrefix>>,
pub ignore: Option<Vec<CheckCodePrefix>>,
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
pub extend_select: Option<Vec<CheckCodePrefix>>,
pub fix: Option<bool>,
pub ignore: Option<Vec<CheckCodePrefix>>,
pub line_length: Option<usize>,
pub per_file_ignores: Option<BTreeMap<String, Vec<CheckCodePrefix>>>,
pub dummy_variable_rgx: Option<String>,
pub select: Option<Vec<CheckCodePrefix>>,
pub target_version: Option<PythonVersion>,
// Plugins
pub flake8_annotations: Option<flake8_annotations::settings::Options>,

View File

@@ -134,6 +134,7 @@ mod tests {
Some(Tools {
ruff: Some(Options {
line_length: None,
fix: None,
exclude: None,
extend_exclude: None,
select: None,
@@ -162,6 +163,7 @@ line-length = 79
Some(Tools {
ruff: Some(Options {
line_length: Some(79),
fix: None,
exclude: None,
extend_exclude: None,
select: None,
@@ -190,6 +192,7 @@ exclude = ["foo.py"]
Some(Tools {
ruff: Some(Options {
line_length: None,
fix: None,
exclude: Some(vec!["foo.py".to_string()]),
extend_exclude: None,
select: None,
@@ -218,6 +221,7 @@ select = ["E501"]
Some(Tools {
ruff: Some(Options {
line_length: None,
fix: None,
exclude: None,
extend_exclude: None,
select: Some(vec![CheckCodePrefix::E501]),
@@ -247,6 +251,7 @@ ignore = ["E501"]
Some(Tools {
ruff: Some(Options {
line_length: None,
fix: None,
exclude: None,
extend_exclude: None,
select: None,
@@ -315,6 +320,7 @@ other-attribute = 1
config,
Options {
line_length: Some(88),
fix: None,
exclude: None,
extend_exclude: Some(vec![
"excluded.py".to_string(),

View File

@@ -40,6 +40,7 @@ pub struct UserConfiguration {
pub extend_exclude: Vec<Exclusion>,
pub extend_ignore: Vec<CheckCodePrefix>,
pub extend_select: Vec<CheckCodePrefix>,
pub fix: bool,
pub ignore: Vec<CheckCodePrefix>,
pub line_length: usize,
pub per_file_ignores: Vec<(Exclusion, Vec<CheckCode>)>,
@@ -74,6 +75,7 @@ impl UserConfiguration {
.collect(),
extend_ignore: configuration.extend_ignore,
extend_select: configuration.extend_select,
fix: configuration.fix,
ignore: configuration.ignore,
line_length: configuration.line_length,
per_file_ignores: configuration

View File

@@ -0,0 +1,21 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnreliableCallableCheck
location:
row: 3
column: 7
end_location:
row: 3
column: 29
fix: ~
- kind: UnreliableCallableCheck
location:
row: 5
column: 7
end_location:
row: 5
column: 36
fix: ~

View File

@@ -0,0 +1,53 @@
---
source: src/linter.rs
expression: checks
---
- kind: StripWithMultiCharacters
location:
row: 4
column: 0
end_location:
row: 4
column: 24
fix: ~
- kind: StripWithMultiCharacters
location:
row: 7
column: 0
end_location:
row: 7
column: 17
fix: ~
- kind: StripWithMultiCharacters
location:
row: 10
column: 0
end_location:
row: 10
column: 25
fix: ~
- kind: StripWithMultiCharacters
location:
row: 13
column: 0
end_location:
row: 13
column: 18
fix: ~
- kind: StripWithMultiCharacters
location:
row: 16
column: 0
end_location:
row: 16
column: 25
fix: ~
- kind: StripWithMultiCharacters
location:
row: 19
column: 0
end_location:
row: 19
column: 18
fix: ~