The 8 new lints in Dart 2.19

Alexey Inkin
Flutter Senior
Published in
5 min readJan 28, 2023

Dart 2.19 is out on Jan 23. Surprisingly we do not see an announcement article in their Medium as usual, because the team is focused on Dart 3 alpha, so we should find new things ourselves. A nerd like me looks on lints first, and there are plenty.

collection_methods_unrelated_type

When we look for a value in a map, we pass a key like this:

final value = map[key];

The brackets here take any type although for a Map<K, V> a key can only be of type K, and passing a key of any other type will bring no fruits.

This code was OK before but now triggers the new collection_methods_unrelated_type lint:

final map = <String, int>{};
final n = map[7]; // Lint: collection_methods_unrelated_type

This is important because such a lookup may come from design errors. Notably we can still query for null:

final map = <String, int>{};
final n = map[null]; // Still no issue.

This is somewhat good because there is a common pattern to look for nullable keys knowing that will return null. This is used to avoid extra ifs.

This lint inspects not only maps and not only the bracket access. Read more abut this lint here.

combinators_ordering

When you import a file or a package, you can opt to only use some of its content so other names do not conflict with your classes:

import 'package:flutter/material.dart' show Container, Text;

The new combinators_ordering lint now checks that you list such members alphabetically. It will fire if we swap Container and Text above. Same works with hide keyword.

Wondering why this is important? Read all about the importance of sorting here.

dangling_library_doc_comments

Pub.dev generates documentation from packages that are uploaded to it. For instance, the app_state package has the following online documentation generated:
https://pub.dev/documentation/app_state/latest/app_state/app_state-library.html

You can see all classes listed below, and some paragraphs of text on top of that. This text comes from the library comment in the library file of the package.

The first doc comment in a library file is taken for that purpose, and this led to a weird habit of putting it above the first export like this:

/// My library comment.
export 'src/file.dart'; // Lint: dangling_library_doc_comments

But this comment has nothing to do with the export directive, and this is misleading.

So the new dangling_library_doc_comments lint now requires that you put a library directive below the first doc comment if your file starts with one, and the code above now triggers a lint. This is the fixed version:

/// My library comment.
library my_library;

export 'src/file.dart';

Ideally we want to omit my_library name because it is redundant and typos in it can lead to problems, but we can only do this if we set the minimal required version of Dart to 2.19. This would cut off the customers with older code, so do not hurry into that.

library_annotations

This one is similar. If you want an annotation to affect the entire library, you can attach it to arbitrary library directive like this:

@TestOn('browser') // Lint: library_annotations
import 'package:test/test.dart';

But again this is confusing, so library_annotations lint was made to force you to add such annotation on a library directive:

@TestOn('browser')
library my_library;

Unfortunately this lint behaves unexpectedly, and I could not make it fire on misuse of @Deprecated annotation which is more common. In my experiments, it only worked on @TestOn annotation from the official example, so I filed an issue. Hopefully it will work with more annotations.

unnecessary_library_directive

If a library directive does not have neither annotations nor a doc comment, it is useless. This lint makes you clean up such annotations. This is beneficial for older packages that had been using library my_library; because it was necessary in the old days. Now this fires the new unnecessary_library_directive lint.

implicit_call_tearoffs

If you have call() method in your class, you can call it just on the instance of the class:

class Addition {
int call(int a, int b) => a + b;
}

void fn() {
final add = Addition();
print(add.call(1, 2)); // Prints '3'.
print(add(1, 2)); // Prints '3'.
}

Note that both lines do the same. This can be used if a class encapsulates an algorithm and has the default method that it’s hard to come up with the name for. After all, you cannot just name it add() because you may have a hierarchy:

abstract class BinaryOperation {
int call(int a, int b);
}

class Addition extends BinaryOperation {
@override
int call(int a, int b) => a + b;
}

class Subtraction extends BinaryOperation {
@override
int call(int a, int b) => a - b;
}

If you want to put the reference to this method into a function variable, you can do it like this:

final int Function(int, int) operation1 = add.call;

It so happens that there is an older way for this which now triggers this new implicit_call_tearoffs lint:

final int Function(int, int) operation2 = add; // Lint: implicit_call_tearoffs

What this line does is it implicitly takes the reference to call method method and assigns it to the function. This is bad because you may have assigned an object to a function variable by accident.

unreachable_from_main

Dart had long been smart enough to flag unused private functions and local variables. Now it learned to flag unused global functions with the new unreachable_from_main lint. It can work across multiple files:

// main.dart
import 'file.dart';

void main() {}
// file.dart
void fn() {} // Lint: unreachable_from_main

This lint only fires when your code contains main() method. For libraries, it is OK to have unreferenced functions because they are designed to be imported by other packages.

use_string_in_part_of_directives

You never know which unusual features Dart supports until they make a lint to discourage them.

In Dart, a file sub.dart can be made a part of a main.dart by double-linking them like this:

// main.dart
part 'dir/sub.dart';

void main() {
_privateFunction();
}
// sub.dart
part of '../main.dart';

void _privateFunction();

This allows the two files to access private identifiers of each other because these files are considered to be a whole. main.dart should be included anywhere you want to access the content of either of the two, and sub.dart cannot be imported anywhere directly. This is commonly used with code generation.

A less common use is to make packages so that only one file contains all identifiers. This is mostly equivalent to exporting, so these two snippets do roughly the same:

// my_package.dart
export 'src/my_class.dart';

// src/my_class.dart
class MyClass {}
// my_package.dart
part 'src/my_class.dart';

// src/my_class.dart
part of '../my_package.dart';
class MyClass {}

The only practical difference is that my_package.dart can use private identifiers of my_class.dart which a library file should not do anyway.

It happens that there is an older way to do the same:

// my_package.dart
library my_package;

// src/my_class.dart
part of my_package; // Library identifier instead of a name in quotes.
class MyClass {}

A file can declare itself a part of a library by that library’s identifier instead of its file name. This makes library resolution non-straightforward because nothing is as simple as referencing a file explicitly by path, so this was deprecated, and the new lint use_string_in_part_of_directives now guards you against that.

The Earlier Lints of Dart 2.18

If you have missed my article on the 3 new lints in Dart 2.18, read it now.

Enabling the New Lints

Read this article on how to enable those new lints manually.

Alternatively, you can use my package total_lints which enables most of the lints for your project. I use it to avoid repeating the linter configuration for my projects.

--

--

Alexey Inkin
Flutter Senior

Google Developer Expert in Flutter. PHP, SQL, TS, Java, C++, professionally since 2003. Open for consulting & dev with my team. Telegram channel: @ainkin_com