Implement pylint too-many-lines (C0302)

This commit is contained in:
tluolamo 2025-12-08 13:10:10 -08:00
parent e548ce1ca9
commit ada46d883d
22 changed files with 228 additions and 1 deletions

View File

@ -259,6 +259,7 @@ linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20 linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15 linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pylint.max_module_lines = 1000
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false

View File

@ -261,6 +261,7 @@ linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20 linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15 linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pylint.max_module_lines = 1000
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false

View File

@ -263,6 +263,7 @@ linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20 linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15 linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pylint.max_module_lines = 1000
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false

View File

@ -263,6 +263,7 @@ linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20 linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15 linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pylint.max_module_lines = 1000
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false

View File

@ -260,6 +260,7 @@ linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20 linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15 linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pylint.max_module_lines = 1000
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false

View File

@ -260,6 +260,7 @@ linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20 linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15 linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pylint.max_module_lines = 1000
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false

View File

@ -259,6 +259,7 @@ linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20 linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15 linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pylint.max_module_lines = 1000
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false

View File

@ -259,6 +259,7 @@ linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20 linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15 linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pylint.max_module_lines = 1000
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false

View File

@ -259,6 +259,7 @@ linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20 linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15 linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pylint.max_module_lines = 1000
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false

View File

@ -372,6 +372,7 @@ linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20 linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15 linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pylint.max_module_lines = 1000
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false

View File

@ -78,6 +78,10 @@ pub(crate) fn check_physical_lines(
if enforce_copyright_notice { if enforce_copyright_notice {
missing_copyright_notice(locator, settings, context); missing_copyright_notice(locator, settings, context);
} }
if context.is_rule_enabled(Rule::TooManyLines) {
pylint::rules::too_many_lines(locator, settings, context);
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -221,6 +221,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "C0206") => rules::pylint::rules::DictIndexMissingItems, (Pylint, "C0206") => rules::pylint::rules::DictIndexMissingItems,
(Pylint, "C0207") => rules::pylint::rules::MissingMaxsplitArg, (Pylint, "C0207") => rules::pylint::rules::MissingMaxsplitArg,
(Pylint, "C0208") => rules::pylint::rules::IterationOverSet, (Pylint, "C0208") => rules::pylint::rules::IterationOverSet,
(Pylint, "C0302") => rules::pylint::rules::TooManyLines,
(Pylint, "C0414") => rules::pylint::rules::UselessImportAlias, (Pylint, "C0414") => rules::pylint::rules::UselessImportAlias,
(Pylint, "C0415") => rules::pylint::rules::ImportOutsideTopLevel, (Pylint, "C0415") => rules::pylint::rules::ImportOutsideTopLevel,
(Pylint, "C1802") => rules::pylint::rules::LenTest, (Pylint, "C1802") => rules::pylint::rules::LenTest,

View File

@ -259,6 +259,7 @@ impl Rule {
| Rule::MissingCopyrightNotice | Rule::MissingCopyrightNotice
| Rule::MissingNewlineAtEndOfFile | Rule::MissingNewlineAtEndOfFile
| Rule::MixedSpacesAndTabs | Rule::MixedSpacesAndTabs
| Rule::TooManyLines
| Rule::TrailingWhitespace => LintSource::PhysicalLines, | Rule::TrailingWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment Rule::AmbiguousUnicodeCharacterComment
| Rule::BlanketTypeIgnore | Rule::BlanketTypeIgnore

View File

@ -0,0 +1,33 @@
//! Settings for the `flake8-lineleak` plugin.
use std::fmt::{Display, Formatter};
use ruff_macros::CacheKey;
use crate::display_settings;
#[derive(Debug, Clone, CacheKey)]
pub struct Settings {
pub max_line_count: usize,
}
impl Default for Settings {
fn default() -> Self {
Self {
max_line_count: 100,
}
}
}
impl Display for Settings {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
display_settings! {
formatter = f,
namespace = "linter.flake8_lineleak",
fields = [
self.max_line_count,
]
}
Ok(())
}
}

View File

@ -444,4 +444,90 @@ mod tests {
assert_diagnostics!(diagnostics); assert_diagnostics!(diagnostics);
Ok(()) Ok(())
} }
#[test]
fn too_many_lines_below_limit() {
use crate::test::test_snippet;
let diagnostics = test_snippet(
r"
# Line 1
# Line 2
# Line 3
import os
"
.trim(),
&LinterSettings {
preview: PreviewMode::Enabled,
pylint: pylint::settings::Settings {
max_module_lines: 1000,
..pylint::settings::Settings::default()
},
..LinterSettings::for_rules(vec![Rule::TooManyLines])
},
);
assert_diagnostics!(diagnostics);
}
#[test]
fn too_many_lines_exceeds_limit() {
use crate::test::test_snippet;
let diagnostics = test_snippet(
r"
# Line 1
# Line 2
# Line 3
# Line 4
# Line 5
# Line 6
# Line 7
# Line 8
# Line 9
# Line 10
# Line 11
import os
"
.trim(),
&LinterSettings {
preview: PreviewMode::Enabled,
pylint: pylint::settings::Settings {
max_module_lines: 10,
..pylint::settings::Settings::default()
},
..LinterSettings::for_rules(vec![Rule::TooManyLines])
},
);
assert!(!diagnostics.is_empty());
}
#[test]
fn too_many_lines_at_limit() {
use crate::test::test_snippet;
let diagnostics = test_snippet(
r"
# Line 1
# Line 2
# Line 3
# Line 4
# Line 5
# Line 6
# Line 7
# Line 8
# Line 9
# Line 10
"
.trim(),
&LinterSettings {
preview: PreviewMode::Enabled,
pylint: pylint::settings::Settings {
max_module_lines: 10,
..pylint::settings::Settings::default()
},
..LinterSettings::for_rules(vec![Rule::TooManyLines])
},
);
assert_diagnostics!(diagnostics);
}
} }

