thailint 0.1.5__py3-none-any.whl → 0.5.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 +1111 -144
- src/config.py +12 -33
- src/core/base.py +102 -5
- src/core/cli_utils.py +206 -0
- src/core/config_parser.py +126 -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 +265 -0
- src/linters/dry/block_grouper.py +59 -0
- src/linters/dry/cache.py +172 -0
- src/linters/dry/cache_query.py +61 -0
- src/linters/dry/config.py +134 -0
- src/linters/dry/config_loader.py +44 -0
- src/linters/dry/deduplicator.py +120 -0
- src/linters/dry/duplicate_storage.py +63 -0
- src/linters/dry/file_analyzer.py +90 -0
- src/linters/dry/inline_ignore.py +140 -0
- src/linters/dry/linter.py +163 -0
- src/linters/dry/python_analyzer.py +668 -0
- src/linters/dry/storage_initializer.py +42 -0
- src/linters/dry/token_hasher.py +169 -0
- src/linters/dry/typescript_analyzer.py +592 -0
- src/linters/dry/violation_builder.py +74 -0
- src/linters/dry/violation_filter.py +94 -0
- src/linters/dry/violation_generator.py +174 -0
- src/linters/file_header/__init__.py +24 -0
- src/linters/file_header/atemporal_detector.py +87 -0
- src/linters/file_header/config.py +66 -0
- src/linters/file_header/field_validator.py +69 -0
- src/linters/file_header/linter.py +313 -0
- src/linters/file_header/python_parser.py +86 -0
- src/linters/file_header/violation_builder.py +78 -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 +262 -471
- 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/magic_numbers/__init__.py +48 -0
- src/linters/magic_numbers/config.py +82 -0
- src/linters/magic_numbers/context_analyzer.py +247 -0
- src/linters/magic_numbers/linter.py +516 -0
- src/linters/magic_numbers/python_analyzer.py +76 -0
- src/linters/magic_numbers/typescript_analyzer.py +218 -0
- src/linters/magic_numbers/violation_builder.py +98 -0
- src/linters/nesting/__init__.py +6 -2
- src/linters/nesting/config.py +17 -4
- src/linters/nesting/linter.py +81 -168
- src/linters/nesting/typescript_analyzer.py +39 -102
- src/linters/nesting/typescript_function_extractor.py +130 -0
- src/linters/nesting/violation_builder.py +139 -0
- src/linters/print_statements/__init__.py +53 -0
- src/linters/print_statements/config.py +83 -0
- src/linters/print_statements/linter.py +430 -0
- src/linters/print_statements/python_analyzer.py +155 -0
- src/linters/print_statements/typescript_analyzer.py +135 -0
- src/linters/print_statements/violation_builder.py +98 -0
- src/linters/srp/__init__.py +99 -0
- src/linters/srp/class_analyzer.py +113 -0
- src/linters/srp/config.py +82 -0
- src/linters/srp/heuristics.py +89 -0
- src/linters/srp/linter.py +234 -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 +54 -9
- src/templates/thailint_config_template.yaml +158 -0
- src/utils/__init__.py +4 -0
- src/utils/project_root.py +203 -0
- thailint-0.5.0.dist-info/METADATA +1286 -0
- thailint-0.5.0.dist-info/RECORD +96 -0
- {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info}/WHEEL +1 -1
- src/.ai/layout.yaml +0 -48
- thailint-0.1.5.dist-info/METADATA +0 -629
- thailint-0.1.5.dist-info/RECORD +0 -28
- {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info}/entry_points.txt +0 -0
- {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info/licenses}/LICENSE +0 -0
src/linters/nesting/linter.py
CHANGED
|
@@ -3,41 +3,42 @@ 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
|
-
from src.core.base import BaseLintContext,
|
|
27
|
-
from src.core.
|
|
24
|
+
from src.core.base import BaseLintContext, MultiLanguageLintRule
|
|
25
|
+
from src.core.linter_utils import 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
|
-
class NestingDepthRule(
|
|
35
|
+
class NestingDepthRule(MultiLanguageLintRule):
|
|
36
36
|
"""Detects excessive nesting depth in functions."""
|
|
37
37
|
|
|
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:
|
|
@@ -54,48 +55,43 @@ class NestingDepthRule(BaseLintRule):
|
|
|
54
55
|
"""Description of what this rule checks."""
|
|
55
56
|
return "Functions should not have excessive nesting depth for better readability"
|
|
56
57
|
|
|
57
|
-
def
|
|
58
|
-
"""
|
|
58
|
+
def _load_config(self, context: BaseLintContext) -> NestingConfig:
|
|
59
|
+
"""Load configuration from context.
|
|
59
60
|
|
|
60
61
|
Args:
|
|
61
|
-
context: Lint context
|
|
62
|
+
context: Lint context
|
|
62
63
|
|
|
63
64
|
Returns:
|
|
64
|
-
|
|
65
|
+
NestingConfig instance
|
|
65
66
|
"""
|
|
66
|
-
|
|
67
|
-
return []
|
|
68
|
-
|
|
69
|
-
# Load configuration
|
|
70
|
-
config = self._load_config(context)
|
|
71
|
-
if not config.enabled:
|
|
72
|
-
return []
|
|
73
|
-
|
|
74
|
-
# Analyze based on language
|
|
75
|
-
if context.language == "python":
|
|
76
|
-
return self._check_python(context, config)
|
|
77
|
-
if context.language in ("typescript", "javascript"):
|
|
78
|
-
return self._check_typescript(context, config)
|
|
79
|
-
return []
|
|
67
|
+
return load_linter_config(context, "nesting", NestingConfig)
|
|
80
68
|
|
|
81
|
-
def
|
|
82
|
-
|
|
69
|
+
def _process_python_functions(
|
|
70
|
+
self, functions: list, analyzer: Any, config: NestingConfig, context: BaseLintContext
|
|
71
|
+
) -> list[Violation]:
|
|
72
|
+
"""Process Python functions and collect violations.
|
|
83
73
|
|
|
84
74
|
Args:
|
|
85
|
-
|
|
75
|
+
functions: List of function AST nodes
|
|
76
|
+
analyzer: Python nesting analyzer
|
|
77
|
+
config: Nesting configuration
|
|
78
|
+
context: Lint context
|
|
86
79
|
|
|
87
80
|
Returns:
|
|
88
|
-
|
|
81
|
+
List of violations
|
|
89
82
|
"""
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if not isinstance(config_dict, dict):
|
|
96
|
-
return NestingConfig()
|
|
83
|
+
violations = []
|
|
84
|
+
for func in functions:
|
|
85
|
+
max_depth, _line = analyzer.calculate_max_depth(func)
|
|
86
|
+
if max_depth <= config.max_nesting_depth:
|
|
87
|
+
continue
|
|
97
88
|
|
|
98
|
-
|
|
89
|
+
violation = self._violation_builder.create_nesting_violation(
|
|
90
|
+
func, max_depth, config, context
|
|
91
|
+
)
|
|
92
|
+
if not self._should_ignore(violation, context):
|
|
93
|
+
violations.append(violation)
|
|
94
|
+
return violations
|
|
99
95
|
|
|
100
96
|
def _check_python(self, context: BaseLintContext, config: NestingConfig) -> list[Violation]:
|
|
101
97
|
"""Check Python code for nesting violations.
|
|
@@ -110,148 +106,65 @@ class NestingDepthRule(BaseLintRule):
|
|
|
110
106
|
try:
|
|
111
107
|
tree = ast.parse(context.file_content or "")
|
|
112
108
|
except SyntaxError as e:
|
|
113
|
-
return [self.
|
|
109
|
+
return [self._violation_builder.create_syntax_error_violation(e, context)]
|
|
114
110
|
|
|
115
111
|
analyzer = PythonNestingAnalyzer()
|
|
116
112
|
functions = analyzer.find_all_functions(tree)
|
|
117
|
-
return self.
|
|
113
|
+
return self._process_python_functions(functions, analyzer, config, context)
|
|
118
114
|
|
|
119
|
-
def
|
|
120
|
-
self,
|
|
121
|
-
functions: list[ast.FunctionDef | ast.AsyncFunctionDef],
|
|
122
|
-
config: NestingConfig,
|
|
123
|
-
context: BaseLintContext,
|
|
115
|
+
def _process_typescript_functions(
|
|
116
|
+
self, functions: list, analyzer: Any, config: NestingConfig, context: BaseLintContext
|
|
124
117
|
) -> list[Violation]:
|
|
125
|
-
"""
|
|
118
|
+
"""Process TypeScript functions and collect violations.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
functions: List of (function_node, function_name) tuples
|
|
122
|
+
analyzer: TypeScript nesting analyzer
|
|
123
|
+
config: Nesting configuration
|
|
124
|
+
context: Lint context
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List of violations
|
|
128
|
+
"""
|
|
126
129
|
violations = []
|
|
127
|
-
for
|
|
128
|
-
|
|
129
|
-
if
|
|
130
|
+
for func_node, func_name in functions:
|
|
131
|
+
max_depth, _line = analyzer.calculate_max_depth(func_node)
|
|
132
|
+
if max_depth <= config.max_nesting_depth:
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
violation = self._violation_builder.create_typescript_nesting_violation(
|
|
136
|
+
(func_node, func_name), max_depth, config, context
|
|
137
|
+
)
|
|
138
|
+
if not self._should_ignore(violation, context):
|
|
130
139
|
violations.append(violation)
|
|
131
140
|
return violations
|
|
132
141
|
|
|
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
142
|
def _check_typescript(self, context: BaseLintContext, config: NestingConfig) -> list[Violation]:
|
|
189
|
-
"""Check TypeScript code for nesting violations.
|
|
143
|
+
"""Check TypeScript code for nesting violations.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
context: Lint context with TypeScript file information
|
|
147
|
+
config: Nesting configuration
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
List of violations found in TypeScript code
|
|
151
|
+
"""
|
|
190
152
|
analyzer = TypeScriptNestingAnalyzer()
|
|
191
153
|
root_node = analyzer.parse_typescript(context.file_content or "")
|
|
192
|
-
|
|
193
154
|
if root_node is None:
|
|
194
155
|
return []
|
|
195
156
|
|
|
196
157
|
functions = analyzer.find_all_functions(root_node)
|
|
197
|
-
return self.
|
|
158
|
+
return self._process_typescript_functions(functions, analyzer, config, context)
|
|
198
159
|
|
|
199
|
-
def
|
|
200
|
-
|
|
201
|
-
) -> list[Violation]:
|
|
202
|
-
"""Check TypeScript functions for nesting violations."""
|
|
203
|
-
violations = []
|
|
204
|
-
|
|
205
|
-
for func_node, func_name in functions:
|
|
206
|
-
violation = self._check_single_typescript_function(
|
|
207
|
-
func_node, func_name, config, context
|
|
208
|
-
)
|
|
209
|
-
if violation:
|
|
210
|
-
violations.append(violation)
|
|
160
|
+
def _should_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
|
|
161
|
+
"""Check if violation should be ignored based on inline directives.
|
|
211
162
|
|
|
212
|
-
|
|
163
|
+
Args:
|
|
164
|
+
violation: Violation to check
|
|
165
|
+
context: Lint context with file content
|
|
213
166
|
|
|
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
|
-
)
|
|
167
|
+
Returns:
|
|
168
|
+
True if violation should be ignored
|
|
169
|
+
"""
|
|
170
|
+
return self._ignore_parser.should_ignore_violation(violation, context.file_content or "")
|
|
@@ -4,36 +4,37 @@ 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
|
+
|
|
25
|
+
# dry: ignore-block - tree-sitter import pattern (common across TypeScript analyzers)
|
|
23
26
|
try:
|
|
24
|
-
|
|
25
|
-
from tree_sitter import Language, Node, Parser
|
|
27
|
+
from tree_sitter import Node
|
|
26
28
|
|
|
27
|
-
TS_LANGUAGE = Language(tstypescript.language_typescript())
|
|
28
|
-
TS_PARSER = Parser(TS_LANGUAGE)
|
|
29
29
|
TREE_SITTER_AVAILABLE = True
|
|
30
30
|
except ImportError:
|
|
31
31
|
TREE_SITTER_AVAILABLE = False
|
|
32
|
-
TS_PARSER = None # type: ignore
|
|
33
32
|
Node = Any # type: ignore
|
|
34
33
|
|
|
34
|
+
from .typescript_function_extractor import TypeScriptFunctionExtractor
|
|
35
|
+
|
|
35
36
|
|
|
36
|
-
class TypeScriptNestingAnalyzer:
|
|
37
|
+
class TypeScriptNestingAnalyzer(TypeScriptBaseAnalyzer):
|
|
37
38
|
"""Calculates maximum nesting depth in TypeScript functions."""
|
|
38
39
|
|
|
39
40
|
# Tree-sitter node types that increase nesting depth
|
|
@@ -48,23 +49,20 @@ class TypeScriptNestingAnalyzer:
|
|
|
48
49
|
"with_statement", # Deprecated but exists
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
def
|
|
52
|
-
"""
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
"""Initialize analyzer with function extractor."""
|
|
54
|
+
super().__init__()
|
|
55
|
+
self.function_extractor = TypeScriptFunctionExtractor()
|
|
56
|
+
|
|
57
|
+
def calculate_max_depth(self, func_node: Node) -> tuple[int, int]:
|
|
58
|
+
"""Calculate maximum nesting depth in a TypeScript function.
|
|
53
59
|
|
|
54
60
|
Args:
|
|
55
|
-
|
|
61
|
+
func_node: Function AST node
|
|
56
62
|
|
|
57
63
|
Returns:
|
|
58
|
-
|
|
64
|
+
Tuple of (max_depth, line_number)
|
|
59
65
|
"""
|
|
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
66
|
if not TREE_SITTER_AVAILABLE:
|
|
69
67
|
return 0, 0
|
|
70
68
|
|
|
@@ -72,17 +70,6 @@ class TypeScriptNestingAnalyzer:
|
|
|
72
70
|
if not body_node:
|
|
73
71
|
return 0, func_node.start_point[0] + 1
|
|
74
72
|
|
|
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
73
|
max_depth = 0
|
|
87
74
|
max_depth_line = body_node.start_point[0] + 1
|
|
88
75
|
|
|
@@ -98,6 +85,7 @@ class TypeScriptNestingAnalyzer:
|
|
|
98
85
|
for child in node.children:
|
|
99
86
|
visit_node(child, new_depth)
|
|
100
87
|
|
|
88
|
+
# Start at depth 1 for function body children
|
|
101
89
|
for child in body_node.children:
|
|
102
90
|
visit_node(child, 1)
|
|
103
91
|
|
|
@@ -107,74 +95,23 @@ class TypeScriptNestingAnalyzer:
|
|
|
107
95
|
"""Find all function definitions in TypeScript AST.
|
|
108
96
|
|
|
109
97
|
Args:
|
|
110
|
-
root_node:
|
|
98
|
+
root_node: Root node to search from
|
|
111
99
|
|
|
112
100
|
Returns:
|
|
113
|
-
List of
|
|
101
|
+
List of (function_node, function_name) tuples
|
|
114
102
|
"""
|
|
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
|
|
103
|
+
return self.function_extractor.collect_all_functions(root_node)
|
|
104
|
+
|
|
105
|
+
def _find_function_body(self, func_node: Node) -> Node | None:
|
|
106
|
+
"""Find the statement_block node in a function.
|
|
142
107
|
|
|
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:
|
|
108
|
+
Args:
|
|
109
|
+
func_node: Function node to search
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Statement block node or None
|
|
113
|
+
"""
|
|
114
|
+
for child in func_node.children:
|
|
115
|
+
if child.type == "statement_block":
|
|
179
116
|
return child
|
|
180
117
|
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"
|