close-sinks

Same as close_sinks in Dart lints

Path
lint/correctness/close-sinks
Default
On (recommended)
Origin
Dart lints
Rule
203 of 512
recommended

Close instances of dart:core Sink.

An unclosed sink can hold file handles, sockets, or buffered data and never signal completion to its listeners:

void log(File file) {
  final sink = file.openWrite(); // BAD: never closed
  sink.write('entry');
}

Future<void> log(File file) async {
  final sink = file.openWrite();
  sink.write('entry');
  await sink.close(); // GOOD
}

Fields and local variables whose type provably implements Sink (released by close) or dart:io Socket (released by close or destroy) are reported unless the file (fields) or enclosing function body (locals) holds an official valid-use/escape case. Constructor arguments and assignments into another local do not escape; unresolved types stay silent.

Invalid

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

class Logger {
  IOSink? _sink;

  void open(File file) {
    _sink = file.openWrite();
  }
}

void writeLocally(File file) {
  final sink = file.openWrite();
  sink.write('entry');
}

void controllerLocal() {
  final controller = StreamController<int>();
  controller.add(1);
}

void typedLocal(File file) {
  IOSink sink = file.openWrite();
  sink.writeln('entry');
}

void socketLocal(Socket socket) {
  Socket connection = socket;
  Socket other = getSocket();
  other.write('x');
  connection.write('y');
}

Socket getSocket() => throw UnimplementedError();
class CascadingBloc {
  final StreamController<int> cascadingEvents = StreamController<int>();

  void addTwice(int event) {
    this.cascadingEvents
      ..add(event)
      ..add(event);
  }
}

class SinkBox {
  final Sink<int> sink;
  SinkBox(this.sink);
  SinkBox.named(this.sink);
}

void constructorArgument() {
  final sink = StreamController<int>();
  SinkBox(sink);
}

void namedConstructorArgument() {
  final sink = StreamController<int>();
  SinkBox.named(sink);
}

void localCascade() {
  final sink = StreamController<int>()..add(1);
}

void sameNamedBlocks() {
  {
    final sink = StreamController<int>();
    sink.add(1);
  }
  {
    final sink = StreamController<int>();
    sink.close();
  }
}

class LeakedField {
  final sink = StreamController<int>();
}

class ReleasedField {
  final sink = StreamController<int>();
  void dispose() => sink.close();
}

class PassedField {
  final sink = StreamController<int>();
  void pass() => takeSink(sink);
}

void takeSink(Sink<int> value) {}

void functionArgument(void Function(Sink<int>) callback) {
  final sink = StreamController<int>();
  callback(sink);
}

class GenericSinkBox<T> {
  GenericSinkBox.named(Sink<T> sink);
}

void genericConstructorArgument() {
  final sink = StreamController<int>();
  GenericSinkBox<int>.named(sink);
}

class FactorySinkBox<T> {
  FactorySinkBox._(Sink<T> sink);

  factory FactorySinkBox() {
    final StreamController<T> sink = StreamController<T>.broadcast();
    return FactorySinkBox<T>._(sink);
  }
}

void dynamicCallback(dynamic callback) {
  final sink = StreamController<int>();
  callback(sink);
}

void functionCallback(Function callback) {
  final sink = StreamController<int>();
  callback(sink);
}

void assertedCallback(void Function(Sink<int>)? callback) {
  final sink = StreamController<int>();
  callback!(sink);
}

void immediateCallback() {
  final sink = StreamController<int>();
  ((Sink<int> value) {})(sink);
}

class CallbackBox {
  CallbackBox(this.callback);
  final void Function(Sink<int>) callback;

  void pass() {
    final sink = StreamController<int>();
    this.callback(sink);
  }
}

void getterCallback(CallbackBox box) {
  final sink = StreamController<int>();
  box.callback(sink);
}

class ChainedSink implements Sink<int> {
  void add(int value) {}
  void close() {}
  Sink<int> next() => this;
}

void chainedCascadeRelease() {
  final sink = ChainedSink();
  sink..next().close();
}

