thailint 0.1.5__py3-none-any.whl → 0.2.0__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 +7 -2
- src/analyzers/__init__.py +23 -0
- src/analyzers/typescript_base.py +148 -0
- src/api.py +1 -1
- src/cli.py +498 -141
- src/config.py +6 -31
- src/core/base.py +12 -0
- src/core/cli_utils.py +206 -0
- src/core/config_parser.py +99 -0
- src/core/linter_utils.py +168 -0
- src/core/registry.py +17 -92
- src/core/rule_discovery.py +132 -0
- src/core/violation_builder.py +122 -0
- src/linter_config/ignore.py +112 -40
- src/linter_config/loader.py +3 -13
- src/linters/dry/__init__.py +23 -0
- src/linters/dry/base_token_analyzer.py +76 -0
- src/linters/dry/block_filter.py +262 -0
- src/linters/dry/block_grouper.py +59 -0
- src/linters/dry/cache.py +218 -0
- src/linters/dry/cache_query.py +61 -0
- src/linters/dry/config.py +130 -0
- src/linters/dry/config_loader.py +44 -0
- src/linters/dry/deduplicator.py +120 -0
- src/linters/dry/duplicate_storage.py +126 -0
- src/linters/dry/file_analyzer.py +127 -0
- src/linters/dry/inline_ignore.py +140 -0
- src/linters/dry/linter.py +170 -0
- src/linters/dry/python_analyzer.py +517 -0
- src/linters/dry/storage_initializer.py +51 -0
- src/linters/dry/token_hasher.py +115 -0
- src/linters/dry/typescript_analyzer.py +590 -0
- src/linters/dry/violation_builder.py +74 -0
- src/linters/dry/violation_filter.py +91 -0
- src/linters/dry/violation_generator.py +174 -0
- src/linters/file_placement/config_loader.py +86 -0
- src/linters/file_placement/directory_matcher.py +80 -0
- src/linters/file_placement/linter.py +252 -472
- src/linters/file_placement/path_resolver.py +61 -0
- src/linters/file_placement/pattern_matcher.py +55 -0
- src/linters/file_placement/pattern_validator.py +106 -0
- src/linters/file_placement/rule_checker.py +229 -0
- src/linters/file_placement/violation_factory.py +177 -0
- src/linters/nesting/config.py +13 -3
- src/linters/nesting/linter.py +76 -152
- src/linters/nesting/typescript_analyzer.py +38 -102
- src/linters/nesting/typescript_function_extractor.py +130 -0
- src/linters/nesting/violation_builder.py +139 -0
- src/linters/srp/__init__.py +99 -0
- src/linters/srp/class_analyzer.py +113 -0
- src/linters/srp/config.py +76 -0
- src/linters/srp/heuristics.py +89 -0
- src/linters/srp/linter.py +225 -0
- src/linters/srp/metrics_evaluator.py +47 -0
- src/linters/srp/python_analyzer.py +72 -0
- src/linters/srp/typescript_analyzer.py +75 -0
- src/linters/srp/typescript_metrics_calculator.py +90 -0
- src/linters/srp/violation_builder.py +117 -0
- src/orchestrator/core.py +42 -7
- src/utils/__init__.py +4 -0
- src/utils/project_root.py +84 -0
- {thailint-0.1.5.dist-info → thailint-0.2.0.dist-info}/METADATA +414 -63
- thailint-0.2.0.dist-info/RECORD +75 -0
- src/.ai/layout.yaml +0 -48
- thailint-0.1.5.dist-info/RECORD +0 -28
- {thailint-0.1.5.dist-info → thailint-0.2.0.dist-info}/LICENSE +0 -0
- {thailint-0.1.5.dist-info → thailint-0.2.0.dist-info}/WHEEL +0 -0
- {thailint-0.1.5.dist-info → thailint-0.2.0.dist-info}/entry_points.txt +0 -0
src/linters/nesting/linter.py
CHANGED
|
@@ -3,33 +3,33 @@ Purpose: Main nesting depth linter rule implementation
|
|
|
3
3
|
|
|
4
4
|
Scope: NestingDepthRule class implementing BaseLintRule interface
|
|
5
5
|
|
|
6
|
-
Overview: Implements nesting depth linter rule following BaseLintRule interface.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
code analysis. Gracefully handles syntax errors by reporting them as violations rather than
|
|
12
|
-
crashing. Supports configuration loading from context metadata for per-file customization.
|
|
6
|
+
Overview: Implements nesting depth linter rule following BaseLintRule interface. Orchestrates
|
|
7
|
+
configuration loading, Python/TypeScript analysis, and violation building through focused helper
|
|
8
|
+
classes. Detects excessive nesting depth in functions using AST analysis. Supports configurable
|
|
9
|
+
max_nesting_depth limit and ignore directives. Main rule class acts as coordinator for nesting
|
|
10
|
+
depth checking workflow.
|
|
13
11
|
|
|
14
|
-
Dependencies: BaseLintRule, BaseLintContext,
|
|
12
|
+
Dependencies: BaseLintRule, BaseLintContext, PythonNestingAnalyzer, TypeScriptNestingAnalyzer, NestingViolationBuilder
|
|
15
13
|
|
|
16
14
|
Exports: NestingDepthRule class
|
|
17
15
|
|
|
18
16
|
Interfaces: NestingDepthRule.check(context) -> list[Violation], properties for rule metadata
|
|
19
17
|
|
|
20
|
-
Implementation: AST-based analysis with configurable limits
|
|
18
|
+
Implementation: Composition pattern with helper classes, AST-based analysis with configurable limits
|
|
21
19
|
"""
|
|
22
20
|
|
|
23
21
|
import ast
|
|
24
22
|
from typing import Any
|
|
25
23
|
|
|
26
24
|
from src.core.base import BaseLintContext, BaseLintRule
|
|
27
|
-
from src.core.
|
|
25
|
+
from src.core.linter_utils import has_file_content, load_linter_config
|
|
26
|
+
from src.core.types import Violation
|
|
28
27
|
from src.linter_config.ignore import IgnoreDirectiveParser
|
|
29
28
|
|
|
30
29
|
from .config import NestingConfig
|
|
31
30
|
from .python_analyzer import PythonNestingAnalyzer
|
|
32
31
|
from .typescript_analyzer import TypeScriptNestingAnalyzer
|
|
32
|
+
from .violation_builder import NestingViolationBuilder
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class NestingDepthRule(BaseLintRule):
|
|
@@ -38,6 +38,7 @@ class NestingDepthRule(BaseLintRule):
|
|
|
38
38
|
def __init__(self) -> None:
|
|
39
39
|
"""Initialize the nesting depth rule."""
|
|
40
40
|
self._ignore_parser = IgnoreDirectiveParser()
|
|
41
|
+
self._violation_builder = NestingViolationBuilder(self.rule_id)
|
|
41
42
|
|
|
42
43
|
@property
|
|
43
44
|
def rule_id(self) -> str:
|
|
@@ -63,39 +64,45 @@ class NestingDepthRule(BaseLintRule):
|
|
|
63
64
|
Returns:
|
|
64
65
|
List of violations found
|
|
65
66
|
"""
|
|
66
|
-
if context
|
|
67
|
+
if not has_file_content(context):
|
|
67
68
|
return []
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
config = self._load_config(context)
|
|
70
|
+
config = load_linter_config(context, "nesting", NestingConfig)
|
|
71
71
|
if not config.enabled:
|
|
72
72
|
return []
|
|
73
73
|
|
|
74
|
-
# Analyze based on language
|
|
75
74
|
if context.language == "python":
|
|
76
75
|
return self._check_python(context, config)
|
|
77
76
|
if context.language in ("typescript", "javascript"):
|
|
78
77
|
return self._check_typescript(context, config)
|
|
79
78
|
return []
|
|
80
79
|
|
|
81
|
-
def
|
|
82
|
-
|
|
80
|
+
def _process_python_functions(
|
|
81
|
+
self, functions: list, analyzer: Any, config: NestingConfig, context: BaseLintContext
|
|
82
|
+
) -> list[Violation]:
|
|
83
|
+
"""Process Python functions and collect violations.
|
|
83
84
|
|
|
84
85
|
Args:
|
|
85
|
-
|
|
86
|
+
functions: List of function AST nodes
|
|
87
|
+
analyzer: Python nesting analyzer
|
|
88
|
+
config: Nesting configuration
|
|
89
|
+
context: Lint context
|
|
86
90
|
|
|
87
91
|
Returns:
|
|
88
|
-
|
|
92
|
+
List of violations
|
|
89
93
|
"""
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if not isinstance(config_dict, dict):
|
|
96
|
-
return NestingConfig()
|
|
94
|
+
violations = []
|
|
95
|
+
for func in functions:
|
|
96
|
+
max_depth, _line = analyzer.calculate_max_depth(func)
|
|
97
|
+
if max_depth <= config.max_nesting_depth:
|
|
98
|
+
continue
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
violation = self._violation_builder.create_nesting_violation(
|
|
101
|
+
func, max_depth, config, context
|
|
102
|
+
)
|
|
103
|
+
if not self._should_ignore(violation, context):
|
|
104
|
+
violations.append(violation)
|
|
105
|
+
return violations
|
|
99
106
|
|
|
100
107
|
def _check_python(self, context: BaseLintContext, config: NestingConfig) -> list[Violation]:
|
|
101
108
|
"""Check Python code for nesting violations.
|
|
@@ -110,148 +117,65 @@ class NestingDepthRule(BaseLintRule):
|
|
|
110
117
|
try:
|
|
111
118
|
tree = ast.parse(context.file_content or "")
|
|
112
119
|
except SyntaxError as e:
|
|
113
|
-
return [self.
|
|
120
|
+
return [self._violation_builder.create_syntax_error_violation(e, context)]
|
|
114
121
|
|
|
115
122
|
analyzer = PythonNestingAnalyzer()
|
|
116
123
|
functions = analyzer.find_all_functions(tree)
|
|
117
|
-
return self.
|
|
124
|
+
return self._process_python_functions(functions, analyzer, config, context)
|
|
118
125
|
|
|
119
|
-
def
|
|
120
|
-
self,
|
|
121
|
-
functions: list[ast.FunctionDef | ast.AsyncFunctionDef],
|
|
122
|
-
config: NestingConfig,
|
|
123
|
-
context: BaseLintContext,
|
|
126
|
+
def _process_typescript_functions(
|
|
127
|
+
self, functions: list, analyzer: Any, config: NestingConfig, context: BaseLintContext
|
|
124
128
|
) -> list[Violation]:
|
|
125
|
-
"""
|
|
129
|
+
"""Process TypeScript functions and collect violations.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
functions: List of (function_node, function_name) tuples
|
|
133
|
+
analyzer: TypeScript nesting analyzer
|
|
134
|
+
config: Nesting configuration
|
|
135
|
+
context: Lint context
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of violations
|
|
139
|
+
"""
|
|
126
140
|
violations = []
|
|
127
|
-
for
|
|
128
|
-
|
|
129
|
-
if
|
|
141
|
+
for func_node, func_name in functions:
|
|
142
|
+
max_depth, _line = analyzer.calculate_max_depth(func_node)
|
|
143
|
+
if max_depth <= config.max_nesting_depth:
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
violation = self._violation_builder.create_typescript_nesting_violation(
|
|
147
|
+
(func_node, func_name), max_depth, config, context
|
|
148
|
+
)
|
|
149
|
+
if not self._should_ignore(violation, context):
|
|
130
150
|
violations.append(violation)
|
|
131
151
|
return violations
|
|
132
152
|
|
|
133
|
-
def _check_single_function(
|
|
134
|
-
self,
|
|
135
|
-
func: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
136
|
-
config: NestingConfig,
|
|
137
|
-
context: BaseLintContext,
|
|
138
|
-
) -> Violation | None:
|
|
139
|
-
"""Check a single function for nesting violations."""
|
|
140
|
-
analyzer = PythonNestingAnalyzer()
|
|
141
|
-
max_depth, _line = analyzer.calculate_max_depth(func)
|
|
142
|
-
|
|
143
|
-
if max_depth <= config.max_nesting_depth:
|
|
144
|
-
return None
|
|
145
|
-
|
|
146
|
-
violation = self._create_nesting_violation(func, max_depth, config, context)
|
|
147
|
-
if self._ignore_parser.should_ignore_violation(violation, context.file_content or ""):
|
|
148
|
-
return None
|
|
149
|
-
|
|
150
|
-
return violation
|
|
151
|
-
|
|
152
|
-
def _create_syntax_error_violation(
|
|
153
|
-
self, error: SyntaxError, context: BaseLintContext
|
|
154
|
-
) -> Violation:
|
|
155
|
-
"""Create violation for syntax error."""
|
|
156
|
-
return Violation(
|
|
157
|
-
rule_id=self.rule_id,
|
|
158
|
-
file_path=str(context.file_path or ""),
|
|
159
|
-
line=error.lineno or 0,
|
|
160
|
-
column=error.offset or 0,
|
|
161
|
-
message=f"Syntax error: {error.msg}",
|
|
162
|
-
severity=Severity.ERROR,
|
|
163
|
-
suggestion="Fix syntax errors before checking nesting depth",
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
def _create_nesting_violation(
|
|
167
|
-
self,
|
|
168
|
-
func: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
169
|
-
max_depth: int,
|
|
170
|
-
config: NestingConfig,
|
|
171
|
-
context: BaseLintContext,
|
|
172
|
-
) -> Violation:
|
|
173
|
-
"""Create violation for excessive nesting."""
|
|
174
|
-
return Violation(
|
|
175
|
-
rule_id=self.rule_id,
|
|
176
|
-
file_path=str(context.file_path or ""),
|
|
177
|
-
line=func.lineno,
|
|
178
|
-
column=func.col_offset,
|
|
179
|
-
message=f"Function '{func.name}' has excessive nesting depth ({max_depth})",
|
|
180
|
-
severity=Severity.ERROR,
|
|
181
|
-
suggestion=(
|
|
182
|
-
f"Maximum nesting depth of {max_depth} exceeds limit of {config.max_nesting_depth}. "
|
|
183
|
-
"Consider extracting nested logic to separate functions, using early returns, "
|
|
184
|
-
"or applying guard clauses to reduce nesting."
|
|
185
|
-
),
|
|
186
|
-
)
|
|
187
|
-
|
|
188
153
|
def _check_typescript(self, context: BaseLintContext, config: NestingConfig) -> list[Violation]:
|
|
189
|
-
"""Check TypeScript code for nesting violations.
|
|
154
|
+
"""Check TypeScript code for nesting violations.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
context: Lint context with TypeScript file information
|
|
158
|
+
config: Nesting configuration
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
List of violations found in TypeScript code
|
|
162
|
+
"""
|
|
190
163
|
analyzer = TypeScriptNestingAnalyzer()
|
|
191
164
|
root_node = analyzer.parse_typescript(context.file_content or "")
|
|
192
|
-
|
|
193
165
|
if root_node is None:
|
|
194
166
|
return []
|
|
195
167
|
|
|
196
168
|
functions = analyzer.find_all_functions(root_node)
|
|
197
|
-
return self.
|
|
169
|
+
return self._process_typescript_functions(functions, analyzer, config, context)
|
|
198
170
|
|
|
199
|
-
def
|
|
200
|
-
|
|
201
|
-
) -> list[Violation]:
|
|
202
|
-
"""Check TypeScript functions for nesting violations."""
|
|
203
|
-
violations = []
|
|
171
|
+
def _should_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
|
|
172
|
+
"""Check if violation should be ignored based on inline directives.
|
|
204
173
|
|
|
205
|
-
|
|
206
|
-
violation
|
|
207
|
-
|
|
208
|
-
)
|
|
209
|
-
if violation:
|
|
210
|
-
violations.append(violation)
|
|
211
|
-
|
|
212
|
-
return violations
|
|
174
|
+
Args:
|
|
175
|
+
violation: Violation to check
|
|
176
|
+
context: Lint context with file content
|
|
213
177
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
analyzer = TypeScriptNestingAnalyzer()
|
|
219
|
-
max_depth, _line = analyzer.calculate_max_depth(func_node)
|
|
220
|
-
|
|
221
|
-
if max_depth <= config.max_nesting_depth:
|
|
222
|
-
return None
|
|
223
|
-
|
|
224
|
-
violation = self._create_typescript_nesting_violation(
|
|
225
|
-
func_node, func_name, max_depth, config, context
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
if self._ignore_parser.should_ignore_violation(violation, context.file_content or ""):
|
|
229
|
-
return None
|
|
230
|
-
|
|
231
|
-
return violation
|
|
232
|
-
|
|
233
|
-
def _create_typescript_nesting_violation( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
234
|
-
self,
|
|
235
|
-
func_node: Any, # tree-sitter Node
|
|
236
|
-
func_name: str,
|
|
237
|
-
max_depth: int,
|
|
238
|
-
config: NestingConfig,
|
|
239
|
-
context: BaseLintContext,
|
|
240
|
-
) -> Violation:
|
|
241
|
-
"""Create violation for excessive nesting in TypeScript."""
|
|
242
|
-
line = func_node.start_point[0] + 1 # Convert to 1-indexed
|
|
243
|
-
column = func_node.start_point[1]
|
|
244
|
-
|
|
245
|
-
return Violation(
|
|
246
|
-
rule_id=self.rule_id,
|
|
247
|
-
file_path=str(context.file_path or ""),
|
|
248
|
-
line=line,
|
|
249
|
-
column=column,
|
|
250
|
-
message=f"Function '{func_name}' has excessive nesting depth ({max_depth})",
|
|
251
|
-
severity=Severity.ERROR,
|
|
252
|
-
suggestion=(
|
|
253
|
-
f"Maximum nesting depth of {max_depth} exceeds limit of {config.max_nesting_depth}. "
|
|
254
|
-
"Consider extracting nested logic to separate functions, using early returns, "
|
|
255
|
-
"or applying guard clauses to reduce nesting."
|
|
256
|
-
),
|
|
257
|
-
)
|
|
178
|
+
Returns:
|
|
179
|
+
True if violation should be ignored
|
|
180
|
+
"""
|
|
181
|
+
return self._ignore_parser.should_ignore_violation(violation, context.file_content or "")
|
|
@@ -4,36 +4,36 @@ Purpose: TypeScript AST-based nesting depth calculator
|
|
|
4
4
|
Scope: TypeScript code nesting depth analysis using tree-sitter parser
|
|
5
5
|
|
|
6
6
|
Overview: Analyzes TypeScript code to calculate maximum nesting depth using AST traversal.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
Extends TypeScriptBaseAnalyzer to reuse common tree-sitter initialization and parsing.
|
|
8
|
+
Delegates function extraction to TypeScriptFunctionExtractor. Implements visitor pattern
|
|
9
|
+
to walk TypeScript AST, tracking current depth and maximum depth found. Increments depth
|
|
10
|
+
for control flow statements. Returns maximum depth and location.
|
|
11
11
|
|
|
12
|
-
Dependencies:
|
|
12
|
+
Dependencies: TypeScriptBaseAnalyzer, TypeScriptFunctionExtractor
|
|
13
13
|
|
|
14
|
-
Exports: TypeScriptNestingAnalyzer class with calculate_max_depth
|
|
14
|
+
Exports: TypeScriptNestingAnalyzer class with calculate_max_depth methods
|
|
15
15
|
|
|
16
|
-
Interfaces: calculate_max_depth(func_node) -> tuple[int, int],
|
|
16
|
+
Interfaces: calculate_max_depth(func_node) -> tuple[int, int], find_all_functions(root_node)
|
|
17
17
|
|
|
18
|
-
Implementation: tree-sitter
|
|
18
|
+
Implementation: Inherits tree-sitter parsing from base, visitor pattern with depth tracking
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
from typing import Any
|
|
22
22
|
|
|
23
|
+
from src.analyzers.typescript_base import TypeScriptBaseAnalyzer
|
|
24
|
+
|
|
23
25
|
try:
|
|
24
|
-
|
|
25
|
-
from tree_sitter import Language, Node, Parser
|
|
26
|
+
from tree_sitter import Node
|
|
26
27
|
|
|
27
|
-
TS_LANGUAGE = Language(tstypescript.language_typescript())
|
|
28
|
-
TS_PARSER = Parser(TS_LANGUAGE)
|
|
29
28
|
TREE_SITTER_AVAILABLE = True
|
|
30
29
|
except ImportError:
|
|
31
30
|
TREE_SITTER_AVAILABLE = False
|
|
32
|
-
TS_PARSER = None # type: ignore
|
|
33
31
|
Node = Any # type: ignore
|
|
34
32
|
|
|
33
|
+
from .typescript_function_extractor import TypeScriptFunctionExtractor
|
|
34
|
+
|
|
35
35
|
|
|
36
|
-
class TypeScriptNestingAnalyzer:
|
|
36
|
+
class TypeScriptNestingAnalyzer(TypeScriptBaseAnalyzer):
|
|
37
37
|
"""Calculates maximum nesting depth in TypeScript functions."""
|
|
38
38
|
|
|
39
39
|
# Tree-sitter node types that increase nesting depth
|
|
@@ -48,23 +48,20 @@ class TypeScriptNestingAnalyzer:
|
|
|
48
48
|
"with_statement", # Deprecated but exists
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
def
|
|
52
|
-
"""
|
|
51
|
+
def __init__(self) -> None:
|
|
52
|
+
"""Initialize analyzer with function extractor."""
|
|
53
|
+
super().__init__()
|
|
54
|
+
self.function_extractor = TypeScriptFunctionExtractor()
|
|
55
|
+
|
|
56
|
+
def calculate_max_depth(self, func_node: Node) -> tuple[int, int]:
|
|
57
|
+
"""Calculate maximum nesting depth in a TypeScript function.
|
|
53
58
|
|
|
54
59
|
Args:
|
|
55
|
-
|
|
60
|
+
func_node: Function AST node
|
|
56
61
|
|
|
57
62
|
Returns:
|
|
58
|
-
|
|
63
|
+
Tuple of (max_depth, line_number)
|
|
59
64
|
"""
|
|
60
|
-
if not TREE_SITTER_AVAILABLE or TS_PARSER is None:
|
|
61
|
-
return None
|
|
62
|
-
|
|
63
|
-
tree = TS_PARSER.parse(bytes(code, "utf8"))
|
|
64
|
-
return tree.root_node
|
|
65
|
-
|
|
66
|
-
def calculate_max_depth(self, func_node: Node) -> tuple[int, int]:
|
|
67
|
-
"""Calculate maximum nesting depth in a TypeScript function."""
|
|
68
65
|
if not TREE_SITTER_AVAILABLE:
|
|
69
66
|
return 0, 0
|
|
70
67
|
|
|
@@ -72,17 +69,6 @@ class TypeScriptNestingAnalyzer:
|
|
|
72
69
|
if not body_node:
|
|
73
70
|
return 0, func_node.start_point[0] + 1
|
|
74
71
|
|
|
75
|
-
return self._calculate_depth_in_body(body_node)
|
|
76
|
-
|
|
77
|
-
def _find_function_body(self, func_node: Node) -> Node | None:
|
|
78
|
-
"""Find the statement_block node in a function."""
|
|
79
|
-
for child in func_node.children:
|
|
80
|
-
if child.type == "statement_block":
|
|
81
|
-
return child
|
|
82
|
-
return None
|
|
83
|
-
|
|
84
|
-
def _calculate_depth_in_body(self, body_node: Node) -> tuple[int, int]:
|
|
85
|
-
"""Calculate max depth within a function body."""
|
|
86
72
|
max_depth = 0
|
|
87
73
|
max_depth_line = body_node.start_point[0] + 1
|
|
88
74
|
|
|
@@ -98,6 +84,7 @@ class TypeScriptNestingAnalyzer:
|
|
|
98
84
|
for child in node.children:
|
|
99
85
|
visit_node(child, new_depth)
|
|
100
86
|
|
|
87
|
+
# Start at depth 1 for function body children
|
|
101
88
|
for child in body_node.children:
|
|
102
89
|
visit_node(child, 1)
|
|
103
90
|
|
|
@@ -107,74 +94,23 @@ class TypeScriptNestingAnalyzer:
|
|
|
107
94
|
"""Find all function definitions in TypeScript AST.
|
|
108
95
|
|
|
109
96
|
Args:
|
|
110
|
-
root_node:
|
|
97
|
+
root_node: Root node to search from
|
|
111
98
|
|
|
112
99
|
Returns:
|
|
113
|
-
List of
|
|
100
|
+
List of (function_node, function_name) tuples
|
|
114
101
|
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
self._collect_functions(root_node, functions)
|
|
120
|
-
return functions
|
|
121
|
-
|
|
122
|
-
def _collect_functions(self, node: Node, functions: list[tuple[Node, str]]) -> None:
|
|
123
|
-
"""Recursively collect function nodes from AST."""
|
|
124
|
-
function_entry = self._extract_function_if_applicable(node)
|
|
125
|
-
if function_entry:
|
|
126
|
-
functions.append(function_entry)
|
|
127
|
-
|
|
128
|
-
for child in node.children:
|
|
129
|
-
self._collect_functions(child, functions)
|
|
130
|
-
|
|
131
|
-
def _extract_function_if_applicable(self, node: Node) -> tuple[Node, str] | None:
|
|
132
|
-
"""Extract function node and name if node is a function type."""
|
|
133
|
-
if node.type == "function_declaration":
|
|
134
|
-
return self._extract_function_declaration(node)
|
|
135
|
-
if node.type == "arrow_function":
|
|
136
|
-
return self._extract_arrow_function(node)
|
|
137
|
-
if node.type == "method_definition":
|
|
138
|
-
return self._extract_method_definition(node)
|
|
139
|
-
if node.type in ("function_expression", "function"):
|
|
140
|
-
return self._extract_function_expression(node)
|
|
141
|
-
return None
|
|
102
|
+
return self.function_extractor.collect_all_functions(root_node)
|
|
103
|
+
|
|
104
|
+
def _find_function_body(self, func_node: Node) -> Node | None:
|
|
105
|
+
"""Find the statement_block node in a function.
|
|
142
106
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
name = "<arrow>"
|
|
152
|
-
parent = node.parent
|
|
153
|
-
if parent and parent.type == "variable_declarator":
|
|
154
|
-
id_node = self._find_child_by_type(parent, "identifier")
|
|
155
|
-
if id_node and id_node.text:
|
|
156
|
-
name = id_node.text.decode("utf8")
|
|
157
|
-
return (node, name)
|
|
158
|
-
|
|
159
|
-
def _extract_method_definition(self, node: Node) -> tuple[Node, str]:
|
|
160
|
-
"""Extract name from method definition node."""
|
|
161
|
-
name_node = self._find_child_by_type(node, "property_identifier")
|
|
162
|
-
name = name_node.text.decode("utf8") if name_node and name_node.text else "<method>"
|
|
163
|
-
return (node, name)
|
|
164
|
-
|
|
165
|
-
def _extract_function_expression(self, node: Node) -> tuple[Node, str]:
|
|
166
|
-
"""Extract name from function expression node."""
|
|
167
|
-
name = "<function>"
|
|
168
|
-
parent = node.parent
|
|
169
|
-
if parent and parent.type == "variable_declarator":
|
|
170
|
-
id_node = self._find_child_by_type(parent, "identifier")
|
|
171
|
-
if id_node and id_node.text:
|
|
172
|
-
name = id_node.text.decode("utf8")
|
|
173
|
-
return (node, name)
|
|
174
|
-
|
|
175
|
-
def _find_child_by_type(self, node: Node, child_type: str) -> Node | None:
|
|
176
|
-
"""Find first child node matching the given type."""
|
|
177
|
-
for child in node.children:
|
|
178
|
-
if child.type == child_type:
|
|
107
|
+
Args:
|
|
108
|
+
func_node: Function node to search
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Statement block node or None
|
|
112
|
+
"""
|
|
113
|
+
for child in func_node.children:
|
|
114
|
+
if child.type == "statement_block":
|
|
179
115
|
return child
|
|
180
116
|
return None
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Extract function information from TypeScript AST nodes
|
|
3
|
+
|
|
4
|
+
Scope: Identifies and extracts function metadata from tree-sitter nodes
|
|
5
|
+
|
|
6
|
+
Overview: Provides function extraction functionality for TypeScript AST analysis. Extends
|
|
7
|
+
TypeScriptBaseAnalyzer to reuse tree-sitter utilities. Handles multiple TypeScript
|
|
8
|
+
function forms including function declarations, arrow functions, method definitions,
|
|
9
|
+
and function expressions. Extracts function name and node for each form. Recursively
|
|
10
|
+
collects all functions in an AST tree. Isolates function identification logic from
|
|
11
|
+
nesting depth calculation.
|
|
12
|
+
|
|
13
|
+
Dependencies: TypeScriptBaseAnalyzer, tree-sitter
|
|
14
|
+
|
|
15
|
+
Exports: TypeScriptFunctionExtractor
|
|
16
|
+
|
|
17
|
+
Interfaces: extract_function_info(node) -> (node, name), collect_all_functions(root_node) -> list
|
|
18
|
+
|
|
19
|
+
Implementation: Inherits tree-sitter utilities from base, pattern matching on node types
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from src.analyzers.typescript_base import TypeScriptBaseAnalyzer
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TypeScriptFunctionExtractor(TypeScriptBaseAnalyzer):
|
|
28
|
+
"""Extracts function information from TypeScript AST nodes."""
|
|
29
|
+
|
|
30
|
+
def collect_all_functions(self, root_node: Any) -> list[tuple[Any, str]]:
|
|
31
|
+
"""Collect all function nodes from TypeScript AST.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
root_node: Root tree-sitter node to search
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
List of (function_node, function_name) tuples
|
|
38
|
+
"""
|
|
39
|
+
functions: list[tuple[Any, str]] = []
|
|
40
|
+
self._collect_functions_recursive(root_node, functions)
|
|
41
|
+
return functions
|
|
42
|
+
|
|
43
|
+
def _collect_functions_recursive(self, node: Any, functions: list[tuple[Any, str]]) -> None:
|
|
44
|
+
"""Recursively collect function nodes.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
node: Current node to examine
|
|
48
|
+
functions: List to append found functions to
|
|
49
|
+
"""
|
|
50
|
+
func_info = self.extract_function_info(node)
|
|
51
|
+
if func_info:
|
|
52
|
+
functions.append(func_info)
|
|
53
|
+
|
|
54
|
+
for child in node.children:
|
|
55
|
+
self._collect_functions_recursive(child, functions)
|
|
56
|
+
|
|
57
|
+
def extract_function_info(self, node: Any) -> tuple[Any, str] | None:
|
|
58
|
+
"""Extract function information if node is a function.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
node: Tree-sitter node to check
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Tuple of (function_node, function_name) or None
|
|
65
|
+
"""
|
|
66
|
+
if node.type == "function_declaration":
|
|
67
|
+
return self._extract_function_declaration(node)
|
|
68
|
+
if node.type == "arrow_function":
|
|
69
|
+
return self._extract_arrow_function(node)
|
|
70
|
+
if node.type == "method_definition":
|
|
71
|
+
return self._extract_method_definition(node)
|
|
72
|
+
if node.type == "function":
|
|
73
|
+
return self._extract_function_expression(node)
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
def _extract_function_declaration(self, node: Any) -> tuple[Any, str]:
|
|
77
|
+
"""Extract name from function declaration.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
node: function_declaration node
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Tuple of (node, function_name)
|
|
84
|
+
"""
|
|
85
|
+
name = self.extract_identifier_name(node)
|
|
86
|
+
return node, name
|
|
87
|
+
|
|
88
|
+
def _extract_arrow_function(self, node: Any) -> tuple[Any, str]:
|
|
89
|
+
"""Extract name from arrow function (usually anonymous).
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
node: arrow_function node
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Tuple of (node, "arrow_function" or variable name)
|
|
96
|
+
"""
|
|
97
|
+
parent = node.parent
|
|
98
|
+
if not (parent and parent.type == "variable_declarator"):
|
|
99
|
+
return node, "arrow_function"
|
|
100
|
+
|
|
101
|
+
name = self.extract_identifier_name(parent)
|
|
102
|
+
return node, name if name != "anonymous" else "arrow_function"
|
|
103
|
+
|
|
104
|
+
def _extract_method_definition(self, node: Any) -> tuple[Any, str]:
|
|
105
|
+
"""Extract name from method definition.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
node: method_definition node
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Tuple of (node, method_name)
|
|
112
|
+
"""
|
|
113
|
+
name = self.extract_identifier_name(node)
|
|
114
|
+
return node, name
|
|
115
|
+
|
|
116
|
+
def _extract_function_expression(self, node: Any) -> tuple[Any, str]:
|
|
117
|
+
"""Extract name from function expression.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
node: function expression node
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Tuple of (node, function_name or "function_expression")
|
|
124
|
+
"""
|
|
125
|
+
parent = node.parent
|
|
126
|
+
if not (parent and parent.type == "variable_declarator"):
|
|
127
|
+
return node, "function_expression"
|
|
128
|
+
|
|
129
|
+
name = self.extract_identifier_name(parent)
|
|
130
|
+
return node, name if name != "anonymous" else "function_expression"
|