Skip to content

Commit 624d604

Browse files
authored
fix: ignore root types in strict-id-in-types rule (#879)
1 parent 6848f90 commit 624d604

File tree

5 files changed

+130
-84
lines changed

5 files changed

+130
-84
lines changed

.changeset/sweet-toys-speak.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-eslint/eslint-plugin': minor
3+
---
4+
5+
fix: ignore root types in `strict-id-in-types` rule

docs/rules/strict-id-in-types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
- Category: `Schema`
66
- Rule name: `@graphql-eslint/strict-id-in-types`
7-
- Requires GraphQL Schema: `false` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
7+
- Requires GraphQL Schema: `true` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
88
- Requires GraphQL Operations: `false` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)
99

1010
Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.

packages/plugin/src/rules/strict-id-in-types.ts

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,38 @@
11
import { Kind, ObjectTypeDefinitionNode } from 'graphql';
2-
import { GraphQLESTreeNode } from '../estree-parser';
32
import { GraphQLESLintRule } from '../types';
4-
import { getLocation } from '../utils';
5-
6-
export interface ExceptionRule {
7-
types?: string[];
8-
suffixes?: string[];
9-
}
3+
import { getLocation, requireGraphQLSchemaFromContext } from '../utils';
4+
import { GraphQLESTreeNode } from '../estree-parser';
105

