thailint 0.12.0__py3-none-any.whl → 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- src/analyzers/__init__.py +4 -3
- src/analyzers/ast_utils.py +54 -0
- src/analyzers/typescript_base.py +4 -0
- src/cli/__init__.py +3 -0
- src/cli/config.py +12 -12
- src/cli/config_merge.py +241 -0
- src/cli/linters/__init__.py +3 -0
- src/cli/linters/code_patterns.py +113 -5
- src/cli/linters/code_smells.py +4 -0
- src/cli/linters/documentation.py +3 -0
- src/cli/linters/structure.py +3 -0
- src/cli/linters/structure_quality.py +3 -0
- src/cli_main.py +3 -0
- src/config.py +2 -1
- src/core/base.py +3 -2
- src/core/cli_utils.py +3 -1
- src/core/config_parser.py +5 -2
- src/core/constants.py +54 -0
- src/core/linter_utils.py +4 -0
- src/core/rule_discovery.py +5 -1
- src/core/violation_builder.py +3 -0
- src/linter_config/directive_markers.py +109 -0
- src/linter_config/ignore.py +225 -383
- src/linter_config/pattern_utils.py +65 -0
- src/linter_config/rule_matcher.py +89 -0
- src/linters/collection_pipeline/any_all_analyzer.py +281 -0
- src/linters/collection_pipeline/ast_utils.py +40 -0
- src/linters/collection_pipeline/config.py +12 -0
- src/linters/collection_pipeline/continue_analyzer.py +2 -8
- src/linters/collection_pipeline/detector.py +262 -32
- src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
- src/linters/collection_pipeline/linter.py +18 -35
- src/linters/collection_pipeline/suggestion_builder.py +68 -1
- src/linters/dry/base_token_analyzer.py +16 -9
- src/linters/dry/block_filter.py +7 -4
- src/linters/dry/cache.py +7 -2
- src/linters/dry/config.py +7 -1
- src/linters/dry/constant_matcher.py +34 -25
- src/linters/dry/file_analyzer.py +4 -2
- src/linters/dry/inline_ignore.py +7 -16
- src/linters/dry/linter.py +48 -25
- src/linters/dry/python_analyzer.py +18 -10
- src/linters/dry/python_constant_extractor.py +51 -52
- src/linters/dry/single_statement_detector.py +14 -12
- src/linters/dry/token_hasher.py +115 -115
- src/linters/dry/typescript_analyzer.py +11 -6
- src/linters/dry/typescript_constant_extractor.py +4 -0
- src/linters/dry/typescript_statement_detector.py +208 -208
- src/linters/dry/typescript_value_extractor.py +3 -0
- src/linters/dry/violation_filter.py +1 -4
- src/linters/dry/violation_generator.py +1 -4
- src/linters/file_header/atemporal_detector.py +4 -0
- src/linters/file_header/base_parser.py +4 -0
- src/linters/file_header/bash_parser.py +4 -0
- src/linters/file_header/field_validator.py +5 -8
- src/linters/file_header/linter.py +19 -12
- src/linters/file_header/markdown_parser.py +6 -0
- src/linters/file_placement/config_loader.py +3 -1
- src/linters/file_placement/linter.py +22 -8
- src/linters/file_placement/pattern_matcher.py +21 -4
- src/linters/file_placement/pattern_validator.py +21 -7
- src/linters/file_placement/rule_checker.py +2 -2
- src/linters/lazy_ignores/__init__.py +43 -0
- src/linters/lazy_ignores/config.py +66 -0
- src/linters/lazy_ignores/directive_utils.py +121 -0
- src/linters/lazy_ignores/header_parser.py +177 -0
- src/linters/lazy_ignores/linter.py +158 -0
- src/linters/lazy_ignores/matcher.py +135 -0
- src/linters/lazy_ignores/python_analyzer.py +201 -0
- src/linters/lazy_ignores/rule_id_utils.py +180 -0
- src/linters/lazy_ignores/skip_detector.py +298 -0
- src/linters/lazy_ignores/types.py +67 -0
- src/linters/lazy_ignores/typescript_analyzer.py +146 -0
- src/linters/lazy_ignores/violation_builder.py +131 -0
- src/linters/lbyl/__init__.py +29 -0
- src/linters/lbyl/config.py +63 -0
- src/linters/lbyl/pattern_detectors/__init__.py +25 -0
- src/linters/lbyl/pattern_detectors/base.py +46 -0
- src/linters/magic_numbers/context_analyzer.py +227 -229
- src/linters/magic_numbers/linter.py +20 -15
- src/linters/magic_numbers/python_analyzer.py +4 -16
- src/linters/magic_numbers/typescript_analyzer.py +9 -16
- src/linters/method_property/config.py +4 -0
- src/linters/method_property/linter.py +5 -4
- src/linters/method_property/python_analyzer.py +5 -4
- src/linters/method_property/violation_builder.py +3 -0
- src/linters/nesting/typescript_analyzer.py +6 -12
- src/linters/nesting/typescript_function_extractor.py +0 -4
- src/linters/print_statements/linter.py +6 -4
- src/linters/print_statements/python_analyzer.py +85 -81
- src/linters/print_statements/typescript_analyzer.py +6 -15
- src/linters/srp/heuristics.py +4 -4
- src/linters/srp/linter.py +12 -12
- src/linters/srp/violation_builder.py +0 -4
- src/linters/stateless_class/linter.py +30 -36
- src/linters/stateless_class/python_analyzer.py +11 -20
- src/linters/stringly_typed/config.py +4 -5
- src/linters/stringly_typed/context_filter.py +410 -410
- src/linters/stringly_typed/function_call_violation_builder.py +93 -95
- src/linters/stringly_typed/linter.py +48 -16
- src/linters/stringly_typed/python/analyzer.py +5 -1
- src/linters/stringly_typed/python/call_tracker.py +8 -5
- src/linters/stringly_typed/python/comparison_tracker.py +10 -5
- src/linters/stringly_typed/python/condition_extractor.py +3 -0
- src/linters/stringly_typed/python/conditional_detector.py +4 -1
- src/linters/stringly_typed/python/match_analyzer.py +8 -2
- src/linters/stringly_typed/python/validation_detector.py +3 -0
- src/linters/stringly_typed/storage.py +14 -14
- src/linters/stringly_typed/typescript/call_tracker.py +9 -3
- src/linters/stringly_typed/typescript/comparison_tracker.py +9 -3
- src/linters/stringly_typed/violation_generator.py +288 -259
- src/orchestrator/core.py +13 -4
- src/templates/thailint_config_template.yaml +166 -0
- src/utils/project_root.py +3 -0
- thailint-0.13.0.dist-info/METADATA +184 -0
- thailint-0.13.0.dist-info/RECORD +189 -0
- thailint-0.12.0.dist-info/METADATA +0 -1667
- thailint-0.12.0.dist-info/RECORD +0 -164
- {thailint-0.12.0.dist-info → thailint-0.13.0.dist-info}/WHEEL +0 -0
- {thailint-0.12.0.dist-info → thailint-0.13.0.dist-info}/entry_points.txt +0 -0
- {thailint-0.12.0.dist-info → thailint-0.13.0.dist-info}/licenses/LICENSE +0 -0
src/core/config_parser.py
CHANGED
|
@@ -27,6 +27,8 @@ from typing import Any, TextIO
|
|
|
27
27
|
|
|
28
28
|
import yaml
|
|
29
29
|
|
|
30
|
+
from src.core.constants import CONFIG_EXTENSIONS
|
|
31
|
+
|
|
30
32
|
|
|
31
33
|
class ConfigParseError(Exception):
|
|
32
34
|
"""Configuration file parsing errors."""
|
|
@@ -113,11 +115,12 @@ def parse_config_file(path: Path, encoding: str = "utf-8") -> dict[str, Any]:
|
|
|
113
115
|
"""
|
|
114
116
|
suffix = path.suffix.lower()
|
|
115
117
|
|
|
116
|
-
|
|
118
|
+
valid_suffixes = (*CONFIG_EXTENSIONS, ".json")
|
|
119
|
+
if suffix not in valid_suffixes:
|
|
117
120
|
raise ConfigParseError(f"Unsupported config format: {suffix}")
|
|
118
121
|
|
|
119
122
|
with path.open(encoding=encoding) as f:
|
|
120
|
-
if suffix in
|
|
123
|
+
if suffix in CONFIG_EXTENSIONS:
|
|
121
124
|
config = parse_yaml(f, path)
|
|
122
125
|
else:
|
|
123
126
|
config = parse_json(f, path)
|
src/core/constants.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Core constants and enums used across the thai-lint codebase
|
|
3
|
+
|
|
4
|
+
Scope: Centralized definitions for language names, storage modes, config extensions
|
|
5
|
+
|
|
6
|
+
Overview: Provides type-safe enums and constants for consistent stringly-typed patterns
|
|
7
|
+
across the codebase. Includes Language enum for programming language detection,
|
|
8
|
+
StorageMode for cache storage options, and CONFIG_EXTENSIONS for config file
|
|
9
|
+
discovery. Using enums ensures compile-time safety and IDE autocompletion.
|
|
10
|
+
|
|
11
|
+
Dependencies: enum module
|
|
12
|
+
|
|
13
|
+
Exports: Language enum, StorageMode enum, CONFIG_EXTENSIONS, IgnoreDirective enum,
|
|
14
|
+
HEADER_SCAN_LINES, MAX_ATTRIBUTE_CHAIN_DEPTH
|
|
15
|
+
|
|
16
|
+
Interfaces: Use enum values instead of string literals throughout codebase
|
|
17
|
+
|
|
18
|
+
Implementation: Standard Python enums with string values for compatibility
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from enum import Enum
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Language(str, Enum):
|
|
25
|
+
"""Supported programming languages for linting."""
|
|
26
|
+
|
|
27
|
+
PYTHON = "python"
|
|
28
|
+
TYPESCRIPT = "typescript"
|
|
29
|
+
JAVASCRIPT = "javascript"
|
|
30
|
+
MARKDOWN = "markdown"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class StorageMode(str, Enum):
|
|
34
|
+
"""Storage modes for DRY linter cache."""
|
|
35
|
+
|
|
36
|
+
MEMORY = "memory"
|
|
37
|
+
TEMPFILE = "tempfile"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class IgnoreDirective(str, Enum):
|
|
41
|
+
"""Inline ignore directive types."""
|
|
42
|
+
|
|
43
|
+
IGNORE = "ignore"
|
|
44
|
+
IGNORE_FILE = "ignore-file"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Valid config file extensions
|
|
48
|
+
CONFIG_EXTENSIONS: tuple[str, str] = (".yaml", ".yml")
|
|
49
|
+
|
|
50
|
+
# Number of lines to scan at file start for ignore directives and headers
|
|
51
|
+
HEADER_SCAN_LINES: int = 10
|
|
52
|
+
|
|
53
|
+
# Maximum depth for attribute chain traversal (e.g., obj.attr.attr2.attr3)
|
|
54
|
+
MAX_ATTRIBUTE_CHAIN_DEPTH: int = 3
|
src/core/linter_utils.py
CHANGED
|
@@ -16,6 +16,10 @@ Exports: get_metadata, get_metadata_value, load_linter_config, has_file_content
|
|
|
16
16
|
Interfaces: All functions take BaseLintContext and return typed values (dict, str, bool, Any)
|
|
17
17
|
|
|
18
18
|
Implementation: Type-safe metadata access with fallbacks, generic config loading with language support
|
|
19
|
+
|
|
20
|
+
Suppressions:
|
|
21
|
+
- invalid-name: T type variable follows Python generic naming convention
|
|
22
|
+
- type:ignore[return-value]: Generic config factory with runtime type checking
|
|
19
23
|
"""
|
|
20
24
|
|
|
21
25
|
from typing import Any, Protocol, TypeVar
|
src/core/rule_discovery.py
CHANGED
|
@@ -19,12 +19,15 @@ Implementation: Package traversal with pkgutil, class introspection with inspect
|
|
|
19
19
|
|
|
20
20
|
import importlib
|
|
21
21
|
import inspect
|
|
22
|
+
import logging
|
|
22
23
|
import pkgutil
|
|
23
24
|
from types import ModuleType
|
|
24
25
|
from typing import Any
|
|
25
26
|
|
|
26
27
|
from .base import BaseLintRule
|
|
27
28
|
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
28
31
|
|
|
29
32
|
def discover_from_package(package_path: str) -> list[BaseLintRule]:
|
|
30
33
|
"""Discover rules from a package and its modules.
|
|
@@ -37,7 +40,8 @@ def discover_from_package(package_path: str) -> list[BaseLintRule]:
|
|
|
37
40
|
"""
|
|
38
41
|
try:
|
|
39
42
|
package = importlib.import_module(package_path)
|
|
40
|
-
except ImportError:
|
|
43
|
+
except ImportError as e:
|
|
44
|
+
logger.debug("Failed to import package %s: %s", package_path, e)
|
|
41
45
|
return []
|
|
42
46
|
|
|
43
47
|
if not hasattr(package, "__path__"):
|
src/core/violation_builder.py
CHANGED
|
@@ -21,6 +21,9 @@ Interfaces: ViolationInfo(rule_id, file_path, line, message, column, severity),
|
|
|
21
21
|
|
|
22
22
|
Implementation: Uses dataclass for type-safe violation info, functions provide build logic
|
|
23
23
|
that constructs Violation objects with proper defaults
|
|
24
|
+
|
|
25
|
+
Suppressions:
|
|
26
|
+
- too-many-arguments,too-many-positional-arguments: Violation fields as parameters
|
|
24
27
|
"""
|
|
25
28
|
|
|
26
29
|
from dataclasses import dataclass
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Ignore directive marker detection for thailint comments
|
|
3
|
+
|
|
4
|
+
Scope: Detection of thailint and design-lint ignore markers in source code
|
|
5
|
+
|
|
6
|
+
Overview: Provides functions for detecting various ignore directive markers in code
|
|
7
|
+
comments. Supports file-level ignores, line-level ignores, block ignores, and
|
|
8
|
+
next-line ignores. Works with both Python (#) and JavaScript (//) comment styles.
|
|
9
|
+
All checks are case-insensitive.
|
|
10
|
+
|
|
11
|
+
Dependencies: None (pure string operations)
|
|
12
|
+
|
|
13
|
+
Exports: Marker detection functions for various ignore directive types
|
|
14
|
+
|
|
15
|
+
Interfaces: has_*_marker(line) -> bool for each marker type
|
|
16
|
+
|
|
17
|
+
Implementation: String-based pattern detection with case-insensitive matching
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def has_ignore_directive_marker(line: str) -> bool:
|
|
22
|
+
"""Check if line contains a file-level ignore directive marker.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
line: Line of code to check
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
True if line has ignore-file marker
|
|
29
|
+
"""
|
|
30
|
+
line_lower = line.lower()
|
|
31
|
+
return "# thailint: ignore-file" in line_lower or "# design-lint: ignore-file" in line_lower
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def has_line_ignore_marker(code: str) -> bool:
|
|
35
|
+
"""Check if code line has an inline ignore marker.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
code: Line of code to check
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if line has inline ignore marker
|
|
42
|
+
"""
|
|
43
|
+
code_lower = code.lower()
|
|
44
|
+
return (
|
|
45
|
+
"# thailint: ignore" in code_lower
|
|
46
|
+
or "# design-lint: ignore" in code_lower
|
|
47
|
+
or "// thailint: ignore" in code_lower
|
|
48
|
+
or "// design-lint: ignore" in code_lower
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def has_ignore_next_line_marker(line: str) -> bool:
|
|
53
|
+
"""Check if line has ignore-next-line marker.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
line: Line of code to check
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
True if line has ignore-next-line marker
|
|
60
|
+
"""
|
|
61
|
+
return "# thailint: ignore-next-line" in line or "# design-lint: ignore-next-line" in line
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def has_ignore_start_marker(line: str) -> bool:
|
|
65
|
+
"""Check if line has ignore-start comment marker.
|
|
66
|
+
|
|
67
|
+
Only matches actual comment lines (starting with # or //), not strings
|
|
68
|
+
containing the marker text.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
line: Line of code to check
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
True if line is a proper ignore-start comment
|
|
75
|
+
"""
|
|
76
|
+
stripped = line.strip().lower()
|
|
77
|
+
if not (stripped.startswith("#") or stripped.startswith("//")):
|
|
78
|
+
return False
|
|
79
|
+
return "ignore-start" in stripped and ("thailint:" in stripped or "design-lint:" in stripped)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def has_ignore_end_marker(line: str) -> bool:
|
|
83
|
+
"""Check if line has ignore-end comment marker.
|
|
84
|
+
|
|
85
|
+
Only matches actual comment lines (starting with # or //), not strings
|
|
86
|
+
containing the marker text.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
line: Line of code to check
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True if line is a proper ignore-end comment
|
|
93
|
+
"""
|
|
94
|
+
stripped = line.strip().lower()
|
|
95
|
+
if not (stripped.startswith("#") or stripped.startswith("//")):
|
|
96
|
+
return False
|
|
97
|
+
return "ignore-end" in stripped and ("thailint:" in stripped or "design-lint:" in stripped)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def check_general_ignore(line: str) -> bool:
|
|
101
|
+
"""Check if line has general ignore directive (no specific rules).
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
line: Line containing ignore directive
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
True if no specific rules are specified (not bracket syntax)
|
|
108
|
+
"""
|
|
109
|
+
return "ignore-file[" not in line
|