1
1
import { Kind , ObjectTypeDefinitionNode } from 'graphql' ;
2
- import { GraphQLESTreeNode } from '../estree-parser' ;
3
2
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' ;
10
5
11
- type StrictIdInTypesRuleConfig = {
6
+ export type StrictIdInTypesRuleConfig = {
12
7
acceptedIdNames ?: string [ ] ;
13
8
acceptedIdTypes ?: string [ ] ;
14
- exceptions ?: ExceptionRule ;
9
+ exceptions ?: {
10
+ types ?: string [ ] ;
11
+ suffixes ?: string [ ] ;
12
+ } ;
15
13
} ;
16
14
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' ;
34
16
35
17
const rule : GraphQLESLintRule < [ StrictIdInTypesRuleConfig ] > = {
36
18
meta : {
37
19
type : 'suggestion' ,
38
20
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.` ,
41
22
category : 'Schema' ,
42
23
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 ,
44
26
examples : [
45
27
{
46
28
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
+ ] ,
48
36
code : /* GraphQL */ `
49
37
# Incorrect field name
50
38
type InvalidFieldName {
@@ -147,6 +135,9 @@ const rule: GraphQLESLintRule<[StrictIdInTypesRuleConfig]> = {
147
135
} ,
148
136
} ,
149
137
} ,
138
+ messages : {
139
+ [ RULE_ID ] : `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }}; Accepted type(s): {{ acceptedTypesString }}.` ,
140
+ } ,
150
141
} ,
151
142
create ( context ) {
152
143
const options : StrictIdInTypesRuleConfig = {
@@ -156,15 +147,26 @@ const rule: GraphQLESLintRule<[StrictIdInTypesRuleConfig]> = {
156
147
...context . options [ 0 ] ,
157
148
} ;
158
149
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
+
159
156
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 ) {
162
165
return ;
163
166
}
164
167
165
168
const validIds = node . fields . filter ( field => {
166
169
const fieldNode = field . rawNode ( ) ;
167
-
168
170
const isValidIdName = options . acceptedIdNames . includes ( fieldNode . name . value ) ;
169
171
170
172
// To be a valid type, it must be non-null and one of the accepted types.
@@ -175,18 +177,18 @@ const rule: GraphQLESLintRule<[StrictIdInTypesRuleConfig]> = {
175
177
176
178
return isValidIdName && isValidIdType ;
177
179
} ) ;
178
- const typeName = node . name . value ;
180
+
179
181
// Usually, there should be only one unique identifier field per type.
180
182
// Some clients allow multiple fields to be used. If more people need this,
181
183
// we can extend this rule later.
182
184
if ( validIds . length !== 1 ) {
183
185
context . report ( {
184
186
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 ,
186
188
data : {
187
189
typeName,
188
- acceptedNamesString : options . acceptedIdNames . join ( ',' ) ,
189
- acceptedTypesString : options . acceptedIdTypes . join ( ',' ) ,
190
+ acceptedNamesString : options . acceptedIdNames . join ( ', ' ) ,
191
+ acceptedTypesString : options . acceptedIdTypes . join ( ', ' ) ,
190
192
} ,
191
193
} ) ;
192
194
}
0 commit comments