hyena_dart 2.0.0 copy "hyena_dart: ^2.0.0" to clipboard
hyena_dart: ^2.0.0 copied to clipboard

A Flutter/Dart codebase analyzer for dead code detection and complexity metrics.

Hyena Dart #

Hyena Logo

A Dart and Flutter codebase analyzer for finding unused declarations and measuring code complexity. Hyena uses the official Dart analyzer package for AST-based analysis.

Features #

  • Dead Code Detection - Find unused classes, constructors, functions, methods, enums, variables, fields, and typedefs
  • Complexity Metrics - Cyclomatic complexity, lines of code, nesting levels, parameter count, maintainability index
  • Multiple Output Formats - Console (colored), JSON, Markdown, HTML, and SARIF 2.1
  • Configurable - Exclude patterns, thresholds, and analysis options via YAML config
  • Workspace Aware - Analyze Dart workspaces package by package without double-counting files
  • Framework Aware - Keep configured routes, registrations, callbacks, and annotated declarations reachable
  • CI/CD Ready - Finding-based exit codes, baselines, source suppressions, and SARIF output

Installation #

Add Hyena to each project as a dev dependency. This pins the analyzer version for local development and CI:

dart pub add dev:hyena_dart
dart run hyena_dart --help
dart run hyena_dart --version
dart run hyena_dart analyze .

For a Flutter project, use flutter pub add dev:hyena_dart and then the same dart run commands. Hyena supports project-local execution only, keeping its version in the project's dependency resolution and lockfile.

Quick Start #

# Analyze current directory
dart run hyena_dart analyze .

# Analyze specific path
dart run hyena_dart analyze lib

# Dead code analysis only
dart run hyena_dart dead-code lib

# Complexity analysis only
dart run hyena_dart complexity lib

Dead-code analysis reports unused public and private declarations by default. Reusable package authors can preserve exported public APIs with --ignore-exports or hyena.dead_code.ignore_exports: true.

Migrating from v1.x #

  • Add Hyena as a project dev dependency with dart pub add dev:hyena_dart (or flutter pub add dev:hyena_dart); v2 no longer maps global executables.
  • Run the CLI and MCP server with dart run hyena_dart and dart run hyena_dart:hyena_mcp.
  • Expect unused public declarations in dead-code results. Set ignore_exports: true for reusable packages whose exported API must remain reachable without an in-repository caller.
  • Update consumers of console, JSON, Markdown, and HTML reports to expect project- or workspace-relative target and file paths. Existing SARIF output, MCP results, and baseline fingerprints were already relative and remain compatible.

Dart Workspaces and Monorepos #

Point Hyena at a directory whose pubspec.yaml declares a Dart workspace to analyze the root package and every workspace member:

dart pub get
dart run hyena_dart analyze .

Hyena supports explicit, nested, and glob workspace entries, subject to the workspace syntax supported by the installed Dart SDK. As required by Dart, each listed member must declare resolution: workspace. Every package is reported independently and keeps its own configuration. Dead-code reachability is joined across package boundaries, so a declaration used by another workspace package stays live while same-named declarations and references from dead callers do not. Descendant package directories are excluded from their parent package, so source files are never counted twice or analyzed under the wrong package boundary.

Configuration is discovered separately for each package. A package-local hyena.yaml or analysis_options.yaml takes precedence; otherwise discovery continues up to the workspace root. An explicit --config file applies to all packages. Console, JSON, Markdown, and HTML reports contain package sections, and every report path, SARIF location, and baseline fingerprint is relative to the common workspace root.

AI Assistant Integration #

Hyena includes a read-only MCP server. The source repository also provides an agent skill. The MCP server exposes one tool, hyena_analyze, for dead-code and complexity analysis with structured results.

After adding Hyena as a project dev dependency, configure an MCP client to launch the server over standard input/output. -C selects the project before resolving its local Hyena dependency, while the server root remains .:

