@@ -3,7 +3,13 @@ import { FromSchema } from 'json-schema-to-ts';
3
3
import { GraphQLESTreeNode } from '../estree-converter/index.js' ;
4
4
import { GraphQLESLintRuleListener } from '../testkit.js' ;
5
5
import { GraphQLESLintRule , ValueOf } from '../types.js' ;
6
- import { ARRAY_DEFAULT_OPTIONS , convertCase , truthy , TYPES_KINDS } from '../utils.js' ;
6
+ import {
7
+ ARRAY_DEFAULT_OPTIONS ,
8
+ convertCase ,
9
+ englishJoinWords ,
10
+ truthy ,
11
+ TYPES_KINDS ,
12
+ } from '../utils.js' ;
7
13
8
14
const KindToDisplayName = {
9
15
// types
@@ -57,6 +63,8 @@ const schema = {
57
63
suffix : { type : 'string' } ,
58
64
forbiddenPrefixes : ARRAY_DEFAULT_OPTIONS ,
59
65
forbiddenSuffixes : ARRAY_DEFAULT_OPTIONS ,
66
+ requiredPrefixes : ARRAY_DEFAULT_OPTIONS ,
67
+ requiredSuffixes : ARRAY_DEFAULT_OPTIONS ,
60
68
ignorePattern : {
61
69
type : 'string' ,
62
70
description : 'Option to skip validation of some words, e.g. acronyms' ,
@@ -113,6 +121,8 @@ type PropertySchema = {
113
121
prefix ?: string ;
114
122
forbiddenPrefixes ?: string [ ] ;
115
123
forbiddenSuffixes ?: string [ ] ;
124
+ requiredPrefixes ?: string [ ] ;
125
+ requiredSuffixes ?: string [ ] ;
116
126
ignorePattern ?: string ;
117
127
} ;
118
128
@@ -194,6 +204,46 @@ export const rule: GraphQLESLintRule<RuleOptions> = {
194
204
}
195
205
` ,
196
206
} ,
207
+ {
208
+ title : 'Correct' ,
209
+ usage : [
210
+ {
211
+ 'FieldDefinition[gqlType.name.value=Boolean]' : {
212
+ style : 'camelCase' ,
213
+ requiredPrefixes : [ 'is' , 'has' ] ,
214
+ } ,
215
+ 'FieldDefinition[gqlType.gqlType.name.value=Boolean]' : {
216
+ style : 'camelCase' ,
217
+ requiredPrefixes : [ 'is' , 'has' ] ,
218
+ } ,
219
+ } ,
220
+ ] ,
221
+ code : /* GraphQL */ `
222
+ type Product {
223
+ isBackordered: Boolean
224
+ isNew: Boolean!
225
+ hasDiscount: Boolean!
226
+ }
227
+ ` ,
228
+ } ,
229
+ {
230
+ title : 'Correct' ,
231
+ usage : [
232
+ {
233
+ 'FieldDefinition[gqlType.gqlType.name.value=SensitiveSecret]' : {
234
+ style : 'camelCase' ,
235
+ requiredSuffixes : [ 'SensitiveSecret' ] ,
236
+ } ,
237
+ } ,
238
+ ] ,
239
+ code : /* GraphQL */ `
240
+ scalar SensitiveSecret
241
+
242
+ type Account {
243
+ accountSensitiveSecret: SensitiveSecret!
244
+ }
245
+ ` ,
246
+ } ,
197
247
] ,
198
248
configOptions : {
199
249
schema : [
@@ -250,17 +300,15 @@ export const rule: GraphQLESLintRule<RuleOptions> = {
250
300
function report (
251
301
node : GraphQLESTreeNode < NameNode > ,
252
302
message : string ,
253
- suggestedName : string ,
303
+ suggestedNames : string [ ] ,
254
304
) : void {
255
305
context . report ( {
256
306
node,
257
307
message,
258
- suggest : [
259
- {
260
- desc : `Rename to \`${ suggestedName } \`` ,
261
- fix : fixer => fixer . replaceText ( node as any , suggestedName ) ,
262
- } ,
263
- ] ,
308
+ suggest : suggestedNames . map ( suggestedName => ( {
309
+ desc : `Rename to \`${ suggestedName } \`` ,
310
+ fix : fixer => fixer . replaceText ( node as any , suggestedName ) ,
311
+ } ) ) ,
264
312
} ) ;
265
313
}
266
314
@@ -269,22 +317,32 @@ export const rule: GraphQLESLintRule<RuleOptions> = {
269
317
if ( ! node ) {
270
318
return ;
271
319
}
272
- const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style, ignorePattern } =
273
- normalisePropertyOption ( selector ) ;
320
+ const {
321
+ prefix,
322
+ suffix,
323
+ forbiddenPrefixes,
324
+ forbiddenSuffixes,
325
+ style,
326
+ ignorePattern,
327
+ requiredPrefixes,
328
+ requiredSuffixes,
329
+ } = normalisePropertyOption ( selector ) ;
274
330
const nodeType = KindToDisplayName [ n . kind ] || n . kind ;
275
331
const nodeName = node . value ;
276
332
const error = getError ( ) ;
277
333
if ( error ) {
278
- const { errorMessage, renameToName } = error ;
334
+ const { errorMessage, renameToNames } = error ;
279
335
const [ leadingUnderscores ] = nodeName . match ( / ^ _ * / ) as RegExpMatchArray ;
280
336
const [ trailingUnderscores ] = nodeName . match ( / _ * $ / ) as RegExpMatchArray ;
281
- const suggestedName = leadingUnderscores + renameToName + trailingUnderscores ;
282
- report ( node , `${ nodeType } "${ nodeName } " should ${ errorMessage } ` , suggestedName ) ;
337
+ const suggestedNames = renameToNames . map (
338
+ renameToName => leadingUnderscores + renameToName + trailingUnderscores ,
339
+ ) ;
340
+ report ( node , `${ nodeType } "${ nodeName } " should ${ errorMessage } ` , suggestedNames ) ;
283
341
}
284
342
285
343
function getError ( ) : {
286
344
errorMessage : string ;
287
- renameToName : string ;
345
+ renameToNames : string [ ] ;
288
346
} | void {
289
347
const name = nodeName . replace ( / ( ^ _ + ) | ( _ + $ ) / g, '' ) ;
290
348
if ( ignorePattern && new RegExp ( ignorePattern , 'u' ) . test ( name ) ) {
@@ -293,27 +351,53 @@ export const rule: GraphQLESLintRule<RuleOptions> = {
293
351
if ( prefix && ! name . startsWith ( prefix ) ) {
294
352
return {
295
353
errorMessage : `have "${ prefix } " prefix` ,
296
- renameToName : prefix + name ,
354
+ renameToNames : [ prefix + name ] ,
297
355
} ;
298
356
}
299
357
if ( suffix && ! name . endsWith ( suffix ) ) {
300
358
return {
301
359
errorMessage : `have "${ suffix } " suffix` ,
302
- renameToName : name + suffix ,
360
+ renameToNames : [ name + suffix ] ,
303
361
} ;
304
362
}
305
363
const forbiddenPrefix = forbiddenPrefixes ?. find ( prefix => name . startsWith ( prefix ) ) ;
306
364
if ( forbiddenPrefix ) {
307
365
return {
308
366
errorMessage : `not have "${ forbiddenPrefix } " prefix` ,
309
- renameToName : name . replace ( new RegExp ( `^${ forbiddenPrefix } ` ) , '' ) ,
367
+ renameToNames : [ name . replace ( new RegExp ( `^${ forbiddenPrefix } ` ) , '' ) ] ,
310
368
} ;
311
369
}
312
370
const forbiddenSuffix = forbiddenSuffixes ?. find ( suffix => name . endsWith ( suffix ) ) ;
313
371
if ( forbiddenSuffix ) {
314
372
return {
315
373
errorMessage : `not have "${ forbiddenSuffix } " suffix` ,
316
- renameToName : name . replace ( new RegExp ( `${ forbiddenSuffix } $` ) , '' ) ,
374
+ renameToNames : [ name . replace ( new RegExp ( `${ forbiddenSuffix } $` ) , '' ) ] ,
375
+ } ;
376
+ }
377
+ if (
378
+ requiredPrefixes &&
379
+ ! requiredPrefixes . some ( requiredPrefix => name . startsWith ( requiredPrefix ) )
380
+ ) {
381
+ return {
382
+ errorMessage : `have one of the following prefixes: ${ englishJoinWords (
383
+ requiredPrefixes ,
384
+ ) } `,
385
+ renameToNames : style
386
+ ? requiredPrefixes . map ( prefix => convertCase ( style , `${ prefix } ${ name } ` ) )
387
+ : requiredPrefixes . map ( prefix => `${ prefix } ${ name } ` ) ,
388
+ } ;
389
+ }
390
+ if (
391
+ requiredSuffixes &&
392
+ ! requiredSuffixes . some ( requiredSuffix => name . endsWith ( requiredSuffix ) )
393
+ ) {
394
+ return {
395
+ errorMessage : `have one of the following suffixes: ${ englishJoinWords (
396
+ requiredSuffixes ,
397
+ ) } `,
398
+ renameToNames : style
399
+ ? requiredSuffixes . map ( suffix => convertCase ( style , `${ name } ${ suffix } ` ) )
400
+ : requiredSuffixes . map ( suffix => `${ name } ${ suffix } ` ) ,
317
401
} ;
318
402
}
319
403
// Style is optional
@@ -324,19 +408,17 @@ export const rule: GraphQLESLintRule<RuleOptions> = {
324
408
if ( ! caseRegex . test ( name ) ) {
325
409
return {
326
410
errorMessage : `be in ${ style } format` ,
327
- renameToName : convertCase ( style , name ) ,
411
+ renameToNames : [ convertCase ( style , name ) ] ,
328
412
} ;
329
413
}
330
414
}
331
415
} ;
332
416
333
417
const checkUnderscore = ( isLeading : boolean ) => ( node : GraphQLESTreeNode < NameNode > ) => {
334
418
const suggestedName = node . value . replace ( isLeading ? / ^ _ + / : / _ + $ / , '' ) ;
335
- report (
336
- node ,
337
- `${ isLeading ? 'Leading' : 'Trailing' } underscores are not allowed` ,
419
+ report ( node , `${ isLeading ? 'Leading' : 'Trailing' } underscores are not allowed` , [
338
420
suggestedName ,
339
- ) ;
421
+ ] ) ;
340
422
} ;
341
423
342
424
const listeners : GraphQLESLintRuleListener = { } ;
0 commit comments