Skip to content

Commit

Permalink
Adds notifier_build lint (#2839)
Browse files Browse the repository at this point in the history
  • Loading branch information
LeonardoRosaa authored Sep 14, 2023
1 parent 1b5e50b commit 0a37ce6
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/riverpod_lint/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Unreleased minor

- Added `notifier_build`, a lint to catch when a Notifier has no `build` method (thansk to @LeonardoRosaa)

## 2.0.4 - 2023-09-04

- `riverpod` upgraded to `2.4.0`
Expand Down
24 changes: 24 additions & 0 deletions packages/riverpod_lint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Riverpod_lint adds various warnings with quick fixes and refactoring options, su
- [functional\_ref (riverpod\_generator only)](#functional_ref-riverpod_generator-only)
- [notifier\_extends (riverpod\_generator only)](#notifier_extends-riverpod_generator-only)
- [avoid\_ref\_inside\_state\_dispose](#avoid_ref_inside_state_dispose)
- [missed\_build\_method (riverpod\_generator only)](#notifier_build-riverpod_generator-only)
- [All assists](#all-assists)
- [Wrap widgets with a `Consumer`](#wrap-widgets-with-a-consumer)
- [Wrap widgets with a `ProviderScope`](#wrap-widgets-with-a-providerscope)
Expand Down Expand Up @@ -551,6 +552,29 @@ class _MyWidgetState extends ConsumerState<MyWidget> {
}
```

### notifier_build (riverpod_generator only)

Classes annotated by `@riverpod` must have the `build` method.

**Good**:

```dart
@riverpod
class Example extends _$Example {
@overried
int build() => 0;
}
```

**Bad**:

```dart
// No "build" method found
@riverpod
class Example extends _$Example {}
```

## All assists

### Wrap widgets with a `Consumer`
Expand Down
2 changes: 2 additions & 0 deletions packages/riverpod_lint/lib/riverpod_lint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'src/lints/avoid_public_notifier_properties.dart';
import 'src/lints/avoid_ref_inside_state_dispose.dart';
import 'src/lints/functional_ref.dart';
import 'src/lints/missing_provider_scope.dart';
import 'src/lints/notifier_build.dart';
import 'src/lints/notifier_extends.dart';
import 'src/lints/provider_dependencies.dart';
import 'src/lints/provider_parameters.dart';
Expand All @@ -33,6 +34,7 @@ class _RiverpodPlugin extends PluginBase {
const ScopedProvidersShouldSpecifyDependencies(),
const UnsupportedProviderValue(),
const AvoidRefInsideStateDispose(),
const NotifierBuild(),
// const AvoidDynamicProviders(),
// // "Avoid passing providers as parameter to objects"
// const AvoidExposingProviderRef(),
Expand Down
90 changes: 90 additions & 0 deletions packages/riverpod_lint/lib/src/lints/notifier_build.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart';

import '../riverpod_custom_lint.dart';

const _buildMethodName = 'build';

class NotifierBuild extends RiverpodLintRule {
const NotifierBuild() : super(code: _code);

static const _code = LintCode(
name: 'notifier_build',
problemMessage:
'Classes annotated by `@riverpod` must have the `build` method',
);

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addClassDeclaration((node) {
final hasRiverpodAnnotation = node.metadata.where(
(element) {
final annotationElement = element.element;

if (annotationElement == null ||
annotationElement is! ExecutableElement) return false;

return riverpodType.isExactlyType(annotationElement.returnType);
},
).isNotEmpty;

if (!hasRiverpodAnnotation) return;

final hasBuildMethod = node.members
.where((e) => e.declaredElement?.displayName == _buildMethodName)
.isNotEmpty;

if (hasBuildMethod) return;

reporter.reportErrorForToken(_code, node.name);
});
}

@override
List<RiverpodFix> getFixes() => [
AddBuildMethodFix(),
];
}

class AddBuildMethodFix extends RiverpodFix {
@override
void run(
CustomLintResolver resolver,
ChangeReporter reporter,
CustomLintContext context,
AnalysisError analysisError,
List<AnalysisError> others,
) {
context.registry.addClassDeclaration((node) {
if (!node.sourceRange.intersects(analysisError.sourceRange)) return;

final changeBuilder = reporter.createChangeBuilder(
message: 'Add build method',
priority: 80,
);

changeBuilder.addDartFileEdit((builder) {
final offset = node.leftBracket.offset + 1;

builder.addSimpleInsertion(
offset,
'''
@override
dynamic build() {
// TODO: implement build
throw UnimplementedError();
}
''',
);
});
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';

/// Fake Provider
typedef _$ExampleProvider1 = Object;

@riverpod
// expect_lint: notifier_build
class ExampleProvider1 extends _$ExampleProvider1 {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'dart:io';

import 'package:collection/collection.dart';
import 'package:riverpod_lint/src/lints/notifier_build.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/utilities.dart';

import '../../golden.dart';

void main() {
testGolden(
'Verify that @riverpod classes has the build method',
'goldens/fixes/notifier_build.json',
() async {
const lint = NotifierBuild();
final fix = lint.getFixes().single;
final file = File(
'test/goldens/fixes/notifier_build.dart',
).absolute;

final result = await resolveFile2(path: file.path);
result as ResolvedUnitResult;

final errors = await lint.testRun(result);

final changes = await Future.wait([
for (final error in errors) fix.testRun(result, error, errors),
]);

return changes.flattened;
},
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';

/// Fake Provider
typedef _$ExampleProvider1 = Object;

/// Fake Provider
typedef _$ExampleProvider = AutoDisposeNotifier<int>;

@riverpod
// expect_lint: notifier_build
class ExampleProvider1 extends _$ExampleProvider1 {}

@riverpod
class ExampleProvider extends _$ExampleProvider {
@override
int build() => 0;
}

0 comments on commit 0a37ce6

Please sign in to comment.