{
  "mcpServers": {
    "hyena": {
      "command": "dart",
      "args": ["-C", "/absolute/path/to/dart-project", "run", "hyena_dart:hyena_mcp", "--root", "."]
    }
  }
}

The workspace root is a startup argument chosen by the user, not a tool argument chosen by the model. Tool calls accept only a relative path and a checks value of both, dead-code, or complexity.

Each request is limited to 10,000 Dart files and 50 MiB of Dart source, returns at most 200 findings with bounded text fields, and is terminated after two minutes. The server processes only one analysis at a time; narrow the target path if a result is truncated.

Per-request cancellation is not supported because dart_mcp 0.5.x does not expose JSON-RPC request IDs to tool handlers. Disconnecting the client stops the worker immediately; otherwise the two-minute limit still applies.

Security properties of the MCP interface:

  • stdio transport only; it does not open a local or remote network server
  • no shell execution, target-code execution, file writes, or baseline changes
  • canonical workspace checks that reject traversal and existing symlink escapes
  • bounded source size, file count, result count, runtime, and concurrency
  • configuration discovery stops at the configured workspace root

The server still runs with the operating-system permissions of the MCP client. Dart analysis may read the installed SDK, package-resolution metadata, and resolved dependencies outside the workspace root, but it does not execute them. Because application-level checks are not an OS sandbox, another process that can mutate the workspace could race validation by replacing a checked path. For stronger isolation, launch the server in a sandbox or container with the workspace and dependency cache mounted read-only and networking disabled.

Contributors using an Agent Skills-compatible client can load the analyzing-dart-code repository skill. It prefers the constrained MCP tool and documents a JSON CLI fallback.

CLI Reference #

Commands #

analyze - Full Analysis

Run both dead code and complexity analysis.

dart run hyena_dart analyze <path> [options]
Option Short Description Default
--format -f Output format: console, json, markdown, html, sarif console
--output -o Output file path (prints to stdout if not specified) -
--config -c Path to configuration file -
--no-color - Disable colored output false
--baseline - Suppress findings recorded in a baseline file -
--write-baseline - Write current findings to a baseline file -
--fail-on - Exit 1 for dead-code, complexity, or both -
--dead-code - Include dead code analysis true
--complexity - Include complexity analysis true
--ignore-exports - Preserve exported public APIs from dead-code findings false
--ignore-private - Ignore private entities false

Examples:

# Full analysis with HTML report
dart run hyena_dart analyze lib --format=html --output=report.html

# JSON output for CI/CD
dart run hyena_dart analyze lib --format=json --output=analysis.json

# Skip complexity analysis
dart run hyena_dart analyze lib --no-complexity

dead-code - Dead Code Analysis

Analyze codebase for unused code entities.

dart run hyena_dart dead-code <path> [options]
Option Short Description Default
--format -f Output format console
--output -o Output file path -
--config -c Path to configuration file -
--baseline - Suppress findings recorded in a baseline file -
--write-baseline - Write current findings to a baseline file -
--fail-on - Exit 1 when dead-code findings remain -
--ignore-exports - Preserve exported public APIs from dead-code findings false
--ignore-private - Ignore private entities false

Examples:

# Preserve a reusable package's exported public API
dart run hyena_dart dead-code lib --ignore-exports

# Markdown report
dart run hyena_dart dead-code lib --format=markdown --output=dead-code.md

complexity - Complexity Analysis

Analyze code complexity metrics.

dart run hyena_dart complexity <path> [options]
Option Short Description Default
--format -f Output format console
--output -o Output file path -
--config -c Path to configuration file -
--baseline - Suppress findings recorded in a baseline file -
--write-baseline - Write current findings to a baseline file -
--fail-on - Exit 1 when complexity findings remain -
--threshold -t Cyclomatic complexity threshold for warnings 20

Examples:

# Set custom threshold
dart run hyena_dart complexity lib --threshold=15

# JSON output
dart run hyena_dart complexity lib --format=json

Configuration #

Create a hyena.yaml file in your project root:

