Flutter Code Quality Checker

🛡️ A comprehensive Dart/Flutter code quality analyzer with 48 rules across 10 categories — get a coding standard score, clickable inline issues, and fix suggestions.
✨ Features at a Glance
🔍 10 Powerful Analyzers
| # |
Category |
What it Checks |
Rules |
| 1 |
🖨️ Print Statements |
print(), debugPrint(), stdout.write() left in code |
4 |
| 2 |
🏗️ Missing Const |
Widget constructors and lists that should use const |
2 |
| 3 |
🧱 BLoC Anti-Patterns |
Side effects in BlocBuilder, missing close(), no Equatable |
6 |
| 4 |
🔄 Unnecessary Rebuilds |
Controllers in build(), MediaQuery.of(), missing Keys |
5 |
| 5 |
📝 Hardcoded Values |
Hardcoded strings, colors, URLs, dimensions in widgets |
5 |
| 6 |
🧹 Clean Code |
Unused imports, dead code, empty catch, TODO/FIXME, commented URLs |
8 |
| 7 |
🌐 API Usage |
Commented-out API calls, unhandled API errors, .catchError() |
3 |
| 8 |
🔒 Null Safety |
Bang operator (!), late keyword, broken ?. chains |
5 |
| 9 |
⏳ Async Operations |
Missing mounted check, unawaited futures, async in initState |
5 |
| 10 |
🏛️ Separation of Concerns |
API/DB calls in widgets, business logic in build(), large files |
5 |
Total: 48 rules — all configurable, all with fix suggestions!
📊 Coding Standard Score (0-100)
Get a quality score with color-coded grades:
| Score |
Grade |
Meaning |
| 🟢 90-100 |
Excellent |
Production ready |
| 🟡 70-89 |
Good |
Minor improvements needed |
| 🟠 50-69 |
Needs Improvement |
Significant issues to address |
| 🔴 0-49 |
Poor |
Major refactoring needed |
The score uses weighted severity penalties: Errors (-5), Warnings (-3), Info (-1), Hints (-0.5). Project-wide scans scale logarithmically so large projects aren't unfairly penalized.
🖥️ Beautiful Bottom Panel UI
- Score Gauge — Animated circular progress with color-coded grade
- Issue Cards — Real-time counts of Errors, Warnings, Info, and Hints
- Clickable Issues — Click any issue to jump directly to the problematic line
- Filters & Sorting — Filter by category/severity; sort by file, line, or severity
- Theme Support — Adapts to VS Code dark and light themes
🎯 Flexible Scope
- Check Current File — Analyze just the active Dart file
- Check Entire Project — Scan all Dart files in your workspace
⚡ Smart Exclusions
Automatically skips generated and non-production files:
*.g.dart, *.freezed.dart, *.mocks.dart
- Test files (configurable)
- Build directories and
.dart_tool
🚀 Getting Started
Installation
- Open VS Code
- Go to Extensions (
Ctrl+Shift+X / Cmd+Shift+X)
- Search for "Flutter Code Quality Checker"
- Click Install
Usage
- Open a Flutter/Dart project
- Open Command Palette (
Ctrl+Shift+P / Cmd+Shift+P)
- Type "Flutter Quality: Analyze Code Quality"
- Select "Check Current File" or "Check Entire Project"
- View results in the Flutter Quality panel at the bottom
You can also:
- Click the shield icon in the editor title bar
- Check the status bar for your last score
- Use keyboard shortcut to re-analyze
📋 Rules Reference
🖨️ Print Statements
// ❌ Bad
print('debug value: $value');
debugPrint('something');
// ✅ Good
if (kDebugMode) {
debugPrint('debug value: $value');
}
🏗️ Missing Const
// ❌ Bad — only flagged when arguments are true compile-time literals
Text('Hello World')
SizedBox(height: 16)
EdgeInsets.all(8.0)
// ✅ Good
const Text('Hello World')
const SizedBox(height: 16)
const EdgeInsets.all(8.0)
// ✅ NOT flagged — runtime values are correctly skipped
SizedBox(height: SizeConfig.h * AppSpacing.s36)
EdgeInsets.only(bottom: AppSpacing.s16)
🧱 BLoC Issues
// ❌ Bad — Side effect in BlocBuilder
BlocBuilder<MyBloc, MyState>(
builder: (context, state) {
Navigator.of(context).pop(); // Side effect!
return Container();
},
);
// ✅ Good — Use BlocListener for side effects
BlocListener<MyBloc, MyState>(
listener: (context, state) {
Navigator.of(context).pop();
},
child: BlocBuilder<MyBloc, MyState>(
builder: (context, state) => Container(),
),
);
🔄 Unnecessary Rebuilds
// ❌ Bad — Controller created in build()
Widget build(BuildContext context) {
final controller = TextEditingController(); // Recreated every build!
return TextField(controller: controller);
}
// ✅ Good
late final _controller = TextEditingController();
Widget build(BuildContext context) {
return TextField(controller: _controller);
}
🔒 Null Safety
// ❌ Bad — Force unwrap risks runtime crash
final name = user!.name;
final first = list.first!;
// ✅ Good
final name = user?.name ?? 'Unknown';
final first = list.firstOrNull;
⏳ Async Operations
// ❌ Bad — Using context after async gap without mounted check
Future<void> _onSubmit() async {
await repository.save(data);
Navigator.of(context).pop(); // Widget might be disposed!
}
// ✅ Good
Future<void> _onSubmit() async {
await repository.save(data);
if (!mounted) return;
Navigator.of(context).pop();
}
🌐 API Usage
// ❌ Bad — API call without error handling
final response = await http.get(Uri.parse(url));
// ✅ Good
try {
final response = await http.get(Uri.parse(url));
} catch (e) {
// Handle error gracefully
}
🏛️ Separation of Concerns
// ❌ Bad — API call directly in widget
class MyWidget extends StatelessWidget {
Widget build(BuildContext context) {
final response = await http.get(url); // Direct API call!
return Text(response.body);
}
}
// ✅ Good — Use Repository + BLoC/Provider pattern
class MyWidget extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<MyBloc, MyState>(
builder: (context, state) => Text(state.data),
);
}
}
🧹 Clean Code
// ❌ Bad — Commented-out code and URLs
// final data = await fetchData();
// final result = transform(data);
// https://api.old-server.com/v1/users
// ✅ Good — Use version control, not comments
// Use proper configuration for URLs
⚙️ Configuration
Open Settings (Ctrl+,) and search for "Flutter Quality Checker":
| Setting |
Default |
Description |
enablePrintCheck |
true |
Check for print statements |
enableConstCheck |
true |
Check for missing const keywords |
enableBlocCheck |
true |
Check for BLoC anti-patterns |
enableRebuildCheck |
true |
Check for unnecessary rebuilds |
enableHardcodedCheck |
true |
Check for hardcoded values |
enableCleanCodeCheck |
true |
Check for dead code, unused imports, TODO/FIXME |
enableApiCheck |
true |
Check for API call issues |
enableNullSafetyCheck |
true |
Check for null safety issues |
enableAsyncCheck |
true |
Check for async operation issues |
enableSeparationCheck |
true |
Check for separation of concerns |
excludePatterns |
["**/*.g.dart", ...] |
Glob patterns to exclude |
excludeTestFiles |
true |
Exclude test files from analysis |
📈 Scoring System
| Severity |
Points Deducted |
Examples |
| 🔴 Error |
-5 per issue |
Missing mounted check, controller in build(), unhandled API |
| ⚠️ Warning |
-3 per issue |
print(), hardcoded URLs, force unwrap (!) |
| ℹ️ Info |
-1 per issue |
Missing const, hardcoded strings, TODO comments |
| 💡 Hint |
-0.5 per issue |
debugPrint(), commented-out code, hardcoded dimensions |
For project-wide scans, deductions scale logarithmically with file count — so a few issues in a big project don't tank your score.
📋 Complete Rules, Severity & Scoring Matrix
Each rule belongs to one of the 10 Categories and carries a specific Severity, which translates directly into score deductions:
| Category |
Rule ID |
Severity |
Deduction |
Description |
| 🖨️ Print Statements |
print-statement |
⚠️ Warning |
-3.0 |
Bare print() statements left in production code. |
|
debug-print |
💡 Hint |
-0.5 |
Bare debugPrint() statements left in code. |
| 🏗️ Missing Const |
missing-const-widget |
ℹ️ Info |
-1.0 |
Eligible Widget constructors invoked without const. |
|
missing-const-list |
💡 Hint |
-0.5 |
Eligible child lists invoked without const. |
| 🧱 BLoC Issues |
bloc-side-effect-in-builder |
🔴 Error |
-5.0 |
Navigation or side-effects inside BlocBuilder. |
|
bloc-missing-close |
🔴 Error |
-5.0 |
BLoC/Cubit instantiated but not closed in dispose(). |
|
bloc-state-no-equatable |
⚠️ Warning |
-3.0 |
BLoC state class not extending Equatable. |
|
bloc-builder-no-buildwhen |
💡 Hint |
-0.5 |
Missing buildWhen logic in complex widgets. |
|
bloc-use-multi-provider |
💡 Hint |
-0.5 |
Multiple nested Providers instead of MultiProvider. |
|
bloc-direct-emit |
🔴 Error |
-5.0 |
Emitting state outside of events/handlers in BLoC. |
| 🔄 Unnecessary Rebuilds |
rebuild-controller-in-build |
🔴 Error |
-5.0 |
Creating animation/text controllers in build(). |
|
rebuild-object-in-build |
⚠️ Warning |
-3.0 |
Allocating styling objects (BoxDecoration/TextStyle) in build(). |
|
rebuild-mediaquery-of |
⚠️ Warning |
-3.0 |
Using MediaQuery.of(context) instead of granular selectors. |
|
rebuild-missing-key |
ℹ️ Info |
-1.0 |
Custom widgets in lists instantiated without Key. |
|
rebuild-setstate-with-statemgmt |
ℹ️ Info |
-1.0 |
Using setState() in a file importing BLoC/Provider. |
| 📝 Hardcoded Values |
hardcoded-string |
ℹ️ Info |
-1.0 |
Hardcoded string literals in UI widgets. |
|
hardcoded-color |
ℹ️ Info |
-1.0 |
Hardcoded color literals (Color(0xFF...)) in widgets. |
|
hardcoded-dimension |
💡 Hint |
-0.5 |
Hardcoded double literals (width/height/padding) in UI. |
|
hardcoded-font-size |
💡 Hint |
-0.5 |
Hardcoded font sizes in TextStyle constructors. |
|
hardcoded-url |
⚠️ Warning |
-3.0 |
Hardcoded API/server URL strings in widgets. |
| 🧹 Clean Code |
clean-unused-import |
⚠️ Warning |
-3.0 |
Import statements where symbols are never referenced. |
|
clean-duplicate-import |
⚠️ Warning |
-3.0 |
The exact same file/package imported multiple times. |
|
clean-unused-variable |
⚠️ Warning |
-3.0 |
Local variable declared but never used in functions. |
|
clean-empty-catch |
⚠️ Warning |
-3.0 |
catch (e) {} blocks with no handling logic. |
|
clean-todo-comment |
ℹ️ Info |
-1.0 |
TODO, FIXME, HACK, or XXX comments left in code. |
|
clean-commented-code |
💡 Hint |
-0.5 |
Multiple lines of commented-out code blocks. |
|
clean-commented-url |
⚠️ Warning |
-3.0 |
Commented-out URL strings (// http://...). |
|
clean-empty-block |
⚠️ Warning |
-3.0 |
Empty if, else, for, while statement blocks. |
| 🌐 API Usage |
api-commented-call |
⚠️ Warning |
-3.0 |
Commented-out API/HTTP calls (e.g. // http.get(...)). |
|
api-prefer-try-catch |
💡 Hint |
-0.5 |
Using .catchError() instead of idiomatic try-catch blocks. |
|
api-unhandled-error |
🔴 Error |
-5.0 |
Async API/repository calls not wrapped in try-catch. |
| 🔒 Null Safety |
null-bang-operator |
⚠️ Warning |
-3.0 |
Force unwrap (!) operator risks runtime crash. |
|
null-late-keyword |
ℹ️ Info |
-1.0 |
late variable declarations risk uninitialized errors. |
|
null-unchecked-nullable |
🔴 Error |
-5.0 |
Unsafe casts (as T) or map key accesses without null check. |
|
null-chain-risk |
🔴 Error |
-5.0 |
Broken ?. chain like obj?.prop.method() causing crash. |
|
null-assert-collection |
⚠️ Warning |
-3.0 |
Force unwrap on collection operators like list.first!. |
| ⏳ Async Operations |
async-missing-mounted |
🔴 Error |
-5.0 |
Using context after async gaps without checking mounted. |
|
async-no-await |
💡 Hint |
-0.5 |
Async functions containing no await statements. |
|
async-unawaited-future |
⚠️ Warning |
-3.0 |
Calling futures without await or assigning to variable. |
|
async-in-initstate |
🔴 Error |
-5.0 |
Direct async/await calls inside initState(). |
|
async-fire-and-forget |
🔴 Error |
-5.0 |
Async operations triggered without error handling catch blocks. |
| 🏛️ Separation of Concerns |
separation-api-in-widget |
🔴 Error |
-5.0 |
Direct HTTP or repository network calls in widget classes. |
|
separation-db-in-widget |
🔴 Error |
-5.0 |
Direct SharedPreferences or database access inside widgets. |
|
separation-logic-in-build |
🔴 Error |
-5.0 |
Heavy arithmetic or non-widget business logic in build(). |
|
separation-state-in-widget |
⚠️ Warning |
-3.0 |
Excessive setState() calls in a single widget file. |
|
separation-large-file |
ℹ️ Info |
-1.0 |
Widget files exceeding 300 lines of code. |
🤝 Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
📄 License
This project is licensed under the MIT License — see the LICENSE file for details.
Made with ❤️ for the Flutter community by Vishal Gole