From bf475e88ca60d77111c7ef324d4e3080451f094c Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 10 Aug 2023 17:29:16 +0200 Subject: [PATCH] [v4] rule changes (#1812) --- .changeset/few-mails-sparkle.md | 57 ++ .../graphql.config.ts | 4 +- .../package.json | 5 +- examples/programmatic/eslint.config.js | 4 +- package.json | 1 - packages/plugin/CHANGELOG.md | 184 +++--- .../__snapshots__/alphabetize.spec.md | 121 ++-- .../__tests__/__snapshots__/examples.spec.md | 44 ++ .../__snapshots__/naming-convention.spec.md | 546 +++++++++++++++++- .../require-nullable-result-in-root.spec.md | 22 +- ...ble.spec.md => require-selections.spec.md} | 16 +- ...pec.md => unique-enum-value-names.spec.md} | 4 +- packages/plugin/__tests__/alphabetize.spec.ts | 29 +- packages/plugin/__tests__/examples.spec.ts | 2 +- .../__tests__/naming-convention.spec.ts | 28 +- .../require-nullable-result-in-root.spec.ts | 10 +- ...ble.spec.ts => require-selections.spec.ts} | 6 +- packages/plugin/__tests__/rules.spec.ts | 2 +- ...pec.ts => unique-enum-value-names.spec.ts} | 4 +- packages/plugin/package.json | 5 +- packages/plugin/src/configs/operations-all.ts | 7 +- .../src/configs/operations-recommended.ts | 4 +- packages/plugin/src/configs/schema-all.ts | 5 +- .../plugin/src/configs/schema-recommended.ts | 24 +- packages/plugin/src/parser.ts | 25 +- packages/plugin/src/rules/alphabetize.ts | 35 +- .../plugin/src/rules/graphql-js-validation.ts | 17 +- packages/plugin/src/rules/index.ts | 8 +- .../src/rules/match-document-filename.ts | 2 +- .../plugin/src/rules/naming-convention.ts | 16 + packages/plugin/src/rules/no-root-type.ts | 2 +- packages/plugin/src/rules/no-unused-fields.ts | 1 + .../plugin/src/rules/require-description.ts | 2 +- .../src/rules/require-import-fragment.ts | 3 +- .../rules/require-nullable-result-in-root.ts | 4 +- ...hen-available.ts => require-selections.ts} | 2 +- ...plicates.ts => unique-enum-value-names.ts} | 5 +- .../plugin/src/rules/unique-fragment-name.ts | 2 +- packages/plugin/src/utils.ts | 14 +- packages/plugin/vite.config.ts | 12 + pnpm-lock.yaml | 140 +---- scripts/generate-configs.ts | 2 +- scripts/patch-graphql.ts | 17 - website/src/pages/rules/_meta.json | 3 +- website/src/pages/rules/alphabetize.md | 28 +- ...precated-rules.mdx => deprecated-rules.md} | 8 + website/src/pages/rules/index.md | 11 +- ...case-insensitive-enum-values-duplicates.md | 47 -- .../pages/rules/{prettier.mdx => prettier.md} | 0 ...hen-available.md => require-selections.md} | 12 +- .../pages/rules/unique-enum-value-names.md | 38 +- 51 files changed, 1062 insertions(+), 528 deletions(-) create mode 100644 .changeset/few-mails-sparkle.md rename packages/plugin/__tests__/__snapshots__/{require-id-when-available.spec.md => require-selections.spec.md} (84%) rename packages/plugin/__tests__/__snapshots__/{no-case-insensitive-enum-values-duplicates.spec.md => unique-enum-value-names.spec.md} (80%) rename packages/plugin/__tests__/{require-id-when-available.spec.ts => require-selections.spec.ts} (97%) rename packages/plugin/__tests__/{no-case-insensitive-enum-values-duplicates.spec.ts => unique-enum-value-names.spec.ts} (59%) rename packages/plugin/src/rules/{require-id-when-available.ts => require-selections.ts} (99%) rename packages/plugin/src/rules/{no-case-insensitive-enum-values-duplicates.ts => unique-enum-value-names.ts} (88%) delete mode 100644 scripts/patch-graphql.ts rename website/src/pages/rules/{deprecated-rules.mdx => deprecated-rules.md} (73%) delete mode 100644 website/src/pages/rules/no-case-insensitive-enum-values-duplicates.md rename website/src/pages/rules/{prettier.mdx => prettier.md} (100%) rename website/src/pages/rules/{require-id-when-available.md => require-selections.md} (80%) diff --git a/.changeset/few-mails-sparkle.md b/.changeset/few-mails-sparkle.md new file mode 100644 index 00000000000..954bb3d0683 --- /dev/null +++ b/.changeset/few-mails-sparkle.md @@ -0,0 +1,57 @@ +--- +'@graphql-eslint/eslint-plugin': major +--- + +- `alphabetize` rule changes + + - add `definitions: true` option for `schema-all`/`operations-all` configs + - rename `values: ['EnumTypeDefinition']` to `values: true` + - rename `variables: ['OperationDefinition']` to `variables: true` + - add `groups: ['id', '*', 'createdAt', 'updatedAt']` for `schema-all`/`operations-all` configs + +- `require-id-when-available` rule changes + + - rename rule to `require-selections` + +- update `schema-all`/`operations-all` configs + +- `require-description` rule changes + + - add `rootField: true` option for `schema-recommended` config + +- require `eslint` at least `>=8.44.0` as peerDependency + +- `naming-convention` + + - add new options for `schema-recommended` config + + ```json5 + { + 'EnumTypeDefinition,EnumTypeExtension': { + forbiddenPrefixes: ['Enum'], + forbiddenSuffixes: ['Enum'] + }, + 'InterfaceTypeDefinition,InterfaceTypeExtension': { + forbiddenPrefixes: ['Interface'], + forbiddenSuffixes: ['Interface'] + }, + 'UnionTypeDefinition,UnionTypeExtension': { + forbiddenPrefixes: ['Union'], + forbiddenSuffixes: ['Union'] + }, + 'ObjectTypeDefinition,ObjectTypeExtension': { + forbiddenPrefixes: ['Type'], + forbiddenSuffixes: ['Type'] + } + } + ``` + +- remove graphql-js' `unique-enum-value-names` rule + +- rename `no-case-insensitive-enum-values-duplicates` to `unique-enum-value-names` + + > Since this rule reports case-insensitive enum values duplicates too + +- `require-nullable-result-in-root` rule changes + + Do not check subscriptions diff --git a/examples/multiple-projects-graphql-config/graphql.config.ts b/examples/multiple-projects-graphql-config/graphql.config.ts index 37e71f05360..943696ed494 100644 --- a/examples/multiple-projects-graphql-config/graphql.config.ts +++ b/examples/multiple-projects-graphql-config/graphql.config.ts @@ -1,5 +1,5 @@ -import { IGraphQLConfig } from 'graphql-config'; -import { GraphQLTagPluckOptions } from '@graphql-tools/graphql-tag-pluck'; +import type { IGraphQLConfig } from 'graphql-config'; +import type { GraphQLTagPluckOptions } from '@graphql-tools/graphql-tag-pluck'; const config: IGraphQLConfig = { projects: { diff --git a/examples/multiple-projects-graphql-config/package.json b/examples/multiple-projects-graphql-config/package.json index 6671f9680e4..f9ebc09077e 100644 --- a/examples/multiple-projects-graphql-config/package.json +++ b/examples/multiple-projects-graphql-config/package.json @@ -11,9 +11,6 @@ }, "devDependencies": { "@graphql-eslint/eslint-plugin": "workspace:*", - "cosmiconfig-typescript-loader": "5.0.0", - "eslint": "8.46.0", - "ts-node": "10.9.1", - "typescript": "5.1.6" + "eslint": "8.46.0" } } diff --git a/examples/programmatic/eslint.config.js b/examples/programmatic/eslint.config.js index 013835cd4a9..7d953da3a8e 100644 --- a/examples/programmatic/eslint.config.js +++ b/examples/programmatic/eslint.config.js @@ -21,7 +21,7 @@ export default [ }, }, rules: { - '@graphql-eslint/require-id-when-available': ['error', { fieldName: '_id' }], + '@graphql-eslint/require-selections': ['error', { fieldName: '_id' }], '@graphql-eslint/unique-fragment-name': 'error', '@graphql-eslint/no-anonymous-operations': 'error', '@graphql-eslint/naming-convention': [ @@ -34,7 +34,7 @@ export default [ }, }, ], - '@graphql-eslint/no-case-insensitive-enum-values-duplicates': ['error'], + '@graphql-eslint/unique-enum-value-names': 'error', '@graphql-eslint/require-description': ['error', { FieldDefinition: true }], }, }, diff --git a/package.json b/package.json index 98bc27b9ed1..fd16cc5aec1 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "generate:configs": "tsx scripts/generate-configs.ts", "lint": "eslint --ignore-path .gitignore --cache .", "lint:prettier": "prettier --cache --check .", - "postinstall": "tsx scripts/patch-graphql.ts", "prebuild": "rimraf tsconfig.tsbuildinfo", "prerelease": "NODE_ENV=production pnpm build", "prettier": "pnpm lint:prettier --write", diff --git a/packages/plugin/CHANGELOG.md b/packages/plugin/CHANGELOG.md index 12f342ef835..fdf01108e8f 100644 --- a/packages/plugin/CHANGELOG.md +++ b/packages/plugin/CHANGELOG.md @@ -552,29 +552,29 @@ Special thanks to @connorjs ### Before - ```json + ```json5 { - "@graphql-eslint/avoid-operation-name-prefix": [ - "error", + '@graphql-eslint/avoid-operation-name-prefix': [ + 'error', { - "keywords": ["Query", "Mutation", "Subscription", "Get"] + keywords: ['Query', 'Mutation', 'Subscription', 'Get'] } ], - "@graphql-eslint/no-operation-name-suffix": "error" + '@graphql-eslint/no-operation-name-suffix': 'error' } ``` ### After - ```json + ```json5 { - "@graphql-eslint/naming-convention": [ - "error", + '@graphql-eslint/naming-convention': [ + 'error', { - "OperationDefinition": { - "style": "PascalCase", - "forbiddenPrefixes": ["Query", "Mutation", "Subscription", "Get"], - "forbiddenSuffixes": ["Query", "Mutation", "Subscription"] + OperationDefinition: { + style: 'PascalCase', + forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'], + forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'] } } ] @@ -605,24 +605,24 @@ Special thanks to @connorjs ### Before - ```json + ```json5 { - "@graphql-eslint/naming-convention": [ - "error", + '@graphql-eslint/naming-convention': [ + 'error', { - "ObjectTypeDefinition": "PascalCase", - "InterfaceTypeDefinition": "PascalCase", - "EnumTypeDefinition": "PascalCase", - "ScalarTypeDefinition": "PascalCase", - "InputObjectTypeDefinition": "PascalCase", - "UnionTypeDefinition": "PascalCase", - "FieldDefinition": "camelCase", - "InputValueDefinition": "camelCase", - "QueryDefinition": { - "forbiddenPrefixes": ["get"] + ObjectTypeDefinition: 'PascalCase', + InterfaceTypeDefinition: 'PascalCase', + EnumTypeDefinition: 'PascalCase', + ScalarTypeDefinition: 'PascalCase', + InputObjectTypeDefinition: 'PascalCase', + UnionTypeDefinition: 'PascalCase', + FieldDefinition: 'camelCase', + InputValueDefinition: 'camelCase', + QueryDefinition: { + forbiddenPrefixes: ['get'] }, - "leadingUnderscore": "allow", - "trailingUnderscore": "allow" + leadingUnderscore: 'allow', + trailingUnderscore: 'allow' } ] } @@ -630,19 +630,19 @@ Special thanks to @connorjs ### After - ```json + ```json5 { - "@graphql-eslint/naming-convention": [ - "error", + '@graphql-eslint/naming-convention': [ + 'error', { - "types": "PascalCase", - "FieldDefinition": "camelCase", - "InputValueDefinition": "camelCase", - "FieldDefinition[parent.name.value=Query]": { - "forbiddenPrefixes": ["get"] + types: 'PascalCase', + FieldDefinition: 'camelCase', + InputValueDefinition: 'camelCase', + 'FieldDefinition[parent.name.value=Query]': { + forbiddenPrefixes: ['get'] }, - "allowLeadingUnderscore": true, - "allowTrailingUnderscore": true + allowLeadingUnderscore: true, + allowTrailingUnderscore: true } ] } @@ -663,21 +663,21 @@ Special thanks to @connorjs ### Before - ```json + ```json5 { - "@graphql-eslint/require-description": [ - "error", + '@graphql-eslint/require-description': [ + 'error', { - "on": [ - "ObjectTypeDefinition", - "InterfaceTypeDefinition", - "EnumTypeDefinition", - "InputObjectTypeDefinition", - "UnionTypeDefinition", - "FieldDefinition", - "InputValueDefinition", - "EnumValueDefinition", - "DirectiveDefinition" + on: [ + 'ObjectTypeDefinition', + 'InterfaceTypeDefinition', + 'EnumTypeDefinition', + 'InputObjectTypeDefinition', + 'UnionTypeDefinition', + 'FieldDefinition', + 'InputValueDefinition', + 'EnumValueDefinition', + 'DirectiveDefinition' ] } ] @@ -686,16 +686,16 @@ Special thanks to @connorjs ### After - ```json + ```json5 { - "@graphql-eslint/require-description": [ - "error", + '@graphql-eslint/require-description': [ + 'error', { - "types": true, - "FieldDefinition": true, - "InputValueDefinition": true, - "EnumValueDefinition": true, - "DirectiveDefinition": true + types: true, + FieldDefinition: true, + InputValueDefinition: true, + EnumValueDefinition: true, + DirectiveDefinition: true } ] } @@ -886,39 +886,41 @@ Special thanks to @connorjs As a drop-in replacement for the whole set of rules we had in `validate-against-schema`, you can use this: -``` - "@graphql-eslint/executable-definitions": "error", - "@graphql-eslint/fields-on-correct-type": "error", - "@graphql-eslint/fragments-on-composite-type": "error", - "@graphql-eslint/known-argument-names": "error", - "@graphql-eslint/known-directives": "error", - "@graphql-eslint/known-fragment-names": "error", - "@graphql-eslint/known-type-names": "error", - "@graphql-eslint/lone-anonymous-operation": "error", - "@graphql-eslint/lone-schema-definition": "error", - "@graphql-eslint/no-fragment-cycles": "error", - "@graphql-eslint/no-undefined-variables": "error", - "@graphql-eslint/no-unused-fragments": "error", - "@graphql-eslint/no-unused-variables": "error", - "@graphql-eslint/overlapping-fields-can-be-merged": "error", - "@graphql-eslint/possible-fragment-spread": "error", - "@graphql-eslint/possible-type-extension": "error", - "@graphql-eslint/provided-required-arguments": "error", - "@graphql-eslint/scalar-leafs": "error", - "@graphql-eslint/one-field-subscriptions": "error", - "@graphql-eslint/unique-argument-names": "error", - "@graphql-eslint/unique-directive-names": "error", - "@graphql-eslint/unique-directive-names-per-location": "error", - "@graphql-eslint/unique-enum-value-names": "error", - "@graphql-eslint/unique-field-definition-names": "error", - "@graphql-eslint/unique-input-field-names": "error", - "@graphql-eslint/unique-operation-types": "error", - "@graphql-eslint/unique-type-names": "error", - "@graphql-eslint/unique-variable-names": "error", - "@graphql-eslint/value-literals-of-correct-type": "error", - "@graphql-eslint/variables-are-input-types": "error", - "@graphql-eslint/variables-in-allowed-position": "error" -``` + ```json5 + { + '@graphql-eslint/executable-definitions': 'error', + '@graphql-eslint/fields-on-correct-type': 'error', + '@graphql-eslint/fragments-on-composite-type': 'error', + '@graphql-eslint/known-argument-names': 'error', + '@graphql-eslint/known-directives': 'error', + '@graphql-eslint/known-fragment-names': 'error', + '@graphql-eslint/known-type-names': 'error', + '@graphql-eslint/lone-anonymous-operation': 'error', + '@graphql-eslint/lone-schema-definition': 'error', + '@graphql-eslint/no-fragment-cycles': 'error', + '@graphql-eslint/no-undefined-variables': 'error', + '@graphql-eslint/no-unused-fragments': 'error', + '@graphql-eslint/no-unused-variables': 'error', + '@graphql-eslint/overlapping-fields-can-be-merged': 'error', + '@graphql-eslint/possible-fragment-spread': 'error', + '@graphql-eslint/possible-type-extension': 'error', + '@graphql-eslint/provided-required-arguments': 'error', + '@graphql-eslint/scalar-leafs': 'error', + '@graphql-eslint/one-field-subscriptions': 'error', + '@graphql-eslint/unique-argument-names': 'error', + '@graphql-eslint/unique-directive-names': 'error', + '@graphql-eslint/unique-directive-names-per-location': 'error', + '@graphql-eslint/unique-enum-value-names': 'error', + '@graphql-eslint/unique-field-definition-names': 'error', + '@graphql-eslint/unique-input-field-names': 'error', + '@graphql-eslint/unique-operation-types': 'error', + '@graphql-eslint/unique-type-names': 'error', + '@graphql-eslint/unique-variable-names': 'error', + '@graphql-eslint/value-literals-of-correct-type': 'error', + '@graphql-eslint/variables-are-input-types': 'error', + '@graphql-eslint/variables-in-allowed-position': 'error' + } + ``` - 61251e7: Bump dependencies and update minimum Node version to `v12` diff --git a/packages/plugin/__tests__/__snapshots__/alphabetize.spec.md b/packages/plugin/__tests__/__snapshots__/alphabetize.spec.md index a8fb875f688..287c08f5962 100644 --- a/packages/plugin/__tests__/__snapshots__/alphabetize.spec.md +++ b/packages/plugin/__tests__/__snapshots__/alphabetize.spec.md @@ -15,8 +15,7 @@ exports[`alphabetize > invalid > Invalid #1 1`] = ` { "fields": [ "ObjectTypeDefinition" - ], - "definitions": false + ] } #### ❌ Error 1/2 @@ -58,8 +57,7 @@ exports[`alphabetize > invalid > Invalid #2 1`] = ` { "fields": [ "ObjectTypeDefinition" - ], - "definitions": false + ] } #### ❌ Error @@ -93,8 +91,7 @@ exports[`alphabetize > invalid > Invalid #3 1`] = ` { "fields": [ "InterfaceTypeDefinition" - ], - "definitions": false + ] } #### ❌ Error 1/2 @@ -135,8 +132,7 @@ exports[`alphabetize > invalid > Invalid #4 1`] = ` { "fields": [ "InputObjectTypeDefinition" - ], - "definitions": false + ] } #### ❌ Error 1/2 @@ -178,8 +174,7 @@ exports[`alphabetize > invalid > Invalid #5 1`] = ` { "fields": [ "InputObjectTypeDefinition" - ], - "definitions": false + ] } #### ❌ Error @@ -212,10 +207,7 @@ exports[`alphabetize > invalid > Invalid #6 1`] = ` #### ⚙️ Options { - "values": [ - "EnumTypeDefinition" - ], - "definitions": false + "values": true } #### ❌ Error 1/2 @@ -255,10 +247,7 @@ exports[`alphabetize > invalid > Invalid #7 1`] = ` #### ⚙️ Options { - "values": [ - "EnumTypeDefinition" - ], - "definitions": false + "values": true } #### ❌ Error @@ -288,8 +277,7 @@ exports[`alphabetize > invalid > Invalid #8 1`] = ` { "arguments": [ "DirectiveDefinition" - ], - "definitions": false + ] } #### ❌ Error 1/2 @@ -319,8 +307,7 @@ exports[`alphabetize > invalid > Invalid #9 1`] = ` { "arguments": [ "FieldDefinition" - ], - "definitions": false + ] } #### ❌ Error 1/2 @@ -358,8 +345,7 @@ exports[`alphabetize > invalid > Invalid #10 1`] = ` { "selections": [ "FragmentDefinition" - ], - "definitions": false + ] } #### ❌ Error 1/2 @@ -406,8 +392,7 @@ exports[`alphabetize > invalid > Invalid #11 1`] = ` { "selections": [ "OperationDefinition" - ], - "definitions": false + ] } #### ❌ Error 1/4 @@ -466,13 +451,10 @@ exports[`alphabetize > invalid > Invalid #12 1`] = ` #### ⚙️ Options { - "variables": [ - "OperationDefinition" - ], + "variables": true, "arguments": [ "Field" - ], - "definitions": false + ] } #### ❌ Error 1/4 @@ -528,10 +510,7 @@ exports[`alphabetize > invalid > should compare with lexicographic order 1`] = ` #### ⚙️ Options { - "values": [ - "EnumTypeDefinition" - ], - "definitions": false + "values": true } #### ❌ Error 1/3 @@ -593,8 +572,7 @@ exports[`alphabetize > invalid > should move comment 1`] = ` { "fields": [ "ObjectTypeDefinition" - ], - "definitions": false + ] } #### ❌ Error 1/3 @@ -663,8 +641,7 @@ exports[`alphabetize > invalid > should sort by group when \`*\` at the start 1` "updatedAt", "id", "createdAt" - ], - "definitions": false + ] } #### ❌ Error 1/4 @@ -738,8 +715,7 @@ exports[`alphabetize > invalid > should sort by group when \`*\` is at the end 1 "id", "createdAt", "*" - ], - "definitions": false + ] } #### ❌ Error 1/4 @@ -813,8 +789,7 @@ exports[`alphabetize > invalid > should sort by group when \`*\` is between 1`] "*", "createdAt", "updatedAt" - ], - "definitions": false + ] } #### ❌ Error 1/4 @@ -1056,6 +1031,63 @@ exports[`alphabetize > invalid > should sort definitions 1`] = ` 59 | # END `; +exports[`alphabetize > invalid > should sort selections by group when \`*\` is between 1`] = ` +#### ⌨️ Code + + 1 | { + 2 | zz + 3 | updatedAt + 4 | createdAt + 5 | aa + 6 | id + 7 | } + +#### ⚙️ Options + + { + "selections": [ + "OperationDefinition" + ], + "groups": [ + "id", + "*", + "createdAt", + "updatedAt" + ] + } + +#### ❌ Error 1/3 + + 3 | updatedAt + > 4 | createdAt + | ^^^^^^^^^ field "createdAt" should be before field "updatedAt" + 5 | aa + +#### ❌ Error 2/3 + + 4 | createdAt + > 5 | aa + | ^^ field "aa" should be before field "createdAt" + 6 | id + +#### ❌ Error 3/3 + + 5 | aa + > 6 | id + | ^^ field "id" should be before field "aa" + 7 | } + +#### 🔧 Autofix output + + 1 | { + 2 | id + 3 | aa + 4 | zz + 5 | createdAt + 6 | updatedAt + 7 | } +`; + exports[`alphabetize > invalid > should sort when selection is aliased 1`] = ` #### ⌨️ Code @@ -1072,8 +1104,7 @@ exports[`alphabetize > invalid > should sort when selection is aliased 1`] = ` { "selections": [ "OperationDefinition" - ], - "definitions": false + ] } #### ❌ Error 1/2 diff --git a/packages/plugin/__tests__/__snapshots__/examples.spec.md b/packages/plugin/__tests__/__snapshots__/examples.spec.md index 21cbea9484e..4924559daf4 100644 --- a/packages/plugin/__tests__/__snapshots__/examples.spec.md +++ b/packages/plugin/__tests__/__snapshots__/examples.spec.md @@ -91,6 +91,28 @@ exports[`Examples > should work in monorepo 1`] = ` ruleId: @graphql-eslint/require-description, severity: 2, }, + { + column: 3, + endColumn: 7, + endLine: 11, + line: 11, + message: Description is required for field "post" in type "Query", + messageId: require-description, + nodeType: null, + ruleId: @graphql-eslint/require-description, + severity: 2, + }, + { + column: 3, + endColumn: 8, + endLine: 12, + line: 12, + message: Description is required for field "posts" in type "Query", + messageId: require-description, + nodeType: null, + ruleId: @graphql-eslint/require-description, + severity: 2, + }, ], }, { @@ -139,6 +161,28 @@ exports[`Examples > should work in monorepo 1`] = ` ruleId: @graphql-eslint/require-description, severity: 2, }, + { + column: 3, + endColumn: 7, + endLine: 10, + line: 10, + message: Description is required for field "user" in type "Query", + messageId: require-description, + nodeType: null, + ruleId: @graphql-eslint/require-description, + severity: 2, + }, + { + column: 3, + endColumn: 8, + endLine: 11, + line: 11, + message: Description is required for field "users" in type "Query", + messageId: require-description, + nodeType: null, + ruleId: @graphql-eslint/require-description, + severity: 2, + }, ], }, ] diff --git a/packages/plugin/__tests__/__snapshots__/naming-convention.spec.md b/packages/plugin/__tests__/__snapshots__/naming-convention.spec.md index f18828aaba5..ed4bd36eeed 100644 --- a/packages/plugin/__tests__/__snapshots__/naming-convention.spec.md +++ b/packages/plugin/__tests__/__snapshots__/naming-convention.spec.md @@ -1976,6 +1976,24 @@ exports[`naming-convention > invalid > schema-recommended config 1`] = ` 13 | fieldSubscription: ID 14 | subscriptionField: ID 15 | } + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } #### ⚙️ Options @@ -2011,11 +2029,43 @@ exports[`naming-convention > invalid > schema-recommended config 1`] = ` "Subscription" ] }, + "EnumTypeDefinition,EnumTypeExtension": { + "forbiddenPrefixes": [ + "Enum" + ], + "forbiddenSuffixes": [ + "Enum" + ] + }, + "InterfaceTypeDefinition,InterfaceTypeExtension": { + "forbiddenPrefixes": [ + "Interface" + ], + "forbiddenSuffixes": [ + "Interface" + ] + }, + "UnionTypeDefinition,UnionTypeExtension": { + "forbiddenPrefixes": [ + "Union" + ], + "forbiddenSuffixes": [ + "Union" + ] + }, + "ObjectTypeDefinition,ObjectTypeExtension": { + "forbiddenPrefixes": [ + "Type" + ], + "forbiddenSuffixes": [ + "Type" + ] + }, "allowLeadingUnderscore": false, "allowTrailingUnderscore": false } -#### ❌ Error 1/7 +#### ❌ Error 1/15 1 | type Query { > 2 | fieldQuery: ID @@ -2039,8 +2089,26 @@ exports[`naming-convention > invalid > schema-recommended config 1`] = ` 13 | fieldSubscription: ID 14 | subscriptionField: ID 15 | } - -#### ❌ Error 2/7 + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 2/15 2 | fieldQuery: ID > 3 | queryField: ID @@ -2064,8 +2132,26 @@ exports[`naming-convention > invalid > schema-recommended config 1`] = ` 13 | fieldSubscription: ID 14 | subscriptionField: ID 15 | } - -#### ❌ Error 3/7 + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 3/15 3 | queryField: ID > 4 | getField: ID @@ -2089,8 +2175,26 @@ exports[`naming-convention > invalid > schema-recommended config 1`] = ` 13 | fieldSubscription: ID 14 | subscriptionField: ID 15 | } - -#### ❌ Error 4/7 + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 4/15 7 | type Mutation { > 8 | fieldMutation: ID @@ -2114,8 +2218,26 @@ exports[`naming-convention > invalid > schema-recommended config 1`] = ` 13 | fieldSubscription: ID 14 | subscriptionField: ID 15 | } - -#### ❌ Error 5/7 + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 5/15 8 | fieldMutation: ID > 9 | mutationField: ID @@ -2139,8 +2261,26 @@ exports[`naming-convention > invalid > schema-recommended config 1`] = ` 13 | fieldSubscription: ID 14 | subscriptionField: ID 15 | } - -#### ❌ Error 6/7 + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 6/15 12 | type Subscription { > 13 | fieldSubscription: ID @@ -2164,8 +2304,26 @@ exports[`naming-convention > invalid > schema-recommended config 1`] = ` 13 | field: ID 14 | subscriptionField: ID 15 | } - -#### ❌ Error 7/7 + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 7/15 13 | fieldSubscription: ID > 14 | subscriptionField: ID @@ -2189,6 +2347,368 @@ exports[`naming-convention > invalid > schema-recommended config 1`] = ` 13 | fieldSubscription: ID 14 | Field: ID 15 | } + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 8/15 + + 16 | + > 17 | enum TestEnum + | ^^^^^^^^ Enumerator "TestEnum" should not have "Enum" suffix + 18 | extend enum EnumTest { + +#### 💡 Suggestion: Rename to \`Test\` + + 1 | type Query { + 2 | fieldQuery: ID + 3 | queryField: ID + 4 | getField: ID + 5 | } + 6 | + 7 | type Mutation { + 8 | fieldMutation: ID + 9 | mutationField: ID + 10 | } + 11 | + 12 | type Subscription { + 13 | fieldSubscription: ID + 14 | subscriptionField: ID + 15 | } + 16 | + 17 | enum Test + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 9/15 + + 17 | enum TestEnum + > 18 | extend enum EnumTest { + | ^^^^^^^^ EnumTypeExtension "EnumTest" should not have "Enum" prefix + 19 | A + +#### 💡 Suggestion: Rename to \`Test\` + + 1 | type Query { + 2 | fieldQuery: ID + 3 | queryField: ID + 4 | getField: ID + 5 | } + 6 | + 7 | type Mutation { + 8 | fieldMutation: ID + 9 | mutationField: ID + 10 | } + 11 | + 12 | type Subscription { + 13 | fieldSubscription: ID + 14 | subscriptionField: ID + 15 | } + 16 | + 17 | enum TestEnum + 18 | extend enum Test { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 10/15 + + 21 | + > 22 | interface TestInterface + | ^^^^^^^^^^^^^ Interface "TestInterface" should not have "Interface" suffix + 23 | extend interface InterfaceTest { + +#### 💡 Suggestion: Rename to \`Test\` + + 1 | type Query { + 2 | fieldQuery: ID + 3 | queryField: ID + 4 | getField: ID + 5 | } + 6 | + 7 | type Mutation { + 8 | fieldMutation: ID + 9 | mutationField: ID + 10 | } + 11 | + 12 | type Subscription { + 13 | fieldSubscription: ID + 14 | subscriptionField: ID + 15 | } + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface Test + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 11/15 + + 22 | interface TestInterface + > 23 | extend interface InterfaceTest { + | ^^^^^^^^^^^^^ InterfaceTypeExtension "InterfaceTest" should not have "Interface" prefix + 24 | id: ID + +#### 💡 Suggestion: Rename to \`Test\` + + 1 | type Query { + 2 | fieldQuery: ID + 3 | queryField: ID + 4 | getField: ID + 5 | } + 6 | + 7 | type Mutation { + 8 | fieldMutation: ID + 9 | mutationField: ID + 10 | } + 11 | + 12 | type Subscription { + 13 | fieldSubscription: ID + 14 | subscriptionField: ID + 15 | } + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface Test { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 12/15 + + 26 | + > 27 | union TestUnion + | ^^^^^^^^^ Union "TestUnion" should not have "Union" suffix + 28 | extend union UnionTest = TestInterface + +#### 💡 Suggestion: Rename to \`Test\` + + 1 | type Query { + 2 | fieldQuery: ID + 3 | queryField: ID + 4 | getField: ID + 5 | } + 6 | + 7 | type Mutation { + 8 | fieldMutation: ID + 9 | mutationField: ID + 10 | } + 11 | + 12 | type Subscription { + 13 | fieldSubscription: ID + 14 | subscriptionField: ID + 15 | } + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union Test + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 13/15 + + 27 | union TestUnion + > 28 | extend union UnionTest = TestInterface + | ^^^^^^^^^ UnionTypeExtension "UnionTest" should not have "Union" prefix + 29 | + +#### 💡 Suggestion: Rename to \`Test\` + + 1 | type Query { + 2 | fieldQuery: ID + 3 | queryField: ID + 4 | getField: ID + 5 | } + 6 | + 7 | type Mutation { + 8 | fieldMutation: ID + 9 | mutationField: ID + 10 | } + 11 | + 12 | type Subscription { + 13 | fieldSubscription: ID + 14 | subscriptionField: ID + 15 | } + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union Test = TestInterface + 29 | + 30 | type TestType + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 14/15 + + 29 | + > 30 | type TestType + | ^^^^^^^^ Type "TestType" should not have "Type" suffix + 31 | extend type TypeTest { + +#### 💡 Suggestion: Rename to \`Test\` + + 1 | type Query { + 2 | fieldQuery: ID + 3 | queryField: ID + 4 | getField: ID + 5 | } + 6 | + 7 | type Mutation { + 8 | fieldMutation: ID + 9 | mutationField: ID + 10 | } + 11 | + 12 | type Subscription { + 13 | fieldSubscription: ID + 14 | subscriptionField: ID + 15 | } + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type Test + 31 | extend type TypeTest { + 32 | id: ID + 33 | } + +#### ❌ Error 15/15 + + 30 | type TestType + > 31 | extend type TypeTest { + | ^^^^^^^^ ObjectTypeExtension "TypeTest" should not have "Type" prefix + 32 | id: ID + +#### 💡 Suggestion: Rename to \`Test\` + + 1 | type Query { + 2 | fieldQuery: ID + 3 | queryField: ID + 4 | getField: ID + 5 | } + 6 | + 7 | type Mutation { + 8 | fieldMutation: ID + 9 | mutationField: ID + 10 | } + 11 | + 12 | type Subscription { + 13 | fieldSubscription: ID + 14 | subscriptionField: ID + 15 | } + 16 | + 17 | enum TestEnum + 18 | extend enum EnumTest { + 19 | A + 20 | } + 21 | + 22 | interface TestInterface + 23 | extend interface InterfaceTest { + 24 | id: ID + 25 | } + 26 | + 27 | union TestUnion + 28 | extend union UnionTest = TestInterface + 29 | + 30 | type TestType + 31 | extend type Test { + 32 | id: ID + 33 | } `; exports[`naming-convention > invalid > should error when selected type names do not match require prefixes 1`] = ` diff --git a/packages/plugin/__tests__/__snapshots__/require-nullable-result-in-root.spec.md b/packages/plugin/__tests__/__snapshots__/require-nullable-result-in-root.spec.md index f0d0452db9f..f1516626d18 100644 --- a/packages/plugin/__tests__/__snapshots__/require-nullable-result-in-root.spec.md +++ b/packages/plugin/__tests__/__snapshots__/require-nullable-result-in-root.spec.md @@ -30,30 +30,16 @@ exports[`require-nullable-result-in-root > invalid > Invalid #1 1`] = ` exports[`require-nullable-result-in-root > invalid > should work with default scalars 1`] = ` #### ⌨️ Code - 1 | type MySubscription - 2 | extend type MySubscription { - 3 | foo: Boolean! - 4 | } - 5 | schema { - 6 | subscription: MySubscription - 7 | } + 1 | type Mutation { foo: Boolean! } #### ❌ Error - 2 | extend type MySubscription { - > 3 | foo: Boolean! - | ^^^^^^^ Unexpected non-null result Boolean in type "MySubscription" - 4 | } + > 1 | type Mutation { foo: Boolean! } + | ^^^^^^^ Unexpected non-null result Boolean in type "Mutation" #### 💡 Suggestion: Make Boolean nullable - 1 | type MySubscription - 2 | extend type MySubscription { - 3 | foo: Boolean - 4 | } - 5 | schema { - 6 | subscription: MySubscription - 7 | } + 1 | type Mutation { foo: Boolean } `; exports[`require-nullable-result-in-root > invalid > should work with extend query 1`] = ` diff --git a/packages/plugin/__tests__/__snapshots__/require-id-when-available.spec.md b/packages/plugin/__tests__/__snapshots__/require-selections.spec.md similarity index 84% rename from packages/plugin/__tests__/__snapshots__/require-id-when-available.spec.md rename to packages/plugin/__tests__/__snapshots__/require-selections.spec.md index 433e8631232..e6cf46ffc38 100644 --- a/packages/plugin/__tests__/__snapshots__/require-id-when-available.spec.md +++ b/packages/plugin/__tests__/__snapshots__/require-selections.spec.md @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`require-id-when-available > invalid > Invalid #1 1`] = ` +exports[`require-selections > invalid > Invalid #1 1`] = ` #### ⌨️ Code 1 | { hasId { name } } @@ -16,7 +16,7 @@ exports[`require-id-when-available > invalid > Invalid #1 1`] = ` 1 | { hasId { id name } } `; -exports[`require-id-when-available > invalid > Invalid #2 1`] = ` +exports[`require-selections > invalid > Invalid #2 1`] = ` #### ⌨️ Code 1 | { hasId { id } } @@ -38,7 +38,7 @@ exports[`require-id-when-available > invalid > Invalid #2 1`] = ` 1 | { hasId { name id } } `; -exports[`require-id-when-available > invalid > should not work with n nested fragments if you never get the id 1`] = ` +exports[`require-selections > invalid > should not work with n nested fragments if you never get the id 1`] = ` #### ⌨️ Code 1 | query User { @@ -64,7 +64,7 @@ exports[`require-id-when-available > invalid > should not work with n nested fra 5 | } `; -exports[`require-id-when-available > invalid > should report an error about missing \`posts.id\` field in fragment 1`] = ` +exports[`require-selections > invalid > should report an error about missing \`posts.id\` field in fragment 1`] = ` #### ⌨️ Code 1 | { user { id ...UserFields } } @@ -76,7 +76,7 @@ exports[`require-id-when-available > invalid > should report an error about miss Include it in your selection set or add to used fragment \`UserFields\`. `; -exports[`require-id-when-available > invalid > should report an error about missing \`user.id\`, \`posts.id\`, \`author.id\` and \`authorPosts.id\` selection 1`] = ` +exports[`require-selections > invalid > should report an error about missing \`user.id\`, \`posts.id\`, \`author.id\` and \`authorPosts.id\` selection 1`] = ` #### ⌨️ Code 1 | { user { ...UserFullFields } } @@ -110,7 +110,7 @@ exports[`require-id-when-available > invalid > should report an error about miss Include it in your selection set or add to used fragments \`UserFullFields\` or \`UserFields\`. `; -exports[`require-id-when-available > invalid > should report an error with union 1`] = ` +exports[`require-selections > invalid > should report an error with union 1`] = ` #### ⌨️ Code 1 | { @@ -140,7 +140,7 @@ exports[`require-id-when-available > invalid > should report an error with union 7 | } `; -exports[`require-id-when-available > invalid > should report an error with union and fragment spread 1`] = ` +exports[`require-selections > invalid > should report an error with union and fragment spread 1`] = ` #### ⌨️ Code 1 | { @@ -170,7 +170,7 @@ exports[`require-id-when-available > invalid > should report an error with union 7 | } `; -exports[`require-id-when-available > invalid > support multiple id field names 1`] = ` +exports[`require-selections > invalid > support multiple id field names 1`] = ` #### ⌨️ Code 1 | { hasId { name } } diff --git a/packages/plugin/__tests__/__snapshots__/no-case-insensitive-enum-values-duplicates.spec.md b/packages/plugin/__tests__/__snapshots__/unique-enum-value-names.spec.md similarity index 80% rename from packages/plugin/__tests__/__snapshots__/no-case-insensitive-enum-values-duplicates.spec.md rename to packages/plugin/__tests__/__snapshots__/unique-enum-value-names.spec.md index 6c24d0ed502..be322d24b1b 100644 --- a/packages/plugin/__tests__/__snapshots__/no-case-insensitive-enum-values-duplicates.spec.md +++ b/packages/plugin/__tests__/__snapshots__/unique-enum-value-names.spec.md @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`no-case-insensitive-enum-values-duplicates > invalid > Invalid #1 1`] = ` +exports[`unique-enum-value-names > invalid > Invalid #1 1`] = ` #### ⌨️ Code 1 | enum A { TEST TesT } @@ -15,7 +15,7 @@ exports[`no-case-insensitive-enum-values-duplicates > invalid > Invalid #1 1`] = 1 | enum A { TEST } `; -exports[`no-case-insensitive-enum-values-duplicates > invalid > Invalid #2 1`] = ` +exports[`unique-enum-value-names > invalid > Invalid #2 1`] = ` #### ⌨️ Code 1 | extend enum A { TEST TesT } diff --git a/packages/plugin/__tests__/alphabetize.spec.ts b/packages/plugin/__tests__/alphabetize.spec.ts index a5be17f3079..6cb77b78c14 100644 --- a/packages/plugin/__tests__/alphabetize.spec.ts +++ b/packages/plugin/__tests__/alphabetize.spec.ts @@ -42,7 +42,7 @@ ruleTester.run('alphabetize', rule, { `, }, { - options: [{ values: ['EnumTypeDefinition'] }], + options: [{ values: true }], code: /* GraphQL */ ` enum Role { ADMIN @@ -136,7 +136,7 @@ ruleTester.run('alphabetize', rule, { errors: [{ message: 'input value "lastName" should be before input value "password"' }], }, { - options: [{ values: ['EnumTypeDefinition'] }], + options: [{ values: true }], code: /* GraphQL */ ` enum Role { SUPER_ADMIN @@ -151,7 +151,7 @@ ruleTester.run('alphabetize', rule, { ], }, { - options: [{ values: ['EnumTypeDefinition'] }], + options: [{ values: true }], code: /* GraphQL */ ` extend enum Role { ADMIN @@ -222,7 +222,7 @@ ruleTester.run('alphabetize', rule, { ], }, { - options: [{ variables: ['OperationDefinition'], arguments: ['Field'] }], + options: [{ variables: true, arguments: ['Field'] }], code: /* GraphQL */ ` mutation ($cc: [Cc!]!, $bb: [Bb!], $aa: Aa!) { test(ccc: $cc, bbb: $bb, aaa: $aa) { @@ -264,7 +264,7 @@ ruleTester.run('alphabetize', rule, { }, { name: 'should compare with lexicographic order', - options: [{ values: ['EnumTypeDefinition'] }], + options: [{ values: true }], code: /* GraphQL */ ` enum Test { "qux" @@ -425,5 +425,24 @@ ruleTester.run('alphabetize', rule, { { message: 'field "guild" should be before field "nachos"' }, ], }, + { + name: 'should sort selections by group when `*` is between', + options: [ + { + selections: ['OperationDefinition'], + groups: ['id', '*', 'createdAt', 'updatedAt'], + }, + ], + code: /* GraphQL */ ` + { + zz + updatedAt + createdAt + aa + id + } + `, + errors: 3, + }, ], }); diff --git a/packages/plugin/__tests__/examples.spec.ts b/packages/plugin/__tests__/examples.spec.ts index 44ff8bb3102..b21fdb72e92 100644 --- a/packages/plugin/__tests__/examples.spec.ts +++ b/packages/plugin/__tests__/examples.spec.ts @@ -72,7 +72,7 @@ describe('Examples', () => { it('should work in monorepo', () => { const cwd = join(CWD, 'examples/monorepo'); const results = getESLintOutput(cwd); - expect(countErrors(results)).toBe(7); + expect(countErrors(results)).toBe(11); testSnapshot(results); }); diff --git a/packages/plugin/__tests__/naming-convention.spec.ts b/packages/plugin/__tests__/naming-convention.spec.ts index a84ae7c686d..cf2b91f24ec 100644 --- a/packages/plugin/__tests__/naming-convention.spec.ts +++ b/packages/plugin/__tests__/naming-convention.spec.ts @@ -394,17 +394,27 @@ ruleTester.run('naming-convention', rule, { fieldSubscription: ID subscriptionField: ID } + + enum TestEnum + extend enum EnumTest { + A + } + + interface TestInterface + extend interface InterfaceTest { + id: ID + } + + union TestUnion + extend union UnionTest = TestInterface + + type TestType + extend type TypeTest { + id: ID + } `, options: (rule.meta.docs!.configOptions as any).schema, - errors: [ - { message: 'Field "fieldQuery" should not have "Query" suffix' }, - { message: 'Field "queryField" should not have "query" prefix' }, - { message: 'Field "getField" should not have "get" prefix' }, - { message: 'Field "fieldMutation" should not have "Mutation" suffix' }, - { message: 'Field "mutationField" should not have "mutation" prefix' }, - { message: 'Field "fieldSubscription" should not have "Subscription" suffix' }, - { message: 'Field "subscriptionField" should not have "subscription" prefix' }, - ], + errors: 15, }, { name: 'operations-recommended config', diff --git a/packages/plugin/__tests__/require-nullable-result-in-root.spec.ts b/packages/plugin/__tests__/require-nullable-result-in-root.spec.ts index 532927229ab..8eb40023a70 100644 --- a/packages/plugin/__tests__/require-nullable-result-in-root.spec.ts +++ b/packages/plugin/__tests__/require-nullable-result-in-root.spec.ts @@ -46,15 +46,7 @@ ruleTester.run('require-nullable-result-in-root', rule, { }), withSchema({ name: 'should work with default scalars', - code: /* GraphQL */ ` - type MySubscription - extend type MySubscription { - foo: Boolean! - } - schema { - subscription: MySubscription - } - `, + code: 'type Mutation { foo: Boolean! }', errors: 1, }), ], diff --git a/packages/plugin/__tests__/require-id-when-available.spec.ts b/packages/plugin/__tests__/require-selections.spec.ts similarity index 97% rename from packages/plugin/__tests__/require-id-when-available.spec.ts rename to packages/plugin/__tests__/require-selections.spec.ts index b9911f635ec..c31e5ad2445 100644 --- a/packages/plugin/__tests__/require-id-when-available.spec.ts +++ b/packages/plugin/__tests__/require-selections.spec.ts @@ -1,4 +1,4 @@ -import { rule, RuleOptions } from '../src/rules/require-id-when-available'; +import { rule, RuleOptions } from '../src/rules/require-selections'; import { ParserOptionsForTests, ruleTester } from './test-utils'; const TEST_SCHEMA = /* GraphQL */ ` @@ -68,7 +68,7 @@ const WITH_SCHEMA = { } satisfies ParserOptionsForTests, }; -const MESSAGE_ID = { messageId: 'require-id-when-available' }; +const MESSAGE_ID = { messageId: 'require-selections' }; const DOCUMENT_WITH_UNION = /* GraphQL */ ` { @@ -80,7 +80,7 @@ const DOCUMENT_WITH_UNION = /* GraphQL */ ` } `; -ruleTester.run('require-id-when-available', rule, { +ruleTester.run('require-selections', rule, { valid: [ { name: 'should completely ignore FragmentDefinition', diff --git a/packages/plugin/__tests__/rules.spec.ts b/packages/plugin/__tests__/rules.spec.ts index 30f7f820793..12d2201b69a 100644 --- a/packages/plugin/__tests__/rules.spec.ts +++ b/packages/plugin/__tests__/rules.spec.ts @@ -45,7 +45,7 @@ describe('Rules', () => { expect(results).toHaveLength(1); }); - it('should load all rules properly from `relay` config', async () => { + it('should load all rules properly from `schema-relay` config', async () => { const eslint = getESLintWithConfig(configs['schema-relay'], { documents: '{ foo }' }); const results = await eslint.lintText('{ foo }', { filePath: 'foo.graphql' }); expect(results).toHaveLength(1); diff --git a/packages/plugin/__tests__/no-case-insensitive-enum-values-duplicates.spec.ts b/packages/plugin/__tests__/unique-enum-value-names.spec.ts similarity index 59% rename from packages/plugin/__tests__/no-case-insensitive-enum-values-duplicates.spec.ts rename to packages/plugin/__tests__/unique-enum-value-names.spec.ts index f29ef3e181e..53d03b17bab 100644 --- a/packages/plugin/__tests__/no-case-insensitive-enum-values-duplicates.spec.ts +++ b/packages/plugin/__tests__/unique-enum-value-names.spec.ts @@ -1,7 +1,7 @@ -import { rule } from '../src/rules/no-case-insensitive-enum-values-duplicates'; +import { rule } from '../src/rules/unique-enum-value-names'; import { ruleTester } from './test-utils'; -ruleTester.run('no-case-insensitive-enum-values-duplicates', rule, { +ruleTester.run('unique-enum-value-names', rule, { valid: [], invalid: [ { diff --git a/packages/plugin/package.json b/packages/plugin/package.json index a1030ae45f0..c980b5295d6 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -39,14 +39,13 @@ "typecheck": "tsc --noEmit" }, "peerDependencies": { - "eslint": "^8", + "eslint": ">=8.44.0", "graphql": "^16" }, "dependencies": { "@graphql-tools/code-file-loader": "^7.3.6", "@graphql-tools/graphql-tag-pluck": "^7.3.6", "@graphql-tools/utils": "^9.0.0", - "chalk": "^4.1.2", "debug": "^4.3.4", "fast-glob": "^3.2.12", "graphql-config": "^4.5.0", @@ -56,7 +55,7 @@ "devDependencies": { "@theguild/eslint-rule-tester": "workspace:*", "@types/debug": "4.1.8", - "@types/eslint": "8.37.0", + "@types/eslint": "8.44.2", "@types/estree": "1.0.1", "@types/graphql-depth-limit": "1.1.3", "@types/json-schema": "7.0.12", diff --git a/packages/plugin/src/configs/operations-all.ts b/packages/plugin/src/configs/operations-all.ts index 9896c9e3007..a2c3cf75843 100644 --- a/packages/plugin/src/configs/operations-all.ts +++ b/packages/plugin/src/configs/operations-all.ts @@ -8,9 +8,11 @@ export = { '@graphql-eslint/alphabetize': [ 'error', { + definitions: true, selections: ['OperationDefinition', 'FragmentDefinition'], - variables: ['OperationDefinition'], + variables: true, arguments: ['Field', 'Directive'], + groups: ['id', '*', 'createdAt', 'updatedAt'], }, ], '@graphql-eslint/lone-executable-definition': 'error', @@ -24,7 +26,6 @@ export = { }, ], '@graphql-eslint/no-one-place-fragments': 'error', - '@graphql-eslint/unique-fragment-name': 'error', - '@graphql-eslint/unique-operation-name': 'error', + '@graphql-eslint/require-import-fragment': 'error', }, }; diff --git a/packages/plugin/src/configs/operations-recommended.ts b/packages/plugin/src/configs/operations-recommended.ts index 3e0346ae6f1..b4930c39f7b 100644 --- a/packages/plugin/src/configs/operations-recommended.ts +++ b/packages/plugin/src/configs/operations-recommended.ts @@ -41,12 +41,14 @@ export = { '@graphql-eslint/overlapping-fields-can-be-merged': 'error', '@graphql-eslint/possible-fragment-spread': 'error', '@graphql-eslint/provided-required-arguments': 'error', - '@graphql-eslint/require-id-when-available': 'error', + '@graphql-eslint/require-selections': 'error', '@graphql-eslint/scalar-leafs': 'error', '@graphql-eslint/selection-set-depth': ['error', { maxDepth: 7 }], '@graphql-eslint/unique-argument-names': 'error', '@graphql-eslint/unique-directive-names-per-location': 'error', + '@graphql-eslint/unique-fragment-name': 'error', '@graphql-eslint/unique-input-field-names': 'error', + '@graphql-eslint/unique-operation-name': 'error', '@graphql-eslint/unique-variable-names': 'error', '@graphql-eslint/value-literals-of-correct-type': 'error', '@graphql-eslint/variables-are-input-types': 'error', diff --git a/packages/plugin/src/configs/schema-all.ts b/packages/plugin/src/configs/schema-all.ts index f159e61b55e..f6a62a62b24 100644 --- a/packages/plugin/src/configs/schema-all.ts +++ b/packages/plugin/src/configs/schema-all.ts @@ -8,12 +8,15 @@ export = { '@graphql-eslint/alphabetize': [ 'error', { + definitions: true, fields: ['ObjectTypeDefinition', 'InterfaceTypeDefinition', 'InputObjectTypeDefinition'], - values: ['EnumTypeDefinition'], + values: true, arguments: ['FieldDefinition', 'Field', 'DirectiveDefinition', 'Directive'], + groups: ['id', '*', 'createdAt', 'updatedAt'], }, ], '@graphql-eslint/input-name': 'error', + '@graphql-eslint/no-root-type': ['error', { disallow: ['mutation', 'subscription'] }], '@graphql-eslint/no-scalar-result-type-on-mutation': 'error', '@graphql-eslint/require-deprecation-date': 'error', '@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error', diff --git a/packages/plugin/src/configs/schema-recommended.ts b/packages/plugin/src/configs/schema-recommended.ts index 4b6be28f61b..54202c400f6 100644 --- a/packages/plugin/src/configs/schema-recommended.ts +++ b/packages/plugin/src/configs/schema-recommended.ts @@ -32,18 +32,38 @@ export = { forbiddenPrefixes: ['subscription'], forbiddenSuffixes: ['Subscription'], }, + 'EnumTypeDefinition,EnumTypeExtension': { + forbiddenPrefixes: ['Enum'], + forbiddenSuffixes: ['Enum'], + }, + 'InterfaceTypeDefinition,InterfaceTypeExtension': { + forbiddenPrefixes: ['Interface'], + forbiddenSuffixes: ['Interface'], + }, + 'UnionTypeDefinition,UnionTypeExtension': { + forbiddenPrefixes: ['Union'], + forbiddenSuffixes: ['Union'], + }, + 'ObjectTypeDefinition,ObjectTypeExtension': { + forbiddenPrefixes: ['Type'], + forbiddenSuffixes: ['Type'], + }, }, ], - '@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error', '@graphql-eslint/no-hashtag-description': 'error', '@graphql-eslint/no-typename-prefix': 'error', '@graphql-eslint/no-unreachable-types': 'error', + '@graphql-eslint/possible-type-extension': 'error', '@graphql-eslint/provided-required-arguments': 'error', '@graphql-eslint/require-deprecation-reason': 'error', - '@graphql-eslint/require-description': ['error', { types: true, DirectiveDefinition: true }], + '@graphql-eslint/require-description': [ + 'error', + { types: true, DirectiveDefinition: true, rootField: true }, + ], '@graphql-eslint/strict-id-in-types': 'error', '@graphql-eslint/unique-directive-names': 'error', '@graphql-eslint/unique-directive-names-per-location': 'error', + '@graphql-eslint/unique-enum-value-names': 'error', '@graphql-eslint/unique-field-definition-names': 'error', '@graphql-eslint/unique-operation-types': 'error', '@graphql-eslint/unique-type-names': 'error', diff --git a/packages/plugin/src/parser.ts b/packages/plugin/src/parser.ts index 20cda16e055..b3186dc8af7 100644 --- a/packages/plugin/src/parser.ts +++ b/packages/plugin/src/parser.ts @@ -14,14 +14,33 @@ const debug = debugFactory('graphql-eslint:parser'); debug('cwd %o', CWD); +const LEGACY_PARSER_OPTIONS_KEYS = [ + 'schema', + 'documents', + 'extensions', + 'include', + 'exclude', + 'projects', + 'schemaOptions', + 'graphQLParserOptions', + 'skipGraphQLConfig', + 'operations', +] as const; + export function parseForESLint(code: string, options: ParserOptions): GraphQLESLintParseResult { + for (const key of LEGACY_PARSER_OPTIONS_KEYS) { + if (key in options) { + throw new Error( + `\`parserOptions.${key}\` was removed in graphql-eslint@4. Use physical graphql-config for setting schema and documents or \`parserOptions.graphQLConfig\` for programmatic usage.`, + ); + } + } + try { const { filePath } = options; // First parse code from file, in case of syntax error do not try load schema, // documents or even graphql-config instance - const { document } = parseGraphQLSDL(filePath, code, { - noLocation: false, - }); + const { document } = parseGraphQLSDL(filePath, code, { noLocation: false }); let project: GraphQLProjectConfig; let schema: Schema, documents: Source[]; diff --git a/packages/plugin/src/rules/alphabetize.ts b/packages/plugin/src/rules/alphabetize.ts index 0b66071b221..f61fe91ed65 100644 --- a/packages/plugin/src/rules/alphabetize.ts +++ b/packages/plugin/src/rules/alphabetize.ts @@ -35,12 +35,10 @@ const fieldsEnum: ( Kind.INTERFACE_TYPE_DEFINITION, Kind.INPUT_OBJECT_TYPE_DEFINITION, ]; -const valuesEnum: ['EnumTypeDefinition'] = [Kind.ENUM_TYPE_DEFINITION]; const selectionsEnum: ('FragmentDefinition' | 'OperationDefinition')[] = [ Kind.OPERATION_DEFINITION, Kind.FRAGMENT_DEFINITION, ]; -const variablesEnum: ['OperationDefinition'] = [Kind.OPERATION_DEFINITION]; const argumentsEnum: ('Directive' | 'DirectiveDefinition' | 'Field' | 'FieldDefinition')[] = [ Kind.FIELD_DEFINITION, Kind.FIELD, @@ -65,10 +63,7 @@ const schema = { description: 'Fields of `type`, `interface`, and `input`.', }, values: { - ...ARRAY_DEFAULT_OPTIONS, - items: { - enum: valuesEnum, - }, + type: 'boolean', description: 'Values of `enum`.', }, selections: { @@ -80,10 +75,7 @@ const schema = { 'Selections of `fragment` and operations `query`, `mutation` and `subscription`.', }, variables: { - ...ARRAY_DEFAULT_OPTIONS, - items: { - enum: variablesEnum, - }, + type: 'boolean', description: 'Variables of operations `query`, `mutation` and `subscription`.', }, arguments: { @@ -97,7 +89,6 @@ const schema = { type: 'boolean', description: 'Definitions – `type`, `interface`, `enum`, `scalar`, `input`, `union` and `directive`.', - default: false, }, groups: { ...ARRAY_DEFAULT_OPTIONS, @@ -147,7 +138,7 @@ export const rule: GraphQLESLintRule = { }, { title: 'Incorrect', - usage: [{ values: [Kind.ENUM_TYPE_DEFINITION] }], + usage: [{ values: true }], code: /* GraphQL */ ` enum Role { SUPER_ADMIN @@ -159,7 +150,7 @@ export const rule: GraphQLESLintRule = { }, { title: 'Correct', - usage: [{ values: [Kind.ENUM_TYPE_DEFINITION] }], + usage: [{ values: true }], code: /* GraphQL */ ` enum Role { ADMIN @@ -199,19 +190,20 @@ export const rule: GraphQLESLintRule = { configOptions: { schema: [ { + definitions: true, fields: fieldsEnum, - values: valuesEnum, + values: true, arguments: argumentsEnum, - // TODO: add in graphql-eslint v4 - // definitions: true, - // groups: ['id', '*', 'createdAt', 'updatedAt'] + groups: ['id', '*', 'createdAt', 'updatedAt'], }, ], operations: [ { + definitions: true, selections: selectionsEnum, - variables: variablesEnum, + variables: true, arguments: [Kind.FIELD, Kind.DIRECTIVE], + groups: ['id', '*', 'createdAt', 'updatedAt'], }, ], }, @@ -360,10 +352,7 @@ export const rule: GraphQLESLintRule = { .flat(); const fieldsSelector = kinds.join(','); - - const hasEnumValues = opts.values?.[0] === Kind.ENUM_TYPE_DEFINITION; const selectionsSelector = opts.selections?.join(','); - const hasVariables = opts.variables?.[0] === Kind.OPERATION_DEFINITION; const argumentsSelector = opts.arguments?.join(','); if (fieldsSelector) { @@ -381,7 +370,7 @@ export const rule: GraphQLESLintRule = { }; } - if (hasEnumValues) { + if (opts.values) { const enumValuesSelector = [Kind.ENUM_TYPE_DEFINITION, Kind.ENUM_TYPE_EXTENSION].join(','); listeners[enumValuesSelector] = ( node: GraphQLESTreeNode, @@ -398,7 +387,7 @@ export const rule: GraphQLESLintRule = { }; } - if (hasVariables) { + if (opts.variables) { listeners.OperationDefinition = (node: GraphQLESTreeNode) => { checkNodes(node.variableDefinitions?.map(varDef => varDef.variable)); }; diff --git a/packages/plugin/src/rules/graphql-js-validation.ts b/packages/plugin/src/rules/graphql-js-validation.ts index cfa55f9a50d..3415693c6ad 100644 --- a/packages/plugin/src/rules/graphql-js-validation.ts +++ b/packages/plugin/src/rules/graphql-js-validation.ts @@ -34,7 +34,7 @@ import { UniqueArgumentNamesRule, UniqueDirectiveNamesRule, UniqueDirectivesPerLocationRule, - UniqueEnumValueNamesRule, + // UniqueEnumValueNamesRule, -- Superseded by graphql-eslint's `unique-enum-value-names` rule UniqueFieldDefinitionNamesRule, UniqueInputFieldNamesRule, UniqueOperationTypesRule, @@ -510,7 +510,7 @@ export const GRAPHQL_JS_VALIDATIONS: Record = Object. return node; }; - return getParentNode(context.getFilename(), node); + return getParentNode(context.filename, node); }, }, { @@ -570,7 +570,6 @@ export const GRAPHQL_JS_VALIDATIONS: Record = Object. description: 'A type extension is only valid if the type is defined and has the same kind.', recommended: true, requiresSchema: true, - isDisabledForAllConfig: true, }, ), validationToRule( @@ -643,18 +642,6 @@ export const GRAPHQL_JS_VALIDATIONS: Record = Object. requiresSchema: true, }, ), - validationToRule( - { - ruleId: 'unique-enum-value-names', - rule: UniqueEnumValueNamesRule, - }, - { - category: 'Schema', - description: 'A GraphQL enum type is only valid if all its values are uniquely named.', - recommended: false, - isDisabledForAllConfig: true, - }, - ), validationToRule( { ruleId: 'unique-field-definition-names', diff --git a/packages/plugin/src/rules/index.ts b/packages/plugin/src/rules/index.ts index 1ba4e1f999a..d997ec54f7f 100644 --- a/packages/plugin/src/rules/index.ts +++ b/packages/plugin/src/rules/index.ts @@ -10,7 +10,6 @@ import { rule as loneExecutableDefinition } from './lone-executable-definition.j import { rule as matchDocumentFilename } from './match-document-filename.js'; import { rule as namingConvention } from './naming-convention.js'; import { rule as noAnonymousOperations } from './no-anonymous-operations.js'; -import { rule as noCaseInsensitiveEnumValuesDuplicates } from './no-case-insensitive-enum-values-duplicates.js'; import { rule as noDeprecated } from './no-deprecated.js'; import { rule as noDuplicateFields } from './no-duplicate-fields.js'; import { rule as noHashtagDescription } from './no-hashtag-description.js'; @@ -28,13 +27,14 @@ import { rule as requireDeprecationDate } from './require-deprecation-date.js'; import { rule as requireDeprecationReason } from './require-deprecation-reason.js'; import { rule as requireDescription } from './require-description.js'; import { rule as requireFieldOfTypeQueryInMutationResult } from './require-field-of-type-query-in-mutation-result.js'; -import { rule as requireIdWhenAvailable } from './require-id-when-available.js'; import { rule as requireImportFragment } from './require-import-fragment.js'; import { rule as requireNullableFieldsWithOneof } from './require-nullable-fields-with-oneof.js'; import { rule as requireNullableResultInRoot } from './require-nullable-result-in-root.js'; +import { rule as requireSelections } from './require-selections.js'; import { rule as requireTypePatternWithOneof } from './require-type-pattern-with-oneof.js'; import { rule as selectionSetDepth } from './selection-set-depth.js'; import { rule as strictIdInTypes } from './strict-id-in-types.js'; +import { rule as uniqueEnumValueNames } from './unique-enum-value-names.js'; import { rule as uniqueFragmentName } from './unique-fragment-name.js'; import { rule as uniqueOperationName } from './unique-operation-name.js'; @@ -47,7 +47,6 @@ export const rules = { 'match-document-filename': matchDocumentFilename, 'naming-convention': namingConvention, 'no-anonymous-operations': noAnonymousOperations, - 'no-case-insensitive-enum-values-duplicates': noCaseInsensitiveEnumValuesDuplicates, 'no-deprecated': noDeprecated, 'no-duplicate-fields': noDuplicateFields, 'no-hashtag-description': noHashtagDescription, @@ -65,13 +64,14 @@ export const rules = { 'require-deprecation-reason': requireDeprecationReason, 'require-description': requireDescription, 'require-field-of-type-query-in-mutation-result': requireFieldOfTypeQueryInMutationResult, - 'require-id-when-available': requireIdWhenAvailable, 'require-import-fragment': requireImportFragment, 'require-nullable-fields-with-oneof': requireNullableFieldsWithOneof, 'require-nullable-result-in-root': requireNullableResultInRoot, + 'require-selections': requireSelections, 'require-type-pattern-with-oneof': requireTypePatternWithOneof, 'selection-set-depth': selectionSetDepth, 'strict-id-in-types': strictIdInTypes, + 'unique-enum-value-names': uniqueEnumValueNames, 'unique-fragment-name': uniqueFragmentName, 'unique-operation-name': uniqueOperationName, }; diff --git a/packages/plugin/src/rules/match-document-filename.ts b/packages/plugin/src/rules/match-document-filename.ts index d9d47157c5c..25bf39bb44a 100644 --- a/packages/plugin/src/rules/match-document-filename.ts +++ b/packages/plugin/src/rules/match-document-filename.ts @@ -183,7 +183,7 @@ export const rule: GraphQLESLintRule = { const options = context.options[0] || { fileExtension: null, }; - const filePath = context.getFilename(); + const filePath = context.filename; const isVirtualFile = VIRTUAL_DOCUMENT_REGEX.test(filePath); if (process.env.NODE_ENV !== 'test' && isVirtualFile) { diff --git a/packages/plugin/src/rules/naming-convention.ts b/packages/plugin/src/rules/naming-convention.ts index 490d2f4e4f9..6e4f11cda3a 100644 --- a/packages/plugin/src/rules/naming-convention.ts +++ b/packages/plugin/src/rules/naming-convention.ts @@ -265,6 +265,22 @@ export const rule: GraphQLESLintRule = { forbiddenPrefixes: ['subscription'], forbiddenSuffixes: ['Subscription'], }, + 'EnumTypeDefinition,EnumTypeExtension': { + forbiddenPrefixes: ['Enum'], + forbiddenSuffixes: ['Enum'], + }, + 'InterfaceTypeDefinition,InterfaceTypeExtension': { + forbiddenPrefixes: ['Interface'], + forbiddenSuffixes: ['Interface'], + }, + 'UnionTypeDefinition,UnionTypeExtension': { + forbiddenPrefixes: ['Union'], + forbiddenSuffixes: ['Union'], + }, + 'ObjectTypeDefinition,ObjectTypeExtension': { + forbiddenPrefixes: ['Type'], + forbiddenSuffixes: ['Type'], + }, }, ], operations: [ diff --git a/packages/plugin/src/rules/no-root-type.ts b/packages/plugin/src/rules/no-root-type.ts index 6490d6a458e..7f45238eba0 100644 --- a/packages/plugin/src/rules/no-root-type.ts +++ b/packages/plugin/src/rules/no-root-type.ts @@ -34,7 +34,6 @@ export const rule: GraphQLESLintRule = { description: 'Disallow using root types `mutation` and/or `subscription`.', url: 'https://the-guild.dev/graphql/eslint/rules/no-root-type', requiresSchema: true, - isDisabledForAllConfig: true, examples: [ { title: 'Incorrect', @@ -55,6 +54,7 @@ export const rule: GraphQLESLintRule = { `, }, ], + configOptions: [{ disallow: ['mutation', 'subscription'] }], }, schema, }, diff --git a/packages/plugin/src/rules/no-unused-fields.ts b/packages/plugin/src/rules/no-unused-fields.ts index 8b6993c5198..60bd7c3af90 100644 --- a/packages/plugin/src/rules/no-unused-fields.ts +++ b/packages/plugin/src/rules/no-unused-fields.ts @@ -52,6 +52,7 @@ export const rule: GraphQLESLintRule = { url: `https://the-guild.dev/graphql/eslint/rules/${RULE_ID}`, requiresSiblings: true, requiresSchema: true, + // Requires documents to be set isDisabledForAllConfig: true, examples: [ { diff --git a/packages/plugin/src/rules/require-description.ts b/packages/plugin/src/rules/require-description.ts index 06cf915a9d3..292557b37cf 100644 --- a/packages/plugin/src/rules/require-description.ts +++ b/packages/plugin/src/rules/require-description.ts @@ -125,7 +125,7 @@ export const rule: GraphQLESLintRule = { { types: true, [Kind.DIRECTIVE_DEFINITION]: true, - // rootField: true TODO enable in graphql-eslint v4 + rootField: true, }, ], recommended: true, diff --git a/packages/plugin/src/rules/require-import-fragment.ts b/packages/plugin/src/rules/require-import-fragment.ts index a15c7fdcafe..2c5380edd4e 100644 --- a/packages/plugin/src/rules/require-import-fragment.ts +++ b/packages/plugin/src/rules/require-import-fragment.ts @@ -60,7 +60,6 @@ export const rule: GraphQLESLintRule = { }, ], requiresSiblings: true, - isDisabledForAllConfig: true, }, hasSuggestions: true, messages: { @@ -72,7 +71,7 @@ export const rule: GraphQLESLintRule = { create(context) { const comments = context.getSourceCode().getAllComments(); const siblings = requireSiblingsOperations(RULE_ID, context); - const filePath = context.getFilename(); + const filePath = context.filename; return { 'FragmentSpread > .name'(node: GraphQLESTreeNode) { diff --git a/packages/plugin/src/rules/require-nullable-result-in-root.ts b/packages/plugin/src/rules/require-nullable-result-in-root.ts index 24c67326b9d..a722ed03530 100644 --- a/packages/plugin/src/rules/require-nullable-result-in-root.ts +++ b/packages/plugin/src/rules/require-nullable-result-in-root.ts @@ -43,9 +43,7 @@ export const rule: GraphQLESLintRule = { create(context) { const schema = requireGraphQLSchemaFromContext(RULE_ID, context); const rootTypeNames = new Set( - [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()] - .filter(truthy) - .map(type => type.name), + [schema.getQueryType(), schema.getMutationType()].filter(truthy).map(type => type.name), ); const sourceCode = context.getSourceCode(); diff --git a/packages/plugin/src/rules/require-id-when-available.ts b/packages/plugin/src/rules/require-selections.ts similarity index 99% rename from packages/plugin/src/rules/require-id-when-available.ts rename to packages/plugin/src/rules/require-selections.ts index 1ed8e649081..1aac5ce6b9c 100644 --- a/packages/plugin/src/rules/require-id-when-available.ts +++ b/packages/plugin/src/rules/require-selections.ts @@ -22,7 +22,7 @@ import { requireSiblingsOperations, } from '../utils.js'; -const RULE_ID = 'require-id-when-available'; +const RULE_ID = 'require-selections'; const DEFAULT_ID_FIELD_NAME = 'id'; const schema = { diff --git a/packages/plugin/src/rules/no-case-insensitive-enum-values-duplicates.ts b/packages/plugin/src/rules/unique-enum-value-names.ts similarity index 88% rename from packages/plugin/src/rules/no-case-insensitive-enum-values-duplicates.ts rename to packages/plugin/src/rules/unique-enum-value-names.ts index 334cccde027..7adb9930e10 100644 --- a/packages/plugin/src/rules/no-case-insensitive-enum-values-duplicates.ts +++ b/packages/plugin/src/rules/unique-enum-value-names.ts @@ -8,10 +8,11 @@ export const rule: GraphQLESLintRule = { type: 'suggestion', hasSuggestions: true, docs: { - url: 'https://the-guild.dev/graphql/eslint/rules/no-case-insensitive-enum-values-duplicates', + url: 'https://the-guild.dev/graphql/eslint/rules/unique-enum-value-names', category: 'Schema', recommended: true, - description: 'Disallow case-insensitive enum values duplicates.', + description: `A GraphQL enum type is only valid if all its values are uniquely named. +> This rule disallows case-insensitive enum values duplicates too.`, examples: [ { title: 'Incorrect', diff --git a/packages/plugin/src/rules/unique-fragment-name.ts b/packages/plugin/src/rules/unique-fragment-name.ts index e938c94e0ac..6c76cd02347 100644 --- a/packages/plugin/src/rules/unique-fragment-name.ts +++ b/packages/plugin/src/rules/unique-fragment-name.ts @@ -18,7 +18,7 @@ export const checkNode = ( node.kind === Kind.FRAGMENT_DEFINITION ? siblings.getFragment(documentName) : siblings.getOperation(documentName); - const filepath = context.getFilename(); + const filepath = context.filename; const conflictingDocuments = siblingDocuments.filter(f => { const isSameName = f.document.name?.value === documentName; diff --git a/packages/plugin/src/utils.ts b/packages/plugin/src/utils.ts index e25e298625e..db290604968 100644 --- a/packages/plugin/src/utils.ts +++ b/packages/plugin/src/utils.ts @@ -1,4 +1,3 @@ -import chalk from 'chalk'; import { AST } from 'eslint'; import { Position } from 'estree'; import { ASTNode, GraphQLSchema, Kind } from 'graphql'; @@ -11,7 +10,7 @@ export function requireSiblingsOperations( ruleId: string, context: GraphQLESLintRuleContext, ): SiblingOperations | never { - const { siblingOperations } = context.parserServices; + const { siblingOperations } = context.sourceCode.parserServices; if (!siblingOperations.available) { throw new Error( `Rule \`${ruleId}\` requires graphql-config \`documents\` field to be set and loaded. See https://the-guild.dev/graphql/config/docs/user/documents for more info`, @@ -24,7 +23,7 @@ export function requireGraphQLSchemaFromContext( ruleId: string, context: GraphQLESLintRuleContext, ): GraphQLSchema | never { - const { schema } = context.parserServices; + const { schema } = context.sourceCode.parserServices; if (!schema) { throw new Error( `Rule \`${ruleId}\` requires graphql-config \`schema\` field to be set and loaded. See https://the-guild.dev/graphql/config/docs/user/schema for more info`, @@ -33,13 +32,18 @@ export function requireGraphQLSchemaFromContext( return schema; } +const chalk = { + red: (str: string) => `\x1b[31m${str}\x1b[39m`, + yellow: (str: string) => `\x1b[33m${str}\x1b[39m`, +}; + export const logger = { error: (...args: unknown[]) => // eslint-disable-next-line no-console - console.error(chalk.red('error'), '[graphql-eslint]', chalk(...args)), + console.error(chalk.red('error'), '[graphql-eslint]', ...args), warn: (...args: unknown[]) => // eslint-disable-next-line no-console - console.warn(chalk.yellow('warning'), '[graphql-eslint]', chalk(...args)), + console.warn(chalk.yellow('warning'), '[graphql-eslint]', ...args), }; export const normalizePath = (path: string): string => (path || '').replace(/\\/g, '/'); diff --git a/packages/plugin/vite.config.ts b/packages/plugin/vite.config.ts index 898ae22d0ed..d622d4474eb 100644 --- a/packages/plugin/vite.config.ts +++ b/packages/plugin/vite.config.ts @@ -1,6 +1,9 @@ +import path from 'node:path'; // @ts-expect-error -- add `"type": "module"` to `package.json` to fix this import { defineConfig } from 'vitest/config'; +const GRAPHQL_PATH = path.join(__dirname, 'node_modules', 'graphql'); + export default defineConfig({ test: { globals: true, @@ -9,6 +12,15 @@ export default defineConfig({ setupFiles: ['./serializer.ts'], alias: { '@graphql-eslint/eslint-plugin': 'src/index.ts', + // fixes Duplicate "graphql" modules cannot be used at the same time since different + 'graphql/validation/index.js': path.join(GRAPHQL_PATH, 'validation', 'index.js'), + 'graphql/validation/validate.js': path.join(GRAPHQL_PATH, 'validation', 'validate.js'), + 'graphql/utilities/valueFromASTUntyped.js': path.join( + GRAPHQL_PATH, + 'utilities', + 'valueFromASTUntyped.js', + ), + graphql: path.join(GRAPHQL_PATH, 'index.js'), }, }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ea10de3ee3..5ccadff13c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,18 +155,9 @@ importers: '@graphql-eslint/eslint-plugin': specifier: workspace:* version: link:../../packages/plugin/dist - cosmiconfig-typescript-loader: - specifier: 5.0.0 - version: 5.0.0(@types/node@18.17.4)(cosmiconfig@8.2.0)(typescript@5.1.6) eslint: specifier: 8.46.0 version: 8.46.0(patch_hash=yi5cqffjk423hcgr7hl33kguwu) - ts-node: - specifier: 10.9.1 - version: 10.9.1(@types/node@18.17.4)(typescript@5.1.6) - typescript: - specifier: 5.1.6 - version: 5.1.6 examples/prettier: dependencies: @@ -261,14 +252,11 @@ importers: '@graphql-tools/utils': specifier: ^9.0.0 version: 9.2.1(graphql@16.7.1) - chalk: - specifier: ^4.1.2 - version: 4.1.2 debug: specifier: ^4.3.4 version: 4.3.4 eslint: - specifier: ^8 + specifier: '>=8.44.0' version: 8.46.0(patch_hash=yi5cqffjk423hcgr7hl33kguwu) fast-glob: specifier: ^3.2.12 @@ -290,8 +278,8 @@ importers: specifier: 4.1.8 version: 4.1.8 '@types/eslint': - specifier: 8.37.0 - version: 8.37.0 + specifier: 8.44.2 + version: 8.44.2 '@types/estree': specifier: 1.0.1 version: 1.0.1 @@ -1083,13 +1071,6 @@ packages: resolution: {integrity: sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==} dev: false - /@cspotcode/source-map-support@0.8.1: - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - dev: true - /@esbuild-kit/cjs-loader@2.4.2: resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} dependencies: @@ -1888,11 +1869,6 @@ packages: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} - /@jridgewell/resolve-uri@3.1.1: - resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} - engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} @@ -1915,13 +1891,6 @@ packages: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 - /@jridgewell/trace-mapping@0.3.9: - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - dependencies: - '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /@lit-labs/ssr-dom-shim@1.1.1: resolution: {integrity: sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ==} dev: false @@ -2953,22 +2922,6 @@ packages: engines: {node: '>=10.13.0'} dev: true - /@tsconfig/node10@1.0.9: - resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} - dev: true - - /@tsconfig/node12@1.0.11: - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - dev: true - - /@tsconfig/node14@1.0.3: - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - dev: true - - /@tsconfig/node16@1.0.4: - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - dev: true - /@types/acorn@4.0.6: resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} dependencies: @@ -3020,11 +2973,11 @@ packages: /@types/eslint-scope@3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: - '@types/eslint': 8.37.0 + '@types/eslint': 8.44.2 '@types/estree': 1.0.1 - /@types/eslint@8.37.0: - resolution: {integrity: sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==} + /@types/eslint@8.44.2: + resolution: {integrity: sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==} dependencies: '@types/estree': 1.0.1 '@types/json-schema': 7.0.12 @@ -3661,10 +3614,6 @@ packages: resolution: {integrity: sha512-Wk7TEzl1KqvTGs/uyhmHO/3XLd3t1UeU4IstvPXVzGPM522cTjqjNZ99esCkcL52sjqjo8e8CTBcWhkxvGzoAw==} dev: false - /arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true - /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} dev: true @@ -4264,20 +4213,6 @@ packages: layout-base: 2.0.1 dev: false - /cosmiconfig-typescript-loader@5.0.0(@types/node@18.17.4)(cosmiconfig@8.2.0)(typescript@5.1.6): - resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} - engines: {node: '>=v16'} - peerDependencies: - '@types/node': '*' - cosmiconfig: '>=8.2' - typescript: '>=4' - dependencies: - '@types/node': 18.17.4 - cosmiconfig: 8.2.0 - jiti: 1.19.1 - typescript: 5.1.6 - dev: true - /cosmiconfig@8.0.0: resolution: {integrity: sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==} engines: {node: '>=14'} @@ -4288,20 +4223,6 @@ packages: path-type: 4.0.0 dev: false - /cosmiconfig@8.2.0: - resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==} - engines: {node: '>=14'} - dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.0 - parse-json: 5.2.0 - path-type: 4.0.0 - dev: true - - /create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true - /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: @@ -4908,11 +4829,6 @@ packages: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true - /diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dev: true - /diff@5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} engines: {node: '>=0.3.1'} @@ -7244,10 +7160,6 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true - /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -10386,37 +10298,6 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true - /ts-node@10.9.1(@types/node@18.17.4)(typescript@5.1.6): - resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 18.17.4 - acorn: 8.10.0 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.1.6 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: true - /tsconfig-paths@3.14.2: resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} dependencies: @@ -10929,10 +10810,6 @@ packages: kleur: 4.1.5 sade: 1.8.1 - /v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - dev: true - /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: @@ -11466,11 +11343,6 @@ packages: yargs-parser: 21.1.1 dev: true - /yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - dev: true - /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} diff --git a/scripts/generate-configs.ts b/scripts/generate-configs.ts index d5521e36e46..956a94e4deb 100644 --- a/scripts/generate-configs.ts +++ b/scripts/generate-configs.ts @@ -23,7 +23,7 @@ type WriteFile = { const writeFormattedFile: WriteFile = async (filePath, code) => { if (filePath.startsWith('configs')) { - code = `export default ${JSON.stringify(code)}`; + code = `export = ${JSON.stringify(code)}`; } const formattedCode = [ diff --git a/scripts/patch-graphql.ts b/scripts/patch-graphql.ts deleted file mode 100644 index 02eebb525c6..00000000000 --- a/scripts/patch-graphql.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * This script fixes an error from vite - * Duplicate "graphql" modules cannot be used at the same time since different - * taken from https://github.com/vitejs/vite/issues/7879#issuecomment-1156166452 - */ - -import { unlink } from 'node:fs/promises'; -import { createRequire } from 'node:module'; -import path from 'node:path'; - -const require = createRequire(path.resolve('./packages/plugin/src')); - -try { - await unlink(require.resolve('graphql').replace(/\.js$/, '.mjs')); -} catch { - // ignore if file not exist (was already patched) -} diff --git a/website/src/pages/rules/_meta.json b/website/src/pages/rules/_meta.json index 417e0bff96c..d632240cc53 100644 --- a/website/src/pages/rules/_meta.json +++ b/website/src/pages/rules/_meta.json @@ -14,7 +14,6 @@ "description-style": "", "input-name": "", "lone-schema-definition": "", - "no-case-insensitive-enum-values-duplicates": "", "no-hashtag-description": "", "no-root-type": "", "no-scalar-result-type-on-mutation": "", @@ -58,8 +57,8 @@ "one-field-subscriptions": "", "overlapping-fields-can-be-merged": "", "possible-fragment-spread": "", - "require-id-when-available": "", "require-import-fragment": "", + "require-selections": "", "scalar-leafs": "", "selection-set-depth": "", "unique-argument-names": "", diff --git a/website/src/pages/rules/alphabetize.md b/website/src/pages/rules/alphabetize.md index af4c16cff72..b0cab34300b 100644 --- a/website/src/pages/rules/alphabetize.md +++ b/website/src/pages/rules/alphabetize.md @@ -45,7 +45,7 @@ type User { ### Incorrect ```graphql -# eslint @graphql-eslint/alphabetize: ['error', { values: ['EnumTypeDefinition'] }] +# eslint @graphql-eslint/alphabetize: ['error', { values: true }] enum Role { SUPER_ADMIN @@ -58,7 +58,7 @@ enum Role { ### Correct ```graphql -# eslint @graphql-eslint/alphabetize: ['error', { values: ['EnumTypeDefinition'] }] +# eslint @graphql-eslint/alphabetize: ['error', { values: true }] enum Role { ADMIN @@ -115,19 +115,10 @@ Additional restrictions: - Minimum items: `1` - Unique items: `true` -### `values` (array) +### `values` (boolean) Values of `enum`. -The elements of the array can contain the following enum values: - -- `EnumTypeDefinition` - -Additional restrictions: - -- Minimum items: `1` -- Unique items: `true` - ### `selections` (array) Selections of `fragment` and operations `query`, `mutation` and `subscription`. @@ -142,19 +133,10 @@ Additional restrictions: - Minimum items: `1` - Unique items: `true` -### `variables` (array) +### `variables` (boolean) Variables of operations `query`, `mutation` and `subscription`. -The elements of the array can contain the following enum values: - -- `OperationDefinition` - -Additional restrictions: - -- Minimum items: `1` -- Unique items: `true` - ### `arguments` (array) Arguments of fields and directives. @@ -175,8 +157,6 @@ Additional restrictions: Definitions – `type`, `interface`, `enum`, `scalar`, `input`, `union` and `directive`. -Default: `false` - ### `groups` (array) Custom order group. Example: `['id', '*', 'createdAt', 'updatedAt']` where `*` says for everything diff --git a/website/src/pages/rules/deprecated-rules.mdx b/website/src/pages/rules/deprecated-rules.md similarity index 73% rename from website/src/pages/rules/deprecated-rules.mdx rename to website/src/pages/rules/deprecated-rules.md index efe89ad8f38..94c532c1033 100644 --- a/website/src/pages/rules/deprecated-rules.mdx +++ b/website/src/pages/rules/deprecated-rules.md @@ -22,3 +22,11 @@ This rule was removed because the same things can be validated using This rule was removed because the same things can be validated using [`naming-convention`](/rules/naming-convention). + +## `require-id-when-available` + +This rule was renamed to [`require-selections`](/rules/require-selections). + +## `no-case-insensitive-enum-values-duplicates` + +This rule was renamed to [`unique-enum-value-names`](/rules/unique-enum-value-names). diff --git a/website/src/pages/rules/index.md b/website/src/pages/rules/index.md index b1442b89efd..46cebc9b1b5 100644 --- a/website/src/pages/rules/index.md +++ b/website/src/pages/rules/index.md @@ -31,13 +31,12 @@ Name            &nbs [match-document-filename](/rules/match-document-filename)|This rule allows you to enforce that the file name should match the operation name.|![all][]|📦|🚀| [naming-convention](/rules/naming-convention)|Require names to follow specified conventions.|![recommended][]|📄 📦|🚀|💡 [no-anonymous-operations](/rules/no-anonymous-operations)|Require name for your GraphQL operations. This is useful since most GraphQL client libraries are using the operation name for caching purposes.|![recommended][]|📦|🚀|💡 -[no-case-insensitive-enum-values-duplicates](/rules/no-case-insensitive-enum-values-duplicates)|Disallow case-insensitive enum values duplicates.|![recommended][]|📄|🚀|💡 [no-deprecated](/rules/no-deprecated)|Enforce that deprecated fields or enum values are not in use by operations.|![recommended][]|📦|🚀|💡 [no-duplicate-fields](/rules/no-duplicate-fields)|Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.|![recommended][]|📦|🚀|💡 [no-fragment-cycles](/rules/no-fragment-cycles)|A GraphQL fragment is only valid when it does not have cycles in fragments usage.|![recommended][]|📦|🔮| [no-hashtag-description](/rules/no-hashtag-description)|Requires to use `"""` or `"` for adding a GraphQL description instead of `#`.|![recommended][]|📄|🚀|💡 [no-one-place-fragments](/rules/no-one-place-fragments)|Disallow fragments that are used only in one place.|![all][]|📦|🚀| -[no-root-type](/rules/no-root-type)|Disallow using root types `mutation` and/or `subscription`.||📄|🚀|💡 +[no-root-type](/rules/no-root-type)|Disallow using root types `mutation` and/or `subscription`.|![all][]|📄|🚀|💡 [no-scalar-result-type-on-mutation](/rules/no-scalar-result-type-on-mutation)|Avoid scalar result type on mutation type to make sure to return a valid state.|![all][]|📄|🚀|💡 [no-typename-prefix](/rules/no-typename-prefix)|Enforces users to avoid using the type name in a field name while defining your schema.|![recommended][]|📄|🚀|💡 [no-undefined-variables](/rules/no-undefined-variables)|A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.|![recommended][]|📦|🔮| @@ -48,7 +47,7 @@ Name            &nbs [one-field-subscriptions](/rules/one-field-subscriptions)|A GraphQL subscription is valid only if it contains a single root field.|![recommended][]|📦|🔮| [overlapping-fields-can-be-merged](/rules/overlapping-fields-can-be-merged)|A selection set is only valid if all fields (including spreading any fragments) either correspond to distinct response names or can be merged without ambiguity.|![recommended][]|📦|🔮| [possible-fragment-spread](/rules/possible-fragment-spread)|A fragment spread is only valid if the type condition could ever possibly be true: if there is a non-empty intersection of the possible parent types, and possible types which pass the type condition.|![recommended][]|📦|🔮| -[possible-type-extension](/rules/possible-type-extension)|A type extension is only valid if the type is defined and has the same kind.||📄|🔮|💡 +[possible-type-extension](/rules/possible-type-extension)|A type extension is only valid if the type is defined and has the same kind.|![recommended][]|📄|🔮|💡 [provided-required-arguments](/rules/provided-required-arguments)|A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.|![recommended][]|📄 📦|🔮| [relay-arguments](/rules/relay-arguments)|Set of rules to follow Relay specification for Arguments.|![relay][]|📄|🚀| [relay-connection-types](/rules/relay-connection-types)|Set of rules to follow Relay specification for Connection types.|![relay][]|📄|🚀| @@ -58,10 +57,10 @@ Name            &nbs [require-deprecation-reason](/rules/require-deprecation-reason)|Require all deprecation directives to specify a reason.|![recommended][]|📄|🚀| [require-description](/rules/require-description)|Enforce descriptions in type definitions and operations.|![recommended][]|📄|🚀| [require-field-of-type-query-in-mutation-result](/rules/require-field-of-type-query-in-mutation-result)|Allow the client in one round-trip not only to call mutation but also to get a wagon of data to update their application.|![all][]|📄|🚀| -[require-id-when-available](/rules/require-id-when-available)|Enforce selecting specific fields when they are available on the GraphQL type.|![recommended][]|📦|🚀|💡 -[require-import-fragment](/rules/require-import-fragment)|Require fragments to be imported via an import expression.||📦|🚀|💡 +[require-import-fragment](/rules/require-import-fragment)|Require fragments to be imported via an import expression.|![all][]|📦|🚀|💡 [require-nullable-fields-with-oneof](/rules/require-nullable-fields-with-oneof)|Require `input` or `type` fields to be non-nullable with `@oneOf` directive.|![all][]|📄|🚀| [require-nullable-result-in-root](/rules/require-nullable-result-in-root)|Require nullable fields in root types.|![all][]|📄|🚀|💡 +[require-selections](/rules/require-selections)|Enforce selecting specific fields when they are available on the GraphQL type.|![recommended][]|📦|🚀|💡 [require-type-pattern-with-oneof](/rules/require-type-pattern-with-oneof)|Enforce types with `@oneOf` directive have `error` and `ok` fields.|![all][]|📄|🚀| [scalar-leafs](/rules/scalar-leafs)|A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.|![recommended][]|📦|🔮|💡 [selection-set-depth](/rules/selection-set-depth)|Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://npmjs.com/package/graphql-depth-limit).|![recommended][]|📦|🚀|💡 @@ -69,7 +68,7 @@ Name            &nbs [unique-argument-names](/rules/unique-argument-names)|A GraphQL field or directive is only valid if all supplied arguments are uniquely named.|![recommended][]|📦|🔮| [unique-directive-names](/rules/unique-directive-names)|A GraphQL document is only valid if all defined directives have unique names.|![recommended][]|📄|🔮| [unique-directive-names-per-location](/rules/unique-directive-names-per-location)|A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.|![recommended][]|📄 📦|🔮| -[unique-enum-value-names](/rules/unique-enum-value-names)|A GraphQL enum type is only valid if all its values are uniquely named.||📄|🔮| +[unique-enum-value-names](/rules/unique-enum-value-names)|A GraphQL enum type is only valid if all its values are uniquely named.|![recommended][]|📄|🚀|💡 [unique-field-definition-names](/rules/unique-field-definition-names)|A GraphQL complex type is only valid if all its fields are uniquely named.|![recommended][]|📄|🔮| [unique-fragment-name](/rules/unique-fragment-name)|Enforce unique fragment names across your project.|![recommended][]|📦|🚀| [unique-input-field-names](/rules/unique-input-field-names)|A GraphQL input object value is only valid if all supplied fields are uniquely named.|![recommended][]|📦|🔮| diff --git a/website/src/pages/rules/no-case-insensitive-enum-values-duplicates.md b/website/src/pages/rules/no-case-insensitive-enum-values-duplicates.md deleted file mode 100644 index 5970efb4fff..00000000000 --- a/website/src/pages/rules/no-case-insensitive-enum-values-duplicates.md +++ /dev/null @@ -1,47 +0,0 @@ -# `no-case-insensitive-enum-values-duplicates` - -✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file -enables this rule. - -💡 This rule provides -[suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) - -- Category: `Schema` -- Rule name: `@graphql-eslint/no-case-insensitive-enum-values-duplicates` -- Requires GraphQL Schema: `false` - [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) -- Requires GraphQL Operations: `false` - [ℹ️](/docs/getting-started#extended-linting-rules-with-siblings-operations) - -Disallow case-insensitive enum values duplicates. - -## Usage Examples - -### Incorrect - -```graphql -# eslint @graphql-eslint/no-case-insensitive-enum-values-duplicates: 'error' - -enum MyEnum { - Value - VALUE - ValuE -} -``` - -### Correct - -```graphql -# eslint @graphql-eslint/no-case-insensitive-enum-values-duplicates: 'error' - -enum MyEnum { - Value1 - Value2 - Value3 -} -``` - -## Resources - -- [Rule source](https://github.com/B2o5T/graphql-eslint/tree/master/packages/plugin/src/rules/no-case-insensitive-enum-values-duplicates.ts) -- [Test source](https://github.com/B2o5T/graphql-eslint/tree/master/packages/plugin/__tests__/no-case-insensitive-enum-values-duplicates.spec.ts) diff --git a/website/src/pages/rules/prettier.mdx b/website/src/pages/rules/prettier.md similarity index 100% rename from website/src/pages/rules/prettier.mdx rename to website/src/pages/rules/prettier.md diff --git a/website/src/pages/rules/require-id-when-available.md b/website/src/pages/rules/require-selections.md similarity index 80% rename from website/src/pages/rules/require-id-when-available.md rename to website/src/pages/rules/require-selections.md index 213466fb823..ae58dc4b49b 100644 --- a/website/src/pages/rules/require-id-when-available.md +++ b/website/src/pages/rules/require-selections.md @@ -1,4 +1,4 @@ -# `require-id-when-available` +# `require-selections` ✅ The `"extends": "plugin:@graphql-eslint/operations-recommended"` property in a configuration file enables this rule. @@ -7,7 +7,7 @@ enables this rule. [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) - Category: `Operations` -- Rule name: `@graphql-eslint/require-id-when-available` +- Rule name: `@graphql-eslint/require-selections` - Requires GraphQL Schema: `true` [ℹ️](/docs/getting-started#extended-linting-rules-with-graphql-schema) - Requires GraphQL Operations: `true` @@ -20,7 +20,7 @@ Enforce selecting specific fields when they are available on the GraphQL type. ### Incorrect ```graphql -# eslint @graphql-eslint/require-id-when-available: 'error' +# eslint @graphql-eslint/require-selections: 'error' # In your schema type User { @@ -39,7 +39,7 @@ query { ### Correct ```graphql -# eslint @graphql-eslint/require-id-when-available: 'error' +# eslint @graphql-eslint/require-selections: 'error' # In your schema type User { @@ -88,5 +88,5 @@ The schema defines the following additional types: ## Resources -- [Rule source](https://github.com/B2o5T/graphql-eslint/tree/master/packages/plugin/src/rules/require-id-when-available.ts) -- [Test source](https://github.com/B2o5T/graphql-eslint/tree/master/packages/plugin/__tests__/require-id-when-available.spec.ts) +- [Rule source](https://github.com/B2o5T/graphql-eslint/tree/master/packages/plugin/src/rules/require-selections.ts) +- [Test source](https://github.com/B2o5T/graphql-eslint/tree/master/packages/plugin/__tests__/require-selections.spec.ts) diff --git a/website/src/pages/rules/unique-enum-value-names.md b/website/src/pages/rules/unique-enum-value-names.md index bfac0692c6e..b630ff4e378 100644 --- a/website/src/pages/rules/unique-enum-value-names.md +++ b/website/src/pages/rules/unique-enum-value-names.md @@ -1,5 +1,11 @@ # `unique-enum-value-names` +✅ The `"extends": "plugin:@graphql-eslint/schema-recommended"` property in a configuration file +enables this rule. + +💡 This rule provides +[suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) + - Category: `Schema` - Rule name: `@graphql-eslint/unique-enum-value-names` - Requires GraphQL Schema: `false` @@ -9,9 +15,35 @@ A GraphQL enum type is only valid if all its values are uniquely named. -> This rule is a wrapper around a `graphql-js` validation function. +> This rule disallows case-insensitive enum values duplicates too. + +## Usage Examples + +### Incorrect + +```graphql +# eslint @graphql-eslint/unique-enum-value-names: 'error' + +enum MyEnum { + Value + VALUE + ValuE +} +``` + +### Correct + +```graphql +# eslint @graphql-eslint/unique-enum-value-names: 'error' + +enum MyEnum { + Value1 + Value2 + Value3 +} +``` ## Resources -- [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/UniqueEnumValueNamesRule.ts) -- [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/UniqueEnumValueNamesRule-test.ts) +- [Rule source](https://github.com/B2o5T/graphql-eslint/tree/master/packages/plugin/src/rules/unique-enum-value-names.ts) +- [Test source](https://github.com/B2o5T/graphql-eslint/tree/master/packages/plugin/__tests__/unique-enum-value-names.spec.ts)