Coverage for tests / test_dead_code_typescript.py: 99%
170 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"""Tests for dead code detection in TypeScript."""
3from __future__ import annotations
5from pathlib import Path
7from parsers.typescript import parse
8from dead_code.parsers.typescript import TypeScriptDeadCodeParser
9from dead_code.core import build_cross_reference_graph
12def test_unused_named_imports():
13 code = """
14import { foo, bar } from './module';
16function test() {
17 foo();
18}
19"""
20 parser = TypeScriptDeadCodeParser()
21 root = parse(code)
22 imports = parser.extract_imports(root, code)
24 import_names = [imp.name for imp in imports]
25 assert "foo" in import_names
26 assert "bar" in import_names
29def test_unused_namespace_imports():
30 code = """
31import * as utils from './utils';
32import { unused } from './other';
34function test() {
35 utils.foo();
36}
37"""
38 parser = TypeScriptDeadCodeParser()
39 root = parse(code)
40 imports = parser.extract_imports(root, code)
42 import_names = [imp.name for imp in imports]
43 assert "utils" in import_names
44 assert "unused" in import_names
47def test_unused_default_imports():
48 code = """
49import Foo from './foo';
50import Bar from './bar';
52function test() {
53 new Foo();
54}
55"""
56 parser = TypeScriptDeadCodeParser()
57 root = parse(code)
58 imports = parser.extract_imports(root, code)
60 import_names = [imp.name for imp in imports]
61 assert "Foo" in import_names
62 assert "Bar" in import_names
65def test_type_only_imports_skipped():
66 code = """
67import type { SomeType } from './types';
69function test() {
70 // Type-only imports shouldn't be tracked
71}
72"""
73 parser = TypeScriptDeadCodeParser()
74 root = parse(code)
75 imports = parser.extract_imports(root, code)
77 # Type-only imports should be skipped
78 import_names = [imp.name for imp in imports]
79 assert len(import_names) == 0
82def test_dead_function_declarations():
83 code = """
84function used() {}
86function unused() {}
88used();
89"""
90 parser = TypeScriptDeadCodeParser()
91 root = parse(code)
92 defs = parser.extract_definitions(root, code)
94 assert len(defs) == 2
95 def_names = [d.name for d in defs]
96 assert "used" in def_names
97 assert "unused" in def_names
100def test_dead_arrow_functions():
101 code = """
102const used = () => {};
103const unused = () => {}
105used();
106"""
107 parser = TypeScriptDeadCodeParser()
108 root = parse(code)
109 defs = parser.extract_definitions(root, code)
111 assert len(defs) == 2
112 def_names = [d.name for d in defs]
113 assert "used" in def_names
114 assert "unused" in def_names
117def test_dead_classes():
118 code = """
119class Used {}
120class Unused {}
122new Used();
123"""
124 parser = TypeScriptDeadCodeParser()
125 root = parse(code)
126 defs = parser.extract_definitions(root, code)
128 assert len(defs) == 2
129 def_names = [d.name for d in defs]
130 assert "Used" in def_names
131 assert "Unused" in def_names
134def test_exported_functions_not_flagged():
135 code = """
136 export function publicFunc() {}
138 export class PublicClass {}
140 // These shouldn't be flagged as unused
141 """
142 parser = TypeScriptDeadCodeParser()
143 root = parse(code)
145 for child in root.children:
146 if child.type == "export_statement":
147 for grandchild in child.children:
148 if grandchild.type in ("function_declaration", "class_declaration"):
149 assert parser.is_exported(grandchild)
152def test_extract_method_definitions():
153 code = """
154class MyClass {
155 public method1() {}
156 private method2() {}
157}
158"""
159 parser = TypeScriptDeadCodeParser()
160 root = parse(code)
161 defs = parser.extract_definitions(root, code)
163 def_names = [d.name for d in defs]
164 method_names = [d.name for d in defs if d.definition_type == "function"]
165 # Methods should be tracked by property_identifier
166 # The query handles method_definition nodes
167 assert len(defs) >= 1
170def test_unused_local_variables():
171 code = """
172 function test() {
173 const used = 1;
174 const unused = 2;
175 return used;
176 }
177 """
178 parser = TypeScriptDeadCodeParser()
179 root = parse(code)
180 refs = parser.extract_references(root, code)
182 ref_names = [r.name for r in refs]
183 assert "used" in ref_names
184 assert "unused" in ref_names
187def test_variables_used_in_nested_blocks():
188 code = """
189function test(x: number) {
190 if (x > 0) {
191 const y = x + 1;
192 return y;
193 }
194 return 0;
195}
196"""
197 parser = TypeScriptDeadCodeParser()
198 root = parse(code)
199 refs = parser.extract_references(root, code)
201 ref_names = [r.name for r in refs]
202 assert "x" in ref_names
203 assert "y" in ref_names # y is used in the if block
206def test_shadowing_separate_tracking():
207 code = """
208function test() {
209 let x = 1;
210 if (true) {
211 let x = 2;
212 return x;
213 }
214 return x;
215}
216"""
217 parser = TypeScriptDeadCodeParser()
218 root = parse(code)
219 refs = parser.extract_references(root, code)
221 # Both x declarations should be used
222 ref_names = [r.name for r in refs]
223 assert ref_names.count("x") >= 2
226def test_class_implementing_interface():
227 code = """
228interface MyInterface {
229 method(x: number): void;
230}
232class MyClass implements MyInterface {
233 method(x: number) {
234 return;
235 }
236}
237"""
238 parser = TypeScriptDeadCodeParser()
239 root = parse(code)
240 defs = parser.extract_definitions(root, code)
242 # MyClass should be extracted as a class definition
243 class_names = [d.name for d in defs if d.definition_type == "class"]
244 assert "MyClass" in class_names
247def test_react_component_pattern():
248 code = """
249 import React from 'react';
251 export function Component({ name }: { name: string }) {
252 return <div>{name}</div>;
253 }
254 """
255 parser = TypeScriptDeadCodeParser()
256 root = parse(code)
258 for child in root.children:
259 if child.type == "export_statement":
260 for grandchild in child.children:
261 if grandchild.type == "function_declaration":
262 assert parser.is_exported(grandchild)
263 return
264 assert False, "Exported function not found"
267def test_extract_references():
268 code = """
269function foo(x: number, y: number): number {
270 const result = x + y;
271 return result;
272}
274const test = foo(1, 2);
275"""
276 parser = TypeScriptDeadCodeParser()
277 root = parse(code)
278 refs = parser.extract_references(root, code)
280 ref_names = [r.name for r in refs]
281 assert "x" in ref_names
282 assert "y" in ref_names
283 assert "foo" in ref_names
284 assert "test" in ref_names
287def test_abstract_class_detection():
288 code = """
289abstract class AbstractClass {
290 abstract method(): void;
291}
293class ConcreteClass extends AbstractClass {
294 method(): void {}
295}
296"""
297 parser = TypeScriptDeadCodeParser()
298 root = parse(code)
299 abstract_classes, abstract_methods = parser.extract_abstract_info(root, code)
301 assert "AbstractClass" in abstract_classes
302 assert "method" in abstract_methods
305def test_dead_function_with_export():
306 code = """
307export const used = () => {};
308const unused = () => {};
310used();
311"""
312 parser = TypeScriptDeadCodeParser()
313 root = parse(code)
314 defs = parser.extract_definitions(root, code)
316 def_names = [d.name for d in defs]
317 assert "used" in def_names
318 assert "unused" in def_names
321def test_default_parameters():
322 code = """
323function greet(name: string = "World"): string {
324 return `Hello, ${name}`;
325}
327console.log(greet());
328"""
329 parser = TypeScriptDeadCodeParser()
330 root = parse(code)
331 refs = parser.extract_references(root, code)
333 ref_names = [r.name for r in refs]
334 assert "name" in ref_names
335 assert "greet" in ref_names
336 assert "console" in ref_names
339def test_for_loop_variables():
340 code = """
341function test() {
342 for (let i = 0; i < 10; i++) {
343 console.log(i);
344 }
345}
346"""
347 parser = TypeScriptDeadCodeParser()
348 root = parse(code)
349 refs = parser.extract_references(root, code)
351 ref_names = [r.name for r in refs]
352 assert "i" in ref_names # used in loop condition and body
355def test_cross_file_dead_code_detection():
356 code1 = """
357 export { foo, bar } from './module';
359 function internalFunc() {}
360 export { internalFunc as exportedFunc };
361 """
363 parser = TypeScriptDeadCodeParser()
364 root = parse(code1)
365 imports = parser.extract_imports(root, code1)
366 defs = parser.extract_definitions(root, code1)
368 # local function definition should be extracted
369 def_names = [d.name for d in defs]
370 assert "internalFunc" in def_names