thailint 0.4.6__tar.gz → 0.7.0__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.4.6 → thailint-0.7.0}/PKG-INFO +149 -3
- {thailint-0.4.6 → thailint-0.7.0}/README.md +148 -2
- {thailint-0.4.6 → thailint-0.7.0}/pyproject.toml +1 -1
- {thailint-0.4.6 → thailint-0.7.0}/src/cli.py +228 -1
- {thailint-0.4.6 → thailint-0.7.0}/src/core/cli_utils.py +16 -1
- {thailint-0.4.6 → thailint-0.7.0}/src/core/registry.py +1 -1
- thailint-0.7.0/src/formatters/__init__.py +22 -0
- thailint-0.7.0/src/formatters/sarif.py +202 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_header/atemporal_detector.py +11 -11
- thailint-0.7.0/src/linters/file_header/base_parser.py +89 -0
- thailint-0.7.0/src/linters/file_header/bash_parser.py +58 -0
- thailint-0.7.0/src/linters/file_header/config.py +126 -0
- thailint-0.7.0/src/linters/file_header/css_parser.py +70 -0
- thailint-0.7.0/src/linters/file_header/field_validator.py +75 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_header/linter.py +113 -121
- thailint-0.7.0/src/linters/file_header/markdown_parser.py +124 -0
- thailint-0.7.0/src/linters/file_header/python_parser.py +42 -0
- thailint-0.7.0/src/linters/file_header/typescript_parser.py +73 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_header/violation_builder.py +13 -12
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_placement/linter.py +9 -11
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/magic_numbers/typescript_analyzer.py +1 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/nesting/typescript_analyzer.py +1 -0
- thailint-0.7.0/src/linters/print_statements/__init__.py +53 -0
- thailint-0.7.0/src/linters/print_statements/config.py +78 -0
- thailint-0.7.0/src/linters/print_statements/linter.py +428 -0
- thailint-0.7.0/src/linters/print_statements/python_analyzer.py +149 -0
- thailint-0.7.0/src/linters/print_statements/typescript_analyzer.py +130 -0
- thailint-0.7.0/src/linters/print_statements/violation_builder.py +96 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/templates/thailint_config_template.yaml +26 -0
- thailint-0.4.6/src/linters/file_header/config.py +0 -66
- thailint-0.4.6/src/linters/file_header/field_validator.py +0 -69
- thailint-0.4.6/src/linters/file_header/python_parser.py +0 -86
- {thailint-0.4.6 → thailint-0.7.0}/CHANGELOG.md +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/LICENSE +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/analyzers/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/analyzers/typescript_base.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/api.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/config.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/core/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/core/base.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/core/config_parser.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/core/linter_utils.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/core/rule_discovery.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/core/types.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/core/violation_builder.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linter_config/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linter_config/ignore.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linter_config/loader.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/base_token_analyzer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/block_filter.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/block_grouper.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/cache.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/cache_query.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/config.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/config_loader.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/deduplicator.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/duplicate_storage.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/file_analyzer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/inline_ignore.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/linter.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/python_analyzer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/storage_initializer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/token_hasher.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/typescript_analyzer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/violation_builder.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/violation_filter.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/dry/violation_generator.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_header/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_placement/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_placement/config_loader.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_placement/directory_matcher.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_placement/path_resolver.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_placement/pattern_matcher.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_placement/pattern_validator.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_placement/rule_checker.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/file_placement/violation_factory.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/magic_numbers/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/magic_numbers/config.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/magic_numbers/context_analyzer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/magic_numbers/linter.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/magic_numbers/python_analyzer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/magic_numbers/violation_builder.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/nesting/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/nesting/config.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/nesting/linter.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/nesting/python_analyzer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/nesting/typescript_function_extractor.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/nesting/violation_builder.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/srp/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/srp/class_analyzer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/srp/config.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/srp/heuristics.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/srp/linter.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/srp/metrics_evaluator.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/srp/python_analyzer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/srp/typescript_analyzer.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/srp/typescript_metrics_calculator.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/linters/srp/violation_builder.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/orchestrator/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/orchestrator/core.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/orchestrator/language_detector.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/utils/__init__.py +0 -0
- {thailint-0.4.6 → thailint-0.7.0}/src/utils/project_root.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thailint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -37,9 +37,10 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
|
|
38
38
|
[](https://opensource.org/licenses/MIT)
|
|
39
39
|
[](https://www.python.org/downloads/)
|
|
40
|
-
[](tests/)
|
|
41
41
|
[](htmlcov/)
|
|
42
42
|
[](https://thai-lint.readthedocs.io/en/latest/?badge=latest)
|
|
43
|
+
[](docs/sarif-output.md)
|
|
43
44
|
|
|
44
45
|
The AI Linter - Enterprise-ready linting and governance for AI-generated code across multiple languages.
|
|
45
46
|
|
|
@@ -73,6 +74,11 @@ thailint complements your existing linting stack by catching the patterns AI too
|
|
|
73
74
|
|
|
74
75
|
### Core Capabilities
|
|
75
76
|
- **File Placement Linting** - Enforce project structure and organization
|
|
77
|
+
- **File Header Linting** - Validate documentation headers in source files
|
|
78
|
+
- Python, TypeScript, JavaScript, Bash, Markdown, CSS support
|
|
79
|
+
- Mandatory field validation (Purpose, Scope, Overview)
|
|
80
|
+
- Atemporal language detection (no dates, "currently", "now")
|
|
81
|
+
- Language-specific header format parsing
|
|
76
82
|
- **Magic Numbers Linting** - Detect unnamed numeric literals that should be constants
|
|
77
83
|
- Python and TypeScript support with AST analysis
|
|
78
84
|
- Context-aware detection (ignores constants, test files, range() usage)
|
|
@@ -122,7 +128,7 @@ cd thai-lint
|
|
|
122
128
|
pip install -e ".[dev]"
|
|
123
129
|
```
|
|
124
130
|
|
|
125
|
-
### From PyPI
|
|
131
|
+
### From PyPI
|
|
126
132
|
|
|
127
133
|
```bash
|
|
128
134
|
pip install thai-lint
|
|
@@ -158,11 +164,17 @@ thailint dry .
|
|
|
158
164
|
# Check for magic numbers
|
|
159
165
|
thailint magic-numbers src/
|
|
160
166
|
|
|
167
|
+
# Check file headers
|
|
168
|
+
thailint file-header src/
|
|
169
|
+
|
|
161
170
|
# With config file
|
|
162
171
|
thailint dry --config .thailint.yaml src/
|
|
163
172
|
|
|
164
173
|
# JSON output for CI/CD
|
|
165
174
|
thailint dry --format json src/
|
|
175
|
+
|
|
176
|
+
# SARIF output for GitHub Code Scanning
|
|
177
|
+
thailint nesting --format sarif src/ > results.sarif
|
|
166
178
|
```
|
|
167
179
|
|
|
168
180
|
**New to thailint?** See the **[Quick Start Guide](https://thai-lint.readthedocs.io/en/latest/quick-start/)** for a complete walkthrough including config generation, understanding output, and next steps.
|
|
@@ -869,6 +881,136 @@ def get_ports(): # thailint: ignore[magic-numbers] - Standard ports
|
|
|
869
881
|
|
|
870
882
|
See **[How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/)** and **[Magic Numbers Linter Guide](https://thai-lint.readthedocs.io/en/latest/magic-numbers-linter/)** for complete documentation.
|
|
871
883
|
|
|
884
|
+
## File Header Linter
|
|
885
|
+
|
|
886
|
+
### Overview
|
|
887
|
+
|
|
888
|
+
The file header linter validates that source files have proper documentation headers containing required fields (Purpose, Scope, Overview) and don't use temporal language (dates, "currently", "now"). It enforces consistent documentation patterns across entire codebases.
|
|
889
|
+
|
|
890
|
+
### Why File Headers?
|
|
891
|
+
|
|
892
|
+
File headers serve as **self-documentation** that helps developers (and AI assistants) quickly understand:
|
|
893
|
+
|
|
894
|
+
- **Purpose**: What does this file do?
|
|
895
|
+
- **Scope**: What area of the system does it cover?
|
|
896
|
+
- **Dependencies**: What does it rely on?
|
|
897
|
+
- **Exports**: What does it provide to other modules?
|
|
898
|
+
|
|
899
|
+
### Quick Start
|
|
900
|
+
|
|
901
|
+
```bash
|
|
902
|
+
# Check file headers in current directory
|
|
903
|
+
thailint file-header .
|
|
904
|
+
|
|
905
|
+
# Check specific directory
|
|
906
|
+
thailint file-header src/
|
|
907
|
+
|
|
908
|
+
# Get JSON output
|
|
909
|
+
thailint file-header --format json src/
|
|
910
|
+
|
|
911
|
+
# Get SARIF output for CI/CD
|
|
912
|
+
thailint file-header --format sarif src/ > results.sarif
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
### Configuration
|
|
916
|
+
|
|
917
|
+
Add to `.thailint.yaml`:
|
|
918
|
+
|
|
919
|
+
```yaml
|
|
920
|
+
file-header:
|
|
921
|
+
enabled: true
|
|
922
|
+
mandatory_fields:
|
|
923
|
+
- Purpose
|
|
924
|
+
- Scope
|
|
925
|
+
- Overview
|
|
926
|
+
ignore:
|
|
927
|
+
- "**/__init__.py"
|
|
928
|
+
- "**/migrations/**"
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
### Example Violation
|
|
932
|
+
|
|
933
|
+
**Code without proper header:**
|
|
934
|
+
```python
|
|
935
|
+
import os
|
|
936
|
+
|
|
937
|
+
def process_data():
|
|
938
|
+
pass
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
**Violation messages:**
|
|
942
|
+
```
|
|
943
|
+
src/utils.py:1 - Missing mandatory field: Purpose
|
|
944
|
+
src/utils.py:1 - Missing mandatory field: Scope
|
|
945
|
+
src/utils.py:1 - Missing mandatory field: Overview
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
**Refactored with header:**
|
|
949
|
+
```python
|
|
950
|
+
"""
|
|
951
|
+
Purpose: Data processing utilities for ETL pipeline
|
|
952
|
+
|
|
953
|
+
Scope: Data transformation layer, used by batch processing jobs
|
|
954
|
+
|
|
955
|
+
Overview: Provides data transformation functions for the ETL pipeline.
|
|
956
|
+
Handles parsing, validation, and normalization of incoming data.
|
|
957
|
+
|
|
958
|
+
Dependencies: os, json
|
|
959
|
+
|
|
960
|
+
Exports: process_data(), validate_input(), transform_record()
|
|
961
|
+
"""
|
|
962
|
+
import os
|
|
963
|
+
|
|
964
|
+
def process_data():
|
|
965
|
+
pass
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
### Atemporal Language Detection
|
|
969
|
+
|
|
970
|
+
The linter detects temporal language that becomes stale:
|
|
971
|
+
|
|
972
|
+
**Temporal (flagged):**
|
|
973
|
+
```python
|
|
974
|
+
"""
|
|
975
|
+
Purpose: Authentication module
|
|
976
|
+
|
|
977
|
+
Overview: Currently handles OAuth. This was recently updated.
|
|
978
|
+
Created: 2024-01-15. Will be extended in the future.
|
|
979
|
+
"""
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
**Atemporal (correct):**
|
|
983
|
+
```python
|
|
984
|
+
"""
|
|
985
|
+
Purpose: Authentication module
|
|
986
|
+
|
|
987
|
+
Overview: Handles OAuth authentication with Google and GitHub.
|
|
988
|
+
Implements authorization code flow with PKCE for security.
|
|
989
|
+
"""
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
### Language Support
|
|
993
|
+
|
|
994
|
+
- **Python**: Module docstrings (`"""..."""`)
|
|
995
|
+
- **TypeScript/JavaScript**: JSDoc comments (`/** ... */`)
|
|
996
|
+
- **Bash**: Hash comments after shebang (`# ...`)
|
|
997
|
+
- **Markdown**: YAML frontmatter (`---...---`)
|
|
998
|
+
- **CSS/SCSS**: Block comments (`/* ... */`)
|
|
999
|
+
|
|
1000
|
+
### Ignoring Violations
|
|
1001
|
+
|
|
1002
|
+
```python
|
|
1003
|
+
# File-level ignore
|
|
1004
|
+
# thailint: ignore-file[file-header]
|
|
1005
|
+
|
|
1006
|
+
# Line-level ignore for atemporal violation
|
|
1007
|
+
"""
|
|
1008
|
+
Overview: Created 2024-01-15. # thailint: ignore[file-header]
|
|
1009
|
+
"""
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
See **[How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/)** and **[File Header Linter Guide](https://thai-lint.readthedocs.io/en/latest/file-header-linter/)** for complete documentation.
|
|
1013
|
+
|
|
872
1014
|
## Pre-commit Hooks
|
|
873
1015
|
|
|
874
1016
|
Automate code quality checks before every commit and push with pre-commit hooks.
|
|
@@ -1153,11 +1295,13 @@ docker run --rm -v /path/to/workspace:/workspace \
|
|
|
1153
1295
|
- **[CLI Reference](https://thai-lint.readthedocs.io/en/latest/cli-reference/)** - All CLI commands and options
|
|
1154
1296
|
- **[Deployment Modes](https://thai-lint.readthedocs.io/en/latest/deployment-modes/)** - CLI, Library, and Docker usage
|
|
1155
1297
|
- **[File Placement Linter](https://thai-lint.readthedocs.io/en/latest/file-placement-linter/)** - Detailed linter guide
|
|
1298
|
+
- **[File Header Linter](https://thai-lint.readthedocs.io/en/latest/file-header-linter/)** - File header validation guide
|
|
1156
1299
|
- **[Magic Numbers Linter](https://thai-lint.readthedocs.io/en/latest/magic-numbers-linter/)** - Magic numbers detection guide
|
|
1157
1300
|
- **[Nesting Depth Linter](https://thai-lint.readthedocs.io/en/latest/nesting-linter/)** - Nesting depth analysis guide
|
|
1158
1301
|
- **[SRP Linter](https://thai-lint.readthedocs.io/en/latest/srp-linter/)** - Single Responsibility Principle guide
|
|
1159
1302
|
- **[DRY Linter](https://thai-lint.readthedocs.io/en/latest/dry-linter/)** - Duplicate code detection guide
|
|
1160
1303
|
- **[Pre-commit Hooks](https://thai-lint.readthedocs.io/en/latest/pre-commit-hooks/)** - Automated quality checks
|
|
1304
|
+
- **[SARIF Output Guide](docs/sarif-output.md)** - SARIF format for GitHub Code Scanning and CI/CD
|
|
1161
1305
|
- **[Publishing Guide](https://thai-lint.readthedocs.io/en/latest/releasing/)** - Release and publishing workflow
|
|
1162
1306
|
- **[Publishing Checklist](https://thai-lint.readthedocs.io/en/latest/publishing-checklist/)** - Post-publication validation
|
|
1163
1307
|
|
|
@@ -1168,6 +1312,8 @@ See [`examples/`](examples/) directory for working code:
|
|
|
1168
1312
|
- **[basic_usage.py](examples/basic_usage.py)** - Simple library API usage
|
|
1169
1313
|
- **[advanced_usage.py](examples/advanced_usage.py)** - Advanced patterns and workflows
|
|
1170
1314
|
- **[ci_integration.py](examples/ci_integration.py)** - CI/CD integration example
|
|
1315
|
+
- **[sarif_usage.py](examples/sarif_usage.py)** - SARIF output format examples
|
|
1316
|
+
- **[file_header_usage.py](examples/file_header_usage.py)** - File header validation examples
|
|
1171
1317
|
|
|
1172
1318
|
## Project Structure
|
|
1173
1319
|
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
[](https://www.python.org/downloads/)
|
|
5
|
-
[](tests/)
|
|
6
6
|
[](htmlcov/)
|
|
7
7
|
[](https://thai-lint.readthedocs.io/en/latest/?badge=latest)
|
|
8
|
+
[](docs/sarif-output.md)
|
|
8
9
|
|
|
9
10
|
The AI Linter - Enterprise-ready linting and governance for AI-generated code across multiple languages.
|
|
10
11
|
|
|
@@ -38,6 +39,11 @@ thailint complements your existing linting stack by catching the patterns AI too
|
|
|
38
39
|
|
|
39
40
|
### Core Capabilities
|
|
40
41
|
- **File Placement Linting** - Enforce project structure and organization
|
|
42
|
+
- **File Header Linting** - Validate documentation headers in source files
|
|
43
|
+
- Python, TypeScript, JavaScript, Bash, Markdown, CSS support
|
|
44
|
+
- Mandatory field validation (Purpose, Scope, Overview)
|
|
45
|
+
- Atemporal language detection (no dates, "currently", "now")
|
|
46
|
+
- Language-specific header format parsing
|
|
41
47
|
- **Magic Numbers Linting** - Detect unnamed numeric literals that should be constants
|
|
42
48
|
- Python and TypeScript support with AST analysis
|
|
43
49
|
- Context-aware detection (ignores constants, test files, range() usage)
|
|
@@ -87,7 +93,7 @@ cd thai-lint
|
|
|
87
93
|
pip install -e ".[dev]"
|
|
88
94
|
```
|
|
89
95
|
|
|
90
|
-
### From PyPI
|
|
96
|
+
### From PyPI
|
|
91
97
|
|
|
92
98
|
```bash
|
|
93
99
|
pip install thai-lint
|
|
@@ -123,11 +129,17 @@ thailint dry .
|
|
|
123
129
|
# Check for magic numbers
|
|
124
130
|
thailint magic-numbers src/
|
|
125
131
|
|
|
132
|
+
# Check file headers
|
|
133
|
+
thailint file-header src/
|
|
134
|
+
|
|
126
135
|
# With config file
|
|
127
136
|
thailint dry --config .thailint.yaml src/
|
|
128
137
|
|
|
129
138
|
# JSON output for CI/CD
|
|
130
139
|
thailint dry --format json src/
|
|
140
|
+
|
|
141
|
+
# SARIF output for GitHub Code Scanning
|
|
142
|
+
thailint nesting --format sarif src/ > results.sarif
|
|
131
143
|
```
|
|
132
144
|
|
|
133
145
|
**New to thailint?** See the **[Quick Start Guide](https://thai-lint.readthedocs.io/en/latest/quick-start/)** for a complete walkthrough including config generation, understanding output, and next steps.
|
|
@@ -834,6 +846,136 @@ def get_ports(): # thailint: ignore[magic-numbers] - Standard ports
|
|
|
834
846
|
|
|
835
847
|
See **[How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/)** and **[Magic Numbers Linter Guide](https://thai-lint.readthedocs.io/en/latest/magic-numbers-linter/)** for complete documentation.
|
|
836
848
|
|
|
849
|
+
## File Header Linter
|
|
850
|
+
|
|
851
|
+
### Overview
|
|
852
|
+
|
|
853
|
+
The file header linter validates that source files have proper documentation headers containing required fields (Purpose, Scope, Overview) and don't use temporal language (dates, "currently", "now"). It enforces consistent documentation patterns across entire codebases.
|
|
854
|
+
|
|
855
|
+
### Why File Headers?
|
|
856
|
+
|
|
857
|
+
File headers serve as **self-documentation** that helps developers (and AI assistants) quickly understand:
|
|
858
|
+
|
|
859
|
+
- **Purpose**: What does this file do?
|
|
860
|
+
- **Scope**: What area of the system does it cover?
|
|
861
|
+
- **Dependencies**: What does it rely on?
|
|
862
|
+
- **Exports**: What does it provide to other modules?
|
|
863
|
+
|
|
864
|
+
### Quick Start
|
|
865
|
+
|
|
866
|
+
```bash
|
|
867
|
+
# Check file headers in current directory
|
|
868
|
+
thailint file-header .
|
|
869
|
+
|
|
870
|
+
# Check specific directory
|
|
871
|
+
thailint file-header src/
|
|
872
|
+
|
|
873
|
+
# Get JSON output
|
|
874
|
+
thailint file-header --format json src/
|
|
875
|
+
|
|
876
|
+
# Get SARIF output for CI/CD
|
|
877
|
+
thailint file-header --format sarif src/ > results.sarif
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
### Configuration
|
|
881
|
+
|
|
882
|
+
Add to `.thailint.yaml`:
|
|
883
|
+
|
|
884
|
+
```yaml
|
|
885
|
+
file-header:
|
|
886
|
+
enabled: true
|
|
887
|
+
mandatory_fields:
|
|
888
|
+
- Purpose
|
|
889
|
+
- Scope
|
|
890
|
+
- Overview
|
|
891
|
+
ignore:
|
|
892
|
+
- "**/__init__.py"
|
|
893
|
+
- "**/migrations/**"
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
### Example Violation
|
|
897
|
+
|
|
898
|
+
**Code without proper header:**
|
|
899
|
+
```python
|
|
900
|
+
import os
|
|
901
|
+
|
|
902
|
+
def process_data():
|
|
903
|
+
pass
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
**Violation messages:**
|
|
907
|
+
```
|
|
908
|
+
src/utils.py:1 - Missing mandatory field: Purpose
|
|
909
|
+
src/utils.py:1 - Missing mandatory field: Scope
|
|
910
|
+
src/utils.py:1 - Missing mandatory field: Overview
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
**Refactored with header:**
|
|
914
|
+
```python
|
|
915
|
+
"""
|
|
916
|
+
Purpose: Data processing utilities for ETL pipeline
|
|
917
|
+
|
|
918
|
+
Scope: Data transformation layer, used by batch processing jobs
|
|
919
|
+
|
|
920
|
+
Overview: Provides data transformation functions for the ETL pipeline.
|
|
921
|
+
Handles parsing, validation, and normalization of incoming data.
|
|
922
|
+
|
|
923
|
+
Dependencies: os, json
|
|
924
|
+
|
|
925
|
+
Exports: process_data(), validate_input(), transform_record()
|
|
926
|
+
"""
|
|
927
|
+
import os
|
|
928
|
+
|
|
929
|
+
def process_data():
|
|
930
|
+
pass
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
### Atemporal Language Detection
|
|
934
|
+
|
|
935
|
+
The linter detects temporal language that becomes stale:
|
|
936
|
+
|
|
937
|
+
**Temporal (flagged):**
|
|
938
|
+
```python
|
|
939
|
+
"""
|
|
940
|
+
Purpose: Authentication module
|
|
941
|
+
|
|
942
|
+
Overview: Currently handles OAuth. This was recently updated.
|
|
943
|
+
Created: 2024-01-15. Will be extended in the future.
|
|
944
|
+
"""
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
**Atemporal (correct):**
|
|
948
|
+
```python
|
|
949
|
+
"""
|
|
950
|
+
Purpose: Authentication module
|
|
951
|
+
|
|
952
|
+
Overview: Handles OAuth authentication with Google and GitHub.
|
|
953
|
+
Implements authorization code flow with PKCE for security.
|
|
954
|
+
"""
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### Language Support
|
|
958
|
+
|
|
959
|
+
- **Python**: Module docstrings (`"""..."""`)
|
|
960
|
+
- **TypeScript/JavaScript**: JSDoc comments (`/** ... */`)
|
|
961
|
+
- **Bash**: Hash comments after shebang (`# ...`)
|
|
962
|
+
- **Markdown**: YAML frontmatter (`---...---`)
|
|
963
|
+
- **CSS/SCSS**: Block comments (`/* ... */`)
|
|
964
|
+
|
|
965
|
+
### Ignoring Violations
|
|
966
|
+
|
|
967
|
+
```python
|
|
968
|
+
# File-level ignore
|
|
969
|
+
# thailint: ignore-file[file-header]
|
|
970
|
+
|
|
971
|
+
# Line-level ignore for atemporal violation
|
|
972
|
+
"""
|
|
973
|
+
Overview: Created 2024-01-15. # thailint: ignore[file-header]
|
|
974
|
+
"""
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
See **[How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/)** and **[File Header Linter Guide](https://thai-lint.readthedocs.io/en/latest/file-header-linter/)** for complete documentation.
|
|
978
|
+
|
|
837
979
|
## Pre-commit Hooks
|
|
838
980
|
|
|
839
981
|
Automate code quality checks before every commit and push with pre-commit hooks.
|
|
@@ -1118,11 +1260,13 @@ docker run --rm -v /path/to/workspace:/workspace \
|
|
|
1118
1260
|
- **[CLI Reference](https://thai-lint.readthedocs.io/en/latest/cli-reference/)** - All CLI commands and options
|
|
1119
1261
|
- **[Deployment Modes](https://thai-lint.readthedocs.io/en/latest/deployment-modes/)** - CLI, Library, and Docker usage
|
|
1120
1262
|
- **[File Placement Linter](https://thai-lint.readthedocs.io/en/latest/file-placement-linter/)** - Detailed linter guide
|
|
1263
|
+
- **[File Header Linter](https://thai-lint.readthedocs.io/en/latest/file-header-linter/)** - File header validation guide
|
|
1121
1264
|
- **[Magic Numbers Linter](https://thai-lint.readthedocs.io/en/latest/magic-numbers-linter/)** - Magic numbers detection guide
|
|
1122
1265
|
- **[Nesting Depth Linter](https://thai-lint.readthedocs.io/en/latest/nesting-linter/)** - Nesting depth analysis guide
|
|
1123
1266
|
- **[SRP Linter](https://thai-lint.readthedocs.io/en/latest/srp-linter/)** - Single Responsibility Principle guide
|
|
1124
1267
|
- **[DRY Linter](https://thai-lint.readthedocs.io/en/latest/dry-linter/)** - Duplicate code detection guide
|
|
1125
1268
|
- **[Pre-commit Hooks](https://thai-lint.readthedocs.io/en/latest/pre-commit-hooks/)** - Automated quality checks
|
|
1269
|
+
- **[SARIF Output Guide](docs/sarif-output.md)** - SARIF format for GitHub Code Scanning and CI/CD
|
|
1126
1270
|
- **[Publishing Guide](https://thai-lint.readthedocs.io/en/latest/releasing/)** - Release and publishing workflow
|
|
1127
1271
|
- **[Publishing Checklist](https://thai-lint.readthedocs.io/en/latest/publishing-checklist/)** - Post-publication validation
|
|
1128
1272
|
|
|
@@ -1133,6 +1277,8 @@ See [`examples/`](examples/) directory for working code:
|
|
|
1133
1277
|
- **[basic_usage.py](examples/basic_usage.py)** - Simple library API usage
|
|
1134
1278
|
- **[advanced_usage.py](examples/advanced_usage.py)** - Advanced patterns and workflows
|
|
1135
1279
|
- **[ci_integration.py](examples/ci_integration.py)** - CI/CD integration example
|
|
1280
|
+
- **[sarif_usage.py](examples/sarif_usage.py)** - SARIF output format examples
|
|
1281
|
+
- **[file_header_usage.py](examples/file_header_usage.py)** - File header validation examples
|
|
1136
1282
|
|
|
1137
1283
|
## Project Structure
|
|
1138
1284
|
|
|
@@ -17,7 +17,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
17
17
|
|
|
18
18
|
[tool.poetry]
|
|
19
19
|
name = "thailint"
|
|
20
|
-
version = "0.
|
|
20
|
+
version = "0.7.0"
|
|
21
21
|
description = "The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages"
|
|
22
22
|
authors = ["Steve Jackson"]
|
|
23
23
|
license = "MIT"
|
|
@@ -38,7 +38,11 @@ logger = logging.getLogger(__name__)
|
|
|
38
38
|
def format_option(func):
|
|
39
39
|
"""Add --format option to a command for output format selection."""
|
|
40
40
|
return click.option(
|
|
41
|
-
"--format",
|
|
41
|
+
"--format",
|
|
42
|
+
"-f",
|
|
43
|
+
type=click.Choice(["text", "json", "sarif"]),
|
|
44
|
+
default="text",
|
|
45
|
+
help="Output format",
|
|
42
46
|
)(func)
|
|
43
47
|
|
|
44
48
|
|
|
@@ -1551,5 +1555,228 @@ def _execute_magic_numbers_lint( # pylint: disable=too-many-arguments,too-many-
|
|
|
1551
1555
|
sys.exit(1 if magic_numbers_violations else 0)
|
|
1552
1556
|
|
|
1553
1557
|
|
|
1558
|
+
# =============================================================================
|
|
1559
|
+
# Print Statements Linter Command
|
|
1560
|
+
# =============================================================================
|
|
1561
|
+
|
|
1562
|
+
|
|
1563
|
+
def _setup_print_statements_orchestrator(
|
|
1564
|
+
path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
|
|
1565
|
+
):
|
|
1566
|
+
"""Set up orchestrator for print-statements command."""
|
|
1567
|
+
from src.orchestrator.core import Orchestrator
|
|
1568
|
+
from src.utils.project_root import get_project_root
|
|
1569
|
+
|
|
1570
|
+
if project_root is None:
|
|
1571
|
+
first_path = path_objs[0] if path_objs else Path.cwd()
|
|
1572
|
+
search_start = first_path if first_path.is_dir() else first_path.parent
|
|
1573
|
+
project_root = get_project_root(search_start)
|
|
1574
|
+
|
|
1575
|
+
orchestrator = Orchestrator(project_root=project_root)
|
|
1576
|
+
|
|
1577
|
+
if config_file:
|
|
1578
|
+
_load_config_file(orchestrator, config_file, verbose)
|
|
1579
|
+
|
|
1580
|
+
return orchestrator
|
|
1581
|
+
|
|
1582
|
+
|
|
1583
|
+
def _run_print_statements_lint(orchestrator, path_objs: list[Path], recursive: bool):
|
|
1584
|
+
"""Execute print-statements lint on files or directories."""
|
|
1585
|
+
all_violations = _execute_linting_on_paths(orchestrator, path_objs, recursive)
|
|
1586
|
+
return [v for v in all_violations if "print-statement" in v.rule_id]
|
|
1587
|
+
|
|
1588
|
+
|
|
1589
|
+
@cli.command("print-statements")
|
|
1590
|
+
@click.argument("paths", nargs=-1, type=click.Path())
|
|
1591
|
+
@click.option("--config", "-c", "config_file", type=click.Path(), help="Path to config file")
|
|
1592
|
+
@format_option
|
|
1593
|
+
@click.option("--recursive/--no-recursive", default=True, help="Scan directories recursively")
|
|
1594
|
+
@click.pass_context
|
|
1595
|
+
def print_statements( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
1596
|
+
ctx,
|
|
1597
|
+
paths: tuple[str, ...],
|
|
1598
|
+
config_file: str | None,
|
|
1599
|
+
format: str,
|
|
1600
|
+
recursive: bool,
|
|
1601
|
+
):
|
|
1602
|
+
"""Check for print/console statements in code.
|
|
1603
|
+
|
|
1604
|
+
Detects print() calls in Python and console.log/warn/error/debug/info calls
|
|
1605
|
+
in TypeScript/JavaScript that should be replaced with proper logging.
|
|
1606
|
+
|
|
1607
|
+
PATHS: Files or directories to lint (defaults to current directory if none provided)
|
|
1608
|
+
|
|
1609
|
+
Examples:
|
|
1610
|
+
|
|
1611
|
+
\b
|
|
1612
|
+
# Check current directory (all files recursively)
|
|
1613
|
+
thai-lint print-statements
|
|
1614
|
+
|
|
1615
|
+
\b
|
|
1616
|
+
# Check specific directory
|
|
1617
|
+
thai-lint print-statements src/
|
|
1618
|
+
|
|
1619
|
+
\b
|
|
1620
|
+
# Check single file
|
|
1621
|
+
thai-lint print-statements src/app.py
|
|
1622
|
+
|
|
1623
|
+
\b
|
|
1624
|
+
# Check multiple files
|
|
1625
|
+
thai-lint print-statements src/app.py src/utils.ts tests/test_app.py
|
|
1626
|
+
|
|
1627
|
+
\b
|
|
1628
|
+
# Get JSON output
|
|
1629
|
+
thai-lint print-statements --format json .
|
|
1630
|
+
|
|
1631
|
+
\b
|
|
1632
|
+
# Use custom config file
|
|
1633
|
+
thai-lint print-statements --config .thailint.yaml src/
|
|
1634
|
+
"""
|
|
1635
|
+
verbose = ctx.obj.get("verbose", False)
|
|
1636
|
+
project_root = _get_project_root_from_context(ctx)
|
|
1637
|
+
|
|
1638
|
+
if not paths:
|
|
1639
|
+
paths = (".",)
|
|
1640
|
+
|
|
1641
|
+
path_objs = [Path(p) for p in paths]
|
|
1642
|
+
|
|
1643
|
+
try:
|
|
1644
|
+
_execute_print_statements_lint(
|
|
1645
|
+
path_objs, config_file, format, recursive, verbose, project_root
|
|
1646
|
+
)
|
|
1647
|
+
except Exception as e:
|
|
1648
|
+
_handle_linting_error(e, verbose)
|
|
1649
|
+
|
|
1650
|
+
|
|
1651
|
+
def _execute_print_statements_lint( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
1652
|
+
path_objs, config_file, format, recursive, verbose, project_root=None
|
|
1653
|
+
):
|
|
1654
|
+
"""Execute print-statements lint."""
|
|
1655
|
+
_validate_paths_exist(path_objs)
|
|
1656
|
+
orchestrator = _setup_print_statements_orchestrator(
|
|
1657
|
+
path_objs, config_file, verbose, project_root
|
|
1658
|
+
)
|
|
1659
|
+
print_statements_violations = _run_print_statements_lint(orchestrator, path_objs, recursive)
|
|
1660
|
+
|
|
1661
|
+
if verbose:
|
|
1662
|
+
logger.info(f"Found {len(print_statements_violations)} print statement violation(s)")
|
|
1663
|
+
|
|
1664
|
+
format_violations(print_statements_violations, format)
|
|
1665
|
+
sys.exit(1 if print_statements_violations else 0)
|
|
1666
|
+
|
|
1667
|
+
|
|
1668
|
+
# File Header Command Helper Functions
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
def _setup_file_header_orchestrator(
|
|
1672
|
+
path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
|
|
1673
|
+
):
|
|
1674
|
+
"""Set up orchestrator for file-header command."""
|
|
1675
|
+
from src.orchestrator.core import Orchestrator
|
|
1676
|
+
from src.utils.project_root import get_project_root
|
|
1677
|
+
|
|
1678
|
+
# Use provided project_root or fall back to auto-detection
|
|
1679
|
+
if project_root is None:
|
|
1680
|
+
first_path = path_objs[0] if path_objs else Path.cwd()
|
|
1681
|
+
search_start = first_path if first_path.is_dir() else first_path.parent
|
|
1682
|
+
project_root = get_project_root(search_start)
|
|
1683
|
+
|
|
1684
|
+
orchestrator = Orchestrator(project_root=project_root)
|
|
1685
|
+
|
|
1686
|
+
if config_file:
|
|
1687
|
+
_load_config_file(orchestrator, config_file, verbose)
|
|
1688
|
+
|
|
1689
|
+
return orchestrator
|
|
1690
|
+
|
|
1691
|
+
|
|
1692
|
+
def _run_file_header_lint(orchestrator, path_objs: list[Path], recursive: bool):
|
|
1693
|
+
"""Execute file-header lint on files or directories."""
|
|
1694
|
+
all_violations = _execute_linting_on_paths(orchestrator, path_objs, recursive)
|
|
1695
|
+
return [v for v in all_violations if "file-header" in v.rule_id]
|
|
1696
|
+
|
|
1697
|
+
|
|
1698
|
+
@cli.command("file-header")
|
|
1699
|
+
@click.argument("paths", nargs=-1, type=click.Path())
|
|
1700
|
+
@click.option("--config", "-c", "config_file", type=click.Path(), help="Path to config file")
|
|
1701
|
+
@format_option
|
|
1702
|
+
@click.option("--recursive/--no-recursive", default=True, help="Scan directories recursively")
|
|
1703
|
+
@click.pass_context
|
|
1704
|
+
def file_header(
|
|
1705
|
+
ctx,
|
|
1706
|
+
paths: tuple[str, ...],
|
|
1707
|
+
config_file: str | None,
|
|
1708
|
+
format: str,
|
|
1709
|
+
recursive: bool,
|
|
1710
|
+
):
|
|
1711
|
+
"""Check file headers for mandatory fields and atemporal language.
|
|
1712
|
+
|
|
1713
|
+
Validates that source files have proper documentation headers containing
|
|
1714
|
+
required fields (Purpose, Scope, Overview, etc.) and don't use temporal
|
|
1715
|
+
language (dates, "currently", "now", etc.).
|
|
1716
|
+
|
|
1717
|
+
Supports Python, TypeScript, JavaScript, Bash, Markdown, and CSS files.
|
|
1718
|
+
|
|
1719
|
+
PATHS: Files or directories to lint (defaults to current directory if none provided)
|
|
1720
|
+
|
|
1721
|
+
Examples:
|
|
1722
|
+
|
|
1723
|
+
\b
|
|
1724
|
+
# Check current directory (all files recursively)
|
|
1725
|
+
thai-lint file-header
|
|
1726
|
+
|
|
1727
|
+
\b
|
|
1728
|
+
# Check specific directory
|
|
1729
|
+
thai-lint file-header src/
|
|
1730
|
+
|
|
1731
|
+
\b
|
|
1732
|
+
# Check single file
|
|
1733
|
+
thai-lint file-header src/cli.py
|
|
1734
|
+
|
|
1735
|
+
\b
|
|
1736
|
+
# Check multiple files
|
|
1737
|
+
thai-lint file-header src/cli.py src/api.py tests/
|
|
1738
|
+
|
|
1739
|
+
\b
|
|
1740
|
+
# Get JSON output
|
|
1741
|
+
thai-lint file-header --format json .
|
|
1742
|
+
|
|
1743
|
+
\b
|
|
1744
|
+
# Get SARIF output for CI/CD integration
|
|
1745
|
+
thai-lint file-header --format sarif src/
|
|
1746
|
+
|
|
1747
|
+
\b
|
|
1748
|
+
# Use custom config file
|
|
1749
|
+
thai-lint file-header --config .thailint.yaml src/
|
|
1750
|
+
"""
|
|
1751
|
+
verbose = ctx.obj.get("verbose", False)
|
|
1752
|
+
project_root = _get_project_root_from_context(ctx)
|
|
1753
|
+
|
|
1754
|
+
# Default to current directory if no paths provided
|
|
1755
|
+
if not paths:
|
|
1756
|
+
paths = (".",)
|
|
1757
|
+
|
|
1758
|
+
path_objs = [Path(p) for p in paths]
|
|
1759
|
+
|
|
1760
|
+
try:
|
|
1761
|
+
_execute_file_header_lint(path_objs, config_file, format, recursive, verbose, project_root)
|
|
1762
|
+
except Exception as e:
|
|
1763
|
+
_handle_linting_error(e, verbose)
|
|
1764
|
+
|
|
1765
|
+
|
|
1766
|
+
def _execute_file_header_lint( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
1767
|
+
path_objs, config_file, format, recursive, verbose, project_root=None
|
|
1768
|
+
):
|
|
1769
|
+
"""Execute file-header lint."""
|
|
1770
|
+
_validate_paths_exist(path_objs)
|
|
1771
|
+
orchestrator = _setup_file_header_orchestrator(path_objs, config_file, verbose, project_root)
|
|
1772
|
+
file_header_violations = _run_file_header_lint(orchestrator, path_objs, recursive)
|
|
1773
|
+
|
|
1774
|
+
if verbose:
|
|
1775
|
+
logger.info(f"Found {len(file_header_violations)} file header violation(s)")
|
|
1776
|
+
|
|
1777
|
+
format_violations(file_header_violations, format)
|
|
1778
|
+
sys.exit(1 if file_header_violations else 0)
|
|
1779
|
+
|
|
1780
|
+
|
|
1554
1781
|
if __name__ == "__main__":
|
|
1555
1782
|
cli()
|