1
- import { GraphQLESLintRule , GraphQLESlintRuleContext } from '../types' ;
2
- import { requireGraphQLSchemaFromContext } from '../utils' ;
3
-
4
1
import { validate , GraphQLSchema , DocumentNode , ASTNode , ValidationRule } from 'graphql' ;
5
2
import { validateSDL } from 'graphql/validation/validate' ;
3
+ import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader' ;
4
+ import fs from 'fs' ;
5
+ import { GraphQLESLintRule , GraphQLESlintRuleContext } from '../types' ;
6
+ import { requireGraphQLSchemaFromContext } from '../utils' ;
6
7
import { GraphQLESTreeNode } from '../estree-parser' ;
7
8
8
9
function extractRuleName ( stack : string | undefined ) : string | null {
@@ -44,11 +45,16 @@ export function validateDoc(
44
45
}
45
46
}
46
47
48
+ const isGraphQLImportFile = rawSDL => {
49
+ const trimmedRawSDL = rawSDL . trim ( ) ;
50
+ return trimmedRawSDL . startsWith ( '# import' ) || trimmedRawSDL . startsWith ( '#import' ) ;
51
+ } ;
52
+
47
53
const validationToRule = (
48
54
name : string ,
49
55
ruleName : string ,
50
- meta : GraphQLESLintRule [ 'meta' ] ,
51
- skipSchema = false
56
+ docs : GraphQLESLintRule [ 'meta' ] [ 'docs '] ,
57
+ getDocumentNode ?: ( context : GraphQLESlintRuleContext ) => DocumentNode | null
52
58
) : Record < typeof name , GraphQLESLintRule < any , true > > => {
53
59
let ruleFn : null | ValidationRule = null ;
54
60
@@ -62,35 +68,42 @@ const validationToRule = (
62
68
}
63
69
}
64
70
71
+ const requiresSchema = docs . requiresSchema ?? true ;
72
+
65
73
return {
66
74
[ name ] : {
67
75
meta : {
68
- ...meta ,
69
76
docs : {
70
77
category : 'Validation' ,
71
- requiresSchema : ! skipSchema ,
78
+ requiresSchema,
72
79
requiresSiblings : false ,
73
80
url : `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${ name } .md` ,
74
- ...meta . docs ,
81
+ ...docs ,
75
82
description :
76
- meta . docs . description +
83
+ docs . description +
77
84
`\n\n> This rule is a wrapper around a \`graphql-js\` validation function. [You can find it's source code here](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/${ ruleName } Rule.ts).` ,
78
85
} ,
79
86
} ,
80
- create : context => {
87
+ create ( context ) {
81
88
return {
82
89
Document ( node ) {
83
90
if ( ! ruleFn ) {
84
91
// eslint-disable-next-line no-console
85
92
console . warn (
86
93
`You rule "${ name } " depends on a GraphQL validation rule ("${ ruleName } ") but it's not available in the "graphql-js" version you are using. Skipping...`
87
94
) ;
88
-
89
95
return ;
90
96
}
91
97
92
- const schema = skipSchema ? null : requireGraphQLSchemaFromContext ( name , context ) ;
93
- validateDoc ( node , context , schema , node . rawNode ( ) , [ ruleFn ] , ruleName ) ;
98
+ const schema = requiresSchema ? requireGraphQLSchemaFromContext ( name , context ) : null ;
99
+
100
+ let documentNode : DocumentNode ;
101
+ const filePath = context . getFilename ( ) ;
102
+ const isVirtualFile = ! fs . existsSync ( filePath ) ;
103
+ if ( ! isVirtualFile && getDocumentNode ) {
104
+ documentNode = getDocumentNode ( context ) ;
105
+ }
106
+ validateDoc ( node , context , schema , documentNode || node . rawNode ( ) , [ ruleFn ] , ruleName ) ;
94
107
} ,
95
108
} ;
96
109
} ,
@@ -101,32 +114,24 @@ const validationToRule = (
101
114
export const GRAPHQL_JS_VALIDATIONS = Object . assign (
102
115
{ } ,
103
116
validationToRule ( 'executable-definitions' , 'ExecutableDefinitions' , {
104
- docs : {
105
- description : `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.` ,
106
- } ,
117
+ description : `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.` ,
107
118
} ) ,
108
119
validationToRule ( 'fields-on-correct-type' , 'FieldsOnCorrectType' , {
109
- docs : {
110
- description : `A GraphQL document is only valid if all fields selected are defined by the parent type, or are an allowed meta field such as __typename.` ,
111
- } ,
120
+ description : `A GraphQL document is only valid if all fields selected are defined by the parent type, or are an allowed meta field such as __typename.` ,
112
121
} ) ,
113
122
validationToRule ( 'fragments-on-composite-type' , 'FragmentsOnCompositeTypes' , {
114
- docs : {
115
- description : `Fragments use a type condition to determine if they apply, since fragments can only be spread into a composite type (object, interface, or union), the type condition must also be a composite type.` ,
116
- } ,
123
+ description : `Fragments use a type condition to determine if they apply, since fragments can only be spread into a composite type (object, interface, or union), the type condition must also be a composite type.` ,
117
124
} ) ,
118
125
validationToRule ( 'known-argument-names' , 'KnownArgumentNames' , {
119
- docs : {
120
- description : `A GraphQL field is only valid if all supplied arguments are defined by that field.` ,
121
- } ,
126
+ description : `A GraphQL field is only valid if all supplied arguments are defined by that field.` ,
122
127
} ) ,
123
128
validationToRule ( 'known-directives' , 'KnownDirectives' , {
124
- docs : {
125
- description : `A GraphQL document is only valid if all \`@directives\` are known by the schema and legally positioned.` ,
126
- } ,
129
+ description : `A GraphQL document is only valid if all \`@directives\` are known by the schema and legally positioned.` ,
127
130
} ) ,
128
- validationToRule ( 'known-fragment-names' , 'KnownFragmentNames' , {
129
- docs : {
131
+ validationToRule (
132
+ 'known-fragment-names' ,
133
+ 'KnownFragmentNames' ,
134
+ {
130
135
description : `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.` ,
131
136
examples : [
132
137
{
@@ -171,170 +176,98 @@ export const GRAPHQL_JS_VALIDATIONS = Object.assign(
171
176
} ,
172
177
] ,
173
178
} ,
174
- } ) ,
179
+ context => {
180
+ const code = context . getSourceCode ( ) . text ;
181
+ if ( ! isGraphQLImportFile ( code ) ) {
182
+ return null ;
183
+ }
184
+ // Import documents if file contains '#import' comments
185
+ const fileLoader = new GraphQLFileLoader ( ) ;
186
+ const graphqlAst = fileLoader . handleFileContent ( code , context . getFilename ( ) , { noLocation : true } ) ;
187
+ return graphqlAst . document ;
188
+ }
189
+ ) ,
175
190
validationToRule ( 'known-type-names' , 'KnownTypeNames' , {
176
- docs : {
177
- description : `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.` ,
178
- } ,
191
+ description : `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.` ,
179
192
} ) ,
180
193
validationToRule ( 'lone-anonymous-operation' , 'LoneAnonymousOperation' , {
181
- docs : {
182
- description : `A GraphQL document is only valid if when it contains an anonymous operation (the query short-hand) that it contains only that one operation definition.` ,
183
- } ,
194
+ description : `A GraphQL document is only valid if when it contains an anonymous operation (the query short-hand) that it contains only that one operation definition.` ,
195
+ } ) ,
196
+ validationToRule ( 'lone-schema-definition' , 'LoneSchemaDefinition' , {
197
+ description : `A GraphQL document is only valid if it contains only one schema definition.` ,
198
+ requiresSchema : false ,
184
199
} ) ,
185
- validationToRule (
186
- 'lone-schema-definition' ,
187
- 'LoneSchemaDefinition' ,
188
- {
189
- docs : {
190
- description : `A GraphQL document is only valid if it contains only one schema definition.` ,
191
- } ,
192
- } ,
193
- true
194
- ) ,
195
200
validationToRule ( 'no-fragment-cycles' , 'NoFragmentCycles' , {
196
- docs : {
197
- description : `A GraphQL fragment is only valid when it does not have cycles in fragments usage.` ,
198
- } ,
201
+ description : `A GraphQL fragment is only valid when it does not have cycles in fragments usage.` ,
199
202
} ) ,
200
203
validationToRule ( 'no-undefined-variables' , 'NoUndefinedVariables' , {
201
- docs : {
202
- description : `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.` ,
203
- } ,
204
+ description : `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.` ,
204
205
} ) ,
205
206
validationToRule ( 'no-unused-fragments' , 'NoUnusedFragments' , {
206
- docs : {
207
- description : `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.` ,
208
- } ,
207
+ description : `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.` ,
209
208
} ) ,
210
209
validationToRule ( 'no-unused-variables' , 'NoUnusedVariables' , {
211
- docs : {
212
- description : `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.` ,
213
- } ,
210
+ description : `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.` ,
214
211
} ) ,
215
212
validationToRule ( 'overlapping-fields-can-be-merged' , 'OverlappingFieldsCanBeMerged' , {
216
- docs : {
217
- description : `A selection set is only valid if all fields (including spreading any fragments) either correspond to distinct response names or can be merged without ambiguity.` ,
218
- } ,
213
+ description : `A selection set is only valid if all fields (including spreading any fragments) either correspond to distinct response names or can be merged without ambiguity.` ,
219
214
} ) ,
220
215
validationToRule ( 'possible-fragment-spread' , 'PossibleFragmentSpreads' , {
221
- docs : {
222
- description : `A fragment spread is only valid if the type condition could ever possibly be true: if there is a non-empty intersection of the possible parent types, and possible types which pass the type condition.` ,
223
- } ,
216
+ description : `A fragment spread is only valid if the type condition could ever possibly be true: if there is a non-empty intersection of the possible parent types, and possible types which pass the type condition.` ,
217
+ } ) ,
218
+ validationToRule ( 'possible-type-extension' , 'PossibleTypeExtensions' , {
219
+ description : `A type extension is only valid if the type is defined and has the same kind.` ,
220
+ requiresSchema : false ,
224
221
} ) ,
225
- validationToRule (
226
- 'possible-type-extension' ,
227
- 'PossibleTypeExtensions' ,
228
- {
229
- docs : {
230
- description : `A type extension is only valid if the type is defined and has the same kind.` ,
231
- } ,
232
- } ,
233
- true
234
- ) ,
235
222
validationToRule ( 'provided-required-arguments' , 'ProvidedRequiredArguments' , {
236
- docs : {
237
- description : `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.` ,
238
- } ,
223
+ description : `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.` ,
239
224
} ) ,
240
225
validationToRule ( 'scalar-leafs' , 'ScalarLeafs' , {
241
- docs : {
242
- description : `A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.` ,
243
- } ,
226
+ description : `A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.` ,
244
227
} ) ,
245
228
validationToRule ( 'one-field-subscriptions' , 'SingleFieldSubscriptions' , {
246
- docs : {
247
- description : `A GraphQL subscription is valid only if it contains a single root field.` ,
248
- } ,
229
+ description : `A GraphQL subscription is valid only if it contains a single root field.` ,
249
230
} ) ,
250
231
validationToRule ( 'unique-argument-names' , 'UniqueArgumentNames' , {
251
- docs : {
252
- description : `A GraphQL field or directive is only valid if all supplied arguments are uniquely named.` ,
253
- } ,
232
+ description : `A GraphQL field or directive is only valid if all supplied arguments are uniquely named.` ,
233
+ } ) ,
234
+ validationToRule ( 'unique-directive-names' , 'UniqueDirectiveNames' , {
235
+ description : `A GraphQL document is only valid if all defined directives have unique names.` ,
236
+ requiresSchema : false ,
254
237
} ) ,
255
- validationToRule (
256
- 'unique-directive-names' ,
257
- 'UniqueDirectiveNames' ,
258
- {
259
- docs : {
260
- description : `A GraphQL document is only valid if all defined directives have unique names.` ,
261
- } ,
262
- } ,
263
- true
264
- ) ,
265
238
validationToRule ( 'unique-directive-names-per-location' , 'UniqueDirectivesPerLocation' , {
266
- docs : {
267
- description : `A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.` ,
268
- } ,
239
+ description : `A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.` ,
240
+ } ) ,
241
+ validationToRule ( 'unique-enum-value-names' , 'UniqueEnumValueNames' , {
242
+ description : `A GraphQL enum type is only valid if all its values are uniquely named.` ,
243
+ requiresSchema : false ,
244
+ } ) ,
245
+ validationToRule ( 'unique-field-definition-names' , 'UniqueFieldDefinitionNames' , {
246
+ description : `A GraphQL complex type is only valid if all its fields are uniquely named.` ,
247
+ requiresSchema : false ,
248
+ } ) ,
249
+ validationToRule ( 'unique-input-field-names' , 'UniqueInputFieldNames' , {
250
+ description : `A GraphQL input object value is only valid if all supplied fields are uniquely named.` ,
251
+ requiresSchema : false ,
252
+ } ) ,
253
+ validationToRule ( 'unique-operation-types' , 'UniqueOperationTypes' , {
254
+ description : `A GraphQL document is only valid if it has only one type per operation.` ,
255
+ requiresSchema : false ,
256
+ } ) ,
257
+ validationToRule ( 'unique-type-names' , 'UniqueTypeNames' , {
258
+ description : `A GraphQL document is only valid if all defined types have unique names.` ,
259
+ requiresSchema : false ,
269
260
} ) ,
270
- validationToRule (
271
- 'unique-enum-value-names' ,
272
- 'UniqueEnumValueNames' ,
273
- {
274
- docs : {
275
- description : `A GraphQL enum type is only valid if all its values are uniquely named.` ,
276
- } ,
277
- } ,
278
- true
279
- ) ,
280
- validationToRule (
281
- 'unique-field-definition-names' ,
282
- 'UniqueFieldDefinitionNames' ,
283
- {
284
- docs : {
285
- description : `A GraphQL complex type is only valid if all its fields are uniquely named.` ,
286
- } ,
287
- } ,
288
- true
289
- ) ,
290
- validationToRule (
291
- 'unique-input-field-names' ,
292
- 'UniqueInputFieldNames' ,
293
- {
294
- docs : {
295
- description : `A GraphQL input object value is only valid if all supplied fields are uniquely named.` ,
296
- } ,
297
- } ,
298
- true
299
- ) ,
300
- validationToRule (
301
- 'unique-operation-types' ,
302
- 'UniqueOperationTypes' ,
303
- {
304
- docs : {
305
- description : `A GraphQL document is only valid if it has only one type per operation.` ,
306
- } ,
307
- } ,
308
- true
309
- ) ,
310
- validationToRule (
311
- 'unique-type-names' ,
312
- 'UniqueTypeNames' ,
313
- {
314
- docs : {
315
- description : `A GraphQL document is only valid if all defined types have unique names.` ,
316
- } ,
317
- } ,
318
- true
319
- ) ,
320
261
validationToRule ( 'unique-variable-names' , 'UniqueVariableNames' , {
321
- docs : {
322
- description : `A GraphQL operation is only valid if all its variables are uniquely named.` ,
323
- } ,
262
+ description : `A GraphQL operation is only valid if all its variables are uniquely named.` ,
324
263
} ) ,
325
264
validationToRule ( 'value-literals-of-correct-type' , 'ValuesOfCorrectType' , {
326
- docs : {
327
- description : `A GraphQL document is only valid if all value literals are of the type expected at their position.` ,
328
- } ,
265
+ description : `A GraphQL document is only valid if all value literals are of the type expected at their position.` ,
329
266
} ) ,
330
267
validationToRule ( 'variables-are-input-types' , 'VariablesAreInputTypes' , {
331
- docs : {
332
- description : `A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).` ,
333
- } ,
268
+ description : `A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).` ,
334
269
} ) ,
335
270
validationToRule ( 'variables-in-allowed-position' , 'VariablesInAllowedPosition' , {
336
- docs : {
337
- description : `Variables passed to field arguments conform to type.` ,
338
- } ,
271
+ description : `Variables passed to field arguments conform to type.` ,
339
272
} )
340
273
) as Record < string , GraphQLESLintRule > ;
0 commit comments