thailint 0.11.0__py3-none-any.whl → 0.13.0__py3-none-any.whl

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