thailint 0.2.0__py3-none-any.whl → 0.15.3__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 (214) hide show
  1. src/__init__.py +1 -0
  2. src/analyzers/__init__.py +4 -3
  3. src/analyzers/ast_utils.py +54 -0
  4. src/analyzers/rust_base.py +155 -0
  5. src/analyzers/rust_context.py +141 -0
  6. src/analyzers/typescript_base.py +4 -0
  7. src/cli/__init__.py +30 -0
  8. src/cli/__main__.py +22 -0
  9. src/cli/config.py +480 -0
  10. src/cli/config_merge.py +241 -0
  11. src/cli/linters/__init__.py +67 -0
  12. src/cli/linters/code_patterns.py +270 -0
  13. src/cli/linters/code_smells.py +342 -0
  14. src/cli/linters/documentation.py +83 -0
  15. src/cli/linters/performance.py +287 -0
  16. src/cli/linters/shared.py +331 -0
  17. src/cli/linters/structure.py +327 -0
  18. src/cli/linters/structure_quality.py +328 -0
  19. src/cli/main.py +120 -0
  20. src/cli/utils.py +395 -0
  21. src/cli_main.py +37 -0
  22. src/config.py +44 -27
  23. src/core/base.py +95 -5
  24. src/core/cli_utils.py +19 -2
  25. src/core/config_parser.py +36 -6
  26. src/core/constants.py +54 -0
  27. src/core/linter_utils.py +95 -6
  28. src/core/python_lint_rule.py +101 -0
  29. src/core/registry.py +1 -1
  30. src/core/rule_discovery.py +147 -84
  31. src/core/types.py +13 -0
  32. src/core/violation_builder.py +78 -15
  33. src/core/violation_utils.py +69 -0
  34. src/formatters/__init__.py +22 -0
  35. src/formatters/sarif.py +202 -0
  36. src/linter_config/directive_markers.py +109 -0
  37. src/linter_config/ignore.py +254 -395
  38. src/linter_config/loader.py +45 -12
  39. src/linter_config/pattern_utils.py +65 -0
  40. src/linter_config/rule_matcher.py +89 -0
  41. src/linters/collection_pipeline/__init__.py +90 -0
  42. src/linters/collection_pipeline/any_all_analyzer.py +281 -0
  43. src/linters/collection_pipeline/ast_utils.py +40 -0
  44. src/linters/collection_pipeline/config.py +75 -0
  45. src/linters/collection_pipeline/continue_analyzer.py +94 -0
  46. src/linters/collection_pipeline/detector.py +360 -0
  47. src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
  48. src/linters/collection_pipeline/linter.py +420 -0
  49. src/linters/collection_pipeline/suggestion_builder.py +130 -0
  50. src/linters/cqs/__init__.py +54 -0
  51. src/linters/cqs/config.py +55 -0
  52. src/linters/cqs/function_analyzer.py +201 -0
  53. src/linters/cqs/input_detector.py +139 -0
  54. src/linters/cqs/linter.py +159 -0
  55. src/linters/cqs/output_detector.py +84 -0
  56. src/linters/cqs/python_analyzer.py +54 -0
  57. src/linters/cqs/types.py +82 -0
  58. src/linters/cqs/typescript_cqs_analyzer.py +61 -0
  59. src/linters/cqs/typescript_function_analyzer.py +192 -0
  60. src/linters/cqs/typescript_input_detector.py +203 -0
  61. src/linters/cqs/typescript_output_detector.py +117 -0
  62. src/linters/cqs/violation_builder.py +94 -0
  63. src/linters/dry/base_token_analyzer.py +16 -9
  64. src/linters/dry/block_filter.py +125 -22
  65. src/linters/dry/block_grouper.py +4 -0
  66. src/linters/dry/cache.py +142 -94
  67. src/linters/dry/cache_query.py +4 -0
  68. src/linters/dry/config.py +68 -21
  69. src/linters/dry/constant.py +92 -0
  70. src/linters/dry/constant_matcher.py +223 -0
  71. src/linters/dry/constant_violation_builder.py +98 -0
  72. src/linters/dry/duplicate_storage.py +20 -82
  73. src/linters/dry/file_analyzer.py +15 -50
  74. src/linters/dry/inline_ignore.py +7 -16
  75. src/linters/dry/linter.py +182 -54
  76. src/linters/dry/python_analyzer.py +108 -336
  77. src/linters/dry/python_constant_extractor.py +100 -0
  78. src/linters/dry/single_statement_detector.py +417 -0
  79. src/linters/dry/storage_initializer.py +9 -18
  80. src/linters/dry/token_hasher.py +129 -71
  81. src/linters/dry/typescript_analyzer.py +68 -380
  82. src/linters/dry/typescript_constant_extractor.py +138 -0
  83. src/linters/dry/typescript_statement_detector.py +255 -0
  84. src/linters/dry/typescript_value_extractor.py +70 -0
  85. src/linters/dry/violation_builder.py +4 -0
  86. src/linters/dry/violation_filter.py +9 -5
  87. src/linters/dry/violation_generator.py +71 -14
  88. src/linters/file_header/__init__.py +24 -0
  89. src/linters/file_header/atemporal_detector.py +105 -0
  90. src/linters/file_header/base_parser.py +93 -0
  91. src/linters/file_header/bash_parser.py +66 -0
  92. src/linters/file_header/config.py +140 -0
  93. src/linters/file_header/css_parser.py +70 -0
  94. src/linters/file_header/field_validator.py +72 -0
  95. src/linters/file_header/linter.py +309 -0
  96. src/linters/file_header/markdown_parser.py +130 -0
  97. src/linters/file_header/python_parser.py +42 -0
  98. src/linters/file_header/typescript_parser.py +73 -0
  99. src/linters/file_header/violation_builder.py +79 -0
  100. src/linters/file_placement/config_loader.py +3 -1
  101. src/linters/file_placement/directory_matcher.py +4 -0
  102. src/linters/file_placement/linter.py +74 -31
  103. src/linters/file_placement/pattern_matcher.py +41 -6
  104. src/linters/file_placement/pattern_validator.py +31 -12
  105. src/linters/file_placement/rule_checker.py +12 -7
  106. src/linters/lazy_ignores/__init__.py +43 -0
  107. src/linters/lazy_ignores/config.py +74 -0
  108. src/linters/lazy_ignores/directive_utils.py +164 -0
  109. src/linters/lazy_ignores/header_parser.py +177 -0
  110. src/linters/lazy_ignores/linter.py +158 -0
  111. src/linters/lazy_ignores/matcher.py +168 -0
  112. src/linters/lazy_ignores/python_analyzer.py +209 -0
  113. src/linters/lazy_ignores/rule_id_utils.py +180 -0
  114. src/linters/lazy_ignores/skip_detector.py +298 -0
  115. src/linters/lazy_ignores/types.py +71 -0
  116. src/linters/lazy_ignores/typescript_analyzer.py +146 -0
  117. src/linters/lazy_ignores/violation_builder.py +135 -0
  118. src/linters/lbyl/__init__.py +31 -0
  119. src/linters/lbyl/config.py +63 -0
  120. src/linters/lbyl/linter.py +67 -0
  121. src/linters/lbyl/pattern_detectors/__init__.py +53 -0
  122. src/linters/lbyl/pattern_detectors/base.py +63 -0
  123. src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
  124. src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
  125. src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
  126. src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
  127. src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
  128. src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
  129. src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
  130. src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
  131. src/linters/lbyl/python_analyzer.py +215 -0
  132. src/linters/lbyl/violation_builder.py +354 -0
  133. src/linters/magic_numbers/__init__.py +48 -0
  134. src/linters/magic_numbers/config.py +82 -0
  135. src/linters/magic_numbers/context_analyzer.py +249 -0
  136. src/linters/magic_numbers/linter.py +462 -0
  137. src/linters/magic_numbers/python_analyzer.py +64 -0
  138. src/linters/magic_numbers/typescript_analyzer.py +215 -0
  139. src/linters/magic_numbers/typescript_ignore_checker.py +81 -0
  140. src/linters/magic_numbers/violation_builder.py +98 -0
  141. src/linters/method_property/__init__.py +49 -0
  142. src/linters/method_property/config.py +138 -0
  143. src/linters/method_property/linter.py +414 -0
  144. src/linters/method_property/python_analyzer.py +473 -0
  145. src/linters/method_property/violation_builder.py +119 -0
  146. src/linters/nesting/__init__.py +6 -2
  147. src/linters/nesting/config.py +6 -3
  148. src/linters/nesting/linter.py +31 -34
  149. src/linters/nesting/python_analyzer.py +4 -0
  150. src/linters/nesting/typescript_analyzer.py +6 -11
  151. src/linters/nesting/violation_builder.py +1 -0
  152. src/linters/performance/__init__.py +91 -0
  153. src/linters/performance/config.py +43 -0
  154. src/linters/performance/constants.py +49 -0
  155. src/linters/performance/linter.py +149 -0
  156. src/linters/performance/python_analyzer.py +365 -0
  157. src/linters/performance/regex_analyzer.py +312 -0
  158. src/linters/performance/regex_linter.py +139 -0
  159. src/linters/performance/typescript_analyzer.py +236 -0
  160. src/linters/performance/violation_builder.py +160 -0
  161. src/linters/print_statements/__init__.py +53 -0
  162. src/linters/print_statements/config.py +78 -0
  163. src/linters/print_statements/linter.py +413 -0
  164. src/linters/print_statements/python_analyzer.py +153 -0
  165. src/linters/print_statements/typescript_analyzer.py +125 -0
  166. src/linters/print_statements/violation_builder.py +96 -0
  167. src/linters/srp/__init__.py +3 -3
  168. src/linters/srp/class_analyzer.py +11 -7
  169. src/linters/srp/config.py +12 -6
  170. src/linters/srp/heuristics.py +56 -22
  171. src/linters/srp/linter.py +47 -39
  172. src/linters/srp/python_analyzer.py +55 -20
  173. src/linters/srp/typescript_metrics_calculator.py +110 -50
  174. src/linters/stateless_class/__init__.py +25 -0
  175. src/linters/stateless_class/config.py +58 -0
  176. src/linters/stateless_class/linter.py +349 -0
  177. src/linters/stateless_class/python_analyzer.py +290 -0
  178. src/linters/stringly_typed/__init__.py +36 -0
  179. src/linters/stringly_typed/config.py +189 -0
  180. src/linters/stringly_typed/context_filter.py +451 -0
  181. src/linters/stringly_typed/function_call_violation_builder.py +135 -0
  182. src/linters/stringly_typed/ignore_checker.py +100 -0
  183. src/linters/stringly_typed/ignore_utils.py +51 -0
  184. src/linters/stringly_typed/linter.py +376 -0
  185. src/linters/stringly_typed/python/__init__.py +33 -0
  186. src/linters/stringly_typed/python/analyzer.py +348 -0
  187. src/linters/stringly_typed/python/call_tracker.py +175 -0
  188. src/linters/stringly_typed/python/comparison_tracker.py +257 -0
  189. src/linters/stringly_typed/python/condition_extractor.py +134 -0
  190. src/linters/stringly_typed/python/conditional_detector.py +179 -0
  191. src/linters/stringly_typed/python/constants.py +21 -0
  192. src/linters/stringly_typed/python/match_analyzer.py +94 -0
  193. src/linters/stringly_typed/python/validation_detector.py +189 -0
  194. src/linters/stringly_typed/python/variable_extractor.py +96 -0
  195. src/linters/stringly_typed/storage.py +620 -0
  196. src/linters/stringly_typed/storage_initializer.py +45 -0
  197. src/linters/stringly_typed/typescript/__init__.py +28 -0
  198. src/linters/stringly_typed/typescript/analyzer.py +157 -0
  199. src/linters/stringly_typed/typescript/call_tracker.py +335 -0
  200. src/linters/stringly_typed/typescript/comparison_tracker.py +378 -0
  201. src/linters/stringly_typed/violation_generator.py +419 -0
  202. src/orchestrator/core.py +264 -16
  203. src/orchestrator/language_detector.py +5 -3
  204. src/templates/thailint_config_template.yaml +354 -0
  205. src/utils/project_root.py +138 -16
  206. thailint-0.15.3.dist-info/METADATA +187 -0
  207. thailint-0.15.3.dist-info/RECORD +226 -0
  208. {thailint-0.2.0.dist-info → thailint-0.15.3.dist-info}/WHEEL +1 -1
  209. thailint-0.15.3.dist-info/entry_points.txt +4 -0
  210. src/cli.py +0 -1055
  211. thailint-0.2.0.dist-info/METADATA +0 -980
  212. thailint-0.2.0.dist-info/RECORD +0 -75
  213. thailint-0.2.0.dist-info/entry_points.txt +0 -4
  214. {thailint-0.2.0.dist-info → thailint-0.15.3.dist-info/licenses}/LICENSE +0 -0
