thailint 0.12.0__py3-none-any.whl → 0.13.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 (121) 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 +3 -0
  8. src/cli/linters/code_patterns.py +113 -5
  9. src/cli/linters/code_smells.py +4 -0
  10. src/cli/linters/documentation.py +3 -0
  11. src/cli/linters/structure.py +3 -0
  12. src/cli/linters/structure_quality.py +3 -0
  13. src/cli_main.py +3 -0
  14. src/config.py +2 -1
  15. src/core/base.py +3 -2
  16. src/core/cli_utils.py +3 -1
  17. src/core/config_parser.py +5 -2
  18. src/core/constants.py +54 -0
  19. src/core/linter_utils.py +4 -0
  20. src/core/rule_discovery.py +5 -1
  21. src/core/violation_builder.py +3 -0
  22. src/linter_config/directive_markers.py +109 -0
  23. src/linter_config/ignore.py +225 -383
  24. src/linter_config/pattern_utils.py +65 -0
  25. src/linter_config/rule_matcher.py +89 -0
  26. src/linters/collection_pipeline/any_all_analyzer.py +281 -0
  27. src/linters/collection_pipeline/ast_utils.py +40 -0
  28. src/linters/collection_pipeline/config.py +12 -0
  29. src/linters/collection_pipeline/continue_analyzer.py +2 -8
  30. src/linters/collection_pipeline/detector.py +262 -32
  31. src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
  32. src/linters/collection_pipeline/linter.py +18 -35
  33. src/linters/collection_pipeline/suggestion_builder.py +68 -1
  34. src/linters/dry/base_token_analyzer.py +16 -9
  35. src/linters/dry/block_filter.py +7 -4
  36. src/linters/dry/cache.py +7 -2
  37. src/linters/dry/config.py +7 -1
  38. src/linters/dry/constant_matcher.py +34 -25
  39. src/linters/dry/file_analyzer.py +4 -2
  40. src/linters/dry/inline_ignore.py +7 -16
  41. src/linters/dry/linter.py +48 -25
  42. src/linters/dry/python_analyzer.py +18 -10
  43. src/linters/dry/python_constant_extractor.py +51 -52
  44. src/linters/dry/single_statement_detector.py +14 -12
  45. src/linters/dry/token_hasher.py +115 -115
  46. src/linters/dry/typescript_analyzer.py +11 -6
  47. src/linters/dry/typescript_constant_extractor.py +4 -0
  48. src/linters/dry/typescript_statement_detector.py +208 -208
  49. src/linters/dry/typescript_value_extractor.py +3 -0
  50. src/linters/dry/violation_filter.py +1 -4
  51. src/linters/dry/violation_generator.py +1 -4
  52. src/linters/file_header/atemporal_detector.py +4 -0
  53. src/linters/file_header/base_parser.py +4 -0
  54. src/linters/file_header/bash_parser.py +4 -0
  55. src/linters/file_header/field_validator.py +5 -8
  56. src/linters/file_header/linter.py +19 -12
  57. src/linters/file_header/markdown_parser.py +6 -0
  58. src/linters/file_placement/config_loader.py +3 -1
  59. src/linters/file_placement/linter.py +22 -8
  60. src/linters/file_placement/pattern_matcher.py +21 -4
  61. src/linters/file_placement/pattern_validator.py +21 -7
  62. src/linters/file_placement/rule_checker.py +2 -2
  63. src/linters/lazy_ignores/__init__.py +43 -0
  64. src/linters/lazy_ignores/config.py +66 -0
  65. src/linters/lazy_ignores/directive_utils.py +121 -0
  66. src/linters/lazy_ignores/header_parser.py +177 -0
  67. src/linters/lazy_ignores/linter.py +158 -0
  68. src/linters/lazy_ignores/matcher.py +135 -0
  69. src/linters/lazy_ignores/python_analyzer.py +201 -0
  70. src/linters/lazy_ignores/rule_id_utils.py +180 -0
  71. src/linters/lazy_ignores/skip_detector.py +298 -0
  72. src/linters/lazy_ignores/types.py +67 -0
  73. src/linters/lazy_ignores/typescript_analyzer.py +146 -0
  74. src/linters/lazy_ignores/violation_builder.py +131 -0
  75. src/linters/lbyl/__init__.py +29 -0
  76. src/linters/lbyl/config.py +63 -0
  77. src/linters/lbyl/pattern_detectors/__init__.py +25 -0
  78. src/linters/lbyl/pattern_detectors/base.py +46 -0
  79. src/linters/magic_numbers/context_analyzer.py +227 -229
  80. src/linters/magic_numbers/linter.py +20 -15
  81. src/linters/magic_numbers/python_analyzer.py +4 -16
  82. src/linters/magic_numbers/typescript_analyzer.py +9 -16
  83. src/linters/method_property/config.py +4 -0
  84. src/linters/method_property/linter.py +5 -4
  85. src/linters/method_property/python_analyzer.py +5 -4
  86. src/linters/method_property/violation_builder.py +3 -0
  87. src/linters/nesting/typescript_analyzer.py +6 -12
  88. src/linters/nesting/typescript_function_extractor.py +0 -4
  89. src/linters/print_statements/linter.py +6 -4
  90. src/linters/print_statements/python_analyzer.py +85 -81
  91. src/linters/print_statements/typescript_analyzer.py +6 -15
  92. src/linters/srp/heuristics.py +4 -4
  93. src/linters/srp/linter.py +12 -12
  94. src/linters/srp/violation_builder.py +0 -4
  95. src/linters/stateless_class/linter.py +30 -36
  96. src/linters/stateless_class/python_analyzer.py +11 -20
  97. src/linters/stringly_typed/config.py +4 -5
  98. src/linters/stringly_typed/context_filter.py +410 -410
  99. src/linters/stringly_typed/function_call_violation_builder.py +93 -95
  100. src/linters/stringly_typed/linter.py +48 -16
  101. src/linters/stringly_typed/python/analyzer.py +5 -1
  102. src/linters/stringly_typed/python/call_tracker.py +8 -5
  103. src/linters/stringly_typed/python/comparison_tracker.py +10 -5
  104. src/linters/stringly_typed/python/condition_extractor.py +3 -0
  105. src/linters/stringly_typed/python/conditional_detector.py +4 -1
  106. src/linters/stringly_typed/python/match_analyzer.py +8 -2
  107. src/linters/stringly_typed/python/validation_detector.py +3 -0
  108. src/linters/stringly_typed/storage.py +14 -14
  109. src/linters/stringly_typed/typescript/call_tracker.py +9 -3
  110. src/linters/stringly_typed/typescript/comparison_tracker.py +9 -3
  111. src/linters/stringly_typed/violation_generator.py +288 -259
  112. src/orchestrator/core.py +13 -4
  113. src/templates/thailint_config_template.yaml +166 -0
  114. src/utils/project_root.py +3 -0
  115. thailint-0.13.0.dist-info/METADATA +184 -0
  116. thailint-0.13.0.dist-info/RECORD +189 -0
  117. thailint-0.12.0.dist-info/METADATA +0 -1667
  118. thailint-0.12.0.dist-info/RECORD +0 -164
  119. {thailint-0.12.0.dist-info → thailint-0.13.0.dist-info}/WHEEL +0 -0
  120. {thailint-0.12.0.dist-info → thailint-0.13.0.dist-info}/entry_points.txt +0 -0
  121. {thailint-0.12.0.dist-info → thailint-0.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,65 @@
1
+ """
2
+ Purpose: Pattern matching utilities for file paths and content parsing
3
+
4
+ Scope: Gitignore-style pattern matching and content parsing
5
+
6
+ Overview: Provides utility functions for matching file paths against gitignore-style
7
+ patterns and extracting patterns from configuration files. Supports directory
8
+ patterns (trailing /), standard glob patterns via fnmatch, and comment filtering.
9
+
10
+ Dependencies: fnmatch for glob pattern matching, pathlib for path operations
11
+
12
+ Exports: matches_pattern, extract_patterns_from_content
13
+
14
+ Interfaces: matches_pattern(path, pattern) -> bool, extract_patterns_from_content(content) -> list
15
+
16
+ Implementation: fnmatch-based pattern matching with directory-aware logic
17
+ """
18
+
19
+ import fnmatch
20
+ from pathlib import Path
21
+
22
+
23
+ def matches_pattern(path: str, pattern: str) -> bool:
24
+ """Check if path matches gitignore-style pattern.
25
+
26
+ Args:
27
+ path: File path to check.
28
+ pattern: Gitignore-style pattern.
29
+
30
+ Returns:
31
+ True if path matches pattern.
32
+ """
33
+ if pattern.endswith("/"):
34
+ return _matches_directory_pattern(path, pattern)
35
+ return fnmatch.fnmatch(path, pattern) or fnmatch.fnmatch(str(Path(path)), pattern)
36
+
37
+
38
+ def _matches_directory_pattern(path: str, pattern: str) -> bool:
39
+ """Check if path matches a directory pattern (trailing /).
40
+
41
+ Args:
42
+ path: File path to check
43
+ pattern: Directory pattern ending with /
44
+
45
+ Returns:
46
+ True if path is within the directory
47
+ """
48
+ dir_pattern = pattern.rstrip("/")
49
+ path_parts = Path(path).parts
50
+ if dir_pattern in path_parts:
51
+ return True
52
+ return fnmatch.fnmatch(path, dir_pattern + "*")
53
+
54
+
55
+ def extract_patterns_from_content(content: str) -> list[str]:
56
+ """Extract non-empty, non-comment patterns from content.
57
+
58
+ Args:
59
+ content: File content with patterns (one per line)
60
+
61
+ Returns:
62
+ List of valid patterns (non-empty, non-comment lines)
63
+ """
64
+ lines = [line.strip() for line in content.splitlines()]
65
+ return [line for line in lines if line and not line.startswith("#")]
@@ -0,0 +1,89 @@
1
+ """
2
+ Purpose: Rule ID matching utilities for ignore directive processing
3
+
4
+ Scope: Pattern matching between rule IDs and ignore patterns
5
+
6
+ Overview: Provides functions for matching rule IDs against ignore patterns. Supports
7
+ exact matching, wildcard matching (*.suffix), and prefix matching (category matches
8
+ category.specific). All comparisons are case-insensitive to handle variations in
9
+ rule ID formatting.
10
+
11
+ Dependencies: re for regex operations
12
+
13
+ Exports: rule_matches, check_bracket_rules, check_space_separated_rules
14
+
15
+ Interfaces: rule_matches(rule_id, pattern) -> bool for checking if rule matches pattern
16
+
17
+ Implementation: String-based pattern matching with wildcard and prefix support
18
+ """
19
+
20
+ import re
21
+
22
+
23
+ def rule_matches(rule_id: str, pattern: str) -> bool:
24
+ """Check if rule ID matches pattern (supports wildcards and prefixes).
25
+
26
+ Args:
27
+ rule_id: Rule ID to check (e.g., "nesting.excessive-depth").
28
+ pattern: Pattern with optional wildcard (e.g., "nesting.*" or "nesting").
29
+
30
+ Returns:
31
+ True if rule matches pattern.
32
+ """
33
+ rule_id_lower = rule_id.lower()
34
+ pattern_lower = pattern.lower()
35
+
36
+ if pattern_lower.endswith("*"):
37
+ prefix = pattern_lower[:-1]
38
+ return rule_id_lower.startswith(prefix)
39
+
40
+ if rule_id_lower == pattern_lower:
41
+ return True
42
+
43
+ if rule_id_lower.startswith(pattern_lower + "."):
44
+ return True
45
+
46
+ return False
47
+
48
+
49
+ def check_bracket_rules(rules_text: str, rule_id: str) -> bool:
50
+ """Check if bracketed rules match the rule ID.
51
+
52
+ Args:
53
+ rules_text: Comma-separated rule patterns from bracket syntax
54
+ rule_id: Rule ID to match against
55
+
56
+ Returns:
57
+ True if any pattern matches the rule ID
58
+ """
59
+ ignored_rules = [r.strip() for r in rules_text.split(",")]
60
+ return any(rule_matches(rule_id, r) for r in ignored_rules)
61
+
62
+
63
+ def check_space_separated_rules(rules_text: str, rule_id: str) -> bool:
64
+ """Check if space-separated rules match the rule ID.
65
+
66
+ Args:
67
+ rules_text: Space or comma-separated rule patterns
68
+ rule_id: Rule ID to match against
69
+
70
+ Returns:
71
+ True if any pattern matches the rule ID
72
+ """
73
+ ignored_rules = [r.strip() for r in re.split(r"[,\s]+", rules_text) if r.strip()]
74
+ return any(rule_matches(rule_id, r) for r in ignored_rules)
75
+
76
+
77
+ def rules_match_violation(ignored_rules: set[str], rule_id: str) -> bool:
78
+ """Check if any of the ignored rules match the violation rule ID.
79
+
80
+ Args:
81
+ ignored_rules: Set of rule patterns to check
82
+ rule_id: Rule ID of the violation
83
+
84
+ Returns:
85
+ True if any pattern matches (or if wildcard "*" is present)
86
+ """
87
+ if "*" in ignored_rules:
88
+ return True
89
+ return any(rule_matches(rule_id, pattern) for pattern in ignored_rules)
@@ -0,0 +1,281 @@
1
+ """
2
+ Purpose: Analyze any()/all() anti-patterns in for loops
3
+
4
+ Scope: Extract and validate loops that return True/False and could use any()/all()
5
+
6
+ Overview: Provides helper functions for analyzing loops that iterate and return boolean
7
+ values based on conditions. Detects patterns like 'for x in iter: if cond: return True;
8
+ return False' which can be refactored to 'return any(cond for x in iter)'. Also handles
9
+ the inverse all() pattern. Requires function context to analyze return statements.
10
+
11
+ Dependencies: ast module for Python AST processing
12
+
13
+ Exports: extract_any_pattern, extract_all_pattern, AnyAllMatch dataclass
14
+
15
+ Interfaces: Functions for analyzing any/all patterns in AST function bodies
16
+
17
+ Implementation: AST-based pattern matching for any/all pattern identification
18
+ """
19
+
20
+ import ast
21
+ from dataclasses import dataclass
22
+ from typing import cast
23
+
24
+ from . import ast_utils
25
+
26
+
27
+ @dataclass
28
+ class AnyAllMatch:
29
+ """Information about a detected any()/all() pattern."""
30
+
31
+ for_node: ast.For
32
+ """The for loop AST node."""
33
+
34
+ condition: ast.expr
35
+ """The condition expression inside the if statement."""
36
+
37
+ is_any: bool
38
+ """True for any() pattern, False for all() pattern."""
39
+
40
+
41
+ def extract_any_pattern(func_body: list[ast.stmt], for_node: ast.For) -> AnyAllMatch | None:
42
+ """Extract any() pattern from a for loop in a function body.
43
+
44
+ Pattern: for x in iter: if cond: return True; return False
45
+
46
+ Args:
47
+ func_body: List of statements in the function body
48
+ for_node: The for loop AST node to analyze
49
+
50
+ Returns:
51
+ AnyAllMatch if pattern detected, None otherwise
52
+ """
53
+ # Check for/else - different semantics, skip
54
+ if for_node.orelse:
55
+ return None
56
+
57
+ # Check loop body has exactly one if statement with return True
58
+ if_return_true = _extract_if_return_true(for_node.body)
59
+ if if_return_true is None:
60
+ return None
61
+
62
+ # Find position of for loop in function body
63
+ for_index = _get_stmt_index(func_body, for_node)
64
+ if for_index is None:
65
+ return None
66
+
67
+ # Check next statement is return False
68
+ if not _is_next_stmt_return_false(func_body, for_index):
69
+ return None
70
+
71
+ return AnyAllMatch(
72
+ for_node=for_node,
73
+ condition=if_return_true.test,
74
+ is_any=True,
75
+ )
76
+
77
+
78
+ def extract_all_pattern(func_body: list[ast.stmt], for_node: ast.For) -> AnyAllMatch | None:
79
+ """Extract all() pattern from a for loop in a function body.
80
+
81
+ Pattern: for x in iter: if not cond: return False; return True
82
+
83
+ Args:
84
+ func_body: List of statements in the function body
85
+ for_node: The for loop AST node to analyze
86
+
87
+ Returns:
88
+ AnyAllMatch if pattern detected, None otherwise
89
+ """
90
+ # Check for/else - different semantics, skip
91
+ if for_node.orelse:
92
+ return None
93
+
94
+ # Check loop body has exactly one if statement with return False
95
+ if_return_false = _extract_if_return_false(for_node.body)
96
+ if if_return_false is None:
97
+ return None
98
+
99
+ # Find position of for loop in function body
100
+ for_index = _get_stmt_index(func_body, for_node)
101
+ if for_index is None:
102
+ return None
103
+
104
+ # Check next statement is return True
105
+ if not _is_next_stmt_return_true(func_body, for_index):
106
+ return None
107
+
108
+ # Invert the condition for all() suggestion
109
+ condition = _invert_condition(if_return_false.test)
110
+
111
+ return AnyAllMatch(
112
+ for_node=for_node,
113
+ condition=condition,
114
+ is_any=False,
115
+ )
116
+
117
+
118
+ def _is_simple_if_return(stmt: ast.stmt) -> ast.Return | None:
119
+ """Check if statement is simple if with single return and no else.
120
+
121
+ Args:
122
+ stmt: Statement to check
123
+
124
+ Returns:
125
+ The return statement if pattern matches, None otherwise
126
+ """
127
+ if not isinstance(stmt, ast.If):
128
+ return None
129
+ if stmt.orelse:
130
+ return None
131
+ if len(stmt.body) != 1:
132
+ return None
133
+ if not isinstance(stmt.body[0], ast.Return):
134
+ return None
135
+ return stmt.body[0]
136
+
137
+
138
+ def _extract_if_return_true(body: list[ast.stmt]) -> ast.If | None:
139
+ """Extract if statement that only contains return True.
140
+
141
+ Args:
142
+ body: List of statements in for loop body
143
+
144
+ Returns:
145
+ The if statement if pattern matches, None otherwise
146
+ """
147
+ if len(body) != 1:
148
+ return None
149
+
150
+ stmt = body[0]
151
+ return_stmt = _is_simple_if_return(stmt)
152
+ if return_stmt is None:
153
+ return None
154
+
155
+ if not _is_literal_true(return_stmt.value):
156
+ return None
157
+
158
+ return cast(ast.If, stmt)
159
+
160
+
161
+ def _extract_if_return_false(body: list[ast.stmt]) -> ast.If | None:
162
+ """Extract if statement that only contains return False.
163
+
164
+ Args:
165
+ body: List of statements in for loop body
166
+
167
+ Returns:
168
+ The if statement if pattern matches, None otherwise
169
+ """
170
+ if len(body) != 1:
171
+ return None
172
+
173
+ stmt = body[0]
174
+ return_stmt = _is_simple_if_return(stmt)
175
+ if return_stmt is None:
176
+ return None
177
+
178
+ if not _is_literal_false(return_stmt.value):
179
+ return None
180
+
181
+ return cast(ast.If, stmt)
182
+
183
+
184
+ def _get_stmt_index(func_body: list[ast.stmt], target: ast.stmt) -> int | None:
185
+ """Find index of a statement in a function body.
186
+
187
+ Args:
188
+ func_body: List of statements in function body
189
+ target: Statement to find
190
+
191
+ Returns:
192
+ Index if found, None otherwise
193
+ """
194
+ for i, stmt in enumerate(func_body):
195
+ if stmt is target:
196
+ return i
197
+ return None
198
+
199
+
200
+ def _is_next_stmt_return_false(func_body: list[ast.stmt], for_index: int) -> bool:
201
+ """Check if the next statement after for loop is return False.
202
+
203
+ Args:
204
+ func_body: List of statements in function body
205
+ for_index: Index of the for loop
206
+
207
+ Returns:
208
+ True if next statement is return False
209
+ """
210
+ stmt = ast_utils.get_next_return_stmt(func_body, for_index)
211
+ if stmt is None:
212
+ return False
213
+ return _is_literal_false(stmt.value)
214
+
215
+
216
+ def _is_next_stmt_return_true(func_body: list[ast.stmt], for_index: int) -> bool:
217
+ """Check if the next statement after for loop is return True.
218
+
219
+ Args:
220
+ func_body: List of statements in function body
221
+ for_index: Index of the for loop
222
+
223
+ Returns:
224
+ True if next statement is return True
225
+ """
226
+ stmt = ast_utils.get_next_return_stmt(func_body, for_index)
227
+ if stmt is None:
228
+ return False
229
+ return _is_literal_true(stmt.value)
230
+
231
+
232
+ def _is_literal_true(node: ast.expr | None) -> bool:
233
+ """Check if expression is literal True.
234
+
235
+ Args:
236
+ node: AST expression node
237
+
238
+ Returns:
239
+ True if node is literal True
240
+ """
241
+ if node is None:
242
+ return False
243
+ if isinstance(node, ast.Constant):
244
+ return node.value is True
245
+ return False
246
+
247
+
248
+ def _is_literal_false(node: ast.expr | None) -> bool:
249
+ """Check if expression is literal False.
250
+
251
+ Args:
252
+ node: AST expression node
253
+
254
+ Returns:
255
+ True if node is literal False
256
+ """
257
+ if node is None:
258
+ return False
259
+ if isinstance(node, ast.Constant):
260
+ return node.value is False
261
+ return False
262
+
263
+
264
+ def _invert_condition(node: ast.expr) -> ast.expr:
265
+ """Invert a boolean condition.
266
+
267
+ If condition is 'not x', returns 'x'.
268
+ Otherwise wraps in 'not (...)'.
269
+
270
+ Args:
271
+ node: AST expression to invert
272
+
273
+ Returns:
274
+ Inverted expression
275
+ """
276
+ # If already negated with 'not', unwrap it
277
+ if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
278
+ return node.operand
279
+
280
+ # Otherwise wrap in 'not'
281
+ return ast.UnaryOp(op=ast.Not(), operand=node)
@@ -0,0 +1,40 @@
1
+ """
2
+ Purpose: Shared AST utility functions for collection pipeline analyzers
3
+
4
+ Scope: Common patterns for analyzing function bodies and return statements
5
+
6
+ Overview: Provides reusable AST analysis helpers to reduce duplication across
7
+ any_all_analyzer, filter_map_analyzer, and other pattern detection modules.
8
+ Centralizes common patterns like finding return statements after for loops.
9
+
10
+ Dependencies: ast module
11
+
12
+ Exports: get_next_return_stmt
13
+
14
+ Interfaces: get_next_return_stmt(func_body, index) -> ast.Return | None
15
+
16
+ Implementation: Pure functions using Python ast module for AST node inspection
17
+ """
18
+
19
+ import ast
20
+
21
+
22
+ def get_next_return_stmt(func_body: list[ast.stmt], current_index: int) -> ast.Return | None:
23
+ """Get the next return statement after a given index, if it exists.
24
+
25
+ Args:
26
+ func_body: List of statements in function body
27
+ current_index: Index of the current statement (e.g., for loop)
28
+
29
+ Returns:
30
+ The Return statement if the next statement is a return, None otherwise
31
+ """
32
+ next_index = current_index + 1
33
+ if next_index >= len(func_body):
34
+ return None
35
+
36
+ stmt = func_body[next_index]
37
+ if not isinstance(stmt, ast.Return):
38
+ return None
39
+
40
+ return stmt
@@ -38,6 +38,15 @@ class CollectionPipelineConfig:
38
38
  ignore: list[str] = field(default_factory=list)
39
39
  """File patterns to ignore."""
40
40
 
41
+ detect_any_all: bool = True
42
+ """Whether to detect any()/all() pattern anti-patterns."""
43
+
44
+ detect_filter_map: bool = True
45
+ """Whether to detect filter-map and takewhile pattern anti-patterns."""
46
+
47
+ use_walrus_operator: bool = True
48
+ """Whether to suggest walrus operator (:=) in filter-map suggestions (Python 3.8+)."""
49
+
41
50
  def __post_init__(self) -> None:
42
51
  """Validate configuration values."""
43
52
  if self.min_continues < 1:
@@ -60,4 +69,7 @@ class CollectionPipelineConfig:
60
69
  enabled=config.get("enabled", True),
61
70
  min_continues=config.get("min_continues", DEFAULT_MIN_CONTINUES),
62
71
  ignore=config.get("ignore", []),
72
+ detect_any_all=config.get("detect_any_all", True),
73
+ detect_filter_map=config.get("detect_filter_map", True),
74
+ use_walrus_operator=config.get("use_walrus_operator", True),
63
75
  )
@@ -66,10 +66,7 @@ def has_side_effects(continues: list[ast.If]) -> bool:
66
66
  Returns:
67
67
  True if any condition has side effects (e.g., walrus operator)
68
68
  """
69
- for if_node in continues:
70
- if _condition_has_side_effects(if_node.test):
71
- return True
72
- return False
69
+ return any(_condition_has_side_effects(if_node.test) for if_node in continues)
73
70
 
74
71
 
75
72
  def _condition_has_side_effects(node: ast.expr) -> bool:
@@ -81,10 +78,7 @@ def _condition_has_side_effects(node: ast.expr) -> bool:
81
78
  Returns:
82
79
  True if expression has side effects
83
80
  """
84
- for child in ast.walk(node):
85
- if isinstance(child, ast.NamedExpr):
86
- return True
87
- return False
81
+ return any(isinstance(child, ast.NamedExpr) for child in ast.walk(node))
88
82
 
89
83
 
90
84
  def has_body_after_continues(body: list[ast.stmt], num_continues: int) -> bool: