clean_architecture_linter 2.4.0 copy "clean_architecture_linter: ^2.4.0" to clipboard
clean_architecture_linter: ^2.4.0 copied to clipboard

A comprehensive analysis_server_plugin that automatically enforces Clean Architecture principles in Flutter projects with Riverpod state management.

Clean Architecture Linter #

pub package License: MIT

πŸ‡°πŸ‡· ν•œκ΅­μ–΄ README | πŸ‡ΊπŸ‡Έ English README

A comprehensive custom lint package that automatically enforces Clean Architecture principles in Flutter/Dart projects with Riverpod state management. Write code naturally while the linter guides you toward perfect Clean Architecture compliance with real-time feedback and actionable corrections.

⚠️ Note: This package is designed for projects using Riverpod for state management. Some presentation layer rules specifically validate Riverpod patterns.

✨ Key Features #

  • πŸ›‘οΈ Automatic Clean Architecture Protection - Write code freely, linter catches violations
  • 🎯 34 Specialized Rules - Comprehensive coverage of all Clean Architecture layers
  • πŸš€ Flutter-Optimized - Built specifically for Flutter development patterns
  • 🎨 Riverpod State Management - Enforces 3-tier provider architecture (Entity β†’ UI β†’ Computed)
  • πŸ“š Educational - Learn Clean Architecture through guided corrections
  • ⚑ Real-time Feedback - Immediate warnings with actionable solutions
  • πŸ”§ Zero Configuration - Works out of the box with sensible defaults
  • πŸ§ͺ Test-Aware - Smart exceptions for test files and development contexts

πŸ“‹ Rules Overview (34 Rules) #

🌐 Core Clean Architecture Principles (6 rules) #

  1. Layer Dependency - Enforces dependency direction (inward only)
  2. Domain Purity - Prevents external framework dependencies in domain layer
  3. Dependency Inversion - Validates abstraction-based dependencies
  4. Repository Interface - Ensures proper repository abstractions
  5. Circular Dependency - Prevents circular dependencies between layers
  6. Boundary Crossing - Validates proper layer boundary crossing

🎯 Domain Layer Rules (2 rules) #

  1. UseCase No Result Return - UseCases should return entities directly (pass-through pattern)
  2. Exception Naming Convention - Feature prefix for domain exceptions

πŸ’Ύ Data Layer Rules (10 rules) #

  1. Model Structure - Freezed models with entity composition
  2. Model Field Duplication - No duplicate entity fields in models
  3. Model Conversion Methods - Required toEntity() method in extensions
  4. Model Naming Convention - Models must end with Model suffix
  5. DataSource Abstraction - Abstract interfaces for data sources
  6. DataSource No Result Return - DataSources throw exceptions
  7. Repository Implementation - RepositoryImpl must implement domain interface
  8. Repository Pass Through - Repositories return Future<Entity> (warns on Result pattern)
  9. Repository No Throw - Repositories use pass-through pattern (AppException types allowed)
  10. DataSource Exception Types - Use defined data layer exceptions only
  11. Model Entity Direct Access - Use .toEntity() instead of direct .entity access

🎨 Presentation Layer Rules (14 rules) #

  1. No Presentation Models - Use Freezed State instead of ViewModels
  2. Extension Location - Extensions in same file as the class
  3. Freezed Usage - Use Freezed instead of Equatable
  4. Riverpod Generator - Use @riverpod annotation
  5. Presentation No Data Exceptions - Use domain exceptions only
  6. Presentation Use AsyncValue - Use AsyncValue for error handling (3-tier architecture)
  7. Presentation No Throw - No exception throwing in Presentation layer
  8. Widget No UseCase Call - Widgets should not call UseCases directly (use Providers)
  9. Widget Ref Read Then When - Avoid using .when() after ref.read() (anti-pattern)
  10. Riverpod Ref Usage - Use ref.watch() in build(), ref.read() in methods (with UseCase detection)
  11. Riverpod Ref After Async Gap - Advisory warning for ref.read/watch/listen/invalidate/refresh after await in provider methods (a preceding if (!ref.mounted) return; guard suppresses it)
  12. Riverpod Provider Naming - Provider functions must include type suffix (repository/usecase/datasource)
  13. Ref Mounted Usage - Avoid ref.mounted in the UI layer (widgets/pages); inside a Notifier it is the recommended disposal guard and is not reported
  14. Riverpod Keep Alive - Only use keepAlive: true for global state (auth, settings, cache)

