(no title)
swynx | 14 days ago
The better approach is reducing false positives at the analysis level so the list you hand to a developer is actionable without requiring a test run for each item.
We built a dead code scanner (Swynx - https://swynx.io) and validated it against 1,100+ open source repos. A few things that moved the needle on Python specifically:
File-level reachability first, then export-level. Instead of parsing the AST for individual call sites (where dynamic dispatch kills you), we do a BFS from every entry point (__main__.py, framework convention files, package.json main/bin, etc.) and follow the import graph. If no path from any entry point reaches a module, it's dead with very high confidence. This sidesteps a huge class of FPs because you're not trying to resolve getattr(module, f"handle_{action}") — you're just asking "does anything import this module at all?"
__getattr__ is a sleeper problem in Python. When a module defines __getattr__, any sibling module becomes dynamically reachable via attribute access. We had to add a rule: if __init__.py defines __getattr__, treat all sibling.py files and sub-packages as live. Same idea with importlib.import_module() - if we see it in the BFS with a resolvable dotted path, we follow it.
Scale reveals your bugs. When you scan 1,000 repos, any repo showing >10% dead is almost certainly a scanner bug, not actually 10% dead. That feedback loop drove our false positive rate down to ~1.7% across hundreds of repos - way below the threshold where people stop trusting the output.
Re: Vulture - I actually just ran Skylos (the OP's tool) against its own codebase. It found some real stuff: constants like FRAMEWORK_FUNCTIONS and ENTRY_POINT_DECORATORS defined in framework_aware.py but never referenced anywhere. Genuinely dead code in a dead code detection tool. But it also flagged its own pytest plugin hooks (pytest_collection_finish, pytest_fixture_setup) as unused at confidence=60 - exactly the framework convention problem the OP described. The confidence scoring approach is right, but the framework awareness has to be deep enough to catch your own patterns.
To the OP's original question: you shouldn't accept false positives, and you shouldn't ignore dead code detection. The answer is two passes - file-level reachability (high confidence, low noise) followed by export-level analysis for files that are reachable but partially dead.
duriantaco|10 days ago
The 2 pass approach with Swynx is cool! Doing a BFS for file-level reachability first to sidestep the AST dynamic dispatch nightmare makes sense. If the module isnt even in the import graph then the confidence is practically 100%. And great callout on `__getattr__` and `importlib`.. Those dynamic edge cases are really sleeper problems for Python static analysis.
Thanks for the check on the pytest hooks and for sharing the Swynx architecture! It’s great to see how you guys tackled the dynamic nature of Python.