1. ホーム
  2. TypeScript

【TypeScript】@ts-expect-error・@ts-ignore の使い方|型エラーを意図的に無視する方法と違いを解説

Share

TypeScript を書いていると、「自分のコードは正しいのに型エラーが出る」という場面にどうしても出くわします。外部ライブラリの型定義が実際の挙動とずれているときや、テストでわざと不正な値を渡したいときです。そんなときに1行だけ型チェックを止められるのが // @ts-expect-error// @ts-ignore です。この記事では2つの書き方と決定的な違い、ファイル全体を対象にする // @ts-nocheck、JSX での書き方、そして「使ってよい場面」までを解説します。

型エラーを意図的に無視したくなる場面

型エラーは基本的に直すものです。それでも「今は直せない・直す必要がない」エラーは実際に存在します。よくあるのは、npm パッケージに同梱された型定義ファイルが実装より古く、実際には受け取れる引数が型の上では受け取れないことになっているケースです。ライブラリ側の修正を待つあいだ、自分のコードをコンパイルできない状態にしておくわけにはいきません。

もうひとつはテストです。「文字列以外を渡したら例外を投げる」という実行時バリデーションを検証したいとき、テストコードではあえて型に合わない値を渡す必要があります。これは型エラーが出るのが正しい状態なので、その1行だけチェックを黙らせたくなります。

こうした場面のために、TypeScript にはコメントでコンパイラに指示を出す仕組み(コメントディレクティブ)が用意されています。@ts-ignore は TypeScript 2.6 から、@ts-expect-error は TypeScript 3.9 から使えます。

基本の書き方は「エラーが出る行の1つ上」

どちらも書き方は同じで、エラーを消したい行のすぐ上にコメントとして書きます。効果があるのは直後の1行だけで、2行下には届きません。

index.ts
// 次の行の型エラーが報告されなくなる
// @ts-expect-error
const count: number = "1";

// @ts-ignore でも同じように消える
// @ts-ignore
const total: number = "2";

// 対象は直後の1行だけなので、ここはふつうにエラーになる
const price: number = "3";
// エラー: Type 'string' is not assignable to type 'number'.

抑制されるのはその行から発生するすべての型エラーです。1行に複数のエラーがあるときは、まとめて消えます。

multiple.ts
declare function fn(a: string, b: number): void;

// 第1引数・第2引数の両方が型違反だが、1つのコメントで両方とも消える
// @ts-expect-error
fn(1, "2");

「1行分だけ」と言いつつ実際にはその行の型チェックを全部止めているので、狙っていないエラーまで一緒に隠れてしまう点は意識しておいてください。

違いは「不要になったとき」にはっきり出る

エラーを消すという結果だけ見れば2つは同じです。決定的に違うのは、そのコメントが要らなくなったときの振る舞いです。@ts-expect-error は「次の行にはエラーがあるはずだ」という宣言でもあるため、次の行にエラーが無いと逆にエラーになります。

unused.ts
// @ts-expect-error
const a: number = 2; // ← この行にエラーは無い
// エラー: TS2578: Unused '@ts-expect-error' directive.
// (エラーはコメントを書いた行に対して報告される)

// @ts-ignore
const b: number = 2; // ← エラーが無くても何も言われない

これは地味に見えて重要な差です。ライブラリの型定義が修正されたり、自分のコードを直してエラーが解消したりしたとき、@ts-expect-error なら「もうこのコメントは要りません」とコンパイラが教えてくれます。放置された抑制コメントは、いつの間にか本物のバグを覆い隠す原因になるため、消し忘れを検出できる価値は大きいと言えます。一方で @ts-ignore は黙って握りつぶすので、何年も残ったまま誰も気づかない、ということが起こります。

そのため、基本は @ts-expect-error を選ぶのがおすすめです。@ts-ignore が向くのは、TypeScript のバージョンアップ中で「そのバージョンでだけエラーになる」場合など、エラーが出たり出なかったりする状況に限られます。

ディレクティブ対象範囲不要になったとき追加バージョン
// @ts-expect-error直後の1行Unused '@ts-expect-error' directive.(TS2578)というエラーになるTypeScript 3.9
// @ts-ignore直後の1行何も起こらない(気づけない)TypeScript 2.6
// @ts-nocheckファイル全体(先頭に書いたときのみ)何も起こらない.ts ファイルは TypeScript 3.7
// @ts-checkファイル全体―(抑制ではなく、JavaScript ファイルの型チェックを有効にする指示)TypeScript 2.3

