Dead-code analysis
The dead-code analyzer resolves Dart syntax to analyzer elements, records declarations and references, and reports declarations that are not reachable from any root.
Analysis flow
Section titled “Analysis flow”- Collect the target Dart file, or recursively collect
*.dartfiles under a target directory. - Apply custom and built-in exclusions.
- Resolve every included compilation unit with the official Dart analyzer.
- Record declarations with source-and-offset identities for their resolved analyzer elements.
- Record references as edges from the declaration that contains each reference.
- Establish roots such as
main, overrides, top-level references, and configured entry points. - Traverse the graph from all roots.
- Report declarations not in the reachable set.
If any included file cannot be resolved or contains an analyzer error, the analysis stops and lists the failures. Run dart pub get in the target package before analysis.
Recorded declarations
Section titled “Recorded declarations”| Category | Details |
|---|---|
| Types | Classes, abstract classes, mixins, named extensions, extension types, enums |
| Executables | Top-level functions, methods, getters, setters, explicit constructors |
| Data | Top-level variables, fields, enum values |
| Aliases | Function type aliases and generic type aliases |
Explicit unnamed, named, and private constructors can be emitted as standalone findings. An unnamed constructor is displayed as Class.new. Anonymous extensions do not have their own declaration entry.
Reachability roots
Section titled “Reachability roots”References outside a recorded declaration are roots. Hyena also roots:
- the top-level
mainfunction; - members that implement an inherited interface member, whether or not they have an
@overrideannotation; - public declarations in directly importable package libraries, library parts, and explicit exports when
ignore_exportsis enabled; - declarations exposed by any branch of a conditional import or export;
- private declarations when
ignore_privateis enabled.
References inside a root or another reachable declaration make their target declarations reachable. This avoids treating a helper as used merely because another unreachable helper calls it.
For a Dart workspace, Hyena Dart 1.2.1 and later join these resolved edges across package boundaries before traversal. A reachable app declaration can keep the exact shared-package declaration it calls, while a same-named declaration in another package remains distinct. Calls made only from unreachable declarations still do not keep their targets alive. See Cross-package dead-code reachability.
When an analyzer element is unavailable, Hyena falls back conservatively to matching simple and qualified names. Unresolved dynamic member access retains same-named member declarations because the runtime target cannot be proven statically. Element-based references remain the primary path.
Configured framework roots
Section titled “Configured framework roots”Use configured roots when a router, dependency-injection system, plugin registry, serializer, or generated callback reaches declarations without a resolvable Dart reference. A matched declaration becomes a root, and normal graph traversal keeps the declarations it calls or references.
Matching a type container also preserves that container’s public members. Unrelated private members and declarations outside the rooted dependency graph remain candidates, so this is narrower than excluding a file or package.
Both lists default to empty. See Framework and generated-code entry roots for the YAML example and exact simple, qualified, and annotation matching rules.
Export handling
Section titled “Export handling”ignore_exports defaults to false in v2, so unreachable public declarations are findings alongside unreachable private declarations. Public visibility alone does not keep a declaration reachable.
Reusable package authors can enable ignore_exports to preserve a public API that is intentionally consumed outside the analyzed project. With ignore_exports: true, Hyena treats public declarations in directly importable package libraries as API roots. A file under lib/ is directly importable unless its package-relative path begins with src/. Declarations in a library’s part files inherit that library’s visibility.
When export preservation is enabled, Hyena identifies public top-level declarations exposed by explicit export directives among the scanned units. It honors show and hide combinators and follows every conditional export branch conservatively.
An exported container then protects its public members and public constructors as roots. Private members remain candidates unless ignore_private is also enabled.
export 'src/client.dart' show Client;With ignore_exports enabled, Client and its public surface are treated conservatively as externally reachable.
Both the combined and dead-code-only commands accept a one-run override:
dart run hyena_dart analyze lib --ignore-exportsdart run hyena_dart dead-code lib --ignore-exportsPrivate and main declarations
Section titled “Private and main declarations”ignore_private defaults to false, so unused private declarations are reported. Enable it when private hooks are invoked indirectly by tooling or conventions:
dart run hyena_dart dead-code lib --ignore-privateignore_main defaults to true and is only configurable in YAML. It omits main from the declaration count and candidate set. main remains a graph root either way.
Suppress an intentional finding
Section titled “Suppress an intentional finding”Place a suppression immediately before a declaration reached through reflection, generated registration, or another mechanism Hyena cannot resolve:
// hyena:ignore dead-codevoid registeredByName() { initializePlugin();}The declaration remains a graph root. Its reachable dependencies, such as initializePlugin above, therefore do not become cascading dead-code findings.
Interpreting findings
Section titled “Interpreting findings”Treat a finding as a static reachability claim within the scanned and resolved source, not proof that deletion is always safe. Review code reached through:
- reflection or string-based registration;
- generated source that Hyena excludes;
- native bindings, build scripts, or framework conventions;
- consumers outside the scanned package or selected target.
Prefer a narrow exclusion or an export/private policy change only when the indirect entry mechanism is intentional and understood.