View File

@ -83,6 +83,7 @@ pub(crate) use sys_exit_alias::*;
pub(crate) use too_many_arguments::*; pub(crate) use too_many_arguments::*;
pub(crate) use too_many_boolean_expressions::*; pub(crate) use too_many_boolean_expressions::*;
pub(crate) use too_many_branches::*; pub(crate) use too_many_branches::*;
pub(crate) use too_many_lines::*;
pub(crate) use too_many_locals::*; pub(crate) use too_many_locals::*;
pub(crate) use too_many_nested_blocks::*; pub(crate) use too_many_nested_blocks::*;
pub(crate) use too_many_positional_arguments::*; pub(crate) use too_many_positional_arguments::*;
@ -194,6 +195,7 @@ mod sys_exit_alias;
mod too_many_arguments; mod too_many_arguments;
mod too_many_boolean_expressions; mod too_many_boolean_expressions;
mod too_many_branches; mod too_many_branches;
mod too_many_lines;
mod too_many_locals; mod too_many_locals;
mod too_many_nested_blocks; mod too_many_nested_blocks;
mod too_many_positional_arguments; mod too_many_positional_arguments;

View File

@ -0,0 +1,58 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_text_size::TextRange;
use crate::Locator;
use crate::Violation;
use crate::checkers::ast::LintContext;
use crate::settings::LinterSettings;
/// ## What it does
/// Checks for modules with too many lines.
///
/// By default, this rule allows up to 1000 lines, as configured by the
/// [`lint.pylint.max-module-lines`] option.
///
/// ## Why is this bad?
/// Modules with many lines are generally harder to read and understand.
/// Extracting functionality into separate modules can improve code organization
/// and maintainability.
///
/// ## Example
/// A module with 1500 lines when `max-module-lines` is set to 1000 will trigger
/// this rule.
///
/// ## Options
/// - `lint.pylint.max-module-lines`
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "v0.14.9")]
pub(crate) struct TooManyLines {
actual_lines: usize,
max_lines: usize,
}
impl Violation for TooManyLines {
#[derive_message_formats]
fn message(&self) -> String {
let TooManyLines {
actual_lines,
max_lines,
} = self;
format!("Too many lines in module ({actual_lines}/{max_lines})")
}
}
/// C0302
pub(crate) fn too_many_lines(locator: &Locator, settings: &LinterSettings, context: &LintContext) {
let actual_lines = locator.contents().lines().count();
let max_lines = settings.pylint.max_module_lines;
if actual_lines > max_lines {
context.report_diagnostic(
TooManyLines {
actual_lines,
max_lines,
},
TextRange::default(),
);
}
}

View File

@ -61,6 +61,7 @@ pub struct Settings {
pub max_public_methods: usize, pub max_public_methods: usize,
pub max_locals: usize, pub max_locals: usize,
pub max_nested_blocks: usize, pub max_nested_blocks: usize,
pub max_module_lines: usize,
} }
impl Default for Settings { impl Default for Settings {
@ -77,6 +78,7 @@ impl Default for Settings {
max_public_methods: 20, max_public_methods: 20,
max_locals: 15, max_locals: 15,
max_nested_blocks: 5, max_nested_blocks: 5,
max_module_lines: 1000,
} }
} }
} }
@ -97,7 +99,8 @@ impl fmt::Display for Settings {
self.max_statements, self.max_statements,
self.max_public_methods, self.max_public_methods,
self.max_locals, self.max_locals,
self.max_nested_blocks self.max_nested_blocks,
self.max_module_lines
] ]
} }
Ok(()) Ok(())

View File

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---

View File

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---

View File

@ -3343,6 +3343,14 @@ pub struct PylintOptions {
example = r"max-nested-blocks = 10" example = r"max-nested-blocks = 10"
)] )]
pub max_nested_blocks: Option<usize>, pub max_nested_blocks: Option<usize>,
/// Maximum number of lines allowed in a module (see `PLC0302`).
#[option(
default = r"1000",
value_type = "int",
example = r"max-module-lines = 1500"
)]
pub max_module_lines: Option<usize>,
} }
impl PylintOptions { impl PylintOptions {
@ -3367,6 +3375,7 @@ impl PylintOptions {
.unwrap_or(defaults.max_public_methods), .unwrap_or(defaults.max_public_methods),
max_locals: self.max_locals.unwrap_or(defaults.max_locals), max_locals: self.max_locals.unwrap_or(defaults.max_locals),
max_nested_blocks: self.max_nested_blocks.unwrap_or(defaults.max_nested_blocks), max_nested_blocks: self.max_nested_blocks.unwrap_or(defaults.max_nested_blocks),
max_module_lines: self.max_module_lines.unwrap_or(defaults.max_module_lines),
} }
} }
} }

12
ruff.schema.json generated
View File

@ -2794,6 +2794,15 @@
"format": "uint", "format": "uint",
"minimum": 0 "minimum": 0
}, },
"max-module-lines": {
"description": "Maximum number of lines allowed in a module (see `PLC0302`).",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0
},
"max-nested-blocks": { "max-nested-blocks": {
"description": "Maximum number of nested blocks allowed within a function or method body\n(see `PLR1702`).", "description": "Maximum number of nested blocks allowed within a function or method body\n(see `PLR1702`).",
"type": [ "type": [
@ -3590,6 +3599,9 @@
"PLC0206", "PLC0206",
"PLC0207", "PLC0207",
"PLC0208", "PLC0208",
"PLC03",
"PLC030",
"PLC0302",
"PLC04", "PLC04",
"PLC041", "PLC041",
"PLC0414", "PLC0414",