ファイル全体を対象にする @ts-nocheck

1行ずつではなくファイルまるごとチェックを外したいときは // @ts-nocheck を使います。もともとは JavaScript ファイル向けの機能でしたが、TypeScript 3.7 から .ts / .tsx ファイルでも使えるようになりました。JavaScript から TypeScript へ移行する途中の巨大なファイルなど、一時的に全体を対象外にしたいときのための逃げ道です。

legacy.ts
// @ts-nocheck 移行中のため一時的に型チェックを止めている

const bad: number = "x";   // エラーにならない
const bad2: string = 42;   // これもエラーにならない

影響範囲がファイル単位と非常に大きいので、常用するものではありません。まずは @ts-expect-error で1行ずつ抑えられないかを検討し、それが現実的でないほどエラーが多いファイルにだけ使ってください。

コメントの後ろに理由を書き残す

ディレクティブの後ろには、そのまま説明文を続けて書けます。TypeScript は説明文の中身を解釈しませんが、公式ドキュメントでも「どのエラーを抑制しているのか説明を書くことを推奨する」とされています。半年後の自分やレビュアーが読んだときに、なぜチェックを外したのか分かるようにしておきましょう。

with-reason.ts
// @ts-expect-error 型定義が options 引数を宣言していないが、実装は対応している(lib v2.3.0 で修正予定)
formatDate(new Date(), { locale: "ja" });

// コロンで区切る書き方もよく使われる
// @ts-expect-error: 実行時バリデーションの検証のため、意図的に不正な値を渡す
validateUser(123);

この「理由を書く」というルールは、ESLint で機械的に強制できます。typescript-eslint の @typescript-eslint/ban-ts-comment ルールは、既定で @ts-ignore@ts-nocheck を禁止し、@ts-expect-error は説明文が付いているときだけ許可します(説明文は既定で3文字以上が必要)。@ts-check はエラーを増やす方向の指示なので既定で許可されています。

eslint.config.js(既定値と同じ設定)
rules: {
  "@typescript-eslint/ban-ts-comment": ["error", {
    "ts-expect-error": "allow-with-description", // 説明付きなら許可
    "ts-ignore": true,                           // 禁止
    "ts-nocheck": true,                          // 禁止
    "ts-check": false,                           // 許可
    minimumDescriptionLength: 3,
  }],
}

推奨設定を有効にしているプロジェクトでこのルールに引っかかったら、@ts-ignore@ts-expect-error に置き換えたうえで理由を書き足す、というのが基本的な直し方になります。

JSX(.tsx)の中では中括弧で囲む

React コンポーネントの JSX の中は、// で始まるコメントをそのまま書けません。JSX の要素の並びの中にコメントを置くには、{/* ... */} という形の JSX 式コンテナに入れる必要があります。ディレクティブも同じ書き方で機能します。

App.tsx
export function App() {
  return (
    <div>
      {/* @ts-expect-error title は string だが、表示崩れの確認用に数値を渡している */}
      <Card title={123} />
    </div>
  );
}

注意したいのは、中括弧とコメントを1行に収めることです。{} を別々の行に分けて中に行コメントを書くと、ディレクティブの「直後の行」が閉じ括弧の行になってしまい、目的の要素には効きません。@ts-expect-error ならこの書き間違いも Unused '@ts-expect-error' directive. として表面化するので、その意味でも @ts-ignore より安全です。

使ってよい場面と、その前に試すこと

外部ライブラリの型定義が実態と合っていないとき

ライブラリの型定義が古く、実際には動く呼び出し方がエラーになる場合です。自分では直せない領域なので、抑制コメントの出番と言えます。ただしこの場合、declare module による型の拡張(モジュール拡張)や、正しい型定義を自分で書いて types に読み込ませる方法で根本的に解決できることもあります。修正の見込みが立つなら、issue や PR の番号をコメントに書き添えておくと後から追いやすくなります。

テストで意図的に不正な値を渡すとき

