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
|
@@ -12,7 +12,7 @@ Overview: Implements magic numbers linter rule following BaseLintRule interface.
|
|
|
12
12
|
because refactoring for A-grade complexity requires extracting helper methods. Class maintains
|
|
13
13
|
single responsibility of magic number detection - all methods support this core purpose.
|
|
14
14
|
|
|
15
|
-
Dependencies: BaseLintRule, BaseLintContext, PythonMagicNumberAnalyzer,
|
|
15
|
+
Dependencies: BaseLintRule, BaseLintContext, PythonMagicNumberAnalyzer, is_acceptable_context,
|
|
16
16
|
ViolationBuilder, MagicNumberConfig, IgnoreDirectiveParser
|
|
17
17
|
|
|
18
18
|
Exports: MagicNumberRule class
|
|
@@ -21,20 +21,28 @@ Interfaces: MagicNumberRule.check(context) -> list[Violation], properties for ru
|
|
|
21
21
|
|
|
22
22
|
Implementation: Composition pattern with helper classes, AST-based analysis with configurable
|
|
23
23
|
allowed numbers and context detection
|
|
24
|
+
|
|
25
|
+
Suppressions:
|
|
26
|
+
- too-many-arguments,too-many-positional-arguments: TypeScript violation creation with related params
|
|
27
|
+
- srp: Rule class coordinates analyzers and violation builders. Method count exceeds limit
|
|
28
|
+
due to complexity refactoring. All methods support magic number detection.
|
|
24
29
|
"""
|
|
25
30
|
|
|
26
31
|
import ast
|
|
27
32
|
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
28
34
|
|
|
29
35
|
from src.core.base import BaseLintContext, MultiLanguageLintRule
|
|
30
36
|
from src.core.linter_utils import load_linter_config
|
|
31
37
|
from src.core.types import Violation
|
|
32
|
-
from src.
|
|
38
|
+
from src.core.violation_utils import get_violation_line, has_python_noqa
|
|
39
|
+
from src.linter_config.ignore import get_ignore_parser
|
|
33
40
|
|
|
34
41
|
from .config import MagicNumberConfig
|
|
35
|
-
from .context_analyzer import
|
|
42
|
+
from .context_analyzer import is_acceptable_context
|
|
36
43
|
from .python_analyzer import PythonMagicNumberAnalyzer
|
|
37
44
|
from .typescript_analyzer import TypeScriptMagicNumberAnalyzer
|
|
45
|
+
from .typescript_ignore_checker import TypeScriptIgnoreChecker
|
|
38
46
|
from .violation_builder import ViolationBuilder
|
|
39
47
|
|
|
40
48
|
|
|
@@ -43,9 +51,9 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
43
51
|
|
|
44
52
|
def __init__(self) -> None:
|
|
45
53
|
"""Initialize the magic numbers rule."""
|
|
46
|
-
self._ignore_parser =
|
|
54
|
+
self._ignore_parser = get_ignore_parser()
|
|
47
55
|
self._violation_builder = ViolationBuilder(self.rule_id)
|
|
48
|
-
self.
|
|
56
|
+
self._typescript_ignore_checker = TypeScriptIgnoreChecker()
|
|
49
57
|
|
|
50
58
|
@property
|
|
51
59
|
def rule_id(self) -> str:
|
|
@@ -131,10 +139,7 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
131
139
|
return False
|
|
132
140
|
|
|
133
141
|
file_path = Path(context.file_path)
|
|
134
|
-
for pattern in config.ignore
|
|
135
|
-
if self._matches_pattern(file_path, pattern):
|
|
136
|
-
return True
|
|
137
|
-
return False
|
|
142
|
+
return any(self._matches_pattern(file_path, pattern) for pattern in config.ignore)
|
|
138
143
|
|
|
139
144
|
def _matches_pattern(self, file_path: Path, pattern: str) -> bool:
|
|
140
145
|
"""Check if file path matches a glob pattern.
|
|
@@ -248,9 +253,7 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
248
253
|
"allowed_numbers": config.allowed_numbers,
|
|
249
254
|
}
|
|
250
255
|
|
|
251
|
-
if
|
|
252
|
-
node, parent, context.file_path, config_dict
|
|
253
|
-
):
|
|
256
|
+
if is_acceptable_context(node, parent, context.file_path, config_dict):
|
|
254
257
|
return False
|
|
255
258
|
|
|
256
259
|
return True
|
|
@@ -282,28 +285,17 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
282
285
|
Returns:
|
|
283
286
|
True if line has generic ignore directive
|
|
284
287
|
"""
|
|
285
|
-
line_text =
|
|
288
|
+
line_text = get_violation_line(violation, context)
|
|
286
289
|
if line_text is None:
|
|
287
290
|
return False
|
|
288
291
|
|
|
289
292
|
return self._has_generic_ignore_directive(line_text)
|
|
290
293
|
|
|
291
|
-
def _get_violation_line(self, violation: Violation, context: BaseLintContext) -> str | None:
|
|
292
|
-
"""Get the line text for a violation."""
|
|
293
|
-
if not context.file_content:
|
|
294
|
-
return None
|
|
295
|
-
|
|
296
|
-
lines = context.file_content.splitlines()
|
|
297
|
-
if violation.line <= 0 or violation.line > len(lines):
|
|
298
|
-
return None
|
|
299
|
-
|
|
300
|
-
return lines[violation.line - 1].lower()
|
|
301
|
-
|
|
302
294
|
def _has_generic_ignore_directive(self, line_text: str) -> bool:
|
|
303
295
|
"""Check if line has generic ignore directive."""
|
|
304
296
|
if self._has_generic_thailint_ignore(line_text):
|
|
305
297
|
return True
|
|
306
|
-
return
|
|
298
|
+
return has_python_noqa(line_text)
|
|
307
299
|
|
|
308
300
|
def _has_generic_thailint_ignore(self, line_text: str) -> bool:
|
|
309
301
|
"""Check for generic thailint: ignore (no brackets)."""
|
|
@@ -312,10 +304,6 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
312
304
|
after_ignore = line_text.split("# thailint: ignore")[1].split("#")[0]
|
|
313
305
|
return "[" not in after_ignore
|
|
314
306
|
|
|
315
|
-
def _has_noqa_directive(self, line_text: str) -> bool:
|
|
316
|
-
"""Check for noqa-style comments."""
|
|
317
|
-
return "# noqa" in line_text
|
|
318
|
-
|
|
319
307
|
def _check_typescript(
|
|
320
308
|
self, context: BaseLintContext, config: MagicNumberConfig
|
|
321
309
|
) -> list[Violation]:
|
|
@@ -433,12 +421,17 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
433
421
|
return value in config.allowed_numbers or self._is_test_file(context.file_path)
|
|
434
422
|
|
|
435
423
|
def _is_typescript_special_context(
|
|
436
|
-
self, node:
|
|
424
|
+
self, node: Any, analyzer: TypeScriptMagicNumberAnalyzer, context: BaseLintContext
|
|
437
425
|
) -> bool:
|
|
438
|
-
"""Check if in TypeScript-specific special context.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
426
|
+
"""Check if in TypeScript-specific special context.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
node: Tree-sitter Node (typed as Any due to optional dependency)
|
|
430
|
+
analyzer: TypeScript analyzer
|
|
431
|
+
context: Lint context
|
|
432
|
+
"""
|
|
433
|
+
in_enum = analyzer.is_enum_context(node)
|
|
434
|
+
in_const_def = analyzer.is_constant_definition(node, context.file_content or "")
|
|
442
435
|
return in_enum or in_const_def
|
|
443
436
|
|
|
444
437
|
def _is_test_file(self, file_path: object) -> bool:
|
|
@@ -466,51 +459,4 @@ class MagicNumberRule(MultiLanguageLintRule): # thailint: ignore[srp]
|
|
|
466
459
|
Returns:
|
|
467
460
|
True if should ignore
|
|
468
461
|
"""
|
|
469
|
-
|
|
470
|
-
if self._ignore_parser.should_ignore_violation(violation, context.file_content or ""):
|
|
471
|
-
return True
|
|
472
|
-
|
|
473
|
-
# Check TypeScript-style comments
|
|
474
|
-
return self._check_typescript_ignore(violation, context)
|
|
475
|
-
|
|
476
|
-
def _check_typescript_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
|
|
477
|
-
"""Check for TypeScript-style ignore directives.
|
|
478
|
-
|
|
479
|
-
Args:
|
|
480
|
-
violation: Violation to check
|
|
481
|
-
context: Lint context
|
|
482
|
-
|
|
483
|
-
Returns:
|
|
484
|
-
True if line has ignore directive
|
|
485
|
-
"""
|
|
486
|
-
line_text = self._get_violation_line(violation, context)
|
|
487
|
-
if line_text is None:
|
|
488
|
-
return False
|
|
489
|
-
|
|
490
|
-
# Check for // thailint: ignore or // noqa
|
|
491
|
-
return self._has_typescript_ignore_directive(line_text)
|
|
492
|
-
|
|
493
|
-
def _has_typescript_ignore_directive(self, line_text: str) -> bool:
|
|
494
|
-
"""Check if line has TypeScript-style ignore directive.
|
|
495
|
-
|
|
496
|
-
Args:
|
|
497
|
-
line_text: Line text to check
|
|
498
|
-
|
|
499
|
-
Returns:
|
|
500
|
-
True if has ignore directive
|
|
501
|
-
"""
|
|
502
|
-
# Check for // thailint: ignore[magic-numbers]
|
|
503
|
-
if "// thailint: ignore[magic-numbers]" in line_text:
|
|
504
|
-
return True
|
|
505
|
-
|
|
506
|
-
# Check for // thailint: ignore (generic)
|
|
507
|
-
if "// thailint: ignore" in line_text:
|
|
508
|
-
after_ignore = line_text.split("// thailint: ignore")[1].split("//")[0]
|
|
509
|
-
if "[" not in after_ignore:
|
|
510
|
-
return True
|
|
511
|
-
|
|
512
|
-
# Check for // noqa
|
|
513
|
-
if "// noqa" in line_text:
|
|
514
|
-
return True
|
|
515
|
-
|
|
516
|
-
return False
|
|
462
|
+
return self._typescript_ignore_checker.should_ignore(violation, context)
|
|
@@ -10,7 +10,7 @@ Overview: Provides PythonMagicNumberAnalyzer class that traverses Python AST to
|
|
|
10
10
|
value, and source location. This analyzer handles Python-specific AST structure and provides
|
|
11
11
|
the foundation for magic number detection by identifying all candidates before context filtering.
|
|
12
12
|
|
|
13
|
-
Dependencies: ast module for AST parsing and node types
|
|
13
|
+
Dependencies: ast module for AST parsing and node types, analyzers.ast_utils
|
|
14
14
|
|
|
15
15
|
Exports: PythonMagicNumberAnalyzer class
|
|
16
16
|
|
|
@@ -23,6 +23,8 @@ Implementation: AST NodeVisitor pattern with parent tracking, filters for numeri
|
|
|
23
23
|
import ast
|
|
24
24
|
from typing import Any
|
|
25
25
|
|
|
26
|
+
from src.analyzers.ast_utils import build_parent_map
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
class PythonMagicNumberAnalyzer(ast.NodeVisitor):
|
|
28
30
|
"""Analyzes Python AST to find numeric literals."""
|
|
@@ -44,24 +46,10 @@ class PythonMagicNumberAnalyzer(ast.NodeVisitor):
|
|
|
44
46
|
List of tuples (node, parent, value, line_number)
|
|
45
47
|
"""
|
|
46
48
|
self.numeric_literals = []
|
|
47
|
-
self.parent_map =
|
|
48
|
-
self._build_parent_map(tree)
|
|
49
|
+
self.parent_map = build_parent_map(tree)
|
|
49
50
|
self.visit(tree)
|
|
50
51
|
return self.numeric_literals
|
|
51
52
|
|
|
52
|
-
def _build_parent_map(self, node: ast.AST, parent: ast.AST | None = None) -> None:
|
|
53
|
-
"""Build a map of nodes to their parents.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
node: Current AST node
|
|
57
|
-
parent: Parent of current node
|
|
58
|
-
"""
|
|
59
|
-
if parent is not None:
|
|
60
|
-
self.parent_map[node] = parent
|
|
61
|
-
|
|
62
|
-
for child in ast.iter_child_nodes(node):
|
|
63
|
-
self._build_parent_map(child, node)
|
|
64
|
-
|
|
65
53
|
def visit_Constant(self, node: ast.Constant) -> None:
|
|
66
54
|
"""Visit a Constant node and check if it's a numeric literal.
|
|
67
55
|
|
|
@@ -20,20 +20,17 @@ Interfaces: find_numeric_literals(root_node) -> list[tuple], is_enum_context(nod
|
|
|
20
20
|
|
|
21
21
|
Implementation: Tree-sitter node traversal with visitor pattern, context-aware filtering
|
|
22
22
|
for acceptable numeric literal locations
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from typing import Any
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
from tree_sitter import Node
|
|
24
|
+
Suppressions:
|
|
25
|
+
- srp: Analyzer implements tree-sitter traversal with context detection methods.
|
|
26
|
+
Methods support single responsibility of magic number detection in TypeScript.
|
|
27
|
+
"""
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
from src.analyzers.typescript_base import (
|
|
30
|
+
TREE_SITTER_AVAILABLE,
|
|
31
|
+
Node,
|
|
32
|
+
TypeScriptBaseAnalyzer,
|
|
33
|
+
)
|
|
37
34
|
|
|
38
35
|
|
|
39
36
|
class TypeScriptMagicNumberAnalyzer(TypeScriptBaseAnalyzer): # thailint: ignore[srp]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: TypeScript-specific ignore directive checking for magic numbers linter
|
|
3
|
+
|
|
4
|
+
Scope: Ignore directive detection for TypeScript/JavaScript files
|
|
5
|
+
|
|
6
|
+
Overview: Provides ignore directive checking functionality specifically for TypeScript and JavaScript
|
|
7
|
+
files in the magic numbers linter. Handles both thailint-style and noqa-style ignore comments
|
|
8
|
+
using TypeScript comment syntax (// instead of #). Extracted from linter.py to reduce file
|
|
9
|
+
size and improve modularity.
|
|
10
|
+
|
|
11
|
+
Dependencies: IgnoreDirectiveParser from src.linter_config.ignore, Violation type, violation_utils
|
|
12
|
+
|
|
13
|
+
Exports: TypeScriptIgnoreChecker class
|
|
14
|
+
|
|
15
|
+
Interfaces: TypeScriptIgnoreChecker.should_ignore(violation, context) -> bool
|
|
16
|
+
|
|
17
|
+
Implementation: Comment parsing with TypeScript-specific syntax handling, uses shared utilities
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from src.core.base import BaseLintContext
|
|
21
|
+
from src.core.types import Violation
|
|
22
|
+
from src.core.violation_utils import get_violation_line, has_typescript_noqa
|
|
23
|
+
from src.linter_config.ignore import get_ignore_parser
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TypeScriptIgnoreChecker:
|
|
27
|
+
"""Checks for TypeScript-style ignore directives in magic numbers linter."""
|
|
28
|
+
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
"""Initialize with standard ignore parser."""
|
|
31
|
+
self._ignore_parser = get_ignore_parser()
|
|
32
|
+
|
|
33
|
+
def should_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
|
|
34
|
+
"""Check if TypeScript violation should be ignored.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
violation: Violation to check
|
|
38
|
+
context: Lint context
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if should ignore
|
|
42
|
+
"""
|
|
43
|
+
if self._ignore_parser.should_ignore_violation(violation, context.file_content or ""):
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
return self._check_typescript_ignore(violation, context)
|
|
47
|
+
|
|
48
|
+
def _check_typescript_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
|
|
49
|
+
"""Check for TypeScript-style ignore directives.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
violation: Violation to check
|
|
53
|
+
context: Lint context
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
True if line has ignore directive
|
|
57
|
+
"""
|
|
58
|
+
line_text = get_violation_line(violation, context)
|
|
59
|
+
if line_text is None:
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
return self._has_typescript_ignore_directive(line_text)
|
|
63
|
+
|
|
64
|
+
def _has_typescript_ignore_directive(self, line_text: str) -> bool:
|
|
65
|
+
"""Check if line has TypeScript-style ignore directive.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
line_text: Line text to check
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if has ignore directive
|
|
72
|
+
"""
|
|
73
|
+
if "// thailint: ignore[magic-numbers]" in line_text:
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
if "// thailint: ignore" in line_text:
|
|
77
|
+
after_ignore = line_text.split("// thailint: ignore")[1].split("//")[0]
|
|
78
|
+
if "[" not in after_ignore:
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
return has_typescript_noqa(line_text)
|
|
@@ -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,138 @@
|
|
|
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
|
+
Suppressions:
|
|
23
|
+
- dry: MethodPropertyConfig includes extensive exclusion lists that share patterns with
|
|
24
|
+
other config classes. Lists are maintained separately for clear documentation.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
# Default action verb prefixes - methods starting with these are excluded
|
|
31
|
+
# These represent actions/transformations, not property access
|
|
32
|
+
DEFAULT_EXCLUDE_PREFIXES: tuple[str, ...] = (
|
|
33
|
+
"to_", # Transformation: to_dict, to_json, to_string
|
|
34
|
+
"dump_", # Serialization: dump_to_json, dump_to_apigw
|
|
35
|
+
"generate_", # Factory: generate_report, generate_html
|
|
36
|
+
"create_", # Factory: create_instance, create_config
|
|
37
|
+
"build_", # Construction: build_query, build_html
|
|
38
|
+
"make_", # Factory: make_request, make_connection
|
|
39
|
+
"render_", # Output: render_template, render_html
|
|
40
|
+
"compute_", # Calculation: compute_hash, compute_total
|
|
41
|
+
"calculate_", # Calculation: calculate_sum, calculate_average
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Default action verb names - exact method names that are excluded
|
|
45
|
+
# These are lifecycle hooks, display actions, and resource operations
|
|
46
|
+
DEFAULT_EXCLUDE_NAMES: frozenset[str] = frozenset(
|
|
47
|
+
{
|
|
48
|
+
"finalize", # Lifecycle hook
|
|
49
|
+
"serialize", # Transformation
|
|
50
|
+
"dump", # Serialization
|
|
51
|
+
"validate", # Validation action
|
|
52
|
+
"show", # Display action
|
|
53
|
+
"display", # Display action
|
|
54
|
+
"print", # Output action
|
|
55
|
+
"refresh", # Update action
|
|
56
|
+
"reset", # State action
|
|
57
|
+
"clear", # State action
|
|
58
|
+
"close", # Resource action
|
|
59
|
+
"open", # Resource action
|
|
60
|
+
"save", # Persistence action
|
|
61
|
+
"load", # Persistence action
|
|
62
|
+
"execute", # Action
|
|
63
|
+
"run", # Action
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _load_list_config(
|
|
69
|
+
config: dict[str, Any], key: str, override_key: str, default: tuple[str, ...]
|
|
70
|
+
) -> tuple[str, ...]:
|
|
71
|
+
"""Load a list config with extend/override semantics."""
|
|
72
|
+
if override_key in config and isinstance(config[override_key], list):
|
|
73
|
+
return tuple(config[override_key])
|
|
74
|
+
if key in config and isinstance(config[key], list):
|
|
75
|
+
return default + tuple(config[key])
|
|
76
|
+
return default
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _load_set_config(
|
|
80
|
+
config: dict[str, Any], key: str, override_key: str, default: frozenset[str]
|
|
81
|
+
) -> frozenset[str]:
|
|
82
|
+
"""Load a set config with extend/override semantics."""
|
|
83
|
+
if override_key in config and isinstance(config[override_key], list):
|
|
84
|
+
return frozenset(config[override_key])
|
|
85
|
+
if key in config and isinstance(config[key], list):
|
|
86
|
+
return default | frozenset(config[key])
|
|
87
|
+
return default
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class MethodPropertyConfig: # thailint: ignore[dry]
|
|
92
|
+
"""Configuration for method-should-be-property linter."""
|
|
93
|
+
|
|
94
|
+
enabled: bool = True
|
|
95
|
+
max_body_statements: int = 3
|
|
96
|
+
ignore: list[str] = field(default_factory=list)
|
|
97
|
+
ignore_methods: list[str] = field(default_factory=list)
|
|
98
|
+
|
|
99
|
+
# Action verb exclusions (extend defaults or override)
|
|
100
|
+
exclude_prefixes: tuple[str, ...] = DEFAULT_EXCLUDE_PREFIXES
|
|
101
|
+
exclude_names: frozenset[str] = DEFAULT_EXCLUDE_NAMES
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def from_dict(
|
|
105
|
+
cls, config: dict[str, Any] | None, language: str | None = None
|
|
106
|
+
) -> "MethodPropertyConfig":
|
|
107
|
+
"""Load configuration from dictionary.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
config: Dictionary containing configuration values, or None
|
|
111
|
+
language: Programming language (unused, for interface compatibility)
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
MethodPropertyConfig instance with values from dictionary
|
|
115
|
+
"""
|
|
116
|
+
if config is None:
|
|
117
|
+
return cls()
|
|
118
|
+
|
|
119
|
+
ignore_patterns = config.get("ignore", [])
|
|
120
|
+
if not isinstance(ignore_patterns, list):
|
|
121
|
+
ignore_patterns = []
|
|
122
|
+
|
|
123
|
+
ignore_methods = config.get("ignore_methods", [])
|
|
124
|
+
if not isinstance(ignore_methods, list):
|
|
125
|
+
ignore_methods = []
|
|
126
|
+
|
|
127
|
+
return cls(
|
|
128
|
+
enabled=config.get("enabled", True),
|
|
129
|
+
max_body_statements=config.get("max_body_statements", 3),
|
|
130
|
+
ignore=ignore_patterns,
|
|
131
|
+
ignore_methods=ignore_methods,
|
|
132
|
+
exclude_prefixes=_load_list_config(
|
|
133
|
+
config, "exclude_prefixes", "exclude_prefixes_override", DEFAULT_EXCLUDE_PREFIXES
|
|
134
|
+
),
|
|
135
|
+
exclude_names=_load_set_config(
|
|
136
|
+
config, "exclude_names", "exclude_names_override", DEFAULT_EXCLUDE_NAMES
|
|
137
|
+
),
|
|
138
|
+
)
|