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.