diff --git a/.changeset/empty-singers-develop.md b/.changeset/empty-singers-develop.md new file mode 100644 index 00000000000..34b679e87dd --- /dev/null +++ b/.changeset/empty-singers-develop.md @@ -0,0 +1,5 @@ +--- +"@graphql-eslint/eslint-plugin": feat +--- + +feat: add a new option `{` for alphabetize rule to sort fields `selection set` diff --git a/packages/plugin/src/configs/operations-all.ts b/packages/plugin/src/configs/operations-all.ts index d132ea0daec..593e7d4d863 100644 --- a/packages/plugin/src/configs/operations-all.ts +++ b/packages/plugin/src/configs/operations-all.ts @@ -13,7 +13,7 @@ export = { selections: ['OperationDefinition', 'FragmentDefinition'], variables: true, arguments: ['Field', 'Directive'], - groups: ['...', 'id', '*', 'createdAt', 'updatedAt'], + groups: ['...', 'id', '*', '{'], }, ], '@graphql-eslint/lone-executable-definition': 'error', diff --git a/packages/plugin/src/rules/alphabetize/index.test.ts b/packages/plugin/src/rules/alphabetize/index.test.ts index b98e385aa3e..17665e817d0 100644 --- a/packages/plugin/src/rules/alphabetize/index.test.ts +++ b/packages/plugin/src/rules/alphabetize/index.test.ts @@ -504,5 +504,57 @@ ruleTester.run('alphabetize', rule, { `, errors: 4, }, + { + name: 'should sort selection set at the end', + options: [ + { + selections: ['OperationDefinition'], + groups: ['id', '*', 'updatedAt', '{'], + }, + ], + code: /* GraphQL */ ` + { + zz + updatedAt + createdAt { + __typename + } + aa + user { + id + } + aab { + id + } + } + `, + errors: 2, + }, + { + name: 'should sort selection set at the start', + options: [ + { + selections: ['OperationDefinition'], + groups: ['{', 'id', '*', 'updatedAt'], + }, + ], + code: /* GraphQL */ ` + { + zz + updatedAt + createdAt { + __typename + } + aa + user { + id + } + aab { + id + } + } + `, + errors: 3, + }, ], }); diff --git a/packages/plugin/src/rules/alphabetize/index.ts b/packages/plugin/src/rules/alphabetize/index.ts index d7533417dab..7437c4d87cd 100644 --- a/packages/plugin/src/rules/alphabetize/index.ts +++ b/packages/plugin/src/rules/alphabetize/index.ts @@ -93,8 +93,13 @@ const schema = { groups: { ...ARRAY_DEFAULT_OPTIONS, minItems: 2, - description: - "Custom order group. Example: `['id', '*', 'createdAt', 'updatedAt', '...']` where `...` stands for fragment spreads, and `*` stands for everything else.", + description: [ + "Order group. Example: `['...', 'id', '*', '{']` where:", + '- `...` stands for fragment spreads', + '- `id` stands for field with name `id`', + '- `*` stands for everything else', + '- `{` stands for fields `selection set`', + ].join('\n'), }, }, }, @@ -203,7 +208,7 @@ export const rule: GraphQLESLintRule = { selections: selectionsEnum, variables: true, arguments: [Kind.FIELD, Kind.DIRECTIVE], - groups: ['...', 'id', '*', 'createdAt', 'updatedAt'], + groups: ['...', 'id', '*', '{'], }, ], }, @@ -417,16 +422,11 @@ function getIndex({ }): number { // Try an exact match let index = groups.indexOf(getName(node)); - + if (index === -1 && 'selectionSet' in node && node.selectionSet) index = groups.indexOf('{'); // Check for the fragment spread group - if (index === -1 && node.kind === Kind.FRAGMENT_SPREAD) { - index = groups.indexOf('...'); - } - + if (index === -1 && node.kind === Kind.FRAGMENT_SPREAD) index = groups.indexOf('...'); // Check for the catch-all group - if (index === -1) { - index = groups.indexOf('*'); - } + if (index === -1) index = groups.indexOf('*'); return index; } diff --git a/packages/plugin/src/rules/alphabetize/snapshot.md b/packages/plugin/src/rules/alphabetize/snapshot.md index 7f57fa7332c..a912d4c5141 100644 --- a/packages/plugin/src/rules/alphabetize/snapshot.md +++ b/packages/plugin/src/rules/alphabetize/snapshot.md @@ -1031,6 +1031,141 @@ exports[`alphabetize > invalid > should sort definitions 1`] = ` 59 | # END `; +exports[`alphabetize > invalid > should sort selection set at the end 1`] = ` +#### ⌨️ Code + + 1 | { + 2 | zz + 3 | updatedAt + 4 | createdAt { + 5 | __typename + 6 | } + 7 | aa + 8 | user { + 9 | id + 10 | } + 11 | aab { + 12 | id + 13 | } + 14 | } + +#### ⚙️ Options + + { + "selections": [ + "OperationDefinition" + ], + "groups": [ + "id", + "*", + "updatedAt", + "{" + ] + } + +#### ❌ Error 1/2 + + 6 | } + > 7 | aa + | ^^ field "aa" should be before field "createdAt" + 8 | user { + +#### ❌ Error 2/2 + + 10 | } + > 11 | aab { + | ^^^ field "aab" should be before field "user" + 12 | id + +#### 🔧 Autofix output + + 1 | { + 2 | aa + 3 | zz + 4 | updatedAt + 5 | aab { + 6 | id + 7 | } + 8 | createdAt { + 9 | __typename + 10 | } + 11 | user { + 12 | id + 13 | } + 14 | } +`; + +exports[`alphabetize > invalid > should sort selection set at the start 1`] = ` +#### ⌨️ Code + + 1 | { + 2 | zz + 3 | updatedAt + 4 | createdAt { + 5 | __typename + 6 | } + 7 | aa + 8 | user { + 9 | id + 10 | } + 11 | aab { + 12 | id + 13 | } + 14 | } + +#### ⚙️ Options + + { + "selections": [ + "OperationDefinition" + ], + "groups": [ + "{", + "id", + "*", + "updatedAt" + ] + } + +#### ❌ Error 1/3 + + 3 | updatedAt + > 4 | createdAt { + | ^^^^^^^^^ field "createdAt" should be before field "updatedAt" + 5 | __typename + +#### ❌ Error 2/3 + + 7 | aa + > 8 | user { + | ^^^^ field "user" should be before field "aa" + 9 | id + +#### ❌ Error 3/3 + + 10 | } + > 11 | aab { + | ^^^ field "aab" should be before field "user" + 12 | id + +#### 🔧 Autofix output + + 1 | { + 2 | aab { + 3 | id + 4 | } + 5 | createdAt { + 6 | __typename + 7 | } + 8 | user { + 9 | id + 10 | } + 11 | aa + 12 | zz + 13 | updatedAt + 14 | } +`; + exports[`alphabetize > invalid > should sort selections by group when \`*\` is between 1`] = ` #### ⌨️ Code diff --git a/website/src/pages/rules/alphabetize.md b/website/src/pages/rules/alphabetize.md index a4dacb5b2ca..d54d0a60b66 100644 --- a/website/src/pages/rules/alphabetize.md +++ b/website/src/pages/rules/alphabetize.md @@ -165,8 +165,12 @@ Definitions – `type`, `interface`, `enum`, `scalar`, `input`, `union` and `dir ### `groups` (array) -Custom order group. Example: `['id', '*', 'createdAt', 'updatedAt', '...']` where `...` stands for -fragment spreads, and `*` stands for everything else. +Custom order group. Example: `['...', 'id', '*', '{']` where: + +- `...` stands for fragment spreads +- `id` stands for field with name `id` +- `*` stands for everything else +- `{` stands for field `selection set` The object is an array with all elements of the type `string`.