void indexedCallback(List<void Function(Sink<int>)> callbacks) {
  final sink = StreamController<int>();
  callbacks[0](sink);
}
Close this `Sink` instance.
4class Logger {
5 IOSink? _sink;
Close this `Sink` instance.
12void writeLocally(File file) {
13 final sink = file.openWrite();
Close this `Sink` instance.
17void controllerLocal() {
18 final controller = StreamController<int>();
Close this `Sink` instance.
22void typedLocal(File file) {
23 IOSink sink = file.openWrite();
Close this `Sink` instance.
28 Socket connection = socket;
29 Socket other = getSocket();
Close this `Sink` instance.
35class CascadingBloc {
36 final StreamController<int> cascadingEvents = StreamController<int>();
Close this `Sink` instance.
51void constructorArgument() {
52 final sink = StreamController<int>();
Close this `Sink` instance.
56void namedConstructorArgument() {
57 final sink = StreamController<int>();
Close this `Sink` instance.
61void localCascade() {
62 final sink = StreamController<int>()..add(1);
Close this `Sink` instance.
66 {
67 final sink = StreamController<int>();
Close this `Sink` instance.
76class LeakedField {
77 final sink = StreamController<int>();
Close this `Sink` instance.
85class PassedField {
86 final sink = StreamController<int>();
Close this `Sink` instance.
92void functionArgument(void Function(Sink<int>) callback) {
93 final sink = StreamController<int>();
Close this `Sink` instance.
101void genericConstructorArgument() {
102 final sink = StreamController<int>();
Close this `Sink` instance.
109 factory FactorySinkBox() {
110 final StreamController<T> sink = StreamController<T>.broadcast();
Close this `Sink` instance.
115void dynamicCallback(dynamic callback) {
116 final sink = StreamController<int>();
Close this `Sink` instance.
120void functionCallback(Function callback) {
121 final sink = StreamController<int>();
Close this `Sink` instance.
125void assertedCallback(void Function(Sink<int>)? callback) {
126 final sink = StreamController<int>();
Close this `Sink` instance.
130void immediateCallback() {
131 final sink = StreamController<int>();
Close this `Sink` instance.
139 void pass() {
140 final sink = StreamController<int>();
Close this `Sink` instance.
145void getterCallback(CallbackBox box) {
146 final sink = StreamController<int>();
Close this `Sink` instance.
156void chainedCascadeRelease() {
157 final sink = ChainedSink();
Close this `Sink` instance.
161void indexedCallback(List<void Function(Sink<int>)> callbacks) {
162 final sink = StreamController<int>();

Valid

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

void consume(IOSink sink) {}

class Logger {
  IOSink? _sink;

  void open(File file) {
    _sink = file.openWrite();
  }

  Future<void> dispose() async {
    await _sink?.close();
  }
}

class Pipe {
  final StreamController<String> controller = StreamController<String>();

  void dispose() => controller.close();
}

class SendingPipe {
  final StreamController<String> sendingController = StreamController<String>();

  void send(String value) => sendingController.add(value);
}

class Bloc {
  final StreamController<int> _events = StreamController<int>();

  void add(int event) => _events.add(event);
}

class NullableBloc {
  StreamController<int>? nullableEvents;

  void add(int event) => nullableEvents?.add(event);
}

class Injected {
  final IOSink sink;

  Injected(this.sink);
}

Future<void> closed(File file) async {
  final sink = file.openWrite();
  sink.write('entry');
  await sink.close();
}

IOSink returned(File file) {
  final sink = file.openWrite();
  return sink;
}

void passed(File file) {
  final sink = file.openWrite();
  consume(sink);
}

void cascaded(File file) {
  final sink = file.openWrite();
  sink
    ..write('a')
    ..close();
}

void destroyed(Socket socket) {
  final Socket connection = socketFor(socket);
  connection.destroy();
}

Socket socketFor(Socket socket) => socket;

void unresolved(dynamic file) {
  final sink = file.openWrite();
  sink.write('entry');
}

void closedFromClosure() {
  final sink = StreamController<int>();
  final dispose = () => sink.close();
  dispose();
}

void releaseLaterSection(File file) {
  final sink = file.openWrite();
  sink
    ..toString()
    ..close();
}

abstract class ResourceBase {
  final events = StreamController<int>();
}

class ResourceChild extends ResourceBase {
  void dispose() => events.close();
}

abstract class ThisResourceBase {
  final events = StreamController<int>();
}

class ThisResourceChild extends ThisResourceBase {
  void dispose() => this.events.close();
}

abstract class MixinResourceBase {
  final events = StreamController<int>();
}

mixin ResourceReleaser on MixinResourceBase {
  void dispose() => events.close();
}

abstract class ExtensionResourceBase {
  final events = StreamController<int>();
}

extension ResourceRelease on ExtensionResourceBase {
  void dispose() => events.close();
}

void siblingCases(int value) {
  switch (value) {
    case 1:
      final sink = StreamController<int>();
      sink.close();
    case 2:
      final sink = StreamController<int>();
      sink.close();
  }
}

void collectionScope() {
  final values = [for (var sink = 0; sink < 1; sink++) sink];
  final sink = StreamController<int>();
  sink.close();
}

void mapCollectionScope() {
  final values = {for (var sink = 0; sink < 1; sink++) sink: sink};
  final sink = StreamController<int>();
  sink.close();
}

void nestedFormal(File file, void callback(IOSink sink)) {
  final sink = file.openWrite();
  sink.close();
}

class ChainedReceiver {
  ChainedReceiver next() => this;
  void take(Sink<int> sink) {}
}

void chainedArgument(ChainedReceiver receiver) {
  final sink = StreamController<int>();
  receiver..next().take(sink);
}

final log = Object();

class MethodShadowsVariable {
  void log(Sink<int> sink) {}
  void write() {
    final sink = StreamController<int>();
    log(sink);
  }
}

void localFunctionShadowsVariable() {
  final take = Object();
  {
    void take(Sink<int> sink) {}
    final sink = StreamController<int>();
    take(sink);
  }
}

abstract class ThisExtensionResourceBase {
  final events = StreamController<int>();
}

extension ThisResourceRelease on ThisExtensionResourceBase {
  void dispose() => this.events.close();
}

class LocalFunctionShadowsField {
  LocalFunctionShadowsField(this.report);
  final void Function(Object) report;

  void run() {
    void report(Sink<int> value) {}
    final sink = StreamController<int>();
    report(sink);
  }
}

Configure close-sinks

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

falcon.jsonjson
{
  "linter": {
    "rules": {
      "correctness": {
        "close-sinks": "error"
      }
    }
  }
}

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

example.dartdart
// falcon-ignore lint/correctness/close-sinks: <reason>