mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
Implement C901 (mccabe) (#765)
This commit is contained in:
parent
6a6f4651aa
commit
f44fada446
22 changed files with 555 additions and 11 deletions
|
@ -98,6 +98,13 @@ _and_ a `pyproject.toml` parameter to `src/pyproject.rs`. If you want to pattern
|
|||
existing example, grep for `dummy_variable_rgx`, which defines a regular expression to match against
|
||||
acceptable unused variables (e.g., `_`).
|
||||
|
||||
If the new plugin's configuration should be cached between runs, you'll need to add it to the
|
||||
`Hash` implementation for `Settings` in `src/settings/mod.rs`.
|
||||
|
||||
You may also want to add the new configuration option to the `flake8-to-ruff` tool, which is
|
||||
responsible for converting `flake8` configuration files to Ruff's TOML format. This logic
|
||||
lives in `flake8_to_ruff/src/converter.rs`.
|
||||
|
||||
## Release process
|
||||
|
||||
As of now, Ruff has an ad hoc release process: releases are cut with high frequency via GitHub
|
||||
|
|
14
README.md
14
README.md
|
@ -240,6 +240,8 @@ Options:
|
|||
The minimum Python version that should be supported
|
||||
--line-length <LINE_LENGTH>
|
||||
Set the line-length for length-associated checks and automatic formatting
|
||||
--max-complexity <MAX_COMPLEXITY>
|
||||
Set the maximum cyclomatic complexity for complexity-associated checks
|
||||
--stdin-filename <STDIN_FILENAME>
|
||||
The name of the file when passing it through stdin
|
||||
-h, --help
|
||||
|
@ -357,8 +359,8 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
|
|||
| ---- | ---- | ------- | --- |
|
||||
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | |
|
||||
| E501 | LineTooLong | Line too long (89 > 88 characters) | |
|
||||
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | 🛠 |
|
||||
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` | 🛠 |
|
||||
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | |
|
||||
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` | |
|
||||
| E713 | NotInTest | Test for membership should be `not in` | |
|
||||
| E714 | NotIsTest | Test for object identity should be `is not` | |
|
||||
| E721 | TypeComparison | Do not compare types, use `isinstance()` | |
|
||||
|
@ -612,6 +614,14 @@ For more, see [flake8-2020](https://pypi.org/project/flake8-2020/1.7.0/) on PyPI
|
|||
| YTT302 | SysVersionCmpStr10 | `sys.version` compared to string (python10), use `sys.version_info` | |
|
||||
| YTT303 | SysVersionSlice1Referenced | `sys.version[:1]` referenced (python10), use `sys.version_info` | |
|
||||
|
||||
### mccabe
|
||||
|
||||
For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| C901 | FunctionIsTooComplex | `...` is too complex (10) | |
|
||||
|
||||
### Ruff-specific rules
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
|
|
|
@ -6,7 +6,9 @@ use ruff::flake8_quotes::settings::Quote;
|
|||
use ruff::flake8_tidy_imports::settings::Strictness;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
use ruff::{flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, pep8_naming};
|
||||
use ruff::{
|
||||
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, mccabe, pep8_naming,
|
||||
};
|
||||
|
||||
use crate::plugin::Plugin;
|
||||
use crate::{parser, plugin};
|
||||
|
@ -73,6 +75,7 @@ pub fn convert(
|
|||
let mut flake8_bugbear: flake8_bugbear::settings::Options = Default::default();
|
||||
let mut flake8_quotes: flake8_quotes::settings::Options = Default::default();
|
||||
let mut flake8_tidy_imports: flake8_tidy_imports::settings::Options = Default::default();
|
||||
let mut mccabe: mccabe::settings::Options = Default::default();
|
||||
let mut pep8_naming: pep8_naming::settings::Options = Default::default();
|
||||
for (key, value) in flake8 {
|
||||
if let Some(value) = value {
|
||||
|
@ -186,6 +189,11 @@ pub fn convert(
|
|||
"docstring-convention" => {
|
||||
// No-op (handled above).
|
||||
}
|
||||
// mccabe
|
||||
"max-complexity" | "max_complexity" => match value.clone().parse::<usize>() {
|
||||
Ok(max_complexity) => mccabe.max_complexity = Some(max_complexity),
|
||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||
},
|
||||
// Unknown
|
||||
_ => eprintln!("Skipping unsupported property: {key}"),
|
||||
}
|
||||
|
@ -207,6 +215,9 @@ pub fn convert(
|
|||
if flake8_tidy_imports != Default::default() {
|
||||
options.flake8_tidy_imports = Some(flake8_tidy_imports);
|
||||
}
|
||||
if mccabe != Default::default() {
|
||||
options.mccabe = Some(mccabe);
|
||||
}
|
||||
if pep8_naming != Default::default() {
|
||||
options.pep8_naming = Some(pep8_naming);
|
||||
}
|
||||
|
@ -253,6 +264,7 @@ mod tests {
|
|||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -288,6 +300,7 @@ mod tests {
|
|||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -323,6 +336,7 @@ mod tests {
|
|||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -358,6 +372,7 @@ mod tests {
|
|||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -398,6 +413,7 @@ mod tests {
|
|||
}),
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -471,6 +487,7 @@ mod tests {
|
|||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -512,6 +529,7 @@ mod tests {
|
|||
}),
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
|
|
@ -15,6 +15,7 @@ pub enum Plugin {
|
|||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
Flake8Annotations,
|
||||
McCabe,
|
||||
PEP8Naming,
|
||||
Pyupgrade,
|
||||
}
|
||||
|
@ -33,6 +34,7 @@ impl FromStr for Plugin {
|
|||
"flake8-print" => Ok(Plugin::Flake8Print),
|
||||
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
|
||||
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
|
||||
"mccabe" => Ok(Plugin::McCabe),
|
||||
"pep8-naming" => Ok(Plugin::PEP8Naming),
|
||||
"pyupgrade" => Ok(Plugin::Pyupgrade),
|
||||
_ => Err(anyhow!("Unknown plugin: {}", string)),
|
||||
|
@ -46,12 +48,13 @@ impl Plugin {
|
|||
Plugin::Flake8Bandit => CheckCodePrefix::S,
|
||||
Plugin::Flake8Bugbear => CheckCodePrefix::B,
|
||||
Plugin::Flake8Builtins => CheckCodePrefix::A,
|
||||
Plugin::Flake8Comprehensions => CheckCodePrefix::C,
|
||||
Plugin::Flake8Comprehensions => CheckCodePrefix::C4,
|
||||
Plugin::Flake8Docstrings => CheckCodePrefix::D,
|
||||
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
|
||||
Plugin::Flake8Print => CheckCodePrefix::T,
|
||||
Plugin::Flake8Quotes => CheckCodePrefix::Q,
|
||||
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
|
||||
Plugin::McCabe => CheckCodePrefix::C9,
|
||||
Plugin::PEP8Naming => CheckCodePrefix::N,
|
||||
Plugin::Pyupgrade => CheckCodePrefix::U,
|
||||
}
|
||||
|
@ -62,7 +65,7 @@ impl Plugin {
|
|||
Plugin::Flake8Bandit => vec![CheckCodePrefix::S],
|
||||
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
|
||||
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
|
||||
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C],
|
||||
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C4],
|
||||
Plugin::Flake8Docstrings => {
|
||||
// Use the user-provided docstring.
|
||||
for key in ["docstring-convention", "docstring_convention"] {
|
||||
|
@ -83,6 +86,7 @@ impl Plugin {
|
|||
Plugin::Flake8Print => vec![CheckCodePrefix::T],
|
||||
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
|
||||
Plugin::McCabe => vec![CheckCodePrefix::C9],
|
||||
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
|
||||
Plugin::Pyupgrade => vec![CheckCodePrefix::U],
|
||||
}
|
||||
|
@ -326,6 +330,10 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
|
|||
"banned-modules" | "banned_modules" => {
|
||||
plugins.insert(Plugin::Flake8TidyImports);
|
||||
}
|
||||
// mccabe
|
||||
"max-complexity" | "max_complexity" => {
|
||||
plugins.insert(Plugin::McCabe);
|
||||
}
|
||||
// pep8-naming
|
||||
"ignore-names" | "ignore_names" => {
|
||||
plugins.insert(Plugin::PEP8Naming);
|
||||
|
|
108
resources/test/fixtures/C901.py
vendored
Normal file
108
resources/test/fixtures/C901.py
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
# Complexity = 1
|
||||
def trivial():
|
||||
pass
|
||||
|
||||
|
||||
# Complexity = 1
|
||||
def expr_as_statement():
|
||||
0xF00D
|
||||
|
||||
|
||||
# Complexity = 1
|
||||
def sequential(n):
|
||||
k = n + 4
|
||||
s = k + n
|
||||
return s
|
||||
|
||||
|
||||
# Complexity = 3
|
||||
def if_elif_else_dead_path(n):
|
||||
if n > 3:
|
||||
return "bigger than three"
|
||||
elif n > 4:
|
||||
return "is never executed"
|
||||
else:
|
||||
return "smaller than or equal to three"
|
||||
|
||||
|
||||
# Complexity = 3
|
||||
def nested_ifs():
|
||||
if n > 3:
|
||||
if n > 4:
|
||||
return "bigger than four"
|
||||
else:
|
||||
return "bigger than three"
|
||||
else:
|
||||
return "smaller than or equal to three"
|
||||
|
||||
|
||||
# Complexity = 2
|
||||
def for_loop():
|
||||
for i in range(10):
|
||||
print(i)
|
||||
|
||||
|
||||
# Complexity = 2
|
||||
def for_else(mylist):
|
||||
for i in mylist:
|
||||
print(i)
|
||||
else:
|
||||
print(None)
|
||||
|
||||
|
||||
# Complexity = 2
|
||||
def recursive(n):
|
||||
if n > 4:
|
||||
return f(n - 1)
|
||||
else:
|
||||
return n
|
||||
|
||||
|
||||
# Complexity = 3
|
||||
def nested_functions():
|
||||
def a():
|
||||
def b():
|
||||
pass
|
||||
|
||||
b()
|
||||
|
||||
a()
|
||||
|
||||
|
||||
# Complexity = 4
|
||||
def try_else():
|
||||
try:
|
||||
print(1)
|
||||
except TypeA:
|
||||
print(2)
|
||||
except TypeB:
|
||||
print(3)
|
||||
else:
|
||||
print(4)
|
||||
|
||||
|
||||
# Complexity = 3
|
||||
def nested_try_finally():
|
||||
try:
|
||||
try:
|
||||
print(1)
|
||||
finally:
|
||||
print(2)
|
||||
finally:
|
||||
print(3)
|
||||
|
||||
|
||||
# Complexity = 3
|
||||
async def foobar(a, b, c):
|
||||
await whatever(a, b, c)
|
||||
if await b:
|
||||
pass
|
||||
async with c:
|
||||
pass
|
||||
async for x in a:
|
||||
pass
|
||||
|
||||
|
||||
# Complexity = 1
|
||||
def annotated_assign():
|
||||
x: Any = None
|
3
resources/test/fixtures/pyproject.toml
vendored
3
resources/test/fixtures/pyproject.toml
vendored
|
@ -16,6 +16,9 @@ multiline-quotes = "double"
|
|||
docstring-quotes = "double"
|
||||
avoid-escape = true
|
||||
|
||||
[tool.ruff.mccabe]
|
||||
max-complexity = 10
|
||||
|
||||
[tool.ruff.pep8-naming]
|
||||
ignore-names = [
|
||||
"setUp",
|
||||
|
|
|
@ -36,8 +36,8 @@ use crate::source_code_locator::SourceCodeLocator;
|
|||
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
|
||||
use crate::{
|
||||
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
|
||||
flake8_comprehensions, flake8_print, flake8_tidy_imports, pep8_naming, pycodestyle, pydocstyle,
|
||||
pyflakes, pyupgrade,
|
||||
flake8_comprehensions, flake8_print, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle,
|
||||
pydocstyle, pyflakes, pyupgrade,
|
||||
};
|
||||
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
|
@ -349,6 +349,16 @@ where
|
|||
if self.settings.enabled.contains(&CheckCode::B019) {
|
||||
flake8_bugbear::plugins::cached_instance_method(self, decorator_list);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::C901) {
|
||||
if let Some(check) = mccabe::checks::function_is_too_complex(
|
||||
stmt,
|
||||
name,
|
||||
body,
|
||||
self.settings.mccabe.max_complexity,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::S107) {
|
||||
self.add_checks(
|
||||
|
|
|
@ -120,6 +120,8 @@ pub enum CheckCode {
|
|||
C415,
|
||||
C416,
|
||||
C417,
|
||||
// mccabe
|
||||
C901,
|
||||
// flake8-tidy-imports
|
||||
I252,
|
||||
// flake8-print
|
||||
|
@ -260,6 +262,7 @@ pub enum CheckCategory {
|
|||
Flake8Quotes,
|
||||
Flake8Annotations,
|
||||
Flake82020,
|
||||
McCabe,
|
||||
Ruff,
|
||||
Meta,
|
||||
}
|
||||
|
@ -282,6 +285,7 @@ impl CheckCategory {
|
|||
CheckCategory::Pyupgrade => "pyupgrade",
|
||||
CheckCategory::Pydocstyle => "pydocstyle",
|
||||
CheckCategory::PEP8Naming => "pep8-naming",
|
||||
CheckCategory::McCabe => "mccabe",
|
||||
CheckCategory::Ruff => "Ruff-specific rules",
|
||||
CheckCategory::Meta => "Meta rules",
|
||||
}
|
||||
|
@ -314,6 +318,7 @@ impl CheckCategory {
|
|||
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
|
||||
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
|
||||
CheckCategory::Flake8Bandit => Some("https://pypi.org/project/flake8-bandit/4.1.1/"),
|
||||
CheckCategory::McCabe => Some("https://pypi.org/project/mccabe/0.7.0/"),
|
||||
CheckCategory::Ruff => None,
|
||||
CheckCategory::Meta => None,
|
||||
}
|
||||
|
@ -546,6 +551,8 @@ pub enum CheckKind {
|
|||
HardcodedPasswordString(String),
|
||||
HardcodedPasswordFuncArg(String),
|
||||
HardcodedPasswordDefault(String),
|
||||
// mccabe
|
||||
FunctionIsTooComplex(String, usize),
|
||||
// Ruff
|
||||
AmbiguousUnicodeCharacterString(char, char),
|
||||
AmbiguousUnicodeCharacterDocstring(char, char),
|
||||
|
@ -826,6 +833,7 @@ impl CheckCode {
|
|||
CheckCode::S105 => CheckKind::HardcodedPasswordString("...".to_string()),
|
||||
CheckCode::S106 => CheckKind::HardcodedPasswordFuncArg("...".to_string()),
|
||||
CheckCode::S107 => CheckKind::HardcodedPasswordDefault("...".to_string()),
|
||||
CheckCode::C901 => CheckKind::FunctionIsTooComplex("...".to_string(), 10),
|
||||
// Ruff
|
||||
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
|
||||
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
|
||||
|
@ -1030,6 +1038,7 @@ impl CheckCode {
|
|||
CheckCode::S105 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::S106 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::S107 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::C901 => CheckCategory::McCabe,
|
||||
CheckCode::RUF001 => CheckCategory::Ruff,
|
||||
CheckCode::RUF002 => CheckCategory::Ruff,
|
||||
CheckCode::RUF003 => CheckCategory::Ruff,
|
||||
|
@ -1250,6 +1259,8 @@ impl CheckKind {
|
|||
CheckKind::HardcodedPasswordString(..) => &CheckCode::S105,
|
||||
CheckKind::HardcodedPasswordFuncArg(..) => &CheckCode::S106,
|
||||
CheckKind::HardcodedPasswordDefault(..) => &CheckCode::S107,
|
||||
// McCabe
|
||||
CheckKind::FunctionIsTooComplex(..) => &CheckCode::C901,
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
|
||||
|
@ -1902,6 +1913,10 @@ impl CheckKind {
|
|||
CheckKind::HardcodedPasswordDefault(string) => {
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
}
|
||||
// McCabe
|
||||
CheckKind::FunctionIsTooComplex(name, complexity) => {
|
||||
format!("`{name}` is too complex ({complexity})")
|
||||
}
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
|
||||
format!(
|
||||
|
|
|
@ -83,6 +83,9 @@ pub enum CheckCodePrefix {
|
|||
C415,
|
||||
C416,
|
||||
C417,
|
||||
C9,
|
||||
C90,
|
||||
C901,
|
||||
D,
|
||||
D1,
|
||||
D10,
|
||||
|
@ -497,6 +500,7 @@ impl CheckCodePrefix {
|
|||
CheckCode::C415,
|
||||
CheckCode::C416,
|
||||
CheckCode::C417,
|
||||
CheckCode::C901,
|
||||
],
|
||||
CheckCodePrefix::C4 => vec![
|
||||
CheckCode::C400,
|
||||
|
@ -552,6 +556,9 @@ impl CheckCodePrefix {
|
|||
CheckCodePrefix::C415 => vec![CheckCode::C415],
|
||||
CheckCodePrefix::C416 => vec![CheckCode::C416],
|
||||
CheckCodePrefix::C417 => vec![CheckCode::C417],
|
||||
CheckCodePrefix::C9 => vec![CheckCode::C901],
|
||||
CheckCodePrefix::C90 => vec![CheckCode::C901],
|
||||
CheckCodePrefix::C901 => vec![CheckCode::C901],
|
||||
CheckCodePrefix::D => vec![
|
||||
CheckCode::D100,
|
||||
CheckCode::D101,
|
||||
|
@ -1246,6 +1253,9 @@ impl CheckCodePrefix {
|
|||
CheckCodePrefix::C415 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::C416 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::C417 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::C9 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::C90 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::C901 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::D => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::D1 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::D10 => PrefixSpecificity::Tens,
|
||||
|
|
|
@ -91,6 +91,9 @@ pub struct Cli {
|
|||
/// formatting.
|
||||
#[arg(long)]
|
||||
pub line_length: Option<usize>,
|
||||
/// Max McCabe complexity allowed for a function.
|
||||
#[arg(long)]
|
||||
pub max_complexity: Option<usize>,
|
||||
/// Round-trip auto-formatting.
|
||||
// TODO(charlie): This should be a sub-command.
|
||||
#[arg(long, hide = true)]
|
||||
|
|
|
@ -41,6 +41,7 @@ mod isort;
|
|||
mod lex;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
pub mod mccabe;
|
||||
pub mod message;
|
||||
mod noqa;
|
||||
pub mod pep8_naming;
|
||||
|
|
|
@ -269,6 +269,9 @@ fn inner_main() -> Result<ExitCode> {
|
|||
if let Some(line_length) = cli.line_length {
|
||||
configuration.line_length = line_length;
|
||||
}
|
||||
if let Some(max_complexity) = cli.max_complexity {
|
||||
configuration.mccabe.max_complexity = max_complexity;
|
||||
}
|
||||
if let Some(target_version) = cli.target_version {
|
||||
configuration.target_version = target_version;
|
||||
}
|
||||
|
|
73
src/mccabe/checks.rs
Normal file
73
src/mccabe/checks.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use rustpython_ast::{ExcepthandlerKind, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
fn get_complexity_number(stmts: &[Stmt]) -> usize {
|
||||
let mut complexity = 0;
|
||||
for stmt in stmts {
|
||||
match &stmt.node {
|
||||
StmtKind::If { body, orelse, .. } => {
|
||||
complexity += 1;
|
||||
complexity += get_complexity_number(body);
|
||||
complexity += get_complexity_number(orelse);
|
||||
}
|
||||
StmtKind::For { body, orelse, .. } | StmtKind::AsyncFor { body, orelse, .. } => {
|
||||
complexity += 1;
|
||||
complexity += get_complexity_number(body);
|
||||
complexity += get_complexity_number(orelse);
|
||||
}
|
||||
StmtKind::While { test, body, orelse } => {
|
||||
complexity += 1;
|
||||
complexity += get_complexity_number(body);
|
||||
complexity += get_complexity_number(orelse);
|
||||
if let ExprKind::BoolOp { .. } = &test.node {
|
||||
complexity += 1;
|
||||
}
|
||||
}
|
||||
StmtKind::Try {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
} => {
|
||||
complexity += 1;
|
||||
complexity += get_complexity_number(body);
|
||||
complexity += get_complexity_number(orelse);
|
||||
complexity += get_complexity_number(finalbody);
|
||||
for handler in handlers {
|
||||
complexity += 1;
|
||||
let ExcepthandlerKind::ExceptHandler { body, .. } = &handler.node;
|
||||
complexity += get_complexity_number(body);
|
||||
}
|
||||
}
|
||||
StmtKind::FunctionDef { body, .. } | StmtKind::AsyncFunctionDef { body, .. } => {
|
||||
complexity += 1;
|
||||
complexity += get_complexity_number(body);
|
||||
}
|
||||
StmtKind::ClassDef { body, .. } => {
|
||||
complexity += 1;
|
||||
complexity += get_complexity_number(body);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
complexity
|
||||
}
|
||||
|
||||
pub fn function_is_too_complex(
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
body: &[Stmt],
|
||||
max_complexity: usize,
|
||||
) -> Option<Check> {
|
||||
let complexity = get_complexity_number(body) + 1;
|
||||
if complexity > max_complexity {
|
||||
Some(Check::new(
|
||||
CheckKind::FunctionIsTooComplex(name.to_string(), complexity),
|
||||
Range::from_located(stmt),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
33
src/mccabe/mod.rs
Normal file
33
src/mccabe/mod.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
pub mod checks;
|
||||
pub mod settings;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::{mccabe, Settings};
|
||||
|
||||
#[test_case(0)]
|
||||
#[test_case(3)]
|
||||
#[test_case(10)]
|
||||
fn max_complexity_zero(max_complexity: usize) -> Result<()> {
|
||||
let snapshot = format!("max_complexity_{}", max_complexity);
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/C901.py"),
|
||||
&Settings {
|
||||
mccabe: mccabe::settings::Settings { max_complexity },
|
||||
..Settings::for_rules(vec![CheckCode::C901])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
28
src/mccabe/settings.rs
Normal file
28
src/mccabe/settings.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
//! Settings for the `mccabe` plugin.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub max_complexity: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct Settings {
|
||||
pub max_complexity: usize,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
Self {
|
||||
max_complexity: options.max_complexity.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self { max_complexity: 10 }
|
||||
}
|
||||
}
|
170
src/mccabe/snapshots/ruff__mccabe__tests__max_complexity_0.snap
Normal file
170
src/mccabe/snapshots/ruff__mccabe__tests__max_complexity_0.snap
Normal file
|
@ -0,0 +1,170 @@
|
|||
---
|
||||
source: src/mccabe/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- trivial
|
||||
- 1
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- expr_as_statement
|
||||
- 1
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- sequential
|
||||
- 1
|
||||
location:
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 19
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- if_elif_else_dead_path
|
||||
- 3
|
||||
location:
|
||||
row: 19
|
||||
column: 0
|
||||
end_location:
|
||||
row: 29
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- nested_ifs
|
||||
- 3
|
||||
location:
|
||||
row: 29
|
||||
column: 0
|
||||
end_location:
|
||||
row: 40
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- for_loop
|
||||
- 2
|
||||
location:
|
||||
row: 40
|
||||
column: 0
|
||||
end_location:
|
||||
row: 46
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- for_else
|
||||
- 2
|
||||
location:
|
||||
row: 46
|
||||
column: 0
|
||||
end_location:
|
||||
row: 54
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- recursive
|
||||
- 2
|
||||
location:
|
||||
row: 54
|
||||
column: 0
|
||||
end_location:
|
||||
row: 62
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- nested_functions
|
||||
- 3
|
||||
location:
|
||||
row: 62
|
||||
column: 0
|
||||
end_location:
|
||||
row: 73
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- a
|
||||
- 2
|
||||
location:
|
||||
row: 63
|
||||
column: 4
|
||||
end_location:
|
||||
row: 69
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- b
|
||||
- 1
|
||||
location:
|
||||
row: 64
|
||||
column: 8
|
||||
end_location:
|
||||
row: 67
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- try_else
|
||||
- 4
|
||||
location:
|
||||
row: 73
|
||||
column: 0
|
||||
end_location:
|
||||
row: 85
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- nested_try_finally
|
||||
- 3
|
||||
location:
|
||||
row: 85
|
||||
column: 0
|
||||
end_location:
|
||||
row: 96
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- foobar
|
||||
- 3
|
||||
location:
|
||||
row: 96
|
||||
column: 0
|
||||
end_location:
|
||||
row: 107
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- annotated_assign
|
||||
- 1
|
||||
location:
|
||||
row: 107
|
||||
column: 0
|
||||
end_location:
|
||||
row: 109
|
||||
column: 0
|
||||
fix: ~
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
source: src/mccabe/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
source: src/mccabe/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
FunctionIsTooComplex:
|
||||
- try_else
|
||||
- 4
|
||||
location:
|
||||
row: 73
|
||||
column: 0
|
||||
end_location:
|
||||
row: 85
|
||||
column: 0
|
||||
fix: ~
|
||||
|
|
@ -13,7 +13,8 @@ use crate::checks_gen::CheckCodePrefix;
|
|||
use crate::settings::pyproject::load_options;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::{
|
||||
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, pep8_naming,
|
||||
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe,
|
||||
pep8_naming,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -36,6 +37,7 @@ pub struct Configuration {
|
|||
pub flake8_quotes: flake8_quotes::settings::Settings,
|
||||
pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings,
|
||||
pub isort: isort::settings::Settings,
|
||||
pub mccabe: mccabe::settings::Settings,
|
||||
pub pep8_naming: pep8_naming::settings::Settings,
|
||||
}
|
||||
|
||||
|
@ -153,6 +155,10 @@ impl Configuration {
|
|||
.isort
|
||||
.map(isort::settings::Settings::from_options)
|
||||
.unwrap_or_default(),
|
||||
mccabe: options
|
||||
.mccabe
|
||||
.map(mccabe::settings::Settings::from_options)
|
||||
.unwrap_or_default(),
|
||||
pep8_naming: options
|
||||
.pep8_naming
|
||||
.map(pep8_naming::settings::Settings::from_options)
|
||||
|
|
|
@ -14,7 +14,8 @@ use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
|
|||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::{
|
||||
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, pep8_naming,
|
||||
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, mccabe,
|
||||
pep8_naming,
|
||||
};
|
||||
|
||||
pub mod configuration;
|
||||
|
@ -39,6 +40,7 @@ pub struct Settings {
|
|||
pub flake8_quotes: flake8_quotes::settings::Settings,
|
||||
pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings,
|
||||
pub isort: isort::settings::Settings,
|
||||
pub mccabe: mccabe::settings::Settings,
|
||||
pub pep8_naming: pep8_naming::settings::Settings,
|
||||
}
|
||||
|
||||
|
@ -59,6 +61,7 @@ impl Settings {
|
|||
flake8_quotes: config.flake8_quotes,
|
||||
flake8_tidy_imports: config.flake8_tidy_imports,
|
||||
isort: config.isort,
|
||||
mccabe: config.mccabe,
|
||||
line_length: config.line_length,
|
||||
pep8_naming: config.pep8_naming,
|
||||
per_file_ignores: config.per_file_ignores,
|
||||
|
@ -82,6 +85,7 @@ impl Settings {
|
|||
flake8_quotes: Default::default(),
|
||||
flake8_tidy_imports: Default::default(),
|
||||
isort: Default::default(),
|
||||
mccabe: Default::default(),
|
||||
pep8_naming: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +105,7 @@ impl Settings {
|
|||
flake8_quotes: Default::default(),
|
||||
flake8_tidy_imports: Default::default(),
|
||||
isort: Default::default(),
|
||||
mccabe: Default::default(),
|
||||
pep8_naming: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +129,7 @@ impl Hash for Settings {
|
|||
self.flake8_quotes.hash(state);
|
||||
self.flake8_tidy_imports.hash(state);
|
||||
self.isort.hash(state);
|
||||
self.mccabe.hash(state);
|
||||
self.pep8_naming.hash(state);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::{
|
||||
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, pep8_naming,
|
||||
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, mccabe,
|
||||
pep8_naming,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
|
@ -29,6 +30,7 @@ pub struct Options {
|
|||
pub flake8_quotes: Option<flake8_quotes::settings::Options>,
|
||||
pub flake8_tidy_imports: Option<flake8_tidy_imports::settings::Options>,
|
||||
pub isort: Option<isort::settings::Options>,
|
||||
pub mccabe: Option<mccabe::settings::Options>,
|
||||
pub pep8_naming: Option<pep8_naming::settings::Options>,
|
||||
// Tables are required to go last.
|
||||
pub per_file_ignores: Option<FnvHashMap<String, Vec<CheckCodePrefix>>>,
|
||||
|
|
|
@ -110,7 +110,7 @@ mod tests {
|
|||
find_project_root, find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools,
|
||||
};
|
||||
use crate::settings::types::PatternPrefixPair;
|
||||
use crate::{flake8_bugbear, flake8_quotes, flake8_tidy_imports, pep8_naming};
|
||||
use crate::{flake8_bugbear, flake8_quotes, flake8_tidy_imports, mccabe, pep8_naming};
|
||||
|
||||
#[test]
|
||||
fn deserialize() -> Result<()> {
|
||||
|
@ -151,6 +151,7 @@ mod tests {
|
|||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
})
|
||||
})
|
||||
|
@ -184,6 +185,7 @@ line-length = 79
|
|||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
})
|
||||
})
|
||||
|
@ -217,6 +219,7 @@ exclude = ["foo.py"]
|
|||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
})
|
||||
})
|
||||
|
@ -250,6 +253,7 @@ select = ["E501"]
|
|||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
})
|
||||
})
|
||||
|
@ -284,6 +288,7 @@ ignore = ["E501"]
|
|||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
})
|
||||
})
|
||||
|
@ -376,6 +381,9 @@ other-attribute = 1
|
|||
ban_relative_imports: Some(Strictness::Parents)
|
||||
}),
|
||||
isort: None,
|
||||
mccabe: Some(mccabe::settings::Options {
|
||||
max_complexity: Some(10),
|
||||
}),
|
||||
pep8_naming: Some(pep8_naming::settings::Options {
|
||||
ignore_names: Some(vec![
|
||||
"setUp".to_string(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue