Skip to content

Commit

Permalink
feat(require-selections rule): introduce new option `requireAllFiel…
Browse files Browse the repository at this point in the history
…ds` to require all values of `fieldName: string[]` option (#2790)

* aa

* aa

* aa

* aa

* aa

* yoyo
  • Loading branch information
dimaMachina authored Nov 29, 2024
1 parent bac2606 commit 438078d
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 8 deletions.
6 changes: 6 additions & 0 deletions .changeset/six-rice-help.md
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions packages/plugin/src/rules/require-selections/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,5 +501,29 @@ ruleTester.run<RuleOptions, true>('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,
},
],
});
26 changes: 18 additions & 8 deletions packages/plugin/src/rules/require-selections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -115,7 +119,7 @@ export const rule: GraphQLESLintRule<RuleOptions, true> = {
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,
Expand Down Expand Up @@ -203,6 +207,18 @@ export const rule: GraphQLESLintRule<RuleOptions, true> = {
return;
}

checkFragments(node as GraphQLESTreeNode<SelectionSetNode>);

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) {
Expand All @@ -228,20 +244,14 @@ export const rule: GraphQLESLintRule<RuleOptions, true> = {
return false;
});
}

const hasId = hasIdField(node);

checkFragments(node as GraphQLESTreeNode<SelectionSetNode>);

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
? ''
Expand Down
62 changes: 62 additions & 0 deletions packages/plugin/src/rules/require-selections/snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions website/content/rules/require-selections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 438078d

Please sign in to comment.