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.
Files changed (68) hide show
  1. src/__init__.py +7 -2
  2. src/analyzers/__init__.py +23 -0
  3. src/analyzers/typescript_base.py +148 -0
  4. src/api.py +1 -1
  5. src/cli.py +498 -141
  6. src/config.py +6 -31
  7. src/core/base.py +12 -0
  8. src/core/cli_utils.py +206 -0
  9. src/core/config_parser.py +99 -0
  10. src/core/linter_utils.py +168 -0
  11. src/core/registry.py +17 -92
  12. src/core/rule_discovery.py +132 -0
  13. src/core/violation_builder.py +122 -0
  14. src/linter_config/ignore.py +112 -40
  15. src/linter_config/loader.py +3 -13
  16. src/linters/dry/__init__.py +23 -0
  17. src/linters/dry/base_token_analyzer.py +76 -0
  18. src/linters/dry/block_filter.py +262 -0
  19. src/linters/dry/block_grouper.py +59 -0
  20. src/linters/dry/cache.py +218 -0
  21. src/linters/dry/cache_query.py +61 -0
  22. src/linters/dry/config.py +130 -0
  23. src/linters/dry/config_loader.py +44 -0
  24. src/linters/dry/deduplicator.py +120 -0
  25. src/linters/dry/duplicate_storage.py +126 -0
  26. src/linters/dry/file_analyzer.py +127 -0
  27. src/linters/dry/inline_ignore.py +140 -0
  28. src/linters/dry/linter.py +170 -0
  29. src/linters/dry/python_analyzer.py +517 -0
  30. src/linters/dry/storage_initializer.py +51 -0
  31. src/linters/dry/token_hasher.py +115 -0
  32. src/linters/dry/typescript_analyzer.py +590 -0
  33. src/linters/dry/violation_builder.py +74 -0
  34. src/linters/dry/violation_filter.py +91 -0
  35. src/linters/dry/violation_generator.py +174 -0
  36. src/linters/file_placement/config_loader.py +86 -0
  37. src/linters/file_placement/directory_matcher.py +80 -0
  38. src/linters/file_placement/linter.py +252 -472
  39. src/linters/file_placement/path_resolver.py +61 -0
  40. src/linters/file_placement/pattern_matcher.py +55 -0
  41. src/linters/file_placement/pattern_validator.py +106 -0
  42. src/linters/file_placement/rule_checker.py +229 -0
  43. src/linters/file_placement/violation_factory.py +177 -0
  44. src/linters/nesting/config.py +13 -3
  45. src/linters/nesting/linter.py +76 -152
  46. src/linters/nesting/typescript_analyzer.py +38 -102
  47. src/linters/nesting/typescript_function_extractor.py +130 -0
  48. src/linters/nesting/violation_builder.py +139 -0
  49. src/linters/srp/__init__.py +99 -0
  50. src/linters/srp/class_analyzer.py +113 -0
  51. src/linters/srp/config.py +76 -0
  52. src/linters/srp/heuristics.py +89 -0
  53. src/linters/srp/linter.py +225 -0
  54. src/linters/srp/metrics_evaluator.py +47 -0
  55. src/linters/srp/python_analyzer.py +72 -0
  56. src/linters/srp/typescript_analyzer.py +75 -0
  57. src/linters/srp/typescript_metrics_calculator.py +90 -0
  58. src/linters/srp/violation_builder.py +117 -0
  59. src/orchestrator/core.py +42 -7
  60. src/utils/__init__.py +4 -0
  61. src/utils/project_root.py +84 -0
  62. {thailint-0.1.5.dist-info → thailint-0.2.0.dist-info}/METADATA +414 -63
  63. thailint-0.2.0.dist-info/RECORD +75 -0
  64. src/.ai/layout.yaml +0 -48
  65. thailint-0.1.5.dist-info/RECORD +0 -28
  66. {thailint-0.1.5.dist-info → thailint-0.2.0.dist-info}/LICENSE +0 -0
  67. {thailint-0.1.5.dist-info → thailint-0.2.0.dist-info}/WHEEL +0 -0
  68. {thailint-0.1.5.dist-info → thailint-0.2.0.dist-info}/entry_points.txt +0 -0
