1414@pytest .fixture
1515def runner ():
1616 from click .testing import CliRunner
17+
1718 return CliRunner ()
1819
1920
@@ -24,8 +25,8 @@ def sample_project(tmp_path):
2425 utils = tmp_path / "src" / "utils.ts"
2526 utils .parent .mkdir (parents = True , exist_ok = True )
2627 utils .write_text (
27- ' export function usedHelper() { return 1; }\n '
28- ' export function unusedHelper() { return 2; }\n '
28+ " export function usedHelper() { return 1; }\n "
29+ " export function unusedHelper() { return 2; }\n "
2930 'export const USED_CONST = "used";\n '
3031 'export const UNUSED_CONST = "unused";\n '
3132 )
@@ -35,48 +36,39 @@ def sample_project(tmp_path):
3536 button .parent .mkdir (parents = True , exist_ok = True )
3637 button .write_text (
3738 'import { usedHelper, USED_CONST } from "../utils";\n '
38- ' export function Button() {\n '
39+ " export function Button() {\n "
3940 ' return <button className="btn-primary">{usedHelper()}</button>;\n '
40- ' }\n '
41+ " }\n "
4142 )
4243
4344 # src/components/UnusedWidget.tsx - never imported
4445 widget = tmp_path / "src" / "components" / "UnusedWidget.tsx"
4546 widget .write_text (
46- 'export function UnusedWidget() {\n '
47- ' return <div>Unused</div>;\n '
48- '}\n '
47+ "export function UnusedWidget() {\n return <div>Unused</div>;\n }\n "
4948 )
5049
5150 # src/styles/main.css - with orphaned class
5251 css = tmp_path / "src" / "styles" / "main.css"
5352 css .parent .mkdir (parents = True , exist_ok = True )
5453 css .write_text (
55- '.btn-primary {\n '
56- ' background: blue;\n '
57- '}\n '
58- '.orphaned-class {\n '
59- ' color: red;\n '
60- '}\n '
54+ ".btn-primary {\n background: blue;\n }\n .orphaned-class {\n color: red;\n }\n "
6155 )
6256
6357 # src/app/page.tsx - Next.js page (entry point)
6458 page = tmp_path / "src" / "app" / "page.tsx"
6559 page .parent .mkdir (parents = True , exist_ok = True )
6660 page .write_text (
6761 'import { Button } from "../components/Button";\n '
68- ' export default function Page() {\n '
69- ' return <Button />;\n '
70- ' }\n '
62+ " export default function Page() {\n "
63+ " return <Button />;\n "
64+ " }\n "
7165 )
7266
7367 # src/app/deadpage/page.tsx - dead route
7468 deadpage = tmp_path / "src" / "app" / "deadpage" / "page.tsx"
7569 deadpage .parent .mkdir (parents = True , exist_ok = True )
7670 deadpage .write_text (
77- 'export default function DeadPage() {\n '
78- ' return <div>Dead</div>;\n '
79- '}\n '
71+ "export default function DeadPage() {\n return <div>Dead</div>;\n }\n "
8072 )
8173
8274 return tmp_path
@@ -138,17 +130,22 @@ def test_scan_result_properties(self, sample_project):
138130 result = scanner .scan ()
139131 # Verify all findings are categorized
140132 for f in result .findings :
141- assert f .category in ("unused_export" , "dead_route" , "orphaned_css" , "unreferenced_component" )
133+ assert f .category in (
134+ "unused_export" ,
135+ "dead_route" ,
136+ "orphaned_css" ,
137+ "unreferenced_component" ,
138+ )
142139
143140
144141class TestExportParsing :
145142 def test_named_exports (self , tmp_path ):
146143 f = tmp_path / "test.ts"
147144 f .write_text (
148- ' export function foo() {}\n '
149- ' export const bar = 1;\n '
150- ' export type Baz = string;\n '
151- ' export interface Qux {}\n '
145+ " export function foo() {}\n "
146+ " export const bar = 1;\n "
147+ " export type Baz = string;\n "
148+ " export interface Qux {}\n "
152149 )
153150 scanner = DeadCodeScanner (tmp_path )
154151 result = scanner .scan ()
@@ -161,11 +158,7 @@ def test_named_exports(self, tmp_path):
161158
162159 def test_export_list (self , tmp_path ):
163160 f = tmp_path / "test.ts"
164- f .write_text (
165- 'const alpha = 1;\n '
166- 'const beta = 2;\n '
167- 'export { alpha, beta };\n '
168- )
161+ f .write_text ("const alpha = 1;\n const beta = 2;\n export { alpha, beta };\n " )
169162 scanner = DeadCodeScanner (tmp_path )
170163 result = scanner .scan ()
171164
@@ -175,7 +168,7 @@ def test_export_list(self, tmp_path):
175168
176169 def test_used_exports_not_reported (self , tmp_path ):
177170 mod = tmp_path / "mod.ts"
178- mod .write_text (' export function myFunc() { return 1; }\n ' )
171+ mod .write_text (" export function myFunc() { return 1; }\n " )
179172 app = tmp_path / "app.ts"
180173 app .write_text ('import { myFunc } from "./mod";\n myFunc();\n ' )
181174
@@ -190,14 +183,13 @@ class TestCSSParsing:
190183 def test_orphaned_css_detection (self , tmp_path ):
191184 css = tmp_path / "styles.css"
192185 css .write_text (
193- '.used-class { color: blue; }\n '
194- '.orphaned-class { color: red; }\n '
186+ ".used-class { color: blue; }\n .orphaned-class { color: red; }\n "
195187 )
196188 component = tmp_path / "Component.tsx"
197189 component .write_text (
198- ' export function Component() {\n '
190+ " export function Component() {\n "
199191 ' return <div className="used-class">Hi</div>;\n '
200- ' }\n '
192+ " }\n "
201193 )
202194
203195 scanner = DeadCodeScanner (tmp_path )
@@ -212,7 +204,9 @@ class TestRouteDetection:
212204 def test_nextjs_app_router_route (self , tmp_path ):
213205 page = tmp_path / "app" / "about" / "page.tsx"
214206 page .parent .mkdir (parents = True , exist_ok = True )
215- page .write_text ('export default function About() { return <div>About</div>; }\n ' )
207+ page .write_text (
208+ "export default function About() { return <div>About</div>; }\n "
209+ )
216210
217211 scanner = DeadCodeScanner (tmp_path )
218212 result = scanner .scan ()
@@ -223,7 +217,7 @@ def test_nextjs_app_router_route(self, tmp_path):
223217 def test_root_route_not_dead (self , tmp_path ):
224218 page = tmp_path / "app" / "page.tsx"
225219 page .parent .mkdir (parents = True , exist_ok = True )
226- page .write_text (' export default function Home() { return <div>Home</div>; }\n ' )
220+ page .write_text (" export default function Home() { return <div>Home</div>; }\n " )
227221
228222 scanner = DeadCodeScanner (tmp_path )
229223 result = scanner .scan ()
@@ -240,7 +234,9 @@ def test_linked_route_not_dead(self, tmp_path):
240234 )
241235 about = tmp_path / "app" / "about" / "page.tsx"
242236 about .parent .mkdir (parents = True , exist_ok = True )
243- about .write_text ('export default function About() { return <div>About</div>; }\n ' )
237+ about .write_text (
238+ "export default function About() { return <div>About</div>; }\n "
239+ )
244240
245241 scanner = DeadCodeScanner (tmp_path )
246242 result = scanner .scan ()
@@ -268,14 +264,18 @@ def test_scan_command(self, runner, sample_project):
268264 assert "DeadCode Scan" in result .output
269265
270266 def test_scan_json_output (self , runner , sample_project ):
271- result = runner .invoke (cli , ["-p" , str (sample_project ), "scan" , "--json-output" ])
267+ result = runner .invoke (
268+ cli , ["-p" , str (sample_project ), "scan" , "--json-output" ]
269+ )
272270 assert result .exit_code == 0
273271 data = json .loads (result .output , strict = False )
274272 assert "findings" in data
275273 assert "files_scanned" in data
276274
277275 def test_scan_category_filter (self , runner , sample_project ):
278- result = runner .invoke (cli , ["-p" , str (sample_project ), "scan" , "-c" , "orphaned_css" ])
276+ result = runner .invoke (
277+ cli , ["-p" , str (sample_project ), "scan" , "-c" , "orphaned_css" ]
278+ )
279279 assert result .exit_code == 0
280280 # Text output should mention the category
281281 assert "Orphaned CSS" in result .output
@@ -287,7 +287,11 @@ def test_scan_nonexistent_dir(self, runner):
287287 def test_remove_dry_run (self , runner , sample_project ):
288288 result = runner .invoke (cli , ["-p" , str (sample_project ), "remove" , "--dry-run" ])
289289 assert result .exit_code == 0
290- assert "WOULD REMOVE" in result .output or "Nothing removable" in result .output or result .exit_code == 0
290+ assert (
291+ "WOULD REMOVE" in result .output
292+ or "Nothing removable" in result .output
293+ or result .exit_code == 0
294+ )
291295
292296 def test_stats_command (self , runner , sample_project ):
293297 result = runner .invoke (cli , ["-p" , str (sample_project ), "stats" ])
@@ -299,24 +303,32 @@ def test_scan_ignore_option(self, runner, tmp_path):
299303 """--ignore option should exclude matching files from scan."""
300304 mod = tmp_path / "src" / "mod.ts"
301305 mod .parent .mkdir (parents = True , exist_ok = True )
302- mod .write_text (' export function unusedFunc() { return 1; }\n ' )
306+ mod .write_text (" export function unusedFunc() { return 1; }\n " )
303307
304- result = runner .invoke (cli , ["-p" , str (tmp_path ), "-i" , "src/" , "scan" , "--json-output" ])
308+ result = runner .invoke (
309+ cli , ["-p" , str (tmp_path ), "-i" , "src/" , "scan" , "--json-output" ]
310+ )
305311 assert result .exit_code == 0
306312 import json
313+
307314 data = json .loads (result .output , strict = False )
308315 assert data ["files_scanned" ] == 0 , "src/ ignored, should have 0 files"
309316
310317 def test_remove_category_filter (self , runner , sample_project ):
311318 """remove --dry-run --category should filter by category."""
312- result = runner .invoke (cli , ["-p" , str (sample_project ), "remove" , "--dry-run" , "-c" , "orphaned_css" ])
319+ result = runner .invoke (
320+ cli ,
321+ ["-p" , str (sample_project ), "remove" , "--dry-run" , "-c" , "orphaned_css" ],
322+ )
313323 assert result .exit_code == 0
314324 # Should mention orphaned class
315325 assert "orphaned-class" in result .output or "Nothing removable" in result .output
316326
317327 def test_remove_nonexistent_dir (self , runner ):
318328 """remove should give graceful error for nonexistent project dir."""
319- result = runner .invoke (cli , ["-p" , "/nonexistent/test/path" , "remove" , "--dry-run" ])
329+ result = runner .invoke (
330+ cli , ["-p" , "/nonexistent/test/path" , "remove" , "--dry-run" ]
331+ )
320332 assert result .exit_code != 0
321333 assert "not found" in result .output
322334
@@ -331,9 +343,11 @@ def test_main_module_entry_point(self, runner):
331343 """Test that python -m deadcode works (__main__ entry point fix)."""
332344 import subprocess
333345 import sys
346+
334347 result = subprocess .run (
335348 [sys .executable , "-m" , "deadcode" , "--help" ],
336- capture_output = True , text = True ,
349+ capture_output = True ,
350+ text = True ,
337351 cwd = str (Path (__file__ ).parent .parent / "src" ),
338352 )
339353 assert result .returncode == 0
@@ -385,8 +399,12 @@ def test_multiline_export_list_used_not_reported(self, tmp_path):
385399
386400 export_names = {f .name for f in result .unused_exports }
387401 # usedInApp appears in both an inline export and the export-list; it's imported so should be absent
388- assert "usedInApp" not in export_names , "usedInApp is imported — should not be reported"
389- assert "alsoUnused" in export_names , "alsoUnused is never imported — should be reported"
402+ assert "usedInApp" not in export_names , (
403+ "usedInApp is imported — should not be reported"
404+ )
405+ assert "alsoUnused" in export_names , (
406+ "alsoUnused is never imported — should be reported"
407+ )
390408
391409 def test_multiline_export_list_with_aliases (self , tmp_path ):
392410 """export { Foo as Bar } aliases: the local name Foo should be tracked, not the alias."""
@@ -412,11 +430,7 @@ def test_single_line_export_list_still_works(self, tmp_path):
412430 """Single-line export { Foo, Bar } should continue to work after the fix."""
413431 mod = tmp_path / "src" / "mod.ts"
414432 mod .parent .mkdir (parents = True , exist_ok = True )
415- mod .write_text (
416- "const alpha = 1;\n "
417- "const beta = 2;\n "
418- "export { alpha, beta };\n "
419- )
433+ mod .write_text ("const alpha = 1;\n const beta = 2;\n export { alpha, beta };\n " )
420434
421435 scanner = DeadCodeScanner (tmp_path )
422436 result = scanner .scan ()
@@ -454,11 +468,11 @@ def test_include_patterns_filters_files(self, tmp_path):
454468 # Create files in two dirs
455469 mod = tmp_path / "src" / "mod.ts"
456470 mod .parent .mkdir (parents = True , exist_ok = True )
457- mod .write_text (' export function foo() { return 1; }\\ n' )
471+ mod .write_text (" export function foo() { return 1; }\\ n" )
458472
459473 lib = tmp_path / "lib" / "helper.ts"
460474 lib .parent .mkdir (parents = True , exist_ok = True )
461- lib .write_text (' export function bar() { return 2; }\\ n' )
475+ lib .write_text (" export function bar() { return 2; }\\ n" )
462476
463477 scanner = DeadCodeScanner (tmp_path , include_patterns = ["src/" ])
464478 result = scanner .scan ()
@@ -470,11 +484,11 @@ def test_include_patterns_allows_multiple(self, tmp_path):
470484 """include_patterns can specify multiple directories."""
471485 mod = tmp_path / "src" / "mod.ts"
472486 mod .parent .mkdir (parents = True , exist_ok = True )
473- mod .write_text (' export function foo() { return 1; }\\ n' )
487+ mod .write_text (" export function foo() { return 1; }\\ n" )
474488
475489 lib = tmp_path / "lib" / "helper.ts"
476490 lib .parent .mkdir (parents = True , exist_ok = True )
477- lib .write_text (' export function bar() { return 2; }\\ n' )
491+ lib .write_text (" export function bar() { return 2; }\\ n" )
478492
479493 scanner = DeadCodeScanner (tmp_path , include_patterns = ["src/" , "lib/" ])
480494 result = scanner .scan ()
@@ -484,45 +498,58 @@ def test_include_patterns_none_scans_all(self, tmp_path):
484498 """When include_patterns is None, all scannable files are included."""
485499 mod = tmp_path / "src" / "mod.ts"
486500 mod .parent .mkdir (parents = True , exist_ok = True )
487- mod .write_text (' export function foo() { return 1; }\\ n' )
501+ mod .write_text (" export function foo() { return 1; }\\ n" )
488502
489503 lib = tmp_path / "lib" / "helper.ts"
490504 lib .parent .mkdir (parents = True , exist_ok = True )
491- lib .write_text (' export function bar() { return 2; }\\ n' )
505+ lib .write_text (" export function bar() { return 2; }\\ n" )
492506
493507 scanner = DeadCodeScanner (tmp_path )
494508 result = scanner .scan ()
495509 assert result .files_scanned == 2
496510
511+
497512class TestScanFormat :
498513 """Tests for the new --format scan option."""
499514
500515 def test_format_compact (self , runner , sample_project ):
501- result = runner .invoke (cli , ["-p" , str (sample_project ), "scan" , "--format=compact" ])
516+ result = runner .invoke (
517+ cli , ["-p" , str (sample_project ), "scan" , "--format=compact" ]
518+ )
502519 assert result .exit_code == 0
503520
504521 def test_format_github (self , runner , sample_project ):
505- result = runner .invoke (cli , ["-p" , str (sample_project ), "scan" , "--format=github" ])
522+ result = runner .invoke (
523+ cli , ["-p" , str (sample_project ), "scan" , "--format=github" ]
524+ )
506525 assert result .exit_code == 0
507526
508527 def test_format_compact_with_findings (self , runner , sample_project ):
509- result = runner .invoke (cli , ["-p" , str (sample_project ), "scan" , "--format=compact" ])
528+ result = runner .invoke (
529+ cli , ["-p" , str (sample_project ), "scan" , "--format=compact" ]
530+ )
510531 assert result .exit_code == 0
511532 assert "unusedHelper" in result .output or "No dead code" in result .output
512533
513534 def test_format_github_with_findings (self , runner , sample_project ):
514- result = runner .invoke (cli , ["-p" , str (sample_project ), "scan" , "--format=github" ])
535+ result = runner .invoke (
536+ cli , ["-p" , str (sample_project ), "scan" , "--format=github" ]
537+ )
515538 assert result .exit_code == 0
516539 assert "::" in result .output or "No dead code" in result .output
517540
518541 def test_format_json_legacy_alias (self , runner , sample_project ):
519- result = runner .invoke (cli , ["-p" , str (sample_project ), "scan" , "--json-output" ])
542+ result = runner .invoke (
543+ cli , ["-p" , str (sample_project ), "scan" , "--json-output" ]
544+ )
520545 assert result .exit_code == 0
521546 data = json .loads (result .output , strict = False )
522547 assert "findings" in data
523548
524549 def test_format_json_explicit (self , runner , sample_project ):
525- result = runner .invoke (cli , ["-p" , str (sample_project ), "scan" , "--format=json" ])
550+ result = runner .invoke (
551+ cli , ["-p" , str (sample_project ), "scan" , "--format=json" ]
552+ )
526553 assert result .exit_code == 0
527554 data = json .loads (result .output , strict = False )
528555 assert "findings" in data
0 commit comments