flow_sketch

🚧 Under active development. This package is pre‑1.0 and its API, options, and output may change without notice. Feedback and issues are welcome.

A CLI that analyzes Dart/Flutter source (AST) and generates Mermaid flowcharts and screen‑transition maps — control flow, go_router / auto_route, plain Navigator, tabs and menus.

  • Turn control flow (if / for / while / return) into a flowchart.
  • Recursively expand callee functions up to a chosen depth (call‑graph tracing).
  • Visualize route trees and screen transitions for go_router / auto_route.
  • Visualize tabs and menus (BottomNavigationBar / TabBar / Drawer).
  • Emit a self‑contained HTML viewer (color‑coded, zoomable, PDF‑exportable).

æ—„æœŹèȘžăźăƒ©ăƒ™ăƒ«ă§ă‚‚ć‡șćŠ›ă§ăăŸă™ïŒˆæ—ąćźšăŻæ—„æœŹèȘžă€--lang en ă§è‹±èȘžïŒ‰ă€‚

The diagrams below are real output from the samples in example/ (GitHub renders them as diagrams).

Control flow — flow_sketch -f example/sample.dart -m calculate --lang en

graph TD
    Start([Start: calculate])
    N1["var total = 0"]
    C1{"if (b == 0)"}
    R1(["Return -1"])
    C2{"if (a > b)"}
    N2["total = a - b"]
    N3["total = b - a"]
    L1{"loop (var i = 0; i < b; i++)"}
    N4["total += i"]
    L2{"loop (total > 100)"}
    N5["total = total - 50"]
    R2(["Return total"])
    Start --> N1
    N1 --> C1
    C1 -- Yes --> R1
    C1 -- No --> C2
    C2 -- Yes --> N2
    C2 -- No --> N3
    N2 --> L1
    N3 --> L1
    L1 -- Yes --> N4
    N4 --> L1
    L1 -- No --> L2
    L2 -- Yes --> N5
    N5 --> L2
    L2 -- No --> R2
    classDef startNode fill:#C8E6C9,stroke:#2E7D46,stroke-width:2px,color:#14401f
    classDef procNode fill:#ECEFF4,stroke:#AEB8C7,stroke-width:1px,color:#26313F
    classDef condNode fill:#FFE0B2,stroke:#EF9500,stroke-width:2px,color:#7A3E00
    classDef retNode fill:#B2DFDB,stroke:#00897B,stroke-width:2px,color:#00382F
    classDef loopNode fill:#E1D5F5,stroke:#7E57C2,stroke-width:2px,color:#3E1D73
    class Start startNode
    class N1,N2,N3,N4,N5 procNode
    class C1,C2 condNode
    class R1,R2 retNode
    class L1,L2 loopNode

Comment annotations — flow_sketch -f example/annotations.dart -m login --depth 0 --lang en

// @flow-label replaces a node label, // @flow appends a note, // @flow-ignore drops a statement.

graph TD
    Start([Start: login])
    N1["Load user profile"]
    C1{"if (user == null) (retry up to 3 times)"}
    N2["retry()"]
    R1(["Return"])
    N3["context.go('/home') (only on success)"]
    End([End])
    Start --> N1
    N1 --> C1
    C1 -- Yes --> N2
    N2 --> R1
    C1 -- No --> N3
    N3 --> End
    classDef startNode fill:#C8E6C9,stroke:#2E7D46,stroke-width:2px,color:#14401f
    classDef procNode fill:#ECEFF4,stroke:#AEB8C7,stroke-width:1px,color:#26313F
    classDef condNode fill:#FFE0B2,stroke:#EF9500,stroke-width:2px,color:#7A3E00
    classDef retNode fill:#B2DFDB,stroke:#00897B,stroke-width:2px,color:#00382F
    classDef endNode fill:#E2E5EA,stroke:#8A94A6,stroke-width:2px,color:#333B48
    class Start startNode
    class N1,N3 procNode
    class C1 condNode
    class R1 retNode
    class End endNode

Screen‑transition map (overview) — flow_sketch -f example/screen_map.dart --mode screen

graph TD
    S0(["HomeScreen"])
    S1(["DetailScreen"])
    S0 -- "push" --> S1
    S2(["SettingsScreen"])
    S0 -- "push" --> S2
    S3(["EditScreen"])
    S2 -- "push: only when editable" --> S3
    classDef screenNode fill:#BBDEFB,stroke:#1E6FB8,stroke-width:2px,color:#0A3255
    class S0,S1,S2,S3 screenNode

go_router routes and transitions — flow_sketch -f example/sample_router.dart --mode route

graph TD
    R0["/ (home)"]
    R1["/detail (detail)"]
    R0 --> R1
    R2["/settings (settings)"]
    R0 --> R2
    R3(["openDetail"])
    R3 -- "go" --> R1
    R4(["openSettings"])
    R4 -- "pushNamed" --> R2
    classDef procNode fill:#ECEFF4,stroke:#AEB8C7,stroke-width:1px,color:#26313F
    classDef screenNode fill:#BBDEFB,stroke:#1E6FB8,stroke-width:2px,color:#0A3255
    class R0,R1,R2 procNode
    class R3,R4 screenNode