@@ -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. Detects excessive
7
- nesting depth in Python and TypeScript code using AST analysis. Supports configurable
8
- max_nesting_depth limit (default 4). Provides helpful violation messages with refactoring
9
- suggestions (early returns, guard clauses, extract method). Integrates with orchestrator for
10
- automatic rule discovery. Handles both Python (using ast) and TypeScript (using typescript-estree)
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, Violation, PythonNestingAnalyzer, TypeScriptNestingAnalyzer
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 and helpful error messages
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.types import Severity, Violation
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.file_content is None:
67
+ if not has_file_content(context):
67
68
  return []
68
69
 
69
- # Load configuration
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 _load_config(self, context: BaseLintContext) -> NestingConfig:
82
- """Load configuration from context metadata.
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
- context: Lint context containing metadata
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
- NestingConfig instance with configuration values
92
+ List of violations
89
93
  """
90
- metadata = getattr(context, "metadata", None)
91
- if metadata is None or not isinstance(metadata, dict):
92
- return NestingConfig()
93
-
94
- config_dict = metadata.get("nesting", {})
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
- return NestingConfig.from_dict(config_dict)
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._create_syntax_error_violation(e, context)]
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._check_functions(functions, config, context)
124
+ return self._process_python_functions(functions, analyzer, config, context)
118
125
 
119
- def _check_functions(
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
- """Check list of functions for nesting violations."""
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 func in functions:
128
- violation = self._check_single_function(func, config, context)
129
- if violation:
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._check_typescript_functions(functions, config, context)
169
+ return self._process_typescript_functions(functions, analyzer, config, context)
198
170
 
199
- def _check_typescript_functions(
200
- self, functions: list, config: NestingConfig, context: BaseLintContext
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
- 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)
211
-
212
- return violations
174
+ Args:
175
+ violation: Violation to check
176
+ context: Lint context with file content
213
177
 
214
- def _check_single_typescript_function(
215
- self, func_node: Any, func_name: str, config: NestingConfig, context: BaseLintContext
216
- ) -> Violation | None:
217
- """Check a single TypeScript function for nesting violations."""
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
- Implements visitor pattern to walk TypeScript AST from tree-sitter, tracking current depth
8
- and maximum depth found. Increments depth for if_statement, for_statement, for_in_statement,
9
- while_statement, do_statement, try_statement, switch_statement nodes. Starts depth counting
10
- at 1 for function body. Returns maximum depth found and location.
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: tree-sitter, tree-sitter-typescript for TypeScript parsing
12
+ Dependencies: TypeScriptBaseAnalyzer, TypeScriptFunctionExtractor
13
13
 
14
- Exports: TypeScriptNestingAnalyzer class with calculate_max_depth and parse_typescript methods
14
+ Exports: TypeScriptNestingAnalyzer class with calculate_max_depth methods
15
15
 
16
- Interfaces: calculate_max_depth(func_node) -> tuple[int, int], parse_typescript(code: str)
16
+ Interfaces: calculate_max_depth(func_node) -> tuple[int, int], find_all_functions(root_node)
17
17
 
18
- Implementation: tree-sitter AST visitor pattern with depth tracking for TypeScript
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
- import tree_sitter_typescript as tstypescript
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 parse_typescript(self, code: str) -> Node | None:
52
- """Parse TypeScript code to AST using tree-sitter.
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
- code: TypeScript source code to parse
60
+ func_node: Function AST node
56
61
 
57
62
  Returns:
58
- Tree-sitter AST root node, or None if parsing fails
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: Tree-sitter root node
97
+ root_node: Root node to search from
111
98
 
112
99
  Returns:
113
- List of tuples: (function_node, function_name)
100
+ List of (function_node, function_name) tuples
114
101
  """
115
- if not TREE_SITTER_AVAILABLE:
116
- return []
117
-
118
- functions: list[tuple[Node, str]] = []
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
- def _extract_function_declaration(self, node: Node) -> tuple[Node, str]:
144
- """Extract name from function declaration node."""
145
- name_node = self._find_child_by_type(node, "identifier")
146
- name = name_node.text.decode("utf8") if name_node and name_node.text else "<anonymous>"
147
- return (node, name)
148
-
149
- def _extract_arrow_function(self, node: Node) -> tuple[Node, str]:
150
- """Extract name from arrow function node."""
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"