async_job_lints
async_job_lints — плагин анализатора для кода
на async_job. Он сообщает об ошибках,
которые компилируются, работают и часто проходят первый тест: ожидание
в уборке, которое так и не случается; ресурс, потерянный при отмене; catch,
который сбрасывает исправное устройство; тест, который не двигает свои
поддельные часы; ожидание, которое отмена не прерывает. Каждое правило
ссылается на страницу async_job, где ошибка показана рядом с правильным
способом.
Коду на solo или flutter_solo нужен вместо него solo_lints: он включает эти правила и добавляет правила контроллера. Называйте один из двух плагинов, а не оба: сервер анализа разрешает версии всех плагинов вместе, и если они не сходятся, не работает ни один плагин.
Установка
Заголовок раздела «Установка»Назовите плагин в analysis_options.yaml в корне пакета или рабочего
пространства:
plugins: async_job_lints: ^0.1.1Плагин сервер анализа скачивает сам, поэтому в pubspec.yaml он
не зависимость. Плагинам нужен Dart 3.10 или новее. Его диагностики показывают
dart analyze и редактор. Запускайте dart analyze для корня пакета или для
отдельных файлов: для каталога ниже корня, например dart analyze lib, он
не показывает ни одной. flutter analyze показывает их на Flutter 3.38,
но начиная с Flutter 3.44 не показывает ни одной; во Flutter-пакете плагин
проверяют через dart analyze. Правку раздела plugins редактор видит после
перезапуска своего сервера анализа.
Предупреждения включаются, как только плагин назван. Два линта,
catch_without_check и bare_await, включаются по имени:
plugins: async_job_lints: version: ^0.1.1 diagnostics: catch_without_check: true bare_await: trueВместо true правилу можно дать уровень: error, warning или info.
bare_await: warning включает линт как предупреждение, и тогда dart analyze
на нём падает, как на любом предупреждении. false выключает предупреждение.
Диагностику глушат именем плагина перед именем правила:
// ignore: async_job_lints/ctx_in_cleanup строкой выше или
// ignore_for_file: async_job_lints/ctx_in_cleanup на весь файл.
Быстрые исправления предлагает редактор. dart fix их не применяет: он
применяет исправления самого анализатора, а не плагинов.
На Dart 3.12 dart analyze в пакете, где назван плагин, печатает каждую
диагностику самого анализатора дважды. Dart 3.13 печатает её один раз.
Библиотека package:async_job_lints/plugin_api.dart — для плагинов
анализатора, построенных поверх этого, как solo_lints, а не для кода, который
плагин проверяет.
Правила
Заголовок раздела «Правила»| Правило | Вид | Сообщает |
|---|---|---|
ctx_in_cleanup |
предупреждение | член контекста, который ждёт, вызван в уборке |
release_on_the_call |
предупреждение | освобождение зарегистрировано строкой ниже вызова, который приносит ресурс |
catch_without_check |
линт | широкий catch в теле, которое действует, не спросив задачу |
sync_fake_async |
предупреждение | async-колбэк fakeAsync |
bare_await |
линт | await в теле задачи, который идёт мимо контекста |
ctx_in_cleanup
Заголовок раздела «ctx_in_cleanup»ctx.onDispose(() async { await ctx.join(() => device.flush()); // ctx_in_cleanup});Уборка идёт после тела, где join бросает StateError, поэтому буфер остаётся
несброшенным, чем бы задача ни кончилась. Ждите там простым await. Правило
смотрит в функции, переданные в ctx.onDispose и ctx.onDiscard
и в dispose: и discard: методов join, abandonable и run, — литералы,
а также локальные функции и final-переменные с литералом, переданные
по имени, — и сообщает о членах, которые там бросают: о контрольных точках,
onCancel, start и each. onDispose, onDiscard, disown, log
и unattended оно не трогает. В контрольные точки входят и те, что solo
добавляет своему контексту, например emit. Подробнее в разделе
«dispose для того, что задача оставляет себе».
release_on_the_call
Заголовок раздела «release_on_the_call»final db = await ctx.join(Database.open);ctx.onDispose(db.close); // release_on_the_callОтмена во время открытия бросает раньше второй строки, и открытая база
теряется. Передайте освобождение вызову, который приносит ресурс:
ctx.join(Database.open, dispose: (db) => db.close()). Правило берёт join,
abandonable и run без своего освобождения, за которыми сразу идёт
onDispose или onDiscard, упоминающий переменную. Подробнее в разделе
«Регистрация по приходу».
Быстрое исправление удаляет onDispose или onDiscard и передаёт освобождение
вызову как dispose: или discard:, так что строки выше превращаются
в ctx.join(Database.open, dispose: (db) => db.close()). Исправление
предлагается, когда освобождение — отрыв метода переменной, как db.close, или
литерал функции без параметров.
catch_without_check
Заголовок раздела «catch_without_check»try { await ctx.join(hw.open); await ctx.join(() => hw.setZoom(zoom));} on Object { // catch_without_check await hw.reset(); rethrow;}Cancelled реализует Exception, поэтому catch без on или с on
на Object, Exception или Outcome ловит его вместе с ошибками, ради
которых его писали, и отменённая задача сбрасывает камеру, которую никто
не ломал. Сделайте ctx.check() первой строкой catch. Правило смотрит в тело
задачи, в объявленные в нём локальные функции и в действия, которых тело ждёт
через join, abandonable и uncancellable; уборку и работу без присмотра
оно не трогает. Правило молчит, когда блок try ничего не ждёт и не доходит
до контрольной точки, а также когда catch начинается с контрольной точки
после любых строк if (...) rethrow;, начинается
с if (ctx.job.isCancelled) rethrow; или кончается rethrow;, ничего
не ожидая и не задевая контрольных точек по пути, — так поступают синхронное
освобождение и запись в лог. Это линт, а не предупреждение: о catch, которому
и при отмене нужно дождаться освобождения, правило тоже сообщает. Там
ctx.check() первой строкой оставил бы ресурс неосвобождённым, и такой catch
сохраняет свои строки под // ignore:. Подробнее в разделе
«Перехват ошибок операции».
Быстрое исправление вставляет ctx.check(); первой строкой catch.
sync_fake_async
Заголовок раздела «sync_fake_async»fakeAsync((async) async { // sync_fake_async async.elapse(const Duration(milliseconds: 10)); await job.cancel(); expect(job.outcome, isA<Cancelled>());});await в колбэке приостанавливает его, и двигать поддельные часы больше
некому: тест висит или кончается зелёным, ничего не выполнив. Оставьте колбэк
синхронным, двигайте часы через elapse и читайте job.outcome. Правило
сообщает об async колбэка, переданного в fakeAsync или FakeAsync.run.
Подробнее в разделе
«Отменённое открытие».
bare_await
Заголовок раздела «bare_await»final job = Job<void>((ctx) async { while (true) { use(await ctx.abandonable(database.readAll)); await Future<void>.delayed(const Duration(seconds: 1)); // bare_await }});Простой await отмену не слышит: задачу отменили, а тело всё пережидает
задержку и переходит к следующей строке. Ждите через контекст: ctx.pause
вместо Future.delayed, ctx.each вместо await for — о нём правило сообщает
своим сообщением, — а для вызова ctx.join или ctx.abandonable. Они
различаются тем, что отмена делает с вызовом. join — для вызова, который
должен закончиться: он принимает отмену сразу, так что onCancel может
остановить вызов, ждёт, пока вызов вернётся, и только тогда бросает.
abandonable перестаёт ждать при отмене и оставляет вызов в полёте. Шаги,
которые не должны слышать отмену, пока не закончатся, кладите
в ctx.uncancellable. Подробнее — в разделе
«Контекст коротко»
справочника async_job и в разделе
«Дать времени пройти»
его страницы о времени.
Правило смотрит в тело задачи — литерал функции или функцию верхнего уровня,
первый параметр которой JobContext, в том числе с именем _. Функции,
объявленные внутри тела, оно не трогает: действия join и uncancellable,
уборку, работу unattended, локальные функции. О локальной функции правило
сообщает там, где тело ждёт её вызова. Правило молчит об ожидании через член
контекста этого тела: join, abandonable, uncancellable, pause, run
и runAll. Молчит о value, done и cancel() ребёнка, которого приносит
ctx.each или которого тело раньше передало в ctx.start в финальной
переменной или финальном поле, и о Future.wait, [...].wait и (...).wait
таких future, об условном выражении из них и о final-переменной, которая
держит такое. Вызов, который передаёт контекст параметру типа JobContext,
позиционному или именованному, ждёт через него. Освобождение сразу после
ctx.disown(v), следующей строкой или под if (ctx.disown(v)), должно
выполниться и при отмене. Правило молчит о значении, которое не future,
в finally, в catch с on Cancelled и в catch, который ловит отмену, —
без типа, on Object, on Exception или on Outcome, — и кончается
rethrow;, а до него из него не уходят ни return, ни break или continue.
Проверки в начале catch мало: она слышит отмену, которая уже пришла, а не ту,
что придёт во время ожидания после неё. Тело задачи с cancellable: false,
литералом или константой, правило не трогает, как и функцию верхнего уровня,
все задачи которой созданы так.
Цикл простых ожиданий внутри одного ctx.join(() async { ... }) правило
не замечает, хотя отмена не прерывает и его. Ребёнок в var, в параметре или
в нефинальном поле и ... в списке Future.wait правило называет, хотя они
ждут через контекст. Называет оно и локальную функцию, которая сама ждёт через
контекст, — там, где тело ждёт её вызова, — и future контекста, продолженный
timeout или then, Future.any, отображённый список вроде
jobs.map(ctx.run) и переменную шаблона.
В контроллере solo задача, которая ждёт задачу из очереди своего же
контроллера, ждёт саму себя: ожидаемая задача стоит в очереди за ней. Это
правило сообщает о таком ожидании, как о любом простом. solo_lints сообщает
о нём как await_own_queue, сообщением об очереди, и bare_await там молчит.
Это линт, а не предупреждение: тесты ждут в телах намеренно, чтобы отменить
задачу посередине. Проект, который держит на нём CI, включает его как
bare_await: warning и ставит // ignore_for_file: async_job_lints/bare_await
в файлы тестов, где тело стоит на простом ожидании.
Плагин работает на Dart 3.10 и новее, с analyzer от 12.1 до 14. SDK Dart,
которому нужен analyzer 15, эту версию не загружает, и её правила молчат,
пока выпуск не поднимет границу.
Типы async_job правила узнают по именам и библиотеке, откуда те пришли,
и от пакета не зависят; так же они узнают контрольные точки, которые solo
добавляет контексту. На версии этих пакетов, где имя сменилось, правило молчит,
а не сообщает о правильном коде. Правила знают и wait — имя abandonable
до async_job 1.0, — поэтому код на 0.x проверяют так же.