Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

New lint: prefer_void_to_null #1100

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
* new lint: `prefer_void_to_null`

# 0.1.58

* roll-back to explicit uses of `new` and `const` to be compatible w/ VMs running `--no-preview-dart-2`
Expand Down
1 change: 1 addition & 0 deletions example/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ linter:
- prefer_iterable_whereType
- prefer_single_quotes
- prefer_typing_uninitialized_variables
- prefer_void_to_null
- public_member_api_docs
- recursive_getters
- slash_for_doc_comments
Expand Down
2 changes: 2 additions & 0 deletions lib/src/rules.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import 'package:linter/src/rules/prefer_is_not_empty.dart';
import 'package:linter/src/rules/prefer_iterable_whereType.dart';
import 'package:linter/src/rules/prefer_single_quotes.dart';
import 'package:linter/src/rules/prefer_typing_uninitialized_variables.dart';
import 'package:linter/src/rules/prefer_void_to_null.dart';
import 'package:linter/src/rules/pub/package_names.dart';
import 'package:linter/src/rules/public_member_api_docs.dart';
import 'package:linter/src/rules/recursive_getters.dart';
Expand Down Expand Up @@ -221,6 +222,7 @@ void registerLintRules() {
..register(new PublicMemberApiDocs())
..register(new PreferSingleQuotes())
..register(new PreferTypingUninitializedVariables())
..register(new PreferVoidToNull())
..register(new PubPackageNames())
..register(new RecursiveGetters())
..registerDefault(new SlashForDocComments())
Expand Down
104 changes: 104 additions & 0 deletions lib/src/rules/prefer_void_to_null.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:linter/src/analyzer.dart';

const _desc =
r"Don't use the Null type, unless you are positive that you don't want void.";

const _details = r'''

**DO NOT** use the type Null where void would work.

**BAD:**
```
Null f() {}
Future<Null> f() {}
Stream<Null> f() {}
f(Null x) {}
```

**GOOD:**
```
void f() {}
Future<void> f() {}
Stream<void> f() {}
f(void x) {}
```

Some exceptions include formulating special function types:

```
Null Function(Null, Null);
```

and for making empty literals which are safe to pass into read-only locations
for any type of map or list:

```
<Null>[];
<int, Null>{};
```
''';

class PreferVoidToNull extends LintRule implements NodeLintRule {
PreferVoidToNull()
: super(
name: 'prefer_void_to_null',
description: _desc,
details: _details,
group: Group.errors,
maturity: Maturity.experimental);

@override
void registerNodeProcessors(NodeLintRegistry registry) {
final visitor = new _Visitor(this);
registry.addSimpleIdentifier(this, visitor);
}
}

class _Visitor extends SimpleAstVisitor<void> {
final LintRule rule;

_Visitor(this.rule);

@override
void visitSimpleIdentifier(SimpleIdentifier id) {
final element = id.staticElement;
if (element is ClassElement && element.type.isDartCoreNull) {
final typeName =
id.parent is PrefixedIdentifier ? id.parent.parent : id.parent;

final parent = typeName.parent;

// Null Function()
if (parent is GenericFunctionType) {
return;
}

// Function(Null)
if (parent is SimpleFormalParameter &&
parent.parent is FormalParameterList &&
parent.parent.parent is GenericFunctionType) {
return;
}

// <Null>[] or <Null, Null>{}
if (parent is TypeArgumentList) {
final literal = parent.parent;
if (literal is ListLiteral && literal.elements.isEmpty) {
return;
} else if (literal is MapLiteral && literal.entries.isEmpty) {
return;
}
}

rule.reportLintForToken(id.token);
}
}
}
137 changes: 137 additions & 0 deletions test/rules/prefer_void_to_null.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// 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 prefer_void_to_null`

// TODO(mfairhurst) test void with a prefix, except that causes bugs.
// TODO(mfairhurst) test defining a class named Null (requires a 2nd file)

import 'dart:async';
import 'dart:core';
import 'dart:core' as core;

void void_; // OK
Null null_; // LINT
core.Null core_null; // LINT
Future<void> future_void; // OK
Future<Null> future_null; // LINT
Future<core.Null> future_core_null; // LINT

void void_f() {} // OK
Null null_f() {} // LINT
core.Null core_null_f() {} // LINT
f_void(void x) {} // OK
f_null(Null x) {} // LINT
f_core_null(core.Null x) {} // LINT

void Function(Null) voidFunctionNull; // OK
Null Function() nullFunctionVoid; // OK
Future<Null> Function() FutureNullFunction; // LINT
void Function(Future<Null>) voidFunctionFutureNull; // LINT

usage() {
void void_; // OK
Null null_; // LINT
core.Null core_null; // LINT
Future<void> future_void; // OK
Future<Null> future_null; // LINT
Future<core.Null> future_core_null; // LINT

future_void.then<Null>((_) {}); // LINT
future_void.then<void>((_) {}); // OK
}

void inference() {
final _null = null; // OK
final nullReturnInferred = () {}; // OK
final nullInferred = nullReturnInferred(); // OK
}

void emptyLiterals() {
<Null>[]; // OK
<Null>[null]; // LINT
<void>[]; // OK
<void>[null]; // OK
<int, Null>{}; // OK
<String, Null>{}; // OK
<Object, Null>{}; // OK
<Null, int>{}; // OK
<Null, String>{}; // OK
<Null, Object>{}; // OK
<Null, Null>{}; // OK
<int, Null>{1: null}; // LINT
<String, Null>{"foo": null}; // LINT
<Object, Null>{null: null}; // LINT
<Null, int>{null: 1}; // LINT
<Null, String>{null: "foo"}; // LINT
<Null, Object>{null: null}; // LINT
<Null, // LINT
Null>{null: null}; // LINT
<int, void>{}; // OK
<String, void>{}; // OK
<Object, void>{}; // OK
<void, int>{}; // OK
<void, String>{}; // OK
<void, Object>{}; // OK
<void, void>{}; // OK
<int, void>{1: null}; // OK
<String, void>{"foo": null}; // OK
<Object, void>{null: null}; // OK
<void, int>{null: 1}; // OK
<void, String>{null: "foo"}; // OK
<void, Object>{null: null}; // OK
<void, void>{null: null}; // OK

// TODO(mfairhurst): is it worth handling more complex literals?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of "complex literals" are you thinking of?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this: <List<Null>, Map<Null, Null>>{}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How often does that arise in practice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is very rare. I'm not sure how to search for it.

}

variableNamedNull() {
var Null; // OK
return Null; // OK
}

parameterNamedNull(Object Null) {
Null; // OK
}

class AsMembers {
void void_; // OK
Null null_; // LINT
core.Null core_null; // LINT
Future<void> future_void; // OK
Future<Null> future_null; // LINT
Future<core.Null> future_core_null; // LINT

void void_f() {} // OK
Null null_f() {} // LINT
core.Null core_null_f() {} // LINT
f_void(void x) {} // OK
f_null(Null x) {} // LINT
f_core_null(core.Null x) {} // LINT

void usage() {
void void_; // OK
Null null_; // LINT
core.Null core_null; // LINT
Future<void> future_void; // OK
Future<Null> future_null; // LINT
Future<core.Null> future_core_null; // LINT

future_void.then<Null>((_) {}); // LINT
future_void.then<void>((_) {}); // OK
}

parameterNamedNull(Object Null) {
Null; // OK
}

variableNamedNull() {
var Null; // OK
return Null; // OK
}
}

class MemberNamedNull {
final Null = null; // OK
}