πŸ”§ Cross-Layer Rules (1 rule) #

  1. Allowed Instance Variables - Enforces stateless architecture (UseCase/Repository/DataSource)

πŸ§ͺ Optional: Test Coverage Rule #

Test Coverage - Enforces test files for UseCases, Repositories, DataSources, and Notifiers (disabled by default)

πŸ“– Implementation Guide: See CLEAN_ARCHITECTURE_GUIDE.md for detailed patterns and examples.

🎨 Riverpod State Management: See CLAUDE.md § Riverpod State Management Patterns for 3-tier provider architecture guide.

πŸš€ Quick Start #

πŸš€ v2.0: Starting with 2.0.0-dev.1, this package runs on the official analysis_server_plugin β€” no custom_lint dependency, no pubspec_overrides.yaml workaround. Lint runs directly via dart analyze (use dart analyze, not flutter analyze β€” see the warning below). Upgrading from a v1 (custom_lint) setup? Follow MIGRATION.md.

πŸ“‹ Requirements #

  • Dart SDK: 3.10.0+
  • Flutter: 3.0+ (optional, for Flutter projects)
  • Riverpod: Required for presentation layer rules (riverpod_generator recommended)

1. Enable the plugin #

# analysis_options.yaml
plugins:
  clean_architecture_linter: ^2.0.0-dev.1

analyzer:
  exclude:
    - test/**
    - "**/*.test.dart"    # Exclude test files
    - "**/*.g.dart"       # Exclude generated files
    - "**/*.freezed.dart" # Exclude Freezed files
    - "**/*.mocks.dart"   # Exclude mock files

Do not also add clean_architecture_linter to dev_dependencies when your project uses analyzer-bound tools such as riverpod_lint. The ASP plugin is resolved in its own synthetic package from the plugins: section, which avoids forcing its analyzer constraints into your app's pub solve.

2. Run the linter #

dart pub get
dart analyze        # Flutter projects too β€” use `dart analyze`, NOT `flutter analyze`

That's it! The 34 rules are reported directly in your dart analyze output.

⚠️ Use dart analyze, not flutter analyze. flutter analyze silently drops analysis_server_plugin diagnostics (it stops collecting before the plugin publishes its results) and prints No issues found! even when violations exist. This applies to CI and local runs alike. Root cause and a regression guard: docs/analysis/FLUTTER_ANALYZE_PLUGIN_LOSS.md.

3. See it in action #

example/ is a runnable Dart project with a good_examples/ (0 warnings) and a bad_examples/ folder (2 intentional violations). Clone this repo, then:

cd example
dart pub get
dart analyze

Real output:

warning - lib/bad_examples/features/todo/data/models/todo_remote_model.dart:12:1 - Model name "TodoRemoteModel" should not include DataSource implementation "remote". This violates implementation independence. Rename to "TodoModel". Models should be independent of DataSource implementation. - model_naming_convention
warning - lib/bad_examples/features/todo/data/repositories/todo_repository_impl.dart:25:3 - Repository should NOT use Result pattern. Use pass-through pattern instead. Return Future<Entity> directly. Let errors pass through to AsyncValue.guard(). - repository_pass_through

