thailint 0.2.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 +44 -27
- src/core/base.py +95 -5
- src/core/cli_utils.py +19 -2
- src/core/config_parser.py +36 -6
- 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 +125 -22
- src/linters/dry/block_grouper.py +4 -0
- src/linters/dry/cache.py +142 -94
- src/linters/dry/cache_query.py +4 -0
- src/linters/dry/config.py +68 -21
- 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 +20 -82
- src/linters/dry/file_analyzer.py +15 -50
- src/linters/dry/inline_ignore.py +7 -16
- src/linters/dry/linter.py +182 -54
- src/linters/dry/python_analyzer.py +108 -336
- src/linters/dry/python_constant_extractor.py +100 -0
- src/linters/dry/single_statement_detector.py +417 -0
- src/linters/dry/storage_initializer.py +9 -18
- src/linters/dry/token_hasher.py +129 -71
- src/linters/dry/typescript_analyzer.py +68 -380
- 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 +9 -5
- src/linters/dry/violation_generator.py +71 -14
- src/linters/file_header/__init__.py +24 -0
- src/linters/file_header/atemporal_detector.py +105 -0
- src/linters/file_header/base_parser.py +93 -0
- src/linters/file_header/bash_parser.py +66 -0
- src/linters/file_header/config.py +140 -0
- src/linters/file_header/css_parser.py +70 -0
- src/linters/file_header/field_validator.py +72 -0
- src/linters/file_header/linter.py +309 -0
- src/linters/file_header/markdown_parser.py +130 -0
- src/linters/file_header/python_parser.py +42 -0
- src/linters/file_header/typescript_parser.py +73 -0
- src/linters/file_header/violation_builder.py +79 -0
- src/linters/file_placement/config_loader.py +3 -1
- src/linters/file_placement/directory_matcher.py +4 -0
- src/linters/file_placement/linter.py +74 -31
- 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/__init__.py +48 -0
- src/linters/magic_numbers/config.py +82 -0
- src/linters/magic_numbers/context_analyzer.py +249 -0
- src/linters/magic_numbers/linter.py +462 -0
- src/linters/magic_numbers/python_analyzer.py +64 -0
- src/linters/magic_numbers/typescript_analyzer.py +215 -0
- src/linters/magic_numbers/typescript_ignore_checker.py +81 -0
- src/linters/magic_numbers/violation_builder.py +98 -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/__init__.py +6 -2
- src/linters/nesting/config.py +6 -3
- src/linters/nesting/linter.py +31 -34
- src/linters/nesting/python_analyzer.py +4 -0
- src/linters/nesting/typescript_analyzer.py +6 -11
- 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/__init__.py +53 -0
- src/linters/print_statements/config.py +78 -0
- src/linters/print_statements/linter.py +413 -0
- src/linters/print_statements/python_analyzer.py +153 -0
- src/linters/print_statements/typescript_analyzer.py +125 -0
- src/linters/print_statements/violation_builder.py +96 -0
- src/linters/srp/__init__.py +3 -3
- src/linters/srp/class_analyzer.py +11 -7
- src/linters/srp/config.py +12 -6
- src/linters/srp/heuristics.py +56 -22
- src/linters/srp/linter.py +47 -39
- 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 +264 -16
- src/orchestrator/language_detector.py +5 -3
- src/templates/thailint_config_template.yaml +354 -0
- src/utils/project_root.py +138 -16
- thailint-0.15.3.dist-info/METADATA +187 -0
- thailint-0.15.3.dist-info/RECORD +226 -0
- {thailint-0.2.0.dist-info → thailint-0.15.3.dist-info}/WHEEL +1 -1
- thailint-0.15.3.dist-info/entry_points.txt +4 -0
- src/cli.py +0 -1055
- thailint-0.2.0.dist-info/METADATA +0 -980
- thailint-0.2.0.dist-info/RECORD +0 -75
- thailint-0.2.0.dist-info/entry_points.txt +0 -4
- {thailint-0.2.0.dist-info → thailint-0.15.3.dist-info/licenses}/LICENSE +0 -0
src/core/rule_discovery.py
CHANGED
|
@@ -10,7 +10,7 @@ Overview: Provides automatic rule discovery functionality for the linter framewo
|
|
|
10
10
|
|
|
11
11
|
Dependencies: importlib, inspect, pkgutil, BaseLintRule
|
|
12
12
|
|
|
13
|
-
Exports: RuleDiscovery
|
|
13
|
+
Exports: discover_from_package function, RuleDiscovery class (compat)
|
|
14
14
|
|
|
15
15
|
Interfaces: discover_from_package(package_path) -> list[BaseLintRule]
|
|
16
16
|
|
|
@@ -19,114 +19,177 @@ Implementation: Package traversal with pkgutil, class introspection with inspect
|
|
|
19
19
|
|
|
20
20
|
import importlib
|
|
21
21
|
import inspect
|
|
22
|
+
import logging
|
|
22
23
|
import pkgutil
|
|
24
|
+
from types import ModuleType
|
|
23
25
|
from typing import Any
|
|
24
26
|
|
|
25
27
|
from .base import BaseLintRule
|
|
26
28
|
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
27
30
|
|
|
28
|
-
class RuleDiscovery:
|
|
29
|
-
"""Discovers linting rules from Python packages."""
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
def discover_from_package(package_path: str) -> list[BaseLintRule]:
|
|
33
|
+
"""Discover rules from a package and its modules.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
Args:
|
|
36
|
+
package_path: Python package path (e.g., 'src.linters')
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
Returns:
|
|
39
|
+
List of discovered rule instances
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
package = importlib.import_module(package_path)
|
|
43
|
+
except ImportError as e:
|
|
44
|
+
logger.debug("Failed to import package %s: %s", package_path, e)
|
|
45
|
+
return []
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
if not hasattr(package, "__path__"):
|
|
48
|
+
return _discover_from_module(package_path)
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
return _discover_from_package_modules(package_path, package)
|
|
49
51
|
|
|
50
|
-
def _discover_from_package_modules(self, package_path: str, package: Any) -> list[BaseLintRule]:
|
|
51
|
-
"""Discover rules from all modules in a package.
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
package: Imported package object
|
|
53
|
+
def _discover_from_package_modules(package_path: str, package: Any) -> list[BaseLintRule]:
|
|
54
|
+
"""Discover rules from all modules in a package.
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
rules = []
|
|
61
|
-
for _, module_name, _ in pkgutil.iter_modules(package.__path__):
|
|
62
|
-
full_module_name = f"{package_path}.{module_name}"
|
|
63
|
-
module_rules = self._try_discover_from_module(full_module_name)
|
|
64
|
-
rules.extend(module_rules)
|
|
65
|
-
return rules
|
|
56
|
+
Args:
|
|
57
|
+
package_path: Package path
|
|
58
|
+
package: Imported package object
|
|
66
59
|
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
Returns:
|
|
61
|
+
List of discovered rules
|
|
62
|
+
"""
|
|
63
|
+
rules = []
|
|
64
|
+
for _, module_name, _ in pkgutil.iter_modules(package.__path__):
|
|
65
|
+
full_module_name = f"{package_path}.{module_name}"
|
|
66
|
+
module_rules = _try_discover_from_module(full_module_name)
|
|
67
|
+
rules.extend(module_rules)
|
|
68
|
+
return rules
|
|
69
69
|
|
|
70
|
-
Args:
|
|
71
|
-
module_name: Full module name
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"""
|
|
76
|
-
try:
|
|
77
|
-
return self._discover_from_module(module_name)
|
|
78
|
-
except (ImportError, AttributeError):
|
|
79
|
-
return []
|
|
71
|
+
def _try_discover_from_module(module_name: str) -> list[BaseLintRule]:
|
|
72
|
+
"""Try to discover rules from a module, return empty list on error.
|
|
80
73
|
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
Args:
|
|
75
|
+
module_name: Full module name
|
|
83
76
|
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
Returns:
|
|
78
|
+
List of discovered rules (empty on error)
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
return _discover_from_module(module_name)
|
|
82
|
+
except (ImportError, AttributeError):
|
|
83
|
+
return []
|
|
86
84
|
|
|
87
|
-
Returns:
|
|
88
|
-
List of discovered rule instances
|
|
89
|
-
"""
|
|
90
|
-
try:
|
|
91
|
-
module = importlib.import_module(module_path)
|
|
92
|
-
except (ImportError, AttributeError):
|
|
93
|
-
return []
|
|
94
|
-
|
|
95
|
-
rules = []
|
|
96
|
-
for _name, obj in inspect.getmembers(module):
|
|
97
|
-
if not self._is_rule_class(obj):
|
|
98
|
-
continue
|
|
99
|
-
rule_instance = self._try_instantiate_rule(obj)
|
|
100
|
-
if rule_instance:
|
|
101
|
-
rules.append(rule_instance)
|
|
102
|
-
return rules
|
|
103
|
-
|
|
104
|
-
def _try_instantiate_rule(self, rule_class: type[BaseLintRule]) -> BaseLintRule | None:
|
|
105
|
-
"""Try to instantiate a rule class.
|
|
106
85
|
|
|
107
|
-
|
|
108
|
-
|
|
86
|
+
def _discover_from_module(module_path: str) -> list[BaseLintRule]:
|
|
87
|
+
"""Discover rules from a specific module.
|
|
109
88
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
89
|
+
Args:
|
|
90
|
+
module_path: Full module path to search
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of discovered rule instances
|
|
94
|
+
"""
|
|
95
|
+
module = _try_import_module(module_path)
|
|
96
|
+
if module is None:
|
|
97
|
+
return []
|
|
98
|
+
return _extract_rules_from_module(module)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _try_import_module(module_path: str) -> ModuleType | None:
|
|
102
|
+
"""Try to import a module, returning None on failure.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
module_path: Full module path to import
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Module object or None if import fails
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
return importlib.import_module(module_path)
|
|
112
|
+
except (ImportError, AttributeError):
|
|
113
|
+
return None
|
|
117
114
|
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
|
|
116
|
+
def _extract_rules_from_module(module: ModuleType) -> list[BaseLintRule]:
|
|
117
|
+
"""Extract rule instances from a module.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
module: Imported module to scan
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of discovered rule instances
|
|
124
|
+
"""
|
|
125
|
+
rule_classes = [obj for _name, obj in inspect.getmembers(module) if _is_rule_class(obj)]
|
|
126
|
+
return _instantiate_rules(rule_classes)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _instantiate_rules(rule_classes: list[type[BaseLintRule]]) -> list[BaseLintRule]:
|
|
130
|
+
"""Instantiate a list of rule classes.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
rule_classes: List of rule classes to instantiate
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of successfully instantiated rules
|
|
137
|
+
"""
|
|
138
|
+
instances = (_try_instantiate_rule(cls) for cls in rule_classes)
|
|
139
|
+
return [inst for inst in instances if inst is not None]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _try_instantiate_rule(rule_class: type[BaseLintRule]) -> BaseLintRule | None:
|
|
143
|
+
"""Try to instantiate a rule class.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
rule_class: Rule class to instantiate
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Rule instance or None on error
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
return rule_class()
|
|
153
|
+
except (TypeError, AttributeError):
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _is_rule_class(obj: Any) -> bool:
|
|
158
|
+
"""Check if an object is a valid rule class.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
obj: Object to check
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
True if obj is a concrete BaseLintRule subclass
|
|
165
|
+
"""
|
|
166
|
+
return (
|
|
167
|
+
inspect.isclass(obj)
|
|
168
|
+
and issubclass(obj, BaseLintRule)
|
|
169
|
+
and obj is not BaseLintRule
|
|
170
|
+
and not inspect.isabstract(obj)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Legacy class wrapper for backward compatibility
|
|
175
|
+
class RuleDiscovery:
|
|
176
|
+
"""Discovers linting rules from Python packages.
|
|
177
|
+
|
|
178
|
+
Note: This class is a thin wrapper around module-level functions
|
|
179
|
+
for backward compatibility.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def __init__(self) -> None:
|
|
183
|
+
"""Initialize the discovery service."""
|
|
184
|
+
pass # No state needed
|
|
185
|
+
|
|
186
|
+
def discover_from_package(self, package_path: str) -> list[BaseLintRule]:
|
|
187
|
+
"""Discover rules from a package and its modules.
|
|
120
188
|
|
|
121
189
|
Args:
|
|
122
|
-
|
|
190
|
+
package_path: Python package path (e.g., 'src.linters')
|
|
123
191
|
|
|
124
192
|
Returns:
|
|
125
|
-
|
|
193
|
+
List of discovered rule instances
|
|
126
194
|
"""
|
|
127
|
-
return (
|
|
128
|
-
inspect.isclass(obj)
|
|
129
|
-
and issubclass(obj, BaseLintRule)
|
|
130
|
-
and obj is not BaseLintRule
|
|
131
|
-
and not inspect.isabstract(obj)
|
|
132
|
-
)
|
|
195
|
+
return discover_from_package(package_path)
|
src/core/types.py
CHANGED
|
@@ -81,3 +81,16 @@ class Violation:
|
|
|
81
81
|
"severity": self.severity.value,
|
|
82
82
|
"suggestion": self.suggestion,
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def from_dict(cls, data: dict) -> "Violation":
|
|
87
|
+
"""Reconstruct Violation from dictionary (for parallel processing)."""
|
|
88
|
+
return cls(
|
|
89
|
+
rule_id=data["rule_id"],
|
|
90
|
+
file_path=data["file_path"],
|
|
91
|
+
line=data["line"],
|
|
92
|
+
column=data["column"],
|
|
93
|
+
message=data["message"],
|
|
94
|
+
severity=Severity(data["severity"]),
|
|
95
|
+
suggestion=data.get("suggestion"),
|
|
96
|
+
)
|
src/core/violation_builder.py
CHANGED
|
@@ -13,13 +13,17 @@ Overview: Provides base classes and data structures for violation creation acros
|
|
|
13
13
|
|
|
14
14
|
Dependencies: dataclasses, src.core.types (Violation, Severity)
|
|
15
15
|
|
|
16
|
-
Exports: ViolationInfo dataclass,
|
|
16
|
+
Exports: ViolationInfo dataclass, build_violation function, build_violation_from_params function,
|
|
17
|
+
BaseViolationBuilder class (compat)
|
|
17
18
|
|
|
18
19
|
Interfaces: ViolationInfo(rule_id, file_path, line, message, column, severity),
|
|
19
|
-
|
|
20
|
+
build_violation(info: ViolationInfo) -> Violation
|
|
20
21
|
|
|
21
|
-
Implementation: Uses dataclass for type-safe violation info,
|
|
22
|
-
|
|
22
|
+
Implementation: Uses dataclass for type-safe violation info, functions provide build logic
|
|
23
|
+
that constructs Violation objects with proper defaults
|
|
24
|
+
|
|
25
|
+
Suppressions:
|
|
26
|
+
- too-many-arguments,too-many-positional-arguments: Violation fields as parameters
|
|
23
27
|
"""
|
|
24
28
|
|
|
25
29
|
from dataclasses import dataclass
|
|
@@ -50,14 +54,82 @@ class ViolationInfo:
|
|
|
50
54
|
suggestion: str | None = None
|
|
51
55
|
|
|
52
56
|
|
|
57
|
+
def build_violation(info: ViolationInfo) -> Violation:
|
|
58
|
+
"""Build a Violation from ViolationInfo.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
info: ViolationInfo containing all violation details
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Violation object with all fields populated
|
|
65
|
+
"""
|
|
66
|
+
return Violation(
|
|
67
|
+
rule_id=info.rule_id,
|
|
68
|
+
file_path=info.file_path,
|
|
69
|
+
line=info.line,
|
|
70
|
+
column=info.column,
|
|
71
|
+
message=info.message,
|
|
72
|
+
severity=info.severity,
|
|
73
|
+
suggestion=info.suggestion,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def build_violation_from_params( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
78
|
+
rule_id: str,
|
|
79
|
+
file_path: str,
|
|
80
|
+
line: int,
|
|
81
|
+
message: str,
|
|
82
|
+
column: int = 1,
|
|
83
|
+
severity: Severity = Severity.ERROR,
|
|
84
|
+
suggestion: str | None = None,
|
|
85
|
+
) -> Violation:
|
|
86
|
+
"""Build a Violation directly from parameters.
|
|
87
|
+
|
|
88
|
+
Note: Pylint too-many-arguments disabled. This convenience function mirrors the
|
|
89
|
+
ViolationInfo dataclass fields (7 parameters, 3 with defaults). The alternative
|
|
90
|
+
would require every caller to create ViolationInfo objects manually, reducing
|
|
91
|
+
readability.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
rule_id: Identifier for the rule that was violated
|
|
95
|
+
file_path: Path to the file containing the violation
|
|
96
|
+
line: Line number where violation occurs (1-indexed)
|
|
97
|
+
message: Description of the violation
|
|
98
|
+
column: Column number where violation occurs (0-indexed, default=1)
|
|
99
|
+
severity: Severity level of the violation (default=ERROR)
|
|
100
|
+
suggestion: Optional suggestion for fixing the violation
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Violation object with all fields populated
|
|
104
|
+
"""
|
|
105
|
+
info = ViolationInfo(
|
|
106
|
+
rule_id=rule_id,
|
|
107
|
+
file_path=file_path,
|
|
108
|
+
line=line,
|
|
109
|
+
message=message,
|
|
110
|
+
column=column,
|
|
111
|
+
severity=severity,
|
|
112
|
+
suggestion=suggestion,
|
|
113
|
+
)
|
|
114
|
+
return build_violation(info)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Legacy class wrapper for backward compatibility
|
|
53
118
|
class BaseViolationBuilder:
|
|
54
119
|
"""Base class for building violations with consistent structure.
|
|
55
120
|
|
|
56
121
|
Provides common build() method for creating Violation objects from ViolationInfo.
|
|
57
122
|
Linter-specific builders extend this class to add their domain-specific violation
|
|
58
123
|
creation methods while inheriting the common construction logic.
|
|
124
|
+
|
|
125
|
+
Note: This class is a thin wrapper around module-level functions
|
|
126
|
+
for backward compatibility.
|
|
59
127
|
"""
|
|
60
128
|
|
|
129
|
+
def __init__(self) -> None:
|
|
130
|
+
"""Initialize the builder."""
|
|
131
|
+
pass # No state needed
|
|
132
|
+
|
|
61
133
|
def build(self, info: ViolationInfo) -> Violation:
|
|
62
134
|
"""Build a Violation from ViolationInfo.
|
|
63
135
|
|
|
@@ -67,15 +139,7 @@ class BaseViolationBuilder:
|
|
|
67
139
|
Returns:
|
|
68
140
|
Violation object with all fields populated
|
|
69
141
|
"""
|
|
70
|
-
return
|
|
71
|
-
rule_id=info.rule_id,
|
|
72
|
-
file_path=info.file_path,
|
|
73
|
-
line=info.line,
|
|
74
|
-
column=info.column,
|
|
75
|
-
message=info.message,
|
|
76
|
-
severity=info.severity,
|
|
77
|
-
suggestion=info.suggestion,
|
|
78
|
-
)
|
|
142
|
+
return build_violation(info)
|
|
79
143
|
|
|
80
144
|
def build_from_params( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
81
145
|
self,
|
|
@@ -110,7 +174,7 @@ class BaseViolationBuilder:
|
|
|
110
174
|
Returns:
|
|
111
175
|
Violation object with all fields populated
|
|
112
176
|
"""
|
|
113
|
-
|
|
177
|
+
return build_violation_from_params(
|
|
114
178
|
rule_id=rule_id,
|
|
115
179
|
file_path=file_path,
|
|
116
180
|
line=line,
|
|
@@ -119,4 +183,3 @@ class BaseViolationBuilder:
|
|
|
119
183
|
severity=severity,
|
|
120
184
|
suggestion=suggestion,
|
|
121
185
|
)
|
|
122
|
-
return self.build(info)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Shared utility functions for violation processing across linters
|
|
3
|
+
|
|
4
|
+
Scope: Common violation-related operations used by multiple linters
|
|
5
|
+
|
|
6
|
+
Overview: Provides shared utility functions for working with violations, including
|
|
7
|
+
extracting line text and checking for ignore directives. These patterns were
|
|
8
|
+
previously duplicated across multiple linter modules (magic_numbers, print_statements,
|
|
9
|
+
method_property). Centralizing them here improves maintainability and ensures
|
|
10
|
+
consistent behavior across all linters.
|
|
11
|
+
|
|
12
|
+
Dependencies: BaseLintContext, Violation types
|
|
13
|
+
|
|
14
|
+
Exports: get_violation_line, has_python_noqa, has_typescript_noqa
|
|
15
|
+
|
|
16
|
+
Interfaces:
|
|
17
|
+
get_violation_line(violation, context) -> str | None
|
|
18
|
+
has_python_noqa(line_text) -> bool
|
|
19
|
+
has_typescript_noqa(line_text) -> bool
|
|
20
|
+
|
|
21
|
+
Implementation: Simple text extraction and pattern matching
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from src.core.base import BaseLintContext
|
|
25
|
+
from src.core.types import Violation
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_violation_line(violation: Violation, context: BaseLintContext) -> str | None:
|
|
29
|
+
"""Get the line text for a violation, lowercased.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
violation: Violation to get line for
|
|
33
|
+
context: Lint context with file content
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Lowercased line text, or None if not available
|
|
37
|
+
"""
|
|
38
|
+
if not context.file_content:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
lines = context.file_content.splitlines()
|
|
42
|
+
if violation.line <= 0 or violation.line > len(lines):
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
return lines[violation.line - 1].lower()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def has_python_noqa(line_text: str) -> bool:
|
|
49
|
+
"""Check if line has Python-style noqa directive.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
line_text: Lowercased line text
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
True if line has # noqa comment
|
|
56
|
+
"""
|
|
57
|
+
return "# noqa" in line_text
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def has_typescript_noqa(line_text: str) -> bool:
|
|
61
|
+
"""Check if line has TypeScript-style noqa directive.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
line_text: Lowercased line text
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
True if line has // noqa comment
|
|
68
|
+
"""
|
|
69
|
+
return "// noqa" in line_text
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: SARIF formatter package for thai-lint output
|
|
3
|
+
|
|
4
|
+
Scope: SARIF v2.1.0 formatter implementation and package exports
|
|
5
|
+
|
|
6
|
+
Overview: Formatters package providing SARIF (Static Analysis Results Interchange Format) v2.1.0
|
|
7
|
+
output generation from thai-lint Violation objects. Enables integration with GitHub Code
|
|
8
|
+
Scanning, Azure DevOps, VS Code SARIF Viewer, and other industry-standard CI/CD platforms.
|
|
9
|
+
Provides the SarifFormatter class for converting violations to SARIF JSON documents.
|
|
10
|
+
|
|
11
|
+
Dependencies: sarif module for SarifFormatter class
|
|
12
|
+
|
|
13
|
+
Exports: SarifFormatter class from sarif.py module
|
|
14
|
+
|
|
15
|
+
Interfaces: from src.formatters.sarif import SarifFormatter
|
|
16
|
+
|
|
17
|
+
Implementation: Package initialization with SarifFormatter export
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from src.formatters.sarif import SarifFormatter
|
|
21
|
+
|
|
22
|
+
__all__ = ["SarifFormatter"]
|