Why more_visibility?
Dart's built-in visibility is limited to library-private (_identifier) and public (everything else). This works for small projects, but as codebases grow, you often need finer control:
- 🚫 Prevent accidental imports of internal utilities from parent directories
- 🔒 Keep implementation details scoped to feature modules
- 📁 Enforce architectural boundaries within your codebase
more_visibility brings Java-style visibility modifiers and automatic directory-private enforcement to Dart, making your codebase more maintainable and preventing architectural drift.
✨ Features
- 🎯 Directory-private enforcement — Underscore-prefixed directories (
_components,_hooks) are automatically restricted to same-level imports. Zero configuration. - 🛡️ Java-style annotations —
@mprotectedand@mdefaultfor fine-grained control over symbol visibility - ⚡ Real-time feedback — Analysis server plugin catches violations in your IDE as you type
- 🔧 Works with code generation — Auto-annotates generated files from Freezed, Riverpod, JsonSerializable, etc.
- 🎨 Configurable severity — Set rules as errors, warnings, or info based on your needs
🚀 Quick Start
1. Install
Add to your pubspec.yaml:
dependencies:
more_visibility_annotation: ^0.1.0
dev_dependencies:
more_visibility: ^0.1.10
2. Enable the plugin
In your analysis_options.yaml:
include: package:more_visibility/more_visibility.yaml
That's it! The plugin is now active and will enforce visibility rules.
📖 Usage
Directory-private (Automatic)
Simply prefix directories with _ to make them private to their parent directory:
lib/
├── pages/
│ ├── page.dart ✅ Can import _components/
│ ├── _components/
│ │ └── button.dart 🔒 Private to lib/pages/
│ └── profile/
│ └── profile_page.dart ❌ Cannot import _components/
Example:
// ✅ lib/pages/page.dart
import '_components/button.dart'; // Same depth - OK
// ❌ lib/pages/profile/profile_page.dart
import '../_components/button.dart'; // Different depth - ERROR
Note: Only applies to your application code. Dependencies are excluded to avoid false positives.
Annotation-based visibility
Control individual declarations with annotations:
import 'package:more_visibility_annotation/more_visibility_annotation.dart';
// 🛡️ Protected: accessible from this directory and subdirectories
@mprotected
final sharedConfig = Config();
// 🔒 Module-default: accessible only within this directory
@mdefault
final localHelper = Helper();
File-level annotations:
@mprotected
library feature_auth;
// All declarations inherit @mprotected
class AuthService { }
final authToken = '';
🎨 Visual Examples
Before more_visibility
// lib/utils/internal_helper.dart
String formatSecret(String secret) => '***$secret***';
// lib/features/auth/login.dart
import '../../utils/internal_helper.dart'; // ⚠️ Unintended coupling
// lib/main.dart
import 'utils/internal_helper.dart'; // ⚠️ Internal API exposed
Problems:
- No enforcement of architectural boundaries
- Internal utilities leak across module boundaries
- Difficult to refactor without breaking unknown dependents
After more_visibility
// lib/utils/_internal/helper.dart (in private directory)
String formatSecret(String secret) => '***$secret***';
// lib/features/auth/login.dart
import '../../utils/_internal/helper.dart'; // ❌ Compile-time error!
// Error: `formatSecret` is in a private directory `/lib/utils/_internal`
// lib/utils/public_api.dart
import '_internal/helper.dart'; // ✅ Same depth - OK
export '_internal/helper.dart' show allowedFunction;
Benefits:
- ✅ Architectural boundaries enforced at compile-time
- ✅ Clear separation between public API and internal implementation
- ✅ Refactoring is safer with explicit visibility scopes
📚 Documentation
| Topic | Description |
|---|---|
| Visibility Rules | Detailed explanation of all visibility rules |
| Usage Guide | Step-by-step usage instructions and patterns |
| Auto-annotation | Configuring builders for generated code |
⚙️ Configuration
Adjusting severity
analyzer:
errors:
directory_private: warning # Default: error
more_visibility_protected: info # Default: error
more_visibility_module_default: ignore # Disable completely
Disabling rules
plugins:
more_visibility:
diagnostics:
directory_private: false # Disable directory-private rule
more_visibility_protected: false # Disable @mprotected checks
Excluding directories
Exclude specific directories from visibility checks using glob patterns:
more_visibility:
exclude:
- test/** # Exclude all test files
- integration_test/** # Exclude integration tests
- lib/generated/** # Exclude generated code
Files matching exclude patterns can freely access protected and private code, making it easy to write tests without visibility restrictions.
Ignoring specific violations
// ignore_for_file: directory_private
// ignore: more_visibility_protected
import '../protected_api.dart';
🔧 Auto-annotation for Generated Code
Works seamlessly with Freezed, Riverpod, JsonSerializable, and other code generators.
build.yaml:
post_process_builders:
more_visibility:auto_annotate:
options:
visibility: mprotected # Default annotation for generated files
Generated files automatically inherit visibility from their source files:
// user.dart
@mprotected
@freezed
class User with _$User {
// ...
}
// user.freezed.dart (generated)
@mprotected // ← Automatically added
part of 'user.dart';
// ...
📦 Requirements
- Dart SDK: 3.10.0 or later
- Flutter SDK: 3.38.0 or later (if using Flutter)
🤝 Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🌟 Star History
If you find this package useful, please consider giving it a star on GitHub!
Made with ❤️ for the Dart & Flutter community