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.
Files changed (91) 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 +1111 -144
  6. src/config.py +12 -33
  7. src/core/base.py +102 -5
  8. src/core/cli_utils.py +206 -0
  9. src/core/config_parser.py +126 -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 +265 -0
  19. src/linters/dry/block_grouper.py +59 -0
  20. src/linters/dry/cache.py +172 -0
  21. src/linters/dry/cache_query.py +61 -0
  22. src/linters/dry/config.py +134 -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 +63 -0
  26. src/linters/dry/file_analyzer.py +90 -0
  27. src/linters/dry/inline_ignore.py +140 -0
  28. src/linters/dry/linter.py +163 -0
  29. src/linters/dry/python_analyzer.py +668 -0
  30. src/linters/dry/storage_initializer.py +42 -0
  31. src/linters/dry/token_hasher.py +169 -0
  32. src/linters/dry/typescript_analyzer.py +592 -0
  33. src/linters/dry/violation_builder.py +74 -0
  34. src/linters/dry/violation_filter.py +94 -0
  35. src/linters/dry/violation_generator.py +174 -0
  36. src/linters/file_header/__init__.py +24 -0
  37. src/linters/file_header/atemporal_detector.py +87 -0
  38. src/linters/file_header/config.py +66 -0
  39. src/linters/file_header/field_validator.py +69 -0
  40. src/linters/file_header/linter.py +313 -0
  41. src/linters/file_header/python_parser.py +86 -0
  42. src/linters/file_header/violation_builder.py +78 -0
  43. src/linters/file_placement/config_loader.py +86 -0
  44. src/linters/file_placement/directory_matcher.py +80 -0
  45. src/linters/file_placement/linter.py +262 -471
  46. src/linters/file_placement/path_resolver.py +61 -0
  47. src/linters/file_placement/pattern_matcher.py +55 -0
  48. src/linters/file_placement/pattern_validator.py +106 -0
  49. src/linters/file_placement/rule_checker.py +229 -0
  50. src/linters/file_placement/violation_factory.py +177 -0
  51. src/linters/magic_numbers/__init__.py +48 -0
  52. src/linters/magic_numbers/config.py +82 -0
  53. src/linters/magic_numbers/context_analyzer.py +247 -0
  54. src/linters/magic_numbers/linter.py +516 -0
  55. src/linters/magic_numbers/python_analyzer.py +76 -0
  56. src/linters/magic_numbers/typescript_analyzer.py +218 -0
  57. src/linters/magic_numbers/violation_builder.py +98 -0
  58. src/linters/nesting/__init__.py +6 -2
  59. src/linters/nesting/config.py +17 -4
  60. src/linters/nesting/linter.py +81 -168
  61. src/linters/nesting/typescript_analyzer.py +39 -102
  62. src/linters/nesting/typescript_function_extractor.py +130 -0
  63. src/linters/nesting/violation_builder.py +139 -0
  64. src/linters/print_statements/__init__.py +53 -0
  65. src/linters/print_statements/config.py +83 -0
  66. src/linters/print_statements/linter.py +430 -0
  67. src/linters/print_statements/python_analyzer.py +155 -0
  68. src/linters/print_statements/typescript_analyzer.py +135 -0
  69. src/linters/print_statements/violation_builder.py +98 -0
  70. src/linters/srp/__init__.py +99 -0
  71. src/linters/srp/class_analyzer.py +113 -0
  72. src/linters/srp/config.py +82 -0
  73. src/linters/srp/heuristics.py +89 -0
  74. src/linters/srp/linter.py +234 -0
  75. src/linters/srp/metrics_evaluator.py +47 -0
  76. src/linters/srp/python_analyzer.py +72 -0
  77. src/linters/srp/typescript_analyzer.py +75 -0
  78. src/linters/srp/typescript_metrics_calculator.py +90 -0
  79. src/linters/srp/violation_builder.py +117 -0
  80. src/orchestrator/core.py +54 -9
  81. src/templates/thailint_config_template.yaml +158 -0
  82. src/utils/__init__.py +4 -0
  83. src/utils/project_root.py +203 -0
  84. thailint-0.5.0.dist-info/METADATA +1286 -0
  85. thailint-0.5.0.dist-info/RECORD +96 -0
  86. {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info}/WHEEL +1 -1
  87. src/.ai/layout.yaml +0 -48
  88. thailint-0.1.5.dist-info/METADATA +0 -629
  89. thailint-0.1.5.dist-info/RECORD +0 -28
  90. {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info}/entry_points.txt +0 -0
  91. {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info/licenses}/LICENSE +0 -0
@@ -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. 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
- from src.core.base import BaseLintContext, BaseLintRule
27
- from src.core.types import Severity, Violation
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(BaseLintRule):
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 check(self, context: BaseLintContext) -> list[Violation]:
58
- """Check for excessive nesting depth violations.
58
+ def _load_config(self, context: BaseLintContext) -> NestingConfig:
59
+ """Load configuration from context.
59
60
 
60
61
  Args:
61
- context: Lint context with file information
62
+ context: Lint context
62
63
 
63
64
  Returns:
64
- List of violations found
65
+ NestingConfig instance
65
66
  """
66
- if context.file_content is None:
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 _load_config(self, context: BaseLintContext) -> NestingConfig:
82
- """Load configuration from context metadata.
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
- context: Lint context containing metadata
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
- NestingConfig instance with configuration values
81
+ List of violations
89
82
  """
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()
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
- return NestingConfig.from_dict(config_dict)
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._create_syntax_error_violation(e, context)]
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._check_functions(functions, config, context)
113
+ return self._process_python_functions(functions, analyzer, config, context)
118
114
 
119
- def _check_functions(
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
- """Check list of functions for nesting violations."""
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 func in functions:
128
- violation = self._check_single_function(func, config, context)
129
- if violation:
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._check_typescript_functions(functions, config, context)
158
+ return self._process_typescript_functions(functions, analyzer, config, context)
198
159
 
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 = []
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
- return violations
163
+ Args:
164
+ violation: Violation to check
165
+ context: Lint context with file content
213
166
 
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
- )
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
- 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
+
25
+ # dry: ignore-block - tree-sitter import pattern (common across TypeScript analyzers)
23
26
  try:
24
- import tree_sitter_typescript as tstypescript
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 parse_typescript(self, code: str) -> Node | None:
52
- """Parse TypeScript code to AST using tree-sitter.
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
- code: TypeScript source code to parse
61
+ func_node: Function AST node
56
62
 
57
63
  Returns:
58
- Tree-sitter AST root node, or None if parsing fails
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: Tree-sitter root node
98
+ root_node: Root node to search from
111
99
 
112
100
  Returns:
113
- List of tuples: (function_node, function_name)
101
+ List of (function_node, function_name) tuples
114
102
  """
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
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
- 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:
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"