紡Book の勉強ノートに戻る
コードレビュー学習2026.05.11

正しいコードなのにツールに怒られた — コメント1行で lint を黙らせる仕組み

ESLintNext.js静的解析ツールチェイン

公開ローンチ前のコード整備で出てきた <a> の警告を黙らせるところから、Lint と Formatter の役割分担、ディレクティブコメントの仕組みまで掘った話です。

この記事でわかること

  • Lint(静的解析)と Formatter(整形)は別物だという話
  • // eslint-disable-next-line のような コメント が、なぜツールへの命令として効くのか
  • 「ここだけ lint を止めたい」時の粒度の選び方
  • 似た仕組みが他のツールにもある話

きっかけ

Next.js のエラー境界用ファイル app/global-error.tsx で、エラー時にホームへ戻すリンクを置いていた:

tsx
"use client";
 
export default function GlobalError() {
    return (
        <html lang="ja">
            <body>
                <h2>エラーが発生しました</h2>
                {/* エラー境界では Link の router context が利用不可なため素の <a> を使う */}
                <a href="/">ホームへ戻る</a>
            </body>
        </html>
    );
}

Next.js のページ内リンクは普通 next/link の <Link> を使います。が、エラー境界(特に <html> を自前で描く global-error)では <Link> の router context が利用できない場合があるため、確実に動く素の <a> を選んでいる。コメントにもそう書いてある。

それでも ESLint は警告してきた:

/app/src/app/global-error.tsx
  9:17  warning  Do not use an `<a>` element to navigate to `/`.
                 Use `<Link />` from `next/link` instead.  @next/next/no-html-link-for-pages

「<a> 使うな、<Link> 使え」── 普通の場面では正しい指摘だが、ここでは意図的に <a> のはず。

Lint は何をしているのか

似て非なるツールとして Formatter(整形ツール、例: Prettier) があるが、役割は完全に別物:

Lint (ESLint) Formatter (Prettier)
役割 「これおかしくない?」を指摘 「見た目を統一」する
例 「使ってない変数があるよ」「<a> じゃなく <Link> 使え」「== じゃなく === 使え」 インデント、改行、クォート種類、行幅を強制
対象 コードの 意味(バグ/規約違反になりそう) コードの 見た目(人間が読みやすい形)

混同しがちだが、Lint は「コード化された人間レビュアー」、Formatter は「機械的な清書ツール」。Next.js プロジェクトは大体この2本立てで動いています。

なぜコメントで挙動制御できるのか

@next/next/no-html-link-for-pages のような ESLint ルールは、コード全体に常に適用されます。今回のように「ここは例外」とピンポイントで伝えたい時、どうするか。

答えは:

tsx
{/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
<a href="/">ホームへ戻る</a>

コメント記法を借りた「ツールへの命令」

ルールは設定ファイル、例外はソースのコメント。ESLint が読むときのメモリに載るだけで、DB には無い。eslint.config.mjsファイル・Git<a> を使うな全ファイルに常に適用app/global-error.tsxファイル・Git人間向けコメント読み飛ばすディレクティブコメント次の1行だけ黙れ<a href="/"> はそのままESLint のメモリ保存しないこの行だけ黙る設定ファイルは変えない人間向けは無視する。合言葉のあるコメントだけが命令になる。
// eslint-disable-next-line @next/next/no-html-link-for-pages
└─────┬──────┘ └────┬────┘ └──────────┬─────────────┘
      ①              ②                 ③
部位 意味
① eslint-disable-next-line 次の1行だけ ESLint のチェックを止めて
② (区切り)
③ @next/next/no-html-link-for-pages 黙らせるルール名(Next.js eslint plugin 由来)

コメント記法を借りた 「ESLint への手紙」 とも言えます。

なぜわざわざコメントを使うのか

設定ファイル(.eslintrc)にも「このファイルだけ無効化」みたいな書き方はあるのに、なぜソース内コメントを使う仕組みが用意されているか:

  • シンタックスエラーにならない — ただのコメント扱いなので、古いツール・違うパーサに通しても壊れません
  • コードと一緒に Git 管理される — 設定ファイルに書くより、現場で見えるし diff も追えます
  • 次の1行だけピンポイント で範囲指定できる ── 設定ファイルだと粒度がファイル単位で粗くなります
  • 意図が現場に残る ── 「なぜ disable してるのか」を直近行にコメントで書けます

粒度の選び方

ESLint のディレクティブコメントには複数の粒度があります:

黙らせる範囲はソースファイルの行の上で決まる。DB には無く、設定ファイルも触らない。1行だけ副作用が最小行行黙る行行範囲指定enable まで行黙る黙る黙る行ファイル全体他のルールも死ぬ黙る黙る黙る黙る黙るハイライトが広いほど、関係ない指摘まで消える。
ディレクティブ 効果範囲
// eslint-disable-next-line 次の1行だけ
// eslint-disable-line そのコメントが書かれた行だけ(行末コメント)
/* eslint-disable */ ... /* eslint-enable */ 範囲指定(block)
/* eslint-disable */(ファイル冒頭) ファイル全体

迷ったら disable-next-line がデフォルト。副作用が最小で、意図が明確に残ります。

テストコードや legacy 移植ファイルではファイル全体の disable が正当な場面もあるが、雑に使うと lint が他の問題を見逃します。

似た仕組みは他にもある

「コードの一部だけ静的解析を止めたい」というニーズは普遍的なので、各ツールが自前の コメント記法を借りた制御API を持っています:

ツール ディレクティブコメント
ESLint // eslint-disable-next-line ...
TypeScript // @ts-ignore / // @ts-expect-error
Prettier // prettier-ignore
Stylelint /* stylelint-disable */
Pylint (Python) # pylint: disable=...
Rubocop (Ruby) # rubocop:disable Layout/LineLength

コメントを使うのは「シンタックスエラーにならない」「他のパーサが壊れない」というメリットを共有しています。

学びポイント

  • Lint と Formatter は別物。役割は「意味の指摘」と「見た目の統一」で分かれています
  • ディレクティブコメントは「ツールへの手紙」。コメント記法を借りて、ツールに直接命令を渡しています
  • disable する時は最小粒度から ── disable-next-line が基本、ファイル単位の disable は副作用が大きいです
  • なぜ disable したかを直近コメントで残す ── 半年後の自分(or 他人)が「なんで lint 切ってんの?」と疑った時の答えがコードに残ります

おわりに

ツールの警告は、コードがおかしい時 / ツールがそのコードの意図を読めない時 の2パターンで出ます。後者の時に「コードを警告に合わせて書き換える」のは、しばしば本来の意図を歪める方向に倒れます。

今回のように「エラー境界では Link が使えない」みたいな事情があるなら、コードを正しく保ったまま ツール側に「ここは例外」と伝える ほうが筋がいいです。そのための仕組みがディレクティブコメントです。

「コメントなのに命令として効く」のは魔法ではなく、ツールがソースを読む時に、コメントまで含めて見ている からこそ成り立つ素直な仕掛けです。


この記事は2026年5月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。