この記事は CyberAgent Developers Advent Calendar 2025 4日目の記事です。
はじめに
グループIT推進本部 CyberAgent group Infrastructure Unit(以下、CIU)所属・Next Expertsの平井( @did0es )です。
CIUのサービスのWebフロントエンドの開発に携わる傍ら、TypeScriptのNext Expertsとして情報発信や社内向けの技術支援を中心に活動しています。
本記事では、TypeScript Compiler(以下、tsc)の compilerOptions のうち、コードスタイルに関わるものをおさらいしつつ、JavaScript・TypeScriptによる開発で広く用いられているESLintと機能や構造を比較し、tscをコンパイラとしてだけでなくLinterとしても活用できるか考察します。
tsc = Linter?
早速ですが、tscはLinterと見なせるでしょうか?
tscはコンパイラとしてTSの型情報をふるい落とし、実行可能なJSに変換する役割を担っています。変換の際、静的解析によって不正なものは型エラーとして報告されます。
これはLinterの検査・結果のレポートと非常に似た挙動です。
また、tscの設定ファイルの tsconfig.json に記述する compilerOptions には、コードスタイルに関するプロパティが存在しています。
追加された際のTypeScriptのメジャーバージョンと併せて紹介します。
- TypeScript 1系以降
noImplicitReturns:関数内でreturnを書かなかった場合エラーにするnoFallthroughCasesInSwitch:switch文でreturnやbreakがブロック内にない場合エラーにするallowUnreachableCode:到達不可能なコードに関するエラーを無効にする(デフォルトは有効)allowUnusedLabels:未使用のラベルがあった際のエラーを無効にする(デフォルトは有効)forceConsistentCasingInFileNames:import時のパスと実際のファイル名の大文字小文字を区別し、一致しない場合はエラーにするnoImplicitAny:暗黙のanyはエラーにする
- TypeScript 2系以降
alwaysStrict:出力するJavaScriptに"use strict";を付与し、strict modeで型検査を行う。noUnusedParameters:未使用の関数の引数があった場合エラーにするnoUnusedLocals:未使用の変数があった場合エラーにするnoImplicitThis:thisがanyの場合エラーにするstrictNullChecks:nullとundefinedを厳密にチェックするstrictFunctionTypes:関数の引数の双変性を無効化。関数を厳密にチェックするstrictPropertyInitialization: クラスのフィールドが初期化されてない場合エラーにするstrictBindCallApply:bindcallapplyの引数を厳密にチェックするuseUnknownInCatchVariables:try catch のcatch(error)のerrorをanyではなくunknownにし、型ガードを記述させる
- TypeScript 4系以降
noPropertyAccessFromIndexSignature: 型にインデックスシグネチャがあるオブジェクトについて、インデックスアクセス(obj[”key”])ではなくドットによるアクセス(obj.key)を行った場合エラーにするexactOptionalPropertyTypes:省略可能な値にundefinedをセットするとエラーにする(省略可能とundefinedを区別する)noUncheckedIndexedAccess:インデックスアクセスした値の型に| undefinedをつけるnoImplicitOverride:親クラスのメソッドをoverrideするときに、overideキーワードを付けないとエラーにする
こういったオプションから、tscはただTypeScriptをJavaScriptに変換するだけでなく、既存のコードスタイルに対して関心を持っている点から、Linterと見なせるかもしれません。
併せて、以下のような erasableSyntaxOnlyオプションを有効化したときに使える、JavaScriptには反映されないTypeScriptの構文も、コードの健全性を担保している点でLinterの一部として見なせます。
- 型注釈:
const foo: numberのような記述。型アノテーション - 型エイリアス:
type Foo = {}のような記述 - インターフェース:
interface Foo {}のような記述 - ジェネリクス:
function foo<T>() {}のような記述。型引数 - 型アサーション:
foo as Barのような記述。型のキャスト - 非nullアサーション:
foo!のような記述 - 型ユーティリティ:Mapped TypesやConditional Typesなどの型
- アンビエント宣言:
declare 〇〇のような記述 - Type Only import/export:
import type 〇〇やexport type 〇〇のような記述
次の項では、構造の観点からtscとLinter(ESLint)を比較します。
tscとLinterの構造の比較
以下はtscとESLintの処理フローです。

tscは実行されるとコンフィグファイルの tsconfig.json を読み込み、 compilerOptions などの内容に基づいてコンパイルします。
ESLintも同じように、実行されると eslint.config.js のようなコンフィグファイルを読み込み、 rules などの内容に基づいてLintします。
どちらもユーザーが選択したオプションを元に処理を実行します。
また、tscもESLintもAbstract Syntax Tree(以下、AST)による静的解析に基づいたツールです。
静的解析とは、コードを直接実行することなくどういった挙動をするのかを解析する技術です。
解析にはASTが用いられます。ASTとは、ソースコードを木構造にして、効率よく探索できるようにした中間表現です。
JavaScriptやTypeScriptの世界ではASTをオブジェクトとして取り回します。
このASTはツールによって異なります。標準化こそされていませんがAcornやEsprimaなどの形式や、こういった既存の形式をフォークして改変したものが広く用いられています。
tscはソースコードを独自のTypeScript ASTに変換し、再帰的に探索しています。
この処理は、 TypeScript Compiler Notes というGitHubリポジトリで解説されています。適宜ご覧ください。
ESLintはソースコードをEspreeという形式のASTに変換し、再帰的かつイベント駆動型の仕組みで探索しています。
この処理は、自著の ESLintとPrettierのコードリーディングでASTベースの静的解析を理解する で解説しています。こちらも適宜ご覧ください。
どちらもASTの形式や探索方法は異なりますが、ソースコードを中間表現に変換し、再帰的に探索した結果を出力する点は同じです。
一方、tscとESLintの間には入出力に関して、明確な挙動の違いがあります。
tscはオプションと入力されたソースコードを照らし合わせてエラーをレポートしますが、エラーの修正は行わず、 CLIのオプションに沿って変換したJSを出力します。
ESLintはルールに違反した入力があればエラーとしてレポートしつつ、 fix オプションを付けるとルールに沿って修正したソースコードを出力します。
ESLintと比較してtscができること、できないことをまとめます。
| tsc | ESLint | |
|---|---|---|
| コードの検査 | △(ESLintほどではないが、tsc = Linter?の項で挙げたオプションや構文でできる) | ◯ |
| コードの修正 | ✗ | ◯ |
| エディタ上でのエラー可視化 | ◯ | ◯ |
| プラグインの実装 | ◯ | ◯ |
まとめ
機能や構造の観点からtscとLinterを比較しました。tscは機能的・構造的にかなりLinterに近いツールです。
しかし、Linterとしての役割を単体で賄えるものではないため、ESLintのようなツールと併用する場面が多く見受けられます。
また、最近のtscの開発動向として、7系以降はGoによる再実装が行われており、パフォーマンス方面を注力するように見受けられます。
Linterとしての機能をより充実させるような方向性ではありませんが、開発体験の向上に向けた開発が日々行われています。
この記事を通して、tscやTypeScriptを取り巻くエコシステムに関心を持っていただければ幸いです。
ご精読いただきありがとうございました。
