1- import { GraphQLESLintRule , GraphQLESlintRuleContext } from '../types' ;
2- import { requireGraphQLSchemaFromContext } from '../utils' ;
3-
41import { validate , GraphQLSchema , DocumentNode , ASTNode , ValidationRule } from 'graphql' ;
52import { 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' ;
67import { GraphQLESTreeNode } from '../estree-parser' ;
78
89function extractRuleName ( stack : string | undefined ) : string | null {
@@ -44,11 +45,16 @@ export function validateDoc(
4445 }
4546}
4647
48+ const isGraphQLImportFile = rawSDL => {
49+ const trimmedRawSDL = rawSDL . trim ( ) ;
50+ return trimmedRawSDL . startsWith ( '# import' ) || trimmedRawSDL . startsWith ( '#import' ) ;
51+ } ;
52+
4753const validationToRule = (
4854 name : string ,
4955 ruleName : string ,
50- meta : GraphQLESLintRule [ 'meta' ] ,
51- skipSchema = false
56+ docs : GraphQLESLintRule [ 'meta' ] [ 'docs '] ,
57+ getDocumentNode ?: ( context : GraphQLESlintRuleContext ) => DocumentNode | null
5258) : Record < typeof name , GraphQLESLintRule < any , true > > => {
5359 let ruleFn : null | ValidationRule = null ;
5460
@@ -62,35 +68,42 @@ const validationToRule = (
6268 }
6369 }
6470
71+ const requiresSchema = docs . requiresSchema ?? true ;
72+
6573 return {
6674 [ name ] : {
6775 meta : {
68- ...meta ,
6976 docs : {
7077 category : 'Validation' ,
71- requiresSchema : ! skipSchema ,
78+ requiresSchema,
7279 requiresSiblings : false ,
7380 url : `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${ name } .md` ,
74- ...meta . docs ,
81+ ...docs ,
7582 description :
76- meta . docs . description +
83+ docs . description +
7784 `\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).` ,
7885 } ,
7986 } ,
80- create : context => {
87+ create ( context ) {
8188 return {
8289 Document ( node ) {
8390 if ( ! ruleFn ) {
8491 // eslint-disable-next-line no-console
8592 console . warn (
8693 `You rule "${ name } " depends on a GraphQL validation rule ("${ ruleName } ") but it's not available in the "graphql-js" version you are using. Skipping...`
8794 ) ;
88-
8995 return ;
9096 }
9197
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 ) ;
94107 } ,
95108 } ;
96109 } ,
@@ -101,32 +114,24 @@ const validationToRule = (
101114export const GRAPHQL_JS_VALIDATIONS = Object . assign (
102115 { } ,
103116 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.` ,
107118 } ) ,
108119 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.` ,
112121 } ) ,
113122 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.` ,
117124 } ) ,
118125 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.` ,
122127 } ) ,
123128 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.` ,
127130 } ) ,
128- validationToRule ( 'known-fragment-names' , 'KnownFragmentNames' , {
129- docs : {
131+ validationToRule (
132+ 'known-fragment-names' ,
133+ 'KnownFragmentNames' ,
134+ {
130135 description : `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.` ,
131136 examples : [
132137 {
@@ -171,170 +176,98 @@ export const GRAPHQL_JS_VALIDATIONS = Object.assign(
171176 } ,
172177 ] ,
173178 } ,
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+ ) ,
175190 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.` ,
179192 } ) ,
180193 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 ,
184199 } ) ,
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- ) ,
195200 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.` ,
199202 } ) ,
200203 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.` ,
204205 } ) ,
205206 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.` ,
209208 } ) ,
210209 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.` ,
214211 } ) ,
215212 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.` ,
219214 } ) ,
220215 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 ,
224221 } ) ,
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- ) ,
235222 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.` ,
239224 } ) ,
240225 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.` ,
244227 } ) ,
245228 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.` ,
249230 } ) ,
250231 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 ,
254237 } ) ,
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- ) ,
265238 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 ,
269260 } ) ,
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- ) ,
320261 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.` ,
324263 } ) ,
325264 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.` ,
329266 } ) ,
330267 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).` ,
334269 } ) ,
335270 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.` ,
339272 } )
340273) as Record < string , GraphQLESLintRule > ;
0 commit comments