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.
Files changed (214) hide show
  1. src/__init__.py +1 -0
  2. src/analyzers/__init__.py +4 -3
  3. src/analyzers/ast_utils.py +54 -0
  4. src/analyzers/rust_base.py +155 -0
  5. src/analyzers/rust_context.py +141 -0
  6. src/analyzers/typescript_base.py +4 -0
  7. src/cli/__init__.py +30 -0
  8. src/cli/__main__.py +22 -0
  9. src/cli/config.py +480 -0
  10. src/cli/config_merge.py +241 -0
  11. src/cli/linters/__init__.py +67 -0
  12. src/cli/linters/code_patterns.py +270 -0
  13. src/cli/linters/code_smells.py +342 -0
  14. src/cli/linters/documentation.py +83 -0
  15. src/cli/linters/performance.py +287 -0
  16. src/cli/linters/shared.py +331 -0
  17. src/cli/linters/structure.py +327 -0
  18. src/cli/linters/structure_quality.py +328 -0
  19. src/cli/main.py +120 -0
  20. src/cli/utils.py +395 -0
  21. src/cli_main.py +37 -0
  22. src/config.py +44 -27
  23. src/core/base.py +95 -5
  24. src/core/cli_utils.py +19 -2
  25. src/core/config_parser.py +36 -6
  26. src/core/constants.py +54 -0
  27. src/core/linter_utils.py +95 -6
  28. src/core/python_lint_rule.py +101 -0
  29. src/core/registry.py +1 -1
  30. src/core/rule_discovery.py +147 -84
  31. src/core/types.py +13 -0
  32. src/core/violation_builder.py +78 -15
  33. src/core/violation_utils.py +69 -0
  34. src/formatters/__init__.py +22 -0
  35. src/formatters/sarif.py +202 -0
  36. src/linter_config/directive_markers.py +109 -0
  37. src/linter_config/ignore.py +254 -395
  38. src/linter_config/loader.py +45 -12
  39. src/linter_config/pattern_utils.py +65 -0
  40. src/linter_config/rule_matcher.py +89 -0
  41. src/linters/collection_pipeline/__init__.py +90 -0
  42. src/linters/collection_pipeline/any_all_analyzer.py +281 -0
  43. src/linters/collection_pipeline/ast_utils.py +40 -0
  44. src/linters/collection_pipeline/config.py +75 -0
  45. src/linters/collection_pipeline/continue_analyzer.py +94 -0
  46. src/linters/collection_pipeline/detector.py +360 -0
  47. src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
  48. src/linters/collection_pipeline/linter.py +420 -0
  49. src/linters/collection_pipeline/suggestion_builder.py +130 -0
  50. src/linters/cqs/__init__.py +54 -0
  51. src/linters/cqs/config.py +55 -0
  52. src/linters/cqs/function_analyzer.py +201 -0
  53. src/linters/cqs/input_detector.py +139 -0
  54. src/linters/cqs/linter.py +159 -0
  55. src/linters/cqs/output_detector.py +84 -0
  56. src/linters/cqs/python_analyzer.py +54 -0
  57. src/linters/cqs/types.py +82 -0
  58. src/linters/cqs/typescript_cqs_analyzer.py +61 -0
  59. src/linters/cqs/typescript_function_analyzer.py +192 -0
  60. src/linters/cqs/typescript_input_detector.py +203 -0
  61. src/linters/cqs/typescript_output_detector.py +117 -0
  62. src/linters/cqs/violation_builder.py +94 -0
  63. src/linters/dry/base_token_analyzer.py +16 -9
  64. src/linters/dry/block_filter.py +125 -22
  65. src/linters/dry/block_grouper.py +4 -0
  66. src/linters/dry/cache.py +142 -94
  67. src/linters/dry/cache_query.py +4 -0
  68. src/linters/dry/config.py +68 -21
  69. src/linters/dry/constant.py +92 -0
  70. src/linters/dry/constant_matcher.py +223 -0
  71. src/linters/dry/constant_violation_builder.py +98 -0
  72. src/linters/dry/duplicate_storage.py +20 -82
  73. src/linters/dry/file_analyzer.py +15 -50
  74. src/linters/dry/inline_ignore.py +7 -16
  75. src/linters/dry/linter.py +182 -54
  76. src/linters/dry/python_analyzer.py +108 -336
  77. src/linters/dry/python_constant_extractor.py +100 -0
  78. src/linters/dry/single_statement_detector.py +417 -0
  79. src/linters/dry/storage_initializer.py +9 -18
  80. src/linters/dry/token_hasher.py +129 -71
  81. src/linters/dry/typescript_analyzer.py +68 -380
  82. src/linters/dry/typescript_constant_extractor.py +138 -0
  83. src/linters/dry/typescript_statement_detector.py +255 -0
  84. src/linters/dry/typescript_value_extractor.py +70 -0
  85. src/linters/dry/violation_builder.py +4 -0
  86. src/linters/dry/violation_filter.py +9 -5
  87. src/linters/dry/violation_generator.py +71 -14
  88. src/linters/file_header/__init__.py +24 -0
  89. src/linters/file_header/atemporal_detector.py +105 -0
  90. src/linters/file_header/base_parser.py +93 -0
  91. src/linters/file_header/bash_parser.py +66 -0
  92. src/linters/file_header/config.py +140 -0
  93. src/linters/file_header/css_parser.py +70 -0
  94. src/linters/file_header/field_validator.py +72 -0
  95. src/linters/file_header/linter.py +309 -0
  96. src/linters/file_header/markdown_parser.py +130 -0
  97. src/linters/file_header/python_parser.py +42 -0
  98. src/linters/file_header/typescript_parser.py +73 -0
  99. src/linters/file_header/violation_builder.py +79 -0
  100. src/linters/file_placement/config_loader.py +3 -1
  101. src/linters/file_placement/directory_matcher.py +4 -0
  102. src/linters/file_placement/linter.py +74 -31
  103. src/linters/file_placement/pattern_matcher.py +41 -6
  104. src/linters/file_placement/pattern_validator.py +31 -12
  105. src/linters/file_placement/rule_checker.py +12 -7
  106. src/linters/lazy_ignores/__init__.py +43 -0
  107. src/linters/lazy_ignores/config.py +74 -0
  108. src/linters/lazy_ignores/directive_utils.py +164 -0
  109. src/linters/lazy_ignores/header_parser.py +177 -0
  110. src/linters/lazy_ignores/linter.py +158 -0
  111. src/linters/lazy_ignores/matcher.py +168 -0
  112. src/linters/lazy_ignores/python_analyzer.py +209 -0
  113. src/linters/lazy_ignores/rule_id_utils.py +180 -0
  114. src/linters/lazy_ignores/skip_detector.py +298 -0
  115. src/linters/lazy_ignores/types.py +71 -0
  116. src/linters/lazy_ignores/typescript_analyzer.py +146 -0
  117. src/linters/lazy_ignores/violation_builder.py +135 -0
  118. src/linters/lbyl/__init__.py +31 -0
  119. src/linters/lbyl/config.py +63 -0
  120. src/linters/lbyl/linter.py +67 -0
  121. src/linters/lbyl/pattern_detectors/__init__.py +53 -0
  122. src/linters/lbyl/pattern_detectors/base.py +63 -0
  123. src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
  124. src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
  125. src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
  126. src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
  127. src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
  128. src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
  129. src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
  130. src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
  131. src/linters/lbyl/python_analyzer.py +215 -0
  132. src/linters/lbyl/violation_builder.py +354 -0
  133. src/linters/magic_numbers/__init__.py +48 -0
  134. src/linters/magic_numbers/config.py +82 -0
  135. src/linters/magic_numbers/context_analyzer.py +249 -0
  136. src/linters/magic_numbers/linter.py +462 -0
  137. src/linters/magic_numbers/python_analyzer.py +64 -0
  138. src/linters/magic_numbers/typescript_analyzer.py +215 -0
  139. src/linters/magic_numbers/typescript_ignore_checker.py +81 -0
  140. src/linters/magic_numbers/violation_builder.py +98 -0
  141. src/linters/method_property/__init__.py +49 -0
  142. src/linters/method_property/config.py +138 -0
  143. src/linters/method_property/linter.py +414 -0
  144. src/linters/method_property/python_analyzer.py +473 -0
  145. src/linters/method_property/violation_builder.py +119 -0
  146. src/linters/nesting/__init__.py +6 -2
  147. src/linters/nesting/config.py +6 -3
  148. src/linters/nesting/linter.py +31 -34
  149. src/linters/nesting/python_analyzer.py +4 -0
  150. src/linters/nesting/typescript_analyzer.py +6 -11
  151. src/linters/nesting/violation_builder.py +1 -0
  152. src/linters/performance/__init__.py +91 -0
  153. src/linters/performance/config.py +43 -0
  154. src/linters/performance/constants.py +49 -0
  155. src/linters/performance/linter.py +149 -0
  156. src/linters/performance/python_analyzer.py +365 -0
  157. src/linters/performance/regex_analyzer.py +312 -0
  158. src/linters/performance/regex_linter.py +139 -0
  159. src/linters/performance/typescript_analyzer.py +236 -0
  160. src/linters/performance/violation_builder.py +160 -0
  161. src/linters/print_statements/__init__.py +53 -0
  162. src/linters/print_statements/config.py +78 -0
  163. src/linters/print_statements/linter.py +413 -0
  164. src/linters/print_statements/python_analyzer.py +153 -0
  165. src/linters/print_statements/typescript_analyzer.py +125 -0
  166. src/linters/print_statements/violation_builder.py +96 -0
  167. src/linters/srp/__init__.py +3 -3
  168. src/linters/srp/class_analyzer.py +11 -7
  169. src/linters/srp/config.py +12 -6
  170. src/linters/srp/heuristics.py +56 -22
  171. src/linters/srp/linter.py +47 -39
  172. src/linters/srp/python_analyzer.py +55 -20
  173. src/linters/srp/typescript_metrics_calculator.py +110 -50
  174. src/linters/stateless_class/__init__.py +25 -0
  175. src/linters/stateless_class/config.py +58 -0
  176. src/linters/stateless_class/linter.py +349 -0
  177. src/linters/stateless_class/python_analyzer.py +290 -0
  178. src/linters/stringly_typed/__init__.py +36 -0
  179. src/linters/stringly_typed/config.py +189 -0
  180. src/linters/stringly_typed/context_filter.py +451 -0
  181. src/linters/stringly_typed/function_call_violation_builder.py +135 -0
  182. src/linters/stringly_typed/ignore_checker.py +100 -0
  183. src/linters/stringly_typed/ignore_utils.py +51 -0
  184. src/linters/stringly_typed/linter.py +376 -0
  185. src/linters/stringly_typed/python/__init__.py +33 -0
  186. src/linters/stringly_typed/python/analyzer.py +348 -0
  187. src/linters/stringly_typed/python/call_tracker.py +175 -0
  188. src/linters/stringly_typed/python/comparison_tracker.py +257 -0
  189. src/linters/stringly_typed/python/condition_extractor.py +134 -0
  190. src/linters/stringly_typed/python/conditional_detector.py +179 -0
  191. src/linters/stringly_typed/python/constants.py +21 -0
  192. src/linters/stringly_typed/python/match_analyzer.py +94 -0
  193. src/linters/stringly_typed/python/validation_detector.py +189 -0
  194. src/linters/stringly_typed/python/variable_extractor.py +96 -0
  195. src/linters/stringly_typed/storage.py +620 -0
  196. src/linters/stringly_typed/storage_initializer.py +45 -0
  197. src/linters/stringly_typed/typescript/__init__.py +28 -0
  198. src/linters/stringly_typed/typescript/analyzer.py +157 -0
  199. src/linters/stringly_typed/typescript/call_tracker.py +335 -0
  200. src/linters/stringly_typed/typescript/comparison_tracker.py +378 -0
  201. src/linters/stringly_typed/violation_generator.py +419 -0
  202. src/orchestrator/core.py +264 -16
  203. src/orchestrator/language_detector.py +5 -3
  204. src/templates/thailint_config_template.yaml +354 -0
  205. src/utils/project_root.py +138 -16
  206. thailint-0.15.3.dist-info/METADATA +187 -0
  207. thailint-0.15.3.dist-info/RECORD +226 -0
  208. {thailint-0.2.0.dist-info → thailint-0.15.3.dist-info}/WHEEL +1 -1
  209. thailint-0.15.3.dist-info/entry_points.txt +4 -0
  210. src/cli.py +0 -1055
  211. thailint-0.2.0.dist-info/METADATA +0 -980
  212. thailint-0.2.0.dist-info/RECORD +0 -75
  213. thailint-0.2.0.dist-info/entry_points.txt +0 -4
  214. {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)
@@ -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()