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/__init__.py
CHANGED
|
@@ -19,6 +19,7 @@ Exports: __version__, Linter (high-level API), cli (CLI entry point), load_confi
|
|
|
19
19
|
Interfaces: Package version string, Linter class API, CLI command group, configuration functions
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
__version__: str
|
|
22
23
|
try:
|
|
23
24
|
from importlib.metadata import version
|
|
24
25
|
|
src/analyzers/__init__.py
CHANGED
|
@@ -9,15 +9,16 @@ Overview: Package containing base analyzer classes for different programming lan
|
|
|
9
9
|
(TypeScriptBaseAnalyzer, etc.) that linter-specific analyzers extend. Centralizes
|
|
10
10
|
language parsing infrastructure to improve maintainability and consistency.
|
|
11
11
|
|
|
12
|
-
Dependencies: tree-sitter, language-specific tree-sitter bindings
|
|
12
|
+
Dependencies: tree-sitter, language-specific tree-sitter bindings, ast module
|
|
13
13
|
|
|
14
|
-
Exports: TypeScriptBaseAnalyzer
|
|
14
|
+
Exports: TypeScriptBaseAnalyzer, build_parent_map
|
|
15
15
|
|
|
16
16
|
Interfaces: Base analyzer classes with parse(), walk_tree(), and extract() methods
|
|
17
17
|
|
|
18
18
|
Implementation: Composition-based design for linter analyzers to use base utilities
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
+
from .ast_utils import build_parent_map
|
|
21
22
|
from .typescript_base import TypeScriptBaseAnalyzer
|
|
22
23
|
|
|
23
|
-
__all__ = ["TypeScriptBaseAnalyzer"]
|
|
24
|
+
__all__ = ["TypeScriptBaseAnalyzer", "build_parent_map"]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Common Python AST utilities for linter analyzers
|
|
3
|
+
|
|
4
|
+
Scope: Shared AST traversal utilities for Python code analysis
|
|
5
|
+
|
|
6
|
+
Overview: Provides common AST utility functions used across multiple Python linters.
|
|
7
|
+
Centralizes shared patterns like parent map building to eliminate code duplication.
|
|
8
|
+
The build_parent_map function creates a dictionary mapping AST nodes to their parents,
|
|
9
|
+
enabling upward tree traversal for context detection.
|
|
10
|
+
|
|
11
|
+
Dependencies: ast module for AST node types
|
|
12
|
+
|
|
13
|
+
Exports: build_parent_map
|
|
14
|
+
|
|
15
|
+
Interfaces: build_parent_map(tree: ast.AST) -> dict[ast.AST, ast.AST]
|
|
16
|
+
|
|
17
|
+
Implementation: Recursive AST traversal with parent tracking
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import ast
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_parent_map(tree: ast.AST) -> dict[ast.AST, ast.AST]:
|
|
24
|
+
"""Build a map of AST nodes to their parent nodes.
|
|
25
|
+
|
|
26
|
+
Enables upward tree traversal for context detection (e.g., finding if a node
|
|
27
|
+
is inside a particular block type).
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
tree: Root AST node to build map from
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Dictionary mapping each node to its parent node
|
|
34
|
+
"""
|
|
35
|
+
parent_map: dict[ast.AST, ast.AST] = {}
|
|
36
|
+
_build_parent_map_recursive(tree, None, parent_map)
|
|
37
|
+
return parent_map
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _build_parent_map_recursive(
|
|
41
|
+
node: ast.AST, parent: ast.AST | None, parent_map: dict[ast.AST, ast.AST]
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Recursively build parent map.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
node: Current AST node
|
|
47
|
+
parent: Parent of current node
|
|
48
|
+
parent_map: Dictionary to populate
|
|
49
|
+
"""
|
|
50
|
+
if parent is not None:
|
|
51
|
+
parent_map[node] = parent
|
|
52
|
+
|
|
53
|
+
for child in ast.iter_child_nodes(node):
|
|
54
|
+
_build_parent_map_recursive(child, node, parent_map)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Base class for Rust AST analysis with tree-sitter parsing
|
|
3
|
+
|
|
4
|
+
Scope: Common tree-sitter initialization, parsing, and traversal utilities for Rust
|
|
5
|
+
|
|
6
|
+
Overview: Provides shared infrastructure for Rust code analysis using tree-sitter parser.
|
|
7
|
+
Implements common tree-sitter initialization with language setup and parser configuration.
|
|
8
|
+
Provides reusable parsing methods that convert Rust source to AST nodes. Includes
|
|
9
|
+
shared traversal utilities for walking AST trees recursively and finding nodes by type.
|
|
10
|
+
Delegates context-specific detection (test functions, async functions) to rust_context
|
|
11
|
+
module. Serves as foundation for specialized Rust analyzers (unwrap abuse, clone abuse).
|
|
12
|
+
|
|
13
|
+
Dependencies: tree-sitter, tree-sitter-rust (optional), src.analyzers.rust_context
|
|
14
|
+
|
|
15
|
+
Exports: RustBaseAnalyzer class with parsing and traversal utilities,
|
|
16
|
+
TREE_SITTER_RUST_AVAILABLE constant for runtime detection
|
|
17
|
+
|
|
18
|
+
Interfaces: parse_rust(code), walk_tree(node, node_type), extract_node_text(node),
|
|
19
|
+
is_inside_test(node), is_async_function(node)
|
|
20
|
+
|
|
21
|
+
Implementation: Tree-sitter parser singleton, recursive AST traversal, composition pattern
|
|
22
|
+
with rust_context helpers
|
|
23
|
+
|
|
24
|
+
Suppressions:
|
|
25
|
+
- type:ignore[assignment]: Tree-sitter RUST_PARSER fallback when import fails
|
|
26
|
+
- type:ignore[assignment,misc]: Tree-sitter Node type alias (optional dependency fallback)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
from src.analyzers import rust_context
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
import tree_sitter_rust as tsrust
|
|
35
|
+
from tree_sitter import Language, Node, Parser
|
|
36
|
+
|
|
37
|
+
RUST_LANGUAGE = Language(tsrust.language())
|
|
38
|
+
RUST_PARSER = Parser(RUST_LANGUAGE)
|
|
39
|
+
TREE_SITTER_RUST_AVAILABLE = True
|
|
40
|
+
except ImportError:
|
|
41
|
+
TREE_SITTER_RUST_AVAILABLE = False
|
|
42
|
+
RUST_PARSER = None # type: ignore[assignment]
|
|
43
|
+
Node = Any # type: ignore[assignment,misc]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RustBaseAnalyzer:
|
|
47
|
+
"""Base analyzer for Rust code using tree-sitter."""
|
|
48
|
+
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
"""Initialize Rust base analyzer."""
|
|
51
|
+
self.tree_sitter_available = TREE_SITTER_RUST_AVAILABLE
|
|
52
|
+
|
|
53
|
+
def parse_rust(self, code: str) -> Node | None:
|
|
54
|
+
"""Parse Rust code to AST using tree-sitter.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
code: Rust source code to parse
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Tree-sitter AST root node, or None if parsing fails or tree-sitter unavailable
|
|
61
|
+
"""
|
|
62
|
+
if not TREE_SITTER_RUST_AVAILABLE or RUST_PARSER is None:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
tree = RUST_PARSER.parse(bytes(code, "utf8"))
|
|
66
|
+
return tree.root_node
|
|
67
|
+
|
|
68
|
+
def walk_tree(self, node: Node, node_type: str) -> list[Node]:
|
|
69
|
+
"""Find all nodes of a specific type in the AST.
|
|
70
|
+
|
|
71
|
+
Recursively walks the tree and collects all nodes matching the given type.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
node: Root tree-sitter node to search from
|
|
75
|
+
node_type: Tree-sitter node type to find (e.g., "function_item")
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of all matching nodes
|
|
79
|
+
"""
|
|
80
|
+
if not TREE_SITTER_RUST_AVAILABLE or node is None:
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
nodes: list[Node] = []
|
|
84
|
+
self._walk_tree_recursive(node, node_type, nodes)
|
|
85
|
+
return nodes
|
|
86
|
+
|
|
87
|
+
def _walk_tree_recursive(self, node: Node, node_type: str, nodes: list[Node]) -> None:
|
|
88
|
+
"""Recursively walk tree to find nodes of specific type.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
node: Current tree-sitter node
|
|
92
|
+
node_type: Node type to find
|
|
93
|
+
nodes: List to accumulate matching nodes
|
|
94
|
+
"""
|
|
95
|
+
if node.type == node_type:
|
|
96
|
+
nodes.append(node)
|
|
97
|
+
|
|
98
|
+
for child in node.children:
|
|
99
|
+
self._walk_tree_recursive(child, node_type, nodes)
|
|
100
|
+
|
|
101
|
+
def extract_node_text(self, node: Node) -> str:
|
|
102
|
+
"""Extract text content from a tree-sitter node.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
node: Tree-sitter node
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Decoded text content of the node
|
|
109
|
+
"""
|
|
110
|
+
text = node.text
|
|
111
|
+
if text is None:
|
|
112
|
+
return ""
|
|
113
|
+
return text.decode()
|
|
114
|
+
|
|
115
|
+
def extract_identifier_name(self, node: Node) -> str:
|
|
116
|
+
"""Extract identifier name from node children.
|
|
117
|
+
|
|
118
|
+
Common pattern for extracting names from function/struct declarations.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
node: Node to extract identifier from
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Identifier name or "anonymous" fallback
|
|
125
|
+
"""
|
|
126
|
+
for child in node.children:
|
|
127
|
+
if child.type == "identifier":
|
|
128
|
+
return self.extract_node_text(child)
|
|
129
|
+
return "anonymous"
|
|
130
|
+
|
|
131
|
+
def is_inside_test(self, node: Node) -> bool:
|
|
132
|
+
"""Check if node is inside a test function or module.
|
|
133
|
+
|
|
134
|
+
Delegates to rust_context module for implementation.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
node: Tree-sitter node to check
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
True if the node is inside a test function or test module
|
|
141
|
+
"""
|
|
142
|
+
return rust_context.is_inside_test(node)
|
|
143
|
+
|
|
144
|
+
def is_async_function(self, node: Node) -> bool:
|
|
145
|
+
"""Check if a function_item is async.
|
|
146
|
+
|
|
147
|
+
Delegates to rust_context module for implementation.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
node: Function item node to check
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
True if the function is declared as async
|
|
154
|
+
"""
|
|
155
|
+
return rust_context.is_async_function(node)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Rust-specific AST context detection utilities
|
|
3
|
+
|
|
4
|
+
Scope: Helper functions for detecting test contexts and async functions in Rust code
|
|
5
|
+
|
|
6
|
+
Overview: Provides standalone helper functions for analyzing Rust AST context information.
|
|
7
|
+
Includes functions for detecting if code is inside a test function or module by checking
|
|
8
|
+
for #[test] or #[cfg(test)] attributes on preceding siblings, and for detecting async
|
|
9
|
+
functions by checking for function_modifiers. These utilities are designed to be used
|
|
10
|
+
in composition with RustBaseAnalyzer for Rust-specific lint rules that need to understand
|
|
11
|
+
code context.
|
|
12
|
+
|
|
13
|
+
Dependencies: tree-sitter (for Node type when available)
|
|
14
|
+
|
|
15
|
+
Exports: is_inside_test, is_async_function, has_test_attribute, has_cfg_test_attribute
|
|
16
|
+
|
|
17
|
+
Interfaces: All functions take tree-sitter Node objects and return bool
|
|
18
|
+
|
|
19
|
+
Implementation: Sibling-based attribute lookup for Rust AST structure, iterative parent
|
|
20
|
+
traversal for context detection
|
|
21
|
+
|
|
22
|
+
Suppressions:
|
|
23
|
+
- misc,assignment: Node type alias when tree-sitter optional dependency unavailable
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from tree_sitter import Node
|
|
30
|
+
|
|
31
|
+
TREE_SITTER_AVAILABLE = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
TREE_SITTER_AVAILABLE = False
|
|
34
|
+
Node = Any # type: ignore[misc,assignment]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_node_text(node: Node) -> str:
|
|
38
|
+
"""Get decoded text from a node.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
node: Tree-sitter node
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Decoded text content
|
|
45
|
+
"""
|
|
46
|
+
return node.text.decode() if node.text else ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def has_test_attribute(function_node: Node) -> bool:
|
|
50
|
+
"""Check if a function has #[test] attribute as preceding sibling.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
function_node: Function item node
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
True if function has #[test] attribute
|
|
57
|
+
"""
|
|
58
|
+
prev_sibling = function_node.prev_sibling
|
|
59
|
+
while prev_sibling is not None and prev_sibling.type == "attribute_item":
|
|
60
|
+
if "test" in _get_node_text(prev_sibling):
|
|
61
|
+
return True
|
|
62
|
+
prev_sibling = prev_sibling.prev_sibling
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def has_cfg_test_attribute(mod_node: Node) -> bool:
|
|
67
|
+
"""Check if a module has #[cfg(test)] attribute as preceding sibling.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
mod_node: Module item node
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if module has #[cfg(test)] attribute
|
|
74
|
+
"""
|
|
75
|
+
prev_sibling = mod_node.prev_sibling
|
|
76
|
+
while prev_sibling is not None and prev_sibling.type == "attribute_item":
|
|
77
|
+
if "cfg(test)" in _get_node_text(prev_sibling):
|
|
78
|
+
return True
|
|
79
|
+
prev_sibling = prev_sibling.prev_sibling
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def is_inside_test(node: Node) -> bool:
|
|
84
|
+
"""Check if node is inside a test function or module.
|
|
85
|
+
|
|
86
|
+
Walks up the tree looking for #[test] or #[cfg(test)] attributes.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
node: Tree-sitter node to check
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True if the node is inside a test function or test module
|
|
93
|
+
"""
|
|
94
|
+
current: Node | None = node
|
|
95
|
+
while current is not None:
|
|
96
|
+
if _is_test_context(current):
|
|
97
|
+
return True
|
|
98
|
+
current = current.parent
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _is_test_context(node: Node) -> bool:
|
|
103
|
+
"""Check if a node represents a test context.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
node: Node to check
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
True if node is a test function or test module
|
|
110
|
+
"""
|
|
111
|
+
if node.type == "function_item":
|
|
112
|
+
return has_test_attribute(node)
|
|
113
|
+
if node.type == "mod_item":
|
|
114
|
+
return has_cfg_test_attribute(node)
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def is_async_function(node: Node) -> bool:
|
|
119
|
+
"""Check if a function_item is async.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
node: Function item node to check
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
True if the function is declared as async
|
|
126
|
+
"""
|
|
127
|
+
return any(
|
|
128
|
+
child.type == "function_modifiers" and _has_async_modifier(child) for child in node.children
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _has_async_modifier(modifiers_node: Node) -> bool:
|
|
133
|
+
"""Check if function_modifiers node contains async keyword.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
modifiers_node: The function_modifiers node
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
True if async keyword is present
|
|
140
|
+
"""
|
|
141
|
+
return any(modifier.type == "async" for modifier in modifiers_node.children)
|
src/analyzers/typescript_base.py
CHANGED
|
@@ -18,6 +18,10 @@ Exports: TypeScriptBaseAnalyzer class with parsing and traversal utilities
|
|
|
18
18
|
Interfaces: parse_typescript(code), walk_tree(node, node_type), extract_node_text(node)
|
|
19
19
|
|
|
20
20
|
Implementation: Tree-sitter parser singleton, recursive AST traversal, composition pattern
|
|
21
|
+
|
|
22
|
+
Suppressions:
|
|
23
|
+
- type:ignore[assignment]: Tree-sitter TS_PARSER fallback when import fails
|
|
24
|
+
- type:ignore[assignment,misc]: Tree-sitter Node type alias (optional dependency fallback)
|
|
21
25
|
"""
|
|
22
26
|
|
|
23
27
|
from typing import Any
|
src/cli/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: CLI package entry point and public API for thai-lint command-line interface
|
|
3
|
+
|
|
4
|
+
Scope: Re-export fully configured CLI with all commands registered
|
|
5
|
+
|
|
6
|
+
Overview: Provides the public API for the modular CLI package by re-exporting the CLI group from
|
|
7
|
+
src.cli.main and triggering command registration by importing submodules. Importing from this
|
|
8
|
+
module (src.cli) gives access to the complete CLI with all commands. Maintains backward
|
|
9
|
+
compatibility with code that imports from src.cli while enabling modular organization.
|
|
10
|
+
|
|
11
|
+
Dependencies: src.cli.main for CLI group, src.cli.config for config commands, src.cli.linters
|
|
12
|
+
for linter commands
|
|
13
|
+
|
|
14
|
+
Exports: cli (main Click command group with all commands registered)
|
|
15
|
+
|
|
16
|
+
Interfaces: Single import point for CLI access via 'from src.cli import cli'
|
|
17
|
+
|
|
18
|
+
Implementation: Imports submodules to trigger command registration via Click decorators
|
|
19
|
+
|
|
20
|
+
Suppressions:
|
|
21
|
+
- F401: Module re-exports required for public API interface
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Import the CLI group from main module
|
|
25
|
+
# Import config and linters to register their commands with the CLI group
|
|
26
|
+
from src.cli import config as _config_module # noqa: F401
|
|
27
|
+
from src.cli import linters as _linters_module # noqa: F401
|
|
28
|
+
from src.cli.main import cli # noqa: F401
|
|
29
|
+
|
|
30
|
+
__all__ = ["cli"]
|
src/cli/__main__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Entry point for running thai-lint CLI as a module (python -m src.cli)
|
|
3
|
+
|
|
4
|
+
Scope: Module execution support for direct CLI invocation
|
|
5
|
+
|
|
6
|
+
Overview: Enables running the CLI via 'python -m src.cli' by invoking the main cli group.
|
|
7
|
+
This file is executed when the package is run as a module, providing an alternative
|
|
8
|
+
entry point to the installed 'thailint' command.
|
|
9
|
+
|
|
10
|
+
Dependencies: src.cli for fully configured CLI
|
|
11
|
+
|
|
12
|
+
Exports: None (execution entry point only)
|
|
13
|
+
|
|
14
|
+
Interfaces: Command-line invocation via 'python -m src.cli [command] [args]'
|
|
15
|
+
|
|
16
|
+
Implementation: Imports and invokes cli() from the package
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from src.cli import cli
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
cli()
|