flow_sketch 0.2.0
flow_sketch: ^0.2.0 copied to clipboard
Generate Mermaid flowcharts and screen-transition maps from Dart/Flutter source (control flow, go_router, auto_route, Navigator) via a simple CLI.
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で英語)。
Gallery #
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/XxxStateconvention. - 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 apubspec.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(orflutter 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/StatefulShellRoutenesting, pluscontext.go(...)/pushNamed(...). - auto_route:
AutoRoute/CustomRoute/RedirectRouteandcontext.router.push(XxxRoute())(resolvespage: XxxRoute.pageandpush(XxxRoute())to the same route). - Plain Navigator:
Navigator.push(context, MaterialPageRoute(builder: (_) => XxxScreen())),Navigator.of(context).push(...),pushNamed,pushNamedAndRemoveUntil, … The widget returned by aMaterialPageRoute/CupertinoPageRoute/PageRouteBuilderbuilder:is used as the destination. - Named routes:
MaterialApp(routes: {'/settings': (ctx) => SettingsScreen()}), resolvingpushNamed('/settings')to the same node. - Dialogs / sheets:
showDialog/showModalBottomSheet/showCupertinoDialog, … - Tabs / menus:
BottomNavigationBar/TabBaritems,DrawerListTile(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/Nobranch 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 mermaidoutput shows the same colors on GitHub / VS Code.
--format htmlloadsmermaid.jsfrom 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
analyzerpackage. --depth 0and--mode route/screenrun on syntactic parsing only (no type resolution). Inroutemode, calls likeGoRoute(...)withoutconst/neware 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). switchis currently rendered as a single node, andbreak/continueare drawn as ordinary process nodes rather than loop exit/continue (if/for/while/returnare 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.