@graphql-eslint/eslint-plugin 2.5.0-alpha-14532ce.0 → 3.0.0-alpha-069461d.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',
@@ -91,6 +118,7 @@ const allConfig = {
91
118
  '@graphql-eslint/match-document-filename': 'error',
92
119
  '@graphql-eslint/no-deprecated': 'error',
93
120
  '@graphql-eslint/no-hashtag-description': 'error',
121
+ '@graphql-eslint/no-root-type': ['error', { disallow: ['subscription'] }],
94
122
  '@graphql-eslint/no-unreachable-types': 'error',
95
123
  '@graphql-eslint/no-unused-fields': 'error',
96
124
  '@graphql-eslint/require-deprecation-date': 'error',
@@ -211,9 +239,14 @@ const loaderCache = new Proxy(Object.create(null), {
211
239
  return true;
212
240
  },
213
241
  });
214
- const isObjectType = (node) => [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
215
- const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
216
- 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
+ ];
217
250
  var CaseStyle;
218
251
  (function (CaseStyle) {
219
252
  CaseStyle["camelCase"] = "camelCase";
@@ -264,7 +297,7 @@ function getLocation(loc, fieldName = '', offset) {
264
297
  }
265
298
 
266
299
  function extractRuleName(stack) {
267
- const match = (stack || '').match(/validation[/\\\\]rules[/\\\\](.*?)\.js:/) || [];
300
+ const match = (stack || '').match(/validation[/\\]rules[/\\](.*?)\.js:/) || [];
268
301
  return match[1] || null;
269
302
  }
270
303
  function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName = null) {
@@ -513,11 +546,23 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
513
546
  }));
514
547
 
515
548
  const ALPHABETIZE = 'ALPHABETIZE';
516
- 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
+ ];
517
554
  const valuesEnum = [graphql.Kind.ENUM_TYPE_DEFINITION];
518
- const selectionsEnum = [graphql.Kind.OPERATION_DEFINITION, graphql.Kind.FRAGMENT_DEFINITION];
555
+ const selectionsEnum = [
556
+ graphql.Kind.OPERATION_DEFINITION,
557
+ graphql.Kind.FRAGMENT_DEFINITION,
558
+ ];
519
559
  const variablesEnum = [graphql.Kind.OPERATION_DEFINITION];