In VS Code / Android Studio / IntelliJ, the same two warnings appear as inline squiggles in the editor and as entries in the Problems panel β€” no extra setup beyond the Dart/Flutter extension. Hovering a squiggle shows the problem message; the correction message underneath tells you the fix (there is no auto-fix quick action yet, since these rules don't register an analyzer CorrectionProducer). Each file under example/lib/bad_examples/ links to its fixed counterpart in example/lib/good_examples/ in a header comment.

  • Local: docs/config/lint_profile_balanced.yaml
  • CI: docs/config/lint_profile_strict.yaml

See docs/config/RECOMMENDED_SETUP.md for details.

🧩 Compatibility β€” analyzer 13 / Riverpod 3+ #

v2.4 runs on the official analysis_server_plugin (>=0.3.15 <0.3.16) and supports analyzer >=13.0.0 <14.0.0, matching the current riverpod_lint 3.1.x analyzer line while avoiding analyzer 14-only plugin-host versions. Consumer smoke keeps the ASP range pinned to 0.3.15 because 0.3.18 still hangs during plugin analysis under analyzer 13.

riverpod_lint 3.1.x carries its own analyzer constraints. Keep analyzer plugins out of dev_dependencies and enable both tools through top-level plugins: when you need them in one consumer project. The analyzer plugin manager resolves all enabled plugins in one synthetic package, so this package keeps its analyzer range aligned with riverpod_lint:

plugins:
  clean_architecture_linter: ^2.4.0
  riverpod_lint: ^3.1.7

The v1 custom_lint upstream (invertase/dart_custom_lint) was archived in May 2026. v2.0 moves fully to the official plugin, so the old pubspec_overrides.yaml bridge is no longer needed β€” delete it when upgrading.

πŸŽ›οΈ Configuration #

Optional: Test Coverage #

In v2.0, rule severity is controlled with the standard analyzer errors: map, keyed by each rule's diagnostic name. Promote a rule to an error, downgrade it to a hint, or silence it:

# analysis_options.yaml
analyzer:
  errors:
    repository_interface: error   # treat as build-breaking
    riverpod_keep_alive: ignore   # silence

The opt-in clean_architecture_linter_require_test (test coverage) rule is not bundled in 2.0.0-dev.1. It will be re-introduced in a later v2 pre-release; track the CHANGELOG.

🚦 Usage #

Folder Structure #

Organize your Flutter project following Clean Architecture:

lib/
β”œβ”€β”€ {feature_name}/
β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”œβ”€β”€ entities/
β”‚   β”‚   β”œβ”€β”€ repositories/
β”‚   β”‚   └── usecases/
β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”œβ”€β”€ datasources/
β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   └── repositories/
β”‚   └── presentation/
β”‚       β”œβ”€β”€ providers/
β”‚       β”œβ”€β”€ widgets/
β”‚       └── pages/

Running the Linter #

# Run the linter (rules are included in the analyzer output)
dart analyze        # Flutter projects too β€” NOT `flutter analyze` (drops plugin diagnostics)

⚠️ In Flutter projects run dart analyze, not flutter analyze. See docs/analysis/FLUTTER_ANALYZE_PLUGIN_LOSS.md.

IDE Integration #

The linter works automatically in:

  • VS Code with the Dart/Flutter extensions
  • IntelliJ IDEA / Android Studio with Flutter plugin

πŸ“š Examples #

βœ… Good Examples #

Domain Entity (Immutable)

// lib/domain/entities/user_entity.dart
class UserEntity {
  final String id;
  final String name;
  final String email;

  const UserEntity({
    required this.id,
    required this.name,
    required this.email,
  });

  bool isValidEmail() {
    return email.contains('@');
  }
}

Data Model with Database (ObjectBox Example)

// lib/data/models/user_model.dart
import 'package:objectbox/objectbox.dart';  // βœ… Allowed

@Entity()  // βœ… Database annotation instead of @freezed
class UserModel {
  @Id()
  int id = 0;

  String name;
  String email;

  UserModel({required this.name, required this.email});

  // βœ… Private database access is allowed
  static Box<UserModel> get _box => objectBoxService.store.box<UserModel>();

  // Conversion method
  UserEntity toEntity() => UserEntity(
    id: id.toString(),
    name: name,
    email: email,
  );
}

Note: When using database libraries (ObjectBox, Realm, Isar, Drift), Models are mutable and use database-specific annotations instead of @freezed. This is an exception to the standard Freezed pattern.

Repository Interface

// lib/domain/repositories/user_repository.dart
abstract class UserRepository {
  Future<UserEntity> getUser(String id);
  Future<void> saveUser(UserEntity user);
}

UseCase with Single Responsibility

// lib/domain/usecases/get_user_usecase.dart
class GetUserUseCase {
  final UserRepository repository;

  GetUserUseCase(this.repository);

  Future<UserEntity> call(String userId) {
    return repository.getUser(userId);
  }
}

❌ Bad Examples (Will be flagged) #

Mutable Domain Entity

// ❌ This will be flagged by entity_immutability
class UserEntity {
  String name; // Non-final field

  void setName(String newName) { // Setter in entity
    name = newName;
  }
}

Domain Layer with External Dependencies

// ❌ This will be flagged by domain_purity
import 'package:http/http.dart'; // External framework import

class UserEntity {
  final String name;
}

UI with Direct Business Logic

// ❌ This will be flagged by business_logic_isolation
class UserWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Business logic in UI layer - WRONG!
    final isValid = email.contains('@') && email.length > 5;
    return Text(isValid ? 'Valid' : 'Invalid');
  }
}