実行時バリデーションのテストは、型エラーになる呼び出しを書くこと自体が目的です。ここは @ts-expect-error が最も素直に機能する場面で、「型エラーが出るはず」という意図がそのままコードに表れます。逆に as any でキャストして黙らせると、後で型が変わってエラーが出なくなっても気づけません。

validateUser.test.ts
import { validateUser } from "./validateUser";

test("文字列以外を渡すと例外を投げる", () => {
  expect(() => {
    // @ts-expect-error 実行時チェックの検証のため、意図的に number を渡す
    validateUser(123);
  }).toThrow();
});

まず型で解決できないかを考える

一方で、自分のコードのエラーを消す目的で気軽に使うのはおすすめできません。多くの場合、エラーは「この値は本当にその型なのか確認していない」というコンパイラの正しい指摘です。typeofin による型の絞り込み(型ガード)を挟むだけで、抑制コメントなしに解決することがほとんどです。

guard.ts
function print(value: string | number) {
  // 避けたい書き方: value が number のときは実行時に落ちる
  // @ts-ignore
  console.log(value.toUpperCase());

  // 望ましい書き方: 型で絞り込めばエラーも実行時の不具合も起きない
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  }
}

抑制コメントはエラーの表示を消すだけで、実行時の危険はそのまま残ります。使うのは「型システムでは表現しきれない」「自分の管理外である」と説明できるときに限る、と考えておくとよいでしょう。

コメントを書いてもエラーが消えないとき

複数行にまたがる式では、エラーが報告される行が違う

もっともよくあるのがこれです。ディレクティブが見ているのはエラーが報告された行番号であって、式の途中の行ではありません。改行を含む代入や関数呼び出しでは、エラーが宣言の先頭行に付くことがあり、その1つ上に書かないと効きません。

multiline.ts
// 効かない例: エラーは const の行に報告されるので抑制されない
const value: number =
  // @ts-expect-error
  "5";
// エラー: Type 'string' is not assignable to type 'number'.
// エラー: TS2578: Unused '@ts-expect-error' directive.

// 効く例: エラーが出ている行の1つ上に置く
// @ts-expect-error
const value2: number =
  "5";

@ts-expect-error を使っていれば「未使用のディレクティブ」というエラーが出るので、位置がずれていることにすぐ気づけます。エディタに表示されるエラーの行番号を確認して、その真上に移動させてください。

構文エラーは抑制できない

これらのディレクティブが止められるのは、型の検査によって出るエラーだけです。括弧の閉じ忘れのようにコードとして解析できない構文エラーは、コメントを書いても消えません。Expression expected.(TS1109)のようなメッセージが残るときは、抑制ではなくコードそのものを直す必要があります。

@ts-nocheck をファイルの途中に書いている

// @ts-nocheck が有効になるのはファイルの先頭、最初の文より前に書いたときだけです。import 文や変数宣言のあとに書いても単なるコメントとして無視され、型チェックは止まりません。効いていないと感じたら、まず位置を確認してください。

エラーが消えたはずなのに @ts-expect-error でエラーが出る

Unused '@ts-expect-error' directive. は不具合ではなく、想定どおりの動作です。ライブラリの更新やコードの修正で元のエラーが解消したというサインなので、そのコメントを削除すれば解決します。この「掃除のタイミングを教えてくれる」挙動こそが @ts-expect-error を選ぶ理由です。

まとめ

// @ts-expect-error// @ts-ignore は、どちらもコメントを書いた直後の1行の型エラーを抑制するディレクティブです。違いは不要になったときの振る舞いで、@ts-expect-error は次の行にエラーが無いと Unused '@ts-expect-error' directive. として知らせてくれるのに対し、@ts-ignore は黙ったままです。消し忘れを防げる分、基本は @ts-expect-error を選び、後ろに理由を書き添えておきましょう。ファイル全体を外す // @ts-nocheck はファイル先頭に書いたときだけ有効で、影響が大きいため移行作業などに限定するのが無難です。JSX の中では {/* @ts-expect-error */} と中括弧で囲む必要がある点、複数行にまたがる式ではエラーが報告される行の真上に置く必要がある点を押さえておけば、狙ったとおりにエラーを抑えられます。そして何より、抑制する前に型ガードや型定義の修正で解決できないかを一度考えることが、結局は一番の近道です。

参考ページ