thailint 0.2.1__tar.gz → 0.3.1__tar.gz
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.
- {thailint-0.2.1 → thailint-0.3.1}/PKG-INFO +196 -42
- {thailint-0.2.1 → thailint-0.3.1}/README.md +195 -41
- {thailint-0.2.1 → thailint-0.3.1}/pyproject.toml +6 -1
- {thailint-0.2.1 → thailint-0.3.1}/src/cli.py +101 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/config.py +6 -2
- {thailint-0.2.1 → thailint-0.3.1}/src/core/base.py +90 -5
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/block_filter.py +5 -2
- thailint-0.3.1/src/linters/dry/cache.py +172 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/config.py +17 -13
- thailint-0.3.1/src/linters/dry/duplicate_storage.py +63 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/file_analyzer.py +11 -48
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/linter.py +5 -12
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/python_analyzer.py +12 -1
- thailint-0.3.1/src/linters/dry/storage_initializer.py +42 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/violation_filter.py +4 -1
- thailint-0.3.1/src/linters/magic_numbers/__init__.py +48 -0
- thailint-0.3.1/src/linters/magic_numbers/config.py +71 -0
- thailint-0.3.1/src/linters/magic_numbers/context_analyzer.py +247 -0
- thailint-0.3.1/src/linters/magic_numbers/linter.py +452 -0
- thailint-0.3.1/src/linters/magic_numbers/python_analyzer.py +76 -0
- thailint-0.3.1/src/linters/magic_numbers/typescript_analyzer.py +217 -0
- thailint-0.3.1/src/linters/magic_numbers/violation_builder.py +98 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/__init__.py +6 -2
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/config.py +6 -3
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/linter.py +8 -19
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/__init__.py +3 -3
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/config.py +12 -6
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/linter.py +33 -24
- thailint-0.2.1/src/linters/dry/cache.py +0 -218
- thailint-0.2.1/src/linters/dry/duplicate_storage.py +0 -126
- thailint-0.2.1/src/linters/dry/storage_initializer.py +0 -51
- {thailint-0.2.1 → thailint-0.3.1}/CHANGELOG.md +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/LICENSE +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/__init__.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/analyzers/__init__.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/analyzers/typescript_base.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/api.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/core/__init__.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/core/cli_utils.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/core/config_parser.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/core/linter_utils.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/core/registry.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/core/rule_discovery.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/core/types.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/core/violation_builder.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linter_config/__init__.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linter_config/ignore.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linter_config/loader.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/__init__.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/__init__.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/base_token_analyzer.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/block_grouper.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/cache_query.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/config_loader.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/deduplicator.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/inline_ignore.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/token_hasher.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/typescript_analyzer.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/violation_builder.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/violation_generator.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/__init__.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/config_loader.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/directory_matcher.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/linter.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/path_resolver.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/pattern_matcher.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/pattern_validator.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/rule_checker.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/violation_factory.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/python_analyzer.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/typescript_analyzer.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/typescript_function_extractor.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/violation_builder.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/class_analyzer.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/heuristics.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/metrics_evaluator.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/python_analyzer.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/typescript_analyzer.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/typescript_metrics_calculator.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/violation_builder.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/orchestrator/__init__.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/orchestrator/core.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/orchestrator/language_detector.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/utils/__init__.py +0 -0
- {thailint-0.2.1 → thailint-0.3.1}/src/utils/project_root.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: thailint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: linter,ai,code-quality,static-analysis,file-placement,governance,multi-language,cli,docker,python
|
|
@@ -48,6 +48,11 @@ thailint is a modern, enterprise-ready multi-language linter designed specifical
|
|
|
48
48
|
|
|
49
49
|
### Core Capabilities
|
|
50
50
|
- **File Placement Linting** - Enforce project structure and organization
|
|
51
|
+
- **Magic Numbers Linting** - Detect unnamed numeric literals that should be constants
|
|
52
|
+
- Python and TypeScript support with AST analysis
|
|
53
|
+
- Context-aware detection (ignores constants, test files, range() usage)
|
|
54
|
+
- Configurable allowed numbers and thresholds
|
|
55
|
+
- Helpful suggestions for extracting to named constants
|
|
51
56
|
- **Nesting Depth Linting** - Detect excessive code nesting with AST analysis
|
|
52
57
|
- Python and TypeScript support with tree-sitter
|
|
53
58
|
- Configurable max depth (default: 4, recommended: 3)
|
|
@@ -57,8 +62,8 @@ thailint is a modern, enterprise-ready multi-language linter designed specifical
|
|
|
57
62
|
- Language-specific thresholds (Python, TypeScript, JavaScript)
|
|
58
63
|
- Refactoring patterns from real-world examples
|
|
59
64
|
- **DRY Linting** - Detect duplicate code across projects
|
|
60
|
-
- Token-based hash detection with SQLite
|
|
61
|
-
- Fast
|
|
65
|
+
- Token-based hash detection with SQLite storage
|
|
66
|
+
- Fast duplicate detection (in-memory or disk-backed)
|
|
62
67
|
- Configurable thresholds (lines, tokens, occurrences)
|
|
63
68
|
- Language-specific detection (Python, TypeScript, JavaScript)
|
|
64
69
|
- False positive filtering (keyword args, imports)
|
|
@@ -125,6 +130,9 @@ thailint nesting src/
|
|
|
125
130
|
# Check for duplicate code
|
|
126
131
|
thailint dry .
|
|
127
132
|
|
|
133
|
+
# Check for magic numbers
|
|
134
|
+
thailint magic-numbers src/
|
|
135
|
+
|
|
128
136
|
# With config file
|
|
129
137
|
thailint dry --config .thailint.yaml src/
|
|
130
138
|
|
|
@@ -228,14 +236,19 @@ dry:
|
|
|
228
236
|
python:
|
|
229
237
|
min_occurrences: 3 # Python: require 3+ occurrences
|
|
230
238
|
|
|
231
|
-
#
|
|
232
|
-
|
|
233
|
-
cache_path: ".thailint-cache/dry.db"
|
|
239
|
+
# Storage settings (SQLite)
|
|
240
|
+
storage_mode: "memory" # Options: "memory" (default) or "tempfile"
|
|
234
241
|
|
|
235
242
|
# Ignore patterns
|
|
236
243
|
ignore:
|
|
237
244
|
- "tests/"
|
|
238
245
|
- "__init__.py"
|
|
246
|
+
|
|
247
|
+
# Magic numbers linter configuration
|
|
248
|
+
magic-numbers:
|
|
249
|
+
enabled: true
|
|
250
|
+
allowed_numbers: [-1, 0, 1, 2, 10, 100, 1000] # Numbers allowed without constants
|
|
251
|
+
max_small_integer: 10 # Max value allowed in range() or enumerate()
|
|
239
252
|
```
|
|
240
253
|
|
|
241
254
|
**JSON format also supported** (`.thailint.json`):
|
|
@@ -268,15 +281,21 @@ dry:
|
|
|
268
281
|
"python": {
|
|
269
282
|
"min_occurrences": 3
|
|
270
283
|
},
|
|
271
|
-
"
|
|
272
|
-
"cache_path": ".thailint-cache/dry.db",
|
|
284
|
+
"storage_mode": "memory",
|
|
273
285
|
"ignore": ["tests/", "__init__.py"]
|
|
286
|
+
},
|
|
287
|
+
"magic-numbers": {
|
|
288
|
+
"enabled": true,
|
|
289
|
+
"allowed_numbers": [-1, 0, 1, 2, 10, 100, 1000],
|
|
290
|
+
"max_small_integer": 10
|
|
274
291
|
}
|
|
275
292
|
}
|
|
276
293
|
```
|
|
277
294
|
|
|
278
295
|
See [Configuration Guide](docs/configuration.md) for complete reference.
|
|
279
296
|
|
|
297
|
+
**Need help with ignores?** See **[How to Ignore Violations](docs/how-to-ignore-violations.md)** for complete guide to all ignore levels (line, method, class, file, repository).
|
|
298
|
+
|
|
280
299
|
## Nesting Depth Linter
|
|
281
300
|
|
|
282
301
|
### Overview
|
|
@@ -484,7 +503,7 @@ See [SRP Linter Guide](docs/srp-linter.md) for comprehensive documentation and r
|
|
|
484
503
|
|
|
485
504
|
### Overview
|
|
486
505
|
|
|
487
|
-
The DRY linter detects duplicate code blocks across your entire project using token-based hashing with SQLite
|
|
506
|
+
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.
|
|
488
507
|
|
|
489
508
|
### Quick Start
|
|
490
509
|
|
|
@@ -495,8 +514,8 @@ thailint dry .
|
|
|
495
514
|
# Use custom thresholds
|
|
496
515
|
thailint dry --min-lines 5 src/
|
|
497
516
|
|
|
498
|
-
#
|
|
499
|
-
thailint dry --
|
|
517
|
+
# Use tempfile storage for large projects
|
|
518
|
+
thailint dry --storage-mode tempfile src/
|
|
500
519
|
|
|
501
520
|
# Get JSON output
|
|
502
521
|
thailint dry --format json src/
|
|
@@ -519,10 +538,8 @@ dry:
|
|
|
519
538
|
typescript:
|
|
520
539
|
min_occurrences: 3 # TypeScript: require 3+ occurrences
|
|
521
540
|
|
|
522
|
-
#
|
|
523
|
-
|
|
524
|
-
cache_path: ".thailint-cache/dry.db"
|
|
525
|
-
cache_max_age_days: 30
|
|
541
|
+
# Storage settings
|
|
542
|
+
storage_mode: "memory" # Options: "memory" (default) or "tempfile"
|
|
526
543
|
|
|
527
544
|
# Ignore patterns
|
|
528
545
|
ignore:
|
|
@@ -543,11 +560,11 @@ dry:
|
|
|
543
560
|
3. Store hashes in SQLite database with file locations
|
|
544
561
|
4. Query for hashes appearing 2+ times across project
|
|
545
562
|
|
|
546
|
-
**SQLite
|
|
547
|
-
-
|
|
548
|
-
-
|
|
549
|
-
-
|
|
550
|
-
-
|
|
563
|
+
**SQLite Storage:**
|
|
564
|
+
- In-memory mode (default): Stores in RAM for best performance
|
|
565
|
+
- Tempfile mode: Stores in temporary disk file for large projects
|
|
566
|
+
- Fresh analysis on every run (no persistence between runs)
|
|
567
|
+
- Fast duplicate detection using B-tree indexes
|
|
551
568
|
|
|
552
569
|
### Example Violation
|
|
553
570
|
|
|
@@ -605,31 +622,14 @@ def validate_admin(admin_data):
|
|
|
605
622
|
return validate_credentials(admin_data)
|
|
606
623
|
```
|
|
607
624
|
|
|
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
625
|
### Performance
|
|
625
626
|
|
|
626
|
-
| Operation | Performance |
|
|
627
|
+
| Operation | Performance | Storage Mode |
|
|
627
628
|
|-----------|-------------|--------------|
|
|
628
|
-
|
|
|
629
|
-
|
|
|
630
|
-
| 50 changed files | 0.5-1s | Partial cache |
|
|
629
|
+
| Scan (1000 files) | 1-3s | Memory (default) |
|
|
630
|
+
| Large project (5000+ files) | Use tempfile mode | Tempfile |
|
|
631
631
|
|
|
632
|
-
**
|
|
632
|
+
**Note**: Every run analyzes files fresh - no persistence between runs ensures accurate results
|
|
633
633
|
|
|
634
634
|
### Language Support
|
|
635
635
|
|
|
@@ -650,7 +650,159 @@ Built-in filters automatically exclude common non-duplication patterns:
|
|
|
650
650
|
3. **Extract Utility Module**: Move helper functions to shared utilities
|
|
651
651
|
4. **Template Method**: Use function parameters for variations
|
|
652
652
|
|
|
653
|
-
See [DRY Linter Guide](docs/dry-linter.md) for comprehensive documentation,
|
|
653
|
+
See [DRY Linter Guide](docs/dry-linter.md) for comprehensive documentation, storage modes, and refactoring patterns.
|
|
654
|
+
|
|
655
|
+
## Magic Numbers Linter
|
|
656
|
+
|
|
657
|
+
### Overview
|
|
658
|
+
|
|
659
|
+
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.
|
|
660
|
+
|
|
661
|
+
### What are Magic Numbers?
|
|
662
|
+
|
|
663
|
+
**Magic numbers** are unnamed numeric literals in code without explanation:
|
|
664
|
+
|
|
665
|
+
```python
|
|
666
|
+
# Bad - Magic numbers
|
|
667
|
+
timeout = 3600 # What is 3600?
|
|
668
|
+
max_retries = 5 # Why 5?
|
|
669
|
+
|
|
670
|
+
# Good - Named constants
|
|
671
|
+
TIMEOUT_SECONDS = 3600
|
|
672
|
+
MAX_RETRY_ATTEMPTS = 5
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Quick Start
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
# Check for magic numbers in current directory
|
|
679
|
+
thailint magic-numbers .
|
|
680
|
+
|
|
681
|
+
# Check specific directory
|
|
682
|
+
thailint magic-numbers src/
|
|
683
|
+
|
|
684
|
+
# Get JSON output
|
|
685
|
+
thailint magic-numbers --format json src/
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### Configuration
|
|
689
|
+
|
|
690
|
+
Add to `.thailint.yaml`:
|
|
691
|
+
|
|
692
|
+
```yaml
|
|
693
|
+
magic-numbers:
|
|
694
|
+
enabled: true
|
|
695
|
+
allowed_numbers: [-1, 0, 1, 2, 10, 100, 1000]
|
|
696
|
+
max_small_integer: 10 # Max for range() to be acceptable
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Example Violation
|
|
700
|
+
|
|
701
|
+
**Code with magic numbers:**
|
|
702
|
+
```python
|
|
703
|
+
def calculate_timeout():
|
|
704
|
+
return 3600 # Magic number - what is 3600?
|
|
705
|
+
|
|
706
|
+
def process_items(items):
|
|
707
|
+
for i in range(100): # Magic number - why 100?
|
|
708
|
+
items[i] *= 1.5 # Magic number - what is 1.5?
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**Violation messages:**
|
|
712
|
+
```
|
|
713
|
+
src/example.py:2 - Magic number 3600 should be a named constant
|
|
714
|
+
src/example.py:5 - Magic number 100 should be a named constant
|
|
715
|
+
src/example.py:6 - Magic number 1.5 should be a named constant
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
**Refactored code:**
|
|
719
|
+
```python
|
|
720
|
+
TIMEOUT_SECONDS = 3600
|
|
721
|
+
MAX_ITEMS = 100
|
|
722
|
+
PRICE_MULTIPLIER = 1.5
|
|
723
|
+
|
|
724
|
+
def calculate_timeout():
|
|
725
|
+
return TIMEOUT_SECONDS
|
|
726
|
+
|
|
727
|
+
def process_items(items):
|
|
728
|
+
for i in range(MAX_ITEMS):
|
|
729
|
+
items[i] *= PRICE_MULTIPLIER
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### Acceptable Contexts
|
|
733
|
+
|
|
734
|
+
The linter **does not** flag numbers in these contexts:
|
|
735
|
+
|
|
736
|
+
| Context | Example | Why Acceptable |
|
|
737
|
+
|---------|---------|----------------|
|
|
738
|
+
| Constants | `MAX_SIZE = 100` | UPPERCASE name provides context |
|
|
739
|
+
| Small `range()` | `range(5)` | Small loop bounds are clear |
|
|
740
|
+
| Test files | `test_*.py` | Test data can be literal |
|
|
741
|
+
| Allowed numbers | `-1, 0, 1, 2, 10` | Common values are self-explanatory |
|
|
742
|
+
|
|
743
|
+
### Refactoring Patterns
|
|
744
|
+
|
|
745
|
+
**Pattern 1: Extract to Module Constants**
|
|
746
|
+
```python
|
|
747
|
+
# Before
|
|
748
|
+
def connect():
|
|
749
|
+
timeout = 30
|
|
750
|
+
retries = 3
|
|
751
|
+
|
|
752
|
+
# After
|
|
753
|
+
DEFAULT_TIMEOUT_SECONDS = 30
|
|
754
|
+
DEFAULT_MAX_RETRIES = 3
|
|
755
|
+
|
|
756
|
+
def connect():
|
|
757
|
+
timeout = DEFAULT_TIMEOUT_SECONDS
|
|
758
|
+
retries = DEFAULT_MAX_RETRIES
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
**Pattern 2: Extract with Units in Name**
|
|
762
|
+
```python
|
|
763
|
+
# Before
|
|
764
|
+
delay = 3600 # Is this seconds? Minutes?
|
|
765
|
+
|
|
766
|
+
# After
|
|
767
|
+
TASK_DELAY_SECONDS = 3600 # Clear unit
|
|
768
|
+
|
|
769
|
+
delay = TASK_DELAY_SECONDS
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
**Pattern 3: Use Standard Library**
|
|
773
|
+
```python
|
|
774
|
+
# Before
|
|
775
|
+
if status == 200:
|
|
776
|
+
return "success"
|
|
777
|
+
|
|
778
|
+
# After
|
|
779
|
+
from http import HTTPStatus
|
|
780
|
+
|
|
781
|
+
if status == HTTPStatus.OK:
|
|
782
|
+
return "success"
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
### Language Support
|
|
786
|
+
|
|
787
|
+
- **Python**: Full support (int, float, scientific notation)
|
|
788
|
+
- **TypeScript**: Full support (int, float, scientific notation)
|
|
789
|
+
- **JavaScript**: Supported via TypeScript parser
|
|
790
|
+
|
|
791
|
+
### Ignoring Violations
|
|
792
|
+
|
|
793
|
+
```python
|
|
794
|
+
# Line-level ignore
|
|
795
|
+
timeout = 3600 # thailint: ignore[magic-numbers] - Industry standard
|
|
796
|
+
|
|
797
|
+
# Method-level ignore
|
|
798
|
+
def get_ports(): # thailint: ignore[magic-numbers] - Standard ports
|
|
799
|
+
return {80: "HTTP", 443: "HTTPS"}
|
|
800
|
+
|
|
801
|
+
# File-level ignore
|
|
802
|
+
# thailint: ignore-file[magic-numbers]
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
See **[How to Ignore Violations](docs/how-to-ignore-violations.md)** and **[Magic Numbers Linter Guide](docs/magic-numbers-linter.md)** for complete documentation.
|
|
654
806
|
|
|
655
807
|
## Pre-commit Hooks
|
|
656
808
|
|
|
@@ -844,10 +996,12 @@ docker run --rm -v $(pwd):/data \
|
|
|
844
996
|
|
|
845
997
|
- **[Getting Started](docs/getting-started.md)** - Installation, first lint, basic config
|
|
846
998
|
- **[Configuration Reference](docs/configuration.md)** - Complete config options (YAML/JSON)
|
|
999
|
+
- **[How to Ignore Violations](docs/how-to-ignore-violations.md)** - Complete guide to all ignore levels
|
|
847
1000
|
- **[API Reference](docs/api-reference.md)** - Library API documentation
|
|
848
1001
|
- **[CLI Reference](docs/cli-reference.md)** - All CLI commands and options
|
|
849
1002
|
- **[Deployment Modes](docs/deployment-modes.md)** - CLI, Library, and Docker usage
|
|
850
1003
|
- **[File Placement Linter](docs/file-placement-linter.md)** - Detailed linter guide
|
|
1004
|
+
- **[Magic Numbers Linter](docs/magic-numbers-linter.md)** - Magic numbers detection guide
|
|
851
1005
|
- **[Nesting Depth Linter](docs/nesting-linter.md)** - Nesting depth analysis guide
|
|
852
1006
|
- **[SRP Linter](docs/srp-linter.md)** - Single Responsibility Principle guide
|
|
853
1007
|
- **[DRY Linter](docs/dry-linter.md)** - Duplicate code detection guide
|
|
@@ -15,6 +15,11 @@ thailint is a modern, enterprise-ready multi-language linter designed specifical
|
|
|
15
15
|
|
|
16
16
|
### Core Capabilities
|
|
17
17
|
- **File Placement Linting** - Enforce project structure and organization
|
|
18
|
+
- **Magic Numbers Linting** - Detect unnamed numeric literals that should be constants
|
|
19
|
+
- Python and TypeScript support with AST analysis
|
|
20
|
+
- Context-aware detection (ignores constants, test files, range() usage)
|
|
21
|
+
- Configurable allowed numbers and thresholds
|
|
22
|
+
- Helpful suggestions for extracting to named constants
|
|
18
23
|
- **Nesting Depth Linting** - Detect excessive code nesting with AST analysis
|
|
19
24
|
- Python and TypeScript support with tree-sitter
|
|
20
25
|
- Configurable max depth (default: 4, recommended: 3)
|
|
@@ -24,8 +29,8 @@ thailint is a modern, enterprise-ready multi-language linter designed specifical
|
|
|
24
29
|
- Language-specific thresholds (Python, TypeScript, JavaScript)
|
|
25
30
|
- Refactoring patterns from real-world examples
|
|
26
31
|
- **DRY Linting** - Detect duplicate code across projects
|
|
27
|
-
- Token-based hash detection with SQLite
|
|
28
|
-
- Fast
|
|
32
|
+
- Token-based hash detection with SQLite storage
|
|
33
|
+
- Fast duplicate detection (in-memory or disk-backed)
|
|
29
34
|
- Configurable thresholds (lines, tokens, occurrences)
|
|
30
35
|
- Language-specific detection (Python, TypeScript, JavaScript)
|
|
31
36
|
- False positive filtering (keyword args, imports)
|
|
@@ -92,6 +97,9 @@ thailint nesting src/
|
|
|
92
97
|
# Check for duplicate code
|
|
93
98
|
thailint dry .
|
|
94
99
|
|
|
100
|
+
# Check for magic numbers
|
|
101
|
+
thailint magic-numbers src/
|
|
102
|
+
|
|
95
103
|
# With config file
|
|
96
104
|
thailint dry --config .thailint.yaml src/
|
|
97
105
|
|
|
@@ -195,14 +203,19 @@ dry:
|
|
|
195
203
|
python:
|
|
196
204
|
min_occurrences: 3 # Python: require 3+ occurrences
|
|
197
205
|
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
cache_path: ".thailint-cache/dry.db"
|
|
206
|
+
# Storage settings (SQLite)
|
|
207
|
+
storage_mode: "memory" # Options: "memory" (default) or "tempfile"
|
|
201
208
|
|
|
202
209
|
# Ignore patterns
|
|
203
210
|
ignore:
|
|
204
211
|
- "tests/"
|
|
205
212
|
- "__init__.py"
|
|
213
|
+
|
|
214
|
+
# Magic numbers linter configuration
|
|
215
|
+
magic-numbers:
|
|
216
|
+
enabled: true
|
|
217
|
+
allowed_numbers: [-1, 0, 1, 2, 10, 100, 1000] # Numbers allowed without constants
|
|
218
|
+
max_small_integer: 10 # Max value allowed in range() or enumerate()
|
|
206
219
|
```
|
|
207
220
|
|
|
208
221
|
**JSON format also supported** (`.thailint.json`):
|
|
@@ -235,15 +248,21 @@ dry:
|
|
|
235
248
|
"python": {
|
|
236
249
|
"min_occurrences": 3
|
|
237
250
|
},
|
|
238
|
-
"
|
|
239
|
-
"cache_path": ".thailint-cache/dry.db",
|
|
251
|
+
"storage_mode": "memory",
|
|
240
252
|
"ignore": ["tests/", "__init__.py"]
|
|
253
|
+
},
|
|
254
|
+
"magic-numbers": {
|
|
255
|
+
"enabled": true,
|
|
256
|
+
"allowed_numbers": [-1, 0, 1, 2, 10, 100, 1000],
|
|
257
|
+
"max_small_integer": 10
|
|
241
258
|
}
|
|
242
259
|
}
|
|
243
260
|
```
|
|
244
261
|
|
|
245
262
|
See [Configuration Guide](docs/configuration.md) for complete reference.
|
|
246
263
|
|
|
264
|
+
**Need help with ignores?** See **[How to Ignore Violations](docs/how-to-ignore-violations.md)** for complete guide to all ignore levels (line, method, class, file, repository).
|
|
265
|
+
|
|
247
266
|
## Nesting Depth Linter
|
|
248
267
|
|
|
249
268
|
### Overview
|
|
@@ -451,7 +470,7 @@ See [SRP Linter Guide](docs/srp-linter.md) for comprehensive documentation and r
|
|
|
451
470
|
|
|
452
471
|
### Overview
|
|
453
472
|
|
|
454
|
-
The DRY linter detects duplicate code blocks across your entire project using token-based hashing with SQLite
|
|
473
|
+
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.
|
|
455
474
|
|
|
456
475
|
### Quick Start
|
|
457
476
|
|
|
@@ -462,8 +481,8 @@ thailint dry .
|
|
|
462
481
|
# Use custom thresholds
|
|
463
482
|
thailint dry --min-lines 5 src/
|
|
464
483
|
|
|
465
|
-
#
|
|
466
|
-
thailint dry --
|
|
484
|
+
# Use tempfile storage for large projects
|
|
485
|
+
thailint dry --storage-mode tempfile src/
|
|
467
486
|
|
|
468
487
|
# Get JSON output
|
|
469
488
|
thailint dry --format json src/
|
|
@@ -486,10 +505,8 @@ dry:
|
|
|
486
505
|
typescript:
|
|
487
506
|
min_occurrences: 3 # TypeScript: require 3+ occurrences
|
|
488
507
|
|
|
489
|
-
#
|
|
490
|
-
|
|
491
|
-
cache_path: ".thailint-cache/dry.db"
|
|
492
|
-
cache_max_age_days: 30
|
|
508
|
+
# Storage settings
|
|
509
|
+
storage_mode: "memory" # Options: "memory" (default) or "tempfile"
|
|
493
510
|
|
|
494
511
|
# Ignore patterns
|
|
495
512
|
ignore:
|
|
@@ -510,11 +527,11 @@ dry:
|
|
|
510
527
|
3. Store hashes in SQLite database with file locations
|
|
511
528
|
4. Query for hashes appearing 2+ times across project
|
|
512
529
|
|
|
513
|
-
**SQLite
|
|
514
|
-
-
|
|
515
|
-
-
|
|
516
|
-
-
|
|
517
|
-
-
|
|
530
|
+
**SQLite Storage:**
|
|
531
|
+
- In-memory mode (default): Stores in RAM for best performance
|
|
532
|
+
- Tempfile mode: Stores in temporary disk file for large projects
|
|
533
|
+
- Fresh analysis on every run (no persistence between runs)
|
|
534
|
+
- Fast duplicate detection using B-tree indexes
|
|
518
535
|
|
|
519
536
|
### Example Violation
|
|
520
537
|
|
|
@@ -572,31 +589,14 @@ def validate_admin(admin_data):
|
|
|
572
589
|
return validate_credentials(admin_data)
|
|
573
590
|
```
|
|
574
591
|
|
|
575
|
-
### Cache Management
|
|
576
|
-
|
|
577
|
-
```bash
|
|
578
|
-
# Normal cached run (default)
|
|
579
|
-
thailint dry src/
|
|
580
|
-
|
|
581
|
-
# Force re-analysis (ignore cache)
|
|
582
|
-
thailint dry --no-cache src/
|
|
583
|
-
|
|
584
|
-
# Clear cache before running
|
|
585
|
-
thailint dry --clear-cache src/
|
|
586
|
-
|
|
587
|
-
# Manual cache cleanup
|
|
588
|
-
rm -rf .thailint-cache/dry.db
|
|
589
|
-
```
|
|
590
|
-
|
|
591
592
|
### Performance
|
|
592
593
|
|
|
593
|
-
| Operation | Performance |
|
|
594
|
+
| Operation | Performance | Storage Mode |
|
|
594
595
|
|-----------|-------------|--------------|
|
|
595
|
-
|
|
|
596
|
-
|
|
|
597
|
-
| 50 changed files | 0.5-1s | Partial cache |
|
|
596
|
+
| Scan (1000 files) | 1-3s | Memory (default) |
|
|
597
|
+
| Large project (5000+ files) | Use tempfile mode | Tempfile |
|
|
598
598
|
|
|
599
|
-
**
|
|
599
|
+
**Note**: Every run analyzes files fresh - no persistence between runs ensures accurate results
|
|
600
600
|
|
|
601
601
|
### Language Support
|
|
602
602
|
|
|
@@ -617,7 +617,159 @@ Built-in filters automatically exclude common non-duplication patterns:
|
|
|
617
617
|
3. **Extract Utility Module**: Move helper functions to shared utilities
|
|
618
618
|
4. **Template Method**: Use function parameters for variations
|
|
619
619
|
|
|
620
|
-
See [DRY Linter Guide](docs/dry-linter.md) for comprehensive documentation,
|
|
620
|
+
See [DRY Linter Guide](docs/dry-linter.md) for comprehensive documentation, storage modes, and refactoring patterns.
|
|
621
|
+
|
|
622
|
+
## Magic Numbers Linter
|
|
623
|
+
|
|
624
|
+
### Overview
|
|
625
|
+
|
|
626
|
+
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.
|
|
627
|
+
|
|
628
|
+
### What are Magic Numbers?
|
|
629
|
+
|
|
630
|
+
**Magic numbers** are unnamed numeric literals in code without explanation:
|
|
631
|
+
|
|
632
|
+
```python
|
|
633
|
+
# Bad - Magic numbers
|
|
634
|
+
timeout = 3600 # What is 3600?
|
|
635
|
+
max_retries = 5 # Why 5?
|
|
636
|
+
|
|
637
|
+
# Good - Named constants
|
|
638
|
+
TIMEOUT_SECONDS = 3600
|
|
639
|
+
MAX_RETRY_ATTEMPTS = 5
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Quick Start
|
|
643
|
+
|
|
644
|
+
```bash
|
|
645
|
+
# Check for magic numbers in current directory
|
|
646
|
+
thailint magic-numbers .
|
|
647
|
+
|
|
648
|
+
# Check specific directory
|
|
649
|
+
thailint magic-numbers src/
|
|
650
|
+
|
|
651
|
+
# Get JSON output
|
|
652
|
+
thailint magic-numbers --format json src/
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### Configuration
|
|
656
|
+
|
|
657
|
+
Add to `.thailint.yaml`:
|
|
658
|
+
|
|
659
|
+
```yaml
|
|
660
|
+
magic-numbers:
|
|
661
|
+
enabled: true
|
|
662
|
+
allowed_numbers: [-1, 0, 1, 2, 10, 100, 1000]
|
|
663
|
+
max_small_integer: 10 # Max for range() to be acceptable
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Example Violation
|
|
667
|
+
|
|
668
|
+
**Code with magic numbers:**
|
|
669
|
+
```python
|
|
670
|
+
def calculate_timeout():
|
|
671
|
+
return 3600 # Magic number - what is 3600?
|
|
672
|
+
|
|
673
|
+
def process_items(items):
|
|
674
|
+
for i in range(100): # Magic number - why 100?
|
|
675
|
+
items[i] *= 1.5 # Magic number - what is 1.5?
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
**Violation messages:**
|
|
679
|
+
```
|
|
680
|
+
src/example.py:2 - Magic number 3600 should be a named constant
|
|
681
|
+
src/example.py:5 - Magic number 100 should be a named constant
|
|
682
|
+
src/example.py:6 - Magic number 1.5 should be a named constant
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Refactored code:**
|
|
686
|
+
```python
|
|
687
|
+
TIMEOUT_SECONDS = 3600
|
|
688
|
+
MAX_ITEMS = 100
|
|
689
|
+
PRICE_MULTIPLIER = 1.5
|
|
690
|
+
|
|
691
|
+
def calculate_timeout():
|
|
692
|
+
return TIMEOUT_SECONDS
|
|
693
|
+
|
|
694
|
+
def process_items(items):
|
|
695
|
+
for i in range(MAX_ITEMS):
|
|
696
|
+
items[i] *= PRICE_MULTIPLIER
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Acceptable Contexts
|
|
700
|
+
|
|
701
|
+
The linter **does not** flag numbers in these contexts:
|
|
702
|
+
|
|
703
|
+
| Context | Example | Why Acceptable |
|
|
704
|
+
|---------|---------|----------------|
|
|
705
|
+
| Constants | `MAX_SIZE = 100` | UPPERCASE name provides context |
|
|
706
|
+
| Small `range()` | `range(5)` | Small loop bounds are clear |
|
|
707
|
+
| Test files | `test_*.py` | Test data can be literal |
|
|
708
|
+
| Allowed numbers | `-1, 0, 1, 2, 10` | Common values are self-explanatory |
|
|
709
|
+
|
|
710
|
+
### Refactoring Patterns
|
|
711
|
+
|
|
712
|
+
**Pattern 1: Extract to Module Constants**
|
|
713
|
+
```python
|
|
714
|
+
# Before
|
|
715
|
+
def connect():
|
|
716
|
+
timeout = 30
|
|
717
|
+
retries = 3
|
|
718
|
+
|
|
719
|
+
# After
|
|
720
|
+
DEFAULT_TIMEOUT_SECONDS = 30
|
|
721
|
+
DEFAULT_MAX_RETRIES = 3
|
|
722
|
+
|
|
723
|
+
def connect():
|
|
724
|
+
timeout = DEFAULT_TIMEOUT_SECONDS
|
|
725
|
+
retries = DEFAULT_MAX_RETRIES
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
**Pattern 2: Extract with Units in Name**
|
|
729
|
+
```python
|
|
730
|
+
# Before
|
|
731
|
+
delay = 3600 # Is this seconds? Minutes?
|
|
732
|
+
|
|
733
|
+
# After
|
|
734
|
+
TASK_DELAY_SECONDS = 3600 # Clear unit
|
|
735
|
+
|
|
736
|
+
delay = TASK_DELAY_SECONDS
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
**Pattern 3: Use Standard Library**
|
|
740
|
+
```python
|
|
741
|
+
# Before
|
|
742
|
+
if status == 200:
|
|
743
|
+
return "success"
|
|
744
|
+
|
|
745
|
+
# After
|
|
746
|
+
from http import HTTPStatus
|
|
747
|
+
|
|
748
|
+
if status == HTTPStatus.OK:
|
|
749
|
+
return "success"
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
### Language Support
|
|
753
|
+
|
|
754
|
+
- **Python**: Full support (int, float, scientific notation)
|
|
755
|
+
- **TypeScript**: Full support (int, float, scientific notation)
|
|
756
|
+
- **JavaScript**: Supported via TypeScript parser
|
|
757
|
+
|
|
758
|
+
### Ignoring Violations
|
|
759
|
+
|
|
760
|
+
```python
|
|
761
|
+
# Line-level ignore
|
|
762
|
+
timeout = 3600 # thailint: ignore[magic-numbers] - Industry standard
|
|
763
|
+
|
|
764
|
+
# Method-level ignore
|
|
765
|
+
def get_ports(): # thailint: ignore[magic-numbers] - Standard ports
|
|
766
|
+
return {80: "HTTP", 443: "HTTPS"}
|
|
767
|
+
|
|
768
|
+
# File-level ignore
|
|
769
|
+
# thailint: ignore-file[magic-numbers]
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
See **[How to Ignore Violations](docs/how-to-ignore-violations.md)** and **[Magic Numbers Linter Guide](docs/magic-numbers-linter.md)** for complete documentation.
|
|
621
773
|
|
|
622
774
|
## Pre-commit Hooks
|
|
623
775
|
|
|
@@ -811,10 +963,12 @@ docker run --rm -v $(pwd):/data \
|
|
|
811
963
|
|
|
812
964
|
- **[Getting Started](docs/getting-started.md)** - Installation, first lint, basic config
|
|
813
965
|
- **[Configuration Reference](docs/configuration.md)** - Complete config options (YAML/JSON)
|
|
966
|
+
- **[How to Ignore Violations](docs/how-to-ignore-violations.md)** - Complete guide to all ignore levels
|
|
814
967
|
- **[API Reference](docs/api-reference.md)** - Library API documentation
|
|
815
968
|
- **[CLI Reference](docs/cli-reference.md)** - All CLI commands and options
|
|
816
969
|
- **[Deployment Modes](docs/deployment-modes.md)** - CLI, Library, and Docker usage
|
|
817
970
|
- **[File Placement Linter](docs/file-placement-linter.md)** - Detailed linter guide
|
|
971
|
+
- **[Magic Numbers Linter](docs/magic-numbers-linter.md)** - Magic numbers detection guide
|
|
818
972
|
- **[Nesting Depth Linter](docs/nesting-linter.md)** - Nesting depth analysis guide
|
|
819
973
|
- **[SRP Linter](docs/srp-linter.md)** - Single Responsibility Principle guide
|
|
820
974
|
- **[DRY Linter](docs/dry-linter.md)** - Duplicate code detection guide
|