widget_analyser
widget_analyser is an offline Dart command line tool for reviewing Flutter widget code. It scans Dart files, discovers widget classes, measures build-method complexity, scores each widget, detects structural similarity, and writes human-readable or machine-readable reports.
The package is designed for local development, pull request checks, and CI quality gates where you want a quick answer to questions like:
- Which widgets are becoming too large or too complex?
- Which widgets have deep composition trees?
- Which widgets have too many constructor parameters?
- Which widgets instantiate many other widgets?
- Which widgets look structurally similar and may be candidates for extraction?
- Which widgets are referenced by other widgets in the same scanned project?
Contents
- Features
- Installation
- Quick start
- CLI reference
- Reports
- Configuration
- Metrics and scoring
- Similarity detection
- Using in CI
- Using as a library
- Limitations
- Changelog
Features
- Recursively scans
.dartfiles under a selected folder. - Reads ignore patterns from configuration and from repeated
--excludeCLI flags. - Uses the Dart analyzer AST instead of regular expressions.
- Discovers classes that extend
StatelessWidget,StatefulWidget, orState. - Calculates widget metrics from each discovered widget's
buildmethod. - Scores widgets with configurable metric thresholds and weights.
- Classifies widgets into
high,medium, andlowquality grades. - Builds a widget usage graph for widgets found inside the scanned project.
- Detects structurally similar build methods using token-based similarity.
- Produces
console,json, andhtmlreports. - Loads configuration from
analysis_options.yaml. - Can be used as a CLI or embedded as a Dart library.
Installation
As a project dev dependency
For most Flutter projects, add widget_analyser as a dev dependency:
dart pub add --dev widget_analyser
Then run it with dart run:
dart run widget_analyser lib
If your package manager resolves executables by package and executable name, this form is also valid:
dart run widget_analyser:widget_analyser lib
As a global tool
You can also install the executable globally:
dart pub global activate widget_analyser
Then run it from any Flutter project:
widget_analyser lib
For global installs, make sure Pub's global bin directory is on your PATH. It is commonly:
~/.pub-cache/bin
See the Dart documentation for activating and running global packages.
Requirements
- Dart SDK
>=3.3.0 <4.0.0 - A Dart or Flutter project containing analyzable
.dartfiles - Flutter widget classes that extend
StatelessWidget,StatefulWidget, orState
The analyser runs locally. It does not upload source code or require a network connection to scan a project.
Quick start
Analyse the lib directory of the current project:
dart run widget_analyser lib
Include structural similarity matches:
dart run widget_analyser lib --show-similarity
Write an interactive HTML report:
dart run widget_analyser lib --reporter html --output-dir build/reports
Write JSON for automation:
dart run widget_analyser lib --reporter json --output-dir build/reports
Print the resolved configuration:
dart run widget_analyser lib --print-config
Analyse the current directory by using the default --root-folder value:
dart run widget_analyser
CLI reference
Usage: dart run widget_analyser [options] <directory>
The positional <directory> is the root folder to scan. If it is omitted, the CLI uses --root-folder, whose default value is ..
| Option | Short | Default | Description |
|---|---|---|---|
--help |
-h |
false |
Prints usage information and exits. |
--reporter <console|json|html> |
-r |
console |
Selects the output format. |
--output-dir <path> |
-o |
not set | Directory used when writing json or html files. |
--show-similarity |
false |
Enables structural similarity detection and includes similar widget pairs in reports. | |
--threshold <value> |
config value | Overrides the similarity threshold for the current run. Accepts either similarity form, such as 0.8, or distance form, such as 0.2. |
|
--report-all |
false |
Includes high-quality widgets in report output. Without this flag, reports show only medium and low grade widgets. | |
--root-folder <path> |
. |
Folder used when no positional directory is provided. | |
--exclude <glob> |
none | Adds an exclude glob. Repeat the flag to add multiple patterns. CLI excludes are appended to configured excludes. | |
--print-config |
-c |
false |
Prints the resolved configuration as JSON and exits without scanning. |
--quiet |
-q |
false |
Suppresses all progress output. Recommended for CI and piped JSON workflows. |
Exit codes
| Code | Meaning |
|---|---|
0 |
Analysis completed and no widget was graded low. |
1 |
Analysis completed and at least one widget was graded low. |
2 |
Invalid arguments, invalid threshold input, missing directory, or analysis failure. |
The non-zero 1 result makes the tool suitable for CI quality gates. If you only want a report artifact and do not want low-quality widgets to fail the job, allow exit code 1 in your script.
Reports
Console report
The default console report is optimized for terminals and pull request logs:
dart run widget_analyser lib
It prints:
- analysed root path
- number of scanned Dart files
- number of discovered widgets
- number of low-quality widgets
- one row per visible widget
- grade, score, cyclomatic complexity, nesting level, parameter count, SLOC, build method lines, and reference count
- up to three important violations per widget
- optional similarity matches when
--show-similarityis enabled
By default, high-quality widgets are hidden so the output stays focused on possible issues. Use --report-all to show everything:
dart run widget_analyser lib --report-all
JSON report
Print JSON to stdout:
dart run widget_analyser lib --reporter json
Tip — piping JSON safely. All scan-progress messages go to stderr, so only the JSON payload reaches stdout. Add
--quietto suppress progress messages entirely when you pipe the output to another tool:dart run widget_analyser lib --reporter json --quiet | jq '.widgets | length'
Write widget_analysis.json to a directory:
dart run widget_analyser lib --reporter json --output-dir build/reports
The JSON payload includes:
analysedAtprojectRoottotalWidgetslowQualitymediumQualityhighQuality- resolved
config widgetsusageGraph
Each widget entry includes its name, file, kind, line range, grade, score, metrics, incoming references, outgoing widget uses, similarity matches, violations, deepest nesting path, and complexity breakdown.
Example shape:
{
"analysedAt": "2026-04-30T06:30:00.000Z",
"projectRoot": "/path/to/app/lib",
"totalWidgets": 42,
"lowQuality": 2,
"mediumQuality": 8,
"highQuality": 32,
"widgets": [
{
"name": "ProfileHeader",
"file": "features/profile/profile_header.dart",
"kind": "stateless",
"grade": "medium",
"score": 0.68,
"metrics": {
"cyclomaticComplexity": 4,
"nestingLevel": 6,
"parameterCount": 7,
"sloc": 58,
"numberOfUsedWidgets": 12,
"buildMethodLines": 74
}
}
]
}
HTML report
Write an HTML report:
dart run widget_analyser lib --reporter html --output-dir build/reports
The command writes:
build/reports/widget_analysis.html
The HTML report contains embedded analysis data and client-side filtering. It shows summary cards, sortable metric columns, grade filters, clone filters, expandable widget details, similarity pairs, and the usage graph. If --output-dir is not provided, the HTML file is written to the scanned project root.
Configuration
widget_analyser reads configuration from analysis_options.yaml in the scanned root directory.
Use a top-level widget_analyser section:
widget_analyser:
metrics:
cyclomatic-complexity: 10
maximum-nesting-level: 5
number-of-parameters: 6
source-lines-of-code: 200
build-method-lines: 250
number-of-used-widgets: 20
weights:
cyclomatic-complexity: 0.25
maximum-nesting-level: 0.20
source-lines-of-code: 0.15
build-method-lines: 0.15
number-of-parameters: 0.15
number-of-used-widgets: 0.10
similarity-threshold: 0.80
report-all: false
exclude:
- '**/*.g.dart'
- '**/*.freezed.dart'
- 'test/**'
The loader also checks a top-level dart_code_metrics section when widget_analyser is not present. Only keys understood by widget_analyser are applied.
Default configuration
If no config file or no supported section exists, these defaults are used:
| Key | Default |
|---|---|
cyclomatic-complexity |
10 |
maximum-nesting-level |
5 |
number-of-parameters |
6 |
source-lines-of-code |
200 |
build-method-lines |
250 |
number-of-used-widgets |
20 |
similarity-threshold |
0.80 |
report-all |
false |
exclude |
**/*.g.dart, **/*.freezed.dart, test/** |
Config keys
| Key | Type | Description |
|---|---|---|
metrics |
map of metric ID to integer | Thresholds used for violations and score calculation. |
weights |
map of metric ID to number | Relative importance of each metric in the quality score. Values are normalized when their total is greater than zero. |
similarity-threshold |
number or numeric string | Minimum similarity required to report a pair. Values below 0.5 are treated as distance and converted to similarity. |
report-all |
boolean | Whether reports should include high-grade widgets by default. |
exclude |
list of strings | Glob patterns, relative to the scanned root, that should be skipped. |
When exclude is set in YAML, it replaces the default exclude list. Add the generated-file defaults yourself if you still want them skipped.
CLI options can override parts of the resolved config:
--thresholdoverridessimilarity-thresholdfor that run.--report-allforcesreport-alltotrue.--excludeappends extra patterns to the configured exclude list.
Unknown config keys are ignored with a warning.
Metric IDs and aliases
Canonical IDs:
| Metric ID | Short alias | Other accepted aliases |
|---|---|---|
cyclomatic-complexity |
cc |
complexity |
maximum-nesting-level |
nesting |
widgets-nesting-level, nest |
number-of-parameters |
params |
|
source-lines-of-code |
sloc |
loc |
build-method-lines |
bml |
build-lines |
number-of-used-widgets |
widgets |
Metrics and scoring
Each discovered widget receives raw metric values, violations, a weighted score, and a grade.
Discovered widgets
The scanner discovers classes whose resolved supertypes include:
StatelessWidgetStatefulWidgetState
For each widget class, it records:
- widget name
- relative file path
- start and end line
- widget kind
- constructor parameter count
- build method AST
- build method source
- widgets instantiated inside the build method
Raw metrics
| Metric | What it measures |
|---|---|
| Cyclomatic complexity | Starts at 1 and adds points for branching constructs in build, including if, loops, switch cases, ternaries, logical operators, null-coalescing operators, catch, and assert. |
| Maximum nesting level | Deepest nested widget instantiation chain inside build. The deepest path is retained for reports. |
| Number of parameters | Parameter count for the primary constructor. The unnamed constructor is preferred when present. |
| Source lines of code | Non-empty, non-comment lines in the extracted build body. |
| Build method lines | Total number of lines in the extracted build body. |
| Number of used widgets | Unique widget classes instantiated inside the build method. |
Violations
For each metric:
infois emitted when the value is at least 60% of the threshold.warningis emitted when the value is above the threshold.erroris emitted when the value is more than 150% of the threshold.
Reports sort violations so more severe items appear first.
Quality score
The score is a weighted value between 0.0 and 1.0.
For each metric, the analyser compares the raw value with the configured threshold. Values below the threshold score better. Values far above the threshold are clamped toward zero for that metric. The metric scores are multiplied by their configured weights and summed.
Grades are assigned from the final score:
| Grade | Score range |
|---|---|
high |
>= 0.75 |
medium |
>= 0.50 and < 0.75 |
low |
< 0.50 |
The CLI exits with code 1 when any widget receives a low grade.
Similarity detection
Similarity detection is disabled by default in the CLI because it compares widget pairs and can be more expensive on large projects.
Enable it with:
dart run widget_analyser lib --show-similarity
The detector tokenizes build method ASTs and compares token frequencies with Jaccard similarity. It keeps both raw and normalized token sequences:
- raw tokens preserve constructor names and identifiers
- normalized tokens focus on structural shape
Similarity matches are classified as:
| Clone type | Meaning |
|---|---|
Type-1 |
Structural duplicate; literals may vary. |
Type-2 |
Near duplicate; names may vary. |
Type-3 |
Close clone with some additions or removals. |
None |
Below clone classification thresholds. |
The configured similarity-threshold controls the minimum raw similarity needed before a pair is reported.
Threshold values are normalized:
0.80means at least 80% similarity.0.20is treated as a distance-style threshold and converted to 80% similarity.- values are clamped to the
0.0to1.0range.
Using in CI
Run the analyser after dependencies are installed:
dart pub get
dart run widget_analyser lib --show-similarity
Because exit code 1 means at least one low-quality widget was found, this command can fail a CI job intentionally:
name: widget-analyser
on:
pull_request:
jobs:
analyse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
- run: dart pub get
- run: dart run widget_analyser lib --show-similarity --quiet
To produce a JSON report for downstream tooling without breaking on low-quality widgets:
set +e
dart run widget_analyser lib --reporter json --quiet > widget_analysis.json
status=$?
set -e
if [ "$status" -gt 1 ]; then
exit "$status"
fi
To always upload an HTML report even when low-quality widgets exist:
set +e
dart run widget_analyser lib --reporter html --output-dir build/reports --show-similarity --quiet
status=$?
set -e
if [ "$status" -gt 1 ]; then
exit "$status"
fi
Using as a library
The package exports the analyser, config types, metric IDs, reporters, and report models:
import 'package:widget_analyser/widget_analyser.dart';
Future<void> main() async {
final projectRoot = '/path/to/flutter_app';
final config = ConfigLoader().load(projectRoot);
final summary = await WidgetAnalyser(
config: config,
includeSimilarity: true,
).analyse(projectRoot);
final json = JsonReporter().render(
summary,
reportAll: config.reportAll,
showSimilarity: true,
);
print(json);
}
Public exports include:
WidgetAnalyserAnalysisConfigConfigLoader- metric ID helpers
ConsoleReporterJsonReporterHtmlReporterWidgetModelWidgetReportAnalysisSummaryMetricViolationSimilarityMatch- related enums such as
WidgetKind,QualityGrade,Severity, andCloneType
Practical examples
Find large widgets but ignore generated files
dart run widget_analyser lib \
--exclude '**/*.g.dart' \
--exclude '**/*.freezed.dart'
Generate a focused pull request report
dart run widget_analyser lib --reporter console
This hides high-grade widgets by default and keeps attention on medium and low-grade widgets.
Generate a full architecture report
dart run widget_analyser lib \
--reporter html \
--output-dir build/reports \
--show-similarity \
--report-all
Tune thresholds for a strict design system package
widget_analyser:
metrics:
cyclomatic-complexity: 6
maximum-nesting-level: 4
number-of-parameters: 5
source-lines-of-code: 80
build-method-lines: 100
number-of-used-widgets: 12
weights:
cyclomatic-complexity: 0.30
maximum-nesting-level: 0.25
number-of-parameters: 0.15
source-lines-of-code: 0.15
build-method-lines: 0.10
number-of-used-widgets: 0.05
Limitations
- Similarity detection compares widget pairs, so very large projects can take longer when
--show-similarityis enabled. - The usage graph only connects widgets discovered inside the scanned project. Flutter framework widgets and package dependencies may appear in per-widget
usesdata but are not graph nodes unless they are also discovered as project widgets. - Metrics are static heuristics. A low grade is a prompt for review, not proof that a widget is wrong.
- The analyser focuses on widget
buildmethods. Business logic outsidebuildis not the primary scoring target.
Contributing
Before sending changes, run:
dart pub get
dart format --set-exit-if-changed .
dart analyze
dart test
This repository uses package:lints/recommended.yaml with strict analyzer language settings.
An example/ directory at the repository root contains a ready-to-use
analysis_options.yaml and a brief usage guide.
Changelog
See CHANGELOG.md for the full release history.
License
See the LICENSE file for details.
Libraries
- widget_analyser
- Offline analysis of Flutter widget classes: metrics, scoring, and reports.