rigid_dart 0.1.0 copy "rigid_dart: ^0.1.0" to clipboard
rigid_dart: ^0.1.0 copied to clipboard

Rust-grade guardrails for Dart/Flutter. Enforces layout safety, state discipline, architecture boundaries, and modern Dart idioms as hard analyzer errors via custom_lint.

[Rigid Dart]

🦀 Rigid Dart

Rust-grade guardrails for Dart & Flutter.
A custom_lint plugin that turns analyzer warnings into walls.

23 rules Error severity Dart 3+ MIT License


The Problem #

Dart's compiler is lenient. It lets you ship layout crashes, untyped state, hardcoded colors, deprecated APIs, and dynamic casts that Rust would catch at compile time. AI agents make this worse — they generate code that works but violates every architecture principle you've established.

The Solution #

Rigid Dart is a custom_lint plugin that enforces 23 rules as hard analyzer errors. Every rule uses TypeChecker-based type resolution — it catches aliases, subclasses, and reexports, not just string names. It also ships 3 quick fixes, a strict analysis_options.yaml base, a configurable presets system, and an optional PATH wrapper that blocks flutter run until your code is clean.

🦀 Rigid Dart gate -- analyzing before compile...

  error - lib/home_page.dart:42:9
    Colors.blue is banned. Use Theme.of(context).colorScheme.* instead.
    rigid_no_hardcoded_colors

════════════════════════════════════════════════════════
  ❌ RIGID DART: Analysis failed. Fix violations above.
════════════════════════════════════════════════════════

The agent sees a build failure. It fixes the code. It retries. That's the loop.


Rules #

Phase 1: Layout Safety #

Catches runtime crashes before they happen.

Rule Sev What it catches
rigid_no_expanded_outside_flex 🔴 Expanded/Flexible outside Row/Column/Flex
rigid_no_unbounded_column 🟡 Nested Column/ListView in scrollables without constraints
rigid_constrained_text_field 🔴 TextField in Row without width constraint

Phase 2: State Discipline #

Enforces Riverpod. Prevents memory leaks and async crashes.

Rule Sev What it catches
rigid_no_set_state 🔴 Any setState() call (type-resolved: checks receiver is State<T>)
rigid_no_change_notifier 🔴 ChangeNotifier subclass/mixin (catches via isAssignableFrom)
rigid_exhaustive_async 🔴 Direct .value on AsyncValue without .when()
rigid_no_build_context_across_async 🔴 BuildContext used after await without mounted check
rigid_dispose_required 🔴 Disposable controllers (AnimationController, FocusNode, etc.) not disposed

Phase 3: Architecture #

Enforces design system usage. Bans magic values.

Rule Sev What it catches
rigid_no_hardcoded_colors 🔴 Color(0xFF...) or Colors.* outside theme definitions
rigid_no_hardcoded_text_style 🟡 Raw TextStyle(fontSize: N) outside theme definitions
rigid_no_magic_numbers 🟡 Literal numbers in layout params (padding, margin, etc.)
rigid_require_tests 🟡 lib/ files without corresponding test/ files
rigid_layer_boundaries 🔴 Cross-layer imports violating user-defined architecture
rigid_no_direct_instantiation 🔴 Direct Repository/Service/Api/Client instantiation in widgets

Phase 4: Freshness #

Bans deprecated APIs. Enforces modern Dart.

Rule Sev What it catches
rigid_no_will_pop_scope 🔴 Deprecated WillPopScopequick fix: PopScope
rigid_no_with_opacity 🔴 Deprecated .withOpacity()quick fix: .withValues(alpha:)
rigid_no_dynamic 🔴 Explicit dynamic type annotations → quick fix: Object?
rigid_no_print 🟡 print() calls in non-test code

Phase 5: Quality #

Prevents sloppy agent patterns.

Rule Sev What it catches
rigid_max_widget_lines 🟡 Widget classes exceeding configurable line threshold (default 250)
rigid_no_raw_async 🟡 Async functions with await but no try/catch
rigid_min_test_assertions 🟡 Test files with test blocks but zero expect() calls
rigid_require_key_in_list 🟡 Widgets in list builders without explicit Key parameter
rigid_no_hardcoded_strings 🟡 Hardcoded string literals in Text(), Tooltip(), InputDecoration

Quick Start #

1. Install #

# pubspec.yaml
dev_dependencies:
  custom_lint: ^0.8.1
  rigid_dart:
    git:
      url: https://github.com/adriancmurray/rigid_dart.git
flutter pub get

2. Configure #

# analysis_options.yaml
include: package:rigid_dart/analysis_options.yaml

analyzer:
  plugins:
    - custom_lint

