New rule: avoid_void_async. `void f() async`, suggest `Future<void>`. (#1128)
* New rule: avoid_void_async. `void f() async`, suggest `Future<void>`.
Fix #1122.
As per discussion there, this is a *new* rule, not part of void_checks,
because it is much more likely to have low false positives.
In my experince, void_checks is very strict. It serves a useful purpose
in pushing people away from the more complex Null type by making void
useful in a way Null used to be used. However, void_checks is not
required for soundness, and it seems uncommon that the theory here
misses something practical.
For that reason, make a new rule. Users interested in making void behave
as if it were Null in certain ways can enable that, while users who just
want to make sure no async functions are declared wrong can enable this.
* Test on appveyor: move Stream into dart.async main file.
diff --git a/example/all.yaml b/example/all.yaml
index 4dc5023..865acf5 100644
--- a/example/all.yaml
+++ b/example/all.yaml
@@ -34,6 +34,7 @@
- avoid_types_as_parameter_names
- avoid_types_on_closure_parameters
- avoid_unused_constructor_parameters
+ - avoid_void_async
- await_only_futures
- camel_case_types
- cancel_subscriptions
diff --git a/lib/src/rules.dart b/lib/src/rules.dart
index 0b64876..f84820d 100644
--- a/lib/src/rules.dart
+++ b/lib/src/rules.dart
@@ -35,6 +35,7 @@
import 'package:linter/src/rules/avoid_types_as_parameter_names.dart';
import 'package:linter/src/rules/avoid_types_on_closure_parameters.dart';
import 'package:linter/src/rules/avoid_unused_constructor_parameters.dart';
+import 'package:linter/src/rules/avoid_void_async.dart';
import 'package:linter/src/rules/await_only_futures.dart';
import 'package:linter/src/rules/camel_case_types.dart';
import 'package:linter/src/rules/cancel_subscriptions.dart';
@@ -161,6 +162,7 @@
..register(new AvoidSlowAsyncIo())
..register(new AvoidTypesAsParameterNames())
..register(new AvoidUnusedConstructorParameters())
+ ..register(new AvoidVoidAsync())
..register(new AwaitOnlyFutures())
..registerDefault(new CamelCaseTypes())
..register(new CancelSubscriptions())
diff --git a/lib/src/rules/avoid_void_async.dart b/lib/src/rules/avoid_void_async.dart
new file mode 100644
index 0000000..c8f5120
--- /dev/null
+++ b/lib/src/rules/avoid_void_async.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:linter/src/analyzer.dart';
+
+const _desc = r'Avoid async functions that return void.';
+
+const _details = r'''
+
+**DO** mark async functions to return Future<void>.
+
+When declaring an async method or function which does not return a value,
+declare that it returns Future<void> and not just void.
+
+**BAD:**
+```
+void f() async {}
+void f2() async => null;
+```
+
+**GOOD:**
+```
+Future<void> f() async {}
+Future<void> f2() async => null;
+```
+
+''';
+
+class AvoidVoidAsync extends LintRule implements NodeLintRule {
+ AvoidVoidAsync()
+ : super(
+ name: 'avoid_void_async',
+ description: _desc,
+ details: _details,
+ group: Group.style);
+
+ @override
+ void registerNodeProcessors(NodeLintRegistry registry) {
+ final visitor = new _Visitor(this);
+ registry.addFunctionDeclaration(this, visitor);
+ registry.addMethodDeclaration(this, visitor);
+ }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+ final LintRule rule;
+
+ _Visitor(this.rule);
+
+ @override
+ void visitFunctionDeclaration(FunctionDeclaration node) {
+ if (_isAsync(node.element) && _isVoid(node.returnType)) {
+ rule.reportLint(node.name);
+ }
+ }
+
+ @override
+ void visitMethodDeclaration(MethodDeclaration node) {
+ if (_isAsync(node.element) && _isVoid(node.returnType)) {
+ rule.reportLint(node.name);
+ }
+ }
+
+ bool _isAsync(ExecutableElement element) {
+ if (element == null) {
+ return false;
+ }
+ return element.isAsynchronous || element.isGenerator;
+ }
+
+ bool _isVoid(TypeAnnotation typeAnnotation) =>
+ typeAnnotation?.type?.isVoid ?? false;
+}
diff --git a/test/mock_sdk.dart b/test/mock_sdk.dart
index 994ac41..c1d48ac 100644
--- a/test/mock_sdk.dart
+++ b/test/mock_sdk.dart
@@ -164,7 +164,8 @@
import 'dart:math';
-part 'stream.dart';
+class Stream<T> {}
+abstract class StreamTransformer<S, T> {}
class Future<T> {
factory Future.delayed(Duration duration, [T computation()]) => null;
@@ -176,8 +177,6 @@
''', const <_MockSdkFile>[
const _MockSdkFile('/lib/async/stream.dart', r'''
part of dart.async;
-class Stream<T> {}
-abstract class StreamTransformer<S, T> {}
''')
]);
diff --git a/test/rules/avoid_void_async.dart b/test/rules/avoid_void_async.dart
new file mode 100644
index 0000000..368a778
--- /dev/null
+++ b/test/rules/avoid_void_async.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `pub run test -N avoid_void_async`
+import 'dart:async';
+
+void main() {
+ () {}; // OK
+ () => null; // OK
+ () async {}; // OK
+ () async => null; // OK
+}
+
+void a() async {} // LINT
+void b() {} // OK
+Future<void> c() async {} // OK
+void d() async => null; // LINT
+void e() => null; // OK
+Future<void> f() async => null; // OK
+
+void g() async* {} // LINT
+void h() sync* {} // LINT
+Stream<void> i() async* {} // OK
+Iterable<void> j() sync* {} // OK
+
+void get k => null; // OK
+void get l async => null; // LINT
+
+void set m(_) => null; // OK
+set n(_) => null; // OK
+
+typedef void f1(int x); // OK
+
+typedef Future<void> f2(int x); // OK
+
+class Foo {
+ static void statica() async {} // LINT
+ static void staticb() {} // OK
+ static Future<void> staticc() async {} // OK
+
+ void a() async {} // LINT
+ void b() {} // OK
+ Future<void> c() async {} // OK
+ void d() async => null; // LINT
+ void e() => null; // OK
+ Future<void> f() async => null; // OK
+
+ void g() async* {} // LINT
+ void h() sync* {} // LINT
+ Stream<void> i() async* {} // OK
+ Iterable<void> j() sync* {} // OK
+
+ void get k => null; // OK
+ void get l async => null; // LINT
+
+ void operator |(_) async => null; // LINT
+ void operator &(_) => null; // OK
+}