import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
// `.claude/**` は Claude Code の作業領域(worktree 等)でリポジトリの成果物ではない。
// ここを走査すると、作業用コピーの中の d-watch-mock.jsx 等が
// ルート側の ignore(相対パス指定)から漏れて lint が落ちる。
{ ignores: ["d-watch-mock.jsx", "lib/generated/**", ".next/**", ".claude/**"] },
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
// 動作ログは lib/logger 経由に統一する。console.* への逆戻りを防ぐ
// (docs/logging-design.md §8 ステップ10)
{
rules: {
"no-console": "error",
},
},
{
files: ["lib/logger/**"],
rules: {
"no-console": "off",
},
},
// scripts/ はアプリのapiルートではなく単発のCLIツール(例: シード)。
// 標準出力そのものが目的なので lib/logger(pinoの構造化ログ)を経由させない。
{
files: ["scripts/**"],
rules: {
"no-console": "off",
},
},
// 生SQL禁止(指示書AE 段階2-1 / D-122)
// SQLインジェクションの入口を構造的に塞ぐ。Prisma のクエリビルダ経由に統一する。
// 本番は MariaDB 10.11 / ローカル検証は MySQL 8.4 なので、
// 生SQLは「ローカルで通っても本番で通らない」リスクもある(CLAUDE.md §3)。
// lib/generated/** は先頭の ignores で除外済み —
// あそこには Prisma が生成した $queryRaw の「型宣言」があり、呼び出しではない。
// どうしても生SQLが必要になったら、このルールを消すのではなく
// タグ付きテンプレート(prisma.$queryRaw`...`)だけを個別に eslint-disable し、
// 理由を docs/decisions.md に書くこと。文字列連結と *Unsafe は例外を作らない。
{
files: ["app/**", "lib/**", "components/**", "scripts/**", "proxy.ts"],
rules: {
"no-restricted-syntax": [
"error",
{
selector: "MemberExpression[property.name=/^\\$(query|execute)Raw(Unsafe)?$/]",
message:
"生SQLは使わないこと(CLAUDE.md §3 / 指示書AE 段階2-1)。Prisma のクエリビルダを使う。",
},
{
selector: "MemberExpression[object.name='Prisma'][property.name='raw']",
message: "Prisma.raw は使わないこと(指示書AE 段階2-1)。Prisma のクエリビルダを使う。",
},
],
},
},
]);
export default eslintConfig;