thailint 0.5.0__py3-none-any.whl → 0.15.3__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/__init__.py +1 -0
- src/analyzers/__init__.py +4 -3
- src/analyzers/ast_utils.py +54 -0
- src/analyzers/rust_base.py +155 -0
- src/analyzers/rust_context.py +141 -0
- src/analyzers/typescript_base.py +4 -0
- src/cli/__init__.py +30 -0
- src/cli/__main__.py +22 -0
- src/cli/config.py +480 -0
- src/cli/config_merge.py +241 -0
- src/cli/linters/__init__.py +67 -0
- src/cli/linters/code_patterns.py +270 -0
- src/cli/linters/code_smells.py +342 -0
- src/cli/linters/documentation.py +83 -0
- src/cli/linters/performance.py +287 -0
- src/cli/linters/shared.py +331 -0
- src/cli/linters/structure.py +327 -0
- src/cli/linters/structure_quality.py +328 -0
- src/cli/main.py +120 -0
- src/cli/utils.py +395 -0
- src/cli_main.py +37 -0
- src/config.py +38 -25
- src/core/base.py +7 -2
- src/core/cli_utils.py +19 -2
- src/core/config_parser.py +5 -2
- src/core/constants.py +54 -0
- src/core/linter_utils.py +95 -6
- src/core/python_lint_rule.py +101 -0
- src/core/registry.py +1 -1
- src/core/rule_discovery.py +147 -84
- src/core/types.py +13 -0
- src/core/violation_builder.py +78 -15
- src/core/violation_utils.py +69 -0
- src/formatters/__init__.py +22 -0
- src/formatters/sarif.py +202 -0
- src/linter_config/directive_markers.py +109 -0
- src/linter_config/ignore.py +254 -395
- src/linter_config/loader.py +45 -12
- src/linter_config/pattern_utils.py +65 -0
- src/linter_config/rule_matcher.py +89 -0
- src/linters/collection_pipeline/__init__.py +90 -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 +75 -0
- src/linters/collection_pipeline/continue_analyzer.py +94 -0
- src/linters/collection_pipeline/detector.py +360 -0
- src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
- src/linters/collection_pipeline/linter.py +420 -0
- src/linters/collection_pipeline/suggestion_builder.py +130 -0
- src/linters/cqs/__init__.py +54 -0
- src/linters/cqs/config.py +55 -0
- src/linters/cqs/function_analyzer.py +201 -0
- src/linters/cqs/input_detector.py +139 -0
- src/linters/cqs/linter.py +159 -0
- src/linters/cqs/output_detector.py +84 -0
- src/linters/cqs/python_analyzer.py +54 -0
- src/linters/cqs/types.py +82 -0
- src/linters/cqs/typescript_cqs_analyzer.py +61 -0
- src/linters/cqs/typescript_function_analyzer.py +192 -0
- src/linters/cqs/typescript_input_detector.py +203 -0
- src/linters/cqs/typescript_output_detector.py +117 -0
- src/linters/cqs/violation_builder.py +94 -0
- src/linters/dry/base_token_analyzer.py +16 -9
- src/linters/dry/block_filter.py +120 -20
- src/linters/dry/block_grouper.py +4 -0
- src/linters/dry/cache.py +104 -10
- src/linters/dry/cache_query.py +4 -0
- src/linters/dry/config.py +54 -11
- src/linters/dry/constant.py +92 -0
- src/linters/dry/constant_matcher.py +223 -0
- src/linters/dry/constant_violation_builder.py +98 -0
- src/linters/dry/duplicate_storage.py +5 -4
- src/linters/dry/file_analyzer.py +4 -2
- src/linters/dry/inline_ignore.py +7 -16
- src/linters/dry/linter.py +183 -48
- src/linters/dry/python_analyzer.py +60 -439
- src/linters/dry/python_constant_extractor.py +100 -0
- src/linters/dry/single_statement_detector.py +417 -0
- src/linters/dry/token_hasher.py +116 -112
- src/linters/dry/typescript_analyzer.py +68 -382
- src/linters/dry/typescript_constant_extractor.py +138 -0
- src/linters/dry/typescript_statement_detector.py +255 -0
- src/linters/dry/typescript_value_extractor.py +70 -0
- src/linters/dry/violation_builder.py +4 -0
- src/linters/dry/violation_filter.py +5 -4
- src/linters/dry/violation_generator.py +71 -14
- src/linters/file_header/atemporal_detector.py +68 -50
- src/linters/file_header/base_parser.py +93 -0
- src/linters/file_header/bash_parser.py +66 -0
- src/linters/file_header/config.py +90 -16
- src/linters/file_header/css_parser.py +70 -0
- src/linters/file_header/field_validator.py +36 -33
- src/linters/file_header/linter.py +140 -144
- src/linters/file_header/markdown_parser.py +130 -0
- src/linters/file_header/python_parser.py +14 -58
- src/linters/file_header/typescript_parser.py +73 -0
- src/linters/file_header/violation_builder.py +13 -12
- src/linters/file_placement/config_loader.py +3 -1
- src/linters/file_placement/directory_matcher.py +4 -0
- src/linters/file_placement/linter.py +66 -34
- src/linters/file_placement/pattern_matcher.py +41 -6
- src/linters/file_placement/pattern_validator.py +31 -12
- src/linters/file_placement/rule_checker.py +12 -7
- src/linters/lazy_ignores/__init__.py +43 -0
- src/linters/lazy_ignores/config.py +74 -0
- src/linters/lazy_ignores/directive_utils.py +164 -0
- src/linters/lazy_ignores/header_parser.py +177 -0
- src/linters/lazy_ignores/linter.py +158 -0
- src/linters/lazy_ignores/matcher.py +168 -0
- src/linters/lazy_ignores/python_analyzer.py +209 -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 +71 -0
- src/linters/lazy_ignores/typescript_analyzer.py +146 -0
- src/linters/lazy_ignores/violation_builder.py +135 -0
- src/linters/lbyl/__init__.py +31 -0
- src/linters/lbyl/config.py +63 -0
- src/linters/lbyl/linter.py +67 -0
- src/linters/lbyl/pattern_detectors/__init__.py +53 -0
- src/linters/lbyl/pattern_detectors/base.py +63 -0
- src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
- src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
- src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
- src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
- src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
- src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
- src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
- src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
- src/linters/lbyl/python_analyzer.py +215 -0
- src/linters/lbyl/violation_builder.py +354 -0
- src/linters/magic_numbers/context_analyzer.py +227 -225
- src/linters/magic_numbers/linter.py +28 -82
- src/linters/magic_numbers/python_analyzer.py +4 -16
- src/linters/magic_numbers/typescript_analyzer.py +9 -12
- src/linters/magic_numbers/typescript_ignore_checker.py +81 -0
- src/linters/method_property/__init__.py +49 -0
- src/linters/method_property/config.py +138 -0
- src/linters/method_property/linter.py +414 -0
- src/linters/method_property/python_analyzer.py +473 -0
- src/linters/method_property/violation_builder.py +119 -0
- src/linters/nesting/linter.py +24 -16
- src/linters/nesting/python_analyzer.py +4 -0
- src/linters/nesting/typescript_analyzer.py +6 -12
- 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/config.py +7 -12
- src/linters/print_statements/linter.py +26 -43
- src/linters/print_statements/python_analyzer.py +91 -93
- src/linters/print_statements/typescript_analyzer.py +15 -25
- src/linters/print_statements/violation_builder.py +12 -14
- src/linters/srp/class_analyzer.py +11 -7
- src/linters/srp/heuristics.py +56 -22
- src/linters/srp/linter.py +15 -16
- src/linters/srp/python_analyzer.py +55 -20
- src/linters/srp/typescript_metrics_calculator.py +110 -50
- src/linters/stateless_class/__init__.py +25 -0
- src/linters/stateless_class/config.py +58 -0
- src/linters/stateless_class/linter.py +349 -0
- src/linters/stateless_class/python_analyzer.py +290 -0
- src/linters/stringly_typed/__init__.py +36 -0
- src/linters/stringly_typed/config.py +189 -0
- src/linters/stringly_typed/context_filter.py +451 -0
- src/linters/stringly_typed/function_call_violation_builder.py +135 -0
- src/linters/stringly_typed/ignore_checker.py +100 -0
- src/linters/stringly_typed/ignore_utils.py +51 -0
- src/linters/stringly_typed/linter.py +376 -0
- src/linters/stringly_typed/python/__init__.py +33 -0
- src/linters/stringly_typed/python/analyzer.py +348 -0
- src/linters/stringly_typed/python/call_tracker.py +175 -0
- src/linters/stringly_typed/python/comparison_tracker.py +257 -0
- src/linters/stringly_typed/python/condition_extractor.py +134 -0
- src/linters/stringly_typed/python/conditional_detector.py +179 -0
- src/linters/stringly_typed/python/constants.py +21 -0
- src/linters/stringly_typed/python/match_analyzer.py +94 -0
- src/linters/stringly_typed/python/validation_detector.py +189 -0
- src/linters/stringly_typed/python/variable_extractor.py +96 -0
- src/linters/stringly_typed/storage.py +620 -0
- src/linters/stringly_typed/storage_initializer.py +45 -0
- src/linters/stringly_typed/typescript/__init__.py +28 -0
- src/linters/stringly_typed/typescript/analyzer.py +157 -0
- src/linters/stringly_typed/typescript/call_tracker.py +335 -0
- src/linters/stringly_typed/typescript/comparison_tracker.py +378 -0
- src/linters/stringly_typed/violation_generator.py +419 -0
- src/orchestrator/core.py +252 -14
- src/orchestrator/language_detector.py +5 -3
- src/templates/thailint_config_template.yaml +196 -0
- src/utils/project_root.py +3 -0
- thailint-0.15.3.dist-info/METADATA +187 -0
- thailint-0.15.3.dist-info/RECORD +226 -0
- thailint-0.15.3.dist-info/entry_points.txt +4 -0
- src/cli.py +0 -1665
- thailint-0.5.0.dist-info/METADATA +0 -1286
- thailint-0.5.0.dist-info/RECORD +0 -96
- thailint-0.5.0.dist-info/entry_points.txt +0 -4
- {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/WHEEL +0 -0
- {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Main method-should-be-property linter rule implementation
|
|
3
|
+
|
|
4
|
+
Scope: Method-should-be-property detection for Python files
|
|
5
|
+
|
|
6
|
+
Overview: Implements method-should-be-property linter rule following MultiLanguageLintRule
|
|
7
|
+
interface. Orchestrates configuration loading, Python AST analysis for property candidates,
|
|
8
|
+
and violation building through focused helper classes. Detects methods that should be
|
|
9
|
+
converted to @property decorators following Pythonic conventions. Supports configurable
|
|
10
|
+
max_body_statements threshold, ignore patterns for excluding files, and inline ignore
|
|
11
|
+
directives (thailint: ignore, noqa) for suppressing specific violations. Handles test file
|
|
12
|
+
detection and non-Python languages gracefully.
|
|
13
|
+
|
|
14
|
+
Dependencies: BaseLintContext and MultiLanguageLintRule from core, ast module, pathlib,
|
|
15
|
+
analyzer classes, config classes
|
|
16
|
+
|
|
17
|
+
Exports: MethodPropertyRule class implementing MultiLanguageLintRule interface
|
|
18
|
+
|
|
19
|
+
Interfaces: check(context) -> list[Violation] for rule validation, standard rule properties
|
|
20
|
+
(rule_id, rule_name, description)
|
|
21
|
+
|
|
22
|
+
Implementation: Composition pattern with helper classes (analyzer, violation builder),
|
|
23
|
+
AST-based analysis for Python with comprehensive exclusion rules
|
|
24
|
+
|
|
25
|
+
Suppressions:
|
|
26
|
+
- srp,dry: Rule class coordinates analyzer, config, and violation building. Method count
|
|
27
|
+
exceeds limit due to comprehensive ignore directive support.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import ast
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
from src.core.base import BaseLintContext, MultiLanguageLintRule
|
|
34
|
+
from src.core.types import Violation
|
|
35
|
+
|
|
36
|
+
from .config import MethodPropertyConfig
|
|
37
|
+
from .python_analyzer import PropertyCandidate, PythonMethodAnalyzer
|
|
38
|
+
from .violation_builder import ViolationBuilder
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MethodPropertyRule(MultiLanguageLintRule): # thailint: ignore[srp,dry]
|
|
42
|
+
"""Detects methods that should be @property decorators."""
|
|
43
|
+
|
|
44
|
+
def __init__(self) -> None:
|
|
45
|
+
"""Initialize the method property rule."""
|
|
46
|
+
self._violation_builder = ViolationBuilder(self.rule_id)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def rule_id(self) -> str:
|
|
50
|
+
"""Unique identifier for this rule."""
|
|
51
|
+
return "method-property.should-be-property"
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def rule_name(self) -> str:
|
|
55
|
+
"""Human-readable name for this rule."""
|
|
56
|
+
return "method should be property"
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def description(self) -> str:
|
|
60
|
+
"""Description of what this rule checks."""
|
|
61
|
+
return "Methods should be converted to @property decorators for Pythonic attribute access"
|
|
62
|
+
|
|
63
|
+
def _load_config(self, context: BaseLintContext) -> MethodPropertyConfig:
|
|
64
|
+
"""Load configuration from context.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
context: Lint context
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
MethodPropertyConfig instance
|
|
71
|
+
"""
|
|
72
|
+
test_config = self._try_load_test_config(context)
|
|
73
|
+
if test_config is not None:
|
|
74
|
+
return test_config
|
|
75
|
+
|
|
76
|
+
return MethodPropertyConfig()
|
|
77
|
+
|
|
78
|
+
def _try_load_test_config(self, context: BaseLintContext) -> MethodPropertyConfig | None:
|
|
79
|
+
"""Try to load test-style configuration.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
context: Lint context
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Config if found, None otherwise
|
|
86
|
+
"""
|
|
87
|
+
if not hasattr(context, "config"):
|
|
88
|
+
return None
|
|
89
|
+
config_attr = context.config
|
|
90
|
+
if config_attr is None or not isinstance(config_attr, dict):
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
# Check for method-property specific config
|
|
94
|
+
linter_config = config_attr.get("method-property", config_attr)
|
|
95
|
+
return MethodPropertyConfig.from_dict(linter_config)
|
|
96
|
+
|
|
97
|
+
def _is_file_ignored(self, context: BaseLintContext, config: MethodPropertyConfig) -> bool:
|
|
98
|
+
"""Check if file matches ignore patterns.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
context: Lint context
|
|
102
|
+
config: Configuration
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
True if file should be ignored
|
|
106
|
+
"""
|
|
107
|
+
if not config.ignore:
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
if not context.file_path:
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
file_path = Path(context.file_path)
|
|
114
|
+
return any(self._matches_pattern(file_path, pattern) for pattern in config.ignore)
|
|
115
|
+
|
|
116
|
+
def _matches_pattern(self, file_path: Path, pattern: str) -> bool:
|
|
117
|
+
"""Check if file path matches a glob pattern.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
file_path: Path to check
|
|
121
|
+
pattern: Glob pattern
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
True if path matches pattern
|
|
125
|
+
"""
|
|
126
|
+
if file_path.match(pattern):
|
|
127
|
+
return True
|
|
128
|
+
if pattern in str(file_path):
|
|
129
|
+
return True
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
def _is_test_file(self, file_path: object) -> bool:
|
|
133
|
+
"""Check if file is a test file.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
file_path: Path to check
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
True if test file
|
|
140
|
+
"""
|
|
141
|
+
path_str = str(file_path)
|
|
142
|
+
file_name = Path(path_str).name
|
|
143
|
+
|
|
144
|
+
# Check test_*.py pattern
|
|
145
|
+
if file_name.startswith("test_") and file_name.endswith(".py"):
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
# Check *_test.py pattern
|
|
149
|
+
if file_name.endswith("_test.py"):
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
def _check_python(
|
|
155
|
+
self, context: BaseLintContext, config: MethodPropertyConfig
|
|
156
|
+
) -> list[Violation]:
|
|
157
|
+
"""Check Python code for method property violations.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
context: Lint context with Python file information
|
|
161
|
+
config: Method property configuration
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
List of violations found in Python code
|
|
165
|
+
"""
|
|
166
|
+
if self._is_file_ignored(context, config):
|
|
167
|
+
return []
|
|
168
|
+
|
|
169
|
+
if self._is_test_file(context.file_path):
|
|
170
|
+
return []
|
|
171
|
+
|
|
172
|
+
tree = self._parse_python_code(context.file_content)
|
|
173
|
+
if tree is None:
|
|
174
|
+
return []
|
|
175
|
+
|
|
176
|
+
analyzer = PythonMethodAnalyzer(
|
|
177
|
+
max_body_statements=config.max_body_statements,
|
|
178
|
+
exclude_prefixes=config.exclude_prefixes,
|
|
179
|
+
exclude_names=config.exclude_names,
|
|
180
|
+
)
|
|
181
|
+
candidates = analyzer.find_property_candidates(tree)
|
|
182
|
+
candidates = self._filter_ignored_methods(candidates, config)
|
|
183
|
+
return self._collect_violations(candidates, context)
|
|
184
|
+
|
|
185
|
+
def _filter_ignored_methods(
|
|
186
|
+
self,
|
|
187
|
+
candidates: list[PropertyCandidate],
|
|
188
|
+
config: MethodPropertyConfig,
|
|
189
|
+
) -> list[PropertyCandidate]:
|
|
190
|
+
"""Filter out candidates with ignored method names.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
candidates: List of property candidates
|
|
194
|
+
config: Configuration with ignore_methods list
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Filtered list of candidates
|
|
198
|
+
"""
|
|
199
|
+
if not config.ignore_methods:
|
|
200
|
+
return candidates
|
|
201
|
+
return [c for c in candidates if c.method_name not in config.ignore_methods]
|
|
202
|
+
|
|
203
|
+
def _parse_python_code(self, code: str | None) -> ast.AST | None:
|
|
204
|
+
"""Parse Python code into AST.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
code: Python source code
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
AST or None if parse fails
|
|
211
|
+
"""
|
|
212
|
+
try:
|
|
213
|
+
return ast.parse(code or "")
|
|
214
|
+
except SyntaxError:
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
def _collect_violations(
|
|
218
|
+
self,
|
|
219
|
+
candidates: list[PropertyCandidate],
|
|
220
|
+
context: BaseLintContext,
|
|
221
|
+
) -> list[Violation]:
|
|
222
|
+
"""Collect violations from property candidates.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
candidates: List of property candidates
|
|
226
|
+
context: Lint context
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
List of violations
|
|
230
|
+
"""
|
|
231
|
+
violations = []
|
|
232
|
+
for candidate in candidates:
|
|
233
|
+
violation = self._create_violation(candidate, context)
|
|
234
|
+
if not self._should_ignore(violation, candidate, context):
|
|
235
|
+
violations.append(violation)
|
|
236
|
+
return violations
|
|
237
|
+
|
|
238
|
+
def _create_violation(
|
|
239
|
+
self,
|
|
240
|
+
candidate: PropertyCandidate,
|
|
241
|
+
context: BaseLintContext,
|
|
242
|
+
) -> Violation:
|
|
243
|
+
"""Create a violation for a property candidate.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
candidate: The property candidate
|
|
247
|
+
context: Lint context
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Violation object
|
|
251
|
+
"""
|
|
252
|
+
return self._violation_builder.create_violation(
|
|
253
|
+
method_name=candidate.method_name,
|
|
254
|
+
line=candidate.line,
|
|
255
|
+
column=candidate.column,
|
|
256
|
+
file_path=context.file_path,
|
|
257
|
+
is_get_prefix=candidate.is_get_prefix,
|
|
258
|
+
class_name=candidate.class_name,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def _should_ignore(
|
|
262
|
+
self,
|
|
263
|
+
violation: Violation,
|
|
264
|
+
candidate: PropertyCandidate,
|
|
265
|
+
context: BaseLintContext,
|
|
266
|
+
) -> bool:
|
|
267
|
+
"""Check if violation should be ignored based on directives.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
violation: Violation to check
|
|
271
|
+
candidate: The property candidate
|
|
272
|
+
context: Lint context
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
True if violation should be ignored
|
|
276
|
+
"""
|
|
277
|
+
if self._has_inline_ignore(violation, context):
|
|
278
|
+
return True
|
|
279
|
+
if self._has_docstring_ignore(candidate, context):
|
|
280
|
+
return True
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
def _has_inline_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
|
|
284
|
+
"""Check for inline ignore directive on method line.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
violation: Violation to check
|
|
288
|
+
context: Lint context
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
True if line has ignore directive
|
|
292
|
+
"""
|
|
293
|
+
line_text = self._get_line_text(violation.line, context)
|
|
294
|
+
if line_text is None:
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
line_lower = line_text.lower()
|
|
298
|
+
|
|
299
|
+
# Check for thailint: ignore[method-property]
|
|
300
|
+
if "thailint:" in line_lower and "ignore" in line_lower:
|
|
301
|
+
return True
|
|
302
|
+
|
|
303
|
+
# Check for noqa
|
|
304
|
+
if "# noqa" in line_lower:
|
|
305
|
+
return True
|
|
306
|
+
|
|
307
|
+
return False
|
|
308
|
+
|
|
309
|
+
def _has_docstring_ignore(
|
|
310
|
+
self,
|
|
311
|
+
candidate: PropertyCandidate,
|
|
312
|
+
context: BaseLintContext,
|
|
313
|
+
) -> bool:
|
|
314
|
+
"""Check for ignore directive in method docstring.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
candidate: Property candidate
|
|
318
|
+
context: Lint context
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
True if docstring has ignore directive
|
|
322
|
+
"""
|
|
323
|
+
tree = self._parse_python_code(context.file_content)
|
|
324
|
+
if tree is None:
|
|
325
|
+
return False
|
|
326
|
+
|
|
327
|
+
docstring = self._find_method_docstring(tree, candidate)
|
|
328
|
+
if docstring is None:
|
|
329
|
+
return False
|
|
330
|
+
|
|
331
|
+
docstring_lower = docstring.lower()
|
|
332
|
+
return "thailint: ignore" in docstring_lower
|
|
333
|
+
|
|
334
|
+
def _find_method_docstring(
|
|
335
|
+
self,
|
|
336
|
+
tree: ast.AST,
|
|
337
|
+
candidate: PropertyCandidate,
|
|
338
|
+
) -> str | None:
|
|
339
|
+
"""Find the docstring for a method.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
tree: AST tree
|
|
343
|
+
candidate: Property candidate
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Docstring text or None
|
|
347
|
+
"""
|
|
348
|
+
target_class = self._find_class_node(tree, candidate.class_name)
|
|
349
|
+
if target_class is None:
|
|
350
|
+
return None
|
|
351
|
+
return self._find_method_in_class(target_class, candidate.method_name)
|
|
352
|
+
|
|
353
|
+
def _find_class_node(self, tree: ast.AST, class_name: str) -> ast.ClassDef | None:
|
|
354
|
+
"""Find a class node by name in the AST.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
tree: AST tree
|
|
358
|
+
class_name: Name of the class to find
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
ClassDef node or None
|
|
362
|
+
"""
|
|
363
|
+
for node in ast.walk(tree):
|
|
364
|
+
if isinstance(node, ast.ClassDef) and node.name == class_name:
|
|
365
|
+
return node
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
def _find_method_in_class(self, class_node: ast.ClassDef, method_name: str) -> str | None:
|
|
369
|
+
"""Find method docstring within a class.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
class_node: Class node to search
|
|
373
|
+
method_name: Method name to find
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Docstring or None
|
|
377
|
+
"""
|
|
378
|
+
for item in class_node.body:
|
|
379
|
+
if isinstance(item, ast.FunctionDef) and item.name == method_name:
|
|
380
|
+
return ast.get_docstring(item)
|
|
381
|
+
return None
|
|
382
|
+
|
|
383
|
+
def _get_line_text(self, line: int, context: BaseLintContext) -> str | None:
|
|
384
|
+
"""Get the text of a specific line.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
line: Line number (1-indexed)
|
|
388
|
+
context: Lint context
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Line text or None
|
|
392
|
+
"""
|
|
393
|
+
if not context.file_content:
|
|
394
|
+
return None
|
|
395
|
+
|
|
396
|
+
lines = context.file_content.splitlines()
|
|
397
|
+
if line <= 0 or line > len(lines):
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
return lines[line - 1]
|
|
401
|
+
|
|
402
|
+
def _check_typescript(
|
|
403
|
+
self, context: BaseLintContext, config: MethodPropertyConfig
|
|
404
|
+
) -> list[Violation]:
|
|
405
|
+
"""Check TypeScript code for violations.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
context: Lint context
|
|
409
|
+
config: Configuration
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Empty list (not implemented for TypeScript)
|
|
413
|
+
"""
|
|
414
|
+
return []
|