@@ -1,980 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: thailint
3
- Version: 0.2.0
4
- Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
5
- License: MIT
6
- Keywords: linter,ai,code-quality,static-analysis,file-placement,governance,multi-language,cli,docker,python
7
- Author: Steve Jackson
8
- Requires-Python: >=3.11,<4.0
9
- Classifier: Development Status :: 3 - Alpha
10
- Classifier: Environment :: Console
11
- Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Operating System :: OS Independent
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.11
16
- Classifier: Programming Language :: Python :: 3.12
17
- Classifier: Programming Language :: Python :: 3.13
18
- Classifier: Programming Language :: Python :: 3 :: Only
19
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
- Classifier: Topic :: Software Development :: Quality Assurance
21
- Classifier: Topic :: Software Development :: Testing
22
- Classifier: Topic :: Utilities
23
- Classifier: Typing :: Typed
24
- Requires-Dist: click (>=8.1.0,<9.0.0)
25
- Requires-Dist: pyprojroot (>=0.3.0,<0.4.0)
26
- Requires-Dist: pyyaml (>=6.0,<7.0)
27
- Requires-Dist: tree-sitter (>=0.25.2,<0.26.0)
28
- Requires-Dist: tree-sitter-typescript (>=0.23.2,<0.24.0)
29
- Project-URL: Documentation, https://github.com/be-wise-be-kind/thai-lint#readme
30
- Project-URL: Homepage, https://github.com/be-wise-be-kind/thai-lint
31
- Project-URL: Repository, https://github.com/be-wise-be-kind/thai-lint
32
- Description-Content-Type: text/markdown
33
-
34
- # thai-lint
35
-
36
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
37
- [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
38
- [![Tests](https://img.shields.io/badge/tests-317%2F317%20passing-brightgreen.svg)](tests/)
39
- [![Coverage](https://img.shields.io/badge/coverage-90%25-brightgreen.svg)](htmlcov/)
40
-
41
- The AI Linter - Enterprise-ready linting and governance for AI-generated code across multiple languages.
42
-
43
- ## Overview
44
-
45
- thailint is a modern, enterprise-ready multi-language linter designed specifically for AI-generated code. It enforces project structure, file placement rules, and coding standards across Python, TypeScript, and other languages.
46
-
47
- ## Features
48
-
49
- ### Core Capabilities
50
- - **File Placement Linting** - Enforce project structure and organization
51
- - **Nesting Depth Linting** - Detect excessive code nesting with AST analysis
52
- - Python and TypeScript support with tree-sitter
53
- - Configurable max depth (default: 4, recommended: 3)
54
- - Helpful refactoring suggestions (guard clauses, extract method)
55
- - **SRP Linting** - Detect Single Responsibility Principle violations
56
- - Heuristic-based analysis (method count, LOC, keywords)
57
- - Language-specific thresholds (Python, TypeScript, JavaScript)
58
- - Refactoring patterns from real-world examples
59
- - **DRY Linting** - Detect duplicate code across projects
60
- - Token-based hash detection with SQLite caching
61
- - Fast incremental scans (10-50x speedup with cache)
62
- - Configurable thresholds (lines, tokens, occurrences)
63
- - Language-specific detection (Python, TypeScript, JavaScript)
64
- - False positive filtering (keyword args, imports)
65
- - **Pluggable Architecture** - Easy to extend with custom linters
66
- - **Multi-Language Support** - Python, TypeScript, JavaScript, and more
67
- - **Flexible Configuration** - YAML/JSON configs with pattern matching
68
- - **5-Level Ignore System** - Repo, directory, file, method, and line-level ignores
69
-
70
- ### Deployment Modes
71
- - **CLI Mode** - Full-featured command-line interface
72
- - **Library API** - Python library for programmatic integration
73
- - **Docker Support** - Containerized deployment for CI/CD
74
-
75
- ### Enterprise Features
76
- - **Performance** - <100ms for single files, <5s for 1000 files
77
- - **Type Safety** - Full type hints and MyPy strict mode
78
- - **Test Coverage** - 90% coverage with 317 tests
79
- - **CI/CD Ready** - Proper exit codes and JSON output
80
- - **Comprehensive Docs** - Complete documentation and examples
81
-
82
- ## Installation
83
-
84
- ### From Source
85
-
86
- ```bash
87
- # Clone repository
88
- git clone https://github.com/be-wise-be-kind/thai-lint.git
89
- cd thai-lint
90
-
91
- # Install dependencies
92
- pip install -e ".[dev]"
93
- ```
94
-
95
- ### From PyPI (once published)
96
-
97
- ```bash
98
- pip install thai-lint
99
- ```
100
-
101
- ### With Docker
102
-
103
- ```bash
104
- # Pull from Docker Hub
105
- docker pull washad/thailint:latest
106
-
107
- # Run CLI
108
- docker run --rm washad/thailint:latest --help
109
- ```
110
-
111
- ## Quick Start
112
-
113
- ### CLI Mode
114
-
115
- ```bash
116
- # Check file placement
117
- thailint file-placement .
118
-
119
- # Check multiple files
120
- thailint nesting file1.py file2.py file3.py
121
-
122
- # Check specific directory
123
- thailint nesting src/
124
-
125
- # Check for duplicate code
126
- thailint dry .
127
-
128
- # With config file
129
- thailint dry --config .thailint.yaml src/
130
-
131
- # JSON output for CI/CD
132
- thailint dry --format json src/
133
- ```
134
-
135
- ### Library Mode
136
-
137
- ```python
138
- from src import Linter
139
-
140
- # Initialize linter
141
- linter = Linter(config_file='.thailint.yaml')
142
-
143
- # Lint directory
144
- violations = linter.lint('src/', rules=['file-placement'])
145
-
146
- # Process results
147
- if violations:
148
- for v in violations:
149
- print(f"{v.file_path}: {v.message}")
150
- ```
151
-
152
- ### Docker Mode
153
-
154
- ```bash
155
- # Lint directory (recommended - lints all files inside)
156
- docker run --rm -v $(pwd):/data \
157
- washad/thailint:latest file-placement /data
158
-
159
- # Lint single file
160
- docker run --rm -v $(pwd):/data \
161
- washad/thailint:latest file-placement /data/src/app.py
162
-
163
- # Lint multiple specific files
164
- docker run --rm -v $(pwd):/data \
165
- washad/thailint:latest nesting /data/src/file1.py /data/src/file2.py
166
-
167
- # Check nesting depth in subdirectory
168
- docker run --rm -v $(pwd):/data \
169
- washad/thailint:latest nesting /data/src
170
- ```
171
-
172
- ## Configuration
173
-
174
- Create `.thailint.yaml` in your project root:
175
-
176
- ```yaml
177
- # File placement linter configuration
178
- file-placement:
179
- enabled: true
180
-
181
- # Global patterns apply to entire project
182
- global_patterns:
183
- deny:
184
- - pattern: "^(?!src/|tests/).*\\.py$"
185
- message: "Python files must be in src/ or tests/"
186
-
187
- # Directory-specific rules
188
- directories:
189
- src:
190
- allow:
191
- - ".*\\.py$"
192
- deny:
193
- - "test_.*\\.py$"
194
-
195
- tests:
196
- allow:
197
- - "test_.*\\.py$"
198
- - "conftest\\.py$"
199
-
200
- # Files/directories to ignore
201
- ignore:
202
- - "__pycache__/"
203
- - "*.pyc"
204
- - ".venv/"
205
-
206
- # Nesting depth linter configuration
207
- nesting:
208
- enabled: true
209
- max_nesting_depth: 4 # Maximum allowed nesting depth
210
-
211
- # Language-specific settings (optional)
212
- languages:
213
- python:
214
- max_depth: 4
215
- typescript:
216
- max_depth: 4
217
- javascript:
218
- max_depth: 4
219
-
220
- # DRY linter configuration
221
- dry:
222
- enabled: true
223
- min_duplicate_lines: 4 # Minimum lines to consider duplicate
224
- min_duplicate_tokens: 30 # Minimum tokens to consider duplicate
225
- min_occurrences: 2 # Report if appears 2+ times
226
-
227
- # Language-specific thresholds
228
- python:
229
- min_occurrences: 3 # Python: require 3+ occurrences
230
-
231
- # Cache settings (SQLite)
232
- cache_enabled: true
233
- cache_path: ".thailint-cache/dry.db"
234
-
235
- # Ignore patterns
236
- ignore:
237
- - "tests/"
238
- - "__init__.py"
239
- ```
240
-
241
- **JSON format also supported** (`.thailint.json`):
242
-
243
- ```json
244
- {
245
- "file-placement": {
246
- "enabled": true,
247
- "directories": {
248
- "src": {
249
- "allow": [".*\\.py$"],
250
- "deny": ["test_.*\\.py$"]
251
- }
252
- },
253
- "ignore": ["__pycache__/", "*.pyc"]
254
- },
255
- "nesting": {
256
- "enabled": true,
257
- "max_nesting_depth": 4,
258
- "languages": {
259
- "python": { "max_depth": 4 },
260
- "typescript": { "max_depth": 4 }
261
- }
262
- },
263
- "dry": {
264
- "enabled": true,
265
- "min_duplicate_lines": 4,
266
- "min_duplicate_tokens": 30,
267
- "min_occurrences": 2,
268
- "python": {
269
- "min_occurrences": 3
270
- },
271
- "cache_enabled": true,
272
- "cache_path": ".thailint-cache/dry.db",
273
- "ignore": ["tests/", "__init__.py"]
274
- }
275
- }
276
- ```
277
-
278
- See [Configuration Guide](docs/configuration.md) for complete reference.
279
-
280
- ## Nesting Depth Linter
281
-
282
- ### Overview
283
-
284
- The nesting depth linter detects deeply nested code (if/for/while/try statements) that reduces readability and maintainability. It uses AST analysis to accurately calculate nesting depth.
285
-
286
- ### Quick Start
287
-
288
- ```bash
289
- # Check nesting depth in current directory
290
- thailint nesting .
291
-
292
- # Use strict limit (max depth 3)
293
- thailint nesting --max-depth 3 src/
294
-
295
- # Get JSON output
296
- thailint nesting --format json src/
297
- ```
298
-
299
- ### Configuration
300
-
301
- Add to `.thailint.yaml`:
302
-
303
- ```yaml
304
- nesting:
305
- enabled: true
306
- max_nesting_depth: 3 # Default: 4, recommended: 3
307
- ```
308
-
309
- ### Example Violation
310
-
311
- **Code with excessive nesting:**
312
- ```python
313
- def process_data(items):
314
- for item in items: # Depth 2
315
- if item.is_valid(): # Depth 3
316
- try: # Depth 4 ← VIOLATION (max=3)
317
- if item.process():
318
- return True
319
- except Exception:
320
- pass
321
- return False
322
- ```
323
-
324
- **Refactored with guard clauses:**
325
- ```python
326
- def process_data(items):
327
- for item in items: # Depth 2
328
- if not item.is_valid():
329
- continue
330
- try: # Depth 3 ✓
331
- if item.process():
332
- return True
333
- except Exception:
334
- pass
335
- return False
336
- ```
337
-
338
- ### Refactoring Patterns
339
-
340
- Common patterns to reduce nesting:
341
-
342
- 1. **Guard Clauses (Early Returns)**
343
- - Replace `if x: do_something()` with `if not x: return`
344
- - Exit early, reduce nesting
345
-
346
- 2. **Extract Method**
347
- - Move nested logic to separate functions
348
- - Improves readability and testability
349
-
350
- 3. **Dispatch Pattern**
351
- - Replace if-elif-else chains with dictionary dispatch
352
- - More extensible and cleaner
353
-
354
- 4. **Flatten Error Handling**
355
- - Combine multiple try-except blocks
356
- - Use tuple of exception types
357
-
358
- ### Language Support
359
-
360
- - **Python**: Full support (if/for/while/with/try/match)
361
- - **TypeScript**: Full support (if/for/while/try/switch)
362
- - **JavaScript**: Supported via TypeScript parser
363
-
364
- See [Nesting Linter Guide](docs/nesting-linter.md) for comprehensive documentation and refactoring patterns.
365
-
366
- ## Single Responsibility Principle (SRP) Linter
367
-
368
- ### Overview
369
-
370
- The SRP linter detects classes that violate the Single Responsibility Principle by having too many methods, too many lines of code, or generic naming patterns. It uses AST analysis with configurable heuristics to identify classes that likely handle multiple responsibilities.
371
-
372
- ### Quick Start
373
-
374
- ```bash
375
- # Check SRP violations in current directory
376
- thailint srp .
377
-
378
- # Use custom thresholds
379
- thailint srp --max-methods 10 --max-loc 300 src/
380
-
381
- # Get JSON output
382
- thailint srp --format json src/
383
- ```
384
-
385
- ### Configuration
386
-
387
- Add to `.thailint.yaml`:
388
-
389
- ```yaml
390
- srp:
391
- enabled: true
392
- max_methods: 7 # Maximum methods per class
393
- max_loc: 200 # Maximum lines of code per class
394
-
395
- # Language-specific thresholds
396
- python:
397
- max_methods: 8
398
- max_loc: 200
399
-
400
- typescript:
401
- max_methods: 10 # TypeScript more verbose
402
- max_loc: 250
403
- ```
404
-
405
- ### Detection Heuristics
406
-
407
- The SRP linter uses three heuristics to detect violations:
408
-
409
- 1. **Method Count**: Classes with >7 methods (default) likely have multiple responsibilities
410
- 2. **Lines of Code**: Classes with >200 LOC (default) are often doing too much
411
- 3. **Responsibility Keywords**: Names containing "Manager", "Handler", "Processor", etc.
412
-
413
- ### Example Violation
414
-
415
- **Code with SRP violation:**
416
- ```python
417
- class UserManager: # 8 methods, contains "Manager" keyword
418
- def create_user(self): pass
419
- def update_user(self): pass
420
- def delete_user(self): pass
421
- def send_email(self): pass # ← Different responsibility
422
- def log_action(self): pass # ← Different responsibility
423
- def validate_data(self): pass # ← Different responsibility
424
- def generate_report(self): pass # ← Different responsibility
425
- def export_data(self): pass # ← Violation at method 8
426
- ```
427
-
428
- **Refactored following SRP:**
429
- ```python
430
- class UserRepository: # 3 methods ✓
431
- def create(self, user): pass
432
- def update(self, user): pass
433
- def delete(self, user): pass
434
-
435
- class EmailService: # 1 method ✓
436
- def send(self, user, template): pass
437
-
438
- class UserAuditLog: # 1 method ✓
439
- def log(self, action, user): pass
440
-
441
- class UserValidator: # 1 method ✓
442
- def validate(self, data): pass
443
-
444
- class ReportGenerator: # 1 method ✓
445
- def generate(self, users): pass
446
- ```
447
-
448
- ### Refactoring Patterns
449
-
450
- Common patterns to fix SRP violations (discovered during dogfooding):
451
-
452
- 1. **Extract Class**
453
- - Split god classes into focused classes
454
- - Each class handles one responsibility
455
-
456
- 2. **Split Configuration and Logic**
457
- - Separate config loading from business logic
458
- - Create dedicated ConfigLoader classes
459
-
460
- 3. **Extract Language-Specific Logic**
461
- - Separate Python/TypeScript analysis
462
- - Use analyzer classes per language
463
-
464
- 4. **Utility Module Pattern**
465
- - Group related helper methods
466
- - Create focused utility classes
467
-
468
- ### Language Support
469
-
470
- - **Python**: Full support with method counting and LOC analysis
471
- - **TypeScript**: Full support with tree-sitter parsing
472
- - **JavaScript**: Supported via TypeScript parser
473
-
474
- ### Real-World Example
475
-
476
- **Large class refactoring:**
477
- - **Before**: FilePlacementLinter (33 methods, 382 LOC) - single class handling config, patterns, validation
478
- - **After**: Extract Class pattern applied - 5 focused classes (ConfigLoader, PatternValidator, RuleChecker, PathResolver, FilePlacementLinter)
479
- - **Result**: Each class ≤8 methods, ≤150 LOC, single responsibility
480
-
481
- See [SRP Linter Guide](docs/srp-linter.md) for comprehensive documentation and refactoring patterns.
482
-
483
- ## DRY Linter (Don't Repeat Yourself)
484
-
485
- ### Overview
486
-
487
- The DRY linter detects duplicate code blocks across your entire project using token-based hashing with SQLite caching. It identifies identical or near-identical code that violates the Don't Repeat Yourself (DRY) principle, helping maintain code quality at scale.
488
-
489
- ### Quick Start
490
-
491
- ```bash
492
- # Check for duplicate code in current directory
493
- thailint dry .
494
-
495
- # Use custom thresholds
496
- thailint dry --min-lines 5 src/
497
-
498
- # Clear cache and re-scan
499
- thailint dry --clear-cache src/
500
-
501
- # Get JSON output
502
- thailint dry --format json src/
503
- ```
504
-
505
- ### Configuration
506
-
507
- Add to `.thailint.yaml`:
508
-
509
- ```yaml
510
- dry:
511
- enabled: true
512
- min_duplicate_lines: 4 # Minimum lines to consider duplicate
513
- min_duplicate_tokens: 30 # Minimum tokens to consider duplicate
514
- min_occurrences: 2 # Report if appears 2+ times
515
-
516
- # Language-specific thresholds
517
- python:
518
- min_occurrences: 3 # Python: require 3+ occurrences
519
- typescript:
520
- min_occurrences: 3 # TypeScript: require 3+ occurrences
521
-
522
- # Cache settings (SQLite for fast incremental scans)
523
- cache_enabled: true
524
- cache_path: ".thailint-cache/dry.db"
525
- cache_max_age_days: 30
526
-
527
- # Ignore patterns
528
- ignore:
529
- - "tests/" # Test code often has acceptable duplication
530
- - "__init__.py" # Import-only files exempt
531
-
532
- # False positive filters
533
- filters:
534
- keyword_argument_filter: true # Filter function call kwargs
535
- import_group_filter: true # Filter import groups
536
- ```
537
-
538
- ### How It Works
539
-
540
- **Token-Based Detection:**
541
- 1. Parse code into tokens (stripping comments, normalizing whitespace)
542
- 2. Create rolling hash windows of N lines
543
- 3. Store hashes in SQLite database with file locations
544
- 4. Query for hashes appearing 2+ times across project
545
-
546
- **SQLite Caching:**
547
- - First scan: Hash all files (~1-3s for 1000 files)
548
- - Subsequent scans: Load cached hashes for unchanged files (~0.1-0.5s)
549
- - 10-50x speedup for incremental scans
550
- - Mtime-based cache invalidation (automatic and safe)
551
-
552
- ### Example Violation
553
-
554
- **Code with duplication:**
555
- ```python
556
- # src/auth.py
557
- def validate_user(user_data):
558
- if not user_data:
559
- return False
560
- if not user_data.get('email'):
561
- return False
562
- if not user_data.get('password'):
563
- return False
564
- return True
565
-
566
- # src/admin.py
567
- def validate_admin(admin_data):
568
- if not admin_data:
569
- return False
570
- if not admin_data.get('email'):
571
- return False
572
- if not admin_data.get('password'):
573
- return False
574
- return True
575
- ```
576
-
577
- **Violation message:**
578
- ```
579
- src/auth.py:3 - Duplicate code detected (4 lines, 2 occurrences)
580
- Locations:
581
- - src/auth.py:3-6
582
- - src/admin.py:3-6
583
- Consider extracting to shared function
584
- ```
585
-
586
- **Refactored (DRY):**
587
- ```python
588
- # src/validators.py
589
- def validate_credentials(data):
590
- if not data:
591
- return False
592
- if not data.get('email'):
593
- return False
594
- if not data.get('password'):
595
- return False
596
- return True
597
-
598
- # src/auth.py & src/admin.py
599
- from src.validators import validate_credentials
600
-
601
- def validate_user(user_data):
602
- return validate_credentials(user_data)
603
-
604
- def validate_admin(admin_data):
605
- return validate_credentials(admin_data)
606
- ```
607
-
608
- ### Cache Management
609
-
610
- ```bash
611
- # Normal cached run (default)
612
- thailint dry src/
613
-
614
- # Force re-analysis (ignore cache)
615
- thailint dry --no-cache src/
616
-
617
- # Clear cache before running
618
- thailint dry --clear-cache src/
619
-
620
- # Manual cache cleanup
621
- rm -rf .thailint-cache/dry.db
622
- ```
623
-
624
- ### Performance
625
-
626
- | Operation | Performance | Cache Status |
627
- |-----------|-------------|--------------|
628
- | First scan (1000 files) | 1-3s | Cache write |
629
- | Unchanged files | 0.1-0.5s | Cache hit |
630
- | 50 changed files | 0.5-1s | Partial cache |
631
-
632
- **Speedup**: 3-10x for incremental scans with cache
633
-
634
- ### Language Support
635
-
636
- - **Python**: Full support with AST-based tokenization
637
- - **TypeScript**: Full support with tree-sitter parsing
638
- - **JavaScript**: Supported via TypeScript parser
639
-
640
- ### False Positive Filtering
641
-
642
- Built-in filters automatically exclude common non-duplication patterns:
643
- - **keyword_argument_filter**: Excludes function calls with keyword arguments
644
- - **import_group_filter**: Excludes import statement groups
645
-
646
- ### Refactoring Patterns
647
-
648
- 1. **Extract Function**: Move repeated logic to shared function
649
- 2. **Extract Base Class**: Create base class for similar implementations
650
- 3. **Extract Utility Module**: Move helper functions to shared utilities
651
- 4. **Template Method**: Use function parameters for variations
652
-
653
- See [DRY Linter Guide](docs/dry-linter.md) for comprehensive documentation, cache management, and refactoring patterns.
654
-
655
- ## Pre-commit Hooks
656
-
657
- Automate code quality checks before every commit and push with pre-commit hooks.
658
-
659
- ### Quick Setup
660
-
661
- ```bash
662
- # 1. Install pre-commit framework
663
- pip install pre-commit
664
-
665
- # 2. Install git hooks
666
- pre-commit install
667
- pre-commit install --hook-type pre-push
668
-
669
- # 3. Test it works
670
- pre-commit run --all-files
671
- ```
672
-
673
- ### What You Get
674
-
675
- **On every commit:**
676
- - Prevents commits to main/master branch
677
- - Auto-fixes formatting issues
678
- - Runs thailint on changed files (fast, uses pass_filenames: true)
679
-
680
- **On every push:**
681
- - Full linting on entire codebase
682
- - Runs complete test suite
683
-
684
- ### Example Configuration
685
-
686
- ```yaml
687
- # .pre-commit-config.yaml
688
- repos:
689
- - repo: local
690
- hooks:
691
- # Prevent commits to protected branches
692
- - id: no-commit-to-main
693
- name: Prevent commits to main branch
694
- entry: bash -c 'branch=$(git rev-parse --abbrev-ref HEAD); if [ "$branch" = "main" ]; then echo "ERROR: Use a feature branch!"; exit 1; fi'
695
- language: system
696
- pass_filenames: false
697
- always_run: true
698
-
699
- # Auto-format code
700
- - id: format
701
- name: Auto-fix formatting
702
- entry: just format
703
- language: system
704
- pass_filenames: false
705
-
706
- # Run thailint on changed files (passes filenames directly)
707
- - id: thailint-changed
708
- name: Lint changed files
709
- entry: thailint nesting
710
- language: system
711
- files: \.(py|ts|tsx|js|jsx)$
712
- pass_filenames: true
713
- ```
714
-
715
- See **[Pre-commit Hooks Guide](docs/pre-commit-hooks.md)** for complete documentation, troubleshooting, and advanced configuration.
716
-
717
- ## Common Use Cases
718
-
719
- ### CI/CD Integration
720
-
721
- ```yaml
722
- # GitHub Actions example
723
- name: Lint
724
-
725
- on: [push, pull_request]
726
-
727
- jobs:
728
- lint:
729
- runs-on: ubuntu-latest
730
- steps:
731
- - uses: actions/checkout@v3
732
- - name: Install thailint
733
- run: pip install thailint
734
- - name: Run file placement linter
735
- run: thailint file-placement .
736
- - name: Run nesting linter
737
- run: thailint nesting src/ --config .thailint.yaml
738
- ```
739
-
740
- ### Editor Integration
741
-
742
- ```python
743
- # VS Code extension example
744
- from src import Linter
745
-
746
- linter = Linter(config_file='.thailint.yaml')
747
- violations = linter.lint(file_path)
748
- ```
749
-
750
- ### Test Suite
751
-
752
- ```python
753
- # pytest integration
754
- import pytest
755
- from src import Linter
756
-
757
- def test_no_violations():
758
- linter = Linter()
759
- violations = linter.lint('src/')
760
- assert len(violations) == 0
761
- ```
762
-
763
- ## Development
764
-
765
- ### Setup Development Environment
766
-
767
- ```bash
768
- # Install development dependencies
769
- pip install -e ".[dev]"
770
-
771
- # Install pre-commit hooks (if using)
772
- pre-commit install
773
- ```
774
-
775
- ### Running Tests
776
-
777
- ```bash
778
- # Run all tests
779
- pytest
780
-
781
- # Run with coverage
782
- pytest --cov=src --cov-report=html
783
-
784
- # Run specific test
785
- pytest tests/test_cli.py::test_hello_command
786
- ```
787
-
788
- ### Code Quality
789
-
790
- ```bash
791
- # Lint code
792
- ruff check src tests
793
-
794
- # Format code
795
- ruff format src tests
796
-
797
- # Type checking
798
- mypy src/
799
- ```
800
-
801
- ### Building
802
-
803
- ```bash
804
- # Build Python package
805
- poetry build
806
-
807
- # Build Docker image locally (optional)
808
- docker build -t washad/thailint:latest .
809
- ```
810
-
811
- ## Docker Usage
812
-
813
- ```bash
814
- # Pull published image
815
- docker pull washad/thailint:latest
816
-
817
- # Run CLI help
818
- docker run --rm washad/thailint:latest --help
819
-
820
- # Lint entire directory (recommended)
821
- docker run --rm -v $(pwd):/data washad/thailint:latest file-placement /data
822
-
823
- # Lint single file
824
- docker run --rm -v $(pwd):/data washad/thailint:latest file-placement /data/src/app.py
825
-
826
- # Lint multiple specific files
827
- docker run --rm -v $(pwd):/data washad/thailint:latest nesting /data/src/file1.py /data/src/file2.py
828
-
829
- # Lint specific subdirectory
830
- docker run --rm -v $(pwd):/data washad/thailint:latest nesting /data/src
831
-
832
- # With custom config
833
- docker run --rm -v $(pwd):/data \
834
- washad/thailint:latest nesting --config /data/.thailint.yaml /data
835
-
836
- # JSON output for CI/CD
837
- docker run --rm -v $(pwd):/data \
838
- washad/thailint:latest file-placement --format json /data
839
- ```
840
-
841
- ## Documentation
842
-
843
- ### Comprehensive Guides
844
-
845
- - **[Getting Started](docs/getting-started.md)** - Installation, first lint, basic config
846
- - **[Configuration Reference](docs/configuration.md)** - Complete config options (YAML/JSON)
847
- - **[API Reference](docs/api-reference.md)** - Library API documentation
848
- - **[CLI Reference](docs/cli-reference.md)** - All CLI commands and options
849
- - **[Deployment Modes](docs/deployment-modes.md)** - CLI, Library, and Docker usage
850
- - **[File Placement Linter](docs/file-placement-linter.md)** - Detailed linter guide
851
- - **[Nesting Depth Linter](docs/nesting-linter.md)** - Nesting depth analysis guide
852
- - **[SRP Linter](docs/srp-linter.md)** - Single Responsibility Principle guide
853
- - **[DRY Linter](docs/dry-linter.md)** - Duplicate code detection guide
854
- - **[Pre-commit Hooks](docs/pre-commit-hooks.md)** - Automated quality checks
855
- - **[Publishing Guide](docs/releasing.md)** - Release and publishing workflow
856
- - **[Publishing Checklist](docs/publishing-checklist.md)** - Post-publication validation
857
-
858
- ### Examples
859
-
860
- See [`examples/`](examples/) directory for working code:
861
-
862
- - **[basic_usage.py](examples/basic_usage.py)** - Simple library API usage
863
- - **[advanced_usage.py](examples/advanced_usage.py)** - Advanced patterns and workflows
864
- - **[ci_integration.py](examples/ci_integration.py)** - CI/CD integration example
865
-
866
- ## Project Structure
867
-
868
- ```
869
- thai-lint/
870
- ├── src/ # Application source code
871
- │ ├── api.py # High-level Library API
872
- │ ├── cli.py # CLI commands
873
- │ ├── core/ # Core abstractions
874
- │ │ ├── base.py # Base linter interfaces
875
- │ │ ├── registry.py # Rule registry
876
- │ │ └── types.py # Core types (Violation, Severity)
877
- │ ├── linters/ # Linter implementations
878
- │ │ └── file_placement/ # File placement linter
879
- │ ├── linter_config/ # Configuration system
880
- │ │ ├── loader.py # Config loader (YAML/JSON)
881
- │ │ └── ignore.py # Ignore directives
882
- │ └── orchestrator/ # Multi-language orchestrator
883
- │ ├── core.py # Main orchestrator
884
- │ └── language_detector.py
885
- ├── tests/ # Test suite (221 tests, 87% coverage)
886
- │ ├── unit/ # Unit tests
887
- │ ├── integration/ # Integration tests
888
- │ └── conftest.py # Pytest fixtures
889
- ├── docs/ # Documentation
890
- │ ├── getting-started.md
891
- │ ├── configuration.md
892
- │ ├── api-reference.md
893
- │ ├── cli-reference.md
894
- │ ├── deployment-modes.md
895
- │ └── file-placement-linter.md
896
- ├── examples/ # Working examples
897
- │ ├── basic_usage.py
898
- │ ├── advanced_usage.py
899
- │ └── ci_integration.py
900
- ├── .ai/ # AI agent documentation
901
- ├── Dockerfile # Multi-stage Docker build
902
- ├── docker-compose.yml # Docker orchestration
903
- └── pyproject.toml # Project configuration
904
- ```
905
-
906
- ## Contributing
907
-
908
- Contributions are welcome! Please follow these steps:
909
-
910
- 1. Fork the repository
911
- 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
912
- 3. Make your changes
913
- 4. Run tests and linting
914
- 5. Commit your changes (`git commit -m 'Add amazing feature'`)
915
- 6. Push to the branch (`git push origin feature/amazing-feature`)
916
- 7. Open a Pull Request
917
-
918
- ### Development Guidelines
919
-
920
- - Write tests for new features
921
- - Follow existing code style (enforced by Ruff)
922
- - Add type hints to all functions
923
- - Update documentation for user-facing changes
924
- - Run `pytest` and `ruff check` before committing
925
-
926
- ## Performance
927
-
928
- thailint is designed for speed and efficiency:
929
-
930
- | Operation | Performance | Target |
931
- |-----------|-------------|--------|
932
- | Single file lint | ~20ms | <100ms |
933
- | 100 files | ~300ms | <1s |
934
- | 1000 files | ~900ms | <5s |
935
- | Config loading | ~10ms | <100ms |
936
-
937
- *Performance benchmarks run on standard hardware, your results may vary.*
938
-
939
- ## Exit Codes
940
-
941
- thailint uses standard exit codes for CI/CD integration:
942
-
943
- - **0** - Success (no violations)
944
- - **1** - Violations found
945
- - **2** - Error occurred (invalid config, file not found, etc.)
946
-
947
- ```bash
948
- thailint file-placement .
949
- if [ $? -eq 0 ]; then
950
- echo "Linting passed"
951
- else
952
- echo "Linting failed"
953
- fi
954
- ```
955
-
956
- ## Architecture
957
-
958
- See [`.ai/docs/`](.ai/docs/) for detailed architecture documentation and [`.ai/howtos/`](.ai/howtos/) for development guides.
959
-
960
- ## License
961
-
962
- MIT License - see LICENSE file for details.
963
-
964
- ## Support
965
-
966
- - **Issues**: https://github.com/be-wise-be-kind/thai-lint/issues
967
- - **Documentation**: `.ai/docs/` and `.ai/howtos/`
968
-
969
- ## Acknowledgments
970
-
971
- Built with:
972
- - [Click](https://click.palletsprojects.com/) - CLI framework
973
- - [pytest](https://pytest.org/) - Testing framework
974
- - [Ruff](https://docs.astral.sh/ruff/) - Linting and formatting
975
- - [Docker](https://www.docker.com/) - Containerization
976
-
977
- ## Changelog
978
-
979
- See [CHANGELOG.md](CHANGELOG.md) for version history.
980
-