mcpField
E
enforcing-typescript-standards

enforcing-typescript-standards

github.com/jgeurts開発1213 回インストール1 日前に更新
リポジトリ

enforcing-typescript-standards について

Enforcing TypeScript Standardsは、TypeScriptコードを扱うエージェント向けのスキルです。型、インポート、クラス構成、安全性、テスト、エラー処理の規則と、変更を確認・改善する手順を示します。

主な機能

  1. 01明示的な型指定と型の再利用
  2. 02インポートとクラスメンバーの整理
  3. 03安全性を高めるTypeScriptパターン
  4. 04エラー処理とテストの改善
  5. 05コードレビューとリファクタリングの指針
  6. 06ビルドとlintの確認

活用シーン

  1. 01.tsおよび.tsxファイルの作成・変更
  2. 02TypeScriptの変更が規則に沿うか確認
  3. 03プロジェクト標準に沿ったリファクタリング
  4. 04型チェックとlint結果の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jgeurts/oxlint-config-decent --skill enforcing-typescript-standards

インストール後、Claude Code で/enforcing-typescript-standardsを入力して読み込まれたか確認します。

詳細

TypeScript 標準の適用

明示的な型指定、インポートの整理、クラスメンバーの順序、コードの安全性に関するルールなど、プロジェクトの中核となる TypeScript 標準を適用します。

トリガー

ユーザーが次のいずれかを発言、または暗に示した場合に、このスキルを有効にします。

  • 「write」「create」「implement」「add」「build」(新しい TypeScript コード)
  • 「fix」「update」「change」「modify」「refactor」(既存の TypeScript コード)
  • 「review」「check」「improve」「clean up」(コード品質)
  • .ts または .tsx ファイルに関するあらゆるリクエスト

具体的なトリガー:

  • 新しい .ts または .tsx ファイルの作成
  • 既存の TypeScript コードの変更
  • TypeScript コードの準拠状況のレビュー

中核となる標準

型安全性
  • 明示的な戻り値の型:実用的な場合は、明示的な戻り値の型を指定してください。型推論が明らかで、明確さが増さない場合は省略します
  • メンバーのアクセス修飾子を明示:クラスメンバーには public、private、または protected が必要です
  • 型専用インポート:型には import type を使用します:import type { Foo } from './foo.js'
  • 型の構成要素をソート:ユニオン型と交差型はアルファベット順に並べる必要があります
  • Error オブジェクトのみを throw:文字列やその他のプリミティブ型は決して throw しないでください
  • any と型アサーションを避ける:any や as キャストより、適切な型付けを優先してください。本当に必要な場合にのみ使用します
  • JSON フィールドを明示的に型指定:JSON データには Record<string, unknown> または具体的なインターフェースを使用し、any は決して使用しないでください
  • 変換には Number() を使用:parseInt(value, 10) や parseFloat(value) より Number(value) を優先してください
  • 既存の型を再利用:新しいインターフェースを定義する前に、直接再利用、拡張、または Pick、Omit、Partial などのユーティリティ型で派生できる既存の型を検索してください
型アサーションの代替手段

as を使用する前に、次の方法を順番に試してください。

  1. ソースで適切に型付けする
  2. 型ガード(typeof、instanceof)
  3. 制御フローによる型の絞り込み
  4. カスタム型述語関数
  5. 判別可能なユニオン型
// Bad
const user = data as User;

// Good
function isUser(data: unknown): data is User {
  return typeof data === 'object' && data !== null && 'id' in data;
}
if (isUser(data)) {
  // data is now typed as User
}
インポートの整理
  • インポート順:組み込み → 外部 → 内部 → 親 → 同階層 → index(各グループ内はアルファベット順)
  • 重複インポートの禁止:同じモジュールからのインポートはまとめます
  • インポート後の改行:インポートブロックの後には空行が必要です
クラスメンバーの順序
  1. シグネチャ(call/construct)
  2. フィールド:private → public → protected
  3. コンストラクター:public → protected → private
  4. メソッド:public → protected → private
コードスタイル
  • 巧妙さよりシンプルさ:気の利いた一行コードより、簡潔で読みやすいコードを優先します
  • 早期リターン:ガード節を使ってネストを減らし、エッジケースでは早期にリターンします
  • Null 合体演算子:デフォルト値には || より ?? を優先します(0 や '' での誤判定を避けられます)
  • オプショナルチェーン:安全なプロパティアクセスには ?. を使用します
  • 既存のパターンに合わせる:コードベースで既に確立されている規約に従います
  • 意味のある識別子:名前は説明的である必要があります(例外:_、i、j、k、e、x、y)
  • 関数宣言:const foo = function() ではなく function foo() を使用します
  • const を優先:再代入が必要でない限り const を使用します
  • var を禁止:常に const または let を使用します
  • オブジェクトの短縮記法:{ foo: foo } ではなく { foo } を使用します
  • テンプレートリテラル:'Hello ' + name ではなく `Hello ${name}` を使用します
  • 厳密等価:null の比較を除き、=== を使用します
  • 1 ファイルにつきクラス 1 つ:1 ファイル内のクラス定義は最大 1 つです
  • reduce を避ける:明確さを優先し、for...of ループや他の配列メソッドを使用します
  • クラスより関数:状態が必要な場合を除き、静的メソッドを持つクラスよりエクスポートされた関数を優先します
  • ネストした関数を禁止:ヘルパー関数は他の関数内ではなく、モジュールレベルで定義します
  • 不変性:既存のオブジェクトや配列を変更せず、新しいものを作成します
