@graphql-eslint/eslint-plugin 2.5.0 → 3.0.0-alpha-0a66b90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.mjs CHANGED
@@ -29,7 +29,34 @@ const recommendedConfig = {
29
29
  '@graphql-eslint/known-type-names': 'error',
30
30
  '@graphql-eslint/lone-anonymous-operation': 'error',
31
31
  '@graphql-eslint/lone-schema-definition': 'error',
32
- '@graphql-eslint/naming-convention': 'error',
32
+ '@graphql-eslint/naming-convention': [
33
+ 'error',
34
+ {
35
+ types: 'PascalCase',
36
+ fields: 'camelCase',
37
+ overrides: {
38
+ EnumValueDefinition: 'UPPER_CASE',
39
+ OperationDefinition: {
40
+ style: 'PascalCase',
41
+ forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
42
+ forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
43
+ },
44
+ FragmentDefinition: { style: 'PascalCase', forbiddenPrefixes: ['Fragment'], forbiddenSuffixes: ['Fragment'] },
45
+ 'FieldDefinition[parent.name.value=Query]': {
46
+ forbiddenPrefixes: ['query', 'get'],
47
+ forbiddenSuffixes: ['Query'],
48
+ },
49
+ 'FieldDefinition[parent.name.value=Mutation]': {
50
+ forbiddenPrefixes: ['mutation'],
51
+ forbiddenSuffixes: ['Mutation'],
52
+ },
53
+ 'FieldDefinition[parent.name.value=Subscription]': {
54
+ forbiddenPrefixes: ['subscription'],
55
+ forbiddenSuffixes: ['Subscription'],
56
+ },
57
+ },
58
+ },
59
+ ],
33
60
  '@graphql-eslint/no-anonymous-operations': 'error',
34
61
  '@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
35
62
  '@graphql-eslint/no-fragment-cycles': 'error',
@@ -89,7 +116,7 @@ const allConfig = {
89
116
  '@graphql-eslint/no-unreachable-types': 'error',
90
117
  '@graphql-eslint/no-unused-fields': 'error',
91
118
  '@graphql-eslint/require-deprecation-date': 'error',
92
- '@graphql-eslint/require-description': 'error',
119
+ '@graphql-eslint/require-description': ['error', { types: true, overrides: { DirectiveDefinition: true } }],
93
120
  '@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
94
121
  '@graphql-eslint/require-id-when-available': 'error',
95
122
  '@graphql-eslint/selection-set-depth': 'error',
@@ -206,9 +233,14 @@ const loaderCache = new Proxy(Object.create(null), {
206
233
  return true;
207
234
  },
208
235
  });
209
- const isObjectType = (node) => [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
210
- const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
211
- const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
236
+ const TYPES_KINDS = [
237
+ Kind.OBJECT_TYPE_DEFINITION,
238
+ Kind.INTERFACE_TYPE_DEFINITION,
239
+ Kind.ENUM_TYPE_DEFINITION,
240
+ Kind.SCALAR_TYPE_DEFINITION,
241
+ Kind.INPUT_OBJECT_TYPE_DEFINITION,
242
+ Kind.UNION_TYPE_DEFINITION,
243
+ ];
212
244
  var CaseStyle;
213
245
  (function (CaseStyle) {
214
246
  CaseStyle["camelCase"] = "camelCase";
@@ -259,7 +291,7 @@ function getLocation(loc, fieldName = '', offset) {
259
291
  }
260
292
 
261
293
  function extractRuleName(stack) {
262
- const match = (stack || '').match(/validation[/\\\\]rules[/\\\\](.*?)\.js:/) || [];
294
+ const match = (stack || '').match(/validation[/\\]rules[/\\](.*?)\.js:/) || [];
263
295
  return match[1] || null;
264
296
  }
265
297
  function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName = null) {
@@ -508,11 +540,23 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
508
540
  }));
509
541
 
510
542
  const ALPHABETIZE = 'ALPHABETIZE';
511
- const fieldsEnum = [Kind.OBJECT_TYPE_DEFINITION, Kind.INTERFACE_TYPE_DEFINITION, Kind.INPUT_OBJECT_TYPE_DEFINITION];
543
+ const fieldsEnum = [
544
+ Kind.OBJECT_TYPE_DEFINITION,
545
+ Kind.INTERFACE_TYPE_DEFINITION,
546
+ Kind.INPUT_OBJECT_TYPE_DEFINITION,
547
+ ];
512
548
  const valuesEnum = [Kind.ENUM_TYPE_DEFINITION];
513
- const selectionsEnum = [Kind.OPERATION_DEFINITION, Kind.FRAGMENT_DEFINITION];
549
+ const selectionsEnum = [
550
+ Kind.OPERATION_DEFINITION,
551
+ Kind.FRAGMENT_DEFINITION,
552
+ ];
514
553
  const variablesEnum = [Kind.OPERATION_DEFINITION];
515
- const argumentsEnum = [Kind.FIELD_DEFINITION, Kind.FIELD, Kind.DIRECTIVE_DEFINITION, Kind.DIRECTIVE];
554
+ const argumentsEnum = [
555
+ Kind.FIELD_DEFINITION,
556
+ Kind.FIELD,
557
+ Kind.DIRECTIVE_DEFINITION,
558
+ Kind.DIRECTIVE,
559
+ ];
516
560
  const rule = {
517
561
  meta: {
518
562
  type: 'suggestion',
@@ -620,38 +664,48 @@ const rule = {
620
664
  properties: {
621
665
  fields: {
622
666
  type: 'array',
623
- contains: {
667
+ uniqueItems: true,
668
+ minItems: 1,
669
+ items: {
624
670
  enum: fieldsEnum,
625
671
  },
626
- description: 'Fields of `type`, `interface`, and `input`.',
672
+ description: 'Fields of `type`, `interface`, and `input`',
627
673
  },
628
674
  values: {
629
675
  type: 'array',
630
- contains: {
676
+ uniqueItems: true,
677
+ minItems: 1,
678
+ items: {
631
679
  enum: valuesEnum,
632
680
  },
633
- description: 'Values of `enum`.',
681
+ description: 'Values of `enum`',
634
682
  },
635
683
  selections: {
636
684
  type: 'array',
637
- contains: {
685
+ uniqueItems: true,
686
+ minItems: 1,
687
+ items: {
638
688
  enum: selectionsEnum,
639
689
  },
640
- description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`.',
690
+ description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`',
641
691
  },
642
692
  variables: {
643
693
  type: 'array',
644
- contains: {
694
+ uniqueItems: true,
695
+ minItems: 1,
696
+ items: {
645
697
  enum: variablesEnum,
646
698
  },
647
- description: 'Variables of operations (`query`, `mutation` and `subscription`).',
699
+ description: 'Variables of operations (`query`, `mutation` and `subscription`)',
648
700
  },
649
701
  arguments: {
650
702
  type: 'array',
651
- contains: {
703
+ uniqueItems: true,
704
+ minItems: 1,
705
+ items: {
652
706
  enum: argumentsEnum,
653
707
  },
654
- description: 'Arguments of fields and directives.',
708
+ description: 'Arguments of fields and directives',
655
709
  },
656
710
  },
657
711
  },
@@ -682,7 +736,7 @@ const rule = {
682
736
  const opts = context.options[0];
683
737
  const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
684
738
  const listeners = {};
685
- const fieldsSelector = [
739
+ const kinds = [
686
740
  fields.has(Kind.OBJECT_TYPE_DEFINITION) && [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION],
687
741
  fields.has(Kind.INTERFACE_TYPE_DEFINITION) && [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION],
688
742
  fields.has(Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
@@ -690,8 +744,9 @@ const rule = {
690
744
  Kind.INPUT_OBJECT_TYPE_EXTENSION,
691
745
  ],
692
746
  ]
693
- .flat()
694
- .join(',');
747
+ .filter(Boolean)
748
+ .flat();
749
+ const fieldsSelector = kinds.join(',');
695
750
  const hasEnumValues = ((_b = opts.values) === null || _b === void 0 ? void 0 : _b[0]) === Kind.ENUM_TYPE_DEFINITION;
696
751
  const selectionsSelector = (_c = opts.selections) === null || _c === void 0 ? void 0 : _c.join(',');
697
752
  const hasVariables = ((_d = opts.variables) === null || _d === void 0 ? void 0 : _d[0]) === Kind.OPERATION_DEFINITION;
@@ -1091,6 +1146,9 @@ const rule$5 = {
1091
1146
  },
1092
1147
  };
1093
1148
 
1149
+ const isObjectType = (node) => [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
1150
+ const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
1151
+ const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
1094
1152
  const rule$6 = {
1095
1153
  meta: {
1096
1154
  type: 'suggestion',
@@ -1131,6 +1189,7 @@ const rule$6 = {
1131
1189
  schema: [
1132
1190
  {
1133
1191
  type: 'object',
1192
+ additionalProperties: false,
1134
1193
  properties: {
1135
1194
  checkInputType: {
1136
1195
  type: 'boolean',
@@ -1153,24 +1212,22 @@ const rule$6 = {
1153
1212
  description: 'Apply the rule to Mutations',
1154
1213
  },
1155
1214
  },
1156
- additionalProperties: false,
1157
1215
  },
1158
1216
  ],
1159
1217
  },
1160
1218
  create(context) {
1161
- var _a;
1162
1219
  const options = {
1163
- caseSensitiveInputType: true,
1164
1220
  checkInputType: false,
1165
- checkMutations: true,
1221
+ caseSensitiveInputType: true,
1166
1222
  checkQueries: false,
1167
- ...(_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a[0],
1223
+ checkMutations: true,
1224
+ ...context.options[0],
1168
1225
  };
1169
1226
  const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
1170
1227
  const listeners = {
1171
- 'FieldDefinition > InputValueDefinition': node => {
1172
- const name = node.name.value;
1173
- if (name !== 'input' && shouldCheckType(node.parent.parent)) {
1228
+ 'FieldDefinition > InputValueDefinition[name.value!=input]'(node) {
1229
+ if (shouldCheckType(node.parent.parent)) {
1230
+ const name = node.name.value;
1174
1231
  context.report({
1175
1232
  loc: getLocation(node.loc, name),
1176
1233
  message: `Input "${name}" should be called "input"`,
@@ -1178,11 +1235,11 @@ const rule$6 = {
1178
1235
  }
1179
1236
  },
1180
1237
  };
1181
- if (options === null || options === void 0 ? void 0 : options.checkInputType) {
1182
- listeners['FieldDefinition > InputValueDefinition NamedType'] = node => {
1238
+ if (options.checkInputType) {
1239
+ listeners['FieldDefinition > InputValueDefinition NamedType'] = (node) => {
1183
1240
  const findInputType = item => {
1184
1241
  let currentNode = item;
1185
- while (currentNode.type !== 'InputValueDefinition') {
1242
+ while (currentNode.type !== Kind.INPUT_VALUE_DEFINITION) {
1186
1243
  currentNode = currentNode.parent;
1187
1244
  }
1188
1245
  return currentNode;
@@ -1399,69 +1456,40 @@ const rule$7 = {
1399
1456
  },
1400
1457
  };
1401
1458
 
1402
- const formats = {
1403
- camelCase: /^[a-z][^_]*$/g,
1404
- PascalCase: /^[A-Z][^_]*$/g,
1405
- snake_case: /^[a-z_][a-z0-9_]*$/g,
1406
- UPPER_CASE: /^[A-Z_][A-Z0-9_]*$/g,
1407
- };
1408
- const acceptedStyles = [
1409
- 'camelCase',
1410
- 'PascalCase',
1411
- 'snake_case',
1412
- 'UPPER_CASE',
1459
+ const FIELDS_KINDS = [
1460
+ Kind.FIELD_DEFINITION,
1461
+ Kind.INPUT_VALUE_DEFINITION,
1462
+ Kind.VARIABLE_DEFINITION,
1463
+ Kind.ARGUMENT,
1464
+ Kind.DIRECTIVE_DEFINITION,
1413
1465
  ];
1414
- function checkNameFormat(params) {
1415
- const { value, style, leadingUnderscore, trailingUnderscore, suffix, prefix, forbiddenPrefixes, forbiddenSuffixes, } = params;
1416
- let name = value;
1417
- if (leadingUnderscore === 'allow') {
1418
- [, name] = name.match(/^_*(.*)$/);
1419
- }
1420
- if (trailingUnderscore === 'allow') {
1421
- name = name.replace(/_*$/, '');
1422
- }
1423
- if (prefix && !name.startsWith(prefix)) {
1424
- return {
1425
- ok: false,
1426
- errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{prefix}}" prefix',
1427
- };
1428
- }
1429
- if (suffix && !name.endsWith(suffix)) {
1430
- return {
1431
- ok: false,
1432
- errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{suffix}}" suffix',
1433
- };
1434
- }
1435
- if (style && !acceptedStyles.includes(style)) {
1436
- return {
1437
- ok: false,
1438
- errorMessage: `{{nodeType}} name "{{nodeName}}" should be in one of the following options: ${acceptedStyles.join(',')}`,
1439
- };
1440
- }
1441
- if (forbiddenPrefixes.some(forbiddenPrefix => name.startsWith(forbiddenPrefix))) {
1442
- return {
1443
- ok: false,
1444
- errorMessage: '{{nodeType}} "{{nodeName}}" should not have one of the following prefix(es): {{forbiddenPrefixes}}',
1445
- };
1446
- }
1447
- if (forbiddenSuffixes.some(forbiddenSuffix => name.endsWith(forbiddenSuffix))) {
1448
- return {
1449
- ok: false,
1450
- errorMessage: '{{nodeType}} "{{nodeName}}" should not have one of the following suffix(es): {{forbiddenSuffixes}}',
1451
- };
1452
- }
1453
- if (!formats[style]) {
1454
- return { ok: true };
1455
- }
1456
- const ok = new RegExp(formats[style]).test(name);
1457
- if (ok) {
1458
- return { ok: true };
1459
- }
1460
- return {
1461
- ok: false,
1462
- errorMessage: '{{nodeType}} name "{{nodeName}}" should be in {{format}} format',
1463
- };
1464
- }
1466
+ const KindToDisplayName = {
1467
+ // types
1468
+ [Kind.OBJECT_TYPE_DEFINITION]: 'Type',
1469
+ [Kind.INTERFACE_TYPE_DEFINITION]: 'Interface',
1470
+ [Kind.ENUM_TYPE_DEFINITION]: 'Enumerator',
1471
+ [Kind.SCALAR_TYPE_DEFINITION]: 'Scalar',
1472
+ [Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'Input type',
1473
+ [Kind.UNION_TYPE_DEFINITION]: 'Union',
1474
+ // fields
1475
+ [Kind.FIELD_DEFINITION]: 'Field',
1476
+ [Kind.INPUT_VALUE_DEFINITION]: 'Input property',
1477
+ [Kind.VARIABLE_DEFINITION]: 'Variable',
1478
+ [Kind.ARGUMENT]: 'Argument',
1479
+ [Kind.DIRECTIVE_DEFINITION]: 'Directive',
1480
+ // rest
1481
+ [Kind.ENUM_VALUE_DEFINITION]: 'Enumeration value',
1482
+ [Kind.OPERATION_DEFINITION]: 'Operation',
1483
+ [Kind.FRAGMENT_DEFINITION]: 'Fragment',
1484
+ };
1485
+ const StyleToRegex = {
1486
+ camelCase: /^[a-z][\dA-Za-z]*$/,
1487
+ PascalCase: /^[A-Z][\dA-Za-z]*$/,
1488
+ snake_case: /^[a-z][\d_a-z]*[\da-z]$/,
1489
+ UPPER_CASE: /^[A-Z][\dA-Z_]*[\dA-Z]$/,
1490
+ };
1491
+ const ALLOWED_KINDS = Object.keys(KindToDisplayName).sort();
1492
+ const ALLOWED_STYLES = Object.keys(StyleToRegex);
1465
1493
  const schemaOption$1 = {
1466
1494
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1467
1495
  };
@@ -1476,89 +1504,120 @@ const rule$8 = {
1476
1504
  examples: [
1477
1505
  {
1478
1506
  title: 'Incorrect',
1479
- usage: [{ ObjectTypeDefinition: 'PascalCase' }],
1507
+ usage: [{ types: 'PascalCase', fields: 'camelCase' }],
1480
1508
  code: /* GraphQL */ `
1481
- type someTypeName {
1482
- f: String!
1509
+ type user {
1510
+ first_name: String!
1483
1511
  }
1484
1512
  `,
1485
1513
  },
1486
1514
  {
1487
1515
  title: 'Correct',
1488
- usage: [{ FieldDefinition: 'camelCase', ObjectTypeDefinition: 'PascalCase' }],
1516
+ usage: [{ types: 'PascalCase', fields: 'camelCase' }],
1489
1517
  code: /* GraphQL */ `
1490
- type SomeTypeName {
1491
- someFieldName: String
1518
+ type User {
1519
+ firstName: String
1492
1520
  }
1493
1521
  `,
1494
1522
  },
1495
1523
  ],
1524
+ optionsForConfig: [
1525
+ {
1526
+ types: 'PascalCase',
1527
+ fields: 'camelCase',
1528
+ overrides: {
1529
+ EnumValueDefinition: 'UPPER_CASE',
1530
+ OperationDefinition: {
1531
+ style: 'PascalCase',
1532
+ forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
1533
+ forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
1534
+ },
1535
+ FragmentDefinition: {
1536
+ style: 'PascalCase',
1537
+ forbiddenPrefixes: ['Fragment'],
1538
+ forbiddenSuffixes: ['Fragment'],
1539
+ },
1540
+ 'FieldDefinition[parent.name.value=Query]': {
1541
+ forbiddenPrefixes: ['query', 'get'],
1542
+ forbiddenSuffixes: ['Query'],
1543
+ },
1544
+ 'FieldDefinition[parent.name.value=Mutation]': {
1545
+ forbiddenPrefixes: ['mutation'],
1546
+ forbiddenSuffixes: ['Mutation'],
1547
+ },
1548
+ 'FieldDefinition[parent.name.value=Subscription]': {
1549
+ forbiddenPrefixes: ['subscription'],
1550
+ forbiddenSuffixes: ['Subscription'],
1551
+ },
1552
+ },
1553
+ },
1554
+ ],
1496
1555
  },
1497
1556
  schema: {
1498
1557
  definitions: {
1499
1558
  asString: {
1500
- type: 'string',
1501
- description: `One of: ${acceptedStyles.map(t => `\`${t}\``).join(', ')}`,
1502
- enum: acceptedStyles,
1559
+ enum: ALLOWED_STYLES,
1560
+ description: `One of: ${ALLOWED_STYLES.map(t => `\`${t}\``).join(', ')}`,
1503
1561
  },
1504
1562
  asObject: {
1505
1563
  type: 'object',
1564
+ additionalProperties: false,
1506
1565
  properties: {
1507
- style: {
1508
- type: 'string',
1509
- enum: acceptedStyles,
1510
- },
1511
- prefix: {
1512
- type: 'string',
1513
- },
1514
- suffix: {
1515
- type: 'string',
1516
- },
1566
+ style: { enum: ALLOWED_STYLES },
1567
+ prefix: { type: 'string' },
1568
+ suffix: { type: 'string' },
1517
1569
  forbiddenPrefixes: {
1518
- additionalItems: false,
1519
1570
  type: 'array',
1571
+ uniqueItems: true,
1520
1572
  minItems: 1,
1521
- items: {
1522
- type: 'string',
1523
- },
1573
+ items: { type: 'string' },
1524
1574
  },
1525
1575
  forbiddenSuffixes: {
1526
- additionalItems: false,
1527
1576
  type: 'array',
1577
+ uniqueItems: true,
1528
1578
  minItems: 1,
1529
- items: {
1530
- type: 'string',
1531
- },
1579
+ items: { type: 'string' },
1532
1580
  },
1533
1581
  },
1534
1582
  },
1535
1583
  },
1536
- $schema: 'http://json-schema.org/draft-04/schema#',
1537
1584
  type: 'array',
1585
+ maxItems: 1,
1538
1586
  items: {
1539
1587
  type: 'object',
1588
+ additionalProperties: false,
1540
1589
  properties: {
1541
- [Kind.FIELD_DEFINITION]: schemaOption$1,
1542
- [Kind.INPUT_OBJECT_TYPE_DEFINITION]: schemaOption$1,
1543
- [Kind.ENUM_VALUE_DEFINITION]: schemaOption$1,
1544
- [Kind.INPUT_VALUE_DEFINITION]: schemaOption$1,
1545
- [Kind.OBJECT_TYPE_DEFINITION]: schemaOption$1,
1546
- [Kind.INTERFACE_TYPE_DEFINITION]: schemaOption$1,
1547
- [Kind.ENUM_TYPE_DEFINITION]: schemaOption$1,
1548
- [Kind.UNION_TYPE_DEFINITION]: schemaOption$1,
1549
- [Kind.SCALAR_TYPE_DEFINITION]: schemaOption$1,
1550
- [Kind.OPERATION_DEFINITION]: schemaOption$1,
1551
- [Kind.FRAGMENT_DEFINITION]: schemaOption$1,
1552
- QueryDefinition: schemaOption$1,
1553
- leadingUnderscore: {
1554
- type: 'string',
1555
- enum: ['allow', 'forbid'],
1556
- default: 'forbid',
1590
+ types: {
1591
+ ...schemaOption$1,
1592
+ description: `Includes:\n\n${TYPES_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
1557
1593
  },
1558
- trailingUnderscore: {
1559
- type: 'string',
1560
- enum: ['allow', 'forbid'],
1561
- default: 'forbid',
1594
+ fields: {
1595
+ ...schemaOption$1,
1596
+ description: `Includes:\n\n${FIELDS_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
1597
+ },
1598
+ allowLeadingUnderscore: {
1599
+ type: 'boolean',
1600
+ default: false,
1601
+ },
1602
+ allowTrailingUnderscore: {
1603
+ type: 'boolean',
1604
+ default: false,
1605
+ },
1606
+ overrides: {
1607
+ type: 'object',
1608
+ additionalProperties: false,
1609
+ description: [
1610
+ 'May contain the following `ASTNode` names:',
1611
+ '',
1612
+ ...ALLOWED_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`),
1613
+ '',
1614
+ "> It's also possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with `ASTNode` name",
1615
+ '>',
1616
+ '> Example: pattern property `FieldDefinition[parent.name.value=Query]` will match only fields for type `Query`',
1617
+ ].join('\n'),
1618
+ patternProperties: {
1619
+ [`^(${ALLOWED_KINDS.join('|')})(.+)?$`]: schemaOption$1,
1620
+ },
1562
1621
  },
1563
1622
  },
1564
1623
  },
@@ -1566,136 +1625,83 @@ const rule$8 = {
1566
1625
  },
1567
1626
  create(context) {
1568
1627
  const options = {
1569
- leadingUnderscore: 'forbid',
1570
- trailingUnderscore: 'forbid',
1571
- ...(context.options[0] || {}),
1628
+ overrides: {},
1629
+ ...context.options[0],
1572
1630
  };
1573
- const checkNode = (node, property, nodeType) => {
1574
- const { style, suffix = '', prefix = '', forbiddenPrefixes = [], forbiddenSuffixes = [] } = property;
1575
- const result = checkNameFormat({
1576
- value: node.value,
1577
- style,
1578
- leadingUnderscore: options.leadingUnderscore,
1579
- trailingUnderscore: options.trailingUnderscore,
1580
- prefix,
1581
- suffix,
1582
- forbiddenPrefixes,
1583
- forbiddenSuffixes,
1584
- });
1585
- if (result.ok === false) {
1631
+ function normalisePropertyOption(kind) {
1632
+ let style = options.overrides[kind];
1633
+ if (!style) {
1634
+ style = TYPES_KINDS.includes(kind) ? options.types : options.fields;
1635
+ }
1636
+ return typeof style === 'object' ? style : { style };
1637
+ }
1638
+ const checkNode = (selector) => (node) => {
1639
+ const { name } = node.kind === Kind.VARIABLE_DEFINITION ? node.variable : node;
1640
+ if (!name) {
1641
+ return;
1642
+ }
1643
+ const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style } = normalisePropertyOption(selector);
1644
+ const nodeType = KindToDisplayName[node.kind] || node.kind;
1645
+ const nodeName = name.value;
1646
+ const errorMessage = getErrorMessage();
1647
+ if (errorMessage) {
1586
1648
  context.report({
1587
- loc: getLocation(node.loc, node.value),
1588
- message: result.errorMessage,
1589
- data: {
1590
- prefix,
1591
- suffix,
1592
- format: style,
1593
- forbiddenPrefixes: forbiddenPrefixes.join(', '),
1594
- forbiddenSuffixes: forbiddenSuffixes.join(', '),
1595
- nodeType,
1596
- nodeName: node.value,
1597
- },
1649
+ loc: getLocation(name.loc, name.value),
1650
+ message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1598
1651
  });
1599
1652
  }
1600
- };
1601
- const normalisePropertyOption = (value) => {
1602
- if (typeof value === 'object') {
1603
- return value;
1604
- }
1605
- return {
1606
- style: value,
1607
- prefix: '',
1608
- suffix: '',
1609
- };
1610
- };
1611
- return {
1612
- Name: node => {
1613
- if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
1614
- context.report({
1615
- loc: getLocation(node.loc, node.value),
1616
- message: 'Leading underscores are not allowed',
1617
- });
1653
+ function getErrorMessage() {
1654
+ let name = nodeName;
1655
+ if (options.allowLeadingUnderscore) {
1656
+ name = name.replace(/^_*/, '');
1618
1657
  }
1619
- if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
1620
- context.report({
1621
- loc: getLocation(node.loc, node.value),
1622
- message: 'Trailing underscores are not allowed',
1623
- });
1658
+ if (options.allowTrailingUnderscore) {
1659
+ name = name.replace(/_*$/, '');
1624
1660
  }
1625
- },
1626
- ObjectTypeDefinition: node => {
1627
- if (options.ObjectTypeDefinition) {
1628
- const property = normalisePropertyOption(options.ObjectTypeDefinition);
1629
- checkNode(node.name, property, 'Type');
1661
+ if (prefix && !name.startsWith(prefix)) {
1662
+ return `have "${prefix}" prefix`;
1630
1663
  }
1631
- },
1632
- InterfaceTypeDefinition: node => {
1633
- if (options.InterfaceTypeDefinition) {
1634
- const property = normalisePropertyOption(options.InterfaceTypeDefinition);
1635
- checkNode(node.name, property, 'Interface');
1664
+ if (suffix && !name.endsWith(suffix)) {
1665
+ return `have "${suffix}" suffix`;
1636
1666
  }
1637
- },
1638
- EnumTypeDefinition: node => {
1639
- if (options.EnumTypeDefinition) {
1640
- const property = normalisePropertyOption(options.EnumTypeDefinition);
1641
- checkNode(node.name, property, 'Enumerator');
1642
- }
1643
- },
1644
- InputObjectTypeDefinition: node => {
1645
- if (options.InputObjectTypeDefinition) {
1646
- const property = normalisePropertyOption(options.InputObjectTypeDefinition);
1647
- checkNode(node.name, property, 'Input type');
1667
+ const forbiddenPrefix = forbiddenPrefixes === null || forbiddenPrefixes === void 0 ? void 0 : forbiddenPrefixes.find(prefix => name.startsWith(prefix));
1668
+ if (forbiddenPrefix) {
1669
+ return `not have "${forbiddenPrefix}" prefix`;
1648
1670
  }
1649
- },
1650
- FieldDefinition: (node) => {
1651
- if (options.QueryDefinition && isQueryType(node.parent)) {
1652
- const property = normalisePropertyOption(options.QueryDefinition);
1653
- checkNode(node.name, property, 'Query');
1671
+ const forbiddenSuffix = forbiddenSuffixes === null || forbiddenSuffixes === void 0 ? void 0 : forbiddenSuffixes.find(suffix => name.endsWith(suffix));
1672
+ if (forbiddenSuffix) {
1673
+ return `not have "${forbiddenSuffix}" suffix`;
1654
1674
  }
1655
- if (options.FieldDefinition && !isQueryType(node.parent)) {
1656
- const property = normalisePropertyOption(options.FieldDefinition);
1657
- checkNode(node.name, property, 'Field');
1675
+ if (style && !ALLOWED_STYLES.includes(style)) {
1676
+ return `be in one of the following options: ${ALLOWED_STYLES.join(', ')}`;
1658
1677
  }
1659
- },
1660
- EnumValueDefinition: node => {
1661
- if (options.EnumValueDefinition) {
1662
- const property = normalisePropertyOption(options.EnumValueDefinition);
1663
- checkNode(node.name, property, 'Enumeration value');
1678
+ const caseRegex = StyleToRegex[style];
1679
+ if (caseRegex && !caseRegex.test(name)) {
1680
+ return `be in ${style} format`;
1664
1681
  }
1665
- },
1666
- InputValueDefinition: node => {
1667
- if (options.InputValueDefinition) {
1668
- const property = normalisePropertyOption(options.InputValueDefinition);
1669
- checkNode(node.name, property, 'Input property');
1670
- }
1671
- },
1672
- OperationDefinition: node => {
1673
- if (options.OperationDefinition) {
1674
- const property = normalisePropertyOption(options.OperationDefinition);
1675
- if (node.name) {
1676
- checkNode(node.name, property, 'Operation');
1677
- }
1678
- }
1679
- },
1680
- FragmentDefinition: node => {
1681
- if (options.FragmentDefinition) {
1682
- const property = normalisePropertyOption(options.FragmentDefinition);
1683
- checkNode(node.name, property, 'Fragment');
1684
- }
1685
- },
1686
- ScalarTypeDefinition: node => {
1687
- if (options.ScalarTypeDefinition) {
1688
- const property = normalisePropertyOption(options.ScalarTypeDefinition);
1689
- checkNode(node.name, property, 'Scalar');
1690
- }
1691
- },
1692
- UnionTypeDefinition: node => {
1693
- if (options.UnionTypeDefinition) {
1694
- const property = normalisePropertyOption(options.UnionTypeDefinition);
1695
- checkNode(node.name, property, 'Union');
1696
- }
1697
- },
1682
+ }
1698
1683
  };
1684
+ const checkUnderscore = (node) => {
1685
+ const name = node.value;
1686
+ context.report({
1687
+ loc: getLocation(node.loc, name),
1688
+ message: `${name.startsWith('_') ? 'Leading' : 'Trailing'} underscores are not allowed`,
1689
+ });
1690
+ };
1691
+ const listeners = {};
1692
+ if (!options.allowLeadingUnderscore) {
1693
+ listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1694
+ }
1695
+ if (!options.allowTrailingUnderscore) {
1696
+ listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1697
+ }
1698
+ const selectors = new Set([options.types && TYPES_KINDS, options.fields && FIELDS_KINDS, Object.keys(options.overrides)]
1699
+ .flat()
1700
+ .filter(Boolean));
1701
+ for (const selector of selectors) {
1702
+ listeners[selector] = checkNode(selector);
1703
+ }
1704
+ return listeners;
1699
1705
  },
1700
1706
  };
1701
1707
 
@@ -2412,6 +2418,7 @@ function convertDescription(node) {
2412
2418
  return [];
2413
2419
  }
2414
2420
 
2421
+ // eslint-disable-next-line unicorn/better-regex
2415
2422
  const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
2416
2423
  const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2417
2424
  const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
@@ -2575,41 +2582,23 @@ const rule$i = {
2575
2582
  };
2576
2583
 
2577
2584
  const REQUIRE_DESCRIPTION_ERROR = 'REQUIRE_DESCRIPTION_ERROR';
2578
- const DESCRIBABLE_NODES = [
2579
- Kind.SCHEMA_DEFINITION,
2580
- Kind.OBJECT_TYPE_DEFINITION,
2585
+ const ALLOWED_KINDS$1 = [
2586
+ ...TYPES_KINDS,
2581
2587
  Kind.FIELD_DEFINITION,
2582
2588
  Kind.INPUT_VALUE_DEFINITION,
2583
- Kind.INTERFACE_TYPE_DEFINITION,
2584
- Kind.UNION_TYPE_DEFINITION,
2585
- Kind.ENUM_TYPE_DEFINITION,
2586
2589
  Kind.ENUM_VALUE_DEFINITION,
2587
- Kind.INPUT_OBJECT_TYPE_DEFINITION,
2588
2590
  Kind.DIRECTIVE_DEFINITION,
2589
2591
  ];
2590
- function verifyRule(context, node) {
2591
- if (node) {
2592
- if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
2593
- context.report({
2594
- loc: getLocation(('name' in node ? node.name : node).loc, 'name' in node ? node.name.value : 'schema'),
2595
- messageId: REQUIRE_DESCRIPTION_ERROR,
2596
- data: {
2597
- nodeType: node.kind,
2598
- },
2599
- });
2600
- }
2601
- }
2602
- }
2603
2592
  const rule$j = {
2604
2593
  meta: {
2605
2594
  docs: {
2606
2595
  category: 'Best Practices',
2607
- description: `Enforce descriptions in your type definitions.`,
2608
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md`,
2596
+ description: 'Enforce descriptions in your type definitions.',
2597
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md',
2609
2598
  examples: [
2610
2599
  {
2611
2600
  title: 'Incorrect',
2612
- usage: [{ on: [Kind.OBJECT_TYPE_DEFINITION, Kind.FIELD_DEFINITION] }],
2601
+ usage: [{ types: true, overrides: { FieldDefinition: true } }],
2613
2602
  code: /* GraphQL */ `
2614
2603
  type someTypeName {
2615
2604
  name: String
@@ -2618,7 +2607,7 @@ const rule$j = {
2618
2607
  },
2619
2608
  {
2620
2609
  title: 'Correct',
2621
- usage: [{ on: [Kind.OBJECT_TYPE_DEFINITION, Kind.FIELD_DEFINITION] }],
2610
+ usage: [{ types: true, overrides: { FieldDefinition: true } }],
2622
2611
  code: /* GraphQL */ `
2623
2612
  """
2624
2613
  Some type description
@@ -2632,35 +2621,69 @@ const rule$j = {
2632
2621
  `,
2633
2622
  },
2634
2623
  ],
2624
+ optionsForConfig: [
2625
+ {
2626
+ types: true,
2627
+ overrides: {
2628
+ [Kind.DIRECTIVE_DEFINITION]: true,
2629
+ },
2630
+ },
2631
+ ],
2635
2632
  },
2636
2633
  type: 'suggestion',
2637
2634
  messages: {
2638
- [REQUIRE_DESCRIPTION_ERROR]: `Description is required for nodes of type "{{ nodeType }}"`,
2635
+ [REQUIRE_DESCRIPTION_ERROR]: 'Description is required for nodes of type "{{ nodeType }}"',
2639
2636
  },
2640
2637
  schema: {
2641
2638
  type: 'array',
2642
- additionalItems: false,
2643
2639
  minItems: 1,
2644
2640
  maxItems: 1,
2645
2641
  items: {
2646
2642
  type: 'object',
2647
- require: ['on'],
2643
+ additionalProperties: false,
2644
+ minProperties: 1,
2648
2645
  properties: {
2649
- on: {
2650
- type: 'array',
2651
- minItems: 1,
2652
- additionalItems: false,
2653
- items: {
2654
- type: 'string',
2655
- enum: DESCRIBABLE_NODES,
2656
- },
2646
+ types: {
2647
+ type: 'boolean',
2648
+ description: `Includes:\n\n${TYPES_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
2649
+ },
2650
+ overrides: {
2651
+ type: 'object',
2652
+ description: 'Configuration for precise `ASTNode`',
2653
+ additionalProperties: false,
2654
+ properties: Object.fromEntries(ALLOWED_KINDS$1.map(kind => [kind, { type: 'boolean' }])),
2657
2655
  },
2658
2656
  },
2659
2657
  },
2660
2658
  },
2661
2659
  },
2662
2660
  create(context) {
2663
- return Object.fromEntries(context.options[0].on.map(optionKey => [optionKey, node => verifyRule(context, node)]));
2661
+ const { types, overrides = {} } = context.options[0];
2662
+ const kinds = new Set(types ? TYPES_KINDS : []);
2663
+ for (const [kind, isEnabled] of Object.entries(overrides)) {
2664
+ if (isEnabled) {
2665
+ kinds.add(kind);
2666
+ }
2667
+ else {
2668
+ kinds.delete(kind);
2669
+ }
2670
+ }
2671
+ const selector = [...kinds].join(',');
2672
+ return {
2673
+ [selector](node) {
2674
+ var _a;
2675
+ const description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value) || '';
2676
+ if (description.trim().length === 0) {
2677
+ context.report({
2678
+ loc: getLocation(node.name.loc, node.name.value),
2679
+ messageId: REQUIRE_DESCRIPTION_ERROR,
2680
+ data: {
2681
+ nodeType: node.kind,
2682
+ },
2683
+ });
2684
+ }
2685
+ },
2686
+ };
2664
2687
  },
2665
2688
  };
2666
2689
 
@@ -3205,7 +3228,7 @@ const rule$n = {
3205
3228
  acceptedIdNames: ['id'],
3206
3229
  acceptedIdTypes: ['ID'],
3207
3230
  exceptions: {},
3208
- ...(context.options[0] || {}),
3231
+ ...context.options[0],
3209
3232
  };
3210
3233
  return {
3211
3234
  ObjectTypeDefinition(node) {