thailint 0.15.0__py3-none-any.whl → 0.15.2__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 (58) 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/srp/heuristics.py +47 -14
  51. src/linters/srp/typescript_metrics_calculator.py +34 -10
  52. src/linters/stringly_typed/ignore_checker.py +4 -6
  53. src/orchestrator/language_detector.py +5 -3
  54. {thailint-0.15.0.dist-info → thailint-0.15.2.dist-info}/METADATA +6 -4
  55. {thailint-0.15.0.dist-info → thailint-0.15.2.dist-info}/RECORD +58 -31
  56. {thailint-0.15.0.dist-info → thailint-0.15.2.dist-info}/WHEEL +0 -0
  57. {thailint-0.15.0.dist-info → thailint-0.15.2.dist-info}/entry_points.txt +0 -0
  58. {thailint-0.15.0.dist-info → thailint-0.15.2.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
+ )
@@ -4,12 +4,13 @@ Purpose: SRP detection heuristics for analyzing code complexity and responsibili
4
4
  Scope: Helper functions for method counting, LOC calculation, and keyword detection
5
5
 
6
6
  Overview: Provides heuristic-based analysis functions for detecting Single Responsibility
7
- Principle violations. Implements method counting that excludes property decorators and
8
- special methods. Provides LOC calculation that filters out blank lines and comments.
9
- Includes keyword detection for identifying generic class names that often indicate SRP
10
- violations (Manager, Handler, etc.). Supports both Python AST and TypeScript tree-sitter
11
- nodes. These heuristics enable practical SRP detection without requiring perfect semantic
12
- analysis, focusing on measurable code metrics that correlate with responsibility scope.
7
+ Principle violations. Implements method counting that excludes property decorators,
8
+ private methods, and special methods. Provides LOC calculation that filters out blank
9
+ lines and comments. Includes keyword detection for identifying generic class names that
10
+ often indicate SRP violations (Manager, Handler, etc.). Supports both Python AST and
11
+ TypeScript tree-sitter nodes. These heuristics enable practical SRP detection without
12
+ requiring perfect semantic analysis, focusing on measurable code metrics that correlate
13
+ with responsibility scope.
13
14
 
14
15
  Dependencies: ast module for Python AST analysis, typing for type hints
15
16
 
@@ -24,23 +25,55 @@ import ast
24
25
 
25
26
 
26
27
  def count_methods(class_node: ast.ClassDef) -> int:
27
- """Count methods in a class (excludes properties and special methods).
28
+ """Count public methods in a class (excludes properties and private methods).
29
+
30
+ Private methods are those starting with underscore (_), including dunder
31
+ methods (__init__, __str__, etc.). This focuses SRP analysis on the public
32
+ interface rather than implementation details.
28
33
 
29
34
  Args:
30
35
  class_node: AST node representing a class definition
31
36
 
32
37
  Returns:
33
- Number of methods in the class
38
+ Number of public methods in the class
34
39
  """
35
- methods = 0
36
40
  func_nodes = (
37
41
  n for n in class_node.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))
38
42
  )
39
- for node in func_nodes:
40
- # Don't count @property decorators as methods
41
- if not has_property_decorator(node):
42
- methods += 1
43
- return methods
43
+ public_methods = [n for n in func_nodes if _is_countable_method(n)]
44
+ return len(public_methods)
45
+
46
+
47
+ def _is_countable_method(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
48
+ """Check if a method should be counted (public and not a property).
49
+
50
+ Args:
51
+ node: Function AST node
52
+
53
+ Returns:
54
+ True if method should be counted
55
+ """
56
+ if has_property_decorator(node):
57
+ return False
58
+ if _is_private_method(node.name):
59
+ return False
60
+ return True
61
+
62
+
63
+ def _is_private_method(method_name: str) -> bool:
64
+ """Check if method is private (starts with underscore).
65
+
66
+ This includes both single underscore (_helper) and dunder methods
67
+ (__init__, __str__). All underscore-prefixed methods are considered
68
+ implementation details.
69
+
70
+ Args:
71
+ method_name: Name of the method to check
72
+
73
+ Returns:
74
+ True if method is private, False otherwise
75
+ """
76
+ return method_name.startswith("_")
44
77
 
45
78
 
46
79
  def count_loc(class_node: ast.ClassDef, source: str) -> int:
@@ -4,9 +4,10 @@ Purpose: TypeScript class metrics calculation for SRP analysis
4
4
  Scope: Calculates method count and lines of code for TypeScript classes
5
5
 
6
6
  Overview: Provides metrics calculation functionality for TypeScript classes in SRP analysis. Counts
7
- public methods in class bodies (excludes constructors), calculates lines of code from AST node
8
- positions, and identifies class body nodes. Uses tree-sitter AST node types. Isolates metrics
9
- calculation from class analysis and tree traversal logic.
7
+ public methods in class bodies (excludes constructors and private methods starting with _),
8
+ calculates lines of code from AST node positions, and identifies class body nodes. Uses
9
+ tree-sitter AST node types. Isolates metrics calculation from class analysis and tree
10
+ traversal logic.
10
11
 
11
12
  Dependencies: typing
12
13
 
@@ -72,22 +73,45 @@ def _get_class_body(class_node: Any) -> Any:
72
73
 
73
74
 
74
75
  def _is_countable_method(node: Any) -> bool:
75
- """Check if node is a method that should be counted.
76
+ """Check if node is a public method that should be counted.
77
+
78
+ Excludes constructors and private methods (names starting with _).
76
79
 
77
80
  Args:
78
81
  node: Tree-sitter node to check
79
82
 
80
83
  Returns:
81
- True if node is a countable method
84
+ True if node is a countable public method
82
85
  """
83
86
  if node.type != "method_definition":
84
87
  return False
85
88
 
86
- # Check if it's a constructor
87
- return all(
88
- not (child.type == "property_identifier" and child.text.decode() == "constructor")
89
- for child in node.children
90
- )
89
+ method_name = _get_method_name(node)
90
+
91
+ # Don't count constructors
92
+ if method_name == "constructor":
93
+ return False
94
+
95
+ # Don't count private methods (underscore prefix convention)
96
+ if method_name and method_name.startswith("_"):
97
+ return False
98
+
99
+ return True
100
+
101
+
102
+ def _get_method_name(node: Any) -> str | None:
103
+ """Extract method name from method_definition node.
104
+
105
+ Args:
106
+ node: Method definition tree-sitter node
107
+
108
+ Returns:
109
+ Method name or None if not found
110
+ """
111
+ for child in node.children:
112
+ if child.type == "property_identifier":
113
+ return child.text.decode()
114
+ return None
91
115
 
92
116
 
93
117
  # Legacy class wrapper for backward compatibility
@@ -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.15.0
3
+ Version: 0.15.2
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
@@ -37,7 +38,7 @@ Description-Content-Type: text/markdown
37
38
 
38
39
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
40
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
40
- [![PyPI](https://img.shields.io/pypi/v/thai-lint)](https://pypi.org/project/thai-lint/)
41
+ [![PyPI](https://img.shields.io/pypi/v/thailint)](https://pypi.org/project/thailint/)
41
42
  [![Documentation](https://readthedocs.org/projects/thai-lint/badge/?version=latest)](https://thai-lint.readthedocs.io/)
42
43
 
43
44
  **The AI Linter** - Catch the mistakes AI coding assistants keep making.
@@ -47,7 +48,7 @@ thailint detects anti-patterns that AI tools frequently introduce: duplicate cod
47
48
  ## Installation
48
49
 
49
50
  ```bash
50
- pip install thai-lint
51
+ pip install thailint
51
52
  ```
52
53
 
53
54
  Or with Docker:
@@ -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