11import { Kind , ObjectTypeDefinitionNode } from 'graphql' ;
2- import { GraphQLESTreeNode } from '../estree-parser' ;
32import { 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
3517const 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 }
0 commit comments