11-
type StrictIdInTypesRuleConfig = {
6+
export type StrictIdInTypesRuleConfig = {
127
acceptedIdNames?: string[];
138
acceptedIdTypes?: string[];
14-
exceptions?: ExceptionRule;
9+
exceptions?: {
10+
types?: string[];
11+
suffixes?: string[];
12+
};
1513
};
1614

17-
interface ShouldIgnoreNodeParams {
18-
node: GraphQLESTreeNode<ObjectTypeDefinitionNode>;
19-
exceptions: ExceptionRule;
20-
}
21-
const shouldIgnoreNode = ({ node, exceptions }: ShouldIgnoreNodeParams): boolean => {
22-
const rawNode = node.rawNode();
23-
24-
if (exceptions.types && exceptions.types.includes(rawNode.name.value)) {
25-
return true;
26-
}
27-
28-
if (exceptions.suffixes && exceptions.suffixes.some(suffix => rawNode.name.value.endsWith(suffix))) {
29-
return true;
30-
}
31-
32-
return false;
33-
};
15+
const RULE_ID = 'strict-id-in-types';
3416

3517
const rule: GraphQLESLintRule<[StrictIdInTypesRuleConfig]> = {
3618
meta: {
3719
type: 'suggestion',
3820
docs: {
39-
description:
40-
'Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.',
21+
description: `Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.`,
4122
category: 'Schema',
4223
recommended: true,
43-
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/strict-id-in-types.md',
24+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
25+
requiresSchema: true,
4426
examples: [
4527
{
4628
title: 'Incorrect',
47-
usage: [{ acceptedIdNames: ['id', '_id'], acceptedIdTypes: ['ID'], exceptions: { suffixes: ['Payload'] } }],
29+
usage: [
30+
{
31+
acceptedIdNames: ['id', '_id'],
32+
acceptedIdTypes: ['ID'],
33+
exceptions: { suffixes: ['Payload'] },
34+
},
35+
],
4836
code: /* GraphQL */ `
4937
# Incorrect field name
5038
type InvalidFieldName {
@@ -147,6 +135,9 @@ const rule: GraphQLESLintRule<[StrictIdInTypesRuleConfig]> = {
147135
},
148136
},
149137
},
138+
messages: {
139+
[RULE_ID]: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }}; Accepted type(s): {{ acceptedTypesString }}.`,
140+
},
150141
},
151142
create(context) {
152143
const options: StrictIdInTypesRuleConfig = {
@@ -156,15 +147,26 @@ const rule: GraphQLESLintRule<[StrictIdInTypesRuleConfig]> = {
156147
...context.options[0],
157148
};
158149

150+
const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
151+
const rootTypeNames = [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()]
152+
.filter(Boolean)
153+
.map(type => type.name);
154+
const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
155+
159156
return {
160-
ObjectTypeDefinition(node) {
161-
if (shouldIgnoreNode({ node, exceptions: options.exceptions })) {
157+
[selector](node: GraphQLESTreeNode<ObjectTypeDefinitionNode>) {
158+
const typeName = node.name.value;
159+
160+
const shouldIgnoreNode =
161+
options.exceptions.types?.includes(typeName) ||
162+
options.exceptions.suffixes?.some(suffix => typeName.endsWith(suffix));
163+
164+
if (shouldIgnoreNode) {
162165
return;
163166
}
164167

165168
const validIds = node.fields.filter(field => {
166169
const fieldNode = field.rawNode();
167-
168170
const isValidIdName = options.acceptedIdNames.includes(fieldNode.name.value);
169171

170172
// To be a valid type, it must be non-null and one of the accepted types.
@@ -175,18 +177,18 @@ const rule: GraphQLESLintRule<[StrictIdInTypesRuleConfig]> = {
175177

176178
return isValidIdName && isValidIdType;
177179
});
178-
const typeName = node.name.value;
180+
179181
// Usually, there should be only one unique identifier field per type.
180182
// Some clients allow multiple fields to be used. If more people need this,
181183
// we can extend this rule later.
182184
if (validIds.length !== 1) {
183185
context.report({
184186
loc: getLocation(node.name.loc, typeName),
185-
message: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }} ; Accepted type(s): {{ acceptedTypesString }}`,
187+
messageId: RULE_ID,
186188
data: {
187189
typeName,
188-
acceptedNamesString: options.acceptedIdNames.join(','),
189-
acceptedTypesString: options.acceptedIdTypes.join(','),
190+
acceptedNamesString: options.acceptedIdNames.join(', '),
191+
acceptedTypesString: options.acceptedIdTypes.join(', '),
190192
},
191193
});
192194
}

packages/plugin/tests/__snapshots__/strict-id-in-types.spec.ts.snap

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,45 @@
22

33
exports[` 1`] = `
44
> 1 | type B { name: String! }
5-
| ^ B must have exactly one non-nullable unique identifier. Accepted name(s): id ; Accepted type(s): ID
5+
| ^ B must have exactly one non-nullable unique identifier. Accepted name(s): id; Accepted type(s): ID.
66
`;
77

88
exports[` 2`] = `
99
> 1 | type B { id: ID! _id: String! }
10-
| ^ B must have exactly one non-nullable unique identifier. Accepted name(s): id,_id ; Accepted type(s): ID,String
10+
| ^ B must have exactly one non-nullable unique identifier. Accepted name(s): id, _id; Accepted type(s): ID, String.
1111
`;
1212

1313
exports[` 3`] = `
1414
> 1 | type B { id: String! } type B1 { id: [String] } type B2 { id: [String!] } type B3 { id: [String]! } type B4 { id: [String!]! }
15-
| ^^ B1 must have exactly one non-nullable unique identifier. Accepted name(s): id ; Accepted type(s): String
15+
| ^^ B1 must have exactly one non-nullable unique identifier. Accepted name(s): id; Accepted type(s): String.
1616
`;
1717

1818
exports[` 4`] = `
1919
> 1 | type B { id: String! } type B1 { id: [String] } type B2 { id: [String!] } type B3 { id: [String]! } type B4 { id: [String!]! }
20-
| ^^ B2 must have exactly one non-nullable unique identifier. Accepted name(s): id ; Accepted type(s): String
20+
| ^^ B2 must have exactly one non-nullable unique identifier. Accepted name(s): id; Accepted type(s): String.
2121
`;
2222

2323
exports[` 5`] = `
2424
> 1 | type B { id: String! } type B1 { id: [String] } type B2 { id: [String!] } type B3 { id: [String]! } type B4 { id: [String!]! }
25-
| ^^ B3 must have exactly one non-nullable unique identifier. Accepted name(s): id ; Accepted type(s): String
25+
| ^^ B3 must have exactly one non-nullable unique identifier. Accepted name(s): id; Accepted type(s): String.
2626
`;
2727

2828
exports[` 6`] = `
2929
> 1 | type B { id: String! } type B1 { id: [String] } type B2 { id: [String!] } type B3 { id: [String]! } type B4 { id: [String!]! }
30-
| ^^ B4 must have exactly one non-nullable unique identifier. Accepted name(s): id ; Accepted type(s): String
30+
| ^^ B4 must have exactly one non-nullable unique identifier. Accepted name(s): id; Accepted type(s): String.
3131
`;
3232

3333
exports[` 7`] = `
3434
> 1 | type B { id: ID! } type Bresult { key: String! } type BPayload { bool: Boolean! } type BPagination { num: Int! }
35-
| ^^^^^^^ Bresult must have exactly one non-nullable unique identifier. Accepted name(s): id ; Accepted type(s): ID
35+
| ^^^^^^^ Bresult must have exactly one non-nullable unique identifier. Accepted name(s): id; Accepted type(s): ID.
3636
`;
3737

3838
exports[` 8`] = `
3939
> 1 | type B { id: ID! } type Bresult { key: String! } type BPayload { bool: Boolean! } type BPagination { num: Int! }
40-
| ^^^^^^^^^^^ BPagination must have exactly one non-nullable unique identifier. Accepted name(s): id ; Accepted type(s): ID
40+
| ^^^^^^^^^^^ BPagination must have exactly one non-nullable unique identifier. Accepted name(s): id; Accepted type(s): ID.
4141
`;
4242

4343
exports[` 9`] = `
4444
> 1 | type B { id: ID! } type BError { message: String! }
45-
| ^^^^^^ BError must have exactly one non-nullable unique identifier. Accepted name(s): id ; Accepted type(s): ID
45+
| ^^^^^^ BError must have exactly one non-nullable unique identifier. Accepted name(s): id; Accepted type(s): ID.
4646
`;

0 commit comments

Comments
 (0)