thailint 0.14.0__py3-none-any.whl → 0.15.1__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 (56) hide show
  1. src/analyzers/rust_base.py +155 -0
  2. src/analyzers/rust_context.py +141 -0
  3. src/cli/config.py +6 -4
  4. src/cli/linters/code_patterns.py +64 -16
  5. src/cli/linters/code_smells.py +23 -14
  6. src/cli/linters/documentation.py +5 -3
  7. src/cli/linters/performance.py +23 -10
  8. src/cli/linters/shared.py +22 -6
  9. src/cli/linters/structure.py +13 -4
  10. src/cli/linters/structure_quality.py +9 -4
  11. src/cli/utils.py +4 -4
  12. src/config.py +34 -21
  13. src/core/python_lint_rule.py +101 -0
  14. src/linter_config/ignore.py +2 -1
  15. src/linters/cqs/__init__.py +54 -0
  16. src/linters/cqs/config.py +55 -0
  17. src/linters/cqs/function_analyzer.py +201 -0
  18. src/linters/cqs/input_detector.py +139 -0
  19. src/linters/cqs/linter.py +159 -0
  20. src/linters/cqs/output_detector.py +84 -0
  21. src/linters/cqs/python_analyzer.py +54 -0
  22. src/linters/cqs/types.py +82 -0
  23. src/linters/cqs/typescript_cqs_analyzer.py +61 -0
  24. src/linters/cqs/typescript_function_analyzer.py +192 -0
  25. src/linters/cqs/typescript_input_detector.py +203 -0
  26. src/linters/cqs/typescript_output_detector.py +117 -0
  27. src/linters/cqs/violation_builder.py +94 -0
  28. src/linters/dry/typescript_value_extractor.py +2 -1
  29. src/linters/file_header/linter.py +2 -1
  30. src/linters/file_placement/linter.py +6 -6
  31. src/linters/file_placement/pattern_validator.py +6 -5
  32. src/linters/file_placement/rule_checker.py +10 -5
  33. src/linters/lazy_ignores/config.py +5 -3
  34. src/linters/lazy_ignores/python_analyzer.py +5 -1
  35. src/linters/lazy_ignores/types.py +2 -1
  36. src/linters/lbyl/__init__.py +3 -1
  37. src/linters/lbyl/linter.py +67 -0
  38. src/linters/lbyl/pattern_detectors/__init__.py +30 -2
  39. src/linters/lbyl/pattern_detectors/base.py +24 -7
  40. src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
  41. src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
  42. src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
  43. src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
  44. src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
  45. src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
  46. src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
  47. src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
  48. src/linters/lbyl/python_analyzer.py +215 -0
  49. src/linters/lbyl/violation_builder.py +354 -0
  50. src/linters/stringly_typed/ignore_checker.py +4 -6
  51. src/orchestrator/language_detector.py +5 -3
  52. {thailint-0.14.0.dist-info → thailint-0.15.1.dist-info}/METADATA +4 -2
  53. {thailint-0.14.0.dist-info → thailint-0.15.1.dist-info}/RECORD +56 -29
  54. {thailint-0.14.0.dist-info → thailint-0.15.1.dist-info}/WHEEL +0 -0
  55. {thailint-0.14.0.dist-info → thailint-0.15.1.dist-info}/entry_points.txt +0 -0
  56. {thailint-0.14.0.dist-info → thailint-0.15.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,354 @@
1
+ """
2
+ Purpose: Build Violation objects with EAFP suggestions for LBYL patterns
3
+
4
+ Scope: Creates violations for detected LBYL anti-patterns with fix suggestions
5
+
6
+ Overview: Provides module-level functions that create Violation objects for LBYL
7
+ anti-patterns detected in Python code. Each violation includes the rule ID, location,
8
+ descriptive message, and an EAFP suggestion showing how to refactor the code using
9
+ try/except. Supports dict key check, hasattr, isinstance, file exists, len check,
10
+ None check, string validator, and division zero-check patterns.
11
+
12
+ Dependencies: src.core.types for Violation, src.core.base for BaseLintContext
13
+
14
+ Exports: build_dict_key_violation, build_hasattr_violation, build_isinstance_violation,
15
+ build_file_exists_violation, build_len_check_violation, create_syntax_error_violation,
16
+ build_none_check_violation, build_string_validator_violation, build_division_check_violation
17
+
18
+ Interfaces: Module functions for building LBYL violations
19
+
20
+ Implementation: Factory functions for each violation type with descriptive suggestions
21
+
22
+ Suppressions:
23
+ too-many-arguments, too-many-positional-arguments: build_string_validator_violation
24
+ requires 6 parameters (file_path, line, column, string_name, validator_method,
25
+ conversion_func) to create accurate violation messages and suggestions
26
+ """
27
+
28
+ from src.core.base import BaseLintContext
29
+ from src.core.types import Violation
30
+
31
+
32
+ def build_dict_key_violation(
33
+ file_path: str,
34
+ line: int,
35
+ column: int,
36
+ dict_name: str,
37
+ key_expression: str,
38
+ ) -> Violation:
39
+ """Build a violation for dict key LBYL pattern.
40
+
41
+ Args:
42
+ file_path: Path to the file containing the violation
43
+ line: Line number (1-indexed)
44
+ column: Column number (0-indexed)
45
+ dict_name: Name of the dict being checked
46
+ key_expression: Expression used as key
47
+
48
+ Returns:
49
+ Violation object with EAFP suggestion
50
+ """
51
+ message = (
52
+ f"LBYL pattern: 'if {key_expression} in {dict_name}' followed by "
53
+ f"'{dict_name}[{key_expression}]'"
54
+ )
55
+
56
+ suggestion = (
57
+ f"Use EAFP: 'with suppress(KeyError): value = {dict_name}[{key_expression}]' "
58
+ f"or 'try: ... except KeyError: ...'"
59
+ )
60
+
61
+ return Violation(
62
+ rule_id="lbyl.dict-key-check",
63
+ file_path=file_path,
64
+ line=line,
65
+ column=column,
66
+ message=message,
67
+ suggestion=suggestion,
68
+ )
69
+
70
+
71
+ def build_hasattr_violation(
72
+ file_path: str,
73
+ line: int,
74
+ column: int,
75
+ object_name: str,
76
+ attribute_name: str,
77
+ ) -> Violation:
78
+ """Build a violation for hasattr LBYL pattern.
79
+
80
+ Args:
81
+ file_path: Path to the file containing the violation
82
+ line: Line number (1-indexed)
83
+ column: Column number (0-indexed)
84
+ object_name: Name of the object being checked
85
+ attribute_name: Name of the attribute being checked
86
+
87
+ Returns:
88
+ Violation object with EAFP suggestion
89
+ """
90
+ message = (
91
+ f"LBYL pattern: 'if hasattr({object_name}, '{attribute_name}')' followed by "
92
+ f"'{object_name}.{attribute_name}'"
93
+ )
94
+
95
+ suggestion = f"Use EAFP: 'try: {object_name}.{attribute_name} except AttributeError: ...'"
96
+
97
+ return Violation(
98
+ rule_id="lbyl.hasattr-check",
99
+ file_path=file_path,
100
+ line=line,
101
+ column=column,
102
+ message=message,
103
+ suggestion=suggestion,
104
+ )
105
+
106
+
107
+ def build_isinstance_violation(
108
+ file_path: str,
109
+ line: int,
110
+ column: int,
111
+ object_name: str,
112
+ type_name: str,
113
+ ) -> Violation:
114
+ """Build a violation for isinstance LBYL pattern.
115
+
116
+ Args:
117
+ file_path: Path to the file containing the violation
118
+ line: Line number (1-indexed)
119
+ column: Column number (0-indexed)
120
+ object_name: Name of the object being checked
121
+ type_name: Name of the type being checked against
122
+
123
+ Returns:
124
+ Violation object with EAFP suggestion
125
+ """
126
+ message = (
127
+ f"LBYL pattern: 'if isinstance({object_name}, {type_name})' before type-specific operation"
128
+ )
129
+
130
+ suggestion = (
131
+ "Consider EAFP: 'try: ... except (TypeError, AttributeError): ...' "
132
+ "instead of isinstance check"
133
+ )
134
+
135
+ return Violation(
136
+ rule_id="lbyl.isinstance-check",
137
+ file_path=file_path,
138
+ line=line,
139
+ column=column,
140
+ message=message,
141
+ suggestion=suggestion,
142
+ )
143
+
144
+
145
+ def create_syntax_error_violation(error: SyntaxError, context: BaseLintContext) -> Violation:
146
+ """Create a violation for a syntax error.
147
+
148
+ Args:
149
+ error: The SyntaxError from parsing
150
+ context: Lint context with file information
151
+
152
+ Returns:
153
+ Violation indicating syntax error
154
+ """
155
+ file_path = str(context.file_path) if context.file_path else "unknown"
156
+ line = error.lineno or 1
157
+ column = error.offset or 0
158
+
159
+ return Violation(
160
+ rule_id="lbyl.syntax-error",
161
+ file_path=file_path,
162
+ line=line,
163
+ column=column,
164
+ message=f"Syntax error: {error.msg}",
165
+ suggestion="Fix the syntax error before running LBYL analysis",
166
+ )
167
+
168
+
169
+ def build_file_exists_violation(
170
+ file_path: str,
171
+ line: int,
172
+ column: int,
173
+ path_expression: str,
174
+ check_type: str,
175
+ ) -> Violation:
176
+ """Build a violation for file exists LBYL pattern.
177
+
178
+ Args:
179
+ file_path: Path to the file containing the violation
180
+ line: Line number (1-indexed)
181
+ column: Column number (0-indexed)
182
+ path_expression: Expression representing the file path being checked
183
+ check_type: Type of check ("os.path.exists", "Path.exists", "exists")
184
+
185
+ Returns:
186
+ Violation object with EAFP suggestion
187
+ """
188
+ message = (
189
+ f"LBYL pattern: 'if {check_type}({path_expression})' followed by "
190
+ f"file operation on '{path_expression}'"
191
+ )
192
+
193
+ suggestion = (
194
+ f"Use EAFP: 'try: with open({path_expression}) as f: ... except FileNotFoundError: ...'"
195
+ )
196
+
197
+ return Violation(
198
+ rule_id="lbyl.file-exists-check",
199
+ file_path=file_path,
200
+ line=line,
201
+ column=column,
202
+ message=message,
203
+ suggestion=suggestion,
204
+ )
205
+
206
+
207
+ def build_len_check_violation(
208
+ file_path: str,
209
+ line: int,
210
+ column: int,
211
+ collection_name: str,
212
+ index_expression: str,
213
+ ) -> Violation:
214
+ """Build a violation for len check LBYL pattern.
215
+
216
+ Args:
217
+ file_path: Path to the file containing the violation
218
+ line: Line number (1-indexed)
219
+ column: Column number (0-indexed)
220
+ collection_name: Name of the collection being checked
221
+ index_expression: Expression used as index
222
+
223
+ Returns:
224
+ Violation object with EAFP suggestion
225
+ """
226
+ message = (
227
+ f"LBYL pattern: 'if len({collection_name}) > {index_expression}' followed by "
228
+ f"'{collection_name}[...]'"
229
+ )
230
+
231
+ suggestion = (
232
+ f"Use EAFP: 'try: value = {collection_name}[{index_expression}] except IndexError: ...'"
233
+ )
234
+
235
+ return Violation(
236
+ rule_id="lbyl.len-check",
237
+ file_path=file_path,
238
+ line=line,
239
+ column=column,
240
+ message=message,
241
+ suggestion=suggestion,
242
+ )
243
+
244
+
245
+ def build_none_check_violation(
246
+ file_path: str,
247
+ line: int,
248
+ column: int,
249
+ variable_name: str,
250
+ ) -> Violation:
251
+ """Build a violation for None check LBYL pattern.
252
+
253
+ Args:
254
+ file_path: Path to the file containing the violation
255
+ line: Line number (1-indexed)
256
+ column: Column number (0-indexed)
257
+ variable_name: Name of the variable being checked for None
258
+
259
+ Returns:
260
+ Violation object with EAFP suggestion
261
+ """
262
+ message = (
263
+ f"LBYL pattern: 'if {variable_name} is not None' followed by '{variable_name}.<method>()'"
264
+ )
265
+
266
+ suggestion = (
267
+ f"Use EAFP: 'try: {variable_name}.<method>() except AttributeError: ...' "
268
+ f"or check if None is a valid state to handle differently"
269
+ )
270
+
271
+ return Violation(
272
+ rule_id="lbyl.none-check",
273
+ file_path=file_path,
274
+ line=line,
275
+ column=column,
276
+ message=message,
277
+ suggestion=suggestion,
278
+ )
279
+
280
+
281
+ def build_string_validator_violation( # pylint: disable=too-many-arguments,too-many-positional-arguments
282
+ file_path: str,
283
+ line: int,
284
+ column: int,
285
+ string_name: str,
286
+ validator_method: str,
287
+ conversion_func: str,
288
+ ) -> Violation:
289
+ """Build a violation for string validator LBYL pattern.
290
+
291
+ Args:
292
+ file_path: Path to the file containing the violation
293
+ line: Line number (1-indexed)
294
+ column: Column number (0-indexed)
295
+ string_name: Name of the string being validated
296
+ validator_method: Validation method used (isnumeric, isdigit, etc.)
297
+ conversion_func: Conversion function used (int, float)
298
+
299
+ Returns:
300
+ Violation object with EAFP suggestion
301
+ """
302
+ message = (
303
+ f"LBYL pattern: 'if {string_name}.{validator_method}()' followed by "
304
+ f"'{conversion_func}({string_name})'"
305
+ )
306
+
307
+ suggestion = f"Use EAFP: 'try: value = {conversion_func}({string_name}) except ValueError: ...'"
308
+
309
+ return Violation(
310
+ rule_id="lbyl.string-validator",
311
+ file_path=file_path,
312
+ line=line,
313
+ column=column,
314
+ message=message,
315
+ suggestion=suggestion,
316
+ )
317
+
318
+
319
+ def build_division_check_violation(
320
+ file_path: str,
321
+ line: int,
322
+ column: int,
323
+ divisor_name: str,
324
+ operation: str,
325
+ ) -> Violation:
326
+ """Build a violation for division zero-check LBYL pattern.
327
+
328
+ Args:
329
+ file_path: Path to the file containing the violation
330
+ line: Line number (1-indexed)
331
+ column: Column number (0-indexed)
332
+ divisor_name: Name of the divisor being checked for zero
333
+ operation: Division operation used (/, //, %, /=, //=, %=)
334
+
335
+ Returns:
336
+ Violation object with EAFP suggestion
337
+ """
338
+ message = (
339
+ f"LBYL pattern: 'if {divisor_name} != 0' followed by "
340
+ f"'{operation}' operation with '{divisor_name}'"
341
+ )
342
+
343
+ suggestion = (
344
+ f"Use EAFP: 'try: result = ... {operation} {divisor_name} except ZeroDivisionError: ...'"
345
+ )
346
+
347
+ return Violation(
348
+ rule_id="lbyl.division-check",
349
+ file_path=file_path,
350
+ line=line,
351
+ column=column,
352
+ message=message,
353
+ suggestion=suggestion,
354
+ )
@@ -19,6 +19,7 @@ Implementation: Uses cached IgnoreDirectiveParser singleton, reads file content
19
19
  supports both stringly-typed.* and stringly-typed specific rule matching
20
20
  """
21
21
 
22
+ from contextlib import suppress
22
23
  from pathlib import Path
23
24
 
24
25
  from src.core.types import Violation
@@ -73,7 +74,7 @@ class IgnoreChecker:
73
74
  Returns:
74
75
  File content or empty string if unreadable
75
76
  """
76
- if file_path in self._file_content_cache:
77
+ with suppress(KeyError):
77
78
  return self._file_content_cache[file_path]
78
79
 
79
80
  content = self._read_file_content(file_path)
@@ -90,12 +91,9 @@ class IgnoreChecker:
90
91
  File content or empty string if unreadable
91
92
  """
92
93
  try:
93
- path = Path(file_path)
94
- if path.exists():
95
- return path.read_text(encoding="utf-8")
94
+ return Path(file_path).read_text(encoding="utf-8")
96
95
  except (OSError, UnicodeDecodeError):
97
- pass
98
- return ""
96
+ return ""
99
97
 
100
98
  def clear_cache(self) -> None:
101
99
  """Clear file content cache."""
@@ -17,12 +17,13 @@ Dependencies: pathlib for file path handling and content reading
17
17
  Exports: detect_language(file_path: Path) -> str function, EXTENSION_MAP constant
18
18
 
19
19
  Interfaces: detect_language(file_path: Path) -> str returns language identifier string
20
- (python, javascript, typescript, java, go, unknown)
20
+ (python, javascript, typescript, java, go, rust, unknown)
21
21
 
22
22
  Implementation: Dictionary-based extension lookup for O(1) detection, first-line shebang
23
23
  parsing with substring matching, lazy file reading only when extension unknown
24
24
  """
25
25
 
26
+ from contextlib import suppress
26
27
  from pathlib import Path
27
28
 
28
29
  # Extension to language mapping
@@ -34,6 +35,7 @@ EXTENSION_MAP = {
34
35
  ".jsx": "javascript",
35
36
  ".java": "java",
36
37
  ".go": "go",
38
+ ".rs": "rust",
37
39
  }
38
40
 
39
41
 
@@ -67,10 +69,10 @@ def detect_language(file_path: Path) -> str:
67
69
  file_path: Path to file to analyze.
68
70
 
69
71
  Returns:
70
- Language identifier (python, javascript, typescript, java, go, unknown).
72
+ Language identifier (python, javascript, typescript, java, go, rust, unknown).
71
73
  """
72
74
  ext = file_path.suffix.lower()
73
- if ext in EXTENSION_MAP:
75
+ with suppress(KeyError):
74
76
  return EXTENSION_MAP[ext]
75
77
 
76
78
  if file_path.exists() and file_path.stat().st_size > 0:
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thailint
3
- Version: 0.14.0
3
+ Version: 0.15.1
4
4
  Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
5
5
  License: MIT
6
6
  License-File: LICENSE
7
- Keywords: linter,ai,code-quality,static-analysis,file-placement,governance,multi-language,cli,docker,python,performance,typescript
7
+ Keywords: linter,ai,code-quality,static-analysis,file-placement,governance,multi-language,cli,docker,python,performance,typescript,rust
8
8
  Author: Steve Jackson
9
9
  Requires-Python: >=3.11,<4.0
10
10
  Classifier: Development Status :: 4 - Beta
@@ -27,6 +27,7 @@ Requires-Dist: click (>=8.1.0,<9.0.0)
27
27
  Requires-Dist: pyprojroot (>=0.3.0,<0.4.0)
28
28
  Requires-Dist: pyyaml (>=6.0,<7.0)
29
29
  Requires-Dist: tree-sitter (>=0.25.2,<0.26.0)
30
+ Requires-Dist: tree-sitter-rust (>=0.23.2,<0.24.0)
30
31
  Requires-Dist: tree-sitter-typescript (>=0.23.2,<0.24.0)
31
32
  Project-URL: Documentation, https://thai-lint.readthedocs.io/
32
33
  Project-URL: Homepage, https://github.com/be-wise-be-kind/thai-lint
@@ -84,6 +85,7 @@ That's it. See violations, fix them, ship better code.
84
85
  | **Lazy Ignores** | Unjustified linting suppressions | `thailint lazy-ignores src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lazy-ignores-linter/) |
85
86
  | **Print Statements** | Debug prints left in code | `thailint print-statements src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/print-statements-linter/) |
86
87
  | **Stringly Typed** | Strings that should be enums | `thailint stringly-typed src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/stringly-typed-linter/) |
88
+ | **LBYL** | Look Before You Leap anti-patterns | `thailint lbyl src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lbyl-linter/) |
87
89
 
88
90
  ## Configuration
89
91
 
@@ -1,30 +1,33 @@
1
1
  src/__init__.py,sha256=0IT3HnAnSBEfos4G_27cflJiaoWWfeSEfHxsUr53OsM,2192
2
2
  src/analyzers/__init__.py,sha256=hCN7ugG_Xs2N_WfWO7ZId-Cv4kvN1AxhIYTA4SD2fJ4,1062
3
3
  src/analyzers/ast_utils.py,sha256=iWCRzlTw2QF5wWzQx1OmKshx7TU3pHxC8_omv9XUInw,1630
4
+ src/analyzers/rust_base.py,sha256=_sqmXhH4vylXbQEPp-gHnUYmf6F_2c5YXZgXBqk2Kcg,5185
5
+ src/analyzers/rust_context.py,sha256=8dV-LRst6CUPc2Z9iBXt70GtRTKo62PF3_uFBtFAKuw,4046
4
6
  src/analyzers/typescript_base.py,sha256=Dtc_2jLSNoadh53MyeW2syrJMBIMVPvotcM8SeDEXH8,5244
5
7
  src/api.py,sha256=pJ5l3qxccKBEY-BkANwzTgLAl1ZFq7OP6hx6LSxbhDw,4664
6
8
  src/cli/__init__.py,sha256=nMxKv4D0t09LwUm4Z5w-b7vIuyn24SFzv78BKzE3AOQ,1272
7
9
  src/cli/__main__.py,sha256=xIKI57yqB1NOw9eXnGXfU8rC2UwcAJYjDxlZbt9eP0w,671
8
- src/cli/config.py,sha256=qbBX8LQrlPcOrvPYcZw9Hobv9FqSI7T3xy2HUITRqAU,14426
10
+ src/cli/config.py,sha256=wh9lp0Z2FUEKbYawB65Y9vrlu1Yiqe8-CD_vnRDqwEg,14467
9
11
  src/cli/config_merge.py,sha256=A1eCthiLwjj0SEhOcxa6t2hYwWMapGJpL9sCmYvUFw4,8464
10
12
  src/cli/linters/__init__.py,sha256=Dlx2CRHT5_QeIeCJ0horPXci9zRP_KzSz_-mg0VMbOw,2343
11
- src/cli/linters/code_patterns.py,sha256=3mi6tRJiNAN2DBdQLcawJhn5pSBDIN1-pLBD_1HNb90,9059
12
- src/cli/linters/code_smells.py,sha256=a5nMke9-6jPmkyG9RrgfuiOYfJuuUsmkujgAdsvnVOs,11457
13
- src/cli/linters/documentation.py,sha256=T2HeyQim4m_aE3f1pNnsCW9rDnTQf_YETcHnExXum4k,3224
14
- src/cli/linters/performance.py,sha256=toQqOPOxiJ5D5TvXv6pFoIibfbquOyrnQVrZUmrz8Zc,9530
15
- src/cli/linters/shared.py,sha256=f3WsTgLyetndI9li9W-q_-MvOXjahNyJGJVCEoUQ6QI,9489
16
- src/cli/linters/structure.py,sha256=77JdZmjp7pBNoMP1SKkj4EQa4z4IZ_260TonvQ__IgE,10515
17
- src/cli/linters/structure_quality.py,sha256=T1uKtDnKLz_YnHe_MP6lB8mvfxdaJNP-XQ8_IWUpTt0,10586
13
+ src/cli/linters/code_patterns.py,sha256=2AcFdLPPdkJ8U883ozXE_0eLWRdiL_xfzXm4kmRn-Ro,11072
14
+ src/cli/linters/code_smells.py,sha256=yrKqRD3JTVVqyPL8rwK9VPbHgccXoj2IfaLK3lBF4Jc,11782
15
+ src/cli/linters/documentation.py,sha256=AhkyXfH2FbcxXT3BV6GIKkGbWCjyrz8qcKhz3sx7Yww,3289
16
+ src/cli/linters/performance.py,sha256=qvCW6T84zNZi1TyuxHtsEksgCtMA5TUgt_UdSq0vqHo,9878
17
+ src/cli/linters/shared.py,sha256=eVXhBDr69fRGAP4lApTR_M0gDfLeWlDAqx6VCEAWXsU,10117
18
+ src/cli/linters/structure.py,sha256=EGjw0zMLdGi4lTwM1zd_FzxQ7A-AG3fNPcKcF5uGFSo,10748
19
+ src/cli/linters/structure_quality.py,sha256=0npTcI6OUMVmvd-Ha6NjDZb2Dn1EhpfTLXmMRB69qaI,10739
18
20
  src/cli/main.py,sha256=mRyRN_IQI2WyqDCxI8vuzdhbkCkVrK6INfJPjU8ayIU,3844
19
- src/cli/utils.py,sha256=L1q2i6NBkWoQZLYnkeNNdfzHYRhaUobbzqSr2w8JUFY,12595
21
+ src/cli/utils.py,sha256=wFMvW7ScseHxKgvAmuQCWWHBGbtBzu8oPwATsCyNsfU,12610
20
22
  src/cli_main.py,sha256=C0Ey7YNlG3ipqb3KsJZ8rL8PJ4ueVp_45IUirGidvHI,1618
21
- src/config.py,sha256=6yQyfvYizffV6GQrnAlL30bKR4iKROPoxoe4al42Pqg,12531
23
+ src/config.py,sha256=O3ixzsYekGjlggmIsawCU1bctOa0MyG2IczHpg3mGyw,12753
22
24
  src/core/__init__.py,sha256=5FtsDvhMt4SNRx3pbcGURrxn135XRbeRrjSUxiXwkNc,381
23
25
  src/core/base.py,sha256=u5A8geprlKnsJk4ShiLHTKXRekZUB4I6rPQWxgiFeto,8019
24
26
  src/core/cli_utils.py,sha256=ZdFSPrZ4WfpTMh-mc_Z3u5OYidE1YyPRKflMynPosa8,6552
25
27
  src/core/config_parser.py,sha256=CRHV2-csxag6yQzx_4IYYz57QSUYjPkeSb0XvOyshRI,4272
26
28
  src/core/constants.py,sha256=PKtPDqk6k9VuOSgjq1FAdi2CTvlnhdXvLj91dNaMDTA,1584
27
29
  src/core/linter_utils.py,sha256=StnKFzJgSvLyao1S0LpTKhsXo8nOwpdKpxo7mXl5PIg,8594
30
+ src/core/python_lint_rule.py,sha256=OpdIDLPV1MDtmjy6GPrrOA3_rRV2_x14keHAQQtI_pc,3516
28
31
  src/core/registry.py,sha256=yRA8mQLiZwjmgxl1wSTgdj1cuo_QXuRdrXt3NpCBUgE,3285
29
32
  src/core/rule_discovery.py,sha256=tgRH-BJGKsQTxfa249yrY7UJuonRjobMCENqmhcbAeY,5496
30
33
  src/core/types.py,sha256=SElFzf_VSrAMsoiE0aU8ZYXuvKqdfwfM5umUHx4eT8w,3342
@@ -34,7 +37,7 @@ src/formatters/__init__.py,sha256=yE1yIL8lplTMEjsmQm7F-kOMaYq7OjmbFuiwwK0D-gM,81
34
37
  src/formatters/sarif.py,sha256=gGOwb_v7j4mx4bpvV1NNDd-JyHH8i8XX89iQ6uRSvG4,7050
35
38
  src/linter_config/__init__.py,sha256=_I2VVlZlfKyT-tKukuUA5-aVcHLOe3m6C2cev43AiEc,298
36
39
  src/linter_config/directive_markers.py,sha256=nRc2Mp3B1mn6tu1XH88ugMxWffk1k8OUNohepaRQ0S0,3245
37
- src/linter_config/ignore.py,sha256=lSt_2nGHiGe06Ha1pl2nZTbAVUjwhKtS2b3UHIZuA1I,13089
40
+ src/linter_config/ignore.py,sha256=40afiMsu1zC29RiwGX8hUPUqM056H_wcOgZZUNOwIes,13111
38
41
  src/linter_config/loader.py,sha256=K6mKRkP2jgwar-pwBoJGWgwynLVjqdez-l3Nd6bUCMk,3363
39
42
  src/linter_config/pattern_utils.py,sha256=BjV95SySST3HqZBwF1Og8yoHqFxuqQ16VPCacE21ks0,2056
40
43
  src/linter_config/rule_matcher.py,sha256=EWqSv4UY90fWps3AzDCSF7PZCbYyFTEBZT2h_txcRms,2779
@@ -48,6 +51,19 @@ src/linters/collection_pipeline/detector.py,sha256=7_keKR5lGo5rvOVCAvV_6-bG29I_S
48
51
  src/linters/collection_pipeline/filter_map_analyzer.py,sha256=QbTjObryaLVB71MD4b4SyoOMEO_EgMNedNZLVm05QCQ,11829
49
52
  src/linters/collection_pipeline/linter.py,sha256=53pkC1a6mvNTzjK_LDV_JmMysbZBLUsM_NjavvAZZBQ,13552
50
53
  src/linters/collection_pipeline/suggestion_builder.py,sha256=4-RHBw95u7gPKpoN1xZlpSOkqzXY7_TRrB_otUXsdDE,4357
54
+ src/linters/cqs/__init__.py,sha256=d78ktPDBk_D9BStrNA99lYFHmuIVWke9wbVHiX_h81g,2120
55
+ src/linters/cqs/config.py,sha256=09U8rGSC8B4QS6ivrCKFPTXzLsU6oTSAjgIkwUrmQlc,2148
56
+ src/linters/cqs/function_analyzer.py,sha256=lPAmqLM2slEN0YkVEUy8xrq3pT7dQahy6sQUY6KvgfA,7756
57
+ src/linters/cqs/input_detector.py,sha256=GdCqXvVIGXOjKdCy5rQsX-IP88-taPcU3TLCumjMIqc,5004
58
+ src/linters/cqs/linter.py,sha256=QWgKuIzE678nUy60dY5BMh1NllLblSh9sbfIxmRlq-o,5666
59
+ src/linters/cqs/output_detector.py,sha256=uLddl9skCTICYXHSvIDYsKLbmKb3wXpZVkXjASVf-fk,3077
60
+ src/linters/cqs/python_analyzer.py,sha256=84x8-vUPRK0GkAgPKB-UzGmRX5Sb84Vq-fi_tpTQAGE,1879
61
+ src/linters/cqs/types.py,sha256=QL_kt1ywYB8KC2QRO-vMMygqHPVMoNS9FAU19zQmRs0,2816
62
+ src/linters/cqs/typescript_cqs_analyzer.py,sha256=kd1_b6dsKDWifxD436zss32eoiX5opURsqm6G8no7YQ,2318
63
+ src/linters/cqs/typescript_function_analyzer.py,sha256=f3TVZXpaDYRUc8xEw3TRBc4Xbg41qmAbACRnjHkTdcQ,7790
64
+ src/linters/cqs/typescript_input_detector.py,sha256=0PazhU69Utc_TGJTehYtSFLn0nhDfELWiX5YcvLWyWk,7979
65
+ src/linters/cqs/typescript_output_detector.py,sha256=b3JlDabgvRElsAkkopIVJfGq6BV0y_wcsNvWzPGO9F4,4122
66
+ src/linters/cqs/violation_builder.py,sha256=6bRWs9b9bqiSYaxi87gxWieKMTZaPCwHMMpplVpKZMo,2868
51
67
  src/linters/dry/__init__.py,sha256=p58tN3z_VbulfTkRm1kLZJ43Bemt66T2sro1teirUY8,826
52
68
  src/linters/dry/base_token_analyzer.py,sha256=hkR3MI6UYwQ7PNJiyGiIPiX7uMrDRHr0mzI-aG8wVCM,3199
53
69
  src/linters/dry/block_filter.py,sha256=3RgmRSqYFk2eqATLOWN3hET09JuaPEFux3ResA0ltqo,11432
@@ -72,7 +88,7 @@ src/linters/dry/token_hasher.py,sha256=RoUXByVHwf9TZjRqXB3aI1htNZS0pX41oOsUxvlsF
72
88
  src/linters/dry/typescript_analyzer.py,sha256=xGUcQO8MvJnAVgn5GZRwQAc5xZsD0T-qOQlFuJcvfZM,10777
73
89
  src/linters/dry/typescript_constant_extractor.py,sha256=ri5NivpcxLAxwdMJvbeTF4Vu0WS_Fgf7FydQGoVNgm0,5130
74
90
  src/linters/dry/typescript_statement_detector.py,sha256=8WiwcjLs8j8_wp0UTsoXN0vVr1mNa562O1CB-FtaQR4,8848
75
- src/linters/dry/typescript_value_extractor.py,sha256=Wi6Yy0yklQSDpeA6FRCsquXILHD8RjRFKJI5Nsg3f70,2506
91
+ src/linters/dry/typescript_value_extractor.py,sha256=TbHIvcEnmjSV1WNnkRbXeUi_JA9-rGX0_BrD4bczO9Y,2519
76
92
  src/linters/dry/violation_builder.py,sha256=WkCibSNytoqMHGC-3GrVff4PD7-SOnVzzZgkMeqmzco,2952
77
93
  src/linters/dry/violation_filter.py,sha256=2e6NHN7GYadt27Pz5kiXhttKPJu1loiXhOi0G3J3Epk,3211
78
94
  src/linters/dry/violation_generator.py,sha256=7jkRfauwAEdipFuDhoy82eIeXS1ECN4W_3jspMlnFaU,6068
@@ -83,7 +99,7 @@ src/linters/file_header/bash_parser.py,sha256=aRlIbR6x8IeYAj8w6a3eQzdZZivHB0oPg8
83
99
  src/linters/file_header/config.py,sha256=gdnZoJ-lEq8DACr6C2UKLorHiFCNdQspP_88FQBtoyc,4755
84
100
  src/linters/file_header/css_parser.py,sha256=ijpGMixg2ZqNWWdiZjSNtMXCOhm6XDfSY7OU68B9fS8,2332
85
101
  src/linters/file_header/field_validator.py,sha256=owA-ahjx0cUWBIqCxT0dMyGTABQA0b8HbdWbZPQk7pw,2769
86
- src/linters/file_header/linter.py,sha256=9XFk9H-0QtXvMBgHfVM5PcI1igDcE8ylLARr0547XZ4,12532
102
+ src/linters/file_header/linter.py,sha256=t51VJzKnRo8s_RM0iZLSQBmmLT4mmM8UDpaRekNurwE,12564
87
103
  src/linters/file_header/markdown_parser.py,sha256=4rNYrxuZbJz4LoSmv0U741Cv7wP9jftTl0Ty7mBDHRI,5323
88
104
  src/linters/file_header/python_parser.py,sha256=RTOeEt1b3tCvFWbZIt89awQA37CUOSBIGagEYnayn-M,1432
89
105
  src/linters/file_header/typescript_parser.py,sha256=R11Vkr6dUVaU8t90m8rrkMzODtBYk7u-TYFsMDRwzX8,2532
@@ -91,28 +107,39 @@ src/linters/file_header/violation_builder.py,sha256=HPYTmrcCmcO6Dx5dhmj85zZgEBM5
91
107
  src/linters/file_placement/__init__.py,sha256=vJ43GZujcbAk-K3DwfsQZ0J3yP_5G35CKssatLyntXk,862
92
108
  src/linters/file_placement/config_loader.py,sha256=tLBeP9njYmtD0FNQsKkywMQJWrZaDBl7z_5sqVLzndc,2690
93
109
  src/linters/file_placement/directory_matcher.py,sha256=1rxJtCEzqDYDQnscVX6pzk7gxCMD11pVIGaWcli-tHY,2742
94
- src/linters/file_placement/linter.py,sha256=8mKCs20iEyUs3GaguHSHvMYdXJhXkoPTOfACM3kAFLs,15310
110
+ src/linters/file_placement/linter.py,sha256=tjmoTYadTrXpyGXGiUNYwo8g5P2eGZ82LArgp9S8ztk,15283
95
111
  src/linters/file_placement/path_resolver.py,sha256=S6g7xOYsoSc0O_RDJh8j4Z2klcwzp16rSUfEAErGOTI,1972
96
112
  src/linters/file_placement/pattern_matcher.py,sha256=56PCVL_4ajpTCnebHNUZKMJyAWeUOUHkeEwd4o3ofXQ,3183
97
- src/linters/file_placement/pattern_validator.py,sha256=kg96qbN62kL0cTMncIFsoofiEJl64jWW4-0eK9_mD98,4247
98
- src/linters/file_placement/rule_checker.py,sha256=OIN8v6uwn6nFiKBKCs2IRgczNt8e5ZyxCvYgkH2KB9Q,7846
113
+ src/linters/file_placement/pattern_validator.py,sha256=P6qbgnVxxFlBrsPzxjXjVlVPpr4ppeuJMhhnCScE6fA,4266
114
+ src/linters/file_placement/rule_checker.py,sha256=HInWmyxxZfqmrBH-5RealvYIV5ChzGd4EwxB6RNZE9w,7930
99
115
  src/linters/file_placement/violation_factory.py,sha256=NkQmBcgpa3g3W2ZdFZNQ5djLVP4x9OKs65d7F1rCKvM,6040
100
116
  src/linters/lazy_ignores/__init__.py,sha256=qPwCC1Y-TPn6tNLTO4X6QsACaAiPMBpsIKlKh_dSz5k,1656
101
- src/linters/lazy_ignores/config.py,sha256=ZM1W9L_pJtirmafGuwiKJyD29dAQIoiNOEMMXnyMATA,2464
117
+ src/linters/lazy_ignores/config.py,sha256=IBW9hO5QgVyIhuSJw6KrupCjbVilDiNUUHL-TcQMOXU,2591
102
118
  src/linters/lazy_ignores/directive_utils.py,sha256=6Mc56hrcFe21LG7PMmIVEPDGKUF6F9dffhHkwixj1R8,3432
103
119
  src/linters/lazy_ignores/header_parser.py,sha256=ADtVJUoJfoVx_zehoPpo6YLws0N-7c2PLK1PhNqn5Uk,6030
104
120
  src/linters/lazy_ignores/linter.py,sha256=1bl3b2NliVurlb9bUcZUzd8sBZG6KJhRODD-Mb46HnE,6052
105
121
  src/linters/lazy_ignores/matcher.py,sha256=jLE12aPPEz6tBejFrmXGXdCb7kzeFh8IYH5b_UjHqpQ,5201
106
- src/linters/lazy_ignores/python_analyzer.py,sha256=JlblemVtYPPdcl9l654WSHIjy0HusXfA5TidzAm5hdc,7211
122
+ src/linters/lazy_ignores/python_analyzer.py,sha256=ia60Y0mw0FxngXWw55JqZggWL8HJD6M3mN93PoqyOGA,7367
107
123
  src/linters/lazy_ignores/rule_id_utils.py,sha256=sE7kAQFO6zGAR5JQN2OLLVjAdiVNPZONTb0sY01ri9w,5809
108
124
  src/linters/lazy_ignores/skip_detector.py,sha256=9RK5uD4b2pAfdJsK1dHRTAWG4kKfAf1yfjc1OBsI14M,10461
109
- src/linters/lazy_ignores/types.py,sha256=8i6mMXRXQkC8M8pH3uH63G-0x0PYWQvz0Ikb0F6cSx4,2204
125
+ src/linters/lazy_ignores/types.py,sha256=ygcRYjiuCAx4qDZoBG6xSWNbd1Fh7_FPwoyH8PvLFvc,2251
110
126
  src/linters/lazy_ignores/typescript_analyzer.py,sha256=k8R60Mcw9OxvHFFUPhUErrb-tbek7Q7PXXZDq_H0ioM,5322
111
127
  src/linters/lazy_ignores/violation_builder.py,sha256=Z5RlCRJKkTfetkRVqsu1rfJRgBeiE9RTEu1djr-nmto,4203
112
- src/linters/lbyl/__init__.py,sha256=rV7NcrP32ku1jp9kABWuvrstJNk4vfoH2IjWEwuwfos,1069
128
+ src/linters/lbyl/__init__.py,sha256=5_an3Zy9iQvbajvuQT_DTMtlIMfOWxwewZGFe-cSDg8,1124
113
129
  src/linters/lbyl/config.py,sha256=kWCjBRs1HEVf9oK4dHKHfjQX8KU-o5i-Jc_94ULDD4Y,2434
114
- src/linters/lbyl/pattern_detectors/__init__.py,sha256=HXxpEmEiVD2K-IhDZ2qbR1mGfaxzOUj9Y3NFGSqcn_w,763
115
- src/linters/lbyl/pattern_detectors/base.py,sha256=4LZYORaYFK0aj4xs4035hegj7cXJtaao0M95kM1QYTk,1272
130
+ src/linters/lbyl/linter.py,sha256=sp6PETOKuk13wzfWYPYmWVqcdCwcFRr6CGvFmNPunaU,2288
131
+ src/linters/lbyl/pattern_detectors/__init__.py,sha256=23wnoXvQs9IFpk1RqjQhqpdtUhEE-MjiIlKrrhrtfxs,2064
132
+ src/linters/lbyl/pattern_detectors/base.py,sha256=O1R2k9ZLECLou7IN-FsNHC530CFxqQ6knhxhhQwpp6w,1881
133
+ src/linters/lbyl/pattern_detectors/dict_key_detector.py,sha256=xUPlHP1ZP8eQhUG8SUd5_Ke_J-h-XuB8_0ZPe9XGhH4,3982
134
+ src/linters/lbyl/pattern_detectors/division_check_detector.py,sha256=FG8PTtF0Ukg6cf7AGhqsSzuyDw90y7R2F63w0BAwwbA,7936
135
+ src/linters/lbyl/pattern_detectors/file_exists_detector.py,sha256=ZXK0rr60QgUNXm96mZmIuoMzHzpZ1USGiR4E_snoWUs,8410
136
+ src/linters/lbyl/pattern_detectors/hasattr_detector.py,sha256=mE0-mgMxArQ2RdQsI8u2dlRUm912k-pufd-6cAEbdfo,4376
137
+ src/linters/lbyl/pattern_detectors/isinstance_detector.py,sha256=3Zv_jPqB29vlrnthAOyEd6fN3f2P-cYtASLHt0PIKp4,4600
138
+ src/linters/lbyl/pattern_detectors/len_check_detector.py,sha256=A2Hlz7tAPmrcccJlXHEVKnEUGwSt9poBwcksRljQshM,6407
139
+ src/linters/lbyl/pattern_detectors/none_check_detector.py,sha256=mrLIK6sZ1QwM734qr5MtsyU-yaXr8zBNOjasXQ5PsEs,5428
140
+ src/linters/lbyl/pattern_detectors/string_validator_detector.py,sha256=GXXcsCfmp0yU7cgmg2LpYl8JRXpeRjZhYdq5A-lTPZY,5068
141
+ src/linters/lbyl/python_analyzer.py,sha256=auPrWFUEmFxtv1GB3exJzz7uX71gXqEUCHKO64UDR8w,8041
142
+ src/linters/lbyl/violation_builder.py,sha256=6wVX9U7Jq1ONWcGuasvIwJE9mXHcT778p0OcPC0Wx7w,10296
116
143
  src/linters/magic_numbers/__init__.py,sha256=17dkCUf0uiYLvpOZF01VDojj92NzxXZMtRhrSBUzsdc,1689
117
144
  src/linters/magic_numbers/config.py,sha256=3zV6ZNezouBWUYy4kMw5PUlPNvIWXVwOxTz1moZfRoI,3270
118
145
  src/linters/magic_numbers/context_analyzer.py,sha256=EgDyxxjvEqyD3FX0Fnxj5RcOPyvyVs_rYFxj2HOxYdg,7309
@@ -166,7 +193,7 @@ src/linters/stringly_typed/__init__.py,sha256=6r4IIykZ6mm551KQpRTSDp418EFqJQbuzj
166
193
  src/linters/stringly_typed/config.py,sha256=-M7fwwr9axQsQcGtowVINC9Bh1cS1b2-KPxFb2GtL3M,7500
167
194
  src/linters/stringly_typed/context_filter.py,sha256=JohTFvXiHKfVzUowRbsDrY37QngJDmhFfoxyoTzKriY,11422
168
195
  src/linters/stringly_typed/function_call_violation_builder.py,sha256=RiuzeKmUzb6Fzdc4j8lXl4V-jf-0xae-5t7YcIaKTMY,4234
169
- src/linters/stringly_typed/ignore_checker.py,sha256=sFV9NzsIhUWZe59h2X9JJv8yE3PWQLWAbhOG7Sl1Cs8,3438
196
+ src/linters/stringly_typed/ignore_checker.py,sha256=QU1x3S6RVujmnIWMTKWD4bwSfrmPLLRBp36T2KD_o1g,3382
170
197
  src/linters/stringly_typed/ignore_utils.py,sha256=hw0wfnGFJQkysr1qi_vmykZPr02SNBElwVHFu55tB6M,1531
171
198
  src/linters/stringly_typed/linter.py,sha256=mKokag3XCQl4QuhT25sekuiX2bMERDUdq9SNxqBpNCw,13440
172
199
  src/linters/stringly_typed/python/__init__.py,sha256=y1ELj3We0_VeA0ygXd1DxudSWrZE5OhLGtZNkKwuomA,1359
@@ -188,12 +215,12 @@ src/linters/stringly_typed/typescript/comparison_tracker.py,sha256=TiEldIqppu6i2
188
215
  src/linters/stringly_typed/violation_generator.py,sha256=aye60bShNnt8f6BPQwduTOLX97jAuy7Z7DLq9wzrFB4,14769
189
216
  src/orchestrator/__init__.py,sha256=XXLDJq2oaB-TpP2Y97GRnde9EkITGuFCmuLrDfxI9nY,245
190
217
  src/orchestrator/core.py,sha256=rt3h-YFgF1aAFeKvTa0PP7k_8zfwpeGIqrIxKuyckxY,17683
191
- src/orchestrator/language_detector.py,sha256=rHyVMApit80NTTNyDH1ObD1usKD8LjGmH3DwqNAWYGc,2736
218
+ src/orchestrator/language_detector.py,sha256=ALt2BEZKXQM2dWr1ChF9lZVj83YF4Bl9xwrB9ezfmMc,2799
192
219
  src/templates/thailint_config_template.yaml,sha256=57ZtLxnIoOHtR5Ejq3clb4nhY9J4n6h36XFb79ZZPlc,12020
193
220
  src/utils/__init__.py,sha256=NiBtKeQ09Y3kuUzeN4O1JNfUIYPQDS2AP1l5ODq-Dec,125
194
221
  src/utils/project_root.py,sha256=aaxUM-LQ1okrPClmZWPFd_D09W3V1ArgJiidEEp_eU8,6262
195
- thailint-0.14.0.dist-info/METADATA,sha256=KPPgrXKUO3T8G03L2L-KgKTzztoB7VORKZuYx0DkYCo,7009
196
- thailint-0.14.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
197
- thailint-0.14.0.dist-info/entry_points.txt,sha256=DNoGUlxpaMFqxQDgHp1yeGqohOjdFR-kH19uHYi3OUY,72
198
- thailint-0.14.0.dist-info/licenses/LICENSE,sha256=kxh1J0Sb62XvhNJ6MZsVNe8PqNVJ7LHRn_EWa-T3djw,1070
199
- thailint-0.14.0.dist-info/RECORD,,
222
+ thailint-0.15.1.dist-info/METADATA,sha256=w_Yxiwk8_exglxZu_OPpJd50uNkq8AbXo8ntyTy9ql0,7205
223
+ thailint-0.15.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
224
+ thailint-0.15.1.dist-info/entry_points.txt,sha256=DNoGUlxpaMFqxQDgHp1yeGqohOjdFR-kH19uHYi3OUY,72
225
+ thailint-0.15.1.dist-info/licenses/LICENSE,sha256=kxh1J0Sb62XvhNJ6MZsVNe8PqNVJ7LHRn_EWa-T3djw,1070
226
+ thailint-0.15.1.dist-info/RECORD,,