In the HTML viewer (--format html / --open) these diagrams can be panned (drag) and zoomed (wheel), and exported to PDF.

Install

Activate the package globally from its directory to use the flow_sketch command from any project:

dart pub global activate --source path /path/to/flow_sketch

With ~/.pub-cache/bin on your PATH:

flow_sketch -f lib/main.dart -m main

Without global activation you can run it from inside the package with dart run flow_sketch ....

Quick start

# Open a graphical viewer in the browser (generates HTML and opens it)
flow_sketch -f lib/main.dart -m main --open

# Not sure which function to pass to -m? List them
flow_sketch -f lib/main.dart --list

# Print control flow to stdout
flow_sketch -f lib/main.dart -m main

# Write to a file
flow_sketch -f lib/main.dart -m main -o main.mmd

# Expand callees one level, but keep logging helpers collapsed
flow_sketch -f lib/main.dart -m main --depth 1 --exclude customLogger --open

# Screen‑transition diagram for go_router / auto_route
flow_sketch -f lib/router/app_router.dart --mode route --open

# Whole‑project screen map (scan a directory)
flow_sketch -f lib --mode screen --direction LR --open

Options

Option Alias Description Default
--file -f Dart file, or a directory (scanned for route/screen). Required. —
--function -m Function/method name to chart (required for --mode flow). —
--output -o Output path (stdout if omitted). stdout
--mode flow (control flow) / route (transitions, detailed) / screen (transitions, overview). flow
--depth How many levels of callees to expand recursively (flow only). 1
--format mermaid (text) / html (self‑contained viewer; PDF export). mermaid
--open Generate HTML and open it in the browser (implies html; temp file if no -o). —
--theme HTML theme: auto (follow OS) / light (white) / dark. auto
--compact Cleaner view: drop arrow method labels (push, 
) and shorten labels to 40 chars (keeps Yes/No). —
--label-length Max node‑label length (truncated with 
). 60
--lang Fixed‑label language: ja / en (Start / End / Menu 
). ja
--list -l List functions/methods in the file (to discover -m candidates). —
--exclude Comma‑separated function names to keep collapsed (--depth 1+). —
--direction Layout: TD (top‑down) / LR (left‑right). TD
--help -h Show help. —

Modes

--mode flow (default) — control flow

Construct Node shape
Statement (expression / variable declaration) rectangle ["..."]
if diamond {"if (...)"} (Then=-- Yes -->, Else=-- No -->)
for / while diamond {"loop (...)"}
early return terminal (["Return ..."])

Expanding into widgets (screens)

With --depth 1+, when an expression constructs a project‑defined widget (e.g. runApp(MyApp())), its build() method is also expanded as a subgraph.

  • StatelessWidget: expands the class's own build().
  • StatefulWidget: resolves the State class by the _XxxState / XxxState convention.
  • Widgets returned by build() are followed recursively within the depth budget.
  • Screen nodes use the [[ClassName]] (subroutine) shape.
# main -> MyApp.build -> each screen's build
flow_sketch -f lib/main.dart -m main --depth 3 --exclude customLogger --open

--depth — expanding callees

--depth controls how many levels of named function/method calls are expanded recursively within the project.

  • --depth 0 — no expansion (closures only). No type resolution, so it is fast and works on a single file without a pubspec.yaml.
  • --depth 1+ — resolves callees by type and expands them, even across files.
    • Dependency packages and the Dart/Flutter SDK are not expanded (project only).
    • Each function is expanded once across the whole diagram; later calls collapse to a single node (prevents blow‑up from helpers like customLogger(); recursion is cut safely).
    • Expanded bodies are wrapped in a subgraph so the boundaries are clear.
    • Requires type resolution, so the target project must have run dart pub get (or flutter pub get).

--mode route — screen transitions (detailed)

Detects the following and creates a node per call site (by enclosing function/method). --function is not needed.

  • go_router: GoRoute / ShellRoute / StatefulShellRoute nesting, plus context.go(...) / pushNamed(...).
  • auto_route: AutoRoute / CustomRoute / RedirectRoute and context.router.push(XxxRoute()) (resolves page: XxxRoute.page and push(XxxRoute()) to the same route).
  • Plain Navigator: Navigator.push(context, MaterialPageRoute(builder: (_) => XxxScreen())), Navigator.of(context).push(...), pushNamed, pushNamedAndRemoveUntil, 
 The widget returned by a MaterialPageRoute / CupertinoPageRoute / PageRouteBuilder builder: is used as the destination.
  • Named routes: MaterialApp(routes: {'/settings': (ctx) => SettingsScreen()}), resolving pushNamed('/settings') to the same node.
  • Dialogs / sheets: showDialog / showModalBottomSheet / showCupertinoDialog, 

  • Tabs / menus: BottomNavigationBar / TabBar items, Drawer ListTile(title: Text('...'), onTap: ...) menu items.

--mode screen — screen map (overview)

