ripplearc_flutter_lint 0.1.0
ripplearc_flutter_lint: ^0.1.0 copied to clipboard
A custom lint library providing rules like prefer_fake_over_mock for better testing practices
Custom Lint Library #
A Dart/Flutter library providing custom lint rules for better code quality and testing practices.
Project Structure #
lib/
rules/ # All lint rules go here
prefer_fake_over_mock_rule.dart
no_optional_operators_in_tests.dart
forbid_forced_unwrapping.dart
test/
rules/ # All rule tests go here
prefer_fake_over_mock_rule_test.dart
no_optional_operators_in_tests_test.dart
forbid_forced_unwrapping_test.dart
example/ # Example files demonstrating rules
example_prefer_fake_over_mock_rule.dart
example_no_optional_operators_in_tests_rule.dart
example_forbid_forced_unwrapping_rule.dart
Rules #
prefer_fake_over_mock #
Recommends using Fake instead of Mock for test doubles. Fakes provide more realistic behavior and are easier to maintain than mocks.
Bad ❌
class MockUserRepository extends Mock implements UserRepository {}
Good ✅
class FakeUserRepository extends Fake implements UserRepository {
@override
Future<User> getUser(String id) async => User(id: id, name: 'Test User');
}
forbid_forced_unwrapping #
Forbids the use of forced unwrapping (!) in production code. This rule encourages the use of null-safe alternatives to prevent runtime null errors.
Bad ❌
final name = user.name!; // Will crash if name is null
print('User: $name');
Good ✅
final name = user.name ?? 'Unknown'; // Safe with default value
print('User: $name');
no_optional_operators_in_tests #
Forbids the use of optional operators (?., ??) in test files. Tests should fail explicitly at the point of failure rather than silently handling null values. This rule is enforced as an error to ensure test reliability.
Bad ❌
test('example', () {
final result = someObject?.someProperty; // ERROR: Optional operators not allowed in tests
expect(result, equals(expected));
});
Good ✅
test('example', () {
final result = someObject.someProperty; // Will fail explicitly if null
expect(result, equals(expected));
});
Registering a Custom Lint Rule #
To register a custom lint rule in your package, follow these steps:
-
Create the Lint Rule: Implement your lint rule by extending
DartLintRuleinlib/rules/. For example:class ForbidForcedUnwrapping extends DartLintRule { const ForbidForcedUnwrapping() : super(code: _code); static const _code = LintCode( name: 'forbid_forced_unwrapping', problemMessage: 'Forced unwrapping (!) is not allowed in production code.', correctionMessage: 'Use null-safe alternatives like null coalescing (??) or explicit null checks.', errorSeverity: ErrorSeverity.WARNING, ); @override void run( CustomLintResolver resolver, ErrorReporter reporter, CustomLintContext context, ) { context.registry.addCompilationUnit((node) { if (_isTestFile(resolver.path)) return; _checkForForcedUnwrapping(node, reporter); }); } } -
Write Unit Tests: Create unit tests in
test/rules/to verify your rule works as expected:void main() { group('ForbidForcedUnwrapping', () { late ForbidForcedUnwrapping rule; late TestErrorReporter reporter; setUp(() { rule = const ForbidForcedUnwrapping(); reporter = TestErrorReporter(); }); test('should flag forced unwrapping in production code', () async { const source = ''' void main() { final String? name = null; final value = name!; // Should flag this print(value); } '''; await analyzeCode(source, path: 'lib/example.dart'); expect(reporter.errors, hasLength(1)); expect(reporter.errors.first.errorCode.name, equals('forbid_forced_unwrapping')); }); }); } -
Create an Example File: Create an example in
example/that demonstrates both the violation and correct usage:class User { final String? name; User({this.name}); } void main() { final user = User(name: null); // Bad: Using forced unwrapping final name = user.name!; // LINT print('User: $name'); // Will crash at runtime // Good: Using null-safe alternatives final safeName = user.name ?? 'Unknown'; print('User: $safeName'); // Safe, will print "User: Unknown" } -
Register the Rule: In
lib/ripplearc_flutter_lint.dart, add your rule to the list:class _RipplearcFlutterLint extends PluginBase { @override List<LintRule> getLintRules(CustomLintConfigs configs) => [ const ForbidForcedUnwrapping(), // ... other rules ]; } -
Configure the Linter: Copy the existing configuration from
example/custom_lint.yamlto your project root:cp example/custom_lint.yaml custom_lint.yaml -
Run the Linter: Use
dart run custom_lintto verify your rule works as expected.
By following these steps, you can successfully register and use custom lint rules in your Dart/Flutter project.
Configuration Files #
analysis_options.yaml #
This file configures the Dart analyzer and enables the custom lint plugin. Place it in your project root:
analyzer:
plugins:
- custom_lint # Enables the custom_lint plugin
custom_lint.yaml #
This configuration file includes all our custom lint rules:
prefer_fake_over_mock- Prefer using Fake over Mock for test doublesforbid_forced_unwrapping- Forbid forced unwrapping in production codeno_optional_operators_in_tests- Forbid optional operators in test files
Rule Configuration
- Each rule is listed under the
rulessection - Rules are enabled by default when listed
- The order of rules doesn't matter
- All rules from the library are available to use
Plugin Configuration
- The
analyzer.pluginssection must includecustom_lint_library - This enables our custom lint rules to be loaded
- Multiple plugins can be listed if needed
By following these steps, you can successfully register and use custom lint rules in your Dart/Flutter project.