thailint 0.7.0__tar.gz → 0.8.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.7.0 → thailint-0.8.0}/PKG-INFO +9 -3
- {thailint-0.7.0 → thailint-0.8.0}/README.md +7 -1
- {thailint-0.7.0 → thailint-0.8.0}/pyproject.toml +2 -2
- {thailint-0.7.0 → thailint-0.8.0}/src/cli.py +118 -1
- {thailint-0.7.0 → thailint-0.8.0}/src/linter_config/loader.py +5 -4
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/block_filter.py +11 -8
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/cache.py +3 -2
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/duplicate_storage.py +5 -4
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/violation_generator.py +1 -1
- thailint-0.8.0/src/linters/method_property/__init__.py +49 -0
- thailint-0.8.0/src/linters/method_property/config.py +135 -0
- thailint-0.8.0/src/linters/method_property/linter.py +419 -0
- thailint-0.8.0/src/linters/method_property/python_analyzer.py +472 -0
- thailint-0.8.0/src/linters/method_property/violation_builder.py +116 -0
- {thailint-0.7.0 → thailint-0.8.0}/CHANGELOG.md +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/LICENSE +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/analyzers/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/analyzers/typescript_base.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/api.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/config.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/core/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/core/base.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/core/cli_utils.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/core/config_parser.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/core/linter_utils.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/core/registry.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/core/rule_discovery.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/core/types.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/core/violation_builder.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/formatters/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/formatters/sarif.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linter_config/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linter_config/ignore.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/base_token_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/block_grouper.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/cache_query.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/config.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/config_loader.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/deduplicator.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/file_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/inline_ignore.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/linter.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/python_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/storage_initializer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/token_hasher.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/typescript_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/violation_builder.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/dry/violation_filter.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/atemporal_detector.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/base_parser.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/bash_parser.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/config.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/css_parser.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/field_validator.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/linter.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/markdown_parser.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/python_parser.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/typescript_parser.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_header/violation_builder.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_placement/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_placement/config_loader.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_placement/directory_matcher.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_placement/linter.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_placement/path_resolver.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_placement/pattern_matcher.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_placement/pattern_validator.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_placement/rule_checker.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/file_placement/violation_factory.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/magic_numbers/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/magic_numbers/config.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/magic_numbers/context_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/magic_numbers/linter.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/magic_numbers/python_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/magic_numbers/typescript_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/magic_numbers/violation_builder.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/nesting/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/nesting/config.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/nesting/linter.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/nesting/python_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/nesting/typescript_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/nesting/typescript_function_extractor.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/nesting/violation_builder.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/print_statements/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/print_statements/config.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/print_statements/linter.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/print_statements/python_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/print_statements/typescript_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/print_statements/violation_builder.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/srp/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/srp/class_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/srp/config.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/srp/heuristics.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/srp/linter.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/srp/metrics_evaluator.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/srp/python_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/srp/typescript_analyzer.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/srp/typescript_metrics_calculator.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/linters/srp/violation_builder.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/orchestrator/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/orchestrator/core.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/orchestrator/language_detector.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/templates/thailint_config_template.yaml +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/utils/__init__.py +0 -0
- {thailint-0.7.0 → thailint-0.8.0}/src/utils/project_root.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thailint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
7
7
|
Keywords: linter,ai,code-quality,static-analysis,file-placement,governance,multi-language,cli,docker,python
|
|
8
8
|
Author: Steve Jackson
|
|
9
9
|
Requires-Python: >=3.11,<4.0
|
|
10
|
-
Classifier: Development Status ::
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
11
|
Classifier: Environment :: Console
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -37,7 +37,7 @@ 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
43
|
[](docs/sarif-output.md)
|
|
@@ -98,6 +98,12 @@ thailint complements your existing linting stack by catching the patterns AI too
|
|
|
98
98
|
- Configurable thresholds (lines, tokens, occurrences)
|
|
99
99
|
- Language-specific detection (Python, TypeScript, JavaScript)
|
|
100
100
|
- False positive filtering (keyword args, imports)
|
|
101
|
+
- **Method Property Linting** - Detect methods that should be @property decorators
|
|
102
|
+
- Python AST-based detection
|
|
103
|
+
- get_* prefix detection (Java-style getters)
|
|
104
|
+
- Simple computed value detection
|
|
105
|
+
- Action verb exclusion (to_*, finalize, serialize)
|
|
106
|
+
- Test file detection
|
|
101
107
|
- **Pluggable Architecture** - Easy to extend with custom linters
|
|
102
108
|
- **Multi-Language Support** - Python, TypeScript, JavaScript, and more
|
|
103
109
|
- **Flexible Configuration** - YAML/JSON configs with pattern matching
|
|
@@ -2,7 +2,7 @@
|
|
|
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
8
|
[](docs/sarif-output.md)
|
|
@@ -63,6 +63,12 @@ thailint complements your existing linting stack by catching the patterns AI too
|
|
|
63
63
|
- Configurable thresholds (lines, tokens, occurrences)
|
|
64
64
|
- Language-specific detection (Python, TypeScript, JavaScript)
|
|
65
65
|
- False positive filtering (keyword args, imports)
|
|
66
|
+
- **Method Property Linting** - Detect methods that should be @property decorators
|
|
67
|
+
- Python AST-based detection
|
|
68
|
+
- get_* prefix detection (Java-style getters)
|
|
69
|
+
- Simple computed value detection
|
|
70
|
+
- Action verb exclusion (to_*, finalize, serialize)
|
|
71
|
+
- Test file detection
|
|
66
72
|
- **Pluggable Architecture** - Easy to extend with custom linters
|
|
67
73
|
- **Multi-Language Support** - Python, TypeScript, JavaScript, and more
|
|
68
74
|
- **Flexible Configuration** - YAML/JSON configs with pattern matching
|
|
@@ -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.8.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,7 @@ keywords = [
|
|
|
38
38
|
"python",
|
|
39
39
|
]
|
|
40
40
|
classifiers = [
|
|
41
|
-
"Development Status ::
|
|
41
|
+
"Development Status :: 4 - Beta",
|
|
42
42
|
"Intended Audience :: Developers",
|
|
43
43
|
"License :: OSI Approved :: MIT License",
|
|
44
44
|
"Programming Language :: Python :: 3",
|
|
@@ -11,7 +11,7 @@ Overview: Provides the main CLI application using Click decorators for command d
|
|
|
11
11
|
|
|
12
12
|
Dependencies: click for CLI framework, logging for structured output, pathlib for file paths
|
|
13
13
|
|
|
14
|
-
Exports: cli (main command group), hello command, config command group,
|
|
14
|
+
Exports: cli (main command group), hello command, config command group, linter commands
|
|
15
15
|
|
|
16
16
|
Interfaces: Click CLI commands, configuration context via Click ctx, logging integration
|
|
17
17
|
|
|
@@ -1778,5 +1778,122 @@ def _execute_file_header_lint( # pylint: disable=too-many-arguments,too-many-po
|
|
|
1778
1778
|
sys.exit(1 if file_header_violations else 0)
|
|
1779
1779
|
|
|
1780
1780
|
|
|
1781
|
+
# =============================================================================
|
|
1782
|
+
# Method Property Linter Command
|
|
1783
|
+
# =============================================================================
|
|
1784
|
+
|
|
1785
|
+
|
|
1786
|
+
def _setup_method_property_orchestrator(
|
|
1787
|
+
path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
|
|
1788
|
+
):
|
|
1789
|
+
"""Set up orchestrator for method-property command."""
|
|
1790
|
+
from src.orchestrator.core import Orchestrator
|
|
1791
|
+
from src.utils.project_root import get_project_root
|
|
1792
|
+
|
|
1793
|
+
if project_root is None:
|
|
1794
|
+
first_path = path_objs[0] if path_objs else Path.cwd()
|
|
1795
|
+
search_start = first_path if first_path.is_dir() else first_path.parent
|
|
1796
|
+
project_root = get_project_root(search_start)
|
|
1797
|
+
|
|
1798
|
+
orchestrator = Orchestrator(project_root=project_root)
|
|
1799
|
+
|
|
1800
|
+
if config_file:
|
|
1801
|
+
_load_config_file(orchestrator, config_file, verbose)
|
|
1802
|
+
|
|
1803
|
+
return orchestrator
|
|
1804
|
+
|
|
1805
|
+
|
|
1806
|
+
def _run_method_property_lint(orchestrator, path_objs: list[Path], recursive: bool):
|
|
1807
|
+
"""Execute method-property lint on files or directories."""
|
|
1808
|
+
all_violations = _execute_linting_on_paths(orchestrator, path_objs, recursive)
|
|
1809
|
+
return [v for v in all_violations if "method-property" in v.rule_id]
|
|
1810
|
+
|
|
1811
|
+
|
|
1812
|
+
@cli.command("method-property")
|
|
1813
|
+
@click.argument("paths", nargs=-1, type=click.Path())
|
|
1814
|
+
@click.option("--config", "-c", "config_file", type=click.Path(), help="Path to config file")
|
|
1815
|
+
@format_option
|
|
1816
|
+
@click.option("--recursive/--no-recursive", default=True, help="Scan directories recursively")
|
|
1817
|
+
@click.pass_context
|
|
1818
|
+
def method_property(
|
|
1819
|
+
ctx,
|
|
1820
|
+
paths: tuple[str, ...],
|
|
1821
|
+
config_file: str | None,
|
|
1822
|
+
format: str,
|
|
1823
|
+
recursive: bool,
|
|
1824
|
+
):
|
|
1825
|
+
"""Check for methods that should be @property decorators.
|
|
1826
|
+
|
|
1827
|
+
Detects Python methods that could be converted to properties following
|
|
1828
|
+
Pythonic conventions:
|
|
1829
|
+
- Methods returning only self._attribute or self.attribute
|
|
1830
|
+
- get_* prefixed methods (Java-style getters)
|
|
1831
|
+
- Simple computed values with no side effects
|
|
1832
|
+
|
|
1833
|
+
PATHS: Files or directories to lint (defaults to current directory if none provided)
|
|
1834
|
+
|
|
1835
|
+
Examples:
|
|
1836
|
+
|
|
1837
|
+
\b
|
|
1838
|
+
# Check current directory (all files recursively)
|
|
1839
|
+
thai-lint method-property
|
|
1840
|
+
|
|
1841
|
+
\b
|
|
1842
|
+
# Check specific directory
|
|
1843
|
+
thai-lint method-property src/
|
|
1844
|
+
|
|
1845
|
+
\b
|
|
1846
|
+
# Check single file
|
|
1847
|
+
thai-lint method-property src/models.py
|
|
1848
|
+
|
|
1849
|
+
\b
|
|
1850
|
+
# Check multiple files
|
|
1851
|
+
thai-lint method-property src/models.py src/services.py
|
|
1852
|
+
|
|
1853
|
+
\b
|
|
1854
|
+
# Get JSON output
|
|
1855
|
+
thai-lint method-property --format json .
|
|
1856
|
+
|
|
1857
|
+
\b
|
|
1858
|
+
# Get SARIF output for CI/CD integration
|
|
1859
|
+
thai-lint method-property --format sarif src/
|
|
1860
|
+
|
|
1861
|
+
\b
|
|
1862
|
+
# Use custom config file
|
|
1863
|
+
thai-lint method-property --config .thailint.yaml src/
|
|
1864
|
+
"""
|
|
1865
|
+
verbose = ctx.obj.get("verbose", False)
|
|
1866
|
+
project_root = _get_project_root_from_context(ctx)
|
|
1867
|
+
|
|
1868
|
+
if not paths:
|
|
1869
|
+
paths = (".",)
|
|
1870
|
+
|
|
1871
|
+
path_objs = [Path(p) for p in paths]
|
|
1872
|
+
|
|
1873
|
+
try:
|
|
1874
|
+
_execute_method_property_lint(
|
|
1875
|
+
path_objs, config_file, format, recursive, verbose, project_root
|
|
1876
|
+
)
|
|
1877
|
+
except Exception as e:
|
|
1878
|
+
_handle_linting_error(e, verbose)
|
|
1879
|
+
|
|
1880
|
+
|
|
1881
|
+
def _execute_method_property_lint( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
1882
|
+
path_objs, config_file, format, recursive, verbose, project_root=None
|
|
1883
|
+
):
|
|
1884
|
+
"""Execute method-property lint."""
|
|
1885
|
+
_validate_paths_exist(path_objs)
|
|
1886
|
+
orchestrator = _setup_method_property_orchestrator(
|
|
1887
|
+
path_objs, config_file, verbose, project_root
|
|
1888
|
+
)
|
|
1889
|
+
method_property_violations = _run_method_property_lint(orchestrator, path_objs, recursive)
|
|
1890
|
+
|
|
1891
|
+
if verbose:
|
|
1892
|
+
logger.info(f"Found {len(method_property_violations)} method-property violation(s)")
|
|
1893
|
+
|
|
1894
|
+
format_violations(method_property_violations, format)
|
|
1895
|
+
sys.exit(1 if method_property_violations else 0)
|
|
1896
|
+
|
|
1897
|
+
|
|
1781
1898
|
if __name__ == "__main__":
|
|
1782
1899
|
cli()
|
|
@@ -19,7 +19,7 @@ Dependencies: PyYAML for YAML parsing with safe_load(), json (stdlib) for JSON p
|
|
|
19
19
|
Exports: LinterConfigLoader class
|
|
20
20
|
|
|
21
21
|
Interfaces: load(config_path: Path) -> dict[str, Any] for loading config files,
|
|
22
|
-
|
|
22
|
+
defaults property -> dict[str, Any] for default configuration structure
|
|
23
23
|
|
|
24
24
|
Implementation: Extension-based format detection (.yaml/.yml vs .json), yaml.safe_load()
|
|
25
25
|
for security, empty dict handling for null YAML, ValueError for unsupported formats
|
|
@@ -51,12 +51,13 @@ class LinterConfigLoader:
|
|
|
51
51
|
ConfigParseError: If file format is unsupported or parsing fails.
|
|
52
52
|
"""
|
|
53
53
|
if not config_path.exists():
|
|
54
|
-
return self.
|
|
54
|
+
return self.defaults
|
|
55
55
|
|
|
56
56
|
return parse_config_file(config_path)
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
@property
|
|
59
|
+
def defaults(self) -> dict[str, Any]:
|
|
60
|
+
"""Default configuration.
|
|
60
61
|
|
|
61
62
|
Returns:
|
|
62
63
|
Default configuration with empty rules and ignore lists.
|
|
@@ -53,9 +53,10 @@ class BaseBlockFilter(ABC):
|
|
|
53
53
|
"""
|
|
54
54
|
pass
|
|
55
55
|
|
|
56
|
+
@property
|
|
56
57
|
@abstractmethod
|
|
57
|
-
def
|
|
58
|
-
"""
|
|
58
|
+
def name(self) -> str:
|
|
59
|
+
"""Filter name for configuration and logging."""
|
|
59
60
|
pass
|
|
60
61
|
|
|
61
62
|
|
|
@@ -152,8 +153,9 @@ class KeywordArgumentFilter(BaseBlockFilter):
|
|
|
152
153
|
return False
|
|
153
154
|
return True
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
@property
|
|
157
|
+
def name(self) -> str:
|
|
158
|
+
"""Filter name."""
|
|
157
159
|
return "keyword_argument_filter"
|
|
158
160
|
|
|
159
161
|
|
|
@@ -184,8 +186,9 @@ class ImportGroupFilter(BaseBlockFilter):
|
|
|
184
186
|
|
|
185
187
|
return True
|
|
186
188
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
@property
|
|
190
|
+
def name(self) -> str:
|
|
191
|
+
"""Filter name."""
|
|
189
192
|
return "import_group_filter"
|
|
190
193
|
|
|
191
194
|
|
|
@@ -204,7 +207,7 @@ class BlockFilterRegistry:
|
|
|
204
207
|
filter_instance: Filter to register
|
|
205
208
|
"""
|
|
206
209
|
self._filters.append(filter_instance)
|
|
207
|
-
self._enabled_filters.add(filter_instance.
|
|
210
|
+
self._enabled_filters.add(filter_instance.name)
|
|
208
211
|
|
|
209
212
|
def enable_filter(self, filter_name: str) -> None:
|
|
210
213
|
"""Enable a specific filter by name.
|
|
@@ -233,7 +236,7 @@ class BlockFilterRegistry:
|
|
|
233
236
|
True if block should be filtered out
|
|
234
237
|
"""
|
|
235
238
|
for filter_instance in self._filters:
|
|
236
|
-
if filter_instance.
|
|
239
|
+
if filter_instance.name not in self._enabled_filters:
|
|
237
240
|
continue
|
|
238
241
|
|
|
239
242
|
if filter_instance.should_filter(block, file_content):
|
|
@@ -157,8 +157,9 @@ class DRYCache:
|
|
|
157
157
|
|
|
158
158
|
return blocks
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
@property
|
|
161
|
+
def duplicate_hashes(self) -> list[int]:
|
|
162
|
+
"""Hash values that appear 2+ times.
|
|
162
163
|
|
|
163
164
|
Returns:
|
|
164
165
|
List of hash values with 2 or more occurrences
|
|
@@ -11,7 +11,7 @@ Dependencies: DRYCache, CodeBlock, Path
|
|
|
11
11
|
|
|
12
12
|
Exports: DuplicateStorage class
|
|
13
13
|
|
|
14
|
-
Interfaces: DuplicateStorage.add_blocks(file_path, blocks),
|
|
14
|
+
Interfaces: DuplicateStorage.add_blocks(file_path, blocks), duplicate_hashes property,
|
|
15
15
|
get_blocks_for_hash(hash_value)
|
|
16
16
|
|
|
17
17
|
Implementation: Delegates to SQLite cache for all storage operations
|
|
@@ -43,13 +43,14 @@ class DuplicateStorage:
|
|
|
43
43
|
if blocks:
|
|
44
44
|
self._cache.add_blocks(file_path, blocks)
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
@property
|
|
47
|
+
def duplicate_hashes(self) -> list[int]:
|
|
48
|
+
"""Hash values with 2+ occurrences from SQLite.
|
|
48
49
|
|
|
49
50
|
Returns:
|
|
50
51
|
List of hash values that appear in multiple blocks
|
|
51
52
|
"""
|
|
52
|
-
return self._cache.
|
|
53
|
+
return self._cache.duplicate_hashes
|
|
53
54
|
|
|
54
55
|
def get_blocks_for_hash(self, hash_value: int) -> list[CodeBlock]:
|
|
55
56
|
"""Get all blocks with given hash value from SQLite.
|
|
@@ -55,7 +55,7 @@ class ViolationGenerator:
|
|
|
55
55
|
Returns:
|
|
56
56
|
List of violations filtered by ignore patterns and inline directives
|
|
57
57
|
"""
|
|
58
|
-
duplicate_hashes = storage.
|
|
58
|
+
duplicate_hashes = storage.duplicate_hashes
|
|
59
59
|
violations = []
|
|
60
60
|
|
|
61
61
|
for hash_value in duplicate_hashes:
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Package exports for method-should-be-property linter
|
|
3
|
+
|
|
4
|
+
Scope: Method property linter public API
|
|
5
|
+
|
|
6
|
+
Overview: Exports the MethodPropertyRule class and MethodPropertyConfig dataclass for use by
|
|
7
|
+
the orchestrator and external consumers. Provides a convenience lint() function for
|
|
8
|
+
standalone usage of the linter.
|
|
9
|
+
|
|
10
|
+
Dependencies: MethodPropertyRule from linter module, MethodPropertyConfig from config module
|
|
11
|
+
|
|
12
|
+
Exports: MethodPropertyRule, MethodPropertyConfig, lint function
|
|
13
|
+
|
|
14
|
+
Interfaces: lint(file_path, content, config) -> list[Violation] convenience function
|
|
15
|
+
|
|
16
|
+
Implementation: Simple re-exports from submodules with optional convenience wrapper
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .config import MethodPropertyConfig
|
|
20
|
+
from .linter import MethodPropertyRule
|
|
21
|
+
|
|
22
|
+
__all__ = ["MethodPropertyRule", "MethodPropertyConfig", "lint"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def lint(
|
|
26
|
+
file_path: str,
|
|
27
|
+
content: str,
|
|
28
|
+
config: dict | None = None,
|
|
29
|
+
) -> list:
|
|
30
|
+
"""Lint a file for method-should-be-property violations.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
file_path: Path to the file being linted
|
|
34
|
+
content: Content of the file
|
|
35
|
+
config: Optional configuration dictionary
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
List of Violation objects
|
|
39
|
+
"""
|
|
40
|
+
from unittest.mock import Mock
|
|
41
|
+
|
|
42
|
+
rule = MethodPropertyRule()
|
|
43
|
+
context = Mock()
|
|
44
|
+
context.file_path = file_path
|
|
45
|
+
context.file_content = content
|
|
46
|
+
context.language = "python"
|
|
47
|
+
context.config = config
|
|
48
|
+
|
|
49
|
+
return rule.check(context)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Configuration schema for method-should-be-property linter
|
|
3
|
+
|
|
4
|
+
Scope: Method property linter configuration for Python files
|
|
5
|
+
|
|
6
|
+
Overview: Defines configuration schema for method-should-be-property linter. Provides
|
|
7
|
+
MethodPropertyConfig dataclass with enabled flag, max_body_statements threshold (default 3)
|
|
8
|
+
for determining when a method body is too complex to be a property candidate, and ignore
|
|
9
|
+
patterns list for excluding specific files or directories. Includes configurable action verb
|
|
10
|
+
exclusions (prefixes and names) with sensible defaults that can be extended or overridden.
|
|
11
|
+
Supports per-file and per-directory config overrides through from_dict class method.
|
|
12
|
+
Integrates with orchestrator's configuration system via .thailint.yaml.
|
|
13
|
+
|
|
14
|
+
Dependencies: dataclasses module for configuration structure, typing module for type hints
|
|
15
|
+
|
|
16
|
+
Exports: MethodPropertyConfig dataclass, DEFAULT_EXCLUDE_PREFIXES, DEFAULT_EXCLUDE_NAMES
|
|
17
|
+
|
|
18
|
+
Interfaces: from_dict(config, language) -> MethodPropertyConfig for configuration loading
|
|
19
|
+
|
|
20
|
+
Implementation: Dataclass with defaults matching Pythonic conventions and common use cases
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
# Default action verb prefixes - methods starting with these are excluded
|
|
27
|
+
# These represent actions/transformations, not property access
|
|
28
|
+
DEFAULT_EXCLUDE_PREFIXES: tuple[str, ...] = (
|
|
29
|
+
"to_", # Transformation: to_dict, to_json, to_string
|
|
30
|
+
"dump_", # Serialization: dump_to_json, dump_to_apigw
|
|
31
|
+
"generate_", # Factory: generate_report, generate_html
|
|
32
|
+
"create_", # Factory: create_instance, create_config
|
|
33
|
+
"build_", # Construction: build_query, build_html
|
|
34
|
+
"make_", # Factory: make_request, make_connection
|
|
35
|
+
"render_", # Output: render_template, render_html
|
|
36
|
+
"compute_", # Calculation: compute_hash, compute_total
|
|
37
|
+
"calculate_", # Calculation: calculate_sum, calculate_average
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Default action verb names - exact method names that are excluded
|
|
41
|
+
# These are lifecycle hooks, display actions, and resource operations
|
|
42
|
+
DEFAULT_EXCLUDE_NAMES: frozenset[str] = frozenset(
|
|
43
|
+
{
|
|
44
|
+
"finalize", # Lifecycle hook
|
|
45
|
+
"serialize", # Transformation
|
|
46
|
+
"dump", # Serialization
|
|
47
|
+
"validate", # Validation action
|
|
48
|
+
"show", # Display action
|
|
49
|
+
"display", # Display action
|
|
50
|
+
"print", # Output action
|
|
51
|
+
"refresh", # Update action
|
|
52
|
+
"reset", # State action
|
|
53
|
+
"clear", # State action
|
|
54
|
+
"close", # Resource action
|
|
55
|
+
"open", # Resource action
|
|
56
|
+
"save", # Persistence action
|
|
57
|
+
"load", # Persistence action
|
|
58
|
+
"execute", # Action
|
|
59
|
+
"run", # Action
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _load_list_config(
|
|
65
|
+
config: dict[str, Any], key: str, override_key: str, default: tuple[str, ...]
|
|
66
|
+
) -> tuple[str, ...]:
|
|
67
|
+
"""Load a list config with extend/override semantics."""
|
|
68
|
+
if override_key in config and isinstance(config[override_key], list):
|
|
69
|
+
return tuple(config[override_key])
|
|
70
|
+
if key in config and isinstance(config[key], list):
|
|
71
|
+
return default + tuple(config[key])
|
|
72
|
+
return default
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _load_set_config(
|
|
76
|
+
config: dict[str, Any], key: str, override_key: str, default: frozenset[str]
|
|
77
|
+
) -> frozenset[str]:
|
|
78
|
+
"""Load a set config with extend/override semantics."""
|
|
79
|
+
if override_key in config and isinstance(config[override_key], list):
|
|
80
|
+
return frozenset(config[override_key])
|
|
81
|
+
if key in config and isinstance(config[key], list):
|
|
82
|
+
return default | frozenset(config[key])
|
|
83
|
+
return default
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class MethodPropertyConfig: # thailint: ignore[dry]
|
|
88
|
+
"""Configuration for method-should-be-property linter."""
|
|
89
|
+
|
|
90
|
+
enabled: bool = True
|
|
91
|
+
max_body_statements: int = 3
|
|
92
|
+
ignore: list[str] = field(default_factory=list)
|
|
93
|
+
ignore_methods: list[str] = field(default_factory=list)
|
|
94
|
+
|
|
95
|
+
# Action verb exclusions (extend defaults or override)
|
|
96
|
+
exclude_prefixes: tuple[str, ...] = DEFAULT_EXCLUDE_PREFIXES
|
|
97
|
+
exclude_names: frozenset[str] = DEFAULT_EXCLUDE_NAMES
|
|
98
|
+
|
|
99
|
+
# dry: ignore-block
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_dict(
|
|
102
|
+
cls, config: dict[str, Any] | None, language: str | None = None
|
|
103
|
+
) -> "MethodPropertyConfig":
|
|
104
|
+
"""Load configuration from dictionary.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
config: Dictionary containing configuration values, or None
|
|
108
|
+
language: Programming language (unused, for interface compatibility)
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
MethodPropertyConfig instance with values from dictionary
|
|
112
|
+
"""
|
|
113
|
+
if config is None:
|
|
114
|
+
return cls()
|
|
115
|
+
|
|
116
|
+
ignore_patterns = config.get("ignore", [])
|
|
117
|
+
if not isinstance(ignore_patterns, list):
|
|
118
|
+
ignore_patterns = []
|
|
119
|
+
|
|
120
|
+
ignore_methods = config.get("ignore_methods", [])
|
|
121
|
+
if not isinstance(ignore_methods, list):
|
|
122
|
+
ignore_methods = []
|
|
123
|
+
|
|
124
|
+
return cls(
|
|
125
|
+
enabled=config.get("enabled", True),
|
|
126
|
+
max_body_statements=config.get("max_body_statements", 3),
|
|
127
|
+
ignore=ignore_patterns,
|
|
128
|
+
ignore_methods=ignore_methods,
|
|
129
|
+
exclude_prefixes=_load_list_config(
|
|
130
|
+
config, "exclude_prefixes", "exclude_prefixes_override", DEFAULT_EXCLUDE_PREFIXES
|
|
131
|
+
),
|
|
132
|
+
exclude_names=_load_set_config(
|
|
133
|
+
config, "exclude_names", "exclude_names_override", DEFAULT_EXCLUDE_NAMES
|
|
134
|
+
),
|
|
135
|
+
)
|