520
- 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
+ ];
521
566
  const rule = {
522
567
  meta: {
523
568
  type: 'suggestion',
@@ -625,38 +670,48 @@ const rule = {
625
670
  properties: {
626
671
  fields: {
627
672
  type: 'array',
628
- contains: {
673
+ uniqueItems: true,
674
+ minItems: 1,
675
+ items: {
629
676
  enum: fieldsEnum,
630
677
  },
631
- description: 'Fields of `type`, `interface`, and `input`.',
678
+ description: 'Fields of `type`, `interface`, and `input`',
632
679
  },
633
680
  values: {
634
681
  type: 'array',
635
- contains: {
682
+ uniqueItems: true,
683
+ minItems: 1,
684
+ items: {
636
685
  enum: valuesEnum,
637
686
  },
638
- description: 'Values of `enum`.',
687
+ description: 'Values of `enum`',
639
688
  },
640
689
  selections: {
641
690
  type: 'array',
642
- contains: {
691
+ uniqueItems: true,
692
+ minItems: 1,
693
+ items: {
643
694
  enum: selectionsEnum,
644
695
  },
645
- description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`.',
696
+ description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`',
646
697
  },
647
698
  variables: {
648
699
  type: 'array',
649
- contains: {
700
+ uniqueItems: true,
701
+ minItems: 1,
702
+ items: {
650
703
  enum: variablesEnum,
651
704
  },
652
- description: 'Variables of operations (`query`, `mutation` and `subscription`).',
705
+ description: 'Variables of operations (`query`, `mutation` and `subscription`)',
653
706
  },
654
707
  arguments: {
655
708
  type: 'array',
656
- contains: {
709
+ uniqueItems: true,
710
+ minItems: 1,
711
+ items: {
657
712
  enum: argumentsEnum,
658
713
  },
659
- description: 'Arguments of fields and directives.',
714
+ description: 'Arguments of fields and directives',
660
715
  },
661
716
  },
662
717
  },
@@ -687,7 +742,7 @@ const rule = {
687
742
  const opts = context.options[0];
688
743
  const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
689
744
  const listeners = {};
690
- const fieldsSelector = [
745
+ const kinds = [
691
746
  fields.has(graphql.Kind.OBJECT_TYPE_DEFINITION) && [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION],
692
747
  fields.has(graphql.Kind.INTERFACE_TYPE_DEFINITION) && [graphql.Kind.INTERFACE_TYPE_DEFINITION, graphql.Kind.INTERFACE_TYPE_EXTENSION],
693
748
  fields.has(graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
@@ -695,8 +750,9 @@ const rule = {
695
750
  graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
696
751
  ],
697
752
  ]
698
- .flat()
699
- .join(',');
753
+ .filter(Boolean)
754
+ .flat();
755
+ const fieldsSelector = kinds.join(',');
700
756
  const hasEnumValues = ((_b = opts.values) === null || _b === void 0 ? void 0 : _b[0]) === graphql.Kind.ENUM_TYPE_DEFINITION;
701
757
  const selectionsSelector = (_c = opts.selections) === null || _c === void 0 ? void 0 : _c.join(',');
702
758
  const hasVariables = ((_d = opts.variables) === null || _d === void 0 ? void 0 : _d[0]) === graphql.Kind.OPERATION_DEFINITION;
@@ -1096,6 +1152,9 @@ const rule$5 = {
1096
1152
  },
1097
1153
  };
1098
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';
1099
1158
  const rule$6 = {
1100
1159
  meta: {
1101
1160
  type: 'suggestion',
@@ -1136,6 +1195,7 @@ const rule$6 = {
1136
1195
  schema: [
1137
1196
  {
1138
1197
  type: 'object',
1198
+ additionalProperties: false,
1139
1199
  properties: {
1140
1200
  checkInputType: {
1141
1201
  type: 'boolean',
@@ -1158,24 +1218,22 @@ const rule$6 = {
1158
1218
  description: 'Apply the rule to Mutations',
1159
1219
  },
1160
1220
  },
1161
- additionalProperties: false,
1162
1221
  },
1163
1222
  ],
1164
1223
  },
1165
1224
  create(context) {
1166
- var _a;
1167
1225
  const options = {
1168
- caseSensitiveInputType: true,
1169
1226
  checkInputType: false,
1170
- checkMutations: true,
1227
+ caseSensitiveInputType: true,
1171
1228
  checkQueries: false,
1172
- ...(_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],
1173
1231
  };
1174
1232
  const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
1175
1233
  const listeners = {
1176
- 'FieldDefinition > InputValueDefinition': node => {
1177
- const name = node.name.value;
1178
- 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;
1179
1237
  context.report({
1180
1238
  loc: getLocation(node.loc, name),
1181
1239
  message: `Input "${name}" should be called "input"`,
@@ -1183,11 +1241,11 @@ const rule$6 = {
1183
1241
  }
1184
1242
  },
1185
1243
  };
1186
- if (options === null || options === void 0 ? void 0 : options.checkInputType) {
1187
- listeners['FieldDefinition > InputValueDefinition NamedType'] = node => {
1244
+ if (options.checkInputType) {
1245
+ listeners['FieldDefinition > InputValueDefinition NamedType'] = (node) => {
1188
1246
  const findInputType = item => {
1189
1247
  let currentNode = item;
1190
- while (currentNode.type !== 'InputValueDefinition') {
1248
+ while (currentNode.type !== graphql.Kind.INPUT_VALUE_DEFINITION) {
1191
1249
  currentNode = currentNode.parent;
1192
1250
  }
1193
1251
  return currentNode;
@@ -1404,69 +1462,40 @@ const rule$7 = {
1404
1462
  },
1405
1463
  };
1406
1464
 
1407
- const formats = {
1408
- camelCase: /^[a-z][^_]*$/g,
1409
- PascalCase: /^[A-Z][^_]*$/g,
1410
- snake_case: /^[a-z_][a-z0-9_]*$/g,
1411
- UPPER_CASE: /^[A-Z_][A-Z0-9_]*$/g,
1412
- };
1413
- const acceptedStyles = [
1414
- 'camelCase',
1415
- 'PascalCase',
1416
- 'snake_case',
1417
- '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,
1418
1471
  ];
1419
- function checkNameFormat(params) {
1420
- const { value, style, leadingUnderscore, trailingUnderscore, suffix, prefix, forbiddenPrefixes, forbiddenSuffixes, } = params;
1421
- let name = value;
1422
- if (leadingUnderscore === 'allow') {
1423
- [, name] = name.match(/^_*(.*)$/);
1424
- }
1425
- if (trailingUnderscore === 'allow') {
1426
- name = name.replace(/_*$/, '');
1427
- }
1428
- if (prefix && !name.startsWith(prefix)) {
1429
- return {
1430
- ok: false,
1431
- errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{prefix}}" prefix',
1432
- };
1433
- }
1434
- if (suffix && !name.endsWith(suffix)) {
1435
- return {
1436
- ok: false,
1437
- errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{suffix}}" suffix',
1438
- };
1439
- }
1440
- if (style && !acceptedStyles.includes(style)) {
1441
- return {
1442
- ok: false,
1443
- errorMessage: `{{nodeType}} name "{{nodeName}}" should be in one of the following options: ${acceptedStyles.join(',')}`,
1444
- };
1445
- }
1446
- if (forbiddenPrefixes.some(forbiddenPrefix => name.startsWith(forbiddenPrefix))) {
1447
- return {
1448
- ok: false,
1449
- errorMessage: '{{nodeType}} "{{nodeName}}" should not have one of the following prefix(es): {{forbiddenPrefixes}}',
1450
- };
1451
- }
1452
- if (forbiddenSuffixes.some(forbiddenSuffix => name.endsWith(forbiddenSuffix))) {
1453
- return {
1454
- ok: false,
1455
- errorMessage: '{{nodeType}} "{{nodeName}}" should not have one of the following suffix(es): {{forbiddenSuffixes}}',
1456
- };
1457
- }
1458
- if (!formats[style]) {
1459
- return { ok: true };
1460
- }
1461
- const ok = new RegExp(formats[style]).test(name);
1462
- if (ok) {
1463
- return { ok: true };
1464
- }
1465
- return {
1466
- ok: false,
1467
- errorMessage: '{{nodeType}} name "{{nodeName}}" should be in {{format}} format',
1468
- };
1469
- }
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);
1470
1499
  const schemaOption$1 = {
1471
1500
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1472
1501
  };
@@ -1481,89 +1510,120 @@ const rule$8 = {
1481
1510
  examples: [
1482
1511
  {
1483
1512
  title: 'Incorrect',
1484
- usage: [{ ObjectTypeDefinition: 'PascalCase' }],
1513
+ usage: [{ types: 'PascalCase', fields: 'camelCase' }],
1485
1514
  code: /* GraphQL */ `
1486
- type someTypeName {
1487
- f: String!
1515
+ type user {
1516
+ first_name: String!
1488
1517
  }
1489
1518
  `,
1490
1519
  },
1491
1520
  {
1492
1521
  title: 'Correct',
1493
- usage: [{ FieldDefinition: 'camelCase', ObjectTypeDefinition: 'PascalCase' }],
1522
+ usage: [{ types: 'PascalCase', fields: 'camelCase' }],
1494
1523
  code: /* GraphQL */ `
1495
- type SomeTypeName {
1496
- someFieldName: String
1524
+ type User {
1525
+ firstName: String
1497
1526
  }
1498
1527
  `,
1499
1528
  },
1500
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
+ ],
1501
1561
  },
1502
1562
  schema: {
1503
1563
  definitions: {
1504
1564
  asString: {
1505
- type: 'string',
1506
- description: `One of: ${acceptedStyles.map(t => `\`${t}\``).join(', ')}`,
1507
- enum: acceptedStyles,
1565
+ enum: ALLOWED_STYLES,
1566
+ description: `One of: ${ALLOWED_STYLES.map(t => `\`${t}\``).join(', ')}`,
1508
1567
  },
1509
1568
  asObject: {
1510
1569
  type: 'object',
1570
+ additionalProperties: false,
1511
1571
  properties: {
1512
- style: {
1513
- type: 'string',
1514
- enum: acceptedStyles,
1515
- },
1516
- prefix: {
1517
- type: 'string',
1518
- },
1519
- suffix: {
1520
- type: 'string',
1521
- },
1572
+ style: { enum: ALLOWED_STYLES },
1573
+ prefix: { type: 'string' },
1574
+ suffix: { type: 'string' },
1522
1575
  forbiddenPrefixes: {
1523
- additionalItems: false,
1524
1576
  type: 'array',
1577
+ uniqueItems: true,
1525
1578
  minItems: 1,
1526
- items: {
1527
- type: 'string',
1528
- },
1579
+ items: { type: 'string' },
1529
1580
  },
1530
1581
  forbiddenSuffixes: {
1531
- additionalItems: false,
1532
1582
  type: 'array',
1583
+ uniqueItems: true,
1533
1584
  minItems: 1,
1534
- items: {
1535
- type: 'string',
1536
- },
1585
+ items: { type: 'string' },
1537
1586
  },
1538
1587
  },
1539
1588
  },
1540
1589
  },
1541
- $schema: 'http://json-schema.org/draft-04/schema#',
1542
1590
  type: 'array',
1591
+ maxItems: 1,
1543
1592
  items: {
1544
1593
  type: 'object',
1594
+ additionalProperties: false,
1545
1595
  properties: {
1546
- [graphql.Kind.FIELD_DEFINITION]: schemaOption$1,
1547
- [graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION]: schemaOption$1,
1548
- [graphql.Kind.ENUM_VALUE_DEFINITION]: schemaOption$1,
1549
- [graphql.Kind.INPUT_VALUE_DEFINITION]: schemaOption$1,
1550
- [graphql.Kind.OBJECT_TYPE_DEFINITION]: schemaOption$1,
1551
- [graphql.Kind.INTERFACE_TYPE_DEFINITION]: schemaOption$1,
1552
- [graphql.Kind.ENUM_TYPE_DEFINITION]: schemaOption$1,
1553
- [graphql.Kind.UNION_TYPE_DEFINITION]: schemaOption$1,
1554
- [graphql.Kind.SCALAR_TYPE_DEFINITION]: schemaOption$1,
1555
- [graphql.Kind.OPERATION_DEFINITION]: schemaOption$1,
1556
- [graphql.Kind.FRAGMENT_DEFINITION]: schemaOption$1,
1557
- QueryDefinition: schemaOption$1,
1558
- leadingUnderscore: {
1559
- type: 'string',
1560
- enum: ['allow', 'forbid'],
1561
- default: 'forbid',
1596
+ types: {
1597
+ ...schemaOption$1,
1598
+ description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
1562
1599
  },
1563
- trailingUnderscore: {
1564
- type: 'string',
1565
- enum: ['allow', 'forbid'],
1566
- default: 'forbid',
1600
+ fields: {
1601
+ ...schemaOption$1,
1602
+ description: `Includes:\n\n${FIELDS_KINDS.map(kind => `- \`${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}\``),
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
+ },
1567
1627
  },
1568
1628
  },
1569
1629
  },
@@ -1571,136 +1631,83 @@ const rule$8 = {
1571
1631
  },
1572
1632
  create(context) {
1573
1633
  const options = {
1574
- leadingUnderscore: 'forbid',
1575
- trailingUnderscore: 'forbid',
1576
- ...(context.options[0] || {}),
1634
+ overrides: {},
1635
+ ...context.options[0],
1577
1636
  };
1578
- const checkNode = (node, property, nodeType) => {
1579
- const { style, suffix = '', prefix = '', forbiddenPrefixes = [], forbiddenSuffixes = [] } = property;
1580
- const result = checkNameFormat({
1581
- value: node.value,
1582
- style,
1583
- leadingUnderscore: options.leadingUnderscore,
1584
- trailingUnderscore: options.trailingUnderscore,
1585
- prefix,
1586
- suffix,
1587
- forbiddenPrefixes,
1588
- forbiddenSuffixes,
1589
- });
1590
- 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) {
1591
1654
  context.report({
1592
- loc: getLocation(node.loc, node.value),
1593
- message: result.errorMessage,
1594
- data: {
1595
- prefix,
1596
- suffix,
1597
- format: style,
1598
- forbiddenPrefixes: forbiddenPrefixes.join(', '),
1599
- forbiddenSuffixes: forbiddenSuffixes.join(', '),
1600
- nodeType,
1601
- nodeName: node.value,
1602
- },
1655
+ loc: getLocation(name.loc, name.value),
1656
+ message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1603
1657
  });
1604
1658
  }
1605
- };
1606
- const normalisePropertyOption = (value) => {
1607
- if (typeof value === 'object') {
1608
- return value;
1609
- }
1610
- return {
1611
- style: value,
1612
- prefix: '',
1613
- suffix: '',
1614
- };
1615
- };
1616
- return {
1617
- Name: node => {
1618
- if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
1619
- context.report({
1620
- loc: getLocation(node.loc, node.value),
1621
- message: 'Leading underscores are not allowed',
1622
- });
1623
- }
1624
- if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
1625
- context.report({
1626
- loc: getLocation(node.loc, node.value),
1627
- message: 'Trailing underscores are not allowed',
1628
- });
1629
- }
1630
- },
1631
- ObjectTypeDefinition: node => {
1632
- if (options.ObjectTypeDefinition) {
1633
- const property = normalisePropertyOption(options.ObjectTypeDefinition);
1634
- checkNode(node.name, property, 'Type');
1635
- }
1636
- },
1637
- InterfaceTypeDefinition: node => {
1638
- if (options.InterfaceTypeDefinition) {
1639
- const property = normalisePropertyOption(options.InterfaceTypeDefinition);
1640
- checkNode(node.name, property, 'Interface');
1641
- }
1642
- },
1643
- EnumTypeDefinition: node => {
1644
- if (options.EnumTypeDefinition) {
1645
- const property = normalisePropertyOption(options.EnumTypeDefinition);
1646
- checkNode(node.name, property, 'Enumerator');
1659
+ function getErrorMessage() {
1660
+ let name = nodeName;
1661
+ if (options.allowLeadingUnderscore) {
1662
+ name = name.replace(/^_*/, '');
1647
1663
  }
1648
- },
1649
- InputObjectTypeDefinition: node => {
1650
- if (options.InputObjectTypeDefinition) {
1651
- const property = normalisePropertyOption(options.InputObjectTypeDefinition);
1652
- checkNode(node.name, property, 'Input type');
1664
+ if (options.allowTrailingUnderscore) {
1665
+ name = name.replace(/_*$/, '');
1653
1666
  }
1654
- },
1655
- FieldDefinition: (node) => {
1656
- if (options.QueryDefinition && isQueryType(node.parent)) {
1657
- const property = normalisePropertyOption(options.QueryDefinition);
1658
- checkNode(node.name, property, 'Query');
1667
+ if (prefix && !name.startsWith(prefix)) {
1668
+ return `have "${prefix}" prefix`;
1659
1669
  }
1660
- if (options.FieldDefinition && !isQueryType(node.parent)) {
1661
- const property = normalisePropertyOption(options.FieldDefinition);
1662
- checkNode(node.name, property, 'Field');
1670
+ if (suffix && !name.endsWith(suffix)) {
1671
+ return `have "${suffix}" suffix`;
1663
1672
  }
1664
- },
1665
- EnumValueDefinition: node => {
1666
- if (options.EnumValueDefinition) {
1667
- const property = normalisePropertyOption(options.EnumValueDefinition);
1668
- checkNode(node.name, property, 'Enumeration value');
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`;
1669
1676
  }
1670
- },
1671
- InputValueDefinition: node => {
1672
- if (options.InputValueDefinition) {
1673
- const property = normalisePropertyOption(options.InputValueDefinition);
1674
- checkNode(node.name, property, 'Input property');
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`;
1675
1680
  }
1676
- },
1677
- OperationDefinition: node => {
1678
- if (options.OperationDefinition) {
1679
- const property = normalisePropertyOption(options.OperationDefinition);
1680
- if (node.name) {
1681
- checkNode(node.name, property, 'Operation');
1682
- }
1681
+ if (style && !ALLOWED_STYLES.includes(style)) {
1682
+ return `be in one of the following options: ${ALLOWED_STYLES.join(', ')}`;
1683
1683
  }
1684
- },
1685
- FragmentDefinition: node => {
1686
- if (options.FragmentDefinition) {
1687
- const property = normalisePropertyOption(options.FragmentDefinition);
1688
- checkNode(node.name, property, 'Fragment');
1684
+ const caseRegex = StyleToRegex[style];
1685
+ if (caseRegex && !caseRegex.test(name)) {
1686
+ return `be in ${style} format`;
1689
1687
  }
1690
- },
1691
- ScalarTypeDefinition: node => {
1692
- if (options.ScalarTypeDefinition) {
1693
- const property = normalisePropertyOption(options.ScalarTypeDefinition);
1694
- checkNode(node.name, property, 'Scalar');
1695
- }
1696
- },
1697
- UnionTypeDefinition: node => {
1698
- if (options.UnionTypeDefinition) {
1699
- const property = normalisePropertyOption(options.UnionTypeDefinition);
1700
- checkNode(node.name, property, 'Union');
1701
- }
1702
- },
1688
+ }
1703
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;
1704
1711
  },
1705
1712
  };
1706
1713
 
@@ -2065,9 +2072,100 @@ const rule$d = {
2065
2072
  },
2066
2073
  };
2067
2074
 
2075
+ const ROOT_TYPES = ['query', 'mutation', 'subscription'];
2076
+ const rule$e = {
2077
+ meta: {
2078
+ type: 'suggestion',
2079
+ docs: {
2080
+ category: 'Validation',
2081
+ description: 'Disallow using root types for `read-only` or `write-only` schemas.',
2082
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-root-type.md',
2083
+ requiresSchema: true,
2084
+ examples: [
2085
+ {
2086
+ title: 'Incorrect (`read-only` schema)',
2087
+ usage: [{ disallow: ['mutation', 'subscription'] }],
2088
+ code: /* GraphQL */ `
2089
+ type Mutation {
2090
+ createUser(input: CreateUserInput!): User!
2091
+ }
2092
+ `,
2093
+ },
2094
+ {
2095
+ title: 'Incorrect (`write-only` schema)',
2096
+ usage: [{ disallow: ['query'] }],
2097
+ code: /* GraphQL */ `
2098
+ type Query {
2099
+ users: [User!]!
2100
+ }
2101
+ `,
2102
+ },
2103
+ {
2104
+ title: 'Correct (`read-only` schema)',
2105
+ usage: [{ disallow: ['mutation', 'subscription'] }],
2106
+ code: /* GraphQL */ `
2107
+ type Query {
2108
+ users: [User!]!
2109
+ }
2110
+ `,
2111
+ },
2112
+ ],
2113
+ optionsForConfig: [{ disallow: ['subscription'] }],
2114
+ },
2115
+ schema: {
2116
+ type: 'array',
2117
+ minItems: 1,
2118
+ maxItems: 1,
2119
+ items: {
2120
+ type: 'object',
2121
+ additionalProperties: false,
2122
+ required: ['disallow'],
2123
+ properties: {
2124
+ disallow: {
2125
+ type: 'array',
2126
+ uniqueItems: true,
2127
+ minItems: 1,
2128
+ items: {
2129
+ enum: ROOT_TYPES,
2130
+ },
2131
+ },
2132
+ },
2133
+ },
2134
+ },
2135
+ },
2136
+ create(context) {
2137
+ const schema = requireGraphQLSchemaFromContext('no-root-type', context);
2138
+ const disallow = new Set(context.options[0].disallow);
2139
+ const rootTypeNames = [
2140
+ disallow.has('query') && schema.getQueryType(),
2141
+ disallow.has('mutation') && schema.getMutationType(),
2142
+ disallow.has('subscription') && schema.getSubscriptionType(),
2143
+ ]
2144
+ .filter(Boolean)
2145
+ .map(type => type.name);
2146
+ if (rootTypeNames.length === 0) {
2147
+ return {};
2148
+ }
2149
+ const selector = [
2150
+ `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})`,
2151
+ '>',
2152
+ `${graphql.Kind.NAME}[value=/^(${rootTypeNames.join('|')})$/]`,
2153
+ ].join(' ');
2154
+ return {
2155
+ [selector](node) {
2156
+ const typeName = node.value;
2157
+ context.report({
2158
+ loc: getLocation(node.loc, typeName),
2159
+ message: `Root type "${typeName}" is forbidden`,
2160
+ });
2161
+ },
2162
+ };
2163
+ },
2164
+ };
2165
+
2068
2166
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
2069
2167
  const RULE_NAME = 'no-unreachable-types';
2070
- const rule$e = {
2168
+ const rule$f = {
2071
2169
  meta: {
2072
2170
  messages: {
2073
2171
  [UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
@@ -2143,7 +2241,7 @@ const rule$e = {
2143
2241
 
2144
2242
  const UNUSED_FIELD = 'UNUSED_FIELD';
2145
2243
  const RULE_NAME$1 = 'no-unused-fields';
2146
- const rule$f = {
2244
+ const rule$g = {
2147
2245
  meta: {
2148
2246
  messages: {
2149
2247
  [UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
@@ -2326,12 +2424,13 @@ function convertDescription(node) {
2326
2424
  return [];
2327
2425
  }
2328
2426
 
2427
+ // eslint-disable-next-line unicorn/better-regex
2329
2428
  const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
2330
2429
  const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2331
2430
  const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
2332
2431
  const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
2333
2432
  const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
2334
- const rule$g = {
2433
+ const rule$h = {
2335
2434
  meta: {
2336
2435
  type: 'suggestion',
2337
2436
  docs: {
@@ -2434,7 +2533,7 @@ const rule$g = {
2434
2533
  },
2435
2534
  };
2436
2535
 
2437
- const rule$h = {
2536
+ const rule$i = {
2438
2537
  meta: {
2439
2538
  docs: {
2440
2539
  description: `Require all deprecation directives to specify a reason.`,
@@ -2514,7 +2613,7 @@ function verifyRule(context, node) {
2514
2613
  }
2515
2614
  }
2516
2615
  }
2517
- const rule$i = {
2616
+ const rule$j = {
2518
2617
  meta: {
2519
2618
  docs: {
2520
2619
  category: 'Best Practices',
@@ -2579,7 +2678,7 @@ const rule$i = {
2579
2678
  };
2580
2679
 
2581
2680
  const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2582
- const rule$j = {
2681
+ const rule$k = {
2583
2682
  meta: {
2584
2683
  type: 'suggestion',
2585
2684
  docs: {
@@ -2747,7 +2846,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2747
2846
 
2748
2847
  const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2749
2848
  const DEFAULT_ID_FIELD_NAME = 'id';
2750
- const rule$k = {
2849
+ const rule$l = {
2751
2850
  meta: {
2752
2851
  type: 'suggestion',
2753
2852
  docs: {
@@ -2883,7 +2982,7 @@ const rule$k = {
2883
2982
  },
2884
2983
  };
2885
2984
 
2886
- const rule$l = {
2985
+ const rule$m = {
2887
2986
  meta: {
2888
2987
  docs: {
2889
2988
  category: 'Best Practices',
@@ -3005,7 +3104,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
3005
3104
  }
3006
3105
  return false;
3007
3106
  };
3008
- const rule$m = {
3107
+ const rule$n = {
3009
3108
  meta: {
3010
3109
  type: 'suggestion',
3011
3110
  docs: {
@@ -3119,7 +3218,7 @@ const rule$m = {
3119
3218
  acceptedIdNames: ['id'],
3120
3219
  acceptedIdTypes: ['ID'],
3121
3220
  exceptions: {},
3122
- ...(context.options[0] || {}),
3221
+ ...context.options[0],
3123
3222
  };
3124
3223
  return {
3125
3224
  ObjectTypeDefinition(node) {
@@ -3182,7 +3281,7 @@ const checkNode = (context, node, ruleName, messageId) => {
3182
3281
  });
3183
3282
  }
3184
3283
  };
3185
- const rule$n = {
3284
+ const rule$o = {
3186
3285
  meta: {
3187
3286
  type: 'suggestion',
3188
3287
  docs: {
@@ -3241,7 +3340,7 @@ const rule$n = {
3241
3340
 
3242
3341
  const RULE_NAME$4 = 'unique-operation-name';
3243
3342
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
3244
- const rule$o = {
3343
+ const rule$p = {
3245
3344
  meta: {
3246
3345
  type: 'suggestion',
3247
3346
  docs: {
@@ -3321,17 +3420,18 @@ const rules = {
3321
3420
  'no-deprecated': rule$b,
3322
3421
  'no-hashtag-description': rule$c,
3323
3422
  'no-operation-name-suffix': rule$d,
3324
- 'no-unreachable-types': rule$e,
3325
- 'no-unused-fields': rule$f,
3326
- 'require-deprecation-date': rule$g,
3327
- 'require-deprecation-reason': rule$h,
3328
- 'require-description': rule$i,
3329
- 'require-field-of-type-query-in-mutation-result': rule$j,
3330
- 'require-id-when-available': rule$k,
3331
- 'selection-set-depth': rule$l,
3332
- 'strict-id-in-types': rule$m,
3333
- 'unique-fragment-name': rule$n,
3334
- 'unique-operation-name': rule$o,
3423
+ 'no-root-type': rule$e,
3424
+ 'no-unreachable-types': rule$f,
3425
+ 'no-unused-fields': rule$g,
3426
+ 'require-deprecation-date': rule$h,
3427
+ 'require-deprecation-reason': rule$i,
3428
+ 'require-description': rule$j,
3429
+ 'require-field-of-type-query-in-mutation-result': rule$k,
3430
+ 'require-id-when-available': rule$l,
3431
+ 'selection-set-depth': rule$m,
3432
+ 'strict-id-in-types': rule$n,
3433
+ 'unique-fragment-name': rule$o,
3434
+ 'unique-operation-name': rule$p,
3335
3435
  };
3336
3436
 
3337
3437
  const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];