null-closures

Same as null_closures in Dart lints

Path
lint/correctness/null-closures
Default
On (recommended)
Origin
Dart lints
Rule
302 of 512
recommended

Reject literal null in the closure slots of curated SDK APIs.

Mirrors the official null_closures lists: the constructors Future(), Future.microtask, Future.sync, Timer(), Timer.periodic, and List.generate; statics on classes named Future (doWhile, forEach, wait(cleanUp:)) and Timer (run), regardless of their library; and instance methods of Iterable, Map, List, Set, Queue, String, and Future invoked on a receiver whose static type is, or has as a supertype, that SDK class (project overrides of those methods included). Invocations without an explicit target — the top-level scheduleMicrotask, implicit-this calls, and cascade sections — are never checked, as in the official lint. Parenthesized nulls do not match. Named arguments are reported as a whole; positional indexes count named arguments too, matching analyzer source order. Static callback APIs match the receiver's class name, even outside the SDK.

Invalid

example.dartdart
import 'dart:async';
import 'dart:collection';

Iterable<int> getValues() => [];

void bad(
  Iterable<int> values,
  Map<String, int> map,
  List<int> list,
  Set<int> set,
  Queue<int> queue,
  String text,
  Future<int> future,
) {
  values.any(null);
  getValues().any(null);
  values.firstWhere(null, orElse: null);
  values.fold(0, null);
  map.putIfAbsent('x', null);
  list.removeWhere(null);
  set.retainWhere(null);
  queue.removeWhere(null);
  text.replaceAllMapped('x', null);
  text.splitMapJoin('x', onMatch: null);
  future.then(null, onError: null);
  Future.microtask(null);
  Future.doWhile(null);
  Timer.run(null);
  List.generate(2, null);
  new Future.sync(null);
}

extension UnrelatedMap on int {
  Object? map(Object? callback) => null;
}

void unrelatedExtensionDoesNotSuppress(List<int> values) {
  values.map(null);
}

class ConstructorBase {
  ConstructorBase(Object? value);
}

class ConstructorInitializers extends ConstructorBase {
  ConstructorInitializers(List<int> values)
      : field = values.map(null),
        assert(values.any(null)),
        super(values.where(null));

  ConstructorInitializers.redirect(List<int> values)
      : this(values.map(null));

  final Iterable<int> field;
}

// The official lint checks the static type and all its supertypes, so a
// project override of an SDK callback method is flagged too.
abstract class CustomIterable implements Iterable<int> {
  @override
  bool any(Object? callback) => false;
}

void overrides(CustomIterable values) {
  values.any(null);
}
Pass a closure instead of `null`.
14) {
15 values.any(null);
Pass a closure instead of `null`.
15 values.any(null);
16 getValues().any(null);
Pass a closure instead of `null`.
16 getValues().any(null);
17 values.firstWhere(null, orElse: null);
Pass a closure instead of `null`.
16 getValues().any(null);
17 values.firstWhere(null, orElse: null);
Pass a closure instead of `null`.
17 values.firstWhere(null, orElse: null);
18 values.fold(0, null);
Pass a closure instead of `null`.
18 values.fold(0, null);
19 map.putIfAbsent('x', null);
Pass a closure instead of `null`.
19 map.putIfAbsent('x', null);
20 list.removeWhere(null);
Pass a closure instead of `null`.
20 list.removeWhere(null);
21 set.retainWhere(null);
Pass a closure instead of `null`.
21 set.retainWhere(null);
22 queue.removeWhere(null);
Pass a closure instead of `null`.
22 queue.removeWhere(null);
23 text.replaceAllMapped('x', null);
Pass a closure instead of `null`.
23 text.replaceAllMapped('x', null);
24 text.splitMapJoin('x', onMatch: null);
Pass a closure instead of `null`.
24 text.splitMapJoin('x', onMatch: null);
25 future.then(null, onError: null);
Pass a closure instead of `null`.
24 text.splitMapJoin('x', onMatch: null);
25 future.then(null, onError: null);
Pass a closure instead of `null`.
25 future.then(null, onError: null);
26 Future.microtask(null);
Pass a closure instead of `null`.
26 Future.microtask(null);
27 Future.doWhile(null);
Pass a closure instead of `null`.
27 Future.doWhile(null);
28 Timer.run(null);
Pass a closure instead of `null`.
28 Timer.run(null);
29 List.generate(2, null);
Pass a closure instead of `null`.
29 List.generate(2, null);
30 new Future.sync(null);
Pass a closure instead of `null`.
37void unrelatedExtensionDoesNotSuppress(List<int> values) {
38 values.map(null);
Pass a closure instead of `null`.
46 ConstructorInitializers(List<int> values)
47 : field = values.map(null),
Pass a closure instead of `null`.
47 : field = values.map(null),
48 assert(values.any(null)),
Pass a closure instead of `null`.
48 assert(values.any(null)),
49 super(values.where(null));
Pass a closure instead of `null`.
51 ConstructorInitializers.redirect(List<int> values)
52 : this(values.map(null));
Pass a closure instead of `null`.
64void overrides(CustomIterable values) {
65 values.any(null);

Valid

example.dartdart
import 'dart:async';

void good(
  Iterable<int> values,
  Future<int> future,
  Completer<int> completer,
  dynamic maybeNull,
) {
  values.any((value) => value > 0);
  values.firstWhere((value) => true, orElse: () => 0);
  future.then((value) => value + 1, onError: (error) => 0);
  completer.complete(null);
  Future.microtask(() => 1);
  scheduleMicrotask(() {});
  values.any(maybeNull);
}

class Iterable<T> {
  bool any(Object? callback) => false;
}

void shadowed(Iterable<int> values) {
  values.any(null);
}

extension CustomAny on String {
  bool any(Object? callback) => false;
}

void overrides(String text) {
  text.any(null);
}

// Neither `Future.delayed` nor the top-level `scheduleMicrotask` is on the
// official list, and cascaded calls have no target to check.
void unlisted(List<int> list) {
  Future.delayed(Duration.zero, null);
  scheduleMicrotask(null);
  list..removeWhere(null);
}

class AsyncShadow {
  final FutureFactory Future = FutureFactory();
}

class FutureFactory {
  void microtask(Object? callback) {}
}

void prefixedSdkShadow(AsyncShadow async) {
  async.Future.microtask(null);
}

Configure null-closures

Set its severity in falcon.json. See the configuration guide.

falcon.jsonjson
{
  "linter": {
    "rules": {
      "correctness": {
        "null-closures": "error"
      }
    }
  }
}

Or silence one occurrence with a suppression comment; the reason is required.

example.dartdart
// falcon-ignore lint/correctness/null-closures: <reason>