rude-clean-code 0.0.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. rude_clean_code-0.2.0/.gitignore +17 -0
  2. rude_clean_code-0.2.0/LICENSE +21 -0
  3. rude_clean_code-0.2.0/PKG-INFO +96 -0
  4. rude_clean_code-0.2.0/README.md +69 -0
  5. rude_clean_code-0.2.0/features/deterministic_rules.feature +31 -0
  6. rude_clean_code-0.2.0/features/module_rules.feature +24 -0
  7. rude_clean_code-0.2.0/features/package_version.feature +8 -0
  8. rude_clean_code-0.2.0/pyproject.toml +57 -0
  9. rude_clean_code-0.2.0/src/rude_clean_code/__init__.py +7 -0
  10. rude_clean_code-0.2.0/src/rude_clean_code/_scope.py +186 -0
  11. rude_clean_code-0.2.0/src/rude_clean_code/plugin.py +30 -0
  12. rude_clean_code-0.2.0/src/rude_clean_code/rules/__init__.py +1 -0
  13. rude_clean_code-0.2.0/src/rude_clean_code/rules/_module_analysis.py +293 -0
  14. rude_clean_code-0.2.0/src/rude_clean_code/rules/classes.py +175 -0
  15. rude_clean_code-0.2.0/src/rude_clean_code/rules/complexity.py +262 -0
  16. rude_clean_code-0.2.0/src/rude_clean_code/rules/dry.py +110 -0
  17. rude_clean_code-0.2.0/src/rude_clean_code/rules/errors.py +233 -0
  18. rude_clean_code-0.2.0/src/rude_clean_code/rules/functions.py +283 -0
  19. rude_clean_code-0.2.0/src/rude_clean_code/rules/heuristics.py +115 -0
  20. rude_clean_code-0.2.0/src/rude_clean_code/rules/modules.py +364 -0
  21. rude_clean_code-0.2.0/src/rude_clean_code/rules/naming.py +350 -0
  22. rude_clean_code-0.2.0/tests/test_class_rules.py +119 -0
  23. rude_clean_code-0.2.0/tests/test_complexity_rules.py +231 -0
  24. rude_clean_code-0.2.0/tests/test_deterministic_rules_bdd.py +311 -0
  25. rude_clean_code-0.2.0/tests/test_function_rules.py +271 -0
  26. rude_clean_code-0.2.0/tests/test_heuristic_rules.py +231 -0
  27. rude_clean_code-0.2.0/tests/test_module_rules.py +402 -0
  28. rude_clean_code-0.2.0/tests/test_module_rules_bdd.py +218 -0
  29. rude_clean_code-0.2.0/tests/test_package_version_bdd.py +81 -0
  30. rude_clean_code-0.2.0/tests/test_plugin_discovery.py +147 -0
  31. rude_clean_code-0.2.0/tests/test_scope_traversal.py +101 -0
  32. rude_clean_code-0.2.0/tests/test_structural_and_error_rules.py +281 -0
  33. rude_clean_code-0.0.0/PKG-INFO +0 -4
  34. rude_clean_code-0.0.0/pyproject.toml +0 -11
  35. rude_clean_code-0.0.0/setup.cfg +0 -4
  36. rude_clean_code-0.0.0/src/rude_clean_code.egg-info/PKG-INFO +0 -4
  37. rude_clean_code-0.0.0/src/rude_clean_code.egg-info/SOURCES.txt +0 -6
  38. rude_clean_code-0.0.0/src/rude_clean_code.egg-info/dependency_links.txt +0 -1
  39. rude_clean_code-0.0.0/src/rude_clean_code.egg-info/top_level.txt +0 -1
  40. /rude_clean_code-0.0.0/src/rude_clean_code/__init__.py → /rude_clean_code-0.2.0/src/rude_clean_code/py.typed +0 -0
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .pixi/
4
+ .venv/
5
+ dist/
6
+ build/
7
+ distributions/
8
+ .coverage
9
+ .coverage.*
10
+ coverage.xml
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ .hypothesis/
15
+ node_modules/
16
+ .tox/
17
+ .doit.db*
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Archont561
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: rude-clean-code
3
+ Version: 0.2.0
4
+ Summary: Clean Code plugin for the Rude Python linter.
5
+ Project-URL: Homepage, https://github.com/Archont561/rude-clean-code#readme
6
+ Project-URL: Documentation, https://github.com/Archont561/rude-clean-code/blob/main/docs/README.md
7
+ Project-URL: Repository, https://github.com/Archont561/rude-clean-code
8
+ Project-URL: Issues, https://github.com/Archont561/rude-clean-code/issues
9
+ Project-URL: Changelog, https://github.com/Archont561/rude-clean-code/blob/main/CHANGELOG.md
10
+ Author: Archont561
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: clean-code,code-quality,linter,rude,solid,static-analysis
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: rude<0.2,>=0.1a4
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Rude Clean Code
29
+
30
+ Clean Code plugin for the [Rude](https://pypi.org/project/rude/) Python linter,
31
+ exposing 26 Clean Code rules across module structure, functions, classes,
32
+ complexity, error handling, naming/literals, same-file duplication, and
33
+ polymorphism indicators (`CC001`–`CC601`) through Rude's `rude.plugins`
34
+ entry point.
35
+
36
+ ## Installation
37
+
38
+ ```sh
39
+ pip install rude-clean-code
40
+ ```
41
+
42
+ Rude discovers the plugin automatically via `rude.plugins` once installed.
43
+
44
+ ## Configuration profiles
45
+
46
+ Add the **Basic** profile snippet (`CC0`–`CC3`) to your `pyproject.toml`, or
47
+ include `"CC4"`, `"CC5"`, `"CC6"` for the **Strict** profile with heuristic
48
+ indicators:
49
+
50
+ ```toml
51
+ [tool.rude]
52
+ select = ["CC0", "CC1", "CC2", "CC3"]
53
+
54
+ [tool.rude.rules.CC101]
55
+ max_lines = 30
56
+ ```
57
+
58
+ Run Rude from the command line:
59
+
60
+ ```sh
61
+ rude check --select CC0,CC1,CC2,CC3 src/
62
+ ```
63
+
64
+ All `CC0xx`–`CC3xx` rules (except `CC002` at `INFO`), plus `CC403` and
65
+ `CC501`, report `WARNING` severity; `CC002` and subjective indicator rules
66
+ (`CC401`, `CC402`, `CC601`) report `INFO`.
67
+
68
+ ## Implemented rules
69
+
70
+ - **Modules (`CC0xx`)**: `CC001` (`max_lines = 400`), `CC002` (`__init__.py`
71
+ contains runtime logic), `CC003` (`__init__.py` missing `__all__`), `CC004`
72
+ (deep relative import, `max_relative_depth = 2`), `CC005` (private symbol
73
+ exported in `__all__`)
74
+ - **Functions (`CC1xx`)**: `CC101` (`max_lines = 30`), `CC102`
75
+ (`max_parameters = 5`), `CC103` (`max_returns = 5`), `CC104`
76
+ (`max_locals = 10`), `CC107` (unused private helper)
77
+ - **Classes (`CC2xx`)**: `CC201` (`max_lines = 200`), `CC202`
78
+ (`max_methods = 15`), `CC207` (`max_bases = 3`)
79
+ - **Complexity and error handling (`CC3xx`)**: `CC301` (`max_depth = 4`),
80
+ `CC302` (`max_operands = 3`), `CC303` (`max_branches = 10`), `CC306`
81
+ (nested ternary), `CC308` (`global` statement), `CC309` (broad exception
82
+ swallowed), `CC310` (missing exception chaining), `CC311` (`max_lines = 15`)
83
+ - **In-file heuristics (`CC4xx`–`CC6xx`, Strict profile)**: `CC401`
84
+ (unclear function name), `CC402` (unclear class/variable name), `CC403`
85
+ (repeated magic literal, `min_occurrences = 3`), `CC501` (duplicate function
86
+ body within the **same file**, `min_lines = 5`; cross-file duplication is
87
+ planned as `CC502`), `CC601` (repeated `isinstance`/attribute switch,
88
+ `min_isinstance = 3`)
89
+
90
+ The package initializer `rude_clean_code` exposes only `VERSION` (read from
91
+ installed distribution metadata); plugin rules live in `rude_clean_code.plugin`.
92
+
93
+ See the
94
+ [repository README](https://github.com/Archont561/rude-clean-code#readme) and
95
+ [rule reference](https://github.com/Archont561/rude-clean-code/blob/main/docs/reference/rules.md)
96
+ for full detection contracts, configuration options, and the backlog roadmap.
@@ -0,0 +1,69 @@
1
+ # Rude Clean Code
2
+
3
+ Clean Code plugin for the [Rude](https://pypi.org/project/rude/) Python linter,
4
+ exposing 26 Clean Code rules across module structure, functions, classes,
5
+ complexity, error handling, naming/literals, same-file duplication, and
6
+ polymorphism indicators (`CC001`–`CC601`) through Rude's `rude.plugins`
7
+ entry point.
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ pip install rude-clean-code
13
+ ```
14
+
15
+ Rude discovers the plugin automatically via `rude.plugins` once installed.
16
+
17
+ ## Configuration profiles
18
+
19
+ Add the **Basic** profile snippet (`CC0`–`CC3`) to your `pyproject.toml`, or
20
+ include `"CC4"`, `"CC5"`, `"CC6"` for the **Strict** profile with heuristic
21
+ indicators:
22
+
23
+ ```toml
24
+ [tool.rude]
25
+ select = ["CC0", "CC1", "CC2", "CC3"]
26
+
27
+ [tool.rude.rules.CC101]
28
+ max_lines = 30
29
+ ```
30
+
31
+ Run Rude from the command line:
32
+
33
+ ```sh
34
+ rude check --select CC0,CC1,CC2,CC3 src/
35
+ ```
36
+
37
+ All `CC0xx`–`CC3xx` rules (except `CC002` at `INFO`), plus `CC403` and
38
+ `CC501`, report `WARNING` severity; `CC002` and subjective indicator rules
39
+ (`CC401`, `CC402`, `CC601`) report `INFO`.
40
+
41
+ ## Implemented rules
42
+
43
+ - **Modules (`CC0xx`)**: `CC001` (`max_lines = 400`), `CC002` (`__init__.py`
44
+ contains runtime logic), `CC003` (`__init__.py` missing `__all__`), `CC004`
45
+ (deep relative import, `max_relative_depth = 2`), `CC005` (private symbol
46
+ exported in `__all__`)
47
+ - **Functions (`CC1xx`)**: `CC101` (`max_lines = 30`), `CC102`
48
+ (`max_parameters = 5`), `CC103` (`max_returns = 5`), `CC104`
49
+ (`max_locals = 10`), `CC107` (unused private helper)
50
+ - **Classes (`CC2xx`)**: `CC201` (`max_lines = 200`), `CC202`
51
+ (`max_methods = 15`), `CC207` (`max_bases = 3`)
52
+ - **Complexity and error handling (`CC3xx`)**: `CC301` (`max_depth = 4`),
53
+ `CC302` (`max_operands = 3`), `CC303` (`max_branches = 10`), `CC306`
54
+ (nested ternary), `CC308` (`global` statement), `CC309` (broad exception
55
+ swallowed), `CC310` (missing exception chaining), `CC311` (`max_lines = 15`)
56
+ - **In-file heuristics (`CC4xx`–`CC6xx`, Strict profile)**: `CC401`
57
+ (unclear function name), `CC402` (unclear class/variable name), `CC403`
58
+ (repeated magic literal, `min_occurrences = 3`), `CC501` (duplicate function
59
+ body within the **same file**, `min_lines = 5`; cross-file duplication is
60
+ planned as `CC502`), `CC601` (repeated `isinstance`/attribute switch,
61
+ `min_isinstance = 3`)
62
+
63
+ The package initializer `rude_clean_code` exposes only `VERSION` (read from
64
+ installed distribution metadata); plugin rules live in `rude_clean_code.plugin`.
65
+
66
+ See the
67
+ [repository README](https://github.com/Archont561/rude-clean-code#readme) and
68
+ [rule reference](https://github.com/Archont561/rude-clean-code/blob/main/docs/reference/rules.md)
69
+ for full detection contracts, configuration options, and the backlog roadmap.
@@ -0,0 +1,31 @@
1
+ Feature: Deterministic Clean Code core rules
2
+ Scenario: Rude discovers Clean Code rules via entry point
3
+ Given the rude-clean-code plugin is installed
4
+ When Rude discovers plugin rules
5
+ Then all registered CC rules are available with their documented severities
6
+ And selecting by CC, family prefix, or exact code filters the matching rules
7
+
8
+ Scenario: Consumer runs Basic profile and configured thresholds on Python code
9
+ Given a temporary project configured with the Basic profile
10
+ And a Python file violating deterministic CC rules and a clean nested function
11
+ When Rude checks the temporary project
12
+ Then diagnostics report measured value, configured limit, and suggested action as warnings
13
+ And Rude exits successfully without treating warnings as errors
14
+
15
+ Scenario: Consumer overrides a deterministic rule threshold in pyproject.toml
16
+ Given a temporary project setting CC101 max_lines to 4
17
+ And a Python function spanning 5 lines
18
+ When Rude checks the temporary project
19
+ Then Rude reports CC101 with measured 5 and limit 4
20
+
21
+ Scenario: Dogfooding Clean Code rules on the package source
22
+ Given the rude-clean-code plugin is installed
23
+ When Rude checks the package source directory with the project Basic profile
24
+ Then no CC diagnostics are emitted
25
+
26
+ Scenario: Consumer runs Strict profile including in-file heuristics
27
+ Given a temporary project configured with the Strict profile
28
+ And a Python file violating heuristic CC rules
29
+ When Rude checks the temporary project
30
+ Then Strict heuristic diagnostics carry expected severities and same-file guidance
31
+ And Rude exits successfully without treating warnings as errors
@@ -0,0 +1,24 @@
1
+ Feature: Module and package organization Clean Code rules
2
+ Scenario: Oversized module receives a tailored non-autofix split suggestion
3
+ Given a temporary module project with CC001 max_lines set to 12
4
+ And a Python module with two separate symbol clusters exceeding 12 lines
5
+ When Rude runs CC0 checks on the module project
6
+ Then Rude reports CC001 without an autofix and suggests splitting by clusters
7
+
8
+ Scenario: Package initializer permits facade constructs and configured VERSION
9
+ Given a temporary module project checking CC002 with VERSION allowed
10
+ And a package initializer with VERSION, TYPE_CHECKING, __all__, and a helper function
11
+ When Rude runs CC0 checks on the module project
12
+ Then Rude flags only the helper function in __init__.py at INFO severity
13
+
14
+ Scenario: Non-empty initializer requires __all__ while marker files stay silent
15
+ Given a temporary module project checking CC003
16
+ And an empty marker __init__.py alongside a non-empty __init__.py missing __all__
17
+ When Rude runs CC0 checks on the module project
18
+ Then Rude flags only the non-empty __init__.py for missing __all__
19
+
20
+ Scenario: Deep relative imports and private __all__ exports are flagged
21
+ Given a temporary module project checking CC004 and CC005
22
+ And a module with a 3-dot relative import and __all__ containing a private name and a dunder
23
+ When Rude runs CC0 checks on the module project
24
+ Then Rude reports CC004 and CC005 while keeping __version__ exempt
@@ -0,0 +1,8 @@
1
+ Feature: Rude Clean Code package version discovery
2
+ Scenario: Discover the installed Rude Clean Code package version
3
+ Given the package is installed
4
+ When its root namespace is inspected
5
+ Then VERSION is its only public export
6
+ And VERSION matches pyproject and installed distribution metadata
7
+ And VERSION is read from distribution metadata
8
+ And importing the root namespace does not load rude or plugin submodules
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27,<1.30"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "rude-clean-code"
7
+ version = "0.2.0"
8
+ description = "Clean Code plugin for the Rude Python linter."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Archont561" }]
14
+ keywords = ["rude", "linter", "clean-code", "solid", "static-analysis", "code-quality"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ "Topic :: Software Development :: Quality Assurance",
25
+ "Typing :: Typed",
26
+ ]
27
+ dependencies = [
28
+ "rude>=0.1a4,<0.2",
29
+ ]
30
+
31
+ [project.entry-points."rude.plugins"]
32
+ rude_clean_code = "rude_clean_code.plugin"
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/Archont561/rude-clean-code#readme"
36
+ Documentation = "https://github.com/Archont561/rude-clean-code/blob/main/docs/README.md"
37
+ Repository = "https://github.com/Archont561/rude-clean-code"
38
+ Issues = "https://github.com/Archont561/rude-clean-code/issues"
39
+ Changelog = "https://github.com/Archont561/rude-clean-code/blob/main/CHANGELOG.md"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/rude_clean_code"]
43
+
44
+ [tool.hatch.build.targets.sdist]
45
+ include = [
46
+ "src/**",
47
+ "tests/**",
48
+ "features/**",
49
+ "/README.md",
50
+ "/LICENSE",
51
+ ]
52
+
53
+ [tool.rude]
54
+ select = ["CC0", "CC1", "CC2", "CC3"]
55
+
56
+ [tool.rude.rules.CC002]
57
+ allowed_names = ["VERSION"]
@@ -0,0 +1,7 @@
1
+ """Rude Clean Code package version."""
2
+
3
+ from importlib.metadata import version as _version
4
+
5
+ VERSION = _version("rude-clean-code")
6
+
7
+ __all__ = ["VERSION"]
@@ -0,0 +1,186 @@
1
+ """Shared own-scope AST traversal, option parsing, and rule base helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator
6
+ from fnmatch import fnmatch
7
+ from typing import TYPE_CHECKING, Any, ClassVar
8
+
9
+ from rude.core.rule import Rule
10
+ from rude.core.types import Diagnostic, Severity
11
+
12
+ if TYPE_CHECKING:
13
+ from rude.core.node import Node
14
+ from rude.core.types import FileContext
15
+
16
+ SCOPE_BOUNDARY_TYPES: frozenset[str] = frozenset(
17
+ {
18
+ "function_definition",
19
+ "class_definition",
20
+ "lambda",
21
+ }
22
+ )
23
+
24
+
25
+ def definition_name(node: Node) -> str:
26
+ """Return the readable name of a function or class definition node."""
27
+ return node.name or "<anonymous>"
28
+
29
+
30
+ def is_dunder(name: str) -> bool:
31
+ """Return whether *name* is a double-underscore Python dunder identifier."""
32
+ return len(name) > 4 and name.startswith("__") and name.endswith("__")
33
+
34
+
35
+ def unwrap_decorated(node: Node) -> Node:
36
+ """Return the underlying definition when *node* is a ``decorated_definition``."""
37
+ if node.type != "decorated_definition":
38
+ return node
39
+ inner = node.child_by_field("definition")
40
+ if inner is not None:
41
+ return inner
42
+ for child in node.named_children:
43
+ if child.type != "decorator":
44
+ return child
45
+ return node
46
+
47
+
48
+ def _decorator_target_names(decorator: Node) -> Iterator[str]:
49
+ for child in decorator.named_children:
50
+ target = child.child_by_field("function") if child.type == "call" else child
51
+ if target is None:
52
+ continue
53
+ text = target.text
54
+ if text:
55
+ yield text
56
+ if "." in text:
57
+ yield text.rsplit(".", maxsplit=1)[-1]
58
+
59
+
60
+ def node_decorators(node: Node) -> list[str]:
61
+ """Return normalized decorator names attached to *node*'s ``decorated_definition``."""
62
+ parent = node.parent
63
+ if parent is None or parent.type != "decorated_definition":
64
+ return []
65
+ names: list[str] = []
66
+ for child in parent.named_children:
67
+ if child.type == "decorator":
68
+ names.extend(_decorator_target_names(child))
69
+ return names
70
+
71
+
72
+ def is_test_context(context: FileContext) -> bool:
73
+ """Return whether *context* points to a test module or file inside a test folder."""
74
+ if context.is_test_file() or context.path.stem == "conftest":
75
+ return True
76
+ parent_parts = set(context.path.parts[:-1])
77
+ return "tests" in parent_parts or "test" in parent_parts
78
+
79
+
80
+ def matches_exclude_paths(
81
+ context: FileContext,
82
+ patterns: tuple[str, ...],
83
+ ) -> bool:
84
+ """Return whether *context* matches any configured path fragment or glob."""
85
+ if not patterns:
86
+ return False
87
+ posix = context.path.as_posix()
88
+ return context.is_in_path(*patterns) or any(
89
+ fnmatch(posix, pattern) for pattern in patterns
90
+ )
91
+
92
+
93
+ def _option_error(
94
+ code: str,
95
+ option_name: str,
96
+ expected: str,
97
+ raw: object,
98
+ ) -> ValueError:
99
+ return ValueError(f"{code} option '{option_name}' must be {expected}, got {raw!r}")
100
+
101
+
102
+ def parse_nonneg_int(code: str, option_name: str, raw: object) -> int:
103
+ """Validate a non-negative integer rule option."""
104
+ if isinstance(raw, bool) or not isinstance(raw, int) or raw < 0:
105
+ raise _option_error(code, option_name, "a non-negative integer", raw)
106
+ return raw
107
+
108
+
109
+ def parse_bool_option(code: str, option_name: str, raw: object) -> bool:
110
+ """Validate a boolean rule option."""
111
+ if not isinstance(raw, bool):
112
+ raise _option_error(code, option_name, "a boolean", raw)
113
+ return raw
114
+
115
+
116
+ def parse_str_list_option(
117
+ code: str,
118
+ option_name: str,
119
+ raw: object,
120
+ ) -> tuple[str, ...]:
121
+ """Validate a list/tuple-of-strings rule option and freeze it into a tuple."""
122
+ if not isinstance(raw, (list, tuple)) or any(
123
+ not isinstance(item, str) for item in raw
124
+ ):
125
+ raise _option_error(code, option_name, "a list of strings", raw)
126
+ return tuple(raw)
127
+
128
+
129
+ def iter_own_scope(root: Node) -> Iterator[Node]:
130
+ """Yield own-scope descendants without entering nested functions, classes, or lambdas."""
131
+ stack = list(reversed(root.named_children))
132
+ while stack:
133
+ current = stack.pop()
134
+ if current.type in SCOPE_BOUNDARY_TYPES:
135
+ continue
136
+ yield current
137
+ stack.extend(reversed(current.named_children))
138
+
139
+
140
+ def max_own_scope_depth(root: Node, control_types: frozenset[str]) -> int:
141
+ """Return maximum nesting depth of *control_types* in *root*'s own scope."""
142
+ max_depth = 0
143
+ stack: list[tuple[Node, int]] = [
144
+ (child, 0) for child in reversed(root.named_children)
145
+ ]
146
+ while stack:
147
+ current, depth = stack.pop()
148
+ if current.type in SCOPE_BOUNDARY_TYPES:
149
+ continue
150
+ next_depth = depth + 1 if current.type in control_types else depth
151
+ max_depth = max(max_depth, next_depth)
152
+ stack.extend((child, next_depth) for child in reversed(current.named_children))
153
+ return max_depth
154
+
155
+
156
+ class ThresholdRule(Rule):
157
+ """Base class for deterministic rules governed by one non-negative integer limit."""
158
+
159
+ severity: ClassVar[Severity | None] = Severity.WARNING
160
+ option_name: ClassVar[str]
161
+ default_limit: ClassVar[int]
162
+
163
+ def __init__(self) -> None:
164
+ setattr(self, self.option_name, self.default_limit)
165
+
166
+ @property
167
+ def limit(self) -> int:
168
+ """Configured upper bound (inclusive) for this rule."""
169
+ return int(getattr(self, self.option_name))
170
+
171
+ def configure(self, options: dict[str, Any]) -> None:
172
+ """Validate and apply the configured threshold from ``[tool.rude.rules.<CODE>]``."""
173
+ if self.option_name not in options:
174
+ return
175
+ value = parse_nonneg_int(self.code, self.option_name, options[self.option_name])
176
+ setattr(self, self.option_name, value)
177
+
178
+ def emit_if_exceeds(
179
+ self,
180
+ node: Node,
181
+ measured: int,
182
+ message: str,
183
+ ) -> Iterator[Diagnostic]:
184
+ """Yield a diagnostic on *node* when *measured* exceeds the configured limit."""
185
+ if measured > self.limit:
186
+ yield self.diagnostic(node, message)
@@ -0,0 +1,30 @@
1
+ """Rude entry-point module exposing implemented Clean Code rules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from rude_clean_code.rules.classes import CLASS_RULES
8
+ from rude_clean_code.rules.complexity import COMPLEXITY_RULES
9
+ from rude_clean_code.rules.dry import DRY_RULES
10
+ from rude_clean_code.rules.errors import ERROR_RULES
11
+ from rude_clean_code.rules.functions import FUNCTION_RULES
12
+ from rude_clean_code.rules.heuristics import HEURISTIC_RULES
13
+ from rude_clean_code.rules.modules import MODULE_RULES
14
+ from rude_clean_code.rules.naming import NAMING_RULES
15
+
16
+ if TYPE_CHECKING:
17
+ from rude.core.rule import Rule
18
+
19
+ RULES: list[type[Rule]] = [
20
+ *MODULE_RULES,
21
+ *FUNCTION_RULES,
22
+ *CLASS_RULES,
23
+ *COMPLEXITY_RULES,
24
+ *ERROR_RULES,
25
+ *NAMING_RULES,
26
+ *DRY_RULES,
27
+ *HEURISTIC_RULES,
28
+ ]
29
+
30
+ __all__ = ["RULES"]
@@ -0,0 +1 @@
1
+ """Rule implementations grouped by Clean Code family."""