clean_architecture_linter 1.0.8
clean_architecture_linter: ^1.0.8 copied to clipboard
A comprehensive custom lint package that automatically enforces Clean Architecture principles in Flutter projects with Riverpod state management.
Clean Architecture Linter #
π°π· νκ΅μ΄ 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
- π― 33 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 (33 Rules) #
π Core Clean Architecture Principles (6 rules) #
- Layer Dependency - Enforces dependency direction (inward only)
- Domain Purity - Prevents external framework dependencies in domain layer
- Dependency Inversion - Validates abstraction-based dependencies
- Repository Interface - Ensures proper repository abstractions
- Circular Dependency - Prevents circular dependencies between layers
- Boundary Crossing - Validates proper layer boundary crossing
π― Domain Layer Rules (4 rules) #
- UseCase No Result Return - UseCases should unwrap Result types
- UseCase Must Convert Failure - UseCases convert Failures to Exceptions
- Exception Naming Convention - Feature prefix for domain exceptions
- Exception Message Localization - Consistent exception messages
πΎ Data Layer Rules (13 rules) #
- Model Structure - Freezed models with entity composition
- Model Field Duplication - No duplicate entity fields in models
- Model Conversion Methods - Required
toEntity()method in extensions - Model Entity Direct Access - Use
.toEntity()instead of direct.entityaccess - Model Naming Convention - Models must end with
Modelsuffix - DataSource Abstraction - Abstract interfaces for data sources
- DataSource No Result Return - DataSources throw exceptions
- Repository Implementation - RepositoryImpl must implement domain interface
- Repository Must Return Result - Repositories wrap results in Result type
- Repository No Throw - Repositories convert exceptions to Result
- DataSource Exception Types - Use defined data layer exceptions only
- Failure Naming Convention - Feature prefix for Failure classes
π¨ Presentation Layer Rules (11 rules) #
- No Presentation Models - Use Freezed State instead of ViewModels
- Extension Location - Extensions in same file as the class
- Freezed Usage - Use Freezed instead of Equatable
- Riverpod Generator - Use
@riverpodannotation - Presentation No Data Exceptions - Use domain exceptions only
- Presentation Use AsyncValue - Use AsyncValue for error handling (3-tier architecture)
- Presentation No Throw - No exception throwing in Presentation layer
- Widget No UseCase Call - Widgets should not call UseCases directly (use Providers)
- Widget Ref Read Then When - Avoid using .when() after ref.read() (anti-pattern)
- Riverpod Ref Usage - Use ref.watch() in build(), ref.read() in methods (with UseCase detection)
- Riverpod Provider Naming - Provider functions must include type suffix (repository/usecase/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 #
π Requirements #
- Dart SDK: 3.6.0+
- Flutter: 3.0+ (optional, for Flutter projects)
- Riverpod: Required for presentation layer rules (riverpod_generator recommended)
1. Add to your project #
# pubspec.yaml
dev_dependencies:
clean_architecture_linter: ^1.0.4
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 run 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
π¦ 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 #
# Activate custom_lint if not already done
dart pub global activate custom_lint
# Run the linter
dart run 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 #
- Fork the repository
- Create a feature branch
- Add tests for new rules
- Format your code:
dart format . - Ensure all tests pass
- 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 #
- β Star this repository if it helped you!
- π Report bugs
- π‘ Request features
- π Read the documentation
π― 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