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.
Files changed (135) hide show
  1. src/analyzers/__init__.py +4 -3
  2. src/analyzers/ast_utils.py +54 -0
  3. src/analyzers/typescript_base.py +4 -0
  4. src/cli/__init__.py +3 -0
  5. src/cli/config.py +12 -12
  6. src/cli/config_merge.py +241 -0
  7. src/cli/linters/__init__.py +9 -0
  8. src/cli/linters/code_patterns.py +107 -257
  9. src/cli/linters/code_smells.py +48 -165
  10. src/cli/linters/documentation.py +21 -95
  11. src/cli/linters/performance.py +274 -0
  12. src/cli/linters/shared.py +232 -6
  13. src/cli/linters/structure.py +26 -21
  14. src/cli/linters/structure_quality.py +28 -21
  15. src/cli_main.py +3 -0
  16. src/config.py +2 -1
  17. src/core/base.py +3 -2
  18. src/core/cli_utils.py +3 -1
  19. src/core/config_parser.py +5 -2
  20. src/core/constants.py +54 -0
  21. src/core/linter_utils.py +95 -6
  22. src/core/rule_discovery.py +5 -1
  23. src/core/violation_builder.py +3 -0
  24. src/linter_config/directive_markers.py +109 -0
  25. src/linter_config/ignore.py +225 -383
  26. src/linter_config/pattern_utils.py +65 -0
  27. src/linter_config/rule_matcher.py +89 -0
  28. src/linters/collection_pipeline/any_all_analyzer.py +281 -0
  29. src/linters/collection_pipeline/ast_utils.py +40 -0
  30. src/linters/collection_pipeline/config.py +12 -0
  31. src/linters/collection_pipeline/continue_analyzer.py +2 -8
  32. src/linters/collection_pipeline/detector.py +262 -32
  33. src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
  34. src/linters/collection_pipeline/linter.py +18 -35
  35. src/linters/collection_pipeline/suggestion_builder.py +68 -1
  36. src/linters/dry/base_token_analyzer.py +16 -9
  37. src/linters/dry/block_filter.py +7 -4
  38. src/linters/dry/cache.py +7 -2
  39. src/linters/dry/config.py +7 -1
  40. src/linters/dry/constant_matcher.py +34 -25
  41. src/linters/dry/file_analyzer.py +4 -2
  42. src/linters/dry/inline_ignore.py +7 -16
  43. src/linters/dry/linter.py +48 -25
  44. src/linters/dry/python_analyzer.py +18 -10
  45. src/linters/dry/python_constant_extractor.py +51 -52
  46. src/linters/dry/single_statement_detector.py +14 -12
  47. src/linters/dry/token_hasher.py +115 -115
  48. src/linters/dry/typescript_analyzer.py +11 -6
  49. src/linters/dry/typescript_constant_extractor.py +4 -0
  50. src/linters/dry/typescript_statement_detector.py +208 -208
  51. src/linters/dry/typescript_value_extractor.py +3 -0
  52. src/linters/dry/violation_filter.py +1 -4
  53. src/linters/dry/violation_generator.py +1 -4
  54. src/linters/file_header/atemporal_detector.py +58 -40
  55. src/linters/file_header/base_parser.py +4 -0
  56. src/linters/file_header/bash_parser.py +4 -0
  57. src/linters/file_header/config.py +14 -0
  58. src/linters/file_header/field_validator.py +5 -8
  59. src/linters/file_header/linter.py +19 -12
  60. src/linters/file_header/markdown_parser.py +6 -0
  61. src/linters/file_placement/config_loader.py +3 -1
  62. src/linters/file_placement/linter.py +22 -8
  63. src/linters/file_placement/pattern_matcher.py +21 -4
  64. src/linters/file_placement/pattern_validator.py +21 -7
  65. src/linters/file_placement/rule_checker.py +2 -2
  66. src/linters/lazy_ignores/__init__.py +43 -0
  67. src/linters/lazy_ignores/config.py +66 -0
  68. src/linters/lazy_ignores/directive_utils.py +121 -0
  69. src/linters/lazy_ignores/header_parser.py +177 -0
  70. src/linters/lazy_ignores/linter.py +158 -0
  71. src/linters/lazy_ignores/matcher.py +135 -0
  72. src/linters/lazy_ignores/python_analyzer.py +205 -0
  73. src/linters/lazy_ignores/rule_id_utils.py +180 -0
  74. src/linters/lazy_ignores/skip_detector.py +298 -0
  75. src/linters/lazy_ignores/types.py +69 -0
  76. src/linters/lazy_ignores/typescript_analyzer.py +146 -0
  77. src/linters/lazy_ignores/violation_builder.py +131 -0
  78. src/linters/lbyl/__init__.py +29 -0
  79. src/linters/lbyl/config.py +63 -0
  80. src/linters/lbyl/pattern_detectors/__init__.py +25 -0
  81. src/linters/lbyl/pattern_detectors/base.py +46 -0
  82. src/linters/magic_numbers/context_analyzer.py +227 -229
  83. src/linters/magic_numbers/linter.py +20 -15
  84. src/linters/magic_numbers/python_analyzer.py +4 -16
  85. src/linters/magic_numbers/typescript_analyzer.py +9 -16
  86. src/linters/method_property/config.py +4 -1
  87. src/linters/method_property/linter.py +5 -10
  88. src/linters/method_property/python_analyzer.py +5 -4
  89. src/linters/method_property/violation_builder.py +3 -0
  90. src/linters/nesting/linter.py +11 -6
  91. src/linters/nesting/typescript_analyzer.py +6 -12
  92. src/linters/nesting/typescript_function_extractor.py +0 -4
  93. src/linters/nesting/violation_builder.py +1 -0
  94. src/linters/performance/__init__.py +91 -0
  95. src/linters/performance/config.py +43 -0
  96. src/linters/performance/constants.py +49 -0
  97. src/linters/performance/linter.py +149 -0
  98. src/linters/performance/python_analyzer.py +365 -0
  99. src/linters/performance/regex_analyzer.py +312 -0
  100. src/linters/performance/regex_linter.py +139 -0
  101. src/linters/performance/typescript_analyzer.py +236 -0
  102. src/linters/performance/violation_builder.py +160 -0
  103. src/linters/print_statements/linter.py +6 -4
  104. src/linters/print_statements/python_analyzer.py +85 -81
  105. src/linters/print_statements/typescript_analyzer.py +6 -15
  106. src/linters/srp/heuristics.py +4 -4
  107. src/linters/srp/linter.py +12 -12
  108. src/linters/srp/violation_builder.py +0 -4
  109. src/linters/stateless_class/linter.py +30 -36
  110. src/linters/stateless_class/python_analyzer.py +11 -20
  111. src/linters/stringly_typed/config.py +4 -5
  112. src/linters/stringly_typed/context_filter.py +410 -410
  113. src/linters/stringly_typed/function_call_violation_builder.py +93 -95
  114. src/linters/stringly_typed/linter.py +48 -16
  115. src/linters/stringly_typed/python/analyzer.py +5 -1
  116. src/linters/stringly_typed/python/call_tracker.py +8 -5
  117. src/linters/stringly_typed/python/comparison_tracker.py +10 -5
  118. src/linters/stringly_typed/python/condition_extractor.py +3 -0
  119. src/linters/stringly_typed/python/conditional_detector.py +4 -1
  120. src/linters/stringly_typed/python/match_analyzer.py +8 -2
  121. src/linters/stringly_typed/python/validation_detector.py +3 -0
  122. src/linters/stringly_typed/storage.py +14 -14
  123. src/linters/stringly_typed/typescript/call_tracker.py +9 -3
  124. src/linters/stringly_typed/typescript/comparison_tracker.py +9 -3
  125. src/linters/stringly_typed/violation_generator.py +288 -259
  126. src/orchestrator/core.py +13 -4
  127. src/templates/thailint_config_template.yaml +196 -0
  128. src/utils/project_root.py +3 -0
  129. thailint-0.14.0.dist-info/METADATA +185 -0
  130. thailint-0.14.0.dist-info/RECORD +199 -0
  131. thailint-0.12.0.dist-info/METADATA +0 -1667
  132. thailint-0.12.0.dist-info/RECORD +0 -164
  133. {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/WHEEL +0 -0
  134. {thailint-0.12.0.dist-info → thailint-0.14.0.dist-info}/entry_points.txt +0 -0
  135. {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