thailint 0.12.0__py3-none-any.whl → 0.14.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 +9 -0
- src/cli/linters/code_patterns.py +107 -257
- src/cli/linters/code_smells.py +48 -165
- src/cli/linters/documentation.py +21 -95
- src/cli/linters/performance.py +274 -0
- src/cli/linters/shared.py +232 -6
- src/cli/linters/structure.py +26 -21
- src/cli/linters/structure_quality.py +28 -21
- 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 +95 -6
- 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 +58 -40
- src/linters/file_header/base_parser.py +4 -0
- src/linters/file_header/bash_parser.py +4 -0
- src/linters/file_header/config.py +14 -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 +205 -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 +69 -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 -1
- src/linters/method_property/linter.py +5 -10
- src/linters/method_property/python_analyzer.py +5 -4
- src/linters/method_property/violation_builder.py +3 -0
- src/linters/nesting/linter.py +11 -6
- src/linters/nesting/typescript_analyzer.py +6 -12
- src/linters/nesting/typescript_function_extractor.py +0 -4
- src/linters/nesting/violation_builder.py +1 -0
- src/linters/performance/__init__.py +91 -0
- src/linters/performance/config.py +43 -0
- src/linters/performance/constants.py +49 -0
- src/linters/performance/linter.py +149 -0
- src/linters/performance/python_analyzer.py +365 -0
- src/linters/performance/regex_analyzer.py +312 -0
- src/linters/performance/regex_linter.py +139 -0
- src/linters/performance/typescript_analyzer.py +236 -0
- src/linters/performance/violation_builder.py +160 -0
- 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 +196 -0
- src/utils/project_root.py +3 -0
- thailint-0.14.0.dist-info/METADATA +185 -0
- thailint-0.14.0.dist-info/RECORD +199 -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.14.0.dist-info}/WHEEL +0 -0
- {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/entry_points.txt +0 -0
- {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Build agent-friendly violation messages for lazy-ignores linter
|
|
3
|
+
|
|
4
|
+
Scope: Violation construction for unjustified ignores and orphaned header suppressions
|
|
5
|
+
|
|
6
|
+
Overview: Provides functions to construct Violation objects with AI-agent-friendly error
|
|
7
|
+
messages. Messages include explicit guidance for adding Suppressions section entries to
|
|
8
|
+
file headers and emphasize the requirement for human approval before adding suppressions.
|
|
9
|
+
Designed to help AI coding assistants understand the proper workflow for handling linting
|
|
10
|
+
suppressions rather than blindly adding ignore directives.
|
|
11
|
+
|
|
12
|
+
Dependencies: src.core.types for Violation dataclass
|
|
13
|
+
|
|
14
|
+
Exports: build_unjustified_violation, build_orphaned_violation
|
|
15
|
+
|
|
16
|
+
Interfaces: Two builder functions returning Violation objects
|
|
17
|
+
|
|
18
|
+
Implementation: Template-based message construction with rule ID formatting
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from src.core.types import Severity, Violation
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def build_unjustified_violation(
|
|
25
|
+
file_path: str,
|
|
26
|
+
line: int,
|
|
27
|
+
column: int,
|
|
28
|
+
rule_id: str,
|
|
29
|
+
raw_text: str,
|
|
30
|
+
) -> Violation:
|
|
31
|
+
"""Create violation for an ignore directive without header justification.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
file_path: Path to the file containing the violation.
|
|
35
|
+
line: Line number where the ignore was found (1-indexed).
|
|
36
|
+
column: Column number where the ignore starts (0-indexed).
|
|
37
|
+
rule_id: The rule ID(s) being suppressed (e.g., "PLR0912").
|
|
38
|
+
raw_text: The raw ignore directive text found in code.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Violation object with agent-friendly guidance message.
|
|
42
|
+
"""
|
|
43
|
+
message = (
|
|
44
|
+
f"Unjustified suppression found: {raw_text} "
|
|
45
|
+
f"(ASK PERMISSION before adding Suppressions header)"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
suggestion = _build_unjustified_suggestion(rule_id)
|
|
49
|
+
|
|
50
|
+
return Violation(
|
|
51
|
+
rule_id="lazy-ignores.unjustified",
|
|
52
|
+
file_path=file_path,
|
|
53
|
+
line=line,
|
|
54
|
+
column=column,
|
|
55
|
+
message=message,
|
|
56
|
+
severity=Severity.ERROR,
|
|
57
|
+
suggestion=suggestion,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _build_unjustified_suggestion(rule_id: str) -> str:
|
|
62
|
+
"""Build the suggestion text for unjustified violations.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
rule_id: The rule ID(s) being suppressed.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Formatted suggestion string with header instructions.
|
|
69
|
+
"""
|
|
70
|
+
# Handle multiple rules (e.g., "PLR0912, PLR0915")
|
|
71
|
+
rule_ids = [r.strip() for r in rule_id.split(",")]
|
|
72
|
+
|
|
73
|
+
suppression_entries = "\n".join(f" {rid}: [Your justification here]" for rid in rule_ids)
|
|
74
|
+
|
|
75
|
+
return f"""To fix, add an entry to the file header Suppressions section:
|
|
76
|
+
|
|
77
|
+
Suppressions:
|
|
78
|
+
{suppression_entries}
|
|
79
|
+
|
|
80
|
+
IMPORTANT: Adding suppressions requires human approval.
|
|
81
|
+
Do not add this entry without explicit permission from a human reviewer.
|
|
82
|
+
Ask first, then add if approved."""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_orphaned_violation(
|
|
86
|
+
file_path: str,
|
|
87
|
+
header_line: int,
|
|
88
|
+
rule_id: str,
|
|
89
|
+
justification: str,
|
|
90
|
+
) -> Violation:
|
|
91
|
+
"""Create violation for a header entry without matching code ignore.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
file_path: Path to the file containing the orphaned entry.
|
|
95
|
+
header_line: Line number of the suppression in the header (1-indexed).
|
|
96
|
+
rule_id: The orphaned rule ID from the header.
|
|
97
|
+
justification: The justification text from the header.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Violation object suggesting removal of the orphaned entry.
|
|
101
|
+
"""
|
|
102
|
+
message = f"Orphaned suppression in header: {rule_id}: {justification}"
|
|
103
|
+
|
|
104
|
+
suggestion = _build_orphaned_suggestion(rule_id)
|
|
105
|
+
|
|
106
|
+
return Violation(
|
|
107
|
+
rule_id="lazy-ignores.orphaned",
|
|
108
|
+
file_path=file_path,
|
|
109
|
+
line=header_line,
|
|
110
|
+
column=0,
|
|
111
|
+
message=message,
|
|
112
|
+
severity=Severity.ERROR,
|
|
113
|
+
suggestion=suggestion,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _build_orphaned_suggestion(rule_id: str) -> str:
|
|
118
|
+
"""Build the suggestion text for orphaned violations.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
rule_id: The orphaned rule ID.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Formatted suggestion string with removal instructions.
|
|
125
|
+
"""
|
|
126
|
+
return f"""This rule is declared in the Suppressions section but no matching
|
|
127
|
+
ignore directive was found in the code.
|
|
128
|
+
|
|
129
|
+
Either:
|
|
130
|
+
1. Remove the entry for {rule_id} from the Suppressions section if the ignore was removed from code
|
|
131
|
+
2. Add the ignore directive if it's missing from the code"""
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: LBYL (Look Before You Leap) linter package exports
|
|
3
|
+
|
|
4
|
+
Scope: Detect LBYL anti-patterns in Python code and suggest EAFP alternatives
|
|
5
|
+
|
|
6
|
+
Overview: Package providing LBYL pattern detection for Python code. Identifies common
|
|
7
|
+
anti-patterns where explicit checks are performed before operations (e.g., if key in
|
|
8
|
+
dict before dict[key]) and suggests EAFP (Easier to Ask Forgiveness than Permission)
|
|
9
|
+
alternatives using try/except blocks. Supports 8 pattern types including dict key
|
|
10
|
+
checking, hasattr, isinstance, file exists, length checks, None checks, string
|
|
11
|
+
validation, and division safety checks.
|
|
12
|
+
|
|
13
|
+
Dependencies: ast module for Python parsing, src.core for base classes
|
|
14
|
+
|
|
15
|
+
Exports: LBYLConfig, LBYLPattern, BaseLBYLDetector
|
|
16
|
+
|
|
17
|
+
Interfaces: LBYLConfig.from_dict() for YAML configuration loading
|
|
18
|
+
|
|
19
|
+
Implementation: AST-based pattern detection with configurable pattern toggles
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .config import LBYLConfig
|
|
23
|
+
from .pattern_detectors.base import BaseLBYLDetector, LBYLPattern
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"LBYLConfig",
|
|
27
|
+
"LBYLPattern",
|
|
28
|
+
"BaseLBYLDetector",
|
|
29
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Configuration dataclass for LBYL linter
|
|
3
|
+
|
|
4
|
+
Scope: Pattern toggles, ignore patterns, and validation
|
|
5
|
+
|
|
6
|
+
Overview: Provides LBYLConfig dataclass with pattern-specific toggles for each LBYL
|
|
7
|
+
pattern type (dict_key, hasattr, isinstance, file_exists, len_check, none_check,
|
|
8
|
+
string_validation, division_check). Some patterns like isinstance and none_check
|
|
9
|
+
are disabled by default due to many valid use cases. Configuration can be loaded
|
|
10
|
+
from dictionary (YAML) with sensible defaults.
|
|
11
|
+
|
|
12
|
+
Dependencies: dataclasses, typing
|
|
13
|
+
|
|
14
|
+
Exports: LBYLConfig
|
|
15
|
+
|
|
16
|
+
Interfaces: LBYLConfig.from_dict() for YAML configuration loading
|
|
17
|
+
|
|
18
|
+
Implementation: Dataclass with factory defaults and conservative default settings
|
|
19
|
+
|
|
20
|
+
Suppressions:
|
|
21
|
+
too-many-instance-attributes: Configuration dataclass requires many toggles
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class LBYLConfig: # pylint: disable=too-many-instance-attributes
|
|
30
|
+
"""Configuration for LBYL linter."""
|
|
31
|
+
|
|
32
|
+
enabled: bool = True
|
|
33
|
+
|
|
34
|
+
# Pattern toggles
|
|
35
|
+
detect_dict_key: bool = True
|
|
36
|
+
detect_hasattr: bool = True
|
|
37
|
+
detect_isinstance: bool = False # Disabled - many valid uses for type narrowing
|
|
38
|
+
detect_file_exists: bool = True
|
|
39
|
+
detect_len_check: bool = True
|
|
40
|
+
detect_none_check: bool = False # Disabled - many valid uses
|
|
41
|
+
detect_string_validation: bool = True
|
|
42
|
+
detect_division_check: bool = True
|
|
43
|
+
|
|
44
|
+
# File patterns to ignore
|
|
45
|
+
ignore: list[str] = field(default_factory=list)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_dict(cls, config: dict[str, Any], language: str | None = None) -> "LBYLConfig":
|
|
49
|
+
"""Load configuration from dictionary."""
|
|
50
|
+
# Language parameter reserved for future multi-language support
|
|
51
|
+
_ = language
|
|
52
|
+
return cls(
|
|
53
|
+
enabled=config.get("enabled", True),
|
|
54
|
+
detect_dict_key=config.get("detect_dict_key", True),
|
|
55
|
+
detect_hasattr=config.get("detect_hasattr", True),
|
|
56
|
+
detect_isinstance=config.get("detect_isinstance", False),
|
|
57
|
+
detect_file_exists=config.get("detect_file_exists", True),
|
|
58
|
+
detect_len_check=config.get("detect_len_check", True),
|
|
59
|
+
detect_none_check=config.get("detect_none_check", False),
|
|
60
|
+
detect_string_validation=config.get("detect_string_validation", True),
|
|
61
|
+
detect_division_check=config.get("detect_division_check", True),
|
|
62
|
+
ignore=config.get("ignore", []),
|
|
63
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Pattern detector exports for LBYL linter
|
|
3
|
+
|
|
4
|
+
Scope: All AST-based pattern detectors for LBYL anti-pattern detection
|
|
5
|
+
|
|
6
|
+
Overview: Exports pattern detector classes for the LBYL linter. Each detector is an
|
|
7
|
+
AST NodeVisitor that identifies specific LBYL anti-patterns. Detectors include
|
|
8
|
+
dict key checking, hasattr, isinstance, file exists, length checks, None checks,
|
|
9
|
+
string validation, and division safety checks.
|
|
10
|
+
|
|
11
|
+
Dependencies: ast module, base detector class
|
|
12
|
+
|
|
13
|
+
Exports: BaseLBYLDetector, LBYLPattern
|
|
14
|
+
|
|
15
|
+
Interfaces: find_patterns(tree: ast.AST) -> list[LBYLPattern]
|
|
16
|
+
|
|
17
|
+
Implementation: Modular detector pattern for extensible LBYL detection
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .base import BaseLBYLDetector, LBYLPattern
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"BaseLBYLDetector",
|
|
24
|
+
"LBYLPattern",
|
|
25
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Base class for LBYL pattern detectors
|
|
3
|
+
|
|
4
|
+
Scope: Abstract base providing common detector interface
|
|
5
|
+
|
|
6
|
+
Overview: Defines BaseLBYLDetector abstract class that all pattern detectors extend.
|
|
7
|
+
Inherits from ast.NodeVisitor for AST traversal. Defines LBYLPattern base dataclass
|
|
8
|
+
for representing detected patterns with line number and column information. Each
|
|
9
|
+
concrete detector implements find_patterns() to identify specific LBYL anti-patterns.
|
|
10
|
+
|
|
11
|
+
Dependencies: abc, ast, dataclasses
|
|
12
|
+
|
|
13
|
+
Exports: BaseLBYLDetector, LBYLPattern
|
|
14
|
+
|
|
15
|
+
Interfaces: find_patterns(tree: ast.AST) -> list[LBYLPattern]
|
|
16
|
+
|
|
17
|
+
Implementation: Abstract base with NodeVisitor pattern for extensibility
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import ast
|
|
21
|
+
from abc import ABC, abstractmethod
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class LBYLPattern:
|
|
27
|
+
"""Base pattern data for detected LBYL anti-patterns."""
|
|
28
|
+
|
|
29
|
+
line_number: int
|
|
30
|
+
column: int
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BaseLBYLDetector(ast.NodeVisitor, ABC):
|
|
34
|
+
"""Base class for LBYL pattern detectors."""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def find_patterns(self, tree: ast.AST) -> list[LBYLPattern]:
|
|
38
|
+
"""Find LBYL patterns in AST.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
tree: Python AST to analyze
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
List of detected LBYL patterns
|
|
45
|
+
"""
|
|
46
|
+
raise NotImplementedError
|