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.
Files changed (85) hide show
  1. {thailint-0.2.1 → thailint-0.3.1}/PKG-INFO +196 -42
  2. {thailint-0.2.1 → thailint-0.3.1}/README.md +195 -41
  3. {thailint-0.2.1 → thailint-0.3.1}/pyproject.toml +6 -1
  4. {thailint-0.2.1 → thailint-0.3.1}/src/cli.py +101 -0
  5. {thailint-0.2.1 → thailint-0.3.1}/src/config.py +6 -2
  6. {thailint-0.2.1 → thailint-0.3.1}/src/core/base.py +90 -5
  7. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/block_filter.py +5 -2
  8. thailint-0.3.1/src/linters/dry/cache.py +172 -0
  9. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/config.py +17 -13
  10. thailint-0.3.1/src/linters/dry/duplicate_storage.py +63 -0
  11. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/file_analyzer.py +11 -48
  12. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/linter.py +5 -12
  13. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/python_analyzer.py +12 -1
  14. thailint-0.3.1/src/linters/dry/storage_initializer.py +42 -0
  15. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/violation_filter.py +4 -1
  16. thailint-0.3.1/src/linters/magic_numbers/__init__.py +48 -0
  17. thailint-0.3.1/src/linters/magic_numbers/config.py +71 -0
  18. thailint-0.3.1/src/linters/magic_numbers/context_analyzer.py +247 -0
  19. thailint-0.3.1/src/linters/magic_numbers/linter.py +452 -0
  20. thailint-0.3.1/src/linters/magic_numbers/python_analyzer.py +76 -0
  21. thailint-0.3.1/src/linters/magic_numbers/typescript_analyzer.py +217 -0
  22. thailint-0.3.1/src/linters/magic_numbers/violation_builder.py +98 -0
  23. {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/__init__.py +6 -2
  24. {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/config.py +6 -3
  25. {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/linter.py +8 -19
  26. {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/__init__.py +3 -3
  27. {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/config.py +12 -6
  28. {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/linter.py +33 -24
  29. thailint-0.2.1/src/linters/dry/cache.py +0 -218
  30. thailint-0.2.1/src/linters/dry/duplicate_storage.py +0 -126
  31. thailint-0.2.1/src/linters/dry/storage_initializer.py +0 -51
  32. {thailint-0.2.1 → thailint-0.3.1}/CHANGELOG.md +0 -0
  33. {thailint-0.2.1 → thailint-0.3.1}/LICENSE +0 -0
  34. {thailint-0.2.1 → thailint-0.3.1}/src/__init__.py +0 -0
  35. {thailint-0.2.1 → thailint-0.3.1}/src/analyzers/__init__.py +0 -0
  36. {thailint-0.2.1 → thailint-0.3.1}/src/analyzers/typescript_base.py +0 -0
  37. {thailint-0.2.1 → thailint-0.3.1}/src/api.py +0 -0
  38. {thailint-0.2.1 → thailint-0.3.1}/src/core/__init__.py +0 -0
  39. {thailint-0.2.1 → thailint-0.3.1}/src/core/cli_utils.py +0 -0
  40. {thailint-0.2.1 → thailint-0.3.1}/src/core/config_parser.py +0 -0
  41. {thailint-0.2.1 → thailint-0.3.1}/src/core/linter_utils.py +0 -0
  42. {thailint-0.2.1 → thailint-0.3.1}/src/core/registry.py +0 -0
  43. {thailint-0.2.1 → thailint-0.3.1}/src/core/rule_discovery.py +0 -0
  44. {thailint-0.2.1 → thailint-0.3.1}/src/core/types.py +0 -0
  45. {thailint-0.2.1 → thailint-0.3.1}/src/core/violation_builder.py +0 -0
  46. {thailint-0.2.1 → thailint-0.3.1}/src/linter_config/__init__.py +0 -0
  47. {thailint-0.2.1 → thailint-0.3.1}/src/linter_config/ignore.py +0 -0
  48. {thailint-0.2.1 → thailint-0.3.1}/src/linter_config/loader.py +0 -0
  49. {thailint-0.2.1 → thailint-0.3.1}/src/linters/__init__.py +0 -0
  50. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/__init__.py +0 -0
  51. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/base_token_analyzer.py +0 -0
  52. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/block_grouper.py +0 -0
  53. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/cache_query.py +0 -0
  54. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/config_loader.py +0 -0
  55. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/deduplicator.py +0 -0
  56. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/inline_ignore.py +0 -0
  57. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/token_hasher.py +0 -0
  58. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/typescript_analyzer.py +0 -0
  59. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/violation_builder.py +0 -0
  60. {thailint-0.2.1 → thailint-0.3.1}/src/linters/dry/violation_generator.py +0 -0
  61. {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/__init__.py +0 -0
  62. {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/config_loader.py +0 -0
  63. {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/directory_matcher.py +0 -0
  64. {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/linter.py +0 -0
  65. {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/path_resolver.py +0 -0
  66. {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/pattern_matcher.py +0 -0
  67. {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/pattern_validator.py +0 -0
  68. {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/rule_checker.py +0 -0
  69. {thailint-0.2.1 → thailint-0.3.1}/src/linters/file_placement/violation_factory.py +0 -0
  70. {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/python_analyzer.py +0 -0
  71. {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/typescript_analyzer.py +0 -0
  72. {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/typescript_function_extractor.py +0 -0
  73. {thailint-0.2.1 → thailint-0.3.1}/src/linters/nesting/violation_builder.py +0 -0
  74. {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/class_analyzer.py +0 -0
  75. {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/heuristics.py +0 -0
  76. {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/metrics_evaluator.py +0 -0
  77. {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/python_analyzer.py +0 -0
  78. {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/typescript_analyzer.py +0 -0
  79. {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/typescript_metrics_calculator.py +0 -0
  80. {thailint-0.2.1 → thailint-0.3.1}/src/linters/srp/violation_builder.py +0 -0
  81. {thailint-0.2.1 → thailint-0.3.1}/src/orchestrator/__init__.py +0 -0
  82. {thailint-0.2.1 → thailint-0.3.1}/src/orchestrator/core.py +0 -0
  83. {thailint-0.2.1 → thailint-0.3.1}/src/orchestrator/language_detector.py +0 -0
  84. {thailint-0.2.1 → thailint-0.3.1}/src/utils/__init__.py +0 -0
  85. {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.2.1
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 caching
61
- - Fast incremental scans (10-50x speedup with cache)
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
- # Cache settings (SQLite)
232
- cache_enabled: true
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
- "cache_enabled": true,
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 caching. It identifies identical or near-identical code that violates the Don't Repeat Yourself (DRY) principle, helping maintain code quality at scale.
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
- # Clear cache and re-scan
499
- thailint dry --clear-cache src/
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
- # Cache settings (SQLite for fast incremental scans)
523
- cache_enabled: true
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 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)
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 | Cache Status |
627
+ | Operation | Performance | Storage Mode |
627
628
  |-----------|-------------|--------------|
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 |
629
+ | Scan (1000 files) | 1-3s | Memory (default) |
630
+ | Large project (5000+ files) | Use tempfile mode | Tempfile |
631
631
 
632
- **Speedup**: 3-10x for incremental scans with cache
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, cache management, and refactoring patterns.
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 caching
28
- - Fast incremental scans (10-50x speedup with cache)
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
- # Cache settings (SQLite)
199
- cache_enabled: true
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
- "cache_enabled": true,
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 caching. It identifies identical or near-identical code that violates the Don't Repeat Yourself (DRY) principle, helping maintain code quality at scale.
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
- # Clear cache and re-scan
466
- thailint dry --clear-cache src/
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
- # Cache settings (SQLite for fast incremental scans)
490
- cache_enabled: true
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 Caching:**
514
- - First scan: Hash all files (~1-3s for 1000 files)
515
- - Subsequent scans: Load cached hashes for unchanged files (~0.1-0.5s)
516
- - 10-50x speedup for incremental scans
517
- - Mtime-based cache invalidation (automatic and safe)
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 | Cache Status |
594
+ | Operation | Performance | Storage Mode |
594
595
  |-----------|-------------|--------------|
595
- | First scan (1000 files) | 1-3s | Cache write |
596
- | Unchanged files | 0.1-0.5s | Cache hit |
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
- **Speedup**: 3-10x for incremental scans with cache
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, cache management, and refactoring patterns.
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