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