Repository Using Result Pattern

// ❌ This will be flagged - use pass-through pattern instead
class UserRepositoryImpl implements UserRepository {
  @override
  Future<Result<UserEntity, Failure>> getUser(String id) async {
    try {
      final model = await dataSource.getUser(id);
      return Success(model.toEntity());
    } catch (e) {
      return Failure(UserFailure.fromException(e));
    }
  }
}

// βœ… Correct: Pass-through pattern
class UserRepositoryImpl implements UserRepository {
  @override
  Future<UserEntity> getUser(String id) async {
    final model = await dataSource.getUser(id);  // Errors pass through
    return model.toEntity();
  }
}

Layer Dependency Violation

// ❌ This will be flagged by avoid_layer_dependency_violation
// In domain layer file:
import 'package:myapp/data/models/user_model.dart'; // Domain importing Data!

class UserEntity extends UserModel { // Wrong dependency direction
  // ...
}

Missing Exception Prefix

// ❌ This will be flagged by ensure_exception_prefix
class NetworkException extends Exception { // Should be UserNetworkException
  // ...
}

πŸ”„ Common Patterns #

Pass-through Error Handling (Recommended)

// βœ… Good: Pass-through pattern
// DataSource throws AppException
class UserRemoteDataSource {
  Future<UserModel> getUser(String id) async {
    try {
      final response = await client.get('/users/$id');
      return UserModel.fromJson(response.data);
    } on DioException catch (e) {
      throw e.toAppException();  // Convert to AppException
    }
  }
}

// Repository passes through (no try-catch)
class UserRepositoryImpl implements UserRepository {
  @override
  Future<UserEntity> getUser(String id) async {
    final model = await dataSource.getUser(id);  // Errors pass through
    return model.toEntity();
  }
}

// UseCase adds business validation
class GetUserUseCase {
  Future<UserEntity> call(String id) {
    if (id.isEmpty) {
      throw const InvalidInputException.withCode('errorValidationIdRequired');
    }
    return repository.getUser(id);  // Pass-through
  }
}

// Presentation uses AsyncValue.guard()
@riverpod
class UserNotifier extends _$UserNotifier {
  @override
  Future<User> build(String id) => ref.read(getUserUseCaseProvider)(id);

  Future<void> refresh() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => ref.read(getUserUseCaseProvider)(id));
  }
}

Proper Exception Naming

// βœ… Good: Proper exception prefixes
class UserNetworkException extends Exception {
  final String message;
  UserNetworkException(this.message);
}

class UserValidationException extends Exception {
  final String field;
  UserValidationException(this.field);
}

For more detailed examples and explanations, see our comprehensive Examples Guide.

πŸ› οΈ Development #

Project Structure #

clean_architecture_linter/
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   └── rules/
β”‚   β”‚       β”œβ”€β”€ domain_rules/
β”‚   β”‚       β”œβ”€β”€ data_rules/
β”‚   β”‚       └── presentation_rules/
β”‚   └── clean_architecture_linter.dart
β”œβ”€β”€ example/
β”œβ”€β”€ test/
└── README.md

Contributing #

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new rules
  4. Format your code: dart format .
  5. Ensure all tests pass
  6. Submit a pull request

See CONTRIBUTING.md for detailed guidelines.

πŸ“„ License #

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Support #

🎯 Roadmap #

  • ❌ Configuration system for custom naming patterns
  • ❌ Support for multiple state management solutions
  • ❌ Integration with CI/CD workflows
  • ❌ Custom rule creation guide
  • ❌ Performance optimizations

Made with ❀️ for the Flutter community

3
likes
150
points
530
downloads

Documentation

Documentation
API reference

Publisher

verified publisherittae.com

Weekly Downloads

A comprehensive analysis_server_plugin that automatically enforces Clean Architecture principles in Flutter projects with Riverpod state management.

Repository (GitHub)
View/report issues
Contributing

Topics

#clean-architecture #lint #custom-lint #linter #static-analysis

License

MIT (license)

Dependencies

analysis_server_plugin, analyzer, path

More

Packages that depend on clean_architecture_linter