Same detection as route, but groups the source by enclosing screen class for a high‑level "which screen leads to which" map.

  • Transitions are grouped per screen class (many transitions from one screen collapse into a single node).
  • State classes are normalized to their widget (_HomeScreenState → HomeScreen).
  • Identical screen→screen edges with the same label are merged.
  • Tab lists and other detail are omitted (to stay high‑level).
route mode:  onDetailTapped ──push──▶ DetailScreen  (per method)
screen mode: HomeScreen     ──push──▶ DetailScreen  (per screen)

Scan a whole project

Transition code is scattered across screen files, so for route / screen you can pass a directory to -f to scan every .dart under it into one diagram (.dart_tool, build, and generated *.g.dart / *.freezed.dart are skipped automatically).

flow_sketch -f lib --mode screen --direction LR --open

Comment annotations

Write a comment on the line above a statement or transition call to affect the diagram. Works in both flow and route/screen modes.

Annotation Effect
// @flow <note> flow: append (note) to the node label / route: append to the arrow label
// @flow-label <label> flow: replace the node label (turn a long expression into a readable name)
// @flow-ignore drop the statement/transition from the diagram (flow passes through)
void login(BuildContext context) {
  // @flow-label Load user profile
  final user = fetchUserFromRemoteServer();

  // @flow retry up to 3 times
  if (user == null) {
    retry();
  }

  // @flow-ignore
  debugLog('verbose noise');

  // @flow only on success
  context.go('/home');
}

Output formats

  • --format mermaid (default) — Mermaid text. Saved as .mmd, it renders in VS Code's Markdown preview, on GitHub, etc.
  • --format html — a self‑contained HTML file that renders in any browser.
    • Color‑coded by node type (start=green, end=grey, branch=amber, loop=purple, return=teal, screen=blue, process=light grey) with a legend.
    • Yes / No branch labels shown as green / red chips.
    • Drag to pan, wheel/buttons to zoom (Fit and 100% buttons). Handles large diagrams.
    • Subgraphs (expanded functions/screens) are highlighted with a soft frame.
    • --theme auto/light/dark; Mermaid source is available in a collapsible panel.

Export to PDF

Click the "PDF" button in the viewer (or use the browser's Print → "Save as PDF"). When printing, the UI is hidden and only the diagram is printed on a white background.

flow_sketch -f lib/main.dart -m main --open   # open in browser, then click "PDF"

Color classes are embedded in the Mermaid text itself (classDef), so --format mermaid output shows the same colors on GitHub / VS Code.

--format html loads mermaid.js from a CDN. Offline, the diagram is not rendered and the Mermaid source is shown as text instead.

Examples

See example/README.md for the full list. A few:

# Control flow (if / for / while / return)
flow_sketch -f example/sample.dart -m calculate

# Comment annotations (@flow / @flow-label / @flow-ignore)
flow_sketch -f example/annotations.dart -m login --depth 0

# Expand a function from another file (--depth 1)
flow_sketch -f example/multifile/app.dart -m run --depth 1

# Widget (screen) expansion (runApp -> build)
flow_sketch -f example/widget_app/app.dart -m main --depth 3

# go_router / auto_route
flow_sketch -f example/sample_router.dart --mode route
flow_sketch -f example/sample_autoroute.dart --mode route

# BottomNavigationBar / Drawer
flow_sketch -f example/sample_navigation.dart --mode route

# Screen map of a tabbed app
flow_sketch -f example/tabbed_app.dart --mode screen

How it works / limitations

  • Analysis uses the analyzer package.
  • --depth 0 and --mode route/screen run on syntactic parsing only (no type resolution). In route mode, calls like GoRoute(...) without const/new are recognized as route definitions by a class‑name heuristic (rarely a same‑named function may be misdetected).
  • Node labels are truncated beyond 60 characters by default (--label-length).
  • switch is currently rendered as a single node, and break / continue are drawn as ordinary process nodes rather than loop exit/continue (if / for / while / return are drawn as branch/loop/terminal).

Tests

dart test

Covers control‑flow analysis, call‑graph expansion (subgraph / dedup), route detection, annotations, i18n, and CLI error handling.

Package layout

File Role
bin/flow_sketch.dart CLI entry point
lib/src/analyzer_service.dart File reading / AST / type resolution
lib/src/flow_visitor.dart Control‑flow analysis (--depth 0, no resolution)
lib/src/flow_builder.dart Call‑graph expansion (--depth 1+, subgraphs)
lib/src/route_visitor.dart Route / tab / menu / transition detection
lib/src/function_lister.dart --list support
lib/src/html_renderer.dart Mermaid → self‑contained HTML
lib/src/flow_annotation.dart // @flow comment annotations
lib/src/mermaid_text.dart Label escaping, coloring, i18n helpers

Troubleshooting

I changed the code but the flow_sketch command behaves the same — the global command runs a compiled snapshot (.dart_tool/pub/bin/) that can go stale. Force a rebuild:

rm -rf .dart_tool/pub/bin && dart pub global activate --source path .

"Maximum text size in diagram exceeded" in the browser — an HTML file made by an older version. The current version raises Mermaid's limits; regenerate it.

License

MIT. See LICENSE.

Libraries