thailint 0.12.0__py3-none-any.whl → 0.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. src/analyzers/__init__.py +4 -3
  2. src/analyzers/ast_utils.py +54 -0
  3. src/analyzers/typescript_base.py +4 -0
  4. src/cli/__init__.py +3 -0
  5. src/cli/config.py +12 -12
  6. src/cli/config_merge.py +241 -0
  7. src/cli/linters/__init__.py +9 -0
  8. src/cli/linters/code_patterns.py +107 -257
  9. src/cli/linters/code_smells.py +48 -165
  10. src/cli/linters/documentation.py +21 -95
  11. src/cli/linters/performance.py +274 -0
  12. src/cli/linters/shared.py +232 -6
  13. src/cli/linters/structure.py +26 -21
  14. src/cli/linters/structure_quality.py +28 -21
  15. src/cli_main.py +3 -0
  16. src/config.py +2 -1
  17. src/core/base.py +3 -2
  18. src/core/cli_utils.py +3 -1
  19. src/core/config_parser.py +5 -2
  20. src/core/constants.py +54 -0
  21. src/core/linter_utils.py +95 -6
  22. src/core/rule_discovery.py +5 -1
  23. src/core/violation_builder.py +3 -0
  24. src/linter_config/directive_markers.py +109 -0
  25. src/linter_config/ignore.py +225 -383
  26. src/linter_config/pattern_utils.py +65 -0
  27. src/linter_config/rule_matcher.py +89 -0
  28. src/linters/collection_pipeline/any_all_analyzer.py +281 -0
  29. src/linters/collection_pipeline/ast_utils.py +40 -0
  30. src/linters/collection_pipeline/config.py +12 -0
  31. src/linters/collection_pipeline/continue_analyzer.py +2 -8
  32. src/linters/collection_pipeline/detector.py +262 -32
  33. src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
  34. src/linters/collection_pipeline/linter.py +18 -35
  35. src/linters/collection_pipeline/suggestion_builder.py +68 -1
  36. src/linters/dry/base_token_analyzer.py +16 -9
  37. src/linters/dry/block_filter.py +7 -4
  38. src/linters/dry/cache.py +7 -2
  39. src/linters/dry/config.py +7 -1
  40. src/linters/dry/constant_matcher.py +34 -25
  41. src/linters/dry/file_analyzer.py +4 -2
  42. src/linters/dry/inline_ignore.py +7 -16
  43. src/linters/dry/linter.py +48 -25
  44. src/linters/dry/python_analyzer.py +18 -10
  45. src/linters/dry/python_constant_extractor.py +51 -52
  46. src/linters/dry/single_statement_detector.py +14 -12
  47. src/linters/dry/token_hasher.py +115 -115
  48. src/linters/dry/typescript_analyzer.py +11 -6
  49. src/linters/dry/typescript_constant_extractor.py +4 -0
  50. src/linters/dry/typescript_statement_detector.py +208 -208
  51. src/linters/dry/typescript_value_extractor.py +3 -0
  52. src/linters/dry/violation_filter.py +1 -4
  53. src/linters/dry/violation_generator.py +1 -4
  54. src/linters/file_header/atemporal_detector.py +58 -40
  55. src/linters/file_header/base_parser.py +4 -0
  56. src/linters/file_header/bash_parser.py +4 -0
  57. src/linters/file_header/config.py +14 -0
  58. src/linters/file_header/field_validator.py +5 -8
  59. src/linters/file_header/linter.py +19 -12
  60. src/linters/file_header/markdown_parser.py +6 -0
  61. src/linters/file_placement/config_loader.py +3 -1
  62. src/linters/file_placement/linter.py +22 -8
  63. src/linters/file_placement/pattern_matcher.py +21 -4
  64. src/linters/file_placement/pattern_validator.py +21 -7
  65. src/linters/file_placement/rule_checker.py +2 -2
  66. src/linters/lazy_ignores/__init__.py +43 -0
  67. src/linters/lazy_ignores/config.py +66 -0
  68. src/linters/lazy_ignores/directive_utils.py +121 -0
  69. src/linters/lazy_ignores/header_parser.py +177 -0
  70. src/linters/lazy_ignores/linter.py +158 -0
  71. src/linters/lazy_ignores/matcher.py +135 -0
  72. src/linters/lazy_ignores/python_analyzer.py +205 -0
  73. src/linters/lazy_ignores/rule_id_utils.py +180 -0
  74. src/linters/lazy_ignores/skip_detector.py +298 -0
  75. src/linters/lazy_ignores/types.py +69 -0
  76. src/linters/lazy_ignores/typescript_analyzer.py +146 -0
  77. src/linters/lazy_ignores/violation_builder.py +131 -0
  78. src/linters/lbyl/__init__.py +29 -0
  79. src/linters/lbyl/config.py +63 -0
  80. src/linters/lbyl/pattern_detectors/__init__.py +25 -0
  81. src/linters/lbyl/pattern_detectors/base.py +46 -0
  82. src/linters/magic_numbers/context_analyzer.py +227 -229
  83. src/linters/magic_numbers/linter.py +20 -15
  84. src/linters/magic_numbers/python_analyzer.py +4 -16
  85. src/linters/magic_numbers/typescript_analyzer.py +9 -16
  86. src/linters/method_property/config.py +4 -1
  87. src/linters/method_property/linter.py +5 -10
  88. src/linters/method_property/python_analyzer.py +5 -4
  89. src/linters/method_property/violation_builder.py +3 -0
  90. src/linters/nesting/linter.py +11 -6
  91. src/linters/nesting/typescript_analyzer.py +6 -12
  92. src/linters/nesting/typescript_function_extractor.py +0 -4
  93. src/linters/nesting/violation_builder.py +1 -0
  94. src/linters/performance/__init__.py +91 -0
  95. src/linters/performance/config.py +43 -0
  96. src/linters/performance/constants.py +49 -0
  97. src/linters/performance/linter.py +149 -0
  98. src/linters/performance/python_analyzer.py +365 -0
  99. src/linters/performance/regex_analyzer.py +312 -0
  100. src/linters/performance/regex_linter.py +139 -0
  101. src/linters/performance/typescript_analyzer.py +236 -0
  102. src/linters/performance/violation_builder.py +160 -0
  103. src/linters/print_statements/linter.py +6 -4
  104. src/linters/print_statements/python_analyzer.py +85 -81
  105. src/linters/print_statements/typescript_analyzer.py +6 -15
  106. src/linters/srp/heuristics.py +4 -4
  107. src/linters/srp/linter.py +12 -12
  108. src/linters/srp/violation_builder.py +0 -4
  109. src/linters/stateless_class/linter.py +30 -36
  110. src/linters/stateless_class/python_analyzer.py +11 -20
  111. src/linters/stringly_typed/config.py +4 -5
  112. src/linters/stringly_typed/context_filter.py +410 -410
  113. src/linters/stringly_typed/function_call_violation_builder.py +93 -95
  114. src/linters/stringly_typed/linter.py +48 -16
  115. src/linters/stringly_typed/python/analyzer.py +5 -1
  116. src/linters/stringly_typed/python/call_tracker.py +8 -5
  117. src/linters/stringly_typed/python/comparison_tracker.py +10 -5
  118. src/linters/stringly_typed/python/condition_extractor.py +3 -0
  119. src/linters/stringly_typed/python/conditional_detector.py +4 -1
  120. src/linters/stringly_typed/python/match_analyzer.py +8 -2
  121. src/linters/stringly_typed/python/validation_detector.py +3 -0
  122. src/linters/stringly_typed/storage.py +14 -14
  123. src/linters/stringly_typed/typescript/call_tracker.py +9 -3
  124. src/linters/stringly_typed/typescript/comparison_tracker.py +9 -3
  125. src/linters/stringly_typed/violation_generator.py +288 -259
  126. src/orchestrator/core.py +13 -4
  127. src/templates/thailint_config_template.yaml +196 -0
  128. src/utils/project_root.py +3 -0
  129. thailint-0.14.0.dist-info/METADATA +185 -0
  130. thailint-0.14.0.dist-info/RECORD +199 -0
  131. thailint-0.12.0.dist-info/METADATA +0 -1667
  132. thailint-0.12.0.dist-info/RECORD +0 -164
  133. {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/WHEEL +0 -0
  134. {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/entry_points.txt +0 -0
  135. {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,1667 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: thailint
3
- Version: 0.12.0
4
- Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
5
- License: MIT
6
- License-File: LICENSE
7
- Keywords: linter,ai,code-quality,static-analysis,file-placement,governance,multi-language,cli,docker,python
8
- Author: Steve Jackson
9
- Requires-Python: >=3.11,<4.0
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Environment :: Console
12
- Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Operating System :: OS Independent
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Classifier: Programming Language :: Python :: 3.13
19
- Classifier: Programming Language :: Python :: 3.14
20
- Classifier: Programming Language :: Python :: 3 :: Only
21
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
- Classifier: Topic :: Software Development :: Quality Assurance
23
- Classifier: Topic :: Software Development :: Testing
24
- Classifier: Topic :: Utilities
25
- Classifier: Typing :: Typed
26
- Requires-Dist: click (>=8.1.0,<9.0.0)
27
- Requires-Dist: pyprojroot (>=0.3.0,<0.4.0)
28
- Requires-Dist: pyyaml (>=6.0,<7.0)
29
- Requires-Dist: tree-sitter (>=0.25.2,<0.26.0)
30
- Requires-Dist: tree-sitter-typescript (>=0.23.2,<0.24.0)
31
- Project-URL: Documentation, https://thai-lint.readthedocs.io/
32
- Project-URL: Homepage, https://github.com/be-wise-be-kind/thai-lint
33
- Project-URL: Repository, https://github.com/be-wise-be-kind/thai-lint
34
- Description-Content-Type: text/markdown
35
-
36
- # thai-lint
37
-
38
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
- [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
40
- [![Tests](https://img.shields.io/badge/tests-1076%2F1076%20passing-brightgreen.svg)](tests/)
41
- [![Coverage](https://img.shields.io/badge/coverage-89%25-brightgreen.svg)](htmlcov/)
42
- [![Documentation Status](https://readthedocs.org/projects/thai-lint/badge/?version=latest)](https://thai-lint.readthedocs.io/en/latest/?badge=latest)
43
- [![SARIF 2.1.0](https://img.shields.io/badge/SARIF-2.1.0-orange.svg)](docs/sarif-output.md)
44
-
45
- The AI Linter - Enterprise-ready linting and governance for AI-generated code across multiple languages.
46
-
47
- ## Documentation
48
-
49
- **New to thailint?** Start here:
50
- - **[Quick Start Guide](https://thai-lint.readthedocs.io/en/latest/quick-start/)** - Get running in 5 minutes
51
- - **[Configuration Reference](https://thai-lint.readthedocs.io/en/latest/configuration/)** - Complete config options for all linters
52
- - **[Troubleshooting Guide](https://thai-lint.readthedocs.io/en/latest/troubleshooting/)** - Common issues and solutions
53
-
54
- **Full Documentation:** Browse the **[documentation site](https://thai-lint.readthedocs.io/)** for comprehensive guides covering installation, all linters, configuration patterns, and integration examples.
55
-
56
- ## Overview
57
-
58
- thailint is a modern, enterprise-ready multi-language linter designed specifically for AI-generated code. It focuses on common mistakes and anti-patterns that AI coding assistants frequently introduce—issues that existing linters don't catch or don't handle consistently across languages.
59
-
60
- **Why thailint?**
61
-
62
- We're not trying to replace the wonderful existing linters like Pylint, ESLint, or Ruff. Instead, thailint fills critical gaps:
63
-
64
- - **AI-Specific Patterns**: AI assistants have predictable blind spots (excessive nesting, magic numbers, SRP violations) that traditional linters miss
65
- - **Cross-Language Consistency**: Detects the same anti-patterns across Python, TypeScript, and JavaScript with unified rules
66
- - **No Existing Solutions**: Issues like excessive nesting depth, file placement violations, and cross-project code duplication lack comprehensive multi-language detection
67
- - **Governance Layer**: Enforces project-wide structure and organization patterns that AI can't infer from local context
68
-
69
- thailint complements your existing linting stack by catching the patterns AI tools repeatedly miss.
70
-
71
- **Complete documentation available at [thai-lint.readthedocs.io](https://thai-lint.readthedocs.io/)** covering installation, configuration, all linters, and troubleshooting.
72
-
73
- ## Features
74
-
75
- ### Core Capabilities
76
- - **File Placement Linting** - Enforce project structure and organization
77
- - **File Header Linting** - Validate documentation headers in source files
78
- - Python, TypeScript, JavaScript, Bash, Markdown, CSS support
79
- - Mandatory field validation (Purpose, Scope, Overview)
80
- - Atemporal language detection (no dates, "currently", "now")
81
- - Language-specific header format parsing
82
- - **Magic Numbers Linting** - Detect unnamed numeric literals that should be constants
83
- - Python and TypeScript support with AST analysis
84
- - Context-aware detection (ignores constants, test files, range() usage)
85
- - Configurable allowed numbers and thresholds
86
- - Helpful suggestions for extracting to named constants
87
- - **Nesting Depth Linting** - Detect excessive code nesting with AST analysis
88
- - Python and TypeScript support with tree-sitter
89
- - Configurable max depth (default: 4, recommended: 3)
90
- - Helpful refactoring suggestions (guard clauses, extract method)
91
- - **SRP Linting** - Detect Single Responsibility Principle violations
92
- - Heuristic-based analysis (method count, LOC, keywords)
93
- - Language-specific thresholds (Python, TypeScript, JavaScript)
94
- - Refactoring patterns from real-world examples
95
- - **DRY Linting** - Detect duplicate code across projects
96
- - Token-based hash detection with SQLite storage
97
- - Fast duplicate detection (in-memory or disk-backed)
98
- - Configurable thresholds (lines, tokens, occurrences)
99
- - Language-specific detection (Python, TypeScript, JavaScript)
100
- - False positive filtering (keyword args, imports)
101
- - **Collection Pipeline Linting** - Detect for loops with embedded filtering
102
- - Based on Martin Fowler's "Replace Loop with Pipeline" refactoring
103
- - Detects if/continue patterns that should use generator expressions
104
- - Generates refactoring suggestions with generator syntax
105
- - Configurable threshold (min_continues)
106
- - Python support with AST analysis
107
- - **Method Property Linting** - Detect methods that should be @property decorators
108
- - Python AST-based detection
109
- - get_* prefix detection (Java-style getters)
110
- - Simple computed value detection
111
- - Action verb exclusion (to_*, finalize, serialize)
112
- - Test file detection
113
- - **Stateless Class Linting** - Detect classes that should be module-level functions
114
- - Python AST-based detection
115
- - No constructor (__init__ or __new__) detection
116
- - No instance state (self.attr) detection
117
- - Excludes ABC, Protocol, and decorated classes
118
- - Helpful refactoring suggestions
119
- - **Stringly-Typed Linting** - Detect string patterns that should use enums
120
- - Python and TypeScript support
121
- - Cross-file detection with SQLite storage
122
- - Detects membership validation, equality chains, function call patterns
123
- - False positive filtering (200+ exclusion patterns)
124
- - Inline ignore directive support
125
- - **Pluggable Architecture** - Easy to extend with custom linters
126
- - **Multi-Language Support** - Python, TypeScript, JavaScript, and more
127
- - **Flexible Configuration** - YAML/JSON configs with pattern matching
128
- - **5-Level Ignore System** - Repo, directory, file, method, and line-level ignores
129
-
130
- ### Deployment Modes
131
- - **CLI Mode** - Full-featured command-line interface
132
- - **Library API** - Python library for programmatic integration
133
- - **Docker Support** - Containerized deployment for CI/CD
134
-
135
- ### Enterprise Features
136
- - **Performance** - <100ms for single files, <5s for 1000 files
137
- - **Type Safety** - Full type hints and MyPy strict mode
138
- - **Test Coverage** - 90% coverage with 317 tests
139
- - **CI/CD Ready** - Proper exit codes and JSON output
140
- - **Comprehensive Docs** - Complete documentation and examples
141
-
142
- ## Installation
143
-
144
- ### From Source
145
-
146
- ```bash
147
- # Clone repository
148
- git clone https://github.com/be-wise-be-kind/thai-lint.git
149
- cd thai-lint
150
-
151
- # Install dependencies
152
- pip install -e ".[dev]"
153
- ```
154
-
155
- ### From PyPI
156
-
157
- ```bash
158
- pip install thai-lint
159
- ```
160
-
161
- ### With Docker
162
-
163
- ```bash
164
- # Pull from Docker Hub
165
- docker pull washad/thailint:latest
166
-
167
- # Run CLI
168
- docker run --rm washad/thailint:latest --help
169
- ```
170
-
171
- ## Quick Start
172
-
173
- ### CLI Mode
174
-
175
- ```bash
176
- # Check file placement
177
- thailint file-placement .
178
-
179
- # Check multiple files
180
- thailint nesting file1.py file2.py file3.py
181
-
182
- # Check specific directory
183
- thailint nesting src/
184
-
185
- # Check for duplicate code
186
- thailint dry .
187
-
188
- # Check for magic numbers
189
- thailint magic-numbers src/
190
-
191
- # Check file headers
192
- thailint file-header src/
193
-
194
- # With config file
195
- thailint dry --config .thailint.yaml src/
196
-
197
- # JSON output for CI/CD
198
- thailint dry --format json src/
199
-
200
- # SARIF output for GitHub Code Scanning
201
- thailint nesting --format sarif src/ > results.sarif
202
- ```
203
-
204
- **New to thailint?** See the **[Quick Start Guide](https://thai-lint.readthedocs.io/en/latest/quick-start/)** for a complete walkthrough including config generation, understanding output, and next steps.
205
-
206
- ### Library Mode
207
-
208
- ```python
209
- from src import Linter
210
-
211
- # Initialize linter
212
- linter = Linter(config_file='.thailint.yaml')
213
-
214
- # Lint directory
215
- violations = linter.lint('src/', rules=['file-placement'])
216
-
217
- # Process results
218
- if violations:
219
- for v in violations:
220
- print(f"{v.file_path}: {v.message}")
221
- ```
222
-
223
- ### Docker Mode
224
-
225
- ```bash
226
- # Lint directory (recommended - lints all files inside)
227
- docker run --rm -v $(pwd):/data \
228
- washad/thailint:latest file-placement /data
229
-
230
- # Lint single file
231
- docker run --rm -v $(pwd):/data \
232
- washad/thailint:latest file-placement /data/src/app.py
233
-
234
- # Lint multiple specific files
235
- docker run --rm -v $(pwd):/data \
236
- washad/thailint:latest nesting /data/src/file1.py /data/src/file2.py
237
-
238
- # Check nesting depth in subdirectory
239
- docker run --rm -v $(pwd):/data \
240
- washad/thailint:latest nesting /data/src
241
- ```
242
-
243
- ### Docker with Sibling Directories
244
-
245
- For Docker environments with sibling directories (e.g., separate config and source directories), use `--project-root` or config path inference:
246
-
247
- ```bash
248
- # Directory structure:
249
- # /workspace/
250
- # ├── root/ # Contains .thailint.yaml and .git
251
- # ├── backend/ # Code to lint
252
- # └── tools/
253
-
254
- # Option 1: Explicit project root (recommended)
255
- docker run --rm -v $(pwd):/data \
256
- washad/thailint:latest \
257
- --project-root /data/root \
258
- magic-numbers /data/backend/
259
-
260
- # Option 2: Config path inference (automatic)
261
- docker run --rm -v $(pwd):/data \
262
- washad/thailint:latest \
263
- --config /data/root/.thailint.yaml \
264
- magic-numbers /data/backend/
265
-
266
- # With ignore patterns resolving from project root
267
- docker run --rm -v $(pwd):/data \
268
- washad/thailint:latest \
269
- --project-root /data/root \
270
- --config /data/root/.thailint.yaml \
271
- magic-numbers /data/backend/
272
- ```
273
-
274
- **Priority order:**
275
- 1. `--project-root` (highest priority - explicit specification)
276
- 2. Inferred from `--config` path directory
277
- 3. Auto-detection from file location (fallback)
278
-
279
- See **[Docker Usage](#docker-usage)** section below for more examples.
280
-
281
- ## Configuration
282
-
283
- Create `.thailint.yaml` in your project root:
284
-
285
- ```yaml
286
- # File placement linter configuration
287
- file-placement:
288
- enabled: true
289
-
290
- # Global patterns apply to entire project
291
- global_patterns:
292
- deny:
293
- - pattern: "^(?!src/|tests/).*\\.py$"
294
- message: "Python files must be in src/ or tests/"
295
-
296
- # Directory-specific rules
297
- directories:
298
- src:
299
- allow:
300
- - ".*\\.py$"
301
- deny:
302
- - "test_.*\\.py$"
303
-
304
- tests:
305
- allow:
306
- - "test_.*\\.py$"
307
- - "conftest\\.py$"
308
-
309
- # Files/directories to ignore
310
- ignore:
311
- - "__pycache__/"
312
- - "*.pyc"
313
- - ".venv/"
314
-
315
- # Nesting depth linter configuration
316
- nesting:
317
- enabled: true
318
- max_nesting_depth: 4 # Maximum allowed nesting depth
319
-
320
- # Language-specific settings (optional)
321
- languages:
322
- python:
323
- max_depth: 4
324
- typescript:
325
- max_depth: 4
326
- javascript:
327
- max_depth: 4
328
-
329
- # DRY linter configuration
330
- dry:
331
- enabled: true
332
- min_duplicate_lines: 4 # Minimum lines to consider duplicate
333
- min_duplicate_tokens: 30 # Minimum tokens to consider duplicate
334
- min_occurrences: 2 # Report if appears 2+ times
335
-
336
- # Language-specific thresholds
337
- python:
338
- min_occurrences: 3 # Python: require 3+ occurrences
339
-
340
- # Storage settings (SQLite)
341
- storage_mode: "memory" # Options: "memory" (default) or "tempfile"
342
-
343
- # Ignore patterns
344
- ignore:
345
- - "tests/"
346
- - "__init__.py"
347
-
348
- # Magic numbers linter configuration
349
- magic-numbers:
350
- enabled: true
351
- allowed_numbers: [-1, 0, 1, 2, 10, 100, 1000] # Numbers allowed without constants
352
- max_small_integer: 10 # Max value allowed in range() or enumerate()
353
- ```
354
-
355
- **JSON format also supported** (`.thailint.json`):
356
-
357
- ```json
358
- {
359
- "file-placement": {
360
- "enabled": true,
361
- "directories": {
362
- "src": {
363
- "allow": [".*\\.py$"],
364
- "deny": ["test_.*\\.py$"]
365
- }
366
- },
367
- "ignore": ["__pycache__/", "*.pyc"]
368
- },
369
- "nesting": {
370
- "enabled": true,
371
- "max_nesting_depth": 4,
372
- "languages": {
373
- "python": { "max_depth": 4 },
374
- "typescript": { "max_depth": 4 }
375
- }
376
- },
377
- "dry": {
378
- "enabled": true,
379
- "min_duplicate_lines": 4,
380
- "min_duplicate_tokens": 30,
381
- "min_occurrences": 2,
382
- "python": {
383
- "min_occurrences": 3
384
- },
385
- "storage_mode": "memory",
386
- "ignore": ["tests/", "__init__.py"]
387
- },
388
- "magic-numbers": {
389
- "enabled": true,
390
- "allowed_numbers": [-1, 0, 1, 2, 10, 100, 1000],
391
- "max_small_integer": 10
392
- }
393
- }
394
- ```
395
-
396
- See [Configuration Guide](https://thai-lint.readthedocs.io/en/latest/configuration/) for complete reference.
397
-
398
- **Need help with ignores?** See **[How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/)** for complete guide to all ignore levels (line, method, class, file, repository).
399
-
400
- ## Nesting Depth Linter
401
-
402
- ### Overview
403
-
404
- 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.
405
-
406
- ### Quick Start
407
-
408
- ```bash
409
- # Check nesting depth in current directory
410
- thailint nesting .
411
-
412
- # Use strict limit (max depth 3)
413
- thailint nesting --max-depth 3 src/
414
-
415
- # Get JSON output
416
- thailint nesting --format json src/
417
- ```
418
-
419
- ### Configuration
420
-
421
- Add to `.thailint.yaml`:
422
-
423
- ```yaml
424
- nesting:
425
- enabled: true
426
- max_nesting_depth: 3 # Default: 4, recommended: 3
427
- ```
428
-
429
- ### Example Violation
430
-
431
- **Code with excessive nesting:**
432
- ```python
433
- def process_data(items):
434
- for item in items: # Depth 2
435
- if item.is_valid(): # Depth 3
436
- try: # Depth 4 ← VIOLATION (max=3)
437
- if item.process():
438
- return True
439
- except Exception:
440
- pass
441
- return False
442
- ```
443
-
444
- **Refactored with guard clauses:**
445
- ```python
446
- def process_data(items):
447
- for item in items: # Depth 2
448
- if not item.is_valid():
449
- continue
450
- try: # Depth 3 ✓
451
- if item.process():
452
- return True
453
- except Exception:
454
- pass
455
- return False
456
- ```
457
-
458
- ### Refactoring Patterns
459
-
460
- Common patterns to reduce nesting:
461
-
462
- 1. **Guard Clauses (Early Returns)**
463
- - Replace `if x: do_something()` with `if not x: return`
464
- - Exit early, reduce nesting
465
-
466
- 2. **Extract Method**
467
- - Move nested logic to separate functions
468
- - Improves readability and testability
469
-
470
- 3. **Dispatch Pattern**
471
- - Replace if-elif-else chains with dictionary dispatch
472
- - More extensible and cleaner
473
-
474
- 4. **Flatten Error Handling**
475
- - Combine multiple try-except blocks
476
- - Use tuple of exception types
477
-
478
- ### Language Support
479
-
480
- - **Python**: Full support (if/for/while/with/try/match)
481
- - **TypeScript**: Full support (if/for/while/try/switch)
482
- - **JavaScript**: Supported via TypeScript parser
483
-
484
- See [Nesting Linter Guide](https://thai-lint.readthedocs.io/en/latest/nesting-linter/) for comprehensive documentation and refactoring patterns.
485
-
486
- ## Single Responsibility Principle (SRP) Linter
487
-
488
- ### Overview
489
-
490
- 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.
491
-
492
- ### Quick Start
493
-
494
- ```bash
495
- # Check SRP violations in current directory
496
- thailint srp .
497
-
498
- # Use custom thresholds
499
- thailint srp --max-methods 10 --max-loc 300 src/
500
-
501
- # Get JSON output
502
- thailint srp --format json src/
503
- ```
504
-
505
- ### Configuration
506
-
507
- Add to `.thailint.yaml`:
508
-
509
- ```yaml
510
- srp:
511
- enabled: true
512
- max_methods: 7 # Maximum methods per class
513
- max_loc: 200 # Maximum lines of code per class
514
-
515
- # Language-specific thresholds
516
- python:
517
- max_methods: 8
518
- max_loc: 200
519
-
520
- typescript:
521
- max_methods: 10 # TypeScript more verbose
522
- max_loc: 250
523
- ```
524
-
525
- ### Detection Heuristics
526
-
527
- The SRP linter uses three heuristics to detect violations:
528
-
529
- 1. **Method Count**: Classes with >7 methods (default) likely have multiple responsibilities
530
- 2. **Lines of Code**: Classes with >200 LOC (default) are often doing too much
531
- 3. **Responsibility Keywords**: Names containing "Manager", "Handler", "Processor", etc.
532
-
533
- ### Example Violation
534
-
535
- **Code with SRP violation:**
536
- ```python
537
- class UserManager: # 8 methods, contains "Manager" keyword
538
- def create_user(self): pass
539
- def update_user(self): pass
540
- def delete_user(self): pass
541
- def send_email(self): pass # ← Different responsibility
542
- def log_action(self): pass # ← Different responsibility
543
- def validate_data(self): pass # ← Different responsibility
544
- def generate_report(self): pass # ← Different responsibility
545
- def export_data(self): pass # ← Violation at method 8
546
- ```
547
-
548
- **Refactored following SRP:**
549
- ```python
550
- class UserRepository: # 3 methods ✓
551
- def create(self, user): pass
552
- def update(self, user): pass
553
- def delete(self, user): pass
554
-
555
- class EmailService: # 1 method ✓
556
- def send(self, user, template): pass
557
-
558
- class UserAuditLog: # 1 method ✓
559
- def log(self, action, user): pass
560
-
561
- class UserValidator: # 1 method ✓
562
- def validate(self, data): pass
563
-
564
- class ReportGenerator: # 1 method ✓
565
- def generate(self, users): pass
566
- ```
567
-
568
- ### Refactoring Patterns
569
-
570
- Common patterns to fix SRP violations (discovered during dogfooding):
571
-
572
- 1. **Extract Class**
573
- - Split god classes into focused classes
574
- - Each class handles one responsibility
575
-
576
- 2. **Split Configuration and Logic**
577
- - Separate config loading from business logic
578
- - Create dedicated ConfigLoader classes
579
-
580
- 3. **Extract Language-Specific Logic**
581
- - Separate Python/TypeScript analysis
582
- - Use analyzer classes per language
583
-
584
- 4. **Utility Module Pattern**
585
- - Group related helper methods
586
- - Create focused utility classes
587
-
588
- ### Language Support
589
-
590
- - **Python**: Full support with method counting and LOC analysis
591
- - **TypeScript**: Full support with tree-sitter parsing
592
- - **JavaScript**: Supported via TypeScript parser
593
-
594
- ### Real-World Example
595
-
596
- **Large class refactoring:**
597
- - **Before**: FilePlacementLinter (33 methods, 382 LOC) - single class handling config, patterns, validation
598
- - **After**: Extract Class pattern applied - 5 focused classes (ConfigLoader, PatternValidator, RuleChecker, PathResolver, FilePlacementLinter)
599
- - **Result**: Each class ≤8 methods, ≤150 LOC, single responsibility
600
-
601
- See [SRP Linter Guide](https://thai-lint.readthedocs.io/en/latest/srp-linter/) for comprehensive documentation and refactoring patterns.
602
-
603
- ## DRY Linter (Don't Repeat Yourself)
604
-
605
- ### Overview
606
-
607
- The DRY linter detects duplicate code blocks across your entire project using token-based hashing with SQLite storage. It identifies identical or near-identical code that violates the Don't Repeat Yourself (DRY) principle, helping maintain code quality at scale.
608
-
609
- ### Quick Start
610
-
611
- ```bash
612
- # Check for duplicate code in current directory
613
- thailint dry .
614
-
615
- # Use custom thresholds
616
- thailint dry --min-lines 5 src/
617
-
618
- # Use tempfile storage for large projects
619
- thailint dry --storage-mode tempfile src/
620
-
621
- # Get JSON output
622
- thailint dry --format json src/
623
- ```
624
-
625
- ### Configuration
626
-
627
- Add to `.thailint.yaml`:
628
-
629
- ```yaml
630
- dry:
631
- enabled: true
632
- min_duplicate_lines: 4 # Minimum lines to consider duplicate
633
- min_duplicate_tokens: 30 # Minimum tokens to consider duplicate
634
- min_occurrences: 2 # Report if appears 2+ times
635
-
636
- # Language-specific thresholds
637
- python:
638
- min_occurrences: 3 # Python: require 3+ occurrences
639
- typescript:
640
- min_occurrences: 3 # TypeScript: require 3+ occurrences
641
-
642
- # Storage settings
643
- storage_mode: "memory" # Options: "memory" (default) or "tempfile"
644
-
645
- # Ignore patterns
646
- ignore:
647
- - "tests/" # Test code often has acceptable duplication
648
- - "__init__.py" # Import-only files exempt
649
-
650
- # False positive filters
651
- filters:
652
- keyword_argument_filter: true # Filter function call kwargs
653
- import_group_filter: true # Filter import groups
654
- ```
655
-
656
- ### How It Works
657
-
658
- **Token-Based Detection:**
659
- 1. Parse code into tokens (stripping comments, normalizing whitespace)
660
- 2. Create rolling hash windows of N lines
661
- 3. Store hashes in SQLite database with file locations
662
- 4. Query for hashes appearing 2+ times across project
663
-
664
- **SQLite Storage:**
665
- - In-memory mode (default): Stores in RAM for best performance
666
- - Tempfile mode: Stores in temporary disk file for large projects
667
- - Fresh analysis on every run (no persistence between runs)
668
- - Fast duplicate detection using B-tree indexes
669
-
670
- ### Example Violation
671
-
672
- **Code with duplication:**
673
- ```python
674
- # src/auth.py
675
- def validate_user(user_data):
676
- if not user_data:
677
- return False
678
- if not user_data.get('email'):
679
- return False
680
- if not user_data.get('password'):
681
- return False
682
- return True
683
-
684
- # src/admin.py
685
- def validate_admin(admin_data):
686
- if not admin_data:
687
- return False
688
- if not admin_data.get('email'):
689
- return False
690
- if not admin_data.get('password'):
691
- return False
692
- return True
693
- ```
694
-
695
- **Violation message:**
696
- ```
697
- src/auth.py:3 - Duplicate code detected (4 lines, 2 occurrences)
698
- Locations:
699
- - src/auth.py:3-6
700
- - src/admin.py:3-6
701
- Consider extracting to shared function
702
- ```
703
-
704
- **Refactored (DRY):**
705
- ```python
706
- # src/validators.py
707
- def validate_credentials(data):
708
- if not data:
709
- return False
710
- if not data.get('email'):
711
- return False
712
- if not data.get('password'):
713
- return False
714
- return True
715
-
716
- # src/auth.py & src/admin.py
717
- from src.validators import validate_credentials
718
-
719
- def validate_user(user_data):
720
- return validate_credentials(user_data)
721
-
722
- def validate_admin(admin_data):
723
- return validate_credentials(admin_data)
724
- ```
725
-
726
- ### Performance
727
-
728
- | Operation | Performance | Storage Mode |
729
- |-----------|-------------|--------------|
730
- | Scan (1000 files) | 1-3s | Memory (default) |
731
- | Large project (5000+ files) | Use tempfile mode | Tempfile |
732
-
733
- **Note**: Every run analyzes files fresh - no persistence between runs ensures accurate results
734
-
735
- ### Language Support
736
-
737
- - **Python**: Full support with AST-based tokenization
738
- - **TypeScript**: Full support with tree-sitter parsing
739
- - **JavaScript**: Supported via TypeScript parser
740
-
741
- ### False Positive Filtering
742
-
743
- Built-in filters automatically exclude common non-duplication patterns:
744
- - **keyword_argument_filter**: Excludes function calls with keyword arguments
745
- - **import_group_filter**: Excludes import statement groups
746
-
747
- ### Refactoring Patterns
748
-
749
- 1. **Extract Function**: Move repeated logic to shared function
750
- 2. **Extract Base Class**: Create base class for similar implementations
751
- 3. **Extract Utility Module**: Move helper functions to shared utilities
752
- 4. **Template Method**: Use function parameters for variations
753
-
754
- See [DRY Linter Guide](https://thai-lint.readthedocs.io/en/latest/dry-linter/) for comprehensive documentation, storage modes, and refactoring patterns.
755
-
756
- ## Collection Pipeline Linter
757
-
758
- ### Overview
759
-
760
- The collection-pipeline linter detects for loops with embedded filtering (if/continue patterns) that should be refactored to use generator expressions or other collection pipelines. Based on Martin Fowler's "Replace Loop with Pipeline" refactoring pattern.
761
-
762
- ### The Anti-Pattern
763
-
764
- ```python
765
- # Anti-pattern: Embedded filtering in loop body
766
- for file_path in dir_path.glob(pattern):
767
- if not file_path.is_file():
768
- continue
769
- if ignore_parser.is_ignored(file_path):
770
- continue
771
- violations.extend(lint_file(file_path))
772
- ```
773
-
774
- ### The Solution
775
-
776
- ```python
777
- # Collection pipeline: Filtering separated from processing
778
- valid_files = (
779
- f for f in dir_path.glob(pattern)
780
- if f.is_file() and not ignore_parser.is_ignored(f)
781
- )
782
- for file_path in valid_files:
783
- violations.extend(lint_file(file_path))
784
- ```
785
-
786
- ### Quick Start
787
-
788
- ```bash
789
- # Check current directory
790
- thailint pipeline .
791
-
792
- # Check specific directory
793
- thailint pipeline src/
794
-
795
- # Only flag patterns with 2+ filter conditions
796
- thailint pipeline --min-continues 2 src/
797
-
798
- # JSON output
799
- thailint pipeline --format json src/
800
- ```
801
-
802
- ### Configuration
803
-
804
- ```yaml
805
- # .thailint.yaml
806
- collection-pipeline:
807
- enabled: true
808
- min_continues: 1 # Minimum if/continue patterns to flag
809
- ignore:
810
- - "tests/**"
811
- - "**/legacy/**"
812
- ```
813
-
814
- ### Example Violation
815
-
816
- **Detected Pattern:**
817
- ```python
818
- def process_files(paths):
819
- for path in paths:
820
- if not path.is_file():
821
- continue
822
- analyze(path)
823
- ```
824
-
825
- **Violation Message:**
826
- ```
827
- src/processor.py:3 - For loop over 'paths' has embedded filtering.
828
- Consider using a generator expression:
829
- for path in (path for path in paths if path.is_file()):
830
- ```
831
-
832
- **Refactored Code:**
833
- ```python
834
- def process_files(paths):
835
- valid_paths = (p for p in paths if p.is_file())
836
- for path in valid_paths:
837
- analyze(path)
838
- ```
839
-
840
- ### Why This Matters
841
-
842
- - **Separation of concerns**: Filtering logic is separate from processing logic
843
- - **Readability**: Intent is clear at a glance
844
- - **Testability**: Filtering can be tested independently
845
- - **Based on**: Martin Fowler's "Replace Loop with Pipeline" refactoring
846
-
847
- ### Ignoring Violations
848
-
849
- ```python
850
- # Line-level ignore
851
- for item in items: # thailint: ignore[collection-pipeline]
852
- if not item.valid:
853
- continue
854
- process(item)
855
- ```
856
-
857
- See [Collection Pipeline Linter Guide](docs/collection-pipeline-linter.md) for comprehensive documentation and refactoring patterns.
858
-
859
- ## Magic Numbers Linter
860
-
861
- ### Overview
862
-
863
- The magic numbers linter detects unnamed numeric literals (magic numbers) that should be extracted to named constants. It uses AST analysis to identify numeric literals that lack meaningful context.
864
-
865
- ### What are Magic Numbers?
866
-
867
- **Magic numbers** are unnamed numeric literals in code without explanation:
868
-
869
- ```python
870
- # Bad - Magic numbers
871
- timeout = 3600 # What is 3600?
872
- max_retries = 5 # Why 5?
873
-
874
- # Good - Named constants
875
- TIMEOUT_SECONDS = 3600
876
- MAX_RETRY_ATTEMPTS = 5
877
- ```
878
-
879
- ### Quick Start
880
-
881
- ```bash
882
- # Check for magic numbers in current directory
883
- thailint magic-numbers .
884
-
885
- # Check specific directory
886
- thailint magic-numbers src/
887
-
888
- # Get JSON output
889
- thailint magic-numbers --format json src/
890
- ```
891
-
892
- ### Configuration
893
-
894
- Add to `.thailint.yaml`:
895
-
896
- ```yaml
897
- magic-numbers:
898
- enabled: true
899
- allowed_numbers: [-1, 0, 1, 2, 10, 100, 1000]
900
- max_small_integer: 10 # Max for range() to be acceptable
901
- ```
902
-
903
- ### Example Violation
904
-
905
- **Code with magic numbers:**
906
- ```python
907
- def calculate_timeout():
908
- return 3600 # Magic number - what is 3600?
909
-
910
- def process_items(items):
911
- for i in range(100): # Magic number - why 100?
912
- items[i] *= 1.5 # Magic number - what is 1.5?
913
- ```
914
-
915
- **Violation messages:**
916
- ```
917
- src/example.py:2 - Magic number 3600 should be a named constant
918
- src/example.py:5 - Magic number 100 should be a named constant
919
- src/example.py:6 - Magic number 1.5 should be a named constant
920
- ```
921
-
922
- **Refactored code:**
923
- ```python
924
- TIMEOUT_SECONDS = 3600
925
- MAX_ITEMS = 100
926
- PRICE_MULTIPLIER = 1.5
927
-
928
- def calculate_timeout():
929
- return TIMEOUT_SECONDS
930
-
931
- def process_items(items):
932
- for i in range(MAX_ITEMS):
933
- items[i] *= PRICE_MULTIPLIER
934
- ```
935
-
936
- ### Acceptable Contexts
937
-
938
- The linter **does not** flag numbers in these contexts:
939
-
940
- | Context | Example | Why Acceptable |
941
- |---------|---------|----------------|
942
- | Constants | `MAX_SIZE = 100` | UPPERCASE name provides context |
943
- | Small `range()` | `range(5)` | Small loop bounds are clear |
944
- | Test files | `test_*.py` | Test data can be literal |
945
- | Allowed numbers | `-1, 0, 1, 2, 10` | Common values are self-explanatory |
946
-
947
- ### Refactoring Patterns
948
-
949
- **Pattern 1: Extract to Module Constants**
950
- ```python
951
- # Before
952
- def connect():
953
- timeout = 30
954
- retries = 3
955
-
956
- # After
957
- DEFAULT_TIMEOUT_SECONDS = 30
958
- DEFAULT_MAX_RETRIES = 3
959
-
960
- def connect():
961
- timeout = DEFAULT_TIMEOUT_SECONDS
962
- retries = DEFAULT_MAX_RETRIES
963
- ```
964
-
965
- **Pattern 2: Extract with Units in Name**
966
- ```python
967
- # Before
968
- delay = 3600 # Is this seconds? Minutes?
969
-
970
- # After
971
- TASK_DELAY_SECONDS = 3600 # Clear unit
972
-
973
- delay = TASK_DELAY_SECONDS
974
- ```
975
-
976
- **Pattern 3: Use Standard Library**
977
- ```python
978
- # Before
979
- if status == 200:
980
- return "success"
981
-
982
- # After
983
- from http import HTTPStatus
984
-
985
- if status == HTTPStatus.OK:
986
- return "success"
987
- ```
988
-
989
- ### Language Support
990
-
991
- - **Python**: Full support (int, float, scientific notation)
992
- - **TypeScript**: Full support (int, float, scientific notation)
993
- - **JavaScript**: Supported via TypeScript parser
994
-
995
- ### Ignoring Violations
996
-
997
- ```python
998
- # Line-level ignore
999
- timeout = 3600 # thailint: ignore[magic-numbers] - Industry standard
1000
-
1001
- # Method-level ignore
1002
- def get_ports(): # thailint: ignore[magic-numbers] - Standard ports
1003
- return {80: "HTTP", 443: "HTTPS"}
1004
-
1005
- # File-level ignore
1006
- # thailint: ignore-file[magic-numbers]
1007
- ```
1008
-
1009
- See **[How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/)** and **[Magic Numbers Linter Guide](https://thai-lint.readthedocs.io/en/latest/magic-numbers-linter/)** for complete documentation.
1010
-
1011
- ## File Header Linter
1012
-
1013
- ### Overview
1014
-
1015
- The file header linter validates that source files have proper documentation headers containing required fields (Purpose, Scope, Overview) and don't use temporal language (dates, "currently", "now"). It enforces consistent documentation patterns across entire codebases.
1016
-
1017
- ### Why File Headers?
1018
-
1019
- File headers serve as **self-documentation** that helps developers (and AI assistants) quickly understand:
1020
-
1021
- - **Purpose**: What does this file do?
1022
- - **Scope**: What area of the system does it cover?
1023
- - **Dependencies**: What does it rely on?
1024
- - **Exports**: What does it provide to other modules?
1025
-
1026
- ### Quick Start
1027
-
1028
- ```bash
1029
- # Check file headers in current directory
1030
- thailint file-header .
1031
-
1032
- # Check specific directory
1033
- thailint file-header src/
1034
-
1035
- # Get JSON output
1036
- thailint file-header --format json src/
1037
-
1038
- # Get SARIF output for CI/CD
1039
- thailint file-header --format sarif src/ > results.sarif
1040
- ```
1041
-
1042
- ### Configuration
1043
-
1044
- Add to `.thailint.yaml`:
1045
-
1046
- ```yaml
1047
- file-header:
1048
- enabled: true
1049
- mandatory_fields:
1050
- - Purpose
1051
- - Scope
1052
- - Overview
1053
- ignore:
1054
- - "**/__init__.py"
1055
- - "**/migrations/**"
1056
- ```
1057
-
1058
- ### Example Violation
1059
-
1060
- **Code without proper header:**
1061
- ```python
1062
- import os
1063
-
1064
- def process_data():
1065
- pass
1066
- ```
1067
-
1068
- **Violation messages:**
1069
- ```
1070
- src/utils.py:1 - Missing mandatory field: Purpose
1071
- src/utils.py:1 - Missing mandatory field: Scope
1072
- src/utils.py:1 - Missing mandatory field: Overview
1073
- ```
1074
-
1075
- **Refactored with header:**
1076
- ```python
1077
- """
1078
- Purpose: Data processing utilities for ETL pipeline
1079
-
1080
- Scope: Data transformation layer, used by batch processing jobs
1081
-
1082
- Overview: Provides data transformation functions for the ETL pipeline.
1083
- Handles parsing, validation, and normalization of incoming data.
1084
-
1085
- Dependencies: os, json
1086
-
1087
- Exports: process_data(), validate_input(), transform_record()
1088
- """
1089
- import os
1090
-
1091
- def process_data():
1092
- pass
1093
- ```
1094
-
1095
- ### Atemporal Language Detection
1096
-
1097
- The linter detects temporal language that becomes stale:
1098
-
1099
- **Temporal (flagged):**
1100
- ```python
1101
- """
1102
- Purpose: Authentication module
1103
-
1104
- Overview: Currently handles OAuth. This was recently updated.
1105
- Created: 2024-01-15. Will be extended in the future.
1106
- """
1107
- ```
1108
-
1109
- **Atemporal (correct):**
1110
- ```python
1111
- """
1112
- Purpose: Authentication module
1113
-
1114
- Overview: Handles OAuth authentication with Google and GitHub.
1115
- Implements authorization code flow with PKCE for security.
1116
- """
1117
- ```
1118
-
1119
- ### Language Support
1120
-
1121
- - **Python**: Module docstrings (`"""..."""`)
1122
- - **TypeScript/JavaScript**: JSDoc comments (`/** ... */`)
1123
- - **Bash**: Hash comments after shebang (`# ...`)
1124
- - **Markdown**: YAML frontmatter (`---...---`)
1125
- - **CSS/SCSS**: Block comments (`/* ... */`)
1126
-
1127
- ### Ignoring Violations
1128
-
1129
- ```python
1130
- # File-level ignore
1131
- # thailint: ignore-file[file-header]
1132
-
1133
- # Line-level ignore for atemporal violation
1134
- """
1135
- Overview: Created 2024-01-15. # thailint: ignore[file-header]
1136
- """
1137
- ```
1138
-
1139
- See **[How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/)** and **[File Header Linter Guide](https://thai-lint.readthedocs.io/en/latest/file-header-linter/)** for complete documentation.
1140
-
1141
- ## Stateless Class Linter
1142
-
1143
- ### Overview
1144
-
1145
- The stateless class linter detects Python classes that have no state (no constructor, no instance attributes) and should be refactored to module-level functions. This is a common anti-pattern in AI-generated code.
1146
-
1147
- ### What Are Stateless Classes?
1148
-
1149
- Stateless classes are classes that:
1150
- - Have no `__init__` or `__new__` method
1151
- - Have no instance attributes (`self.attr` assignments)
1152
- - Have 2+ methods (grouped functionality without state)
1153
-
1154
- ```python
1155
- # Bad - Stateless class (no state, just grouped functions)
1156
- class TokenHasher:
1157
- def hash_token(self, token: str) -> str:
1158
- return hashlib.sha256(token.encode()).hexdigest()
1159
-
1160
- def verify_token(self, token: str, hash_value: str) -> bool:
1161
- return self.hash_token(token) == hash_value
1162
-
1163
- # Good - Module-level functions
1164
- def hash_token(token: str) -> str:
1165
- return hashlib.sha256(token.encode()).hexdigest()
1166
-
1167
- def verify_token(token: str, hash_value: str) -> bool:
1168
- return hash_token(token) == hash_value
1169
- ```
1170
-
1171
- ### Quick Start
1172
-
1173
- ```bash
1174
- # Check stateless classes in current directory
1175
- thailint stateless-class .
1176
-
1177
- # Check specific directory
1178
- thailint stateless-class src/
1179
-
1180
- # Get JSON output
1181
- thailint stateless-class --format json src/
1182
- ```
1183
-
1184
- ### Configuration
1185
-
1186
- Add to `.thailint.yaml`:
1187
-
1188
- ```yaml
1189
- stateless-class:
1190
- enabled: true
1191
- min_methods: 2 # Minimum methods to flag
1192
- ```
1193
-
1194
- ### Example Violation
1195
-
1196
- **Code with stateless class:**
1197
- ```python
1198
- class StringUtils:
1199
- def capitalize_words(self, text: str) -> str:
1200
- return ' '.join(w.capitalize() for w in text.split())
1201
-
1202
- def reverse_words(self, text: str) -> str:
1203
- return ' '.join(reversed(text.split()))
1204
- ```
1205
-
1206
- **Violation message:**
1207
- ```
1208
- src/utils.py:1 - Class 'StringUtils' has no state and should be refactored to module-level functions
1209
- ```
1210
-
1211
- **Refactored code:**
1212
- ```python
1213
- def capitalize_words(text: str) -> str:
1214
- return ' '.join(w.capitalize() for w in text.split())
1215
-
1216
- def reverse_words(text: str) -> str:
1217
- return ' '.join(reversed(text.split()))
1218
- ```
1219
-
1220
- ### Exclusion Rules
1221
-
1222
- The linter does NOT flag classes that:
1223
- - Have `__init__` or `__new__` constructors
1224
- - Have instance attributes (`self.attr = value`)
1225
- - Have class-level attributes
1226
- - Inherit from ABC or Protocol
1227
- - Have any decorator (`@dataclass`, `@register`, etc.)
1228
- - Have 0-1 methods
1229
-
1230
- ### Ignoring Violations
1231
-
1232
- ```python
1233
- # Line-level ignore
1234
- class TokenHasher: # thailint: ignore[stateless-class] - Legacy API
1235
- def hash(self, token): ...
1236
-
1237
- # File-level ignore
1238
- # thailint: ignore-file[stateless-class]
1239
- ```
1240
-
1241
- See **[How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/)** and **[Stateless Class Linter Guide](https://thai-lint.readthedocs.io/en/latest/stateless-class-linter/)** for complete documentation.
1242
-
1243
- ## Pre-commit Hooks
1244
-
1245
- Automate code quality checks before every commit and push with pre-commit hooks.
1246
-
1247
- ### Quick Setup
1248
-
1249
- ```bash
1250
- # 1. Install pre-commit framework
1251
- pip install pre-commit
1252
-
1253
- # 2. Install git hooks
1254
- pre-commit install
1255
- pre-commit install --hook-type pre-push
1256
-
1257
- # 3. Test it works
1258
- pre-commit run --all-files
1259
- ```
1260
-
1261
- ### What You Get
1262
-
1263
- **On every commit:**
1264
- - Prevents commits to main/master branch
1265
- - Auto-fixes formatting issues
1266
- - Runs thailint on changed files (fast, uses pass_filenames: true)
1267
-
1268
- **On every push:**
1269
- - Full linting on entire codebase
1270
- - Runs complete test suite
1271
-
1272
- ### Example Configuration
1273
-
1274
- ```yaml
1275
- # .pre-commit-config.yaml
1276
- repos:
1277
- - repo: local
1278
- hooks:
1279
- # Prevent commits to protected branches
1280
- - id: no-commit-to-main
1281
- name: Prevent commits to main branch
1282
- entry: bash -c 'branch=$(git rev-parse --abbrev-ref HEAD); if [ "$branch" = "main" ]; then echo "ERROR: Use a feature branch!"; exit 1; fi'
1283
- language: system
1284
- pass_filenames: false
1285
- always_run: true
1286
-
1287
- # Auto-format code
1288
- - id: format
1289
- name: Auto-fix formatting
1290
- entry: just format
1291
- language: system
1292
- pass_filenames: false
1293
-
1294
- # Run thailint on changed files (passes filenames directly)
1295
- - id: thailint-changed
1296
- name: Lint changed files
1297
- entry: thailint nesting
1298
- language: system
1299
- files: \.(py|ts|tsx|js|jsx)$
1300
- pass_filenames: true
1301
- ```
1302
-
1303
- See **[Pre-commit Hooks Guide](https://thai-lint.readthedocs.io/en/latest/pre-commit-hooks/)** for complete documentation, troubleshooting, and advanced configuration.
1304
-
1305
- ## Common Use Cases
1306
-
1307
- ### CI/CD Integration
1308
-
1309
- ```yaml
1310
- # GitHub Actions example
1311
- name: Lint
1312
-
1313
- on: [push, pull_request]
1314
-
1315
- jobs:
1316
- lint:
1317
- runs-on: ubuntu-latest
1318
- steps:
1319
- - uses: actions/checkout@v3
1320
- - name: Install thailint
1321
- run: pip install thailint
1322
- - name: Run file placement linter
1323
- run: thailint file-placement .
1324
- - name: Run nesting linter
1325
- run: thailint nesting src/ --config .thailint.yaml
1326
- ```
1327
-
1328
- ### Editor Integration
1329
-
1330
- ```python
1331
- # VS Code extension example
1332
- from src import Linter
1333
-
1334
- linter = Linter(config_file='.thailint.yaml')
1335
- violations = linter.lint(file_path)
1336
- ```
1337
-
1338
- ### Test Suite
1339
-
1340
- ```python
1341
- # pytest integration
1342
- import pytest
1343
- from src import Linter
1344
-
1345
- def test_no_violations():
1346
- linter = Linter()
1347
- violations = linter.lint('src/')
1348
- assert len(violations) == 0
1349
- ```
1350
-
1351
- ## Development
1352
-
1353
- ### Setup Development Environment
1354
-
1355
- ```bash
1356
- # Install dependencies and activate virtualenv
1357
- just init
1358
-
1359
- # Or manually:
1360
- poetry install
1361
- source $(poetry env info --path)/bin/activate
1362
- ```
1363
-
1364
- ### Running Tests
1365
-
1366
- ```bash
1367
- # Run all tests (parallel mode - fast)
1368
- just test
1369
-
1370
- # Run with coverage (serial mode)
1371
- just test-coverage
1372
-
1373
- # Run specific test
1374
- poetry run pytest tests/test_cli.py::test_hello_command -v
1375
- ```
1376
-
1377
- ### Code Quality
1378
-
1379
- ```bash
1380
- # Fast linting (Ruff only - use during development)
1381
- just lint
1382
-
1383
- # Comprehensive linting (Ruff + Pylint + Flake8 + MyPy)
1384
- just lint-all
1385
-
1386
- # Security scanning
1387
- just lint-security
1388
-
1389
- # Complexity analysis (Radon + Xenon + Nesting)
1390
- just lint-complexity
1391
-
1392
- # SOLID principles (SRP)
1393
- just lint-solid
1394
-
1395
- # DRY principles (duplicate code detection)
1396
- just lint-dry
1397
-
1398
- # ALL quality checks (runs everything)
1399
- just lint-full
1400
-
1401
- # Auto-fix formatting issues
1402
- just format
1403
- ```
1404
-
1405
- ### Dogfooding (Lint Our Own Code)
1406
-
1407
- ```bash
1408
- # Lint file placement
1409
- just lint-placement
1410
-
1411
- # Check nesting depth
1412
- just lint-nesting
1413
-
1414
- # Check for magic numbers
1415
- poetry run thai-lint magic-numbers src/
1416
- ```
1417
-
1418
- ### Building and Publishing
1419
-
1420
- ```bash
1421
- # Build Python package
1422
- poetry build
1423
-
1424
- # Build Docker image locally
1425
- docker build -t washad/thailint:latest .
1426
-
1427
- # Publish to PyPI and Docker Hub (runs tests + linting + version bump)
1428
- just publish
1429
- ```
1430
-
1431
- ### Quick Development Workflows
1432
-
1433
- ```bash
1434
- # Make changes, then run quality checks
1435
- just lint-full
1436
-
1437
- # Share changes for collaboration (skips hooks)
1438
- just share "WIP: feature description"
1439
-
1440
- # Clean up cache and artifacts
1441
- just clean
1442
- ```
1443
-
1444
- See `just --list` or `just help` for all available commands.
1445
-
1446
- ## Docker Usage
1447
-
1448
- ### Basic Docker Commands
1449
-
1450
- ```bash
1451
- # Pull published image
1452
- docker pull washad/thailint:latest
1453
-
1454
- # Run CLI help
1455
- docker run --rm washad/thailint:latest --help
1456
-
1457
- # Lint entire directory (recommended)
1458
- docker run --rm -v $(pwd):/data washad/thailint:latest file-placement /data
1459
-
1460
- # Lint single file
1461
- docker run --rm -v $(pwd):/data washad/thailint:latest file-placement /data/src/app.py
1462
-
1463
- # Lint multiple specific files
1464
- docker run --rm -v $(pwd):/data washad/thailint:latest nesting /data/src/file1.py /data/src/file2.py
1465
-
1466
- # Lint specific subdirectory
1467
- docker run --rm -v $(pwd):/data washad/thailint:latest nesting /data/src
1468
-
1469
- # Collection pipeline linter
1470
- docker run --rm -v $(pwd):/data washad/thailint:latest pipeline /data/src
1471
-
1472
- # With custom config
1473
- docker run --rm -v $(pwd):/data \
1474
- washad/thailint:latest nesting --config /data/.thailint.yaml /data
1475
-
1476
- # JSON output for CI/CD
1477
- docker run --rm -v $(pwd):/data \
1478
- washad/thailint:latest file-placement --format json /data
1479
- ```
1480
-
1481
- ### Docker with Sibling Directories (Advanced)
1482
-
1483
- For complex Docker setups with sibling directories, use `--project-root` for explicit control:
1484
-
1485
- ```bash
1486
- # Scenario: Monorepo with separate config and code directories
1487
- # Directory structure:
1488
- # /workspace/
1489
- # ├── config/ # Contains .thailint.yaml
1490
- # ├── backend/app/ # Python backend code
1491
- # ├── frontend/ # TypeScript frontend
1492
- # └── tools/ # Build tools
1493
-
1494
- # Explicit project root (recommended for Docker)
1495
- docker run --rm -v /path/to/workspace:/workspace \
1496
- washad/thailint:latest \
1497
- --project-root /workspace/config \
1498
- magic-numbers /workspace/backend/
1499
-
1500
- # Config path inference (automatic - no --project-root needed)
1501
- docker run --rm -v /path/to/workspace:/workspace \
1502
- washad/thailint:latest \
1503
- --config /workspace/config/.thailint.yaml \
1504
- magic-numbers /workspace/backend/
1505
-
1506
- # Lint multiple sibling directories with shared config
1507
- docker run --rm -v /path/to/workspace:/workspace \
1508
- washad/thailint:latest \
1509
- --project-root /workspace/config \
1510
- nesting /workspace/backend/ /workspace/frontend/
1511
- ```
1512
-
1513
- **When to use `--project-root` in Docker:**
1514
- - **Sibling directory structures** - When config/code aren't nested
1515
- - **Monorepos** - Multiple projects sharing one config
1516
- - **CI/CD** - Explicit paths prevent auto-detection issues
1517
- - **Ignore patterns** - Ensures patterns resolve from correct base directory
1518
-
1519
- ## Documentation
1520
-
1521
- ### Comprehensive Guides
1522
-
1523
- - **[Getting Started](https://thai-lint.readthedocs.io/en/latest/getting-started/)** - Installation, first lint, basic config
1524
- - **[Configuration Reference](https://thai-lint.readthedocs.io/en/latest/configuration/)** - Complete config options (YAML/JSON)
1525
- - **[How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/)** - Complete guide to all ignore levels
1526
- - **[API Reference](https://thai-lint.readthedocs.io/en/latest/api-reference/)** - Library API documentation
1527
- - **[CLI Reference](https://thai-lint.readthedocs.io/en/latest/cli-reference/)** - All CLI commands and options
1528
- - **[Deployment Modes](https://thai-lint.readthedocs.io/en/latest/deployment-modes/)** - CLI, Library, and Docker usage
1529
- - **[File Placement Linter](https://thai-lint.readthedocs.io/en/latest/file-placement-linter/)** - Detailed linter guide
1530
- - **[File Header Linter](https://thai-lint.readthedocs.io/en/latest/file-header-linter/)** - File header validation guide
1531
- - **[Magic Numbers Linter](https://thai-lint.readthedocs.io/en/latest/magic-numbers-linter/)** - Magic numbers detection guide
1532
- - **[Nesting Depth Linter](https://thai-lint.readthedocs.io/en/latest/nesting-linter/)** - Nesting depth analysis guide
1533
- - **[SRP Linter](https://thai-lint.readthedocs.io/en/latest/srp-linter/)** - Single Responsibility Principle guide
1534
- - **[DRY Linter](https://thai-lint.readthedocs.io/en/latest/dry-linter/)** - Duplicate code detection guide
1535
- - **[Collection Pipeline Linter](https://thai-lint.readthedocs.io/en/latest/collection-pipeline-linter/)** - Loop filtering refactoring guide
1536
- - **[Method Property Linter](https://thai-lint.readthedocs.io/en/latest/method-property-linter/)** - Method-to-property conversion guide
1537
- - **[Stateless Class Linter](https://thai-lint.readthedocs.io/en/latest/stateless-class-linter/)** - Stateless class detection guide
1538
- - **[Pre-commit Hooks](https://thai-lint.readthedocs.io/en/latest/pre-commit-hooks/)** - Automated quality checks
1539
- - **[SARIF Output Guide](docs/sarif-output.md)** - SARIF format for GitHub Code Scanning and CI/CD
1540
- - **[Publishing Guide](https://thai-lint.readthedocs.io/en/latest/releasing/)** - Release and publishing workflow
1541
- - **[Publishing Checklist](https://thai-lint.readthedocs.io/en/latest/publishing-checklist/)** - Post-publication validation
1542
-
1543
- ### Examples
1544
-
1545
- See [`examples/`](examples/) directory for working code:
1546
-
1547
- - **[basic_usage.py](examples/basic_usage.py)** - Simple library API usage
1548
- - **[advanced_usage.py](examples/advanced_usage.py)** - Advanced patterns and workflows
1549
- - **[ci_integration.py](examples/ci_integration.py)** - CI/CD integration example
1550
- - **[sarif_usage.py](examples/sarif_usage.py)** - SARIF output format examples
1551
- - **[file_header_usage.py](examples/file_header_usage.py)** - File header validation examples
1552
-
1553
- ## Project Structure
1554
-
1555
- ```
1556
- thai-lint/
1557
- ├── src/ # Application source code
1558
- │ ├── api.py # High-level Library API
1559
- │ ├── cli.py # CLI commands
1560
- │ ├── core/ # Core abstractions
1561
- │ │ ├── base.py # Base linter interfaces
1562
- │ │ ├── registry.py # Rule registry
1563
- │ │ └── types.py # Core types (Violation, Severity)
1564
- │ ├── linters/ # Linter implementations
1565
- │ │ └── file_placement/ # File placement linter
1566
- │ ├── linter_config/ # Configuration system
1567
- │ │ ├── loader.py # Config loader (YAML/JSON)
1568
- │ │ └── ignore.py # Ignore directives
1569
- │ └── orchestrator/ # Multi-language orchestrator
1570
- │ ├── core.py # Main orchestrator
1571
- │ └── language_detector.py
1572
- ├── tests/ # Test suite (221 tests, 87% coverage)
1573
- │ ├── unit/ # Unit tests
1574
- │ ├── integration/ # Integration tests
1575
- │ └── conftest.py # Pytest fixtures
1576
- ├── docs/ # Documentation
1577
- │ ├── getting-started.md
1578
- │ ├── configuration.md
1579
- │ ├── api-reference.md
1580
- │ ├── cli-reference.md
1581
- │ ├── deployment-modes.md
1582
- │ └── file-placement-linter.md
1583
- ├── examples/ # Working examples
1584
- │ ├── basic_usage.py
1585
- │ ├── advanced_usage.py
1586
- │ └── ci_integration.py
1587
- ├── .ai/ # AI agent documentation
1588
- ├── Dockerfile # Multi-stage Docker build
1589
- ├── docker-compose.yml # Docker orchestration
1590
- └── pyproject.toml # Project configuration
1591
- ```
1592
-
1593
- ## Contributing
1594
-
1595
- Contributions are welcome! Please follow these steps:
1596
-
1597
- 1. Fork the repository
1598
- 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
1599
- 3. Make your changes
1600
- 4. Run tests and linting
1601
- 5. Commit your changes (`git commit -m 'Add amazing feature'`)
1602
- 6. Push to the branch (`git push origin feature/amazing-feature`)
1603
- 7. Open a Pull Request
1604
-
1605
- ### Development Guidelines
1606
-
1607
- - Write tests for new features
1608
- - Follow existing code style (enforced by Ruff)
1609
- - Add type hints to all functions
1610
- - Update documentation for user-facing changes
1611
- - Run `pytest` and `ruff check` before committing
1612
-
1613
- ## Performance
1614
-
1615
- thailint is designed for speed and efficiency:
1616
-
1617
- | Operation | Performance | Target |
1618
- |-----------|-------------|--------|
1619
- | Single file lint | ~20ms | <100ms |
1620
- | 100 files | ~300ms | <1s |
1621
- | 1000 files | ~900ms | <5s |
1622
- | Config loading | ~10ms | <100ms |
1623
-
1624
- *Performance benchmarks run on standard hardware, your results may vary.*
1625
-
1626
- ## Exit Codes
1627
-
1628
- thailint uses standard exit codes for CI/CD integration:
1629
-
1630
- - **0** - Success (no violations)
1631
- - **1** - Violations found
1632
- - **2** - Error occurred (invalid config, file not found, etc.)
1633
-
1634
- ```bash
1635
- thailint file-placement .
1636
- if [ $? -eq 0 ]; then
1637
- echo "Linting passed"
1638
- else
1639
- echo "Linting failed"
1640
- fi
1641
- ```
1642
-
1643
- ## Architecture
1644
-
1645
- See [`.ai/docs/`](.ai/docs/) for detailed architecture documentation and [`.ai/howtos/`](.ai/howtos/) for development guides.
1646
-
1647
- ## License
1648
-
1649
- MIT License - see LICENSE file for details.
1650
-
1651
- ## Support
1652
-
1653
- - **Issues**: https://github.com/be-wise-be-kind/thai-lint/issues
1654
- - **Documentation**: `.ai/docs/` and `.ai/howtos/`
1655
-
1656
- ## Acknowledgments
1657
-
1658
- Built with:
1659
- - [Click](https://click.palletsprojects.com/) - CLI framework
1660
- - [pytest](https://pytest.org/) - Testing framework
1661
- - [Ruff](https://docs.astral.sh/ruff/) - Linting and formatting
1662
- - [Docker](https://www.docker.com/) - Containerization
1663
-
1664
- ## Changelog
1665
-
1666
- See [CHANGELOG.md](CHANGELOG.md) for version history.
1667
-