lint-and-style-config について
Dart と Flutter プロジェクトの lint・スタイル設定を保守するスキルです。ルールセットの固定、アナライザーの動作、抑制の扱い、解析だけでなくビルドも行う CI ゲートを説明します。
主な機能
- 01very_good_analysis のバージョン固定
- 02アナライザーと lint ルールの設定
- 03ルールの有効性確認
- 04生成ファイルとカバレッジ除外の同期
- 05抑制の制限と CI ビルドの必須化
活用シーン
- 01Dart lint ルールの追加や調整
- 02SDK またはルールセットの更新
- 03アナライザー設定が無効な原因の調査
- 04解析・フォーマット・ビルドゲートの設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ zakariaf/ flutter-skills --skill lint-and-style-configインストール後、Claude Code で/lint-and-style-configを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples/analysis_options.yamlcallback_future_hole.dartreferences/config-mechanics.mdrule-catalog.mdscripts/lint-gates.shverify-include-pin.sh詳細
Lint とスタイル設定
スタイルは好みではなく、ツールチェーンによって適用されます。空白は dart format が管理し、正確性はアナライザーが管理します。また、見逃すと問題を引き起こす少数のバグクラスを、ビルド失敗となるエラーに昇格させます。このスキルは analysis_options.yaml 自体と、それが実際に機能するかどうかを決める仕組みを対象とします。このファイルの編集、lint の追加や抑制、SDK/ルールセットの更新時に適用してください。
作業内容に応じて、次のリファレンスを確認してください。
references/rule-catalog.md— 昇格対象のすべてのルールとそのバグクラス、意図的なレベル引き下げ、議論のあるpublic_member_api_docsの判断、複雑度制限の表。references/config-mechanics.md— include のピン留めで陥りやすい問題、errors:による再ランク付けとlinter:による有効化、sealed switch の analyze と compile の違い、カバレッジのミラーリング、riverpod_lint プラグイン、抑制の規律。
examples/analysis_options.yaml をパッケージのルートにコピーし、初期設定として使用してください。PR の前に scripts/verify-include-pin.sh と scripts/lint-gates.sh を実行してください。
絶対に守るルール
- 空白を決めるのは
dart formatだけです。 手動でフォーマットしたり、結果に異議を唱えたりしてはいけません。正当な理由をコメントに記した手組みの表の周囲を除き、// dart format offは使用しないでください。CI ではdart format --set-exit-if-changed .を実行します。 - バージョン番号付きの include をピン留めして、
very_good_analysisをベースにしてください。 裸のanalysis_options.yamlは使用しないでください。pub upgradeによって、何がエラーと見なされるかが予告なく変わってはいけません。VGA はすでにstrict-casts、strict-inference、strict-raw-typesを設定しています。language:ブロックに重複して記述しないでください(冗長な設定はずれが生じ、矛盾の原因になります)。 - info も失敗です。 CI では
dart analyze --fatal-infos --fatal-warningsを実行します。未対応の info が蓄積するとノイズになり、実際の問題が見えにくくなります。 - エラーに昇格するのは、見逃すと問題を引き起こすバグクラスだけです。 Future の破棄、失敗の握りつぶし、await 後のデッドコンテキスト、リソースリーク、dynamic 境界の型の抜け穴、網羅性のない enum switch、
printが該当します。スタイル上の好みは対象外です。 errors:による昇格が機能するのは、ベースのルールセットですでにそのルールが有効な場合だけです。errors:は既存の診断のランクを変更するもので、無効なルールを有効にはできません。VGA にないルールを有効にするには、まずlinter: rules:に追加してください。linter: rules:でリスト形式とマップ形式を混在させてはいけません。 同じブロック内に- ruleの項目とrule: trueの項目があると、パースエラーになります。アナライザーが壊れていてもビルドは成功してしまいます。ベースルールを無効にするには、errors: <rule>: ignoreを使用してください。- CI では解析だけでなく、ビルドも実行してください。 sealed switch の網羅性チェックは lint ではなくコンパイルエラーです。
errors:の上書きで抑制できるとは限らず、dart compileに到達するコードパスでは失敗します。解析だけのゲートでは、ビルドできないコードでも成功する可能性があります。 - 生成コードを除外し、カバレッジのフィルタリングにも同じ glob を正確に反映してください。 そうしないと生成ファイルがカバレッジの数値を薄め、意味のないものになります。
- 抑制は行単位に限り、同じ行または直前の行に理由を記載してください。 昇格対象のルールに
// ignore_for_file:を使ってはいけません。また、アーキテクチャ/インポートのゲートに対する抑制も禁止です。コードを修正することを優先してください。 custom_lintは追加しないでください。 riverpod_lint 3.x はファーストパーティのプラグインシステム上で動作し、通常のflutter analyzeで診断が実行されます。
include と、すべてを知らぬ間に無効化する落とし穴
include: package:very_good_analysis/analysis_options.10.3.0.yaml
解決済みのパッケージ内にそのファイル名が存在しない場合、warning • include_file_not_found が 1 件表示され、ルールが一切ない状態で解析が実行されます。何もチェックしないビルドでも成功します。
SDK/VGA を更新するコミットと同じコミット内で include のファイル名も更新し、ファイルが存在することを確認してください(ls ~/.pub-cache/hosted/pub.dev/very_good_analysis-*/lib/)。更新のたびに、既知の違反が引き続きエラーになることを確認してください。成功表示を鵜呑みにしてはいけません。scripts/verify-include-pin.sh が存在確認を自動化します。
無視されるバグをエラーに昇格する
analyzer:
errors:
unawaited_futures: error
discarded_futures: error
empty_catches: error
avoid_catches_without_on_clauses: error
only_throw_errors: error
throw_in_finally: error
use_build_context_synchronously: error
cancel_subscriptions: error
close_sinks: error # VGA ships this at `ignore`; re-promoted
avoid_dynamic_calls: error
always_declare_return_types: error
cast_nullable_to_non_nullable: error
exhaustive_cases: error
avoid_print: error
avoid_slow_async_io: error
それぞれ、個人開発者が読み飛ばす情報レベルの波線を、ビルド失敗に変えます。各ルールのバグの種類と、意図的に warning/ignore のままにしているルール(deprecated_member_use、invalid_annotation_target、lines_longer_than_80_chars)については、references/rule-catalog.md を参照してください。
errors: は優先度を変更し、linter: rules: は有効化する
# Promoting works ONLY because VGA already enables these rules (close_sinks even
# at severity `ignore` counts as enabled — the override resurrects it).
analyzer:
errors:
close_sinks: error
# To enable a rule VGA does NOT ship, add it here FIRST, then optionally promote.
linter:
rules:
some_rule_vga_omits: true
実際に違反を起こして検証してください。昇格が適用されたと決めつけてはいけません。出荷時の設定では、昇格するすべてのルールが VGA で有効になっているため、linter: rules: ブロックは不要です。
sealed の網羅性チェックはコンパイルエラーであり、Lint ではない
sealed class SaveResult {}
class Saved extends SaveResult {}
class SaveFailed extends SaveResult {}
// Missing a case is `non_exhaustive_switch_statement` — a COMPILE error.
// (A switch *expression* raises `non_exhaustive_switch_expression` instead.)
String describe(SaveResult r) {
switch (r) {
case Saved():
return 'saved';
case SaveFailed():
return 'failed';
}
}
exhaustive_cases が対象とするのは、enum/static const の集合だけです。失敗とモードの型は sealed としてモデル化し、新しいケースが追加されたとき、default に落ちて見過ごされるのではなく、すべての呼び出し箇所でビルドが失敗するようにしてください。errors: による上書きで網羅性診断を抑制できるとは限りません。また、dart analyze をどうにか通せた場合でも dart compile は失敗します。そのため、CI では必ずビルドし、解析だけで済ませてはいけません。dart3-idioms-and-coding-standards を参照してください。
除外設定をカバレッジにも反映する
analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "**/*.drift.dart"
- "**/generated_plugin_registrant.dart"
- "test/drift/generated/**" # drift schema snapshots — never reformat/fix
同一の glob をカバレッジのフィルタリングにも適用する必要があります(lcov --remove またはレポーターの除外設定)。除外設定とカバレッジフィルターの不一致は、数値を実態より高く見せます。testing-strategy と codegen-and-toolchain を参照してください。
Lint では検出できない穴
// WRONG — flagged by NOTHING. The arrow returns the Future (so discarded_futures
// is satisfied), but the target is VoidCallback, so Dart drops it silently.
onPressed: () => repository.save(note),
// RIGHT — a void-returning intent method the callback holds safely.
class NoteController {
void saveNow(Note note) => unawaited(_repo.save(note).catchError(_report));
}
// onPressed: () => controller.saveNow(note),
対策は構造によって行い、規律に頼ってはいけません。アナライザーが問題なしと判定しても、処理が実行された証拠にはなりません。重大度が最も高い障害はどのルールでも検出できず、手動での確認が必要です。examples/callback_future_hole.dart と async-safety を参照してください。
アンチパターン
include: package:very_good_analysis/analysis_options.yamlをそのまま使う — pub のアップグレードでルールセットが予告なく変わり、固定したファイルがないとルールが予告なく無効になります。バージョンを固定して検証してください。language:ブロックでstrict-casts/strict-raw-typesを再宣言する — VGA が設定しているため、複製した設定はずれて、いずれ矛盾します。- VGA で有効になっていないルールを昇格する —
errors:では無効なルールを復活させられないため、昇格しても何も起きません。 - 1 つの
linter: rules:ブロックで- ruleとrule: falseを混在させる — 解析エラーが発生し、アナライザーが壊れてもビルドは成功します。 - 解析だけを行う CI — sealed switch の網羅性が不十分で、
dart compileに失敗するコードも通過します。 - 除外設定をカバレッジに反映しない — 生成ファイルによって数値が実態より高くなります。
- 昇格したルールに
// ignore_for_file:を付ける、またはインポートのゲートに抑制を付ける — 次の編集で保護が失われます。正しさを守るゲートの抑制はブロッカーです。 custom_lintを追加する — レガシーのプラグインシステムです。riverpod_lint 3.x では不要です。- ドリフトスキーマのスナップショットを再フォーマットする — マイグレーションテストのベースラインが壊れます。
完了の定義
-
dart format --set-exit-if-changed .がクリーンであり、dart analyze --fatal-infos --fatal-warningsが成功し、CI で解析だけでなくビルドも行われる。 - include はバージョン固定された VGA ファイルで、pub キャッシュ内に解決される(
scripts/verify-include-pin.shが成功する)。 - 冗長な
language:ブロックがなく、厳格モードは VGA から設定される。 - 無視されるバグの種類が
errorに昇格され、昇格したルールはすべて VGA で実際に有効になっている。 -
linter: rules:にリスト形式とマップ形式が混在していない。 - 生成ファイルの除外設定があり、カバレッジのフィルタリングにも反映されている。
- 抑制は理由を添えて行単位で指定され、アーキテクチャのゲートには抑制がなく、昇格したルールに
// ignore_for_file:が付いていない。 - riverpod_lint が
version:キーを指定して設定されており、custom_lintがない。
関連スキル
dartdoc-conventions—public_member_api_docs: errorと、文書化すべき境界を支えます。async-safety— この設定では検出できない、コールバック内でFutureが破棄される問題です。error-handling-typed-results— 網羅性をコンパイル時の保証にする、sealed なFailure/Resultの骨格です。dart3-idioms-and-coding-standards— ルール 7 を支える sealed 型と網羅的な switch の規律です。naming-conventions— アナライザーでは強制されない、役割を示す接尾辞と Effective Dart の命名規則です。state-management-riverpod—riverpod_lintがチェックする Riverpod のパターンです。codegen-and-toolchain/run-codegen— これらの除外設定が対象とする生成ファイルです。ci-pipeline-and-gates— これらのゲートと、ビルド時には解析を実行しないルールを適用する場所です。testing-strategy— カバレッジのミラーリングと、アナライザーが成功しても証明できないことです。dependency-hygiene— バージョンを固定した lint の include を依存関係の観点から扱います。
参考資料
- Effective Dart:スタイル · 使用法 · ドキュメント
- Dart リンタールール
dart formatdart analyze/analysis_optionsvery_good_analysis·flutter_lintsriverpod_lint· Riverpod:3.0 の新機能