命名規則
  • 列挙型メンバー:PascalCase を使用します(例:MyValue)
  • 末尾のアンダースコアを禁止:識別子の末尾に _ を付けることはできません
コメント
  • 冗長なコメントを避ける:コードから明確に読み取れる内容には、決してコメントを付けないでください
  • 重複するコメントを避ける:関数名、型、近くにあるコメントの情報を繰り返さないでください
  • 意味のあるコメントのみ:「何をするか」ではなく「なぜそうするか」を説明するコメントだけを追加してください。コードを見れば何をするかは分かります
ブール式
  • 真偽値チェックを優先する:明示的な比較より、暗黙の真偽値チェックを使用してください
  • 例外:0/''(有効な値)とnull/undefinedを区別することが意味的に重要な場合は、明示的なチェックを使用してください
テスト
  • モックを最小限にする:何もかもモックにするのは避け、利用できる場合は実際の実装やデータジェネレーターを使用してください
  • 実際の動作をテストする:モックのテストはあまり価値がありません。実際のコードパスをテストしてください
  • 手を抜かない:正常系だけでなく、エッジケースも網羅する徹底したテストを書いてください
エラー処理
  • 具体的なエラー型を使う:意味がある場合は、汎用的なErrorより具体的なエラー型を優先してください
  • エラーを握りつぶさない:空のcatchブロックでエラーを握りつぶさないでください
  • 拒否を処理する:Promiseの拒否は必ず処理してください
  • エラーを伝播させる:再スローやログ出力だけを目的にエラーを捕捉しないでください。エラーハンドラーまで伝播させてください

負の知識

次のアンチパターンを避けてください。

  • 本番コードでのconsole.log()文
  • eval()またはFunction()コンストラクター
  • ネストした三項演算子
  • Promise.allを使えばより簡潔になるループ内のawait(順序が重要な場合や、並列化で複雑になる場合は逐次的なawaitで構いません)
  • 空のインターフェース
  • 変数のシャドーイング
  • ループ内で定義される関数
  • 説明なしの@ts-ignore(10文字以上の説明を付けた@ts-expect-errorを使用してください)
  • コードを言い換えるコメント:counter++の上にある// increment counter
  • 型情報と重複するコメント:戻り値の型が: stringなのに// returns a stringと書くこと
  • コメントアウトされたコード(削除してください。必要な場合はバージョン管理を使用してください)
  • 冗長なブール値比較:arr.length > 0、str !== ''、obj !== null && obj !== undefined
  • コメントでのリントルールの無効化(代わりにコードを修正してください)
  • any型やas型アサーションの多用
  • 実際の実装やデータジェネレーターを使わず、テストでモックを使いすぎること
  • エラーを黙って握りつぶす空のcatchブロック
  • ??のほうが適切な場面で、デフォルト値に||を使うこと
  • 早期リターンで簡潔にできる箇所の深いネスト
  • 再スローやログ出力だけを目的としたエラーの捕捉
  • 関数内での関数定義のネスト
  • 新しいオブジェクトや配列を作らず、既存のものを変更すること
  • TOCTOU:操作前にファイル/リソースの存在を確認すること(操作を試み、エラーを処理してください)
  • 静的メソッドのみを持つクラス(代わりに通常の関数を使用してください)
  • 既存のインターフェースを再利用したり、Pick/Omit/Partialで派生させたりせずに、インターフェースを重複定義すること

検証ワークフロー

  1. 分析:コードの変更をこれらのTypeScript標準と照らし合わせて確認します
  2. 生成/リファクタリング:上記のすべてのルールに準拠するよう、コードを作成または変更します
  3. 簡素化:簡素化できる箇所を見直します。巧妙な解決策より、明確で分かりやすいコードを優先してください
  4. 命名を確認:変更後も、変数名と関数名が文脈に合っていることを確認します
  5. ビルド:型エラーなしでコンパイルできることを確認します(例:pnpm run buildまたはpnpm exec tsc --noEmit)
  6. リント:タスクを完了する前に、pnpm run lintを実行して準拠していることを確認します

例

