11import { validate , GraphQLSchema , DocumentNode , ASTNode , ValidationRule } from 'graphql' ;
22import { validateSDL } from 'graphql/validation/validate' ;
3- import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader' ;
4- import fs from 'fs' ;
3+ import { parseImportLine , processImport } from '@graphql-tools/import' ;
4+ import { existsSync } from 'fs' ;
5+ import { join , dirname } from 'path' ;
56import { GraphQLESLintRule , GraphQLESLintRuleContext } from '../types' ;
6- import { requireGraphQLSchemaFromContext } from '../utils' ;
7+ import { requireGraphQLSchemaFromContext , requireSiblingsOperations } from '../utils' ;
78import { GraphQLESTreeNode } from '../estree-parser' ;
89
910function extractRuleName ( stack : string | undefined ) : string | null {
@@ -24,7 +25,7 @@ export function validateDoc(
2425 rules : ReadonlyArray < ValidationRule > ,
2526 ruleName : string | null = null
2627) : void {
27- if ( documentNode && documentNode . definitions && documentNode . definitions . length > 0 ) {
28+ if ( documentNode ? .definitions ? .length > 0 ) {
2829 try {
2930 const validationErrors = schema ? validate ( schema , documentNode , rules ) : validateSDL ( documentNode , null , rules ) ;
3031
@@ -69,19 +70,17 @@ const validationToRule = (
6970 }
7071
7172 const requiresSchema = docs . requiresSchema ?? true ;
72-
73+ const requiresSiblings = docs . requiresSiblings ?? false ;
7374 return {
7475 [ name ] : {
7576 meta : {
7677 docs : {
78+ ...docs ,
7779 category : 'Validation' ,
7880 requiresSchema,
79- requiresSiblings : false ,
81+ requiresSiblings,
8082 url : `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${ name } .md` ,
81- ...docs ,
82- description :
83- docs . description +
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).` ,
83+ description : `${ docs . description } \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).` ,
8584 } ,
8685 } ,
8786 create ( context ) {
@@ -98,9 +97,8 @@ const validationToRule = (
9897 const schema = requiresSchema ? requireGraphQLSchemaFromContext ( name , context ) : null ;
9998
10099 let documentNode : DocumentNode ;
101- const filePath = context . getFilename ( ) ;
102- const isVirtualFile = ! fs . existsSync ( filePath ) ;
103- if ( ! isVirtualFile && getDocumentNode ) {
100+ const isRealFile = existsSync ( context . getFilename ( ) ) ;
101+ if ( isRealFile && getDocumentNode ) {
104102 documentNode = getDocumentNode ( context ) ;
105103 }
106104 validateDoc ( node , context , schema , documentNode || node . rawNode ( ) , [ ruleFn ] , ruleName ) ;
@@ -181,10 +179,8 @@ export const GRAPHQL_JS_VALIDATIONS = Object.assign(
181179 if ( ! isGraphQLImportFile ( code ) ) {
182180 return null ;
183181 }
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 ;
182+ // Import documents because file contains '#import' comments
183+ return processImport ( context . getFilename ( ) ) ;
188184 }
189185 ) ,
190186 validationToRule ( 'known-type-names' , 'KnownTypeNames' , {
@@ -203,9 +199,45 @@ export const GRAPHQL_JS_VALIDATIONS = Object.assign(
203199 validationToRule ( 'no-undefined-variables' , 'NoUndefinedVariables' , {
204200 description : `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.` ,
205201 } ) ,
206- validationToRule ( 'no-unused-fragments' , 'NoUnusedFragments' , {
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- } ) ,
202+ validationToRule (
203+ 'no-unused-fragments' ,
204+ 'NoUnusedFragments' ,
205+ {
206+ description : `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.` ,
207+ requiresSiblings : true ,
208+ } ,
209+ context => {
210+ const siblings = requireSiblingsOperations ( 'no-unused-fragments' , context ) ;
211+ const documents = [ ...siblings . getOperations ( ) , ...siblings . getFragments ( ) ]
212+ . filter ( ( { document } ) => isGraphQLImportFile ( document . loc . source . body ) )
213+ . map ( ( { filePath, document } ) => ( {
214+ filePath,
215+ code : document . loc . source . body ,
216+ } ) ) ;
217+
218+ const getParentNode = ( filePath : string ) : DocumentNode | null => {
219+ for ( const { filePath : docFilePath , code } of documents ) {
220+ const isFileImported = code
221+ . split ( '\n' )
222+ . filter ( isGraphQLImportFile )
223+ . map ( line => parseImportLine ( line . replace ( '#' , '' ) ) )
224+ . some ( o => filePath === join ( dirname ( docFilePath ) , o . from ) ) ;
225+
226+ if ( ! isFileImported ) {
227+ continue ;
228+ }
229+ // Import first file that import this file
230+ const document = processImport ( docFilePath ) ;
231+ // Import most top file that import this file
232+ return getParentNode ( docFilePath ) || document ;
233+ }
234+
235+ return null ;
236+ } ;
237+
238+ return getParentNode ( context . getFilename ( ) ) ;
239+ }
240+ ) ,
209241 validationToRule ( 'no-unused-variables' , 'NoUnusedVariables' , {
210242 description : `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.` ,
211243 } ) ,
0 commit comments