Coverage for dead_code / parsers / typescript.py: 71%
192 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1"""TypeScript dead code parser."""
3from __future__ import annotations
5from tree_sitter import Node
7from parsers.typescript import TS_LANGUAGE
8from patterns.common import create_query, run_captures
9from dead_code.parsers.base import (
10 SymbolDefinition,
11 ImportedSymbol,
12 VariableDefinition,
13 SymbolReference,
14 DeadCodeParser,
15)
18class TypeScriptDeadCodeParser(DeadCodeParser):
19 def extract_imports(self, root: Node, source: str) -> list[ImportedSymbol]:
20 imports = []
21 query_str = "(import_statement) @import_stmt"
22 query = create_query(TS_LANGUAGE, query_str)
23 captures = run_captures(query, root)
25 for stmt_node, _ in captures:
26 self._process_import_statement(stmt_node, imports)
27 return imports
29 def _process_import_statement(
30 self, stmt_node: Node, imports: list[ImportedSymbol]
31 ) -> None:
32 for child in stmt_node.children:
33 if child.type == "import" and len(stmt_node.children) > 1:
34 next_child = stmt_node.children[1]
35 if (
36 next_child.type == "type"
37 and next_child.text
38 and next_child.text.decode() == "type"
39 ):
40 return
41 if child.type == "import_clause":
42 self._process_import_clause(child, imports)
43 break
45 def _process_import_clause(
46 self, clause_node: Node, imports: list[ImportedSymbol]
47 ) -> None:
48 for child in clause_node.children:
49 if child.type == "named_imports":
50 self._process_named_imports(child, imports)
51 elif child.type == "namespace_import":
52 self._process_namespace_import(child, imports)
53 elif child.type == "identifier":
54 name = child.text.decode() if child.text else ""
55 if name and name[0].isupper():
56 imports.append(
57 ImportedSymbol(name, child.start_point[0] + 1, None, None)
58 )
60 def _process_named_imports(
61 self, named_imports: Node, imports: list[ImportedSymbol]
62 ) -> None:
63 for child in named_imports.children:
64 if child.type == "import_specifier":
65 for nested in child.children:
66 if nested.type == "identifier":
67 name = nested.text.decode() if nested.text else ""
68 if name:
69 imports.append(
70 ImportedSymbol(
71 name, nested.start_point[0] + 1, None, None
72 )
73 )
75 def _process_namespace_import(
76 self, namespace_node: Node, imports: list[ImportedSymbol]
77 ) -> None:
78 for child in namespace_node.children:
79 if child.type == "identifier":
80 name = child.text.decode() if child.text else ""
81 if name:
82 imports.append(
83 ImportedSymbol(name, child.start_point[0] + 1, None, None)
84 )
86 def extract_definitions(self, root: Node, source: str) -> list[SymbolDefinition]:
87 defs = []
88 query_str = "(function_declaration) @func_decl"
89 try:
90 query = create_query(TS_LANGUAGE, query_str)
91 captures = run_captures(query, root)
92 for node, _ in captures:
93 for child in node.children:
94 if child.type == "identifier":
95 text = child.text.decode() if child.text else ""
96 is_exported = self.is_exported(node)
97 defs.append(
98 SymbolDefinition(
99 text, node.start_point[0] + 1, "function", is_exported
100 )
101 )
102 break
103 except Exception:
104 pass
106 query_str = "(generator_function_declaration) @func_decl"
107 try:
108 query = create_query(TS_LANGUAGE, query_str)
109 captures = run_captures(query, root)
110 for node, _ in captures:
111 for child in node.children:
112 if child.type == "identifier":
113 text = child.text.decode() if child.text else ""
114 is_exported = self.is_exported(node)
115 defs.append(
116 SymbolDefinition(
117 text, node.start_point[0] + 1, "function", is_exported
118 )
119 )
120 break
121 except Exception:
122 pass
124 query_str = "(class_declaration) @class_decl"
125 try:
126 query = create_query(TS_LANGUAGE, query_str)
127 captures = run_captures(query, root)
128 for node, _ in captures:
129 for child in node.children:
130 if child.type == "type_identifier":
131 text = child.text.decode() if child.text else ""
132 is_exported = self.is_exported(node)
133 defs.append(
134 SymbolDefinition(
135 text, node.start_point[0] + 1, "class", is_exported
136 )
137 )
138 break
139 except Exception:
140 pass
142 query_str = "(method_definition) @method_def"
143 try:
144 query = create_query(TS_LANGUAGE, query_str)
145 captures = run_captures(query, root)
146 for node, _ in captures:
147 for child in node.children:
148 if child.type == "property_identifier":
149 text = child.text.decode() if child.text else ""
150 is_exported = self.is_exported(node)
151 defs.append(
152 SymbolDefinition(
153 text, node.start_point[0] + 1, "function", is_exported
154 )
155 )
156 break
157 except Exception:
158 pass
160 query_str = "(lexical_declaration (variable_declarator name: (identifier) @name value: (arrow_function)))"
161 try:
162 query = create_query(TS_LANGUAGE, query_str)
163 captures = run_captures(query, root)
164 for node, capture_name in captures:
165 text = node.text.decode() if node.text else ""
166 if capture_name == "name":
167 lexical_parent = node.parent.parent
168 is_exported = (
169 self.is_exported(lexical_parent) if lexical_parent else False
170 )
171 defs.append(
172 SymbolDefinition(
173 text, node.start_point[0] + 1, "function", is_exported
174 )
175 )
176 except Exception:
177 pass
179 return defs
181 def extract_abstract_info(
182 self, root: Node, source: str
183 ) -> tuple[set[str], set[str]]:
184 abstract_classes = set()
185 abstract_methods = set()
187 query_str = "(abstract_class_declaration name: (type_identifier) @class_name)"
188 try:
189 query = create_query(TS_LANGUAGE, query_str)
190 captures = run_captures(query, root)
191 for node, capture_name in captures:
192 text = node.text.decode() if node.text else ""
193 if capture_name == "class_name":
194 abstract_classes.add(text)
195 except Exception:
196 pass
198 query_str = (
199 "(abstract_method_signature name: (property_identifier) @method_name)"
200 )
201 try:
202 query = create_query(TS_LANGUAGE, query_str)
203 captures = run_captures(query, root)
204 for node, capture_name in captures:
205 text = node.text.decode() if node.text else ""
206 if capture_name == "method_name":
207 abstract_methods.add(text)
208 except Exception:
209 pass
211 return abstract_classes, abstract_methods
213 def extract_variable_definitions(
214 self, root: Node, source: str
215 ) -> dict[tuple, list[VariableDefinition]]:
216 query_str = (
217 "(lexical_declaration (variable_declarator name: (identifier) @var_name))"
218 )
219 query = create_query(TS_LANGUAGE, query_str)
220 captures = run_captures(query, root)
222 result: dict[tuple, list[VariableDefinition]] = {}
223 source_lines = source.split("\n")
225 for node, capture_name in captures:
226 if capture_name == "var_name":
227 text = node.text.decode() if node.text else ""
228 scope = self._find_enclosing_scope(node, source_lines)
229 if scope is None:
230 continue
231 if scope not in result:
232 result[scope] = []
233 result[scope].append(
234 VariableDefinition(
235 name=text,
236 line=node.start_point[0] + 1,
237 scope_type="local",
238 definition_type="variable",
239 )
240 )
242 return result
244 def extract_references(self, root: Node, source: str) -> list[SymbolReference]:
245 query_str = "(identifier) @usage_ref"
246 query = create_query(TS_LANGUAGE, query_str)
247 captures = run_captures(query, root)
249 refs = []
250 for node, capture_name in captures:
251 if capture_name == "usage_ref" and node.text:
252 refs.append(
253 SymbolReference(node.text.decode(), node.start_point[0] + 1)
254 )
255 return refs
257 def extract_scope_references(
258 self, root: Node, source: str
259 ) -> dict[tuple, list[SymbolReference]]:
260 query_str = "(identifier) @usage_ref"
261 query = create_query(TS_LANGUAGE, query_str)
262 captures = run_captures(query, root)
264 result: dict[tuple, list[SymbolReference]] = {}
265 source_lines = source.split("\n")
267 for node, capture_name in captures:
268 if capture_name == "usage_ref" and node.text:
269 scope = self._find_enclosing_scope(node, source_lines)
270 if scope is None:
271 continue
272 if scope not in result:
273 result[scope] = []
274 result[scope].append(
275 SymbolReference(
276 name=node.text.decode(),
277 line=node.start_point[0] + 1,
278 )
279 )
281 return result
283 def _find_enclosing_scope(
284 self, node: Node, source_lines: list[str]
285 ) -> tuple | None:
286 parent = node.parent
287 while parent:
288 if parent.type in (
289 "function_declaration",
290 "generator_function_declaration",
291 "method_definition",
292 "arrow_function",
293 ):
294 return (None, parent.start_point[0] + 1)
295 parent = parent.parent
296 return None
298 def is_exported(self, node: Node) -> bool:
299 parent = node.parent
300 while parent:
301 if parent.type == "export_statement":
302 return True
303 parent = parent.parent
304 return False