コメントの例
// Standard
// Retry with exponential backoff to handle transient network failures
async function fetchWithRetry(url: string, attempts = 3): Promise<Response> {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fetch(url);
    } catch {
      await sleep(2 ** i * 100);
    }
  }
  throw new Error(`Failed after ${attempts} attempts`);
}

// Non-Standard
/**
 * Fetches data from a URL with retry logic
 * @param url - The URL to fetch from
 * @param attempts - Number of attempts (default 3)
 * @returns A Promise that resolves to a Response
 */
async function fetchWithRetry(url: string, attempts = 3): Promise<Response> {
  // Loop through attempts
  for (let i = 0; i < attempts; i++) {
    try {
      // Try to fetch the URL
      return await fetch(url);
    } catch {
      // Wait before retrying
      await sleep(2 ** i * 100);
    }
  }
  // Throw error if all attempts fail
  throw new Error(`Failed after ${attempts} attempts`);
}
ブール式の例
// Standard
if (myArray.length) {
}
if (myString) {
}
if (myObject) {
}
if (!value) {
}

// Non-Standard
if (myArray.length !== 0) {
}
if (myArray.length > 0) {
}
if (myString !== '') {
}
if (myObject !== null && myObject !== undefined) {
}
if (value === null || value === undefined) {
}
早期リターンの例
// Standard
function processUser(user: User | null): Result {
  if (!user) {
    return { error: 'No user provided' };
  }
  if (!user.isActive) {
    return { error: 'User is inactive' };
  }
  return { data: transform(user) };
}

// Non-Standard
function processUser(user: User | null): Result {
  if (user) {
    if (user.isActive) {
      return { data: transform(user) };
    } else {
      return { error: 'User is inactive' };
    }
  } else {
    return { error: 'No user provided' };
  }
}
クラスより関数を使う例
// Standard
export function calculateTotal(items: Item[]): number {
  return items.reduce((sum, item) => sum + item.price, 0);
}

export function formatCurrency(amount: number): string {
  return `$${amount.toFixed(2)}`;
}

// Non-Standard
export class Calculator {
  static calculateTotal(items: Item[]): number {
    return items.reduce((sum, item) => sum + item.price, 0);
  }

  static formatCurrency(amount: number): string {
    return `$${amount.toFixed(2)}`;
  }
}
関数のネストを避ける例
// Standard
function transformItem(item: Item): TransformedItem {
  return { id: item.id, name: item.name.toUpperCase() };
}

async function processItems(items: Item[]): Promise<TransformedItem[]> {
  return items.map(transformItem);
}

// Non-Standard
async function processItems(items: Item[]): Promise<TransformedItem[]> {
  function transformItem(item: Item): TransformedItem {
    return { id: item.id, name: item.name.toUpperCase() };
  }
  return items.map(transformItem);
}
不変性の例
// Standard
function addItem(items: Item[], newItem: Item): Item[] {
  return [...items, newItem];
}

function removeItem(items: Item[], id: string): Item[] {
  return items.filter((item) => item.id !== id);
}

function updateItem(items: Item[], id: string, updates: Partial<Item>): Item[] {
  return items.map((item) => (item.id === id ? { ...item, ...updates } : item));
}

// Non-Standard
function addItem(items: Item[], newItem: Item): Item[] {
  items.push(newItem);
  return items;
}

function removeItem(items: Item[], id: string): Item[] {
  const index = items.findIndex((item) => item.id === id);
  items.splice(index, 1);
  return items;
}
エラー伝播の例
// Standard
async function getUser(id: string): Promise<User> {
  return userService.findById(id);
}

// Non-Standard
async function getUser(id: string): Promise<User> {
  try {
    return await userService.findById(id);
  } catch (error) {
    console.error(error);
    throw error;
  }
}
TOCTOUの例
// Standard
async function readConfig(path: string): Promise<Config> {
  try {
    const content = await readFile(path, 'utf-8');
    return JSON.parse(content);
  } catch (error) {
    if (isNotFoundError(error)) {
      return defaultConfig;
    }
    throw error;
  }
}

// Non-Standard
async function readConfig(path: string): Promise<Config> {
  if (await fileExists(path)) {
    const content = await readFile(path, 'utf-8');
    return JSON.parse(content);
  }
  return defaultConfig;
}
型の再利用例
// Given an existing type
interface User {
  id: string;
  email: string;
  name: string;
  passwordHash: string;
  createdAt: Date;
  updatedAt: Date;
}

// Standard - derive from existing type
type PublicUser = Omit<User, 'passwordHash'>;
type UserSummary = Pick<User, 'id' | 'name'>;
type UserUpdate = Partial<Pick<User, 'email' | 'name'>>;

// Non-Standard - duplicating fields that already exist
interface PublicUser {
  id: string;
  email: string;
  name: string;
  createdAt: Date;
  updatedAt: Date;
}

interface UserSummary {
  id: string;
  name: string;
}

interface UserUpdate {
  email?: string;
  name?: string;
}

コメント