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.
Files changed (135) hide show
  1. src/analyzers/__init__.py +4 -3
  2. src/analyzers/ast_utils.py +54 -0
  3. src/analyzers/typescript_base.py +4 -0
  4. src/cli/__init__.py +3 -0
  5. src/cli/config.py +12 -12
  6. src/cli/config_merge.py +241 -0
  7. src/cli/linters/__init__.py +9 -0
  8. src/cli/linters/code_patterns.py +107 -257
  9. src/cli/linters/code_smells.py +48 -165
  10. src/cli/linters/documentation.py +21 -95
  11. src/cli/linters/performance.py +274 -0
  12. src/cli/linters/shared.py +232 -6
  13. src/cli/linters/structure.py +26 -21
  14. src/cli/linters/structure_quality.py +28 -21
  15. src/cli_main.py +3 -0
  16. src/config.py +2 -1
  17. src/core/base.py +3 -2
  18. src/core/cli_utils.py +3 -1
  19. src/core/config_parser.py +5 -2
  20. src/core/constants.py +54 -0
  21. src/core/linter_utils.py +95 -6
  22. src/core/rule_discovery.py +5 -1
  23. src/core/violation_builder.py +3 -0
  24. src/linter_config/directive_markers.py +109 -0
  25. src/linter_config/ignore.py +225 -383
  26. src/linter_config/pattern_utils.py +65 -0
  27. src/linter_config/rule_matcher.py +89 -0
  28. src/linters/collection_pipeline/any_all_analyzer.py +281 -0
  29. src/linters/collection_pipeline/ast_utils.py +40 -0
  30. src/linters/collection_pipeline/config.py +12 -0
  31. src/linters/collection_pipeline/continue_analyzer.py +2 -8
  32. src/linters/collection_pipeline/detector.py +262 -32
  33. src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
  34. src/linters/collection_pipeline/linter.py +18 -35
  35. src/linters/collection_pipeline/suggestion_builder.py +68 -1
  36. src/linters/dry/base_token_analyzer.py +16 -9
  37. src/linters/dry/block_filter.py +7 -4
  38. src/linters/dry/cache.py +7 -2
  39. src/linters/dry/config.py +7 -1
  40. src/linters/dry/constant_matcher.py +34 -25
  41. src/linters/dry/file_analyzer.py +4 -2
  42. src/linters/dry/inline_ignore.py +7 -16
  43. src/linters/dry/linter.py +48 -25
  44. src/linters/dry/python_analyzer.py +18 -10
  45. src/linters/dry/python_constant_extractor.py +51 -52
  46. src/linters/dry/single_statement_detector.py +14 -12
  47. src/linters/dry/token_hasher.py +115 -115
  48. src/linters/dry/typescript_analyzer.py +11 -6
  49. src/linters/dry/typescript_constant_extractor.py +4 -0
  50. src/linters/dry/typescript_statement_detector.py +208 -208
  51. src/linters/dry/typescript_value_extractor.py +3 -0
  52. src/linters/dry/violation_filter.py +1 -4
  53. src/linters/dry/violation_generator.py +1 -4
  54. src/linters/file_header/atemporal_detector.py +58 -40
  55. src/linters/file_header/base_parser.py +4 -0
  56. src/linters/file_header/bash_parser.py +4 -0
  57. src/linters/file_header/config.py +14 -0
  58. src/linters/file_header/field_validator.py +5 -8
  59. src/linters/file_header/linter.py +19 -12
  60. src/linters/file_header/markdown_parser.py +6 -0
  61. src/linters/file_placement/config_loader.py +3 -1
  62. src/linters/file_placement/linter.py +22 -8
  63. src/linters/file_placement/pattern_matcher.py +21 -4
  64. src/linters/file_placement/pattern_validator.py +21 -7
  65. src/linters/file_placement/rule_checker.py +2 -2
  66. src/linters/lazy_ignores/__init__.py +43 -0
  67. src/linters/lazy_ignores/config.py +66 -0
  68. src/linters/lazy_ignores/directive_utils.py +121 -0
  69. src/linters/lazy_ignores/header_parser.py +177 -0
  70. src/linters/lazy_ignores/linter.py +158 -0
  71. src/linters/lazy_ignores/matcher.py +135 -0
  72. src/linters/lazy_ignores/python_analyzer.py +205 -0
  73. src/linters/lazy_ignores/rule_id_utils.py +180 -0
  74. src/linters/lazy_ignores/skip_detector.py +298 -0
  75. src/linters/lazy_ignores/types.py +69 -0
  76. src/linters/lazy_ignores/typescript_analyzer.py +146 -0
  77. src/linters/lazy_ignores/violation_builder.py +131 -0
  78. src/linters/lbyl/__init__.py +29 -0
  79. src/linters/lbyl/config.py +63 -0
  80. src/linters/lbyl/pattern_detectors/__init__.py +25 -0
  81. src/linters/lbyl/pattern_detectors/base.py +46 -0
  82. src/linters/magic_numbers/context_analyzer.py +227 -229
  83. src/linters/magic_numbers/linter.py +20 -15
  84. src/linters/magic_numbers/python_analyzer.py +4 -16
  85. src/linters/magic_numbers/typescript_analyzer.py +9 -16
  86. src/linters/method_property/config.py +4 -1
  87. src/linters/method_property/linter.py +5 -10
  88. src/linters/method_property/python_analyzer.py +5 -4
  89. src/linters/method_property/violation_builder.py +3 -0
  90. src/linters/nesting/linter.py +11 -6
  91. src/linters/nesting/typescript_analyzer.py +6 -12
  92. src/linters/nesting/typescript_function_extractor.py +0 -4
  93. src/linters/nesting/violation_builder.py +1 -0
  94. src/linters/performance/__init__.py +91 -0
  95. src/linters/performance/config.py +43 -0
  96. src/linters/performance/constants.py +49 -0
  97. src/linters/performance/linter.py +149 -0
  98. src/linters/performance/python_analyzer.py +365 -0
  99. src/linters/performance/regex_analyzer.py +312 -0
  100. src/linters/performance/regex_linter.py +139 -0
  101. src/linters/performance/typescript_analyzer.py +236 -0
  102. src/linters/performance/violation_builder.py +160 -0
  103. src/linters/print_statements/linter.py +6 -4
  104. src/linters/print_statements/python_analyzer.py +85 -81
  105. src/linters/print_statements/typescript_analyzer.py +6 -15
  106. src/linters/srp/heuristics.py +4 -4
  107. src/linters/srp/linter.py +12 -12
  108. src/linters/srp/violation_builder.py +0 -4
  109. src/linters/stateless_class/linter.py +30 -36
  110. src/linters/stateless_class/python_analyzer.py +11 -20
  111. src/linters/stringly_typed/config.py +4 -5
  112. src/linters/stringly_typed/context_filter.py +410 -410
  113. src/linters/stringly_typed/function_call_violation_builder.py +93 -95
  114. src/linters/stringly_typed/linter.py +48 -16
  115. src/linters/stringly_typed/python/analyzer.py +5 -1
  116. src/linters/stringly_typed/python/call_tracker.py +8 -5
  117. src/linters/stringly_typed/python/comparison_tracker.py +10 -5
  118. src/linters/stringly_typed/python/condition_extractor.py +3 -0
  119. src/linters/stringly_typed/python/conditional_detector.py +4 -1
  120. src/linters/stringly_typed/python/match_analyzer.py +8 -2
  121. src/linters/stringly_typed/python/validation_detector.py +3 -0
  122. src/linters/stringly_typed/storage.py +14 -14
  123. src/linters/stringly_typed/typescript/call_tracker.py +9 -3
  124. src/linters/stringly_typed/typescript/comparison_tracker.py +9 -3
  125. src/linters/stringly_typed/violation_generator.py +288 -259
  126. src/orchestrator/core.py +13 -4
  127. src/templates/thailint_config_template.yaml +196 -0
  128. src/utils/project_root.py +3 -0
  129. thailint-0.14.0.dist-info/METADATA +185 -0
  130. thailint-0.14.0.dist-info/RECORD +199 -0
  131. thailint-0.12.0.dist-info/METADATA +0 -1667
  132. thailint-0.12.0.dist-info/RECORD +0 -164
  133. {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/WHEEL +0 -0
  134. {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/entry_points.txt +0 -0
  135. {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 ContextAnalyzer class that determines whether a numeric literal is in an acceptable
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. Method count (10)
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: ContextAnalyzer class
15
+ Exports: is_acceptable_context function and helper functions
18
16
 
19
- Interfaces: ContextAnalyzer.is_acceptable_context(node, parent, file_path, config) -> bool,
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
- class ContextAnalyzer: # thailint: ignore[srp]
31
- """Analyzes contexts to determine if numeric literals are acceptable."""
32
-
33
- def __init__(self) -> None:
34
- """Initialize the context analyzer."""
35
- pass # Stateless analyzer for context checking
36
-
37
- def is_acceptable_context(
38
- self,
39
- node: ast.Constant,
40
- parent: ast.AST | None,
41
- file_path: Path | None,
42
- config: dict,
43
- ) -> bool:
44
- """Check if a numeric literal is in an acceptable context.
45
-
46
- Args:
47
- node: The numeric constant node
48
- parent: The parent node in the AST
49
- file_path: Path to the file being analyzed
50
- config: Configuration with allowed_numbers and max_small_integer
51
-
52
- Returns:
53
- True if the context is acceptable and should not be flagged
54
- """
55
- # File-level and definition checks
56
- if self.is_test_file(file_path) or self.is_constant_definition(node, parent):
57
- return True
58
-
59
- # Usage pattern checks
60
- return self._is_acceptable_usage_pattern(node, parent, config)
61
-
62
- def _is_acceptable_usage_pattern(
63
- self, node: ast.Constant, parent: ast.AST | None, config: dict
64
- ) -> bool:
65
- """Check if numeric literal is in acceptable usage pattern.
66
-
67
- Args:
68
- node: The numeric constant node
69
- parent: The parent node in the AST
70
- config: Configuration with max_small_integer threshold
71
-
72
- Returns:
73
- True if usage pattern is acceptable
74
- """
75
- if self.is_small_integer_in_range(node, parent, config):
76
- return True
77
-
78
- if self.is_small_integer_in_enumerate(node, parent, config):
79
- return True
80
-
81
- return self.is_string_repetition(node, parent)
82
-
83
- def is_test_file(self, file_path: Path | None) -> bool:
84
- """Check if the file is a test file.
85
-
86
- Args:
87
- file_path: Path to the file
88
-
89
- Returns:
90
- True if the file is a test file (matches test_*.py pattern)
91
- """
92
- if not file_path:
93
- return False
94
- return file_path.name.startswith("test_") or "_test.py" in file_path.name
95
-
96
- def is_constant_definition(self, node: ast.Constant, parent: ast.AST | None) -> bool:
97
- """Check if the number is part of an UPPERCASE constant definition.
98
-
99
- Args:
100
- node: The numeric constant node
101
- parent: The parent node in the AST
102
-
103
- Returns:
104
- True if this is a constant definition
105
- """
106
- if not self._is_assignment_node(parent):
107
- return False
108
-
109
- # Type narrowing: parent is ast.Assign after the check above
110
- assert isinstance(parent, ast.Assign) # nosec B101
111
- return self._has_constant_target(parent)
112
-
113
- def _is_assignment_node(self, parent: ast.AST | None) -> bool:
114
- """Check if parent is an assignment node."""
115
- return parent is not None and isinstance(parent, ast.Assign)
116
-
117
- def _has_constant_target(self, parent: ast.Assign) -> bool:
118
- """Check if assignment has uppercase constant target."""
119
- return any(
120
- isinstance(target, ast.Name) and self._is_constant_name(target.id)
121
- for target in parent.targets
122
- )
123
-
124
- def _is_constant_name(self, name: str) -> bool:
125
- """Check if a name follows constant naming convention.
126
-
127
- Args:
128
- name: Variable name to check
129
-
130
- Returns:
131
- True if the name is UPPERCASE (constant convention)
132
- """
133
- return name.isupper() and len(name) > 1
134
-
135
- def is_small_integer_in_range(
136
- self, node: ast.Constant, parent: ast.AST | None, config: dict
137
- ) -> bool:
138
- """Check if this is a small integer used in range() call.
139
-
140
- Args:
141
- node: The numeric constant node
142
- parent: The parent node in the AST
143
- config: Configuration with max_small_integer threshold
144
-
145
- Returns:
146
- True if this is a small integer in range()
147
- """
148
- if not isinstance(node.value, int):
149
- return False
150
-
151
- max_small_int = config.get("max_small_integer", 10)
152
- if not 0 <= node.value <= max_small_int:
153
- return False
154
-
155
- return self._is_in_range_call(parent)
156
-
157
- def is_small_integer_in_enumerate(
158
- self, node: ast.Constant, parent: ast.AST | None, config: dict
159
- ) -> bool:
160
- """Check if this is a small integer used in enumerate() call.
161
-
162
- Args:
163
- node: The numeric constant node
164
- parent: The parent node in the AST
165
- config: Configuration with max_small_integer threshold
166
-
167
- Returns:
168
- True if this is a small integer in enumerate()
169
- """
170
- if not isinstance(node.value, int):
171
- return False
172
-
173
- max_small_int = config.get("max_small_integer", 10)
174
- if not 0 <= node.value <= max_small_int:
175
- return False
176
-
177
- return self._is_in_enumerate_call(parent)
178
-
179
- def _is_in_range_call(self, parent: ast.AST | None) -> bool:
180
- """Check if the parent is a range() call.
181
-
182
- Args:
183
- parent: The parent node
184
-
185
- Returns:
186
- True if parent is range() call
187
- """
188
- return (
189
- isinstance(parent, ast.Call)
190
- and isinstance(parent.func, ast.Name)
191
- and parent.func.id == "range"
192
- )
193
-
194
- def _is_in_enumerate_call(self, parent: ast.AST | None) -> bool:
195
- """Check if the parent is an enumerate() call.
196
-
197
- Args:
198
- parent: The parent node
199
-
200
- Returns:
201
- True if parent is enumerate() call
202
- """
203
- return (
204
- isinstance(parent, ast.Call)
205
- and isinstance(parent.func, ast.Name)
206
- and parent.func.id == "enumerate"
207
- )
208
-
209
- def is_string_repetition(self, node: ast.Constant, parent: ast.AST | None) -> bool:
210
- """Check if this number is used in string repetition (e.g., "-" * 40).
211
-
212
- Args:
213
- node: The numeric constant node
214
- parent: The parent node in the AST
215
-
216
- Returns:
217
- True if this is a string repetition pattern
218
- """
219
- if not isinstance(node.value, int):
220
- return False
221
-
222
- if not isinstance(parent, ast.BinOp):
223
- return False
224
-
225
- if not isinstance(parent.op, ast.Mult):
226
- return False
227
-
228
- # Check if either operand is a string constant
229
- return self._has_string_operand(parent)
230
-
231
- def _has_string_operand(self, binop: ast.BinOp) -> bool:
232
- """Check if binary operation has a string operand.
233
-
234
- Args:
235
- binop: Binary operation node
236
-
237
- Returns:
238
- True if either left or right operand is a string constant
239
- """
240
- return self._is_string_constant(binop.left) or self._is_string_constant(binop.right)
241
-
242
- def _is_string_constant(self, node: ast.AST) -> bool:
243
- """Check if a node is a string constant.
244
-
245
- Args:
246
- node: AST node to check
247
-
248
- Returns:
249
- True if node is a Constant with string value
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, ContextAnalyzer,
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 ContextAnalyzer
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 self._context_analyzer.is_acceptable_context(
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: object, analyzer: TypeScriptMagicNumberAnalyzer, context: BaseLintContext
424
+ self, node: Any, analyzer: TypeScriptMagicNumberAnalyzer, context: BaseLintContext
425
425
  ) -> bool:
426
- """Check if in TypeScript-specific special context."""
427
- # Calls require type: ignore because node is typed as object but analyzer expects Node
428
- in_enum = analyzer.is_enum_context(node) # type: ignore[arg-type]
429
- in_const_def = analyzer.is_constant_definition(node, context.file_content or "") # type: ignore[arg-type]
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
- # dry: ignore-block - tree-sitter import pattern (common across TypeScript analyzers)
30
- try:
31
- from tree_sitter import Node
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
- TREE_SITTER_AVAILABLE = True
34
- except ImportError:
35
- TREE_SITTER_AVAILABLE = False
36
- Node = Any # type: ignore
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