thailint 0.5.0__py3-none-any.whl → 0.15.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- src/__init__.py +1 -0
- src/analyzers/__init__.py +4 -3
- src/analyzers/ast_utils.py +54 -0
- src/analyzers/rust_base.py +155 -0
- src/analyzers/rust_context.py +141 -0
- src/analyzers/typescript_base.py +4 -0
- src/cli/__init__.py +30 -0
- src/cli/__main__.py +22 -0
- src/cli/config.py +480 -0
- src/cli/config_merge.py +241 -0
- src/cli/linters/__init__.py +67 -0
- src/cli/linters/code_patterns.py +270 -0
- src/cli/linters/code_smells.py +342 -0
- src/cli/linters/documentation.py +83 -0
- src/cli/linters/performance.py +287 -0
- src/cli/linters/shared.py +331 -0
- src/cli/linters/structure.py +327 -0
- src/cli/linters/structure_quality.py +328 -0
- src/cli/main.py +120 -0
- src/cli/utils.py +395 -0
- src/cli_main.py +37 -0
- src/config.py +38 -25
- src/core/base.py +7 -2
- src/core/cli_utils.py +19 -2
- src/core/config_parser.py +5 -2
- src/core/constants.py +54 -0
- src/core/linter_utils.py +95 -6
- src/core/python_lint_rule.py +101 -0
- src/core/registry.py +1 -1
- src/core/rule_discovery.py +147 -84
- src/core/types.py +13 -0
- src/core/violation_builder.py +78 -15
- src/core/violation_utils.py +69 -0
- src/formatters/__init__.py +22 -0
- src/formatters/sarif.py +202 -0
- src/linter_config/directive_markers.py +109 -0
- src/linter_config/ignore.py +254 -395
- src/linter_config/loader.py +45 -12
- src/linter_config/pattern_utils.py +65 -0
- src/linter_config/rule_matcher.py +89 -0
- src/linters/collection_pipeline/__init__.py +90 -0
- src/linters/collection_pipeline/any_all_analyzer.py +281 -0
- src/linters/collection_pipeline/ast_utils.py +40 -0
- src/linters/collection_pipeline/config.py +75 -0
- src/linters/collection_pipeline/continue_analyzer.py +94 -0
- src/linters/collection_pipeline/detector.py +360 -0
- src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
- src/linters/collection_pipeline/linter.py +420 -0
- src/linters/collection_pipeline/suggestion_builder.py +130 -0
- src/linters/cqs/__init__.py +54 -0
- src/linters/cqs/config.py +55 -0
- src/linters/cqs/function_analyzer.py +201 -0
- src/linters/cqs/input_detector.py +139 -0
- src/linters/cqs/linter.py +159 -0
- src/linters/cqs/output_detector.py +84 -0
- src/linters/cqs/python_analyzer.py +54 -0
- src/linters/cqs/types.py +82 -0
- src/linters/cqs/typescript_cqs_analyzer.py +61 -0
- src/linters/cqs/typescript_function_analyzer.py +192 -0
- src/linters/cqs/typescript_input_detector.py +203 -0
- src/linters/cqs/typescript_output_detector.py +117 -0
- src/linters/cqs/violation_builder.py +94 -0
- src/linters/dry/base_token_analyzer.py +16 -9
- src/linters/dry/block_filter.py +120 -20
- src/linters/dry/block_grouper.py +4 -0
- src/linters/dry/cache.py +104 -10
- src/linters/dry/cache_query.py +4 -0
- src/linters/dry/config.py +54 -11
- src/linters/dry/constant.py +92 -0
- src/linters/dry/constant_matcher.py +223 -0
- src/linters/dry/constant_violation_builder.py +98 -0
- src/linters/dry/duplicate_storage.py +5 -4
- src/linters/dry/file_analyzer.py +4 -2
- src/linters/dry/inline_ignore.py +7 -16
- src/linters/dry/linter.py +183 -48
- src/linters/dry/python_analyzer.py +60 -439
- src/linters/dry/python_constant_extractor.py +100 -0
- src/linters/dry/single_statement_detector.py +417 -0
- src/linters/dry/token_hasher.py +116 -112
- src/linters/dry/typescript_analyzer.py +68 -382
- src/linters/dry/typescript_constant_extractor.py +138 -0
- src/linters/dry/typescript_statement_detector.py +255 -0
- src/linters/dry/typescript_value_extractor.py +70 -0
- src/linters/dry/violation_builder.py +4 -0
- src/linters/dry/violation_filter.py +5 -4
- src/linters/dry/violation_generator.py +71 -14
- src/linters/file_header/atemporal_detector.py +68 -50
- src/linters/file_header/base_parser.py +93 -0
- src/linters/file_header/bash_parser.py +66 -0
- src/linters/file_header/config.py +90 -16
- src/linters/file_header/css_parser.py +70 -0
- src/linters/file_header/field_validator.py +36 -33
- src/linters/file_header/linter.py +140 -144
- src/linters/file_header/markdown_parser.py +130 -0
- src/linters/file_header/python_parser.py +14 -58
- src/linters/file_header/typescript_parser.py +73 -0
- src/linters/file_header/violation_builder.py +13 -12
- src/linters/file_placement/config_loader.py +3 -1
- src/linters/file_placement/directory_matcher.py +4 -0
- src/linters/file_placement/linter.py +66 -34
- src/linters/file_placement/pattern_matcher.py +41 -6
- src/linters/file_placement/pattern_validator.py +31 -12
- src/linters/file_placement/rule_checker.py +12 -7
- src/linters/lazy_ignores/__init__.py +43 -0
- src/linters/lazy_ignores/config.py +74 -0
- src/linters/lazy_ignores/directive_utils.py +164 -0
- src/linters/lazy_ignores/header_parser.py +177 -0
- src/linters/lazy_ignores/linter.py +158 -0
- src/linters/lazy_ignores/matcher.py +168 -0
- src/linters/lazy_ignores/python_analyzer.py +209 -0
- src/linters/lazy_ignores/rule_id_utils.py +180 -0
- src/linters/lazy_ignores/skip_detector.py +298 -0
- src/linters/lazy_ignores/types.py +71 -0
- src/linters/lazy_ignores/typescript_analyzer.py +146 -0
- src/linters/lazy_ignores/violation_builder.py +135 -0
- src/linters/lbyl/__init__.py +31 -0
- src/linters/lbyl/config.py +63 -0
- src/linters/lbyl/linter.py +67 -0
- src/linters/lbyl/pattern_detectors/__init__.py +53 -0
- src/linters/lbyl/pattern_detectors/base.py +63 -0
- src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
- src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
- src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
- src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
- src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
- src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
- src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
- src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
- src/linters/lbyl/python_analyzer.py +215 -0
- src/linters/lbyl/violation_builder.py +354 -0
- src/linters/magic_numbers/context_analyzer.py +227 -225
- src/linters/magic_numbers/linter.py +28 -82
- src/linters/magic_numbers/python_analyzer.py +4 -16
- src/linters/magic_numbers/typescript_analyzer.py +9 -12
- src/linters/magic_numbers/typescript_ignore_checker.py +81 -0
- src/linters/method_property/__init__.py +49 -0
- src/linters/method_property/config.py +138 -0
- src/linters/method_property/linter.py +414 -0
- src/linters/method_property/python_analyzer.py +473 -0
- src/linters/method_property/violation_builder.py +119 -0
- src/linters/nesting/linter.py +24 -16
- src/linters/nesting/python_analyzer.py +4 -0
- src/linters/nesting/typescript_analyzer.py +6 -12
- src/linters/nesting/violation_builder.py +1 -0
- src/linters/performance/__init__.py +91 -0
- src/linters/performance/config.py +43 -0
- src/linters/performance/constants.py +49 -0
- src/linters/performance/linter.py +149 -0
- src/linters/performance/python_analyzer.py +365 -0
- src/linters/performance/regex_analyzer.py +312 -0
- src/linters/performance/regex_linter.py +139 -0
- src/linters/performance/typescript_analyzer.py +236 -0
- src/linters/performance/violation_builder.py +160 -0
- src/linters/print_statements/config.py +7 -12
- src/linters/print_statements/linter.py +26 -43
- src/linters/print_statements/python_analyzer.py +91 -93
- src/linters/print_statements/typescript_analyzer.py +15 -25
- src/linters/print_statements/violation_builder.py +12 -14
- src/linters/srp/class_analyzer.py +11 -7
- src/linters/srp/heuristics.py +56 -22
- src/linters/srp/linter.py +15 -16
- src/linters/srp/python_analyzer.py +55 -20
- src/linters/srp/typescript_metrics_calculator.py +110 -50
- src/linters/stateless_class/__init__.py +25 -0
- src/linters/stateless_class/config.py +58 -0
- src/linters/stateless_class/linter.py +349 -0
- src/linters/stateless_class/python_analyzer.py +290 -0
- src/linters/stringly_typed/__init__.py +36 -0
- src/linters/stringly_typed/config.py +189 -0
- src/linters/stringly_typed/context_filter.py +451 -0
- src/linters/stringly_typed/function_call_violation_builder.py +135 -0
- src/linters/stringly_typed/ignore_checker.py +100 -0
- src/linters/stringly_typed/ignore_utils.py +51 -0
- src/linters/stringly_typed/linter.py +376 -0
- src/linters/stringly_typed/python/__init__.py +33 -0
- src/linters/stringly_typed/python/analyzer.py +348 -0
- src/linters/stringly_typed/python/call_tracker.py +175 -0
- src/linters/stringly_typed/python/comparison_tracker.py +257 -0
- src/linters/stringly_typed/python/condition_extractor.py +134 -0
- src/linters/stringly_typed/python/conditional_detector.py +179 -0
- src/linters/stringly_typed/python/constants.py +21 -0
- src/linters/stringly_typed/python/match_analyzer.py +94 -0
- src/linters/stringly_typed/python/validation_detector.py +189 -0
- src/linters/stringly_typed/python/variable_extractor.py +96 -0
- src/linters/stringly_typed/storage.py +620 -0
- src/linters/stringly_typed/storage_initializer.py +45 -0
- src/linters/stringly_typed/typescript/__init__.py +28 -0
- src/linters/stringly_typed/typescript/analyzer.py +157 -0
- src/linters/stringly_typed/typescript/call_tracker.py +335 -0
- src/linters/stringly_typed/typescript/comparison_tracker.py +378 -0
- src/linters/stringly_typed/violation_generator.py +419 -0
- src/orchestrator/core.py +252 -14
- src/orchestrator/language_detector.py +5 -3
- src/templates/thailint_config_template.yaml +196 -0
- src/utils/project_root.py +3 -0
- thailint-0.15.3.dist-info/METADATA +187 -0
- thailint-0.15.3.dist-info/RECORD +226 -0
- thailint-0.15.3.dist-info/entry_points.txt +4 -0
- src/cli.py +0 -1665
- thailint-0.5.0.dist-info/METADATA +0 -1286
- thailint-0.5.0.dist-info/RECORD +0 -96
- thailint-0.5.0.dist-info/entry_points.txt +0 -4
- {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/WHEEL +0 -0
- {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,1286 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: thailint
|
|
3
|
-
Version: 0.5.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 :: 3 - Alpha
|
|
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
|
-
[](https://opensource.org/licenses/MIT)
|
|
39
|
-
[](https://www.python.org/downloads/)
|
|
40
|
-
[](tests/)
|
|
41
|
-
[](htmlcov/)
|
|
42
|
-
[](https://thai-lint.readthedocs.io/en/latest/?badge=latest)
|
|
43
|
-
|
|
44
|
-
The AI Linter - Enterprise-ready linting and governance for AI-generated code across multiple languages.
|
|
45
|
-
|
|
46
|
-
## Documentation
|
|
47
|
-
|
|
48
|
-
**New to thailint?** Start here:
|
|
49
|
-
- **[Quick Start Guide](https://thai-lint.readthedocs.io/en/latest/quick-start/)** - Get running in 5 minutes
|
|
50
|
-
- **[Configuration Reference](https://thai-lint.readthedocs.io/en/latest/configuration/)** - Complete config options for all linters
|
|
51
|
-
- **[Troubleshooting Guide](https://thai-lint.readthedocs.io/en/latest/troubleshooting/)** - Common issues and solutions
|
|
52
|
-
|
|
53
|
-
**Full Documentation:** Browse the **[documentation site](https://thai-lint.readthedocs.io/)** for comprehensive guides covering installation, all linters, configuration patterns, and integration examples.
|
|
54
|
-
|
|
55
|
-
## Overview
|
|
56
|
-
|
|
57
|
-
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.
|
|
58
|
-
|
|
59
|
-
**Why thailint?**
|
|
60
|
-
|
|
61
|
-
We're not trying to replace the wonderful existing linters like Pylint, ESLint, or Ruff. Instead, thailint fills critical gaps:
|
|
62
|
-
|
|
63
|
-
- **AI-Specific Patterns**: AI assistants have predictable blind spots (excessive nesting, magic numbers, SRP violations) that traditional linters miss
|
|
64
|
-
- **Cross-Language Consistency**: Detects the same anti-patterns across Python, TypeScript, and JavaScript with unified rules
|
|
65
|
-
- **No Existing Solutions**: Issues like excessive nesting depth, file placement violations, and cross-project code duplication lack comprehensive multi-language detection
|
|
66
|
-
- **Governance Layer**: Enforces project-wide structure and organization patterns that AI can't infer from local context
|
|
67
|
-
|
|
68
|
-
thailint complements your existing linting stack by catching the patterns AI tools repeatedly miss.
|
|
69
|
-
|
|
70
|
-
**Complete documentation available at [thai-lint.readthedocs.io](https://thai-lint.readthedocs.io/)** covering installation, configuration, all linters, and troubleshooting.
|
|
71
|
-
|
|
72
|
-
## Features
|
|
73
|
-
|
|
74
|
-
### Core Capabilities
|
|
75
|
-
- **File Placement Linting** - Enforce project structure and organization
|
|
76
|
-
- **Magic Numbers Linting** - Detect unnamed numeric literals that should be constants
|
|
77
|
-
- Python and TypeScript support with AST analysis
|
|
78
|
-
- Context-aware detection (ignores constants, test files, range() usage)
|
|
79
|
-
- Configurable allowed numbers and thresholds
|
|
80
|
-
- Helpful suggestions for extracting to named constants
|
|
81
|
-
- **Nesting Depth Linting** - Detect excessive code nesting with AST analysis
|
|
82
|
-
- Python and TypeScript support with tree-sitter
|
|
83
|
-
- Configurable max depth (default: 4, recommended: 3)
|
|
84
|
-
- Helpful refactoring suggestions (guard clauses, extract method)
|
|
85
|
-
- **SRP Linting** - Detect Single Responsibility Principle violations
|
|
86
|
-
- Heuristic-based analysis (method count, LOC, keywords)
|
|
87
|
-
- Language-specific thresholds (Python, TypeScript, JavaScript)
|
|
88
|
-
- Refactoring patterns from real-world examples
|
|
89
|
-
- **DRY Linting** - Detect duplicate code across projects
|
|
90
|
-
- Token-based hash detection with SQLite storage
|
|
91
|
-
- Fast duplicate detection (in-memory or disk-backed)
|
|
92
|
-
- Configurable thresholds (lines, tokens, occurrences)
|
|
93
|
-
- Language-specific detection (Python, TypeScript, JavaScript)
|
|
94
|
-
- False positive filtering (keyword args, imports)
|
|
95
|
-
- **Pluggable Architecture** - Easy to extend with custom linters
|
|
96
|
-
- **Multi-Language Support** - Python, TypeScript, JavaScript, and more
|
|
97
|
-
- **Flexible Configuration** - YAML/JSON configs with pattern matching
|
|
98
|
-
- **5-Level Ignore System** - Repo, directory, file, method, and line-level ignores
|
|
99
|
-
|
|
100
|
-
### Deployment Modes
|
|
101
|
-
- **CLI Mode** - Full-featured command-line interface
|
|
102
|
-
- **Library API** - Python library for programmatic integration
|
|
103
|
-
- **Docker Support** - Containerized deployment for CI/CD
|
|
104
|
-
|
|
105
|
-
### Enterprise Features
|
|
106
|
-
- **Performance** - <100ms for single files, <5s for 1000 files
|
|
107
|
-
- **Type Safety** - Full type hints and MyPy strict mode
|
|
108
|
-
- **Test Coverage** - 90% coverage with 317 tests
|
|
109
|
-
- **CI/CD Ready** - Proper exit codes and JSON output
|
|
110
|
-
- **Comprehensive Docs** - Complete documentation and examples
|
|
111
|
-
|
|
112
|
-
## Installation
|
|
113
|
-
|
|
114
|
-
### From Source
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
# Clone repository
|
|
118
|
-
git clone https://github.com/be-wise-be-kind/thai-lint.git
|
|
119
|
-
cd thai-lint
|
|
120
|
-
|
|
121
|
-
# Install dependencies
|
|
122
|
-
pip install -e ".[dev]"
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### From PyPI
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
pip install thai-lint
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### With Docker
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
# Pull from Docker Hub
|
|
135
|
-
docker pull washad/thailint:latest
|
|
136
|
-
|
|
137
|
-
# Run CLI
|
|
138
|
-
docker run --rm washad/thailint:latest --help
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Quick Start
|
|
142
|
-
|
|
143
|
-
### CLI Mode
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
# Check file placement
|
|
147
|
-
thailint file-placement .
|
|
148
|
-
|
|
149
|
-
# Check multiple files
|
|
150
|
-
thailint nesting file1.py file2.py file3.py
|
|
151
|
-
|
|
152
|
-
# Check specific directory
|
|
153
|
-
thailint nesting src/
|
|
154
|
-
|
|
155
|
-
# Check for duplicate code
|
|
156
|
-
thailint dry .
|
|
157
|
-
|
|
158
|
-
# Check for magic numbers
|
|
159
|
-
thailint magic-numbers src/
|
|
160
|
-
|
|
161
|
-
# With config file
|
|
162
|
-
thailint dry --config .thailint.yaml src/
|
|
163
|
-
|
|
164
|
-
# JSON output for CI/CD
|
|
165
|
-
thailint dry --format json src/
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
**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.
|
|
169
|
-
|
|
170
|
-
### Library Mode
|
|
171
|
-
|
|
172
|
-
```python
|
|
173
|
-
from src import Linter
|
|
174
|
-
|
|
175
|
-
# Initialize linter
|
|
176
|
-
linter = Linter(config_file='.thailint.yaml')
|
|
177
|
-
|
|
178
|
-
# Lint directory
|
|
179
|
-
violations = linter.lint('src/', rules=['file-placement'])
|
|
180
|
-
|
|
181
|
-
# Process results
|
|
182
|
-
if violations:
|
|
183
|
-
for v in violations:
|
|
184
|
-
print(f"{v.file_path}: {v.message}")
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Docker Mode
|
|
188
|
-
|
|
189
|
-
```bash
|
|
190
|
-
# Lint directory (recommended - lints all files inside)
|
|
191
|
-
docker run --rm -v $(pwd):/data \
|
|
192
|
-
washad/thailint:latest file-placement /data
|
|
193
|
-
|
|
194
|
-
# Lint single file
|
|
195
|
-
docker run --rm -v $(pwd):/data \
|
|
196
|
-
washad/thailint:latest file-placement /data/src/app.py
|
|
197
|
-
|
|
198
|
-
# Lint multiple specific files
|
|
199
|
-
docker run --rm -v $(pwd):/data \
|
|
200
|
-
washad/thailint:latest nesting /data/src/file1.py /data/src/file2.py
|
|
201
|
-
|
|
202
|
-
# Check nesting depth in subdirectory
|
|
203
|
-
docker run --rm -v $(pwd):/data \
|
|
204
|
-
washad/thailint:latest nesting /data/src
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Docker with Sibling Directories
|
|
208
|
-
|
|
209
|
-
For Docker environments with sibling directories (e.g., separate config and source directories), use `--project-root` or config path inference:
|
|
210
|
-
|
|
211
|
-
```bash
|
|
212
|
-
# Directory structure:
|
|
213
|
-
# /workspace/
|
|
214
|
-
# ├── root/ # Contains .thailint.yaml and .git
|
|
215
|
-
# ├── backend/ # Code to lint
|
|
216
|
-
# └── tools/
|
|
217
|
-
|
|
218
|
-
# Option 1: Explicit project root (recommended)
|
|
219
|
-
docker run --rm -v $(pwd):/data \
|
|
220
|
-
washad/thailint:latest \
|
|
221
|
-
--project-root /data/root \
|
|
222
|
-
magic-numbers /data/backend/
|
|
223
|
-
|
|
224
|
-
# Option 2: Config path inference (automatic)
|
|
225
|
-
docker run --rm -v $(pwd):/data \
|
|
226
|
-
washad/thailint:latest \
|
|
227
|
-
--config /data/root/.thailint.yaml \
|
|
228
|
-
magic-numbers /data/backend/
|
|
229
|
-
|
|
230
|
-
# With ignore patterns resolving from project root
|
|
231
|
-
docker run --rm -v $(pwd):/data \
|
|
232
|
-
washad/thailint:latest \
|
|
233
|
-
--project-root /data/root \
|
|
234
|
-
--config /data/root/.thailint.yaml \
|
|
235
|
-
magic-numbers /data/backend/
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
**Priority order:**
|
|
239
|
-
1. `--project-root` (highest priority - explicit specification)
|
|
240
|
-
2. Inferred from `--config` path directory
|
|
241
|
-
3. Auto-detection from file location (fallback)
|
|
242
|
-
|
|
243
|
-
See **[Docker Usage](#docker-usage)** section below for more examples.
|
|
244
|
-
|
|
245
|
-
## Configuration
|
|
246
|
-
|
|
247
|
-
Create `.thailint.yaml` in your project root:
|
|
248
|
-
|
|
249
|
-
```yaml
|
|
250
|
-
# File placement linter configuration
|
|
251
|
-
file-placement:
|
|
252
|
-
enabled: true
|
|
253
|
-
|
|
254
|
-
# Global patterns apply to entire project
|
|
255
|
-
global_patterns:
|
|
256
|
-
deny:
|
|
257
|
-
- pattern: "^(?!src/|tests/).*\\.py$"
|
|
258
|
-
message: "Python files must be in src/ or tests/"
|
|
259
|
-
|
|
260
|
-
# Directory-specific rules
|
|
261
|
-
directories:
|
|
262
|
-
src:
|
|
263
|
-
allow:
|
|
264
|
-
- ".*\\.py$"
|
|
265
|
-
deny:
|
|
266
|
-
- "test_.*\\.py$"
|
|
267
|
-
|
|
268
|
-
tests:
|
|
269
|
-
allow:
|
|
270
|
-
- "test_.*\\.py$"
|
|
271
|
-
- "conftest\\.py$"
|
|
272
|
-
|
|
273
|
-
# Files/directories to ignore
|
|
274
|
-
ignore:
|
|
275
|
-
- "__pycache__/"
|
|
276
|
-
- "*.pyc"
|
|
277
|
-
- ".venv/"
|
|
278
|
-
|
|
279
|
-
# Nesting depth linter configuration
|
|
280
|
-
nesting:
|
|
281
|
-
enabled: true
|
|
282
|
-
max_nesting_depth: 4 # Maximum allowed nesting depth
|
|
283
|
-
|
|
284
|
-
# Language-specific settings (optional)
|
|
285
|
-
languages:
|
|
286
|
-
python:
|
|
287
|
-
max_depth: 4
|
|
288
|
-
typescript:
|
|
289
|
-
max_depth: 4
|
|
290
|
-
javascript:
|
|
291
|
-
max_depth: 4
|
|
292
|
-
|
|
293
|
-
# DRY linter configuration
|
|
294
|
-
dry:
|
|
295
|
-
enabled: true
|
|
296
|
-
min_duplicate_lines: 4 # Minimum lines to consider duplicate
|
|
297
|
-
min_duplicate_tokens: 30 # Minimum tokens to consider duplicate
|
|
298
|
-
min_occurrences: 2 # Report if appears 2+ times
|
|
299
|
-
|
|
300
|
-
# Language-specific thresholds
|
|
301
|
-
python:
|
|
302
|
-
min_occurrences: 3 # Python: require 3+ occurrences
|
|
303
|
-
|
|
304
|
-
# Storage settings (SQLite)
|
|
305
|
-
storage_mode: "memory" # Options: "memory" (default) or "tempfile"
|
|
306
|
-
|
|
307
|
-
# Ignore patterns
|
|
308
|
-
ignore:
|
|
309
|
-
- "tests/"
|
|
310
|
-
- "__init__.py"
|
|
311
|
-
|
|
312
|
-
# Magic numbers linter configuration
|
|
313
|
-
magic-numbers:
|
|
314
|
-
enabled: true
|
|
315
|
-
allowed_numbers: [-1, 0, 1, 2, 10, 100, 1000] # Numbers allowed without constants
|
|
316
|
-
max_small_integer: 10 # Max value allowed in range() or enumerate()
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
**JSON format also supported** (`.thailint.json`):
|
|
320
|
-
|
|
321
|
-
```json
|
|
322
|
-
{
|
|
323
|
-
"file-placement": {
|
|
324
|
-
"enabled": true,
|
|
325
|
-
"directories": {
|
|
326
|
-
"src": {
|
|
327
|
-
"allow": [".*\\.py$"],
|
|
328
|
-
"deny": ["test_.*\\.py$"]
|
|
329
|
-
}
|
|
330
|
-
},
|
|
331
|
-
"ignore": ["__pycache__/", "*.pyc"]
|
|
332
|
-
},
|
|
333
|
-
"nesting": {
|
|
334
|
-
"enabled": true,
|
|
335
|
-
"max_nesting_depth": 4,
|
|
336
|
-
"languages": {
|
|
337
|
-
"python": { "max_depth": 4 },
|
|
338
|
-
"typescript": { "max_depth": 4 }
|
|
339
|
-
}
|
|
340
|
-
},
|
|
341
|
-
"dry": {
|
|
342
|
-
"enabled": true,
|
|
343
|
-
"min_duplicate_lines": 4,
|
|
344
|
-
"min_duplicate_tokens": 30,
|
|
345
|
-
"min_occurrences": 2,
|
|
346
|
-
"python": {
|
|
347
|
-
"min_occurrences": 3
|
|
348
|
-
},
|
|
349
|
-
"storage_mode": "memory",
|
|
350
|
-
"ignore": ["tests/", "__init__.py"]
|
|
351
|
-
},
|
|
352
|
-
"magic-numbers": {
|
|
353
|
-
"enabled": true,
|
|
354
|
-
"allowed_numbers": [-1, 0, 1, 2, 10, 100, 1000],
|
|
355
|
-
"max_small_integer": 10
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
See [Configuration Guide](https://thai-lint.readthedocs.io/en/latest/configuration/) for complete reference.
|
|
361
|
-
|
|
362
|
-
**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).
|
|
363
|
-
|
|
364
|
-
## Nesting Depth Linter
|
|
365
|
-
|
|
366
|
-
### Overview
|
|
367
|
-
|
|
368
|
-
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.
|
|
369
|
-
|
|
370
|
-
### Quick Start
|
|
371
|
-
|
|
372
|
-
```bash
|
|
373
|
-
# Check nesting depth in current directory
|
|
374
|
-
thailint nesting .
|
|
375
|
-
|
|
376
|
-
# Use strict limit (max depth 3)
|
|
377
|
-
thailint nesting --max-depth 3 src/
|
|
378
|
-
|
|
379
|
-
# Get JSON output
|
|
380
|
-
thailint nesting --format json src/
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### Configuration
|
|
384
|
-
|
|
385
|
-
Add to `.thailint.yaml`:
|
|
386
|
-
|
|
387
|
-
```yaml
|
|
388
|
-
nesting:
|
|
389
|
-
enabled: true
|
|
390
|
-
max_nesting_depth: 3 # Default: 4, recommended: 3
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
### Example Violation
|
|
394
|
-
|
|
395
|
-
**Code with excessive nesting:**
|
|
396
|
-
```python
|
|
397
|
-
def process_data(items):
|
|
398
|
-
for item in items: # Depth 2
|
|
399
|
-
if item.is_valid(): # Depth 3
|
|
400
|
-
try: # Depth 4 ← VIOLATION (max=3)
|
|
401
|
-
if item.process():
|
|
402
|
-
return True
|
|
403
|
-
except Exception:
|
|
404
|
-
pass
|
|
405
|
-
return False
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
**Refactored with guard clauses:**
|
|
409
|
-
```python
|
|
410
|
-
def process_data(items):
|
|
411
|
-
for item in items: # Depth 2
|
|
412
|
-
if not item.is_valid():
|
|
413
|
-
continue
|
|
414
|
-
try: # Depth 3 ✓
|
|
415
|
-
if item.process():
|
|
416
|
-
return True
|
|
417
|
-
except Exception:
|
|
418
|
-
pass
|
|
419
|
-
return False
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
### Refactoring Patterns
|
|
423
|
-
|
|
424
|
-
Common patterns to reduce nesting:
|
|
425
|
-
|
|
426
|
-
1. **Guard Clauses (Early Returns)**
|
|
427
|
-
- Replace `if x: do_something()` with `if not x: return`
|
|
428
|
-
- Exit early, reduce nesting
|
|
429
|
-
|
|
430
|
-
2. **Extract Method**
|
|
431
|
-
- Move nested logic to separate functions
|
|
432
|
-
- Improves readability and testability
|
|
433
|
-
|
|
434
|
-
3. **Dispatch Pattern**
|
|
435
|
-
- Replace if-elif-else chains with dictionary dispatch
|
|
436
|
-
- More extensible and cleaner
|
|
437
|
-
|
|
438
|
-
4. **Flatten Error Handling**
|
|
439
|
-
- Combine multiple try-except blocks
|
|
440
|
-
- Use tuple of exception types
|
|
441
|
-
|
|
442
|
-
### Language Support
|
|
443
|
-
|
|
444
|
-
- **Python**: Full support (if/for/while/with/try/match)
|
|
445
|
-
- **TypeScript**: Full support (if/for/while/try/switch)
|
|
446
|
-
- **JavaScript**: Supported via TypeScript parser
|
|
447
|
-
|
|
448
|
-
See [Nesting Linter Guide](https://thai-lint.readthedocs.io/en/latest/nesting-linter/) for comprehensive documentation and refactoring patterns.
|
|
449
|
-
|
|
450
|
-
## Single Responsibility Principle (SRP) Linter
|
|
451
|
-
|
|
452
|
-
### Overview
|
|
453
|
-
|
|
454
|
-
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.
|
|
455
|
-
|
|
456
|
-
### Quick Start
|
|
457
|
-
|
|
458
|
-
```bash
|
|
459
|
-
# Check SRP violations in current directory
|
|
460
|
-
thailint srp .
|
|
461
|
-
|
|
462
|
-
# Use custom thresholds
|
|
463
|
-
thailint srp --max-methods 10 --max-loc 300 src/
|
|
464
|
-
|
|
465
|
-
# Get JSON output
|
|
466
|
-
thailint srp --format json src/
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
### Configuration
|
|
470
|
-
|
|
471
|
-
Add to `.thailint.yaml`:
|
|
472
|
-
|
|
473
|
-
```yaml
|
|
474
|
-
srp:
|
|
475
|
-
enabled: true
|
|
476
|
-
max_methods: 7 # Maximum methods per class
|
|
477
|
-
max_loc: 200 # Maximum lines of code per class
|
|
478
|
-
|
|
479
|
-
# Language-specific thresholds
|
|
480
|
-
python:
|
|
481
|
-
max_methods: 8
|
|
482
|
-
max_loc: 200
|
|
483
|
-
|
|
484
|
-
typescript:
|
|
485
|
-
max_methods: 10 # TypeScript more verbose
|
|
486
|
-
max_loc: 250
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
### Detection Heuristics
|
|
490
|
-
|
|
491
|
-
The SRP linter uses three heuristics to detect violations:
|
|
492
|
-
|
|
493
|
-
1. **Method Count**: Classes with >7 methods (default) likely have multiple responsibilities
|
|
494
|
-
2. **Lines of Code**: Classes with >200 LOC (default) are often doing too much
|
|
495
|
-
3. **Responsibility Keywords**: Names containing "Manager", "Handler", "Processor", etc.
|
|
496
|
-
|
|
497
|
-
### Example Violation
|
|
498
|
-
|
|
499
|
-
**Code with SRP violation:**
|
|
500
|
-
```python
|
|
501
|
-
class UserManager: # 8 methods, contains "Manager" keyword
|
|
502
|
-
def create_user(self): pass
|
|
503
|
-
def update_user(self): pass
|
|
504
|
-
def delete_user(self): pass
|
|
505
|
-
def send_email(self): pass # ← Different responsibility
|
|
506
|
-
def log_action(self): pass # ← Different responsibility
|
|
507
|
-
def validate_data(self): pass # ← Different responsibility
|
|
508
|
-
def generate_report(self): pass # ← Different responsibility
|
|
509
|
-
def export_data(self): pass # ← Violation at method 8
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
**Refactored following SRP:**
|
|
513
|
-
```python
|
|
514
|
-
class UserRepository: # 3 methods ✓
|
|
515
|
-
def create(self, user): pass
|
|
516
|
-
def update(self, user): pass
|
|
517
|
-
def delete(self, user): pass
|
|
518
|
-
|
|
519
|
-
class EmailService: # 1 method ✓
|
|
520
|
-
def send(self, user, template): pass
|
|
521
|
-
|
|
522
|
-
class UserAuditLog: # 1 method ✓
|
|
523
|
-
def log(self, action, user): pass
|
|
524
|
-
|
|
525
|
-
class UserValidator: # 1 method ✓
|
|
526
|
-
def validate(self, data): pass
|
|
527
|
-
|
|
528
|
-
class ReportGenerator: # 1 method ✓
|
|
529
|
-
def generate(self, users): pass
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
### Refactoring Patterns
|
|
533
|
-
|
|
534
|
-
Common patterns to fix SRP violations (discovered during dogfooding):
|
|
535
|
-
|
|
536
|
-
1. **Extract Class**
|
|
537
|
-
- Split god classes into focused classes
|
|
538
|
-
- Each class handles one responsibility
|
|
539
|
-
|
|
540
|
-
2. **Split Configuration and Logic**
|
|
541
|
-
- Separate config loading from business logic
|
|
542
|
-
- Create dedicated ConfigLoader classes
|
|
543
|
-
|
|
544
|
-
3. **Extract Language-Specific Logic**
|
|
545
|
-
- Separate Python/TypeScript analysis
|
|
546
|
-
- Use analyzer classes per language
|
|
547
|
-
|
|
548
|
-
4. **Utility Module Pattern**
|
|
549
|
-
- Group related helper methods
|
|
550
|
-
- Create focused utility classes
|
|
551
|
-
|
|
552
|
-
### Language Support
|
|
553
|
-
|
|
554
|
-
- **Python**: Full support with method counting and LOC analysis
|
|
555
|
-
- **TypeScript**: Full support with tree-sitter parsing
|
|
556
|
-
- **JavaScript**: Supported via TypeScript parser
|
|
557
|
-
|
|
558
|
-
### Real-World Example
|
|
559
|
-
|
|
560
|
-
**Large class refactoring:**
|
|
561
|
-
- **Before**: FilePlacementLinter (33 methods, 382 LOC) - single class handling config, patterns, validation
|
|
562
|
-
- **After**: Extract Class pattern applied - 5 focused classes (ConfigLoader, PatternValidator, RuleChecker, PathResolver, FilePlacementLinter)
|
|
563
|
-
- **Result**: Each class ≤8 methods, ≤150 LOC, single responsibility
|
|
564
|
-
|
|
565
|
-
See [SRP Linter Guide](https://thai-lint.readthedocs.io/en/latest/srp-linter/) for comprehensive documentation and refactoring patterns.
|
|
566
|
-
|
|
567
|
-
## DRY Linter (Don't Repeat Yourself)
|
|
568
|
-
|
|
569
|
-
### Overview
|
|
570
|
-
|
|
571
|
-
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.
|
|
572
|
-
|
|
573
|
-
### Quick Start
|
|
574
|
-
|
|
575
|
-
```bash
|
|
576
|
-
# Check for duplicate code in current directory
|
|
577
|
-
thailint dry .
|
|
578
|
-
|
|
579
|
-
# Use custom thresholds
|
|
580
|
-
thailint dry --min-lines 5 src/
|
|
581
|
-
|
|
582
|
-
# Use tempfile storage for large projects
|
|
583
|
-
thailint dry --storage-mode tempfile src/
|
|
584
|
-
|
|
585
|
-
# Get JSON output
|
|
586
|
-
thailint dry --format json src/
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
### Configuration
|
|
590
|
-
|
|
591
|
-
Add to `.thailint.yaml`:
|
|
592
|
-
|
|
593
|
-
```yaml
|
|
594
|
-
dry:
|
|
595
|
-
enabled: true
|
|
596
|
-
min_duplicate_lines: 4 # Minimum lines to consider duplicate
|
|
597
|
-
min_duplicate_tokens: 30 # Minimum tokens to consider duplicate
|
|
598
|
-
min_occurrences: 2 # Report if appears 2+ times
|
|
599
|
-
|
|
600
|
-
# Language-specific thresholds
|
|
601
|
-
python:
|
|
602
|
-
min_occurrences: 3 # Python: require 3+ occurrences
|
|
603
|
-
typescript:
|
|
604
|
-
min_occurrences: 3 # TypeScript: require 3+ occurrences
|
|
605
|
-
|
|
606
|
-
# Storage settings
|
|
607
|
-
storage_mode: "memory" # Options: "memory" (default) or "tempfile"
|
|
608
|
-
|
|
609
|
-
# Ignore patterns
|
|
610
|
-
ignore:
|
|
611
|
-
- "tests/" # Test code often has acceptable duplication
|
|
612
|
-
- "__init__.py" # Import-only files exempt
|
|
613
|
-
|
|
614
|
-
# False positive filters
|
|
615
|
-
filters:
|
|
616
|
-
keyword_argument_filter: true # Filter function call kwargs
|
|
617
|
-
import_group_filter: true # Filter import groups
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
### How It Works
|
|
621
|
-
|
|
622
|
-
**Token-Based Detection:**
|
|
623
|
-
1. Parse code into tokens (stripping comments, normalizing whitespace)
|
|
624
|
-
2. Create rolling hash windows of N lines
|
|
625
|
-
3. Store hashes in SQLite database with file locations
|
|
626
|
-
4. Query for hashes appearing 2+ times across project
|
|
627
|
-
|
|
628
|
-
**SQLite Storage:**
|
|
629
|
-
- In-memory mode (default): Stores in RAM for best performance
|
|
630
|
-
- Tempfile mode: Stores in temporary disk file for large projects
|
|
631
|
-
- Fresh analysis on every run (no persistence between runs)
|
|
632
|
-
- Fast duplicate detection using B-tree indexes
|
|
633
|
-
|
|
634
|
-
### Example Violation
|
|
635
|
-
|
|
636
|
-
**Code with duplication:**
|
|
637
|
-
```python
|
|
638
|
-
# src/auth.py
|
|
639
|
-
def validate_user(user_data):
|
|
640
|
-
if not user_data:
|
|
641
|
-
return False
|
|
642
|
-
if not user_data.get('email'):
|
|
643
|
-
return False
|
|
644
|
-
if not user_data.get('password'):
|
|
645
|
-
return False
|
|
646
|
-
return True
|
|
647
|
-
|
|
648
|
-
# src/admin.py
|
|
649
|
-
def validate_admin(admin_data):
|
|
650
|
-
if not admin_data:
|
|
651
|
-
return False
|
|
652
|
-
if not admin_data.get('email'):
|
|
653
|
-
return False
|
|
654
|
-
if not admin_data.get('password'):
|
|
655
|
-
return False
|
|
656
|
-
return True
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
**Violation message:**
|
|
660
|
-
```
|
|
661
|
-
src/auth.py:3 - Duplicate code detected (4 lines, 2 occurrences)
|
|
662
|
-
Locations:
|
|
663
|
-
- src/auth.py:3-6
|
|
664
|
-
- src/admin.py:3-6
|
|
665
|
-
Consider extracting to shared function
|
|
666
|
-
```
|
|
667
|
-
|
|
668
|
-
**Refactored (DRY):**
|
|
669
|
-
```python
|
|
670
|
-
# src/validators.py
|
|
671
|
-
def validate_credentials(data):
|
|
672
|
-
if not data:
|
|
673
|
-
return False
|
|
674
|
-
if not data.get('email'):
|
|
675
|
-
return False
|
|
676
|
-
if not data.get('password'):
|
|
677
|
-
return False
|
|
678
|
-
return True
|
|
679
|
-
|
|
680
|
-
# src/auth.py & src/admin.py
|
|
681
|
-
from src.validators import validate_credentials
|
|
682
|
-
|
|
683
|
-
def validate_user(user_data):
|
|
684
|
-
return validate_credentials(user_data)
|
|
685
|
-
|
|
686
|
-
def validate_admin(admin_data):
|
|
687
|
-
return validate_credentials(admin_data)
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
### Performance
|
|
691
|
-
|
|
692
|
-
| Operation | Performance | Storage Mode |
|
|
693
|
-
|-----------|-------------|--------------|
|
|
694
|
-
| Scan (1000 files) | 1-3s | Memory (default) |
|
|
695
|
-
| Large project (5000+ files) | Use tempfile mode | Tempfile |
|
|
696
|
-
|
|
697
|
-
**Note**: Every run analyzes files fresh - no persistence between runs ensures accurate results
|
|
698
|
-
|
|
699
|
-
### Language Support
|
|
700
|
-
|
|
701
|
-
- **Python**: Full support with AST-based tokenization
|
|
702
|
-
- **TypeScript**: Full support with tree-sitter parsing
|
|
703
|
-
- **JavaScript**: Supported via TypeScript parser
|
|
704
|
-
|
|
705
|
-
### False Positive Filtering
|
|
706
|
-
|
|
707
|
-
Built-in filters automatically exclude common non-duplication patterns:
|
|
708
|
-
- **keyword_argument_filter**: Excludes function calls with keyword arguments
|
|
709
|
-
- **import_group_filter**: Excludes import statement groups
|
|
710
|
-
|
|
711
|
-
### Refactoring Patterns
|
|
712
|
-
|
|
713
|
-
1. **Extract Function**: Move repeated logic to shared function
|
|
714
|
-
2. **Extract Base Class**: Create base class for similar implementations
|
|
715
|
-
3. **Extract Utility Module**: Move helper functions to shared utilities
|
|
716
|
-
4. **Template Method**: Use function parameters for variations
|
|
717
|
-
|
|
718
|
-
See [DRY Linter Guide](https://thai-lint.readthedocs.io/en/latest/dry-linter/) for comprehensive documentation, storage modes, and refactoring patterns.
|
|
719
|
-
|
|
720
|
-
## Magic Numbers Linter
|
|
721
|
-
|
|
722
|
-
### Overview
|
|
723
|
-
|
|
724
|
-
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.
|
|
725
|
-
|
|
726
|
-
### What are Magic Numbers?
|
|
727
|
-
|
|
728
|
-
**Magic numbers** are unnamed numeric literals in code without explanation:
|
|
729
|
-
|
|
730
|
-
```python
|
|
731
|
-
# Bad - Magic numbers
|
|
732
|
-
timeout = 3600 # What is 3600?
|
|
733
|
-
max_retries = 5 # Why 5?
|
|
734
|
-
|
|
735
|
-
# Good - Named constants
|
|
736
|
-
TIMEOUT_SECONDS = 3600
|
|
737
|
-
MAX_RETRY_ATTEMPTS = 5
|
|
738
|
-
```
|
|
739
|
-
|
|
740
|
-
### Quick Start
|
|
741
|
-
|
|
742
|
-
```bash
|
|
743
|
-
# Check for magic numbers in current directory
|
|
744
|
-
thailint magic-numbers .
|
|
745
|
-
|
|
746
|
-
# Check specific directory
|
|
747
|
-
thailint magic-numbers src/
|
|
748
|
-
|
|
749
|
-
# Get JSON output
|
|
750
|
-
thailint magic-numbers --format json src/
|
|
751
|
-
```
|
|
752
|
-
|
|
753
|
-
### Configuration
|
|
754
|
-
|
|
755
|
-
Add to `.thailint.yaml`:
|
|
756
|
-
|
|
757
|
-
```yaml
|
|
758
|
-
magic-numbers:
|
|
759
|
-
enabled: true
|
|
760
|
-
allowed_numbers: [-1, 0, 1, 2, 10, 100, 1000]
|
|
761
|
-
max_small_integer: 10 # Max for range() to be acceptable
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
### Example Violation
|
|
765
|
-
|
|
766
|
-
**Code with magic numbers:**
|
|
767
|
-
```python
|
|
768
|
-
def calculate_timeout():
|
|
769
|
-
return 3600 # Magic number - what is 3600?
|
|
770
|
-
|
|
771
|
-
def process_items(items):
|
|
772
|
-
for i in range(100): # Magic number - why 100?
|
|
773
|
-
items[i] *= 1.5 # Magic number - what is 1.5?
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
**Violation messages:**
|
|
777
|
-
```
|
|
778
|
-
src/example.py:2 - Magic number 3600 should be a named constant
|
|
779
|
-
src/example.py:5 - Magic number 100 should be a named constant
|
|
780
|
-
src/example.py:6 - Magic number 1.5 should be a named constant
|
|
781
|
-
```
|
|
782
|
-
|
|
783
|
-
**Refactored code:**
|
|
784
|
-
```python
|
|
785
|
-
TIMEOUT_SECONDS = 3600
|
|
786
|
-
MAX_ITEMS = 100
|
|
787
|
-
PRICE_MULTIPLIER = 1.5
|
|
788
|
-
|
|
789
|
-
def calculate_timeout():
|
|
790
|
-
return TIMEOUT_SECONDS
|
|
791
|
-
|
|
792
|
-
def process_items(items):
|
|
793
|
-
for i in range(MAX_ITEMS):
|
|
794
|
-
items[i] *= PRICE_MULTIPLIER
|
|
795
|
-
```
|
|
796
|
-
|
|
797
|
-
### Acceptable Contexts
|
|
798
|
-
|
|
799
|
-
The linter **does not** flag numbers in these contexts:
|
|
800
|
-
|
|
801
|
-
| Context | Example | Why Acceptable |
|
|
802
|
-
|---------|---------|----------------|
|
|
803
|
-
| Constants | `MAX_SIZE = 100` | UPPERCASE name provides context |
|
|
804
|
-
| Small `range()` | `range(5)` | Small loop bounds are clear |
|
|
805
|
-
| Test files | `test_*.py` | Test data can be literal |
|
|
806
|
-
| Allowed numbers | `-1, 0, 1, 2, 10` | Common values are self-explanatory |
|
|
807
|
-
|
|
808
|
-
### Refactoring Patterns
|
|
809
|
-
|
|
810
|
-
**Pattern 1: Extract to Module Constants**
|
|
811
|
-
```python
|
|
812
|
-
# Before
|
|
813
|
-
def connect():
|
|
814
|
-
timeout = 30
|
|
815
|
-
retries = 3
|
|
816
|
-
|
|
817
|
-
# After
|
|
818
|
-
DEFAULT_TIMEOUT_SECONDS = 30
|
|
819
|
-
DEFAULT_MAX_RETRIES = 3
|
|
820
|
-
|
|
821
|
-
def connect():
|
|
822
|
-
timeout = DEFAULT_TIMEOUT_SECONDS
|
|
823
|
-
retries = DEFAULT_MAX_RETRIES
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
**Pattern 2: Extract with Units in Name**
|
|
827
|
-
```python
|
|
828
|
-
# Before
|
|
829
|
-
delay = 3600 # Is this seconds? Minutes?
|
|
830
|
-
|
|
831
|
-
# After
|
|
832
|
-
TASK_DELAY_SECONDS = 3600 # Clear unit
|
|
833
|
-
|
|
834
|
-
delay = TASK_DELAY_SECONDS
|
|
835
|
-
```
|
|
836
|
-
|
|
837
|
-
**Pattern 3: Use Standard Library**
|
|
838
|
-
```python
|
|
839
|
-
# Before
|
|
840
|
-
if status == 200:
|
|
841
|
-
return "success"
|
|
842
|
-
|
|
843
|
-
# After
|
|
844
|
-
from http import HTTPStatus
|
|
845
|
-
|
|
846
|
-
if status == HTTPStatus.OK:
|
|
847
|
-
return "success"
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
### Language Support
|
|
851
|
-
|
|
852
|
-
- **Python**: Full support (int, float, scientific notation)
|
|
853
|
-
- **TypeScript**: Full support (int, float, scientific notation)
|
|
854
|
-
- **JavaScript**: Supported via TypeScript parser
|
|
855
|
-
|
|
856
|
-
### Ignoring Violations
|
|
857
|
-
|
|
858
|
-
```python
|
|
859
|
-
# Line-level ignore
|
|
860
|
-
timeout = 3600 # thailint: ignore[magic-numbers] - Industry standard
|
|
861
|
-
|
|
862
|
-
# Method-level ignore
|
|
863
|
-
def get_ports(): # thailint: ignore[magic-numbers] - Standard ports
|
|
864
|
-
return {80: "HTTP", 443: "HTTPS"}
|
|
865
|
-
|
|
866
|
-
# File-level ignore
|
|
867
|
-
# thailint: ignore-file[magic-numbers]
|
|
868
|
-
```
|
|
869
|
-
|
|
870
|
-
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.
|
|
871
|
-
|
|
872
|
-
## Pre-commit Hooks
|
|
873
|
-
|
|
874
|
-
Automate code quality checks before every commit and push with pre-commit hooks.
|
|
875
|
-
|
|
876
|
-
### Quick Setup
|
|
877
|
-
|
|
878
|
-
```bash
|
|
879
|
-
# 1. Install pre-commit framework
|
|
880
|
-
pip install pre-commit
|
|
881
|
-
|
|
882
|
-
# 2. Install git hooks
|
|
883
|
-
pre-commit install
|
|
884
|
-
pre-commit install --hook-type pre-push
|
|
885
|
-
|
|
886
|
-
# 3. Test it works
|
|
887
|
-
pre-commit run --all-files
|
|
888
|
-
```
|
|
889
|
-
|
|
890
|
-
### What You Get
|
|
891
|
-
|
|
892
|
-
**On every commit:**
|
|
893
|
-
- Prevents commits to main/master branch
|
|
894
|
-
- Auto-fixes formatting issues
|
|
895
|
-
- Runs thailint on changed files (fast, uses pass_filenames: true)
|
|
896
|
-
|
|
897
|
-
**On every push:**
|
|
898
|
-
- Full linting on entire codebase
|
|
899
|
-
- Runs complete test suite
|
|
900
|
-
|
|
901
|
-
### Example Configuration
|
|
902
|
-
|
|
903
|
-
```yaml
|
|
904
|
-
# .pre-commit-config.yaml
|
|
905
|
-
repos:
|
|
906
|
-
- repo: local
|
|
907
|
-
hooks:
|
|
908
|
-
# Prevent commits to protected branches
|
|
909
|
-
- id: no-commit-to-main
|
|
910
|
-
name: Prevent commits to main branch
|
|
911
|
-
entry: bash -c 'branch=$(git rev-parse --abbrev-ref HEAD); if [ "$branch" = "main" ]; then echo "ERROR: Use a feature branch!"; exit 1; fi'
|
|
912
|
-
language: system
|
|
913
|
-
pass_filenames: false
|
|
914
|
-
always_run: true
|
|
915
|
-
|
|
916
|
-
# Auto-format code
|
|
917
|
-
- id: format
|
|
918
|
-
name: Auto-fix formatting
|
|
919
|
-
entry: just format
|
|
920
|
-
language: system
|
|
921
|
-
pass_filenames: false
|
|
922
|
-
|
|
923
|
-
# Run thailint on changed files (passes filenames directly)
|
|
924
|
-
- id: thailint-changed
|
|
925
|
-
name: Lint changed files
|
|
926
|
-
entry: thailint nesting
|
|
927
|
-
language: system
|
|
928
|
-
files: \.(py|ts|tsx|js|jsx)$
|
|
929
|
-
pass_filenames: true
|
|
930
|
-
```
|
|
931
|
-
|
|
932
|
-
See **[Pre-commit Hooks Guide](https://thai-lint.readthedocs.io/en/latest/pre-commit-hooks/)** for complete documentation, troubleshooting, and advanced configuration.
|
|
933
|
-
|
|
934
|
-
## Common Use Cases
|
|
935
|
-
|
|
936
|
-
### CI/CD Integration
|
|
937
|
-
|
|
938
|
-
```yaml
|
|
939
|
-
# GitHub Actions example
|
|
940
|
-
name: Lint
|
|
941
|
-
|
|
942
|
-
on: [push, pull_request]
|
|
943
|
-
|
|
944
|
-
jobs:
|
|
945
|
-
lint:
|
|
946
|
-
runs-on: ubuntu-latest
|
|
947
|
-
steps:
|
|
948
|
-
- uses: actions/checkout@v3
|
|
949
|
-
- name: Install thailint
|
|
950
|
-
run: pip install thailint
|
|
951
|
-
- name: Run file placement linter
|
|
952
|
-
run: thailint file-placement .
|
|
953
|
-
- name: Run nesting linter
|
|
954
|
-
run: thailint nesting src/ --config .thailint.yaml
|
|
955
|
-
```
|
|
956
|
-
|
|
957
|
-
### Editor Integration
|
|
958
|
-
|
|
959
|
-
```python
|
|
960
|
-
# VS Code extension example
|
|
961
|
-
from src import Linter
|
|
962
|
-
|
|
963
|
-
linter = Linter(config_file='.thailint.yaml')
|
|
964
|
-
violations = linter.lint(file_path)
|
|
965
|
-
```
|
|
966
|
-
|
|
967
|
-
### Test Suite
|
|
968
|
-
|
|
969
|
-
```python
|
|
970
|
-
# pytest integration
|
|
971
|
-
import pytest
|
|
972
|
-
from src import Linter
|
|
973
|
-
|
|
974
|
-
def test_no_violations():
|
|
975
|
-
linter = Linter()
|
|
976
|
-
violations = linter.lint('src/')
|
|
977
|
-
assert len(violations) == 0
|
|
978
|
-
```
|
|
979
|
-
|
|
980
|
-
## Development
|
|
981
|
-
|
|
982
|
-
### Setup Development Environment
|
|
983
|
-
|
|
984
|
-
```bash
|
|
985
|
-
# Install dependencies and activate virtualenv
|
|
986
|
-
just init
|
|
987
|
-
|
|
988
|
-
# Or manually:
|
|
989
|
-
poetry install
|
|
990
|
-
source $(poetry env info --path)/bin/activate
|
|
991
|
-
```
|
|
992
|
-
|
|
993
|
-
### Running Tests
|
|
994
|
-
|
|
995
|
-
```bash
|
|
996
|
-
# Run all tests (parallel mode - fast)
|
|
997
|
-
just test
|
|
998
|
-
|
|
999
|
-
# Run with coverage (serial mode)
|
|
1000
|
-
just test-coverage
|
|
1001
|
-
|
|
1002
|
-
# Run specific test
|
|
1003
|
-
poetry run pytest tests/test_cli.py::test_hello_command -v
|
|
1004
|
-
```
|
|
1005
|
-
|
|
1006
|
-
### Code Quality
|
|
1007
|
-
|
|
1008
|
-
```bash
|
|
1009
|
-
# Fast linting (Ruff only - use during development)
|
|
1010
|
-
just lint
|
|
1011
|
-
|
|
1012
|
-
# Comprehensive linting (Ruff + Pylint + Flake8 + MyPy)
|
|
1013
|
-
just lint-all
|
|
1014
|
-
|
|
1015
|
-
# Security scanning
|
|
1016
|
-
just lint-security
|
|
1017
|
-
|
|
1018
|
-
# Complexity analysis (Radon + Xenon + Nesting)
|
|
1019
|
-
just lint-complexity
|
|
1020
|
-
|
|
1021
|
-
# SOLID principles (SRP)
|
|
1022
|
-
just lint-solid
|
|
1023
|
-
|
|
1024
|
-
# DRY principles (duplicate code detection)
|
|
1025
|
-
just lint-dry
|
|
1026
|
-
|
|
1027
|
-
# ALL quality checks (runs everything)
|
|
1028
|
-
just lint-full
|
|
1029
|
-
|
|
1030
|
-
# Auto-fix formatting issues
|
|
1031
|
-
just format
|
|
1032
|
-
```
|
|
1033
|
-
|
|
1034
|
-
### Dogfooding (Lint Our Own Code)
|
|
1035
|
-
|
|
1036
|
-
```bash
|
|
1037
|
-
# Lint file placement
|
|
1038
|
-
just lint-placement
|
|
1039
|
-
|
|
1040
|
-
# Check nesting depth
|
|
1041
|
-
just lint-nesting
|
|
1042
|
-
|
|
1043
|
-
# Check for magic numbers
|
|
1044
|
-
poetry run thai-lint magic-numbers src/
|
|
1045
|
-
```
|
|
1046
|
-
|
|
1047
|
-
### Building and Publishing
|
|
1048
|
-
|
|
1049
|
-
```bash
|
|
1050
|
-
# Build Python package
|
|
1051
|
-
poetry build
|
|
1052
|
-
|
|
1053
|
-
# Build Docker image locally
|
|
1054
|
-
docker build -t washad/thailint:latest .
|
|
1055
|
-
|
|
1056
|
-
# Publish to PyPI and Docker Hub (runs tests + linting + version bump)
|
|
1057
|
-
just publish
|
|
1058
|
-
```
|
|
1059
|
-
|
|
1060
|
-
### Quick Development Workflows
|
|
1061
|
-
|
|
1062
|
-
```bash
|
|
1063
|
-
# Make changes, then run quality checks
|
|
1064
|
-
just lint-full
|
|
1065
|
-
|
|
1066
|
-
# Share changes for collaboration (skips hooks)
|
|
1067
|
-
just share "WIP: feature description"
|
|
1068
|
-
|
|
1069
|
-
# Clean up cache and artifacts
|
|
1070
|
-
just clean
|
|
1071
|
-
```
|
|
1072
|
-
|
|
1073
|
-
See `just --list` or `just help` for all available commands.
|
|
1074
|
-
|
|
1075
|
-
## Docker Usage
|
|
1076
|
-
|
|
1077
|
-
### Basic Docker Commands
|
|
1078
|
-
|
|
1079
|
-
```bash
|
|
1080
|
-
# Pull published image
|
|
1081
|
-
docker pull washad/thailint:latest
|
|
1082
|
-
|
|
1083
|
-
# Run CLI help
|
|
1084
|
-
docker run --rm washad/thailint:latest --help
|
|
1085
|
-
|
|
1086
|
-
# Lint entire directory (recommended)
|
|
1087
|
-
docker run --rm -v $(pwd):/data washad/thailint:latest file-placement /data
|
|
1088
|
-
|
|
1089
|
-
# Lint single file
|
|
1090
|
-
docker run --rm -v $(pwd):/data washad/thailint:latest file-placement /data/src/app.py
|
|
1091
|
-
|
|
1092
|
-
# Lint multiple specific files
|
|
1093
|
-
docker run --rm -v $(pwd):/data washad/thailint:latest nesting /data/src/file1.py /data/src/file2.py
|
|
1094
|
-
|
|
1095
|
-
# Lint specific subdirectory
|
|
1096
|
-
docker run --rm -v $(pwd):/data washad/thailint:latest nesting /data/src
|
|
1097
|
-
|
|
1098
|
-
# With custom config
|
|
1099
|
-
docker run --rm -v $(pwd):/data \
|
|
1100
|
-
washad/thailint:latest nesting --config /data/.thailint.yaml /data
|
|
1101
|
-
|
|
1102
|
-
# JSON output for CI/CD
|
|
1103
|
-
docker run --rm -v $(pwd):/data \
|
|
1104
|
-
washad/thailint:latest file-placement --format json /data
|
|
1105
|
-
```
|
|
1106
|
-
|
|
1107
|
-
### Docker with Sibling Directories (Advanced)
|
|
1108
|
-
|
|
1109
|
-
For complex Docker setups with sibling directories, use `--project-root` for explicit control:
|
|
1110
|
-
|
|
1111
|
-
```bash
|
|
1112
|
-
# Scenario: Monorepo with separate config and code directories
|
|
1113
|
-
# Directory structure:
|
|
1114
|
-
# /workspace/
|
|
1115
|
-
# ├── config/ # Contains .thailint.yaml
|
|
1116
|
-
# ├── backend/app/ # Python backend code
|
|
1117
|
-
# ├── frontend/ # TypeScript frontend
|
|
1118
|
-
# └── tools/ # Build tools
|
|
1119
|
-
|
|
1120
|
-
# Explicit project root (recommended for Docker)
|
|
1121
|
-
docker run --rm -v /path/to/workspace:/workspace \
|
|
1122
|
-
washad/thailint:latest \
|
|
1123
|
-
--project-root /workspace/config \
|
|
1124
|
-
magic-numbers /workspace/backend/
|
|
1125
|
-
|
|
1126
|
-
# Config path inference (automatic - no --project-root needed)
|
|
1127
|
-
docker run --rm -v /path/to/workspace:/workspace \
|
|
1128
|
-
washad/thailint:latest \
|
|
1129
|
-
--config /workspace/config/.thailint.yaml \
|
|
1130
|
-
magic-numbers /workspace/backend/
|
|
1131
|
-
|
|
1132
|
-
# Lint multiple sibling directories with shared config
|
|
1133
|
-
docker run --rm -v /path/to/workspace:/workspace \
|
|
1134
|
-
washad/thailint:latest \
|
|
1135
|
-
--project-root /workspace/config \
|
|
1136
|
-
nesting /workspace/backend/ /workspace/frontend/
|
|
1137
|
-
```
|
|
1138
|
-
|
|
1139
|
-
**When to use `--project-root` in Docker:**
|
|
1140
|
-
- **Sibling directory structures** - When config/code aren't nested
|
|
1141
|
-
- **Monorepos** - Multiple projects sharing one config
|
|
1142
|
-
- **CI/CD** - Explicit paths prevent auto-detection issues
|
|
1143
|
-
- **Ignore patterns** - Ensures patterns resolve from correct base directory
|
|
1144
|
-
|
|
1145
|
-
## Documentation
|
|
1146
|
-
|
|
1147
|
-
### Comprehensive Guides
|
|
1148
|
-
|
|
1149
|
-
- **[Getting Started](https://thai-lint.readthedocs.io/en/latest/getting-started/)** - Installation, first lint, basic config
|
|
1150
|
-
- **[Configuration Reference](https://thai-lint.readthedocs.io/en/latest/configuration/)** - Complete config options (YAML/JSON)
|
|
1151
|
-
- **[How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/)** - Complete guide to all ignore levels
|
|
1152
|
-
- **[API Reference](https://thai-lint.readthedocs.io/en/latest/api-reference/)** - Library API documentation
|
|
1153
|
-
- **[CLI Reference](https://thai-lint.readthedocs.io/en/latest/cli-reference/)** - All CLI commands and options
|
|
1154
|
-
- **[Deployment Modes](https://thai-lint.readthedocs.io/en/latest/deployment-modes/)** - CLI, Library, and Docker usage
|
|
1155
|
-
- **[File Placement Linter](https://thai-lint.readthedocs.io/en/latest/file-placement-linter/)** - Detailed linter guide
|
|
1156
|
-
- **[Magic Numbers Linter](https://thai-lint.readthedocs.io/en/latest/magic-numbers-linter/)** - Magic numbers detection guide
|
|
1157
|
-
- **[Nesting Depth Linter](https://thai-lint.readthedocs.io/en/latest/nesting-linter/)** - Nesting depth analysis guide
|
|
1158
|
-
- **[SRP Linter](https://thai-lint.readthedocs.io/en/latest/srp-linter/)** - Single Responsibility Principle guide
|
|
1159
|
-
- **[DRY Linter](https://thai-lint.readthedocs.io/en/latest/dry-linter/)** - Duplicate code detection guide
|
|
1160
|
-
- **[Pre-commit Hooks](https://thai-lint.readthedocs.io/en/latest/pre-commit-hooks/)** - Automated quality checks
|
|
1161
|
-
- **[Publishing Guide](https://thai-lint.readthedocs.io/en/latest/releasing/)** - Release and publishing workflow
|
|
1162
|
-
- **[Publishing Checklist](https://thai-lint.readthedocs.io/en/latest/publishing-checklist/)** - Post-publication validation
|
|
1163
|
-
|
|
1164
|
-
### Examples
|
|
1165
|
-
|
|
1166
|
-
See [`examples/`](examples/) directory for working code:
|
|
1167
|
-
|
|
1168
|
-
- **[basic_usage.py](examples/basic_usage.py)** - Simple library API usage
|
|
1169
|
-
- **[advanced_usage.py](examples/advanced_usage.py)** - Advanced patterns and workflows
|
|
1170
|
-
- **[ci_integration.py](examples/ci_integration.py)** - CI/CD integration example
|
|
1171
|
-
|
|
1172
|
-
## Project Structure
|
|
1173
|
-
|
|
1174
|
-
```
|
|
1175
|
-
thai-lint/
|
|
1176
|
-
├── src/ # Application source code
|
|
1177
|
-
│ ├── api.py # High-level Library API
|
|
1178
|
-
│ ├── cli.py # CLI commands
|
|
1179
|
-
│ ├── core/ # Core abstractions
|
|
1180
|
-
│ │ ├── base.py # Base linter interfaces
|
|
1181
|
-
│ │ ├── registry.py # Rule registry
|
|
1182
|
-
│ │ └── types.py # Core types (Violation, Severity)
|
|
1183
|
-
│ ├── linters/ # Linter implementations
|
|
1184
|
-
│ │ └── file_placement/ # File placement linter
|
|
1185
|
-
│ ├── linter_config/ # Configuration system
|
|
1186
|
-
│ │ ├── loader.py # Config loader (YAML/JSON)
|
|
1187
|
-
│ │ └── ignore.py # Ignore directives
|
|
1188
|
-
│ └── orchestrator/ # Multi-language orchestrator
|
|
1189
|
-
│ ├── core.py # Main orchestrator
|
|
1190
|
-
│ └── language_detector.py
|
|
1191
|
-
├── tests/ # Test suite (221 tests, 87% coverage)
|
|
1192
|
-
│ ├── unit/ # Unit tests
|
|
1193
|
-
│ ├── integration/ # Integration tests
|
|
1194
|
-
│ └── conftest.py # Pytest fixtures
|
|
1195
|
-
├── docs/ # Documentation
|
|
1196
|
-
│ ├── getting-started.md
|
|
1197
|
-
│ ├── configuration.md
|
|
1198
|
-
│ ├── api-reference.md
|
|
1199
|
-
│ ├── cli-reference.md
|
|
1200
|
-
│ ├── deployment-modes.md
|
|
1201
|
-
│ └── file-placement-linter.md
|
|
1202
|
-
├── examples/ # Working examples
|
|
1203
|
-
│ ├── basic_usage.py
|
|
1204
|
-
│ ├── advanced_usage.py
|
|
1205
|
-
│ └── ci_integration.py
|
|
1206
|
-
├── .ai/ # AI agent documentation
|
|
1207
|
-
├── Dockerfile # Multi-stage Docker build
|
|
1208
|
-
├── docker-compose.yml # Docker orchestration
|
|
1209
|
-
└── pyproject.toml # Project configuration
|
|
1210
|
-
```
|
|
1211
|
-
|
|
1212
|
-
## Contributing
|
|
1213
|
-
|
|
1214
|
-
Contributions are welcome! Please follow these steps:
|
|
1215
|
-
|
|
1216
|
-
1. Fork the repository
|
|
1217
|
-
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
1218
|
-
3. Make your changes
|
|
1219
|
-
4. Run tests and linting
|
|
1220
|
-
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
1221
|
-
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
1222
|
-
7. Open a Pull Request
|
|
1223
|
-
|
|
1224
|
-
### Development Guidelines
|
|
1225
|
-
|
|
1226
|
-
- Write tests for new features
|
|
1227
|
-
- Follow existing code style (enforced by Ruff)
|
|
1228
|
-
- Add type hints to all functions
|
|
1229
|
-
- Update documentation for user-facing changes
|
|
1230
|
-
- Run `pytest` and `ruff check` before committing
|
|
1231
|
-
|
|
1232
|
-
## Performance
|
|
1233
|
-
|
|
1234
|
-
thailint is designed for speed and efficiency:
|
|
1235
|
-
|
|
1236
|
-
| Operation | Performance | Target |
|
|
1237
|
-
|-----------|-------------|--------|
|
|
1238
|
-
| Single file lint | ~20ms | <100ms |
|
|
1239
|
-
| 100 files | ~300ms | <1s |
|
|
1240
|
-
| 1000 files | ~900ms | <5s |
|
|
1241
|
-
| Config loading | ~10ms | <100ms |
|
|
1242
|
-
|
|
1243
|
-
*Performance benchmarks run on standard hardware, your results may vary.*
|
|
1244
|
-
|
|
1245
|
-
## Exit Codes
|
|
1246
|
-
|
|
1247
|
-
thailint uses standard exit codes for CI/CD integration:
|
|
1248
|
-
|
|
1249
|
-
- **0** - Success (no violations)
|
|
1250
|
-
- **1** - Violations found
|
|
1251
|
-
- **2** - Error occurred (invalid config, file not found, etc.)
|
|
1252
|
-
|
|
1253
|
-
```bash
|
|
1254
|
-
thailint file-placement .
|
|
1255
|
-
if [ $? -eq 0 ]; then
|
|
1256
|
-
echo "Linting passed"
|
|
1257
|
-
else
|
|
1258
|
-
echo "Linting failed"
|
|
1259
|
-
fi
|
|
1260
|
-
```
|
|
1261
|
-
|
|
1262
|
-
## Architecture
|
|
1263
|
-
|
|
1264
|
-
See [`.ai/docs/`](.ai/docs/) for detailed architecture documentation and [`.ai/howtos/`](.ai/howtos/) for development guides.
|
|
1265
|
-
|
|
1266
|
-
## License
|
|
1267
|
-
|
|
1268
|
-
MIT License - see LICENSE file for details.
|
|
1269
|
-
|
|
1270
|
-
## Support
|
|
1271
|
-
|
|
1272
|
-
- **Issues**: https://github.com/be-wise-be-kind/thai-lint/issues
|
|
1273
|
-
- **Documentation**: `.ai/docs/` and `.ai/howtos/`
|
|
1274
|
-
|
|
1275
|
-
## Acknowledgments
|
|
1276
|
-
|
|
1277
|
-
Built with:
|
|
1278
|
-
- [Click](https://click.palletsprojects.com/) - CLI framework
|
|
1279
|
-
- [pytest](https://pytest.org/) - Testing framework
|
|
1280
|
-
- [Ruff](https://docs.astral.sh/ruff/) - Linting and formatting
|
|
1281
|
-
- [Docker](https://www.docker.com/) - Containerization
|
|
1282
|
-
|
|
1283
|
-
## Changelog
|
|
1284
|
-
|
|
1285
|
-
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
1286
|
-
|