@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.js CHANGED
@@ -35,7 +35,34 @@ const recommendedConfig = {
35
35
  '@graphql-eslint/known-type-names': 'error',
36
36
  '@graphql-eslint/lone-anonymous-operation': 'error',
37
37
  '@graphql-eslint/lone-schema-definition': 'error',
38
- '@graphql-eslint/naming-convention': 'error',
38
+ '@graphql-eslint/naming-convention': [
39
+ 'error',
40
+ {
41
+ types: 'PascalCase',
42
+ fields: 'camelCase',
43
+ overrides: {
44
+ EnumValueDefinition: 'UPPER_CASE',
45
+ OperationDefinition: {
46
+ style: 'PascalCase',
47
+ forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
48
+ forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
49
+ },
50
+ FragmentDefinition: { style: 'PascalCase', forbiddenPrefixes: ['Fragment'], forbiddenSuffixes: ['Fragment'] },
51
+ 'FieldDefinition[parent.name.value=Query]': {
52
+ forbiddenPrefixes: ['query', 'get'],
53
+ forbiddenSuffixes: ['Query'],
54
+ },
55
+ 'FieldDefinition[parent.name.value=Mutation]': {
56
+ forbiddenPrefixes: ['mutation'],
57
+ forbiddenSuffixes: ['Mutation'],
58
+ },
59
+ 'FieldDefinition[parent.name.value=Subscription]': {
60
+ forbiddenPrefixes: ['subscription'],
61
+ forbiddenSuffixes: ['Subscription'],
62
+ },
63
+ },
64
+ },
65
+ ],
39
66
  '@graphql-eslint/no-anonymous-operations': 'error',
40
67
  '@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
41
68
  '@graphql-eslint/no-fragment-cycles': 'error',
@@ -95,7 +122,7 @@ const allConfig = {
95
122
  '@graphql-eslint/no-unreachable-types': 'error',
96
123
  '@graphql-eslint/no-unused-fields': 'error',
97
124
  '@graphql-eslint/require-deprecation-date': 'error',
98
- '@graphql-eslint/require-description': 'error',
125
+ '@graphql-eslint/require-description': ['error', { types: true, overrides: { DirectiveDefinition: true } }],
99
126
  '@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
100
127
  '@graphql-eslint/require-id-when-available': 'error',
101
128
  '@graphql-eslint/selection-set-depth': 'error',
@@ -212,9 +239,14 @@ const loaderCache = new Proxy(Object.create(null), {
212
239
  return true;
213
240
  },
214
241
  });
215
- const isObjectType = (node) => [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
216
- const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
217
- const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
242
+ const TYPES_KINDS = [
243
+ graphql.Kind.OBJECT_TYPE_DEFINITION,
244
+ graphql.Kind.INTERFACE_TYPE_DEFINITION,
245
+ graphql.Kind.ENUM_TYPE_DEFINITION,
246
+ graphql.Kind.SCALAR_TYPE_DEFINITION,
247
+ graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
248
+ graphql.Kind.UNION_TYPE_DEFINITION,
249
+ ];
218
250
  var CaseStyle;
219
251
  (function (CaseStyle) {
220
252
  CaseStyle["camelCase"] = "camelCase";
@@ -265,7 +297,7 @@ function getLocation(loc, fieldName = '', offset) {
265
297
  }
266
298
 
267
299
  function extractRuleName(stack) {
268
- const match = (stack || '').match(/validation[/\\\\]rules[/\\\\](.*?)\.js:/) || [];
300
+ const match = (stack || '').match(/validation[/\\]rules[/\\](.*?)\.js:/) || [];
269
301
  return match[1] || null;
270
302
  }
271
303
  function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName = null) {
@@ -514,11 +546,23 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
514
546
  }));
515
547
 
516
548
  const ALPHABETIZE = 'ALPHABETIZE';
517
- const fieldsEnum = [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.INTERFACE_TYPE_DEFINITION, graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION];
549
+ const fieldsEnum = [
550
+ graphql.Kind.OBJECT_TYPE_DEFINITION,
551
+ graphql.Kind.INTERFACE_TYPE_DEFINITION,
552
+ graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
553
+ ];
518
554
  const valuesEnum = [graphql.Kind.ENUM_TYPE_DEFINITION];
519
- const selectionsEnum = [graphql.Kind.OPERATION_DEFINITION, graphql.Kind.FRAGMENT_DEFINITION];
555
+ const selectionsEnum = [
556
+ graphql.Kind.OPERATION_DEFINITION,
557
+ graphql.Kind.FRAGMENT_DEFINITION,
558
+ ];
520
559
  const variablesEnum = [graphql.Kind.OPERATION_DEFINITION];
521
- const argumentsEnum = [graphql.Kind.FIELD_DEFINITION, graphql.Kind.FIELD, graphql.Kind.DIRECTIVE_DEFINITION, graphql.Kind.DIRECTIVE];
560
+ const argumentsEnum = [
561
+ graphql.Kind.FIELD_DEFINITION,
562
+ graphql.Kind.FIELD,
563
+ graphql.Kind.DIRECTIVE_DEFINITION,
564
+ graphql.Kind.DIRECTIVE,
565
+ ];
522
566
  const rule = {
523
567
  meta: {
524
568
  type: 'suggestion',
@@ -626,38 +670,48 @@ const rule = {
626
670
  properties: {
627
671
  fields: {
628
672
  type: 'array',
629
- contains: {
673
+ uniqueItems: true,
674
+ minItems: 1,
675
+ items: {
630
676
  enum: fieldsEnum,
631
677
  },
632
- description: 'Fields of `type`, `interface`, and `input`.',
678
+ description: 'Fields of `type`, `interface`, and `input`',
633
679
  },
634
680
  values: {
635
681
  type: 'array',
636
- contains: {
682
+ uniqueItems: true,
683
+ minItems: 1,
684
+ items: {
637
685
  enum: valuesEnum,
638
686
  },
639
- description: 'Values of `enum`.',
687
+ description: 'Values of `enum`',
640
688
  },
641
689
  selections: {
642
690
  type: 'array',
643
- contains: {
691
+ uniqueItems: true,
692
+ minItems: 1,
693
+ items: {
644
694
  enum: selectionsEnum,
645
695
  },
646
- description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`.',
696
+ description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`',
647
697
  },
648
698
  variables: {
649
699
  type: 'array',
650
- contains: {
700
+ uniqueItems: true,
701
+ minItems: 1,
702
+ items: {
651
703
  enum: variablesEnum,
652
704
  },
653
- description: 'Variables of operations (`query`, `mutation` and `subscription`).',
705
+ description: 'Variables of operations (`query`, `mutation` and `subscription`)',
654
706
  },
655
707
  arguments: {
656
708
  type: 'array',
657
- contains: {
709
+ uniqueItems: true,
710
+ minItems: 1,
711
+ items: {
658
712
  enum: argumentsEnum,
659
713
  },
660
- description: 'Arguments of fields and directives.',
714
+ description: 'Arguments of fields and directives',
661
715
  },
662
716
  },
663
717
  },
@@ -688,7 +742,7 @@ const rule = {
688
742
  const opts = context.options[0];
689
743
  const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
690
744
  const listeners = {};
691
- const fieldsSelector = [
745
+ const kinds = [
692
746
  fields.has(graphql.Kind.OBJECT_TYPE_DEFINITION) && [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION],
693
747
  fields.has(graphql.Kind.INTERFACE_TYPE_DEFINITION) && [graphql.Kind.INTERFACE_TYPE_DEFINITION, graphql.Kind.INTERFACE_TYPE_EXTENSION],
694
748
  fields.has(graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
@@ -696,8 +750,9 @@ const rule = {
696
750
  graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
697
751
  ],
698
752
  ]
699
- .flat()
700
- .join(',');
753
+ .filter(Boolean)
754
+ .flat();
755
+ const fieldsSelector = kinds.join(',');
701
756
  const hasEnumValues = ((_b = opts.values) === null || _b === void 0 ? void 0 : _b[0]) === graphql.Kind.ENUM_TYPE_DEFINITION;
702
757
  const selectionsSelector = (_c = opts.selections) === null || _c === void 0 ? void 0 : _c.join(',');
703
758
  const hasVariables = ((_d = opts.variables) === null || _d === void 0 ? void 0 : _d[0]) === graphql.Kind.OPERATION_DEFINITION;
@@ -1097,6 +1152,9 @@ const rule$5 = {
1097
1152
  },
1098
1153
  };
1099
1154
 
1155
+ const isObjectType = (node) => [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
1156
+ const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
1157
+ const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
1100
1158
  const rule$6 = {
1101
1159
  meta: {
1102
1160
  type: 'suggestion',
@@ -1137,6 +1195,7 @@ const rule$6 = {
1137
1195
  schema: [
1138
1196
  {
1139
1197
  type: 'object',
1198
+ additionalProperties: false,
1140
1199
  properties: {
1141
1200
  checkInputType: {
1142
1201
  type: 'boolean',
@@ -1159,24 +1218,22 @@ const rule$6 = {
1159
1218
  description: 'Apply the rule to Mutations',
1160
1219
  },
1161
1220
  },
1162
- additionalProperties: false,
1163
1221
  },
1164
1222
  ],
1165
1223
  },
1166
1224
  create(context) {
1167
- var _a;
1168
1225
  const options = {
1169
- caseSensitiveInputType: true,
1170
1226
  checkInputType: false,
1171
- checkMutations: true,
1227
+ caseSensitiveInputType: true,
1172
1228
  checkQueries: false,
1173
- ...(_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a[0],
1229
+ checkMutations: true,
1230
+ ...context.options[0],
1174
1231
  };
1175
1232
  const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
1176
1233
  const listeners = {
1177
- 'FieldDefinition > InputValueDefinition': node => {
1178
- const name = node.name.value;
1179
- if (name !== 'input' && shouldCheckType(node.parent.parent)) {
1234
+ 'FieldDefinition > InputValueDefinition[name.value!=input]'(node) {
1235
+ if (shouldCheckType(node.parent.parent)) {
1236
+ const name = node.name.value;
1180
1237
  context.report({
1181
1238
  loc: getLocation(node.loc, name),
1182
1239
  message: `Input "${name}" should be called "input"`,
@@ -1184,11 +1241,11 @@ const rule$6 = {
1184
1241
  }
1185
1242
  },
1186
1243
  };
1187
- if (options === null || options === void 0 ? void 0 : options.checkInputType) {
1188
- listeners['FieldDefinition > InputValueDefinition NamedType'] = node => {
1244
+ if (options.checkInputType) {
1245
+ listeners['FieldDefinition > InputValueDefinition NamedType'] = (node) => {
1189
1246
  const findInputType = item => {
1190
1247
  let currentNode = item;
1191
- while (currentNode.type !== 'InputValueDefinition') {
1248
+ while (currentNode.type !== graphql.Kind.INPUT_VALUE_DEFINITION) {
1192
1249
  currentNode = currentNode.parent;
1193
1250
  }
1194
1251
  return currentNode;
@@ -1405,69 +1462,40 @@ const rule$7 = {
1405
1462
  },
1406
1463
  };
1407
1464
 
1408
- const formats = {
1409
- camelCase: /^[a-z][^_]*$/g,
1410
- PascalCase: /^[A-Z][^_]*$/g,
1411
- snake_case: /^[a-z_][a-z0-9_]*$/g,
1412
- UPPER_CASE: /^[A-Z_][A-Z0-9_]*$/g,
1413
- };
1414
- const acceptedStyles = [
1415
- 'camelCase',
1416
- 'PascalCase',
1417
- 'snake_case',
1418
- 'UPPER_CASE',
1465
+ const FIELDS_KINDS = [
1466
+ graphql.Kind.FIELD_DEFINITION,
1467
+ graphql.Kind.INPUT_VALUE_DEFINITION,
1468
+ graphql.Kind.VARIABLE_DEFINITION,
1469
+ graphql.Kind.ARGUMENT,
1470
+ graphql.Kind.DIRECTIVE_DEFINITION,
1419
1471
  ];
1420
- function checkNameFormat(params) {
1421
- const { value, style, leadingUnderscore, trailingUnderscore, suffix, prefix, forbiddenPrefixes, forbiddenSuffixes, } = params;
1422
- let name = value;
1423
- if (leadingUnderscore === 'allow') {
1424
- [, name] = name.match(/^_*(.*)$/);
1425
- }
1426
- if (trailingUnderscore === 'allow') {
1427
- name = name.replace(/_*$/, '');
1428
- }
1429
- if (prefix && !name.startsWith(prefix)) {
1430
- return {
1431
- ok: false,
1432
- errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{prefix}}" prefix',
1433
- };
1434
- }
1435
- if (suffix && !name.endsWith(suffix)) {
1436
- return {
1437
- ok: false,
1438
- errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{suffix}}" suffix',
1439
- };
1440
- }
1441
- if (style && !acceptedStyles.includes(style)) {
1442
- return {
1443
- ok: false,
1444
- errorMessage: `{{nodeType}} name "{{nodeName}}" should be in one of the following options: ${acceptedStyles.join(',')}`,
1445
- };
1446
- }
1447
- if (forbiddenPrefixes.some(forbiddenPrefix => name.startsWith(forbiddenPrefix))) {
1448
- return {
1449
- ok: false,
1450
- errorMessage: '{{nodeType}} "{{nodeName}}" should not have one of the following prefix(es): {{forbiddenPrefixes}}',
1451
- };
1452
- }
1453
- if (forbiddenSuffixes.some(forbiddenSuffix => name.endsWith(forbiddenSuffix))) {
1454
- return {
1455
- ok: false,
1456
- errorMessage: '{{nodeType}} "{{nodeName}}" should not have one of the following suffix(es): {{forbiddenSuffixes}}',
1457
- };
1458
- }
1459
- if (!formats[style]) {
1460
- return { ok: true };
1461
- }
1462
- const ok = new RegExp(formats[style]).test(name);
1463
- if (ok) {
1464
- return { ok: true };
1465
- }
1466
- return {
1467
- ok: false,
1468
- errorMessage: '{{nodeType}} name "{{nodeName}}" should be in {{format}} format',
1469
- };
1470
- }
1472
+ const KindToDisplayName = {
1473
+ // types
1474
+ [graphql.Kind.OBJECT_TYPE_DEFINITION]: 'Type',
1475
+ [graphql.Kind.INTERFACE_TYPE_DEFINITION]: 'Interface',
1476
+ [graphql.Kind.ENUM_TYPE_DEFINITION]: 'Enumerator',
1477
+ [graphql.Kind.SCALAR_TYPE_DEFINITION]: 'Scalar',
1478
+ [graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'Input type',
1479
+ [graphql.Kind.UNION_TYPE_DEFINITION]: 'Union',
1480
+ // fields
1481
+ [graphql.Kind.FIELD_DEFINITION]: 'Field',
1482
+ [graphql.Kind.INPUT_VALUE_DEFINITION]: 'Input property',
1483
+ [graphql.Kind.VARIABLE_DEFINITION]: 'Variable',
1484
+ [graphql.Kind.ARGUMENT]: 'Argument',
1485
+ [graphql.Kind.DIRECTIVE_DEFINITION]: 'Directive',
1486
+ // rest
1487
+ [graphql.Kind.ENUM_VALUE_DEFINITION]: 'Enumeration value',
1488
+ [graphql.Kind.OPERATION_DEFINITION]: 'Operation',
1489
+ [graphql.Kind.FRAGMENT_DEFINITION]: 'Fragment',
1490
+ };
1491
+ const StyleToRegex = {
1492
+ camelCase: /^[a-z][\dA-Za-z]*$/,
1493
+ PascalCase: /^[A-Z][\dA-Za-z]*$/,
1494
+ snake_case: /^[a-z][\d_a-z]*[\da-z]$/,
1495
+ UPPER_CASE: /^[A-Z][\dA-Z_]*[\dA-Z]$/,
1496
+ };
1497
+ const ALLOWED_KINDS = Object.keys(KindToDisplayName).sort();
1498
+ const ALLOWED_STYLES = Object.keys(StyleToRegex);
1471
1499
  const schemaOption$1 = {
1472
1500
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1473
1501
  };
@@ -1482,89 +1510,120 @@ const rule$8 = {
1482
1510
  examples: [
1483
1511
  {
1484
1512
  title: 'Incorrect',
1485
- usage: [{ ObjectTypeDefinition: 'PascalCase' }],
1513
+ usage: [{ types: 'PascalCase', fields: 'camelCase' }],
1486
1514
  code: /* GraphQL */ `
1487
- type someTypeName {
1488
- f: String!
1515
+ type user {
1516
+ first_name: String!
1489
1517
  }
1490
1518
  `,
1491
1519
  },
1492
1520
  {
1493
1521
  title: 'Correct',
1494
- usage: [{ FieldDefinition: 'camelCase', ObjectTypeDefinition: 'PascalCase' }],
1522
+ usage: [{ types: 'PascalCase', fields: 'camelCase' }],
1495
1523
  code: /* GraphQL */ `
1496
- type SomeTypeName {
1497
- someFieldName: String
1524
+ type User {
1525
+ firstName: String
1498
1526
  }
1499
1527
  `,
1500
1528
  },
1501
1529
  ],
1530
+ optionsForConfig: [
1531
+ {
1532
+ types: 'PascalCase',
1533
+ fields: 'camelCase',
1534
+ overrides: {
1535
+ EnumValueDefinition: 'UPPER_CASE',
1536
+ OperationDefinition: {
1537
+ style: 'PascalCase',
1538
+ forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
1539
+ forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
1540
+ },
1541
+ FragmentDefinition: {
1542
+ style: 'PascalCase',
1543
+ forbiddenPrefixes: ['Fragment'],
1544
+ forbiddenSuffixes: ['Fragment'],
1545
+ },
1546
+ 'FieldDefinition[parent.name.value=Query]': {
1547
+ forbiddenPrefixes: ['query', 'get'],
1548
+ forbiddenSuffixes: ['Query'],
1549
+ },
1550
+ 'FieldDefinition[parent.name.value=Mutation]': {
1551
+ forbiddenPrefixes: ['mutation'],
1552
+ forbiddenSuffixes: ['Mutation'],
1553
+ },
1554
+ 'FieldDefinition[parent.name.value=Subscription]': {
1555
+ forbiddenPrefixes: ['subscription'],
1556
+ forbiddenSuffixes: ['Subscription'],
1557
+ },
1558
+ },
1559
+ },
1560
+ ],
1502
1561
  },
1503
1562
  schema: {
1504
1563
  definitions: {
1505
1564
  asString: {
1506
- type: 'string',
1507
- description: `One of: ${acceptedStyles.map(t => `\`${t}\``).join(', ')}`,
1508
- enum: acceptedStyles,
1565
+ enum: ALLOWED_STYLES,
1566
+ description: `One of: ${ALLOWED_STYLES.map(t => `\`${t}\``).join(', ')}`,
1509
1567
  },
1510
1568
  asObject: {
1511
1569
  type: 'object',
1570
+ additionalProperties: false,
1512
1571
  properties: {
1513
- style: {
1514
- type: 'string',
1515
- enum: acceptedStyles,
1516
- },
1517
- prefix: {
1518
- type: 'string',
1519
- },
1520
- suffix: {
1521
- type: 'string',
1522
- },
1572
+ style: { enum: ALLOWED_STYLES },
1573
+ prefix: { type: 'string' },
1574
+ suffix: { type: 'string' },
1523
1575
  forbiddenPrefixes: {
1524
- additionalItems: false,
1525
1576
  type: 'array',
1577
+ uniqueItems: true,
1526
1578
  minItems: 1,
1527
- items: {
1528
- type: 'string',
1529
- },
1579
+ items: { type: 'string' },
1530
1580
  },
1531
1581
  forbiddenSuffixes: {
1532
- additionalItems: false,
1533
1582
  type: 'array',
1583
+ uniqueItems: true,
1534
1584
  minItems: 1,
1535
- items: {
1536
- type: 'string',
1537
- },
1585
+ items: { type: 'string' },
1538
1586
  },
1539
1587
  },
1540
1588
  },
1541
1589
  },
1542
- $schema: 'http://json-schema.org/draft-04/schema#',
1543
1590
  type: 'array',
1591
+ maxItems: 1,
1544
1592
  items: {
1545
1593
  type: 'object',
1594
+ additionalProperties: false,
1546
1595
  properties: {
1547
- [graphql.Kind.FIELD_DEFINITION]: schemaOption$1,
1548
- [graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION]: schemaOption$1,
1549
- [graphql.Kind.ENUM_VALUE_DEFINITION]: schemaOption$1,
1550
- [graphql.Kind.INPUT_VALUE_DEFINITION]: schemaOption$1,
1551
- [graphql.Kind.OBJECT_TYPE_DEFINITION]: schemaOption$1,
1552
- [graphql.Kind.INTERFACE_TYPE_DEFINITION]: schemaOption$1,
1553
- [graphql.Kind.ENUM_TYPE_DEFINITION]: schemaOption$1,
1554
- [graphql.Kind.UNION_TYPE_DEFINITION]: schemaOption$1,
1555
- [graphql.Kind.SCALAR_TYPE_DEFINITION]: schemaOption$1,
1556
- [graphql.Kind.OPERATION_DEFINITION]: schemaOption$1,
1557
- [graphql.Kind.FRAGMENT_DEFINITION]: schemaOption$1,
1558
- QueryDefinition: schemaOption$1,
1559
- leadingUnderscore: {
1560
- type: 'string',
1561
- enum: ['allow', 'forbid'],
1562
- default: 'forbid',
1596
+ types: {
1597
+ ...schemaOption$1,
1598
+ description: `Includes:\n\n${TYPES_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
1563
1599
  },
1564
- trailingUnderscore: {
1565
- type: 'string',
1566
- enum: ['allow', 'forbid'],
1567
- default: 'forbid',
1600
+ fields: {
1601
+ ...schemaOption$1,
1602
+ description: `Includes:\n\n${FIELDS_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
1603
+ },
1604
+ allowLeadingUnderscore: {
1605
+ type: 'boolean',
1606
+ default: false,
1607
+ },
1608
+ allowTrailingUnderscore: {
1609
+ type: 'boolean',
1610
+ default: false,
1611
+ },
1612
+ overrides: {
1613
+ type: 'object',
1614
+ additionalProperties: false,
1615
+ description: [
1616
+ 'May contain the following `ASTNode` names:',
1617
+ '',
1618
+ ...ALLOWED_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`),
1619
+ '',
1620
+ "> It's also possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with `ASTNode` name",
1621
+ '>',
1622
+ '> Example: pattern property `FieldDefinition[parent.name.value=Query]` will match only fields for type `Query`',
1623
+ ].join('\n'),
1624
+ patternProperties: {
1625
+ [`^(${ALLOWED_KINDS.join('|')})(.+)?$`]: schemaOption$1,
1626
+ },
1568
1627
  },
1569
1628
  },
1570
1629
  },
@@ -1572,136 +1631,83 @@ const rule$8 = {
1572
1631
  },
1573
1632
  create(context) {
1574
1633
  const options = {
1575
- leadingUnderscore: 'forbid',
1576
- trailingUnderscore: 'forbid',
1577
- ...(context.options[0] || {}),
1634
+ overrides: {},
1635
+ ...context.options[0],
1578
1636
  };
1579
- const checkNode = (node, property, nodeType) => {
1580
- const { style, suffix = '', prefix = '', forbiddenPrefixes = [], forbiddenSuffixes = [] } = property;
1581
- const result = checkNameFormat({
1582
- value: node.value,
1583
- style,
1584
- leadingUnderscore: options.leadingUnderscore,
1585
- trailingUnderscore: options.trailingUnderscore,
1586
- prefix,
1587
- suffix,
1588
- forbiddenPrefixes,
1589
- forbiddenSuffixes,
1590
- });
1591
- if (result.ok === false) {
1637
+ function normalisePropertyOption(kind) {
1638
+ let style = options.overrides[kind];
1639
+ if (!style) {
1640
+ style = TYPES_KINDS.includes(kind) ? options.types : options.fields;
1641
+ }
1642
+ return typeof style === 'object' ? style : { style };
1643
+ }
1644
+ const checkNode = (selector) => (node) => {
1645
+ const { name } = node.kind === graphql.Kind.VARIABLE_DEFINITION ? node.variable : node;
1646
+ if (!name) {
1647
+ return;
1648
+ }
1649
+ const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style } = normalisePropertyOption(selector);
1650
+ const nodeType = KindToDisplayName[node.kind] || node.kind;
1651
+ const nodeName = name.value;
1652
+ const errorMessage = getErrorMessage();
1653
+ if (errorMessage) {
1592
1654
  context.report({
1593
- loc: getLocation(node.loc, node.value),
1594
- message: result.errorMessage,
1595
- data: {
1596
- prefix,
1597
- suffix,
1598
- format: style,
1599
- forbiddenPrefixes: forbiddenPrefixes.join(', '),
1600
- forbiddenSuffixes: forbiddenSuffixes.join(', '),
1601
- nodeType,
1602
- nodeName: node.value,
1603
- },
1655
+ loc: getLocation(name.loc, name.value),
1656
+ message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1604
1657
  });
1605
1658
  }
1606
- };
1607
- const normalisePropertyOption = (value) => {
1608
- if (typeof value === 'object') {
1609
- return value;
1610
- }
1611
- return {
1612
- style: value,
1613
- prefix: '',
1614
- suffix: '',
1615
- };
1616
- };
1617
- return {
1618
- Name: node => {
1619
- if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
1620
- context.report({
1621
- loc: getLocation(node.loc, node.value),
1622
- message: 'Leading underscores are not allowed',
1623
- });
1659
+ function getErrorMessage() {
1660
+ let name = nodeName;
1661
+ if (options.allowLeadingUnderscore) {
1662
+ name = name.replace(/^_*/, '');
1624
1663
  }
1625
- if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
1626
- context.report({
1627
- loc: getLocation(node.loc, node.value),
1628
- message: 'Trailing underscores are not allowed',
1629
- });
1664
+ if (options.allowTrailingUnderscore) {
1665
+ name = name.replace(/_*$/, '');
1630
1666
  }
1631
- },
1632
- ObjectTypeDefinition: node => {
1633
- if (options.ObjectTypeDefinition) {
1634
- const property = normalisePropertyOption(options.ObjectTypeDefinition);
1635
- checkNode(node.name, property, 'Type');
1667
+ if (prefix && !name.startsWith(prefix)) {
1668
+ return `have "${prefix}" prefix`;
1636
1669
  }
1637
- },
1638
- InterfaceTypeDefinition: node => {
1639
- if (options.InterfaceTypeDefinition) {
1640
- const property = normalisePropertyOption(options.InterfaceTypeDefinition);
1641
- checkNode(node.name, property, 'Interface');
1670
+ if (suffix && !name.endsWith(suffix)) {
1671
+ return `have "${suffix}" suffix`;
1642
1672
  }
1643
- },
1644
- EnumTypeDefinition: node => {
1645
- if (options.EnumTypeDefinition) {
1646
- const property = normalisePropertyOption(options.EnumTypeDefinition);
1647
- checkNode(node.name, property, 'Enumerator');
1648
- }
1649
- },
1650
- InputObjectTypeDefinition: node => {
1651
- if (options.InputObjectTypeDefinition) {
1652
- const property = normalisePropertyOption(options.InputObjectTypeDefinition);
1653
- checkNode(node.name, property, 'Input type');
1673
+ const forbiddenPrefix = forbiddenPrefixes === null || forbiddenPrefixes === void 0 ? void 0 : forbiddenPrefixes.find(prefix => name.startsWith(prefix));
1674
+ if (forbiddenPrefix) {
1675
+ return `not have "${forbiddenPrefix}" prefix`;
1654
1676
  }
1655
- },
1656
- FieldDefinition: (node) => {
1657
- if (options.QueryDefinition && isQueryType(node.parent)) {
1658
- const property = normalisePropertyOption(options.QueryDefinition);
1659
- checkNode(node.name, property, 'Query');
1677
+ const forbiddenSuffix = forbiddenSuffixes === null || forbiddenSuffixes === void 0 ? void 0 : forbiddenSuffixes.find(suffix => name.endsWith(suffix));
1678
+ if (forbiddenSuffix) {
1679
+ return `not have "${forbiddenSuffix}" suffix`;
1660
1680
  }
1661
- if (options.FieldDefinition && !isQueryType(node.parent)) {
1662
- const property = normalisePropertyOption(options.FieldDefinition);
1663
- checkNode(node.name, property, 'Field');
1681
+ if (style && !ALLOWED_STYLES.includes(style)) {
1682
+ return `be in one of the following options: ${ALLOWED_STYLES.join(', ')}`;
1664
1683
  }
1665
- },
1666
- EnumValueDefinition: node => {
1667
- if (options.EnumValueDefinition) {
1668
- const property = normalisePropertyOption(options.EnumValueDefinition);
1669
- checkNode(node.name, property, 'Enumeration value');
1684
+ const caseRegex = StyleToRegex[style];
1685
+ if (caseRegex && !caseRegex.test(name)) {
1686
+ return `be in ${style} format`;
1670
1687
  }
1671
- },
1672
- InputValueDefinition: node => {
1673
- if (options.InputValueDefinition) {
1674
- const property = normalisePropertyOption(options.InputValueDefinition);
1675
- checkNode(node.name, property, 'Input property');
1676
- }
1677
- },
1678
- OperationDefinition: node => {
1679
- if (options.OperationDefinition) {
1680
- const property = normalisePropertyOption(options.OperationDefinition);
1681
- if (node.name) {
1682
- checkNode(node.name, property, 'Operation');
1683
- }
1684
- }
1685
- },
1686
- FragmentDefinition: node => {
1687
- if (options.FragmentDefinition) {
1688
- const property = normalisePropertyOption(options.FragmentDefinition);
1689
- checkNode(node.name, property, 'Fragment');
1690
- }
1691
- },
1692
- ScalarTypeDefinition: node => {
1693
- if (options.ScalarTypeDefinition) {
1694
- const property = normalisePropertyOption(options.ScalarTypeDefinition);
1695
- checkNode(node.name, property, 'Scalar');
1696
- }
1697
- },
1698
- UnionTypeDefinition: node => {
1699
- if (options.UnionTypeDefinition) {
1700
- const property = normalisePropertyOption(options.UnionTypeDefinition);
1701
- checkNode(node.name, property, 'Union');
1702
- }
1703
- },
1688
+ }
1704
1689
  };
1690
+ const checkUnderscore = (node) => {
1691
+ const name = node.value;
1692
+ context.report({
1693
+ loc: getLocation(node.loc, name),
1694
+ message: `${name.startsWith('_') ? 'Leading' : 'Trailing'} underscores are not allowed`,
1695
+ });
1696
+ };
1697
+ const listeners = {};
1698
+ if (!options.allowLeadingUnderscore) {
1699
+ listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1700
+ }
1701
+ if (!options.allowTrailingUnderscore) {
1702
+ listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1703
+ }
1704
+ const selectors = new Set([options.types && TYPES_KINDS, options.fields && FIELDS_KINDS, Object.keys(options.overrides)]
1705
+ .flat()
1706
+ .filter(Boolean));
1707
+ for (const selector of selectors) {
1708
+ listeners[selector] = checkNode(selector);
1709
+ }
1710
+ return listeners;
1705
1711
  },
1706
1712
  };
1707
1713
 
@@ -2418,6 +2424,7 @@ function convertDescription(node) {
2418
2424
  return [];
2419
2425
  }
2420
2426
 
2427
+ // eslint-disable-next-line unicorn/better-regex
2421
2428
  const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
2422
2429
  const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2423
2430
  const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
@@ -2581,41 +2588,23 @@ const rule$i = {
2581
2588
  };
2582
2589
 
2583
2590
  const REQUIRE_DESCRIPTION_ERROR = 'REQUIRE_DESCRIPTION_ERROR';
2584
- const DESCRIBABLE_NODES = [
2585
- graphql.Kind.SCHEMA_DEFINITION,
2586
- graphql.Kind.OBJECT_TYPE_DEFINITION,
2591
+ const ALLOWED_KINDS$1 = [
2592
+ ...TYPES_KINDS,
2587
2593
  graphql.Kind.FIELD_DEFINITION,
2588
2594
  graphql.Kind.INPUT_VALUE_DEFINITION,
2589
- graphql.Kind.INTERFACE_TYPE_DEFINITION,
2590
- graphql.Kind.UNION_TYPE_DEFINITION,
2591
- graphql.Kind.ENUM_TYPE_DEFINITION,
2592
2595
  graphql.Kind.ENUM_VALUE_DEFINITION,
2593
- graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
2594
2596
  graphql.Kind.DIRECTIVE_DEFINITION,
2595
2597
  ];
2596
- function verifyRule(context, node) {
2597
- if (node) {
2598
- if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
2599
- context.report({
2600
- loc: getLocation(('name' in node ? node.name : node).loc, 'name' in node ? node.name.value : 'schema'),
2601
- messageId: REQUIRE_DESCRIPTION_ERROR,
2602
- data: {
2603
- nodeType: node.kind,
2604
- },
2605
- });
2606
- }
2607
- }
2608
- }
2609
2598
  const rule$j = {
2610
2599
  meta: {
2611
2600
  docs: {
2612
2601
  category: 'Best Practices',
2613
- description: `Enforce descriptions in your type definitions.`,
2614
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md`,
2602
+ description: 'Enforce descriptions in your type definitions.',
2603
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md',
2615
2604
  examples: [
2616
2605
  {
2617
2606
  title: 'Incorrect',
2618
- usage: [{ on: [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.FIELD_DEFINITION] }],
2607
+ usage: [{ types: true, overrides: { FieldDefinition: true } }],
2619
2608
  code: /* GraphQL */ `
2620
2609
  type someTypeName {
2621
2610
  name: String
@@ -2624,7 +2613,7 @@ const rule$j = {
2624
2613
  },
2625
2614
  {
2626
2615
  title: 'Correct',
2627
- usage: [{ on: [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.FIELD_DEFINITION] }],
2616
+ usage: [{ types: true, overrides: { FieldDefinition: true } }],
2628
2617
  code: /* GraphQL */ `
2629
2618
  """
2630
2619
  Some type description
@@ -2638,35 +2627,69 @@ const rule$j = {
2638
2627
  `,
2639
2628
  },
2640
2629
  ],
2630
+ optionsForConfig: [
2631
+ {
2632
+ types: true,
2633
+ overrides: {
2634
+ [graphql.Kind.DIRECTIVE_DEFINITION]: true,
2635
+ },
2636
+ },
2637
+ ],
2641
2638
  },
2642
2639
  type: 'suggestion',
2643
2640
  messages: {
2644
- [REQUIRE_DESCRIPTION_ERROR]: `Description is required for nodes of type "{{ nodeType }}"`,
2641
+ [REQUIRE_DESCRIPTION_ERROR]: 'Description is required for nodes of type "{{ nodeType }}"',
2645
2642
  },
2646
2643
  schema: {
2647
2644
  type: 'array',
2648
- additionalItems: false,
2649
2645
  minItems: 1,
2650
2646
  maxItems: 1,
2651
2647
  items: {
2652
2648
  type: 'object',
2653
- require: ['on'],
2649
+ additionalProperties: false,
2650
+ minProperties: 1,
2654
2651
  properties: {
2655
- on: {
2656
- type: 'array',
2657
- minItems: 1,
2658
- additionalItems: false,
2659
- items: {
2660
- type: 'string',
2661
- enum: DESCRIBABLE_NODES,
2662
- },
2652
+ types: {
2653
+ type: 'boolean',
2654
+ description: `Includes:\n\n${TYPES_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
2655
+ },
2656
+ overrides: {
2657
+ type: 'object',
2658
+ description: 'Configuration for precise `ASTNode`',
2659
+ additionalProperties: false,
2660
+ properties: Object.fromEntries(ALLOWED_KINDS$1.map(kind => [kind, { type: 'boolean' }])),
2663
2661
  },
2664
2662
  },
2665
2663
  },
2666
2664
  },
2667
2665
  },
2668
2666
  create(context) {
2669
- return Object.fromEntries(context.options[0].on.map(optionKey => [optionKey, node => verifyRule(context, node)]));
2667
+ const { types, overrides = {} } = context.options[0];
2668
+ const kinds = new Set(types ? TYPES_KINDS : []);
2669
+ for (const [kind, isEnabled] of Object.entries(overrides)) {
2670
+ if (isEnabled) {
2671
+ kinds.add(kind);
2672
+ }
2673
+ else {
2674
+ kinds.delete(kind);
2675
+ }
2676
+ }
2677
+ const selector = [...kinds].join(',');
2678
+ return {
2679
+ [selector](node) {
2680
+ var _a;
2681
+ const description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value) || '';
2682
+ if (description.trim().length === 0) {
2683
+ context.report({
2684
+ loc: getLocation(node.name.loc, node.name.value),
2685
+ messageId: REQUIRE_DESCRIPTION_ERROR,
2686
+ data: {
2687
+ nodeType: node.kind,
2688
+ },
2689
+ });
2690
+ }
2691
+ },
2692
+ };
2670
2693
  },
2671
2694
  };
2672
2695
 
@@ -3211,7 +3234,7 @@ const rule$n = {
3211
3234
  acceptedIdNames: ['id'],
3212
3235
  acceptedIdTypes: ['ID'],
3213
3236
  exceptions: {},
3214
- ...(context.options[0] || {}),
3237
+ ...context.options[0],
3215
3238
  };
3216
3239
  return {
3217
3240
  ObjectTypeDefinition(node) {