hyena:
  # Glob patterns to exclude from analysis
  exclude:
    - "**/*.g.dart"
    - "**/*.freezed.dart"
    - "**/*.mocks.dart"
    - "**/generated/**"

  # Complexity thresholds
  complexity:
    cyclomatic_threshold: 20
    max_nesting: 5
    max_parameters: 6

  # Dead code options
  dead_code:
    ignore_main: true
    ignore_exports: false
    ignore_private: false

    # Declarations retained as framework or generated-code roots
    entry_points:
      - AppRoutes
      - ServiceRegistry.register
      - generatedCallbacks

    # Annotations whose declarations are retained as roots
    entry_point_annotations:
      - RoutePage
      - injectable
      - riverpod.Riverpod

Entry points use exact declaration names. A simple name such as register matches declarations with that simple name; a qualified name such as ServiceRegistry.register targets one member. Annotation names can omit a leading @. Simple annotation names match the final component regardless of an import prefix, while qualified names match the exact lexical prefix and name.

Configured declarations become dead-code reachability roots, so declarations they call or reference are retained too. Configured classes and other type containers also retain their public members. The lists are empty by default; add only entry points actually used by a framework, generator, serializer, router, dependency-injection system, or plugin runtime.

You can also add the configuration to your existing analysis_options.yaml:

# Your existing linter rules...
linter:
  rules:
    - prefer_const_constructors

# Hyena configuration
hyena:
  exclude:
    - "**/*.g.dart"
  complexity:
    cyclomatic_threshold: 15

Output Formats #

Console (Default) #

Colored terminal output with summary and details.

JSON #

Machine-readable format for CI/CD integration:

{
  "targetPath": "lib",
  "duration": "205ms",
  "deadCode": {
    "summary": {
      "totalDeclarations": 170,
      "unusedCount": 5,
      "deadCodePercentage": "2.94"
    },
    "unusedEntities": [...]
  },
  "complexity": {
    "summary": {
      "totalFiles": 17,
      "totalFunctions": 133,
      "highComplexityFunctions": 2
    },
    "files": [...]
  }
}

Markdown #

GitHub-friendly format with tables and collapsible sections.

HTML #

Visual report with styled cards, tables, color-coded metrics, and project-relative source paths.

SARIF #

SARIF 2.1 output for code-scanning systems:

dart run hyena_dart analyze . --format=sarif --output=hyena.sarif

CI/CD Usage #

Hyena exits with code 0 by default, even when it reports findings. Opt into a stable exit code 1 for selected categories:

dart run hyena_dart analyze . --fail-on=dead-code,complexity

To adopt Hyena without failing on existing findings, create and commit a baseline, then fail only on new findings:

dart run hyena_dart analyze . --write-baseline=hyena-baseline.json
dart run hyena_dart analyze . \
  --baseline=hyena-baseline.json \
  --fail-on=dead-code,complexity

Baseline fingerprints use the rule, package-relative path, symbol type, and full symbol name. Moving a declaration to another line does not invalidate its baseline entry.

Performance Benchmarks #

The source repository includes deterministic single-package and workspace benchmark corpora. Run these commands from a source checkout. Fixture generation and warm-up runs happen outside measured samples. The harness records median and p95 wall time, files and lines per second, sampled process RSS, runtime metadata, and correctness signatures:

# Fast local regression suite
dart run benchmark/benchmark.dart \
  --suite=quick \
  --output=benchmark-results.json

# Larger 500/1,000-file and 20-package scenarios
dart run benchmark/benchmark.dart --suite=full

# Advisory comparison with the released v1.2.1 baseline
dart run benchmark/benchmark.dart \
  --baseline=benchmark/results/v1.2.1.json

The Performance benchmarks GitHub Actions workflow runs either generated suite on demand and uploads its JSON result. Comparisons are intentionally advisory until repeated hosted-runner measurements establish stable regression thresholds.

The open-source benchmark report records pinned Melos, Flame, and Flutter packages measurements, representative review leads, and known compatibility limits. Findings from these scans are triage candidates rather than automatic refactoring instructions.

