@@ -7,26 +7,23 @@ import {
7
7
InputObjectTypeDefinitionNode ,
8
8
InputObjectTypeExtensionNode ,
9
9
FieldDefinitionNode ,
10
- InputValueDefinitionNode ,
11
10
EnumTypeDefinitionNode ,
12
11
EnumTypeExtensionNode ,
13
- EnumValueDefinitionNode ,
14
12
DirectiveDefinitionNode ,
15
- ArgumentNode ,
16
- VariableDefinitionNode ,
17
13
OperationDefinitionNode ,
18
14
FieldNode ,
19
15
DirectiveNode ,
20
16
SelectionSetNode ,
21
- FragmentSpreadNode ,
17
+ ASTNode ,
22
18
} from 'graphql' ;
23
19
import type { SourceLocation , Comment } from 'estree' ;
24
20
import type { AST } from 'eslint' ;
21
+ import lowerCase from 'lodash.lowercase' ;
25
22
import { GraphQLESLintRule } from '../types' ;
26
23
import { GraphQLESTreeNode } from '../estree-parser' ;
27
24
import { GraphQLESLintRuleListener } from '../testkit' ;
28
25
29
- const ALPHABETIZE = 'ALPHABETIZE ' ;
26
+ const RULE_ID = 'alphabetize ' ;
30
27
31
28
const fieldsEnum : ( 'ObjectTypeDefinition' | 'InterfaceTypeDefinition' | 'InputObjectTypeDefinition' ) [ ] = [
32
29
Kind . OBJECT_TYPE_DEFINITION ,
@@ -52,6 +49,7 @@ export type AlphabetizeConfig = {
52
49
selections ?: typeof selectionsEnum ;
53
50
variables ?: typeof variablesEnum ;
54
51
arguments ?: typeof argumentsEnum ;
52
+ definitions ?: boolean ;
55
53
} ;
56
54
57
55
const rule : GraphQLESLintRule < [ AlphabetizeConfig ] > = {
@@ -61,7 +59,7 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
61
59
docs : {
62
60
category : [ 'Schema' , 'Operations' ] ,
63
61
description : `Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.` ,
64
- url : ' https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize .md' ,
62
+ url : ` https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${ RULE_ID } .md` ,
65
63
examples : [
66
64
{
67
65
title : 'Incorrect' ,
@@ -144,6 +142,8 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
144
142
fields : fieldsEnum ,
145
143
values : valuesEnum ,
146
144
arguments : argumentsEnum ,
145
+ // TODO: add in graphql-eslint v4
146
+ // definitions: true,
147
147
} ,
148
148
] ,
149
149
operations : [
@@ -156,7 +156,7 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
156
156
} ,
157
157
} ,
158
158
messages : {
159
- [ ALPHABETIZE ] : '" {{ currName }}" should be before " {{ prevName }}" ' ,
159
+ [ RULE_ID ] : '` {{ currName }}` should be before {{ prevName }}. ' ,
160
160
} ,
161
161
schema : {
162
162
type : 'array' ,
@@ -174,7 +174,7 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
174
174
items : {
175
175
enum : fieldsEnum ,
176
176
} ,
177
- description : 'Fields of `type`, `interface`, and `input`' ,
177
+ description : 'Fields of `type`, `interface`, and `input`. ' ,
178
178
} ,
179
179
values : {
180
180
type : 'array' ,
@@ -183,7 +183,7 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
183
183
items : {
184
184
enum : valuesEnum ,
185
185
} ,
186
- description : 'Values of `enum`' ,
186
+ description : 'Values of `enum`. ' ,
187
187
} ,
188
188
selections : {
189
189
type : 'array' ,
@@ -192,7 +192,7 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
192
192
items : {
193
193
enum : selectionsEnum ,
194
194
} ,
195
- description : 'Selections of operations ( `query`, `mutation` and `subscription`) and `fragment` ' ,
195
+ description : 'Selections of `fragment` and operations `query`, `mutation` and `subscription`. ' ,
196
196
} ,
197
197
variables : {
198
198
type : 'array' ,
@@ -201,7 +201,7 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
201
201
items : {
202
202
enum : variablesEnum ,
203
203
} ,
204
- description : 'Variables of operations ( `query`, `mutation` and `subscription`) ' ,
204
+ description : 'Variables of operations `query`, `mutation` and `subscription`. ' ,
205
205
} ,
206
206
arguments : {
207
207
type : 'array' ,
@@ -210,7 +210,12 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
210
210
items : {
211
211
enum : argumentsEnum ,
212
212
} ,
213
- description : 'Arguments of fields and directives' ,
213
+ description : 'Arguments of fields and directives.' ,
214
+ } ,
215
+ definitions : {
216
+ type : 'boolean' ,
217
+ description : 'Definitions – `type`, `interface`, `enum`, `scalar`, `input`, `union` and `directive`.' ,
218
+ default : false ,
214
219
} ,
215
220
} ,
216
221
} ,
@@ -232,7 +237,17 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
232
237
if ( tokenBefore ) {
233
238
return commentsBefore . filter ( comment => ! isNodeAndCommentOnSameLine ( tokenBefore , comment ) ) ;
234
239
}
235
- return commentsBefore ;
240
+ const filteredComments = [ ] ;
241
+ const nodeLine = node . loc . start . line ;
242
+ // Break on comment that not attached to node
243
+ for ( let i = commentsBefore . length - 1 ; i >= 0 ; i -= 1 ) {
244
+ const comment = commentsBefore [ i ] ;
245
+ if ( nodeLine - comment . loc . start . line - filteredComments . length > 1 ) {
246
+ break ;
247
+ }
248
+ filteredComments . unshift ( comment ) ;
249
+ }
250
+ return filteredComments ;
236
251
}
237
252
238
253
function getRangeWithComments ( node ) : AST . Range {
@@ -243,37 +258,37 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
243
258
return [ from . range [ 0 ] , to . range [ 1 ] ] ;
244
259
}
245
260
246
- function checkNodes (
247
- nodes : GraphQLESTreeNode <
248
- | FieldDefinitionNode
249
- | InputValueDefinitionNode
250
- | EnumValueDefinitionNode
251
- | FieldNode
252
- | FragmentSpreadNode
253
- | ArgumentNode
254
- | VariableDefinitionNode [ 'variable' ]
255
- > [ ]
256
- ) {
261
+ function checkNodes ( nodes : GraphQLESTreeNode < ASTNode > [ ] ) {
257
262
// Starts from 1, ignore nodes.length <= 1
258
263
for ( let i = 1 ; i < nodes . length ; i += 1 ) {
259
- const prevNode = nodes [ i - 1 ] ;
260
264
const currNode = nodes [ i ] ;
261
- const prevName = prevNode . name . value ;
262
- const currName = currNode . name . value ;
263
- // Compare with lexicographic order
264
- if ( prevName . localeCompare ( currName ) !== 1 ) {
265
+ const currName = 'name' in currNode && currNode . name ?. value ;
266
+ if ( ! currName ) {
267
+ // we don't move unnamed current nodes
265
268
continue ;
266
269
}
267
- const isVariableNode = currNode . kind === Kind . VARIABLE ;
270
+
271
+ const prevNode = nodes [ i - 1 ] ;
272
+ const prevName = 'name' in prevNode && prevNode . name ?. value ;
273
+ if ( prevName ) {
274
+ // Compare with lexicographic order
275
+ const compareResult = prevName . localeCompare ( currName ) ;
276
+ const shouldSort = compareResult === 1 ;
277
+ if ( ! shouldSort ) {
278
+ const isSameName = compareResult === 0 ;
279
+ if ( ! isSameName || ! prevNode . kind . endsWith ( 'Extension' ) || currNode . kind . endsWith ( 'Extension' ) ) {
280
+ continue ;
281
+ }
282
+ }
283
+ }
284
+
268
285
context . report ( {
269
286
node : currNode . name ,
270
- messageId : ALPHABETIZE ,
271
- data : isVariableNode
272
- ? {
273
- currName : `$${ currName } ` ,
274
- prevName : `$${ prevName } ` ,
275
- }
276
- : { currName, prevName } ,
287
+ messageId : RULE_ID ,
288
+ data : {
289
+ currName,
290
+ prevName : prevName ? `\`${ prevName } \`` : lowerCase ( prevNode . kind ) ,
291
+ } ,
277
292
* fix ( fixer ) {
278
293
const prevRange = getRangeWithComments ( prevNode ) ;
279
294
const currRange = getRangeWithComments ( currNode ) ;
@@ -331,13 +346,10 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
331
346
if ( selectionsSelector ) {
332
347
listeners [ `:matches(${ selectionsSelector } ) SelectionSet` ] = ( node : GraphQLESTreeNode < SelectionSetNode > ) => {
333
348
checkNodes (
334
- node . selections
335
- // inline fragment don't have name, so we skip them
336
- . filter ( selection => selection . kind !== Kind . INLINE_FRAGMENT )
337
- . map ( selection =>
338
- // sort by alias is field is renamed
339
- 'alias' in selection && selection . alias ? ( { name : selection . alias } as any ) : selection
340
- )
349
+ node . selections . map ( selection =>
350
+ // sort by alias is field is renamed
351
+ 'alias' in selection && selection . alias ? ( { name : selection . alias } as any ) : selection
352
+ )
341
353
) ;
342
354
} ;
343
355
}
@@ -356,6 +368,12 @@ const rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
356
368
} ;
357
369
}
358
370
371
+ if ( opts . definitions ) {
372
+ listeners . Document = node => {
373
+ checkNodes ( node . definitions ) ;
374
+ } ;
375
+ }
376
+
359
377
return listeners ;
360
378
} ,
361
379
} ;
0 commit comments