From 438078d54442ef5e584affb2ae82c32898ce7e3e Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Fri, 29 Nov 2024 23:45:59 +0700 Subject: [PATCH] feat(`require-selections` rule): introduce new option `requireAllFields` to require all values of `fieldName: string[]` option (#2790) * aa * aa * aa * aa * aa * yoyo --- .changeset/six-rice-help.md | 6 ++ .../rules/require-selections/index.test.ts | 24 +++++++ .../src/rules/require-selections/index.ts | 26 +++++--- .../src/rules/require-selections/snapshot.md | 62 +++++++++++++++++++ website/content/rules/require-selections.mdx | 4 ++ 5 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 .changeset/six-rice-help.md diff --git a/.changeset/six-rice-help.md b/.changeset/six-rice-help.md new file mode 100644 index 00000000000..3ded4b6cb8e --- /dev/null +++ b/.changeset/six-rice-help.md @@ -0,0 +1,6 @@ +--- +'@graphql-eslint/eslint-plugin': minor +--- + +feat(`require-selections` rule): introduce new option `requireAllFields` to require all values of +`fieldName: string[]` option diff --git a/packages/plugin/src/rules/require-selections/index.test.ts b/packages/plugin/src/rules/require-selections/index.test.ts index 74067641d91..91dddb0654d 100644 --- a/packages/plugin/src/rules/require-selections/index.test.ts +++ b/packages/plugin/src/rules/require-selections/index.test.ts @@ -501,5 +501,29 @@ ruleTester.run('require-selections', rule, { }, errors: 2, }, + { + name: 'should require all fields with `requireAllFields` option', + code: '{ hasId { id } }', + options: [{ requireAllFields: true, fieldName: ['name', '_id'] }], + parserOptions: { + graphQLConfig: { + schema: TEST_SCHEMA, + documents: '{ foo }', + }, + }, + errors: 2, + }, + { + name: 'should require rest of all fields with `requireAllFields` option', + code: '{ hasId { _id } }', + options: [{ requireAllFields: true, fieldName: ['name', '_id'] }], + parserOptions: { + graphQLConfig: { + schema: TEST_SCHEMA, + documents: '{ foo }', + }, + }, + errors: 1, + }, ], }); diff --git a/packages/plugin/src/rules/require-selections/index.ts b/packages/plugin/src/rules/require-selections/index.ts index 595aa93d72e..12748fe0c84 100644 --- a/packages/plugin/src/rules/require-selections/index.ts +++ b/packages/plugin/src/rules/require-selections/index.ts @@ -42,6 +42,10 @@ const schema = { oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asArray' }], default: DEFAULT_ID_FIELD_NAME, }, + requireAllFields: { + type: 'boolean', + description: 'Whether all fields of `fieldName` option must be included.', + }, }, }, } as const; @@ -115,7 +119,7 @@ export const rule: GraphQLESLintRule = { create(context) { const schema = requireGraphQLSchema(RULE_ID, context); const siblings = requireGraphQLOperations(RULE_ID, context); - const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {}; + const { fieldName = DEFAULT_ID_FIELD_NAME, requireAllFields } = context.options[0] || {}; const idNames = asArray(fieldName); // Check selections only in OperationDefinition, @@ -203,6 +207,18 @@ export const rule: GraphQLESLintRule = { return; } + checkFragments(node as GraphQLESTreeNode); + + if (requireAllFields) { + for (const idName of idNames) { + report([idName]); + } + } else { + report(idNames); + } + } + + function report(idNames: string[]) { function hasIdField({ selections }: typeof node): boolean { return selections.some(selection => { if (selection.kind === Kind.FIELD) { @@ -228,20 +244,14 @@ export const rule: GraphQLESLintRule = { return false; }); } - const hasId = hasIdField(node); - - checkFragments(node as GraphQLESTreeNode); - if (hasId) { return; } - - const pluralSuffix = idNames.length > 1 ? 's' : ''; const fieldName = englishJoinWords( idNames.map(name => `\`${(parent.alias || parent.name).value}.${name}\``), ); - + const pluralSuffix = idNames.length > 1 ? 's' : ''; const addition = checkedFragmentSpreads.size === 0 ? '' diff --git a/packages/plugin/src/rules/require-selections/snapshot.md b/packages/plugin/src/rules/require-selections/snapshot.md index 55274a56a94..f49b3098012 100644 --- a/packages/plugin/src/rules/require-selections/snapshot.md +++ b/packages/plugin/src/rules/require-selections/snapshot.md @@ -250,6 +250,68 @@ exports[`require-selections > invalid > should report an error with union and no 3 | ...UnionFragment `; +exports[`require-selections > invalid > should require all fields with \`requireAllFields\` option 1`] = ` +#### ⌨️ Code + + 1 | { hasId { id } } + +#### ⚙️ Options + + { + "requireAllFields": true, + "fieldName": [ + "name", + "_id" + ] + } + +#### ❌ Error 1/2 + + > 1 | { hasId { id } } + | ^ Field \`hasId.name\` must be selected when it's available on a type. + Include it in your selection set. + +#### 💡 Suggestion: Add \`name\` selection + + 1 | { hasId { name id } } + +#### ❌ Error 2/2 + + > 1 | { hasId { id } } + | ^ Field \`hasId._id\` must be selected when it's available on a type. + Include it in your selection set. + +#### 💡 Suggestion: Add \`_id\` selection + + 1 | { hasId { _id id } } +`; + +exports[`require-selections > invalid > should require rest of all fields with \`requireAllFields\` option 1`] = ` +#### ⌨️ Code + + 1 | { hasId { _id } } + +#### ⚙️ Options + + { + "requireAllFields": true, + "fieldName": [ + "name", + "_id" + ] + } + +#### ❌ Error + + > 1 | { hasId { _id } } + | ^ Field \`hasId.name\` must be selected when it's available on a type. + Include it in your selection set. + +#### 💡 Suggestion: Add \`name\` selection + + 1 | { hasId { name _id } } +`; + exports[`require-selections > invalid > support multiple id field names 1`] = ` #### ⌨️ Code diff --git a/website/content/rules/require-selections.mdx b/website/content/rules/require-selections.mdx index 26ea95a5c94..ba5d8f5dd1f 100644 --- a/website/content/rules/require-selections.mdx +++ b/website/content/rules/require-selections.mdx @@ -80,6 +80,10 @@ The object must be one of the following types: Default: `"id"` +### `requireAllFields` (boolean) + +Whether all fields of `fieldName` option must be included. + --- # Sub Schemas