1
- import { ASTKindToNode , Kind } from 'graphql' ;
1
+ import { ASTKindToNode , ASTNode , ASTVisitor , GraphQLSchema , isInterfaceType , Kind , visit } from 'graphql' ;
2
+ import lowerCase from 'lodash.lowercase' ;
2
3
import { GraphQLESLintRule , ValueOf } from '../types' ;
3
- import { requireReachableTypesFromContext } from '../utils' ;
4
+ import { getTypeName , requireGraphQLSchemaFromContext } from '../utils' ;
4
5
import { GraphQLESTreeNode } from '../estree-parser' ;
5
6
6
- const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE' ;
7
7
const RULE_ID = 'no-unreachable-types' ;
8
8
9
9
const KINDS = [
@@ -25,10 +25,66 @@ const KINDS = [
25
25
type AllowedKind = typeof KINDS [ number ] ;
26
26
type AllowedKindToNode = Pick < ASTKindToNode , AllowedKind > ;
27
27
28
+ type ReachableTypes = Set < string > ;
29
+
30
+ let reachableTypesCache : ReachableTypes ;
31
+
32
+ function getReachableTypes ( schema : GraphQLSchema ) : ReachableTypes {
33
+ // We don't want cache reachableTypes on test environment
34
+ // Otherwise reachableTypes will be same for all tests
35
+ if ( process . env . NODE_ENV !== 'test' && reachableTypesCache ) {
36
+ return reachableTypesCache ;
37
+ }
38
+ const reachableTypes : ReachableTypes = new Set ( ) ;
39
+
40
+ const collect = ( node : ASTNode ) : false | void => {
41
+ const typeName = getTypeName ( node ) ;
42
+ if ( reachableTypes . has ( typeName ) ) {
43
+ return ;
44
+ }
45
+ reachableTypes . add ( typeName ) ;
46
+ const type = schema . getType ( typeName ) || schema . getDirective ( typeName ) ;
47
+
48
+ if ( isInterfaceType ( type ) ) {
49
+ const { objects, interfaces } = schema . getImplementations ( type ) ;
50
+ for ( const { astNode } of [ ...objects , ...interfaces ] ) {
51
+ visit ( astNode , visitor ) ;
52
+ }
53
+ } else if ( type . astNode ) {
54
+ // astNode can be undefined for ID, String, Boolean
55
+ visit ( type . astNode , visitor ) ;
56
+ }
57
+ } ;
58
+
59
+ const visitor : ASTVisitor = {
60
+ InterfaceTypeDefinition : collect ,
61
+ ObjectTypeDefinition : collect ,
62
+ InputValueDefinition : collect ,
63
+ UnionTypeDefinition : collect ,
64
+ FieldDefinition : collect ,
65
+ Directive : collect ,
66
+ NamedType : collect ,
67
+ } ;
68
+
69
+ for ( const type of [
70
+ schema , // visiting SchemaDefinition node
71
+ schema . getQueryType ( ) ,
72
+ schema . getMutationType ( ) ,
73
+ schema . getSubscriptionType ( ) ,
74
+ ] ) {
75
+ // if schema don't have Query type, schema.astNode will be undefined
76
+ if ( type ?. astNode ) {
77
+ visit ( type . astNode , visitor ) ;
78
+ }
79
+ }
80
+ reachableTypesCache = reachableTypes ;
81
+ return reachableTypesCache ;
82
+ }
83
+
28
84
const rule : GraphQLESLintRule = {
29
85
meta : {
30
86
messages : {
31
- [ UNREACHABLE_TYPE ] : 'Type " {{ typeName }}" is unreachable' ,
87
+ [ RULE_ID ] : '{{ type }} ` {{ typeName }}` is unreachable. ' ,
32
88
} ,
33
89
docs : {
34
90
description : `Requires all types to be reachable at some level by root level fields.` ,
@@ -70,18 +126,23 @@ const rule: GraphQLESLintRule = {
70
126
hasSuggestions : true ,
71
127
} ,
72
128
create ( context ) {
73
- const reachableTypes = requireReachableTypesFromContext ( RULE_ID , context ) ;
129
+ const schema = requireGraphQLSchemaFromContext ( RULE_ID , context ) ;
130
+ const reachableTypes = getReachableTypes ( schema ) ;
74
131
const selector = KINDS . join ( ',' ) ;
75
132
76
133
return {
77
134
[ selector ] ( node : GraphQLESTreeNode < ValueOf < AllowedKindToNode > > ) {
78
135
const typeName = node . name . value ;
79
136
80
137
if ( ! reachableTypes . has ( typeName ) ) {
138
+ const type = lowerCase ( node . kind . replace ( / ( E x t e n s i o n | D e f i n i t i o n ) $ / , '' ) )
81
139
context . report ( {
82
140
node : node . name ,
83
- messageId : UNREACHABLE_TYPE ,
84
- data : { typeName } ,
141
+ messageId : RULE_ID ,
142
+ data : {
143
+ type : type [ 0 ] . toUpperCase ( ) + type . slice ( 1 ) ,
144
+ typeName
145
+ } ,
85
146
suggest : [
86
147
{
87
148
desc : `Remove \`${ typeName } \`` ,
0 commit comments