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. Write code naturally while the linter guides you toward perfect Clean Architecture compliance with real-time feedback and actionable corrections.

✨ Key Features

  • πŸ›‘οΈ Automatic Clean Architecture Protection - Write code freely, linter catches violations
  • 🎯 26 Specialized Rules - Comprehensive coverage of all Clean Architecture layers
  • πŸš€ Flutter-Optimized - Built specifically for Flutter development patterns
  • πŸ“š 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 (26 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 (4 rules)

  1. UseCase No Result Return - UseCases should unwrap Result types
  2. UseCase Must Convert Failure - UseCases convert Failures to Exceptions
  3. Exception Naming Convention - Feature prefix for domain exceptions
  4. Exception Message Localization - Consistent exception messages

πŸ’Ύ 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() and fromEntity()
  4. DataSource Abstraction - Abstract interfaces for data sources
  5. DataSource No Result Return - DataSources throw exceptions
  6. Repository Implementation - RepositoryImpl must implement domain interface
  7. Repository Must Return Result - Repositories wrap results in Result type
  8. Repository No Throw - Repositories convert exceptions to Result
  9. DataSource Exception Types - Use defined data layer exceptions only
  10. Failure Naming Convention - Feature prefix for Failure classes

🎨 Presentation Layer Rules (6 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

πŸ§ͺ 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.

πŸš€ Quick Start

πŸ“‹ Requirements

  • Dart SDK: 3.6.0+
  • Flutter: 3.0+ (optional, for Flutter projects)

1. Add to your project

# pubspec.yaml
dev_dependencies:
  clean_architecture_linter: ^1.0.0
  custom_lint: ^0.7.6

2. Enable custom lint

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

3. Run the linter

dart pub get
dart pub custom_lint

That's it! The linter will now automatically enforce Clean Architecture principles in your codebase.

πŸŽ›οΈ Configuration

Optional: Test Coverage

The clean_architecture_linter_require_test rule is disabled by default. Enable it to enforce test files for critical components:

# analysis_options.yaml
custom_lint:
  rules:
    - clean_architecture_linter_require_test: true
      check_usecases: true       # Require tests for UseCases
      check_repositories: true   # Require tests for Repositories
      check_datasources: true    # Require tests for DataSources
      check_notifiers: true      # Require tests for Notifiers

🚦 Usage

Folder Structure

Organize your Flutter project following Clean Architecture:

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

Running the Linter

# Activate custom_lint if not already done
dart pub global activate custom_lint

# Run the linter
dart pub custom_lint

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('@');
  }
}

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 Throwing Exceptions

// ❌ This will be flagged by avoid_exception_throwing_in_repository
class UserRepositoryImpl implements UserRepository {
  @override
  Future<UserEntity> getUser(String id) async {
    if (id.isEmpty) {
      throw ArgumentError('ID cannot be empty'); // Should return Result instead
    }
    // ...
  }
}

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

Proper Error Handling with Result Type

// βœ… Good: Using Result pattern
sealed class Result<T, E> {}
class Success<T, E> extends Result<T, E> {
  final T value;
  Success(this.value);
}
class Failure<T, E> extends Result<T, E> {
  final E error;
  Failure(this.error);
}

// Repository implementation
class UserRepositoryImpl implements UserRepository {
  @override
  Future<Result<UserEntity, UserException>> getUser(String id) async {
    try {
      final userData = await dataSource.getUser(id);
      return Success(userData.toEntity());
    } catch (e) {
      return Failure(UserDataException(e.toString()));
    }
  }
}

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 --line-length=120 .
  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

Libraries

clean_architecture_linter
A focused custom lint package that enforces Clean Architecture principles in Flutter projects.