The 8 new lints in Dart 2.19
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 if
s.
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.