For an explicitly reviewed production checkout, use the read-only external mode:

dart run benchmark/benchmark.dart \
  --target=/path/to/repository \
  --label=reviewed-production-repo \
  --checks=both \
  --output=production-benchmark.json

External mode does not run target code, shell hooks, or pub get, and it does not write inside the target. Output paths inside the target, including paths through symlink aliases, are rejected. The checkout should already contain the package resolution metadata needed by Dart analysis. Hyena may read resolved SDK and package dependencies just as it does during normal analysis. Benchmark JSON uses the supplied label and aggregate corpus counts rather than recording the target's absolute path.

Source Suppressions #

Place an ignore comment immediately before a declaration when a finding is intentional:

// hyena:ignore dead-code
void retainedForReflection() {}

// hyena:ignore complexity
void generatedDispatcher() {
  // All complexity threshold findings are suppressed.
}

// Rule-specific complexity suppressions:
// hyena:ignore cyclomatic-complexity
void stateMachine() {}

// hyena:ignore max-nesting
void nestedParser() {}

// hyena:ignore max-parameters
void frameworkCallback(int a, int b, int c, int d, int e, int f, int g) {}

Suppressed dead-code declarations remain reachability roots, so dependencies used by an intentionally retained declaration are not reported as cascading dead code.

Metrics Explained #

Dead Code Detection #

Detects the following unused entities:

  • Classes (including abstract classes)
  • Explicit unnamed, named, and private constructors
  • Mixins and Extensions
  • Enums and enum values
  • Top-level and instance functions/methods
  • Getters and setters
  • Variables and fields
  • Typedefs

Complexity Metrics #

Metric Description
Cyclomatic Complexity Number of linearly independent paths through code. Higher = more complex.
Lines of Code (LOC) Non-blank lines in a function.
Max Nesting Level Deepest level of nested control structures.
Parameter Count Number of function parameters.
Maintainability Index Composite score (0-100). Higher = more maintainable.

Complexity Thresholds #

Cyclomatic Complexity Risk Level
1-10 Low - Simple, easy to test
11-20 Moderate - More complex
21-50 High - Difficult to test
50+ Very High - Untestable, refactor recommended

Programmatic Usage #

You can also use Hyena as a library:

import 'package:hyena_dart/hyena_dart.dart';

void main() async {
  // AnalysisRunner automatically handles single packages and Dart workspaces.
  final workspaceResult = await const AnalysisRunner().analyze('.');
  print('Packages: ${workspaceResult.packageAnalyses.length}');

  final config = AnalyzerConfig(
    cyclomaticThreshold: 15,
    ignoreExports: false,
  );

  // Dead code analysis
  final deadCodeAnalyzer = DeadCodeAnalyzer(config);
  final deadCodeReport = await deadCodeAnalyzer.analyze('./lib');
  print('Unused entities: ${deadCodeReport.unusedCount}');

  // Complexity analysis
  final complexityAnalyzer = ComplexityAnalyzer(config);
  final complexityReport = await complexityAnalyzer.analyze('./lib');
  print('High complexity functions: ${complexityReport.highComplexityFunctions.length}');

  // Generate reports
  final result = AnalysisResult(
    deadCodeReport: deadCodeReport,
    complexityReport: complexityReport,
    targetPath: './lib',
    duration: Duration(milliseconds: 100),
  );

  final reporter = JsonReporter();
  final json = await reporter.generate(result);
  print(json);
}

License #

MIT

0
likes
150
points
5
downloads

Documentation

API reference

Publisher

verified publisheronlychan.xyz

Weekly Downloads

A Flutter/Dart codebase analyzer for dead code detection and complexity metrics.

Repository (GitHub)
View/report issues

Topics

#analyzer #dead-code #complexity #linter #cli

License

MIT (license)

Dependencies

analyzer, args, dart_mcp, glob, path, yaml

More

Packages that depend on hyena_dart