3. Analyze #

# Standard analysis (strict options from rigid_dart)
dart analyze --fatal-infos

# Custom rules (the 15 rigid_* rules)
dart run custom_lint

Quick Fixes #

Three rules offer IDE quick fixes (lightbulb menu):

Rule Quick fix
rigid_no_with_opacity Replace .withOpacity(x).withValues(alpha: x)
rigid_no_will_pop_scope Replace WillPopScopePopScope
rigid_no_dynamic Replace dynamicObject?

Enforcement Tiers #

Tier Name Blocks Agent can bypass?
1 Advisor Nothing — IDE warnings only ✅ Yes
2 Gatekeeper git commit 🔶 Can still run locally
3 Compiler flutter run/build/test ❌ No

See AGENT.md for full setup instructions for each tier.


Suppressing Rules #

Per-line only. Global suppression is not supported by design.

// ignore: rigid_no_hardcoded_colors
final debugColor = Colors.red; // Emergency escape hatch

For multi-line blocks:

// ignore_for_file: rigid_no_magic_numbers
// Only in spacing_tokens.dart where constants are DEFINED
const kCardPadding = 16.0;
const kSectionSpacing = 24.0;

Modifying Rules #

Adding a new rule #

  1. Create a file in lib/rules/<phase>/ (e.g., lib/rules/state/no_global_keys.dart)
  2. Extend DartLintRule from custom_lint_builder
  3. Define a LintCode with a rigid_ prefix and appropriate severity
  4. Implement the run method using context.registry.add* callbacks
  5. Register the rule in lib/rigid_dart.dart

Example skeleton:

import 'package:analyzer/error/error.dart' show DiagnosticSeverity;
import 'package:analyzer/error/listener.dart' show DiagnosticReporter;
import 'package:custom_lint_builder/custom_lint_builder.dart';

class NoGlobalKeys extends DartLintRule {
  const NoGlobalKeys() : super(code: _code);

  static const _code = LintCode(
    name: 'rigid_no_global_keys',
    problemMessage: 'GlobalKey is banned. Use ValueKey or UniqueKey.',
    errorSeverity: DiagnosticSeverity.ERROR,
  );

  @override
  void run(
    CustomLintResolver resolver,
    DiagnosticReporter reporter,
    CustomLintContext context,
  ) {
    context.registry.addInstanceCreationExpression((node) {
      // Your detection logic here
      reporter.atNode(node, code);
    });
  }
}

Then add to lib/rigid_dart.dart:

import 'package:rigid_dart/rules/state/no_global_keys.dart';
// ...
const NoGlobalKeys(),

Changing severity #

Edit the errorSeverity parameter in the rule's LintCode:

  • DiagnosticSeverity.ERROR — hard error (red squiggly)
  • DiagnosticSeverity.WARNING — warning (yellow squiggly)
  • DiagnosticSeverity.INFO — informational (blue squiggly)

Disabling a rule #

Remove it from the getLintRules list in lib/rigid_dart.dart. Do not comment it out — unused imports trigger their own warnings.

Testing changes #

cd packages/rigid_dart
dart analyze  # Must show 0 issues

Then test against a consumer project:

cd apps/your_app
flutter pub get          # Picks up local changes via path dep
dart run custom_lint     # Verify your rule fires

Shared Analysis Options #

package:rigid_dart/analysis_options.yaml includes:

analyzer:
  language:
    strict-casts: true       # No implicit dynamic downcasts
    strict-inference: true   # No implicit dynamic in generics

linter:
  rules:
    avoid_dynamic_calls: true
    always_declare_return_types: true
    prefer_final_locals: true
    prefer_const_constructors: true
    prefer_const_declarations: true
    unawaited_futures: true
    # ... and more

Projects that include: this file inherit all settings. Override specific rules in your project's analysis_options.yaml under linter: rules:.


For AI Agents #

This repo includes AGENT.md — a machine-readable playbook for IDE agents (Cursor, Windsurf, Claude Code, GitHub Copilot, etc.). Give an agent this repo URL and it can install, configure, and verify rigid_dart with zero guesswork.


License #

MIT — see LICENSE.

0
likes
0
points
15
downloads

Publisher

unverified uploader

Weekly Downloads

Rust-grade guardrails for Dart/Flutter. Enforces layout safety, state discipline, architecture boundaries, and modern Dart idioms as hard analyzer errors via custom_lint.

Repository (GitHub)
View/report issues

Topics

#linting #static-analysis #flutter #architecture #code-quality

License

unknown (license)

Dependencies

analyzer, custom_lint_builder, path, yaml

More

Packages that depend on rigid_dart