prisma-lint の ルール一覧


## はじめに

`prisma-lint` に設定できる Rule は [RULES.md](https://github.com/loop-payments/prisma-lint/blob/main/RULES.md) に記載。
[RULES.md](https://github.com/loop-payments/prisma-lint/blob/main/RULES.md) の内容を日本語で説明したく訳を作成しました。

:::note warn
2024/11時点の情報です。最新の情報は下記URLを確認してください。

https://github.com/loop-payments/prisma-lint/blob/main/RULES.md

:::

## ルール

### ban-unbounded-string-type

文字列フィールドが長さを制限するために、データベースネイティブの型で定義されていることを確認する（例: @db.VarChar(x)）。
意図せず無制限の長さをサポートするAPIを構築しないようにすることを目的としている。
このルールは https://brandur.org/text に影響を受け作成された。

#### 設定

```ts
z.object({
  allowNativeTextType: z.boolean().optional(),
}).strict();
```

#### 例

```prisma:デフォルト
// OK
model User {
  id String @db.VarChar(36)
}

// NG
model User {
  id String
}

// NG
model User {
 id String @db.Text
}
```

```prisma:{ allowNativeTextType: true }の場合
// OK
model User {
  id String @db.Text
}
```

### enum-name-pascal-case

Enum 型の名前が PascalCase であることを確認する。

#### 設定

```ts:
z.object({
  allowList: z.array(z.union([z.string(), z.instanceof(RegExp)])).optional(),
  trimPrefix: z
    .union([
      z.string(),
      z.instanceof(RegExp),
      z.array(z.union([z.string(), z.instanceof(RegExp)])),
    ])
    .optional(),
}).strict();
```

#### 例

```prisma:デフォルト
// OK
enum ExampleOptions {
  value1
}

// NG
enum exampleOptions {
  value1
}

// NG
enum example_options {
 value1
}
```

### enum-value-snake-case

列挙型の値が snake_case であることを確認する。
このルールは、特定の列挙型の値を無視するためにprisma-lint-ignore-enumコメントが使用可能。

```ts
/// prisma-lint-ignore-enum enum-value-snake-case SCREAMING_SNAKE
```

上記記述によって `SCREAMING_SNAKE` が許可される。それ以外の値はsnake_caseである必要がある。

#### 設定

```ts
z.object({
  allowList: z.array(z.union([z.string(), z.instanceof(RegExp)])).optional(),
  trimPrefix: z
    .union([
      z.string(),
      z.instanceof(RegExp),
      z.array(z.union([z.string(), z.instanceof(RegExp)])),
    ])
    .optional(),
}).strict();
```

#### 例

```prisma:デフォルト
// OK
enum Example {
  value
}

// OK
enum Example {
  value_1
}

// NG
enum Example {
  Value
}

// NG
enum Example {
  VALUE
}

// NG
enum Example {
  camelCase
}
```

### field-name-camel-case

フィールド名がキャメルケースで記述されていることを確認する。

#### 設定

```ts
z.object({
  allowList: z.array(z.union([z.string(), z.instanceof(RegExp)])).optional(),
  trimPrefix: z
    .union([
      z.string(),
      z.instanceof(RegExp),
      z.array(z.union([z.string(), z.instanceof(RegExp)])),
    ])
    .optional(),
}).strict();
```

#### 例

```prisma:デフォルト
// OK
model User {
  rowId String @id
}

// NG
model User {
  RowId String @id
}

// NG
model User {
 row_id String @id
}
```

### field-name-mapping-snake-case

フィールドのマッピング名がスネークケースであることを確認する。

#### 設定

```ts
z.object({
  compoundWords: z.array(z.string()).optional(),
  requireUnderscorePrefixForIds: z.boolean().optional(),
})
  .strict()
  .optional();
```

#### 例

```prisma:デフォルト
// OK
model UserRole {
  userId String @map(name: "user_id")
}

model UserRole {
  // 単語のみで構成されるフィールド名はマッピング不要
  id String
  // 関連フィールドはマッピング不要
  grantedByUser User
}

// NG
model UserRole {
  userId String
}

model UserRole {
  userId String @map(name: "user_i_d")
}
```

```prisma:{ compoundWords: ["GraphQL"] } を指定した場合
// OK
model PersistedQuery {
  graphQLId String @map(name: "graphql_id")
}

// NG
model PersistedQuery {
  graphQLId String @map(name: "graph_q_l_id")
}
```

```prisma:{ requireUnderscorePrefixForIds: true } を指定した場合
// OK
model PersistedQuery {
  id String @id @map(name: "_id")
  otherField String @map(name: "other_field")
}

// NG
model PersistedQuery {
  id String @id @map(name: "id")
  otherField String @map(name: "other_field")
}
```

```prisma:enum を使用した場合
// OK
enum RoleType {
  ADMIN
  MEMBER
}

model UserRole {
  roleType RoleType @map(name: "role_type")
}

// NG
model UserRole {
  roleType RoleType
}
```

```prisma:カスタム型 を使用した場合
// OK
type UserInfo {
  institution String
}

model User {
  userInfo UserInfo @map(name: "user_info")
}

// NG
model User {
  userInfo UserInfo
}
```

```prisma:パラメータ付きコメント を使用した場合
// OK
type Post {
  /// prisma-lint-ignore-model field-name-mapping-snake-case tenantId
  tenantId String
  userId String @map(name: "user_id")
}

// NG
type Post {
  /// prisma-lint-ignore-model field-name-mapping-snake-case tenantId
  tenantId String
  userId String
}
```

### field-order

モデル内のフィールドが指定された順序で並んでいることを確認する。

order リストに含まれないフィールドは無視。
フィールドを必須にする場合は、require-field ルールを使用する。

order リストの最初のフィールドはモデルの先頭に、最後のフィールドはモデルの末尾に配置されることが要求される。特別なフィールド名 ... は、その位置に任意の数のフィールドが配置可能であることを示す。

#### 設定

```ts
z.object({
  order: z.array(z.string()),
}).strict();
```

#### 例

```prisma:{ order: ['tenantId', 'id', 'createdAt', 'updatedAt', '...'] } を指定した場合
// OK
model User {
  tenantId String
  id String @id
  email String
}

model User {
  tenantId String
  id String @id
  createdAt DateTime
  email String
}

// NG
model User {
  id String @id
  email String
  tenantId String
}
```

```prisma:{ order: ['tenantId', 'id', '...', 'createdAt', 'updatedAt'] } を指定した場合
// OK
model User {
  tenantId String
  id String
  email String
  createdAt DateTime
  updatedAt DateTime
}

model User {
  tenantId String
  id String
  email String
  createdAt DateTime
}

// NG
model User {
  id String @id
  createdAt DateTime
  email String
}
```

#### forbid-field

特定の名前のフィールドを禁止する。

#### 設定

```ts
z.object({
  forbid: z.array(z.union([z.string(), z.instanceof(RegExp)])),
}).strict();
```

#### 例

```prisma:{ forbid: ["id"] } を指定した場合
// OK
type Product {
  uuid String
}

// NG
type Product {
  id String
}
```

```prisma:{ forbid: ["/^(?!.*[aA]mountD6$).*D6$/"] } を指定した場合
// OK
type Product {
  id String
  priceAmountD6 Int
}

// NG
type Product {
  id Int
  priceD6 Int
}
```

#### forbid-required-ignored-field

デフォルト値のない必須の無視されたフィールドを禁止する。

このルールは、フィールドがクライアントに生成されない一方で、データベースが対応する非NULL列を要求することを防ぐ。

さらなる保護を検討する場合は、Prisma Safety の使用を推奨。

#### 例

```prisma:デフォルト
// OK
type Product {
  uuid String
  toBeRemoved String? @ignore
}

// OK
type Product {
  uuid String
  toBeRemoved Boolean @default(false) @ignore
}

// NG
type Product {
  uuid String
  toBeRemoved String @ignore
}
```

#### model-name-grammatical-number

モデル名が単数形または複数形のスタイルに準拠しているかをチェックする。

文法的数 (Grammatical number) に基づき、モデル名を制約する。

#### 設定

```ts
z.object({
  style: z.enum(['singular', 'plural']),
  allowlist: z.array(z.union([z.string(), z.instanceof(RegExp)])).optional(),
}).strict();
```

#### 例

```prisma:{ style: "singular" } を指定した場合
// OK
model User {
  id String @id
}

// NG
model Users {
  id String @id
}
```

```prisma:{ style: "plural" } を指定した場合
// OK
model Users {
  id String @id
}

// NG
model User {
  id String @id
}
```

```prisma:{ style: "singular", allowlist: ["UserData"] } を指定した場合
// OK
model UserData {
  id String @id
}

model User {
  id String @id
}

// NG ("Data" はデフォルトで複数形と見なされる)
model TenantData {
  id String @id
}

model Users {
  id String @id
}
```

```prisma:{ style: "singular", allowlist: ["/Data$/"] } を指定した場合
// OK
model UserData {
  id String @id
}

model TenantData {
  id String @id
}

// NG
model DataRecords {
  id String @id
}

model Users {
  id String @id
}
```

#### model-name-mapping-snake-case

モデルのマッピング名がスネークケース (snake_case) に準拠しているかをチェックする。

#### 設定

```ts
z.object({
  compoundWords: z.array(z.string()).optional(),
  irregularPlurals: z.record(z.string()).optional(),
  pluralize: z.boolean().optional(),
  trimPrefix: z.string().optional(),
})
  .strict()
  .optional();
```

#### 例

```prisma:デフォルト
// OK
model UserRole {
  id String @id
  @@map(name: "user_role")
}

// NG
model UserRole {
  id String @id
}

model UserRole {
  id String @id
  @@map(name: "user_roles")
}
```

```prisma:{ trimPrefix: "Db" } を指定した場合
// OK
model DbUserRole {
  id String @id
  @@map(name: "user_role")
}

// NG
model DbUserRole {
  id String @id
  @@map(name: "db_user_role")
}
```

```prisma:{ compoundWords: ["GraphQL"] } を指定した場合
// OK
model GraphQLPersistedQuery {
  id String @id
  @@map(name: "graphql_persisted_query")
}

// NG
model GraphQLPersistedQuery {
  id String @id
  @@map(name: "graph_q_l_persisted_query")
}
```

```prisma:{ pluralize: true } を指定した場合
// OK
model UserRole {
  id String @id
  @@map(name: "user_roles")
}

// NG
model UserRole {
  id String @id
}

model UserRole {
  id String @id
  @@map(name: "user_role")
}
```

#### model-name-pascal-case

モデル名がパスカルケース (PascalCase) に準拠しているかをチェックする。

#### 設定

```ts
z.object({
  allowList: z.array(z.union([z.string(), z.instanceof(RegExp)])).optional(),
  trimPrefix: z
    .union([
      z.string(),
      z.instanceof(RegExp),
      z.array(z.union([z.string(), z.instanceof(RegExp)])),
    ])
    .optional(),
}).strict();
```

#### 例

```prisma:デフォルト
// OK
model DbUser {
  id String @id
}

// NG
model dbUser {
  id String @id
}

// NG
model db_user {
  id String @id
}
```

#### model-name-prefix

モデル名が指定されたプレフィックスを含んでいるかをチェックする。

#### 設定

```ts
z.object({
  prefix: z.string(),
}).strict();
```

#### 例

```prisma:{ prefix: "Db" } の場合
// OK
model DbUser {
  id String @id
}

// NG
model User {
  id String @id
}
```

#### require-default-empty-arrays

配列フィールドにデフォルト値として空配列を設定することを要求する。

#### 動機

空配列をデフォルト値として明示的に設定することで、予期しないエラーを防ぐ。詳細な動機は Prisma lint issue #275 を参照。

#### 例

```prisma:デフォルト
// OK
model Post {
  tags String[] @default([])
}

// NG
model Post {
  tags String[]
}
```

#### require-field-index

特定のフィールドにインデックスが設定されていることを確認する。

#### 設定

```ts
z.object({
  forAllRelations: z.boolean().optional(),
  forNames: z
    .union([z.string(), z.array(z.union([z.string(), z.instanceof(RegExp)]))])
    .optional(),
}).strict();
```

#### 例

```prisma:{ forNames: ["createdAt"] } の場合
// OK
model User {
  createdAt DateTime @unique
}

model User {
  createdAt DateTime
  @@index([createdAt])
}

model User {
  createdAt DateTime
  id String
  @@index([createdAt, id])
}

// NG
model User {
  createdAt string
}

model User {
  createdAt DateTime
  id String
  @@index([id, createdAt])
}
```

```prisma:{ forNames: "/Id$/" } の場合
// OK
model User {
  tenantId String
  @@index([tenantId])
}

// NG
model User {
  tenantId String
}
```

```prisma:{ forAllRelations: true } の場合
// OK
type Bar {
  fooId String
  foo Foo @relation(fields: [fooId], references: [id])
  @@index([fooId])
}

// NG
type Bar {
  fooId String
  foo Foo @relation(fields: [fooId], references: [id])
}
```

#### require-field-type

特定のフィールドが特定の型を持っていることを確認する。

#### 設定

```ts
z.object({
  require: z.array(
    z.object({
      ifName: z.union([z.string(), z.instanceof(RegExp)]),
      type: z.string(),
    }),
  ),
}).strict();
```

#### 例

```prisma:{ require: [{ ifName: "id", type: "String" }] } の場合
// OK
model User {
  id String
}

// NG
model User {
  id Int
}
```

```prisma:{ require: [{ ifName: "/At$/", type: "DateTime" }] } の場合
// OK
model User {
  createdAt DateTime
  updatedAt DateTime
}

// NG
model User {
  createdAt String
  updatedAt String
}
```

#### require-field

モデルが特定のフィールドを持つことを確認する。

このルールは prisma-lint-ignore-model コメントを使用して特定のフィールドを無視することができる。例えば:

```prisma
/// prisma-lint-ignore-model require-field tenantId
```

これは、モデル内の tenantId フィールド違反のみを無視する。他の必須フィールドは引き続き適用される。複数のフィールドを無視する場合は、カンマで区切って指定する。

#### 設定

```ts
z.object({
  require: z.array(
    z.union([
      z.string(),
      z.object({
        name: z.string(),
        ifSibling: z.union([z.string(), z.instanceof(RegExp)]),
      }),
    ]),
  ),
}).strict();
```

#### 例

```prisma:{ require: ["id"] } の場合
// OK
model User {
  id Int @id
}

// NG
model User {
  name String
}
```

```prisma:{ require: [{ name: "currencyCode", ifSibling: "/mountD6$/" }] } の場合
// OK
model Product {
  currencyCode String
  priceAmountD6 Int
}

// NG
model Product {
  priceAmountD6 Int
}
```

## Ref

https://github.com/loop-payments/prisma-lint?tab=readme-ov-file

https://www.npmjs.com/package/prisma-lint