thailint 0.12.0__py3-none-any.whl → 0.14.0__py3-none-any.whl
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.
- src/analyzers/__init__.py +4 -3
- src/analyzers/ast_utils.py +54 -0
- src/analyzers/typescript_base.py +4 -0
- src/cli/__init__.py +3 -0
- src/cli/config.py +12 -12
- src/cli/config_merge.py +241 -0
- src/cli/linters/__init__.py +9 -0
- src/cli/linters/code_patterns.py +107 -257
- src/cli/linters/code_smells.py +48 -165
- src/cli/linters/documentation.py +21 -95
- src/cli/linters/performance.py +274 -0
- src/cli/linters/shared.py +232 -6
- src/cli/linters/structure.py +26 -21
- src/cli/linters/structure_quality.py +28 -21
- src/cli_main.py +3 -0
- src/config.py +2 -1
- src/core/base.py +3 -2
- src/core/cli_utils.py +3 -1
- src/core/config_parser.py +5 -2
- src/core/constants.py +54 -0
- src/core/linter_utils.py +95 -6
- src/core/rule_discovery.py +5 -1
- src/core/violation_builder.py +3 -0
- src/linter_config/directive_markers.py +109 -0
- src/linter_config/ignore.py +225 -383
- src/linter_config/pattern_utils.py +65 -0
- src/linter_config/rule_matcher.py +89 -0
- src/linters/collection_pipeline/any_all_analyzer.py +281 -0
- src/linters/collection_pipeline/ast_utils.py +40 -0
- src/linters/collection_pipeline/config.py +12 -0
- src/linters/collection_pipeline/continue_analyzer.py +2 -8
- src/linters/collection_pipeline/detector.py +262 -32
- src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
- src/linters/collection_pipeline/linter.py +18 -35
- src/linters/collection_pipeline/suggestion_builder.py +68 -1
- src/linters/dry/base_token_analyzer.py +16 -9
- src/linters/dry/block_filter.py +7 -4
- src/linters/dry/cache.py +7 -2
- src/linters/dry/config.py +7 -1
- src/linters/dry/constant_matcher.py +34 -25
- src/linters/dry/file_analyzer.py +4 -2
- src/linters/dry/inline_ignore.py +7 -16
- src/linters/dry/linter.py +48 -25
- src/linters/dry/python_analyzer.py +18 -10
- src/linters/dry/python_constant_extractor.py +51 -52
- src/linters/dry/single_statement_detector.py +14 -12
- src/linters/dry/token_hasher.py +115 -115
- src/linters/dry/typescript_analyzer.py +11 -6
- src/linters/dry/typescript_constant_extractor.py +4 -0
- src/linters/dry/typescript_statement_detector.py +208 -208
- src/linters/dry/typescript_value_extractor.py +3 -0
- src/linters/dry/violation_filter.py +1 -4
- src/linters/dry/violation_generator.py +1 -4
- src/linters/file_header/atemporal_detector.py +58 -40
- src/linters/file_header/base_parser.py +4 -0
- src/linters/file_header/bash_parser.py +4 -0
- src/linters/file_header/config.py +14 -0
- src/linters/file_header/field_validator.py +5 -8
- src/linters/file_header/linter.py +19 -12
- src/linters/file_header/markdown_parser.py +6 -0
- src/linters/file_placement/config_loader.py +3 -1
- src/linters/file_placement/linter.py +22 -8
- src/linters/file_placement/pattern_matcher.py +21 -4
- src/linters/file_placement/pattern_validator.py +21 -7
- src/linters/file_placement/rule_checker.py +2 -2
- src/linters/lazy_ignores/__init__.py +43 -0
- src/linters/lazy_ignores/config.py +66 -0
- src/linters/lazy_ignores/directive_utils.py +121 -0
- src/linters/lazy_ignores/header_parser.py +177 -0
- src/linters/lazy_ignores/linter.py +158 -0
- src/linters/lazy_ignores/matcher.py +135 -0
- src/linters/lazy_ignores/python_analyzer.py +205 -0
- src/linters/lazy_ignores/rule_id_utils.py +180 -0
- src/linters/lazy_ignores/skip_detector.py +298 -0
- src/linters/lazy_ignores/types.py +69 -0
- src/linters/lazy_ignores/typescript_analyzer.py +146 -0
- src/linters/lazy_ignores/violation_builder.py +131 -0
- src/linters/lbyl/__init__.py +29 -0
- src/linters/lbyl/config.py +63 -0
- src/linters/lbyl/pattern_detectors/__init__.py +25 -0
- src/linters/lbyl/pattern_detectors/base.py +46 -0
- src/linters/magic_numbers/context_analyzer.py +227 -229
- src/linters/magic_numbers/linter.py +20 -15
- src/linters/magic_numbers/python_analyzer.py +4 -16
- src/linters/magic_numbers/typescript_analyzer.py +9 -16
- src/linters/method_property/config.py +4 -1
- src/linters/method_property/linter.py +5 -10
- src/linters/method_property/python_analyzer.py +5 -4
- src/linters/method_property/violation_builder.py +3 -0
- src/linters/nesting/linter.py +11 -6
- src/linters/nesting/typescript_analyzer.py +6 -12
- src/linters/nesting/typescript_function_extractor.py +0 -4
- src/linters/nesting/violation_builder.py +1 -0
- src/linters/performance/__init__.py +91 -0
- src/linters/performance/config.py +43 -0
- src/linters/performance/constants.py +49 -0
- src/linters/performance/linter.py +149 -0
- src/linters/performance/python_analyzer.py +365 -0
- src/linters/performance/regex_analyzer.py +312 -0
- src/linters/performance/regex_linter.py +139 -0
- src/linters/performance/typescript_analyzer.py +236 -0
- src/linters/performance/violation_builder.py +160 -0
- src/linters/print_statements/linter.py +6 -4
- src/linters/print_statements/python_analyzer.py +85 -81
- src/linters/print_statements/typescript_analyzer.py +6 -15
- src/linters/srp/heuristics.py +4 -4
- src/linters/srp/linter.py +12 -12
- src/linters/srp/violation_builder.py +0 -4
- src/linters/stateless_class/linter.py +30 -36
- src/linters/stateless_class/python_analyzer.py +11 -20
- src/linters/stringly_typed/config.py +4 -5
- src/linters/stringly_typed/context_filter.py +410 -410
- src/linters/stringly_typed/function_call_violation_builder.py +93 -95
- src/linters/stringly_typed/linter.py +48 -16
- src/linters/stringly_typed/python/analyzer.py +5 -1
- src/linters/stringly_typed/python/call_tracker.py +8 -5
- src/linters/stringly_typed/python/comparison_tracker.py +10 -5
- src/linters/stringly_typed/python/condition_extractor.py +3 -0
- src/linters/stringly_typed/python/conditional_detector.py +4 -1
- src/linters/stringly_typed/python/match_analyzer.py +8 -2
- src/linters/stringly_typed/python/validation_detector.py +3 -0
- src/linters/stringly_typed/storage.py +14 -14
- src/linters/stringly_typed/typescript/call_tracker.py +9 -3
- src/linters/stringly_typed/typescript/comparison_tracker.py +9 -3
- src/linters/stringly_typed/violation_generator.py +288 -259
- src/orchestrator/core.py +13 -4
- src/templates/thailint_config_template.yaml +196 -0
- src/utils/project_root.py +3 -0
- thailint-0.14.0.dist-info/METADATA +185 -0
- thailint-0.14.0.dist-info/RECORD +199 -0
- thailint-0.12.0.dist-info/METADATA +0 -1667
- thailint-0.12.0.dist-info/RECORD +0 -164
- {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/WHEEL +0 -0
- {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/entry_points.txt +0 -0
- {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,249 +3,247 @@ Purpose: Analyzes contexts to determine if numeric literals are acceptable
|
|
|
3
3
|
|
|
4
4
|
Scope: Context detection for magic number acceptable usage patterns
|
|
5
5
|
|
|
6
|
-
Overview: Provides
|
|
6
|
+
Overview: Provides module-level functions that determine whether a numeric literal is in an acceptable
|
|
7
7
|
context where it should not be flagged as a magic number. Detects acceptable contexts including
|
|
8
8
|
constant definitions (UPPERCASE names), small integers in range() or enumerate() calls, test files,
|
|
9
9
|
and configuration contexts. Uses AST node analysis to inspect parent nodes and determine the usage
|
|
10
10
|
pattern of numeric literals. Helps reduce false positives by distinguishing between legitimate
|
|
11
|
-
numeric literals and true magic numbers that should be extracted to constants.
|
|
12
|
-
exceeds SRP limit (8) because refactoring for A-grade complexity requires extracting helper methods.
|
|
13
|
-
Class maintains single responsibility of context analysis - all methods support this core purpose.
|
|
11
|
+
numeric literals and true magic numbers that should be extracted to constants.
|
|
14
12
|
|
|
15
13
|
Dependencies: ast module for AST node types, pathlib for Path handling
|
|
16
14
|
|
|
17
|
-
Exports:
|
|
15
|
+
Exports: is_acceptable_context function and helper functions
|
|
18
16
|
|
|
19
|
-
Interfaces:
|
|
20
|
-
various helper methods for specific context checks
|
|
17
|
+
Interfaces: is_acceptable_context(node, parent, file_path, config) -> bool
|
|
21
18
|
|
|
22
19
|
Implementation: AST parent node inspection, pattern matching for acceptable contexts, configurable
|
|
23
20
|
max_small_integer threshold for range detection
|
|
21
|
+
|
|
22
|
+
Suppressions:
|
|
23
|
+
- B101: Assert used for internal invariant checking, not security validation
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
import ast
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
node:
|
|
40
|
-
parent:
|
|
41
|
-
file_path: Path
|
|
42
|
-
config:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
"""
|
|
251
|
-
return isinstance(node, ast.Constant) and isinstance(node.value, str)
|
|
30
|
+
def is_acceptable_context(
|
|
31
|
+
node: ast.Constant,
|
|
32
|
+
parent: ast.AST | None,
|
|
33
|
+
file_path: Path | None,
|
|
34
|
+
config: dict,
|
|
35
|
+
) -> bool:
|
|
36
|
+
"""Check if a numeric literal is in an acceptable context.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
node: The numeric constant node
|
|
40
|
+
parent: The parent node in the AST
|
|
41
|
+
file_path: Path to the file being analyzed
|
|
42
|
+
config: Configuration with allowed_numbers and max_small_integer
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
True if the context is acceptable and should not be flagged
|
|
46
|
+
"""
|
|
47
|
+
# File-level and definition checks
|
|
48
|
+
if is_test_file(file_path) or is_constant_definition(node, parent):
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
# Usage pattern checks
|
|
52
|
+
return _is_acceptable_usage_pattern(node, parent, config)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _is_acceptable_usage_pattern(node: ast.Constant, parent: ast.AST | None, config: dict) -> bool:
|
|
56
|
+
"""Check if numeric literal is in acceptable usage pattern.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
node: The numeric constant node
|
|
60
|
+
parent: The parent node in the AST
|
|
61
|
+
config: Configuration with max_small_integer threshold
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
True if usage pattern is acceptable
|
|
65
|
+
"""
|
|
66
|
+
if is_small_integer_in_range(node, parent, config):
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
if is_small_integer_in_enumerate(node, parent, config):
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
return is_string_repetition(node, parent)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def is_test_file(file_path: Path | None) -> bool:
|
|
76
|
+
"""Check if the file is a test file.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
file_path: Path to the file
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
True if the file is a test file (matches test_*.py pattern)
|
|
83
|
+
"""
|
|
84
|
+
if not file_path:
|
|
85
|
+
return False
|
|
86
|
+
return file_path.name.startswith("test_") or "_test.py" in file_path.name
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def is_constant_definition(node: ast.Constant, parent: ast.AST | None) -> bool:
|
|
90
|
+
"""Check if the number is part of an UPPERCASE constant definition.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
node: The numeric constant node
|
|
94
|
+
parent: The parent node in the AST
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
True if this is a constant definition
|
|
98
|
+
"""
|
|
99
|
+
if not _is_assignment_node(parent):
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
# Type narrowing: parent is ast.Assign after the check above
|
|
103
|
+
assert isinstance(parent, ast.Assign) # nosec B101
|
|
104
|
+
return _has_constant_target(parent)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _is_assignment_node(parent: ast.AST | None) -> bool:
|
|
108
|
+
"""Check if parent is an assignment node."""
|
|
109
|
+
return parent is not None and isinstance(parent, ast.Assign)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _has_constant_target(parent: ast.Assign) -> bool:
|
|
113
|
+
"""Check if assignment has uppercase constant target."""
|
|
114
|
+
return any(
|
|
115
|
+
isinstance(target, ast.Name) and _is_constant_name(target.id) for target in parent.targets
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _is_constant_name(name: str) -> bool:
|
|
120
|
+
"""Check if a name follows constant naming convention.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
name: Variable name to check
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
True if the name is UPPERCASE (constant convention)
|
|
127
|
+
"""
|
|
128
|
+
return name.isupper() and len(name) > 1
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def is_small_integer_in_range(node: ast.Constant, parent: ast.AST | None, config: dict) -> bool:
|
|
132
|
+
"""Check if this is a small integer used in range() call.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
node: The numeric constant node
|
|
136
|
+
parent: The parent node in the AST
|
|
137
|
+
config: Configuration with max_small_integer threshold
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
True if this is a small integer in range()
|
|
141
|
+
"""
|
|
142
|
+
if not isinstance(node.value, int):
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
max_small_int = config.get("max_small_integer", 10)
|
|
146
|
+
if not 0 <= node.value <= max_small_int:
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
return _is_in_range_call(parent)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def is_small_integer_in_enumerate(node: ast.Constant, parent: ast.AST | None, config: dict) -> bool:
|
|
153
|
+
"""Check if this is a small integer used in enumerate() call.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
node: The numeric constant node
|
|
157
|
+
parent: The parent node in the AST
|
|
158
|
+
config: Configuration with max_small_integer threshold
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
True if this is a small integer in enumerate()
|
|
162
|
+
"""
|
|
163
|
+
if not isinstance(node.value, int):
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
max_small_int = config.get("max_small_integer", 10)
|
|
167
|
+
if not 0 <= node.value <= max_small_int:
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
return _is_in_enumerate_call(parent)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _is_in_range_call(parent: ast.AST | None) -> bool:
|
|
174
|
+
"""Check if the parent is a range() call.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
parent: The parent node
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
True if parent is range() call
|
|
181
|
+
"""
|
|
182
|
+
return (
|
|
183
|
+
isinstance(parent, ast.Call)
|
|
184
|
+
and isinstance(parent.func, ast.Name)
|
|
185
|
+
and parent.func.id == "range"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _is_in_enumerate_call(parent: ast.AST | None) -> bool:
|
|
190
|
+
"""Check if the parent is an enumerate() call.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
parent: The parent node
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
True if parent is enumerate() call
|
|
197
|
+
"""
|
|
198
|
+
return (
|
|
199
|
+
isinstance(parent, ast.Call)
|
|
200
|
+
and isinstance(parent.func, ast.Name)
|
|
201
|
+
and parent.func.id == "enumerate"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def is_string_repetition(node: ast.Constant, parent: ast.AST | None) -> bool:
|
|
206
|
+
"""Check if this number is used in string repetition (e.g., "-" * 40).
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
node: The numeric constant node
|
|
210
|
+
parent: The parent node in the AST
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
True if this is a string repetition pattern
|
|
214
|
+
"""
|
|
215
|
+
if not isinstance(node.value, int):
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
if not isinstance(parent, ast.BinOp):
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
if not isinstance(parent.op, ast.Mult):
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
# Check if either operand is a string constant
|
|
225
|
+
return _has_string_operand(parent)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _has_string_operand(binop: ast.BinOp) -> bool:
|
|
229
|
+
"""Check if binary operation has a string operand.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
binop: Binary operation node
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
True if either left or right operand is a string constant
|
|
236
|
+
"""
|
|
237
|
+
return _is_string_constant(binop.left) or _is_string_constant(binop.right)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _is_string_constant(node: ast.AST) -> bool:
|
|
241
|
+
"""Check if a node is a string constant.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
node: AST node to check
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
True if node is a Constant with string value
|
|
248
|
+
"""
|
|
249
|
+
return isinstance(node, ast.Constant) and isinstance(node.value, str)
|
|
@@ -12,7 +12,7 @@ Overview: Implements magic numbers linter rule following BaseLintRule interface.
|
|
|
12
12
|
because refactoring for A-grade complexity requires extracting helper methods. Class maintains
|
|
13
13
|
single responsibility of magic number detection - all methods support this core purpose.
|
|
14
14
|
|
|
15
|
-
Dependencies: BaseLintRule, BaseLintContext, PythonMagicNumberAnalyzer,
|
|
15
|
+
Dependencies: BaseLintRule, BaseLintContext, PythonMagicNumberAnalyzer, is_acceptable_context,
|
|
16
16
|
ViolationBuilder, MagicNumberConfig, IgnoreDirectiveParser
|
|
17
17
|
|
|
18
18
|
Exports: MagicNumberRule class
|
|
@@ -21,10 +21,16 @@ Interfaces: MagicNumberRule.check(context) -> list[Violation], properties for ru
|
|
|
21
21
|
|
|
22
22
|
Implementation: Composition pattern with helper classes, AST-based analysis with configurable
|
|
23
23
|
allowed numbers and context detection
|
|
24
|
+
|
|
25
|
+
Suppressions:
|
|
26
|
+
- too-many-arguments,too-many-positional-arguments: TypeScript violation creation with related params
|
|
27
|
+
- srp: Rule class coordinates analyzers and violation builders. Method count exceeds limit
|
|
28
|
+
due to complexity refactoring. All methods support magic number detection.
|
|
24
29
|
"""
|
|
25
30
|
|
|
26
31
|
import ast
|
|
27
32
|
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
28
34
|
|
|
29
35
|
from src.core.base import BaseLintContext, MultiLanguageLintRule
|
|
30
36
|
from src.core.linter_utils import load_linter_config
|
|
@@ -33,7 +39,7 @@ from src.core.violation_utils import get_violation_line, has_python_noqa
|
|
|
33
39
|
from src.linter_config.ignore import get_ignore_parser
|
|
34
40
|
|
|
35
41
|
from .config import MagicNumberConfig
|
|
36
|
-
from .context_analyzer import
|
|
42
|
+
from .context_analyzer import is_acceptable_context
|
|
37
43
|
from .python_analyzer import PythonMagicNumberAnalyzer
|
|
38
44
|
from .typescript_analyzer import TypeScriptMagicNumberAnalyzer
|
|
39
45
|
from .typescript_ignore_checker import TypeScriptIgnoreChecker
|
|
@@ -47,7 +53,6 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
47
53
|
"""Initialize the magic numbers rule."""
|
|
48
54
|
self._ignore_parser = get_ignore_parser()
|
|
49
55
|
self._violation_builder = ViolationBuilder(self.rule_id)
|
|
50
|
-
self._context_analyzer = ContextAnalyzer()
|
|
51
56
|
self._typescript_ignore_checker = TypeScriptIgnoreChecker()
|
|
52
57
|
|
|
53
58
|
@property
|
|
@@ -134,10 +139,7 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
134
139
|
return False
|
|
135
140
|
|
|
136
141
|
file_path = Path(context.file_path)
|
|
137
|
-
for pattern in config.ignore
|
|
138
|
-
if self._matches_pattern(file_path, pattern):
|
|
139
|
-
return True
|
|
140
|
-
return False
|
|
142
|
+
return any(self._matches_pattern(file_path, pattern) for pattern in config.ignore)
|
|
141
143
|
|
|
142
144
|
def _matches_pattern(self, file_path: Path, pattern: str) -> bool:
|
|
143
145
|
"""Check if file path matches a glob pattern.
|
|
@@ -251,9 +253,7 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
251
253
|
"allowed_numbers": config.allowed_numbers,
|
|
252
254
|
}
|
|
253
255
|
|
|
254
|
-
if
|
|
255
|
-
node, parent, context.file_path, config_dict
|
|
256
|
-
):
|
|
256
|
+
if is_acceptable_context(node, parent, context.file_path, config_dict):
|
|
257
257
|
return False
|
|
258
258
|
|
|
259
259
|
return True
|
|
@@ -421,12 +421,17 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
421
421
|
return value in config.allowed_numbers or self._is_test_file(context.file_path)
|
|
422
422
|
|
|
423
423
|
def _is_typescript_special_context(
|
|
424
|
-
self, node:
|
|
424
|
+
self, node: Any, analyzer: TypeScriptMagicNumberAnalyzer, context: BaseLintContext
|
|
425
425
|
) -> bool:
|
|
426
|
-
"""Check if in TypeScript-specific special context.
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
426
|
+
"""Check if in TypeScript-specific special context.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
node: Tree-sitter Node (typed as Any due to optional dependency)
|
|
430
|
+
analyzer: TypeScript analyzer
|
|
431
|
+
context: Lint context
|
|
432
|
+
"""
|
|
433
|
+
in_enum = analyzer.is_enum_context(node)
|
|
434
|
+
in_const_def = analyzer.is_constant_definition(node, context.file_content or "")
|
|
430
435
|
return in_enum or in_const_def
|
|
431
436
|
|
|
432
437
|
def _is_test_file(self, file_path: object) -> bool:
|
|
@@ -10,7 +10,7 @@ Overview: Provides PythonMagicNumberAnalyzer class that traverses Python AST to
|
|
|
10
10
|
value, and source location. This analyzer handles Python-specific AST structure and provides
|
|
11
11
|
the foundation for magic number detection by identifying all candidates before context filtering.
|
|
12
12
|
|
|
13
|
-
Dependencies: ast module for AST parsing and node types
|
|
13
|
+
Dependencies: ast module for AST parsing and node types, analyzers.ast_utils
|
|
14
14
|
|
|
15
15
|
Exports: PythonMagicNumberAnalyzer class
|
|
16
16
|
|
|
@@ -23,6 +23,8 @@ Implementation: AST NodeVisitor pattern with parent tracking, filters for numeri
|
|
|
23
23
|
import ast
|
|
24
24
|
from typing import Any
|
|
25
25
|
|
|
26
|
+
from src.analyzers.ast_utils import build_parent_map
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
class PythonMagicNumberAnalyzer(ast.NodeVisitor):
|
|
28
30
|
"""Analyzes Python AST to find numeric literals."""
|
|
@@ -44,24 +46,10 @@ class PythonMagicNumberAnalyzer(ast.NodeVisitor):
|
|
|
44
46
|
List of tuples (node, parent, value, line_number)
|
|
45
47
|
"""
|
|
46
48
|
self.numeric_literals = []
|
|
47
|
-
self.parent_map =
|
|
48
|
-
self._build_parent_map(tree)
|
|
49
|
+
self.parent_map = build_parent_map(tree)
|
|
49
50
|
self.visit(tree)
|
|
50
51
|
return self.numeric_literals
|
|
51
52
|
|
|
52
|
-
def _build_parent_map(self, node: ast.AST, parent: ast.AST | None = None) -> None:
|
|
53
|
-
"""Build a map of nodes to their parents.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
node: Current AST node
|
|
57
|
-
parent: Parent of current node
|
|
58
|
-
"""
|
|
59
|
-
if parent is not None:
|
|
60
|
-
self.parent_map[node] = parent
|
|
61
|
-
|
|
62
|
-
for child in ast.iter_child_nodes(node):
|
|
63
|
-
self._build_parent_map(child, node)
|
|
64
|
-
|
|
65
53
|
def visit_Constant(self, node: ast.Constant) -> None:
|
|
66
54
|
"""Visit a Constant node and check if it's a numeric literal.
|
|
67
55
|
|
|
@@ -20,20 +20,17 @@ Interfaces: find_numeric_literals(root_node) -> list[tuple], is_enum_context(nod
|
|
|
20
20
|
|
|
21
21
|
Implementation: Tree-sitter node traversal with visitor pattern, context-aware filtering
|
|
22
22
|
for acceptable numeric literal locations
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from typing import Any
|
|
26
|
-
|
|
27
|
-
from src.analyzers.typescript_base import TypeScriptBaseAnalyzer
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
Suppressions:
|
|
25
|
+
- srp: Analyzer implements tree-sitter traversal with context detection methods.
|
|
26
|
+
Methods support single responsibility of magic number detection in TypeScript.
|
|
27
|
+
"""
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
from src.analyzers.typescript_base import (
|
|
30
|
+
TREE_SITTER_AVAILABLE,
|
|
31
|
+
Node,
|
|
32
|
+
TypeScriptBaseAnalyzer,
|
|
33
|
+
)
|
|
37
34
|
|
|
38
35
|
|
|
39
36
|
class TypeScriptMagicNumberAnalyzer(TypeScriptBaseAnalyzer): # thailint: ignore[srp]
|
|
@@ -44,10 +41,6 @@ class TypeScriptMagicNumberAnalyzer(TypeScriptBaseAnalyzer): # thailint: ignore
|
|
|
44
41
|
of TypeScript magic number detection - all methods support this core purpose.
|
|
45
42
|
"""
|
|
46
43
|
|
|
47
|
-
def __init__(self) -> None: # pylint: disable=useless-parent-delegation
|
|
48
|
-
"""Initialize the TypeScript magic number analyzer."""
|
|
49
|
-
super().__init__() # Sets self.tree_sitter_available from base class
|
|
50
|
-
|
|
51
44
|
def find_numeric_literals(self, root_node: Node) -> list[tuple[Node, float | int, int]]:
|
|
52
45
|
"""Find all numeric literal nodes in TypeScript/JavaScript AST.
|
|
53
46
|
|