@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.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',
@@ -85,6 +112,7 @@ const allConfig = {
85
112
  '@graphql-eslint/match-document-filename': 'error',
86
113
  '@graphql-eslint/no-deprecated': 'error',
87
114
  '@graphql-eslint/no-hashtag-description': 'error',
115
+ '@graphql-eslint/no-root-type': ['error', { disallow: ['subscription'] }],
88
116
  '@graphql-eslint/no-unreachable-types': 'error',
89
117
  '@graphql-eslint/no-unused-fields': 'error',
90
118
  '@graphql-eslint/require-deprecation-date': 'error',
@@ -205,9 +233,14 @@ const loaderCache = new Proxy(Object.create(null), {
205
233
  return true;
206
234
  },
207
235
  });
208
- const isObjectType = (node) => [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
209
- const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
210
- 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
+ ];
211
244
  var CaseStyle;
212
245
  (function (CaseStyle) {
213
246
  CaseStyle["camelCase"] = "camelCase";
@@ -258,7 +291,7 @@ function getLocation(loc, fieldName = '', offset) {
258
291
  }
259
292
 
260
293
  function extractRuleName(stack) {
261
- const match = (stack || '').match(/validation[/\\\\]rules[/\\\\](.*?)\.js:/) || [];
294
+ const match = (stack || '').match(/validation[/\\]rules[/\\](.*?)\.js:/) || [];
262
295
  return match[1] || null;
263
296
  }
264
297
  function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName = null) {
@@ -507,11 +540,23 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
507
540
  }));
508
541
 
509
542
  const ALPHABETIZE = 'ALPHABETIZE';
510
- 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
+ ];
511
548
  const valuesEnum = [Kind.ENUM_TYPE_DEFINITION];
512
- const selectionsEnum = [Kind.OPERATION_DEFINITION, Kind.FRAGMENT_DEFINITION];
549
+ const selectionsEnum = [
550
+ Kind.OPERATION_DEFINITION,
551
+ Kind.FRAGMENT_DEFINITION,
552
+ ];
513
553
  const variablesEnum = [Kind.OPERATION_DEFINITION];
514
- 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
+ ];
515
560
  const rule = {
516
561
  meta: {
517
562
  type: 'suggestion',
@@ -619,38 +664,48 @@ const rule = {
619
664
  properties: {
620
665
  fields: {
621
666
  type: 'array',
622
- contains: {
667
+ uniqueItems: true,
668
+ minItems: 1,
669
+ items: {
623
670
  enum: fieldsEnum,
624
671
  },
625
- description: 'Fields of `type`, `interface`, and `input`.',
672
+ description: 'Fields of `type`, `interface`, and `input`',
626
673
  },
627
674
  values: {
628
675
  type: 'array',
629
- contains: {
676
+ uniqueItems: true,
677
+ minItems: 1,
678
+ items: {
630
679
  enum: valuesEnum,
631
680
  },
632
- description: 'Values of `enum`.',
681
+ description: 'Values of `enum`',
633
682
  },
634
683
  selections: {
635
684
  type: 'array',
636
- contains: {
685
+ uniqueItems: true,
686
+ minItems: 1,
687
+ items: {
637
688
  enum: selectionsEnum,
638
689
  },
639
- description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`.',
690
+ description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`',
640
691
  },
641
692
  variables: {
642
693
  type: 'array',
643
- contains: {
694
+ uniqueItems: true,
695
+ minItems: 1,
696
+ items: {
644
697
  enum: variablesEnum,
645
698
  },
646
- description: 'Variables of operations (`query`, `mutation` and `subscription`).',
699
+ description: 'Variables of operations (`query`, `mutation` and `subscription`)',
647
700
  },
648
701
  arguments: {
649
702
  type: 'array',
650
- contains: {
703
+ uniqueItems: true,
704
+ minItems: 1,
705
+ items: {
651
706
  enum: argumentsEnum,
652
707
  },
653
- description: 'Arguments of fields and directives.',
708
+ description: 'Arguments of fields and directives',
654
709
  },
655
710
  },
656
711
  },
@@ -681,7 +736,7 @@ const rule = {
681
736
  const opts = context.options[0];
682
737
  const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
683
738
  const listeners = {};
684
- const fieldsSelector = [
739
+ const kinds = [
685
740
  fields.has(Kind.OBJECT_TYPE_DEFINITION) && [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION],
686
741
  fields.has(Kind.INTERFACE_TYPE_DEFINITION) && [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION],
687
742
  fields.has(Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
@@ -689,8 +744,9 @@ const rule = {
689
744
  Kind.INPUT_OBJECT_TYPE_EXTENSION,
690
745
  ],
691
746
  ]
692
- .flat()
693
- .join(',');
747
+ .filter(Boolean)
748
+ .flat();
749
+ const fieldsSelector = kinds.join(',');
694
750
  const hasEnumValues = ((_b = opts.values) === null || _b === void 0 ? void 0 : _b[0]) === Kind.ENUM_TYPE_DEFINITION;
695
751
  const selectionsSelector = (_c = opts.selections) === null || _c === void 0 ? void 0 : _c.join(',');
696
752
  const hasVariables = ((_d = opts.variables) === null || _d === void 0 ? void 0 : _d[0]) === Kind.OPERATION_DEFINITION;
@@ -1090,6 +1146,9 @@ const rule$5 = {
1090
1146
  },
1091
1147
  };
1092
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';
1093
1152
  const rule$6 = {
1094
1153
  meta: {
1095
1154
  type: 'suggestion',
@@ -1130,6 +1189,7 @@ const rule$6 = {
1130
1189
  schema: [
1131
1190
  {
1132
1191
  type: 'object',
1192
+ additionalProperties: false,
1133
1193
  properties: {
1134
1194
  checkInputType: {
1135
1195
  type: 'boolean',
@@ -1152,24 +1212,22 @@ const rule$6 = {
1152
1212
  description: 'Apply the rule to Mutations',
1153
1213
  },
1154
1214
  },
1155
- additionalProperties: false,
1156
1215
  },
1157
1216
  ],
1158
1217
  },
1159
1218
  create(context) {
1160
- var _a;
1161
1219
  const options = {
1162
- caseSensitiveInputType: true,
1163
1220
  checkInputType: false,
1164
- checkMutations: true,
1221
+ caseSensitiveInputType: true,
1165
1222
  checkQueries: false,
1166
- ...(_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],
1167
1225
  };
1168
1226
  const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
1169
1227
  const listeners = {
1170
- 'FieldDefinition > InputValueDefinition': node => {
1171
- const name = node.name.value;
1172
- 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;
1173
1231
  context.report({
1174
1232
  loc: getLocation(node.loc, name),
1175
1233
  message: `Input "${name}" should be called "input"`,
@@ -1177,11 +1235,11 @@ const rule$6 = {
1177
1235
  }
1178
1236
  },
1179
1237
  };
1180
- if (options === null || options === void 0 ? void 0 : options.checkInputType) {
1181
- listeners['FieldDefinition > InputValueDefinition NamedType'] = node => {
1238
+ if (options.checkInputType) {
1239
+ listeners['FieldDefinition > InputValueDefinition NamedType'] = (node) => {
1182
1240
  const findInputType = item => {
1183
1241
  let currentNode = item;
1184
- while (currentNode.type !== 'InputValueDefinition') {
1242
+ while (currentNode.type !== Kind.INPUT_VALUE_DEFINITION) {
1185
1243
  currentNode = currentNode.parent;
1186
1244
  }
1187
1245
  return currentNode;
@@ -1398,69 +1456,40 @@ const rule$7 = {
1398
1456
  },
1399
1457
  };
1400
1458
 
1401
- const formats = {
1402
- camelCase: /^[a-z][^_]*$/g,
1403
- PascalCase: /^[A-Z][^_]*$/g,
1404
- snake_case: /^[a-z_][a-z0-9_]*$/g,
1405
- UPPER_CASE: /^[A-Z_][A-Z0-9_]*$/g,
1406
- };
1407
- const acceptedStyles = [
1408
- 'camelCase',
1409
- 'PascalCase',
1410
- 'snake_case',
1411
- '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,
1412
1465
  ];
1413
- function checkNameFormat(params) {
1414
- const { value, style, leadingUnderscore, trailingUnderscore, suffix, prefix, forbiddenPrefixes, forbiddenSuffixes, } = params;
1415
- let name = value;
1416
- if (leadingUnderscore === 'allow') {
1417
- [, name] = name.match(/^_*(.*)$/);
1418
- }
1419
- if (trailingUnderscore === 'allow') {
1420
- name = name.replace(/_*$/, '');
1421
- }
1422
- if (prefix && !name.startsWith(prefix)) {
1423
- return {
1424
- ok: false,
1425
- errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{prefix}}" prefix',
1426
- };
1427
- }
1428
- if (suffix && !name.endsWith(suffix)) {
1429
- return {
1430
- ok: false,
1431
- errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{suffix}}" suffix',
1432
- };
1433
- }
1434
- if (style && !acceptedStyles.includes(style)) {
1435
- return {
1436
- ok: false,
1437
- errorMessage: `{{nodeType}} name "{{nodeName}}" should be in one of the following options: ${acceptedStyles.join(',')}`,
1438
- };
1439
- }
1440
- if (forbiddenPrefixes.some(forbiddenPrefix => name.startsWith(forbiddenPrefix))) {
1441
- return {
1442
- ok: false,
1443
- errorMessage: '{{nodeType}} "{{nodeName}}" should not have one of the following prefix(es): {{forbiddenPrefixes}}',
1444
- };
1445
- }
1446
- if (forbiddenSuffixes.some(forbiddenSuffix => name.endsWith(forbiddenSuffix))) {
1447
- return {
1448
- ok: false,
1449
- errorMessage: '{{nodeType}} "{{nodeName}}" should not have one of the following suffix(es): {{forbiddenSuffixes}}',
1450
- };
1451
- }
1452
- if (!formats[style]) {
1453
- return { ok: true };
1454
- }
1455
- const ok = new RegExp(formats[style]).test(name);
1456
- if (ok) {
1457
- return { ok: true };
1458
- }
1459
- return {
1460
- ok: false,
1461
- errorMessage: '{{nodeType}} name "{{nodeName}}" should be in {{format}} format',
1462
- };
1463
- }
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);
1464
1493
  const schemaOption$1 = {
1465
1494
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1466
1495
  };
@@ -1475,89 +1504,120 @@ const rule$8 = {
1475
1504
  examples: [
1476
1505
  {
1477
1506
  title: 'Incorrect',
1478
- usage: [{ ObjectTypeDefinition: 'PascalCase' }],
1507
+ usage: [{ types: 'PascalCase', fields: 'camelCase' }],
1479
1508
  code: /* GraphQL */ `
1480
- type someTypeName {
1481
- f: String!
1509
+ type user {
1510
+ first_name: String!
1482
1511
  }
1483
1512
  `,
1484
1513
  },
1485
1514
  {
1486
1515
  title: 'Correct',
1487
- usage: [{ FieldDefinition: 'camelCase', ObjectTypeDefinition: 'PascalCase' }],
1516
+ usage: [{ types: 'PascalCase', fields: 'camelCase' }],
1488
1517
  code: /* GraphQL */ `
1489
- type SomeTypeName {
1490
- someFieldName: String
1518
+ type User {
1519
+ firstName: String
1491
1520
  }
1492
1521
  `,
1493
1522
  },
1494
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
+ ],
1495
1555
  },
1496
1556
  schema: {
1497
1557
  definitions: {
1498
1558
  asString: {
1499
- type: 'string',
1500
- description: `One of: ${acceptedStyles.map(t => `\`${t}\``).join(', ')}`,
1501
- enum: acceptedStyles,
1559
+ enum: ALLOWED_STYLES,
1560
+ description: `One of: ${ALLOWED_STYLES.map(t => `\`${t}\``).join(', ')}`,
1502
1561
  },
1503
1562
  asObject: {
1504
1563
  type: 'object',
1564
+ additionalProperties: false,
1505
1565
  properties: {
1506
- style: {
1507
- type: 'string',
1508
- enum: acceptedStyles,
1509
- },
1510
- prefix: {
1511
- type: 'string',
1512
- },
1513
- suffix: {
1514
- type: 'string',
1515
- },
1566
+ style: { enum: ALLOWED_STYLES },
1567
+ prefix: { type: 'string' },
1568
+ suffix: { type: 'string' },
1516
1569
  forbiddenPrefixes: {
1517
- additionalItems: false,
1518
1570
  type: 'array',
1571
+ uniqueItems: true,
1519
1572
  minItems: 1,
1520
- items: {
1521
- type: 'string',
1522
- },
1573
+ items: { type: 'string' },
1523
1574
  },
1524
1575
  forbiddenSuffixes: {
1525
- additionalItems: false,
1526
1576
  type: 'array',
1577
+ uniqueItems: true,
1527
1578
  minItems: 1,
1528
- items: {
1529
- type: 'string',
1530
- },
1579
+ items: { type: 'string' },
1531
1580
  },
1532
1581
  },
1533
1582
  },
1534
1583
  },
1535
- $schema: 'http://json-schema.org/draft-04/schema#',
1536
1584
  type: 'array',
1585
+ maxItems: 1,
1537
1586
  items: {
1538
1587
  type: 'object',
1588
+ additionalProperties: false,
1539
1589
  properties: {
1540
- [Kind.FIELD_DEFINITION]: schemaOption$1,
1541
- [Kind.INPUT_OBJECT_TYPE_DEFINITION]: schemaOption$1,
1542
- [Kind.ENUM_VALUE_DEFINITION]: schemaOption$1,
1543
- [Kind.INPUT_VALUE_DEFINITION]: schemaOption$1,
1544
- [Kind.OBJECT_TYPE_DEFINITION]: schemaOption$1,
1545
- [Kind.INTERFACE_TYPE_DEFINITION]: schemaOption$1,
1546
- [Kind.ENUM_TYPE_DEFINITION]: schemaOption$1,
1547
- [Kind.UNION_TYPE_DEFINITION]: schemaOption$1,
1548
- [Kind.SCALAR_TYPE_DEFINITION]: schemaOption$1,
1549
- [Kind.OPERATION_DEFINITION]: schemaOption$1,
1550
- [Kind.FRAGMENT_DEFINITION]: schemaOption$1,
1551
- QueryDefinition: schemaOption$1,
1552
- leadingUnderscore: {
1553
- type: 'string',
1554
- enum: ['allow', 'forbid'],
1555
- default: 'forbid',
1590
+ types: {
1591
+ ...schemaOption$1,
1592
+ description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
1556
1593
  },
1557
- trailingUnderscore: {
1558
- type: 'string',
1559
- enum: ['allow', 'forbid'],
1560
- default: 'forbid',
1594
+ fields: {
1595
+ ...schemaOption$1,
1596
+ description: `Includes:\n\n${FIELDS_KINDS.map(kind => `- \`${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}\``),
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
+ },
1561
1621
  },
1562
1622
  },
1563
1623
  },
@@ -1565,136 +1625,83 @@ const rule$8 = {
1565
1625
  },
1566
1626
  create(context) {
1567
1627
  const options = {
1568
- leadingUnderscore: 'forbid',
1569
- trailingUnderscore: 'forbid',
1570
- ...(context.options[0] || {}),
1628
+ overrides: {},
1629
+ ...context.options[0],
1571
1630
  };
1572
- const checkNode = (node, property, nodeType) => {
1573
- const { style, suffix = '', prefix = '', forbiddenPrefixes = [], forbiddenSuffixes = [] } = property;
1574
- const result = checkNameFormat({
1575
- value: node.value,
1576
- style,
1577
- leadingUnderscore: options.leadingUnderscore,
1578
- trailingUnderscore: options.trailingUnderscore,
1579
- prefix,
1580
- suffix,
1581
- forbiddenPrefixes,
1582
- forbiddenSuffixes,
1583
- });
1584
- 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) {
1585
1648
  context.report({
1586
- loc: getLocation(node.loc, node.value),
1587
- message: result.errorMessage,
1588
- data: {
1589
- prefix,
1590
- suffix,
1591
- format: style,
1592
- forbiddenPrefixes: forbiddenPrefixes.join(', '),
1593
- forbiddenSuffixes: forbiddenSuffixes.join(', '),
1594
- nodeType,
1595
- nodeName: node.value,
1596
- },
1649
+ loc: getLocation(name.loc, name.value),
1650
+ message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1597
1651
  });
1598
1652
  }
1599
- };
1600
- const normalisePropertyOption = (value) => {
1601
- if (typeof value === 'object') {
1602
- return value;
1603
- }
1604
- return {
1605
- style: value,
1606
- prefix: '',
1607
- suffix: '',
1608
- };
1609
- };
1610
- return {
1611
- Name: node => {
1612
- if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
1613
- context.report({
1614
- loc: getLocation(node.loc, node.value),
1615
- message: 'Leading underscores are not allowed',
1616
- });
1617
- }
1618
- if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
1619
- context.report({
1620
- loc: getLocation(node.loc, node.value),
1621
- message: 'Trailing underscores are not allowed',
1622
- });
1623
- }
1624
- },
1625
- ObjectTypeDefinition: node => {
1626
- if (options.ObjectTypeDefinition) {
1627
- const property = normalisePropertyOption(options.ObjectTypeDefinition);
1628
- checkNode(node.name, property, 'Type');
1629
- }
1630
- },
1631
- InterfaceTypeDefinition: node => {
1632
- if (options.InterfaceTypeDefinition) {
1633
- const property = normalisePropertyOption(options.InterfaceTypeDefinition);
1634
- checkNode(node.name, property, 'Interface');
1635
- }
1636
- },
1637
- EnumTypeDefinition: node => {
1638
- if (options.EnumTypeDefinition) {
1639
- const property = normalisePropertyOption(options.EnumTypeDefinition);
1640
- checkNode(node.name, property, 'Enumerator');
1653
+ function getErrorMessage() {
1654
+ let name = nodeName;
1655
+ if (options.allowLeadingUnderscore) {
1656
+ name = name.replace(/^_*/, '');
1641
1657
  }
1642
- },
1643
- InputObjectTypeDefinition: node => {
1644
- if (options.InputObjectTypeDefinition) {
1645
- const property = normalisePropertyOption(options.InputObjectTypeDefinition);
1646
- checkNode(node.name, property, 'Input type');
1658
+ if (options.allowTrailingUnderscore) {
1659
+ name = name.replace(/_*$/, '');
1647
1660
  }
1648
- },
1649
- FieldDefinition: (node) => {
1650
- if (options.QueryDefinition && isQueryType(node.parent)) {
1651
- const property = normalisePropertyOption(options.QueryDefinition);
1652
- checkNode(node.name, property, 'Query');
1661
+ if (prefix && !name.startsWith(prefix)) {
1662
+ return `have "${prefix}" prefix`;
1653
1663
  }
1654
- if (options.FieldDefinition && !isQueryType(node.parent)) {
1655
- const property = normalisePropertyOption(options.FieldDefinition);
1656
- checkNode(node.name, property, 'Field');
1664
+ if (suffix && !name.endsWith(suffix)) {
1665
+ return `have "${suffix}" suffix`;
1657
1666
  }
1658
- },
1659
- EnumValueDefinition: node => {
1660
- if (options.EnumValueDefinition) {
1661
- const property = normalisePropertyOption(options.EnumValueDefinition);
1662
- checkNode(node.name, property, 'Enumeration value');
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`;
1663
1670
  }
1664
- },
1665
- InputValueDefinition: node => {
1666
- if (options.InputValueDefinition) {
1667
- const property = normalisePropertyOption(options.InputValueDefinition);
1668
- checkNode(node.name, property, 'Input property');
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`;
1669
1674
  }
1670
- },
1671
- OperationDefinition: node => {
1672
- if (options.OperationDefinition) {
1673
- const property = normalisePropertyOption(options.OperationDefinition);
1674
- if (node.name) {
1675
- checkNode(node.name, property, 'Operation');
1676
- }
1675
+ if (style && !ALLOWED_STYLES.includes(style)) {
1676
+ return `be in one of the following options: ${ALLOWED_STYLES.join(', ')}`;
1677
1677
  }
1678
- },
1679
- FragmentDefinition: node => {
1680
- if (options.FragmentDefinition) {
1681
- const property = normalisePropertyOption(options.FragmentDefinition);
1682
- checkNode(node.name, property, 'Fragment');
1678
+ const caseRegex = StyleToRegex[style];
1679
+ if (caseRegex && !caseRegex.test(name)) {
1680
+ return `be in ${style} format`;
1683
1681
  }
1684
- },
1685
- ScalarTypeDefinition: node => {
1686
- if (options.ScalarTypeDefinition) {
1687
- const property = normalisePropertyOption(options.ScalarTypeDefinition);
1688
- checkNode(node.name, property, 'Scalar');
1689
- }
1690
- },
1691
- UnionTypeDefinition: node => {
1692
- if (options.UnionTypeDefinition) {
1693
- const property = normalisePropertyOption(options.UnionTypeDefinition);
1694
- checkNode(node.name, property, 'Union');
1695
- }
1696
- },
1682
+ }
1697
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;
1698
1705
  },
1699
1706
  };
1700
1707
 
@@ -2059,9 +2066,100 @@ const rule$d = {
2059
2066
  },
2060
2067
  };
2061
2068
 
2069
+ const ROOT_TYPES = ['query', 'mutation', 'subscription'];
2070
+ const rule$e = {
2071
+ meta: {
2072
+ type: 'suggestion',
2073
+ docs: {
2074
+ category: 'Validation',
2075
+ description: 'Disallow using root types for `read-only` or `write-only` schemas.',
2076
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-root-type.md',
2077
+ requiresSchema: true,
2078
+ examples: [
2079
+ {
2080
+ title: 'Incorrect (`read-only` schema)',
2081
+ usage: [{ disallow: ['mutation', 'subscription'] }],
2082
+ code: /* GraphQL */ `
2083
+ type Mutation {
2084
+ createUser(input: CreateUserInput!): User!
2085
+ }
2086
+ `,
2087
+ },
2088
+ {
2089
+ title: 'Incorrect (`write-only` schema)',
2090
+ usage: [{ disallow: ['query'] }],
2091
+ code: /* GraphQL */ `
2092
+ type Query {
2093
+ users: [User!]!
2094
+ }
2095
+ `,
2096
+ },
2097
+ {
2098
+ title: 'Correct (`read-only` schema)',
2099
+ usage: [{ disallow: ['mutation', 'subscription'] }],
2100
+ code: /* GraphQL */ `
2101
+ type Query {
2102
+ users: [User!]!
2103
+ }
2104
+ `,
2105
+ },
2106
+ ],
2107
+ optionsForConfig: [{ disallow: ['subscription'] }],
2108
+ },
2109
+ schema: {
2110
+ type: 'array',
2111
+ minItems: 1,
2112
+ maxItems: 1,
2113
+ items: {
2114
+ type: 'object',
2115
+ additionalProperties: false,
2116
+ required: ['disallow'],
2117
+ properties: {
2118
+ disallow: {
2119
+ type: 'array',
2120
+ uniqueItems: true,
2121
+ minItems: 1,
2122
+ items: {
2123
+ enum: ROOT_TYPES,
2124
+ },
2125
+ },
2126
+ },
2127
+ },
2128
+ },
2129
+ },
2130
+ create(context) {
2131
+ const schema = requireGraphQLSchemaFromContext('no-root-type', context);
2132
+ const disallow = new Set(context.options[0].disallow);
2133
+ const rootTypeNames = [
2134
+ disallow.has('query') && schema.getQueryType(),
2135
+ disallow.has('mutation') && schema.getMutationType(),
2136
+ disallow.has('subscription') && schema.getSubscriptionType(),
2137
+ ]
2138
+ .filter(Boolean)
2139
+ .map(type => type.name);
2140
+ if (rootTypeNames.length === 0) {
2141
+ return {};
2142
+ }
2143
+ const selector = [
2144
+ `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})`,
2145
+ '>',
2146
+ `${Kind.NAME}[value=/^(${rootTypeNames.join('|')})$/]`,
2147
+ ].join(' ');
2148
+ return {
2149
+ [selector](node) {
2150
+ const typeName = node.value;
2151
+ context.report({
2152
+ loc: getLocation(node.loc, typeName),
2153
+ message: `Root type "${typeName}" is forbidden`,
2154
+ });
2155
+ },
2156
+ };
2157
+ },
2158
+ };
2159
+
2062
2160
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
2063
2161
  const RULE_NAME = 'no-unreachable-types';
2064
- const rule$e = {
2162
+ const rule$f = {
2065
2163
  meta: {
2066
2164
  messages: {
2067
2165
  [UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
@@ -2137,7 +2235,7 @@ const rule$e = {
2137
2235
 
2138
2236
  const UNUSED_FIELD = 'UNUSED_FIELD';
2139
2237
  const RULE_NAME$1 = 'no-unused-fields';
2140
- const rule$f = {
2238
+ const rule$g = {
2141
2239
  meta: {
2142
2240
  messages: {
2143
2241
  [UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
@@ -2320,12 +2418,13 @@ function convertDescription(node) {
2320
2418
  return [];
2321
2419
  }
2322
2420
 
2421
+ // eslint-disable-next-line unicorn/better-regex
2323
2422
  const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
2324
2423
  const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2325
2424
  const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
2326
2425
  const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
2327
2426
  const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
2328
- const rule$g = {
2427
+ const rule$h = {
2329
2428
  meta: {
2330
2429
  type: 'suggestion',
2331
2430
  docs: {
@@ -2428,7 +2527,7 @@ const rule$g = {
2428
2527
  },
2429
2528
  };
2430
2529
 
2431
- const rule$h = {
2530
+ const rule$i = {
2432
2531
  meta: {
2433
2532
  docs: {
2434
2533
  description: `Require all deprecation directives to specify a reason.`,
@@ -2508,7 +2607,7 @@ function verifyRule(context, node) {
2508
2607
  }
2509
2608
  }
2510
2609
  }
2511
- const rule$i = {
2610
+ const rule$j = {
2512
2611
  meta: {
2513
2612
  docs: {
2514
2613
  category: 'Best Practices',
@@ -2573,7 +2672,7 @@ const rule$i = {
2573
2672
  };
2574
2673
 
2575
2674
  const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2576
- const rule$j = {
2675
+ const rule$k = {
2577
2676
  meta: {
2578
2677
  type: 'suggestion',
2579
2678
  docs: {
@@ -2741,7 +2840,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2741
2840
 
2742
2841
  const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2743
2842
  const DEFAULT_ID_FIELD_NAME = 'id';
2744
- const rule$k = {
2843
+ const rule$l = {
2745
2844
  meta: {
2746
2845
  type: 'suggestion',
2747
2846
  docs: {
@@ -2877,7 +2976,7 @@ const rule$k = {
2877
2976
  },
2878
2977
  };
2879
2978
 
2880
- const rule$l = {
2979
+ const rule$m = {
2881
2980
  meta: {
2882
2981
  docs: {
2883
2982
  category: 'Best Practices',
@@ -2999,7 +3098,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
2999
3098
  }
3000
3099
  return false;
3001
3100
  };
3002
- const rule$m = {
3101
+ const rule$n = {
3003
3102
  meta: {
3004
3103
  type: 'suggestion',
3005
3104
  docs: {
@@ -3113,7 +3212,7 @@ const rule$m = {
3113
3212
  acceptedIdNames: ['id'],
3114
3213
  acceptedIdTypes: ['ID'],
3115
3214
  exceptions: {},
3116
- ...(context.options[0] || {}),
3215
+ ...context.options[0],
3117
3216
  };
3118
3217
  return {
3119
3218
  ObjectTypeDefinition(node) {
@@ -3176,7 +3275,7 @@ const checkNode = (context, node, ruleName, messageId) => {
3176
3275
  });
3177
3276
  }
3178
3277
  };
3179
- const rule$n = {
3278
+ const rule$o = {
3180
3279
  meta: {
3181
3280
  type: 'suggestion',
3182
3281
  docs: {
@@ -3235,7 +3334,7 @@ const rule$n = {
3235
3334
 
3236
3335
  const RULE_NAME$4 = 'unique-operation-name';
3237
3336
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
3238
- const rule$o = {
3337
+ const rule$p = {
3239
3338
  meta: {
3240
3339
  type: 'suggestion',
3241
3340
  docs: {
@@ -3315,17 +3414,18 @@ const rules = {
3315
3414
  'no-deprecated': rule$b,
3316
3415
  'no-hashtag-description': rule$c,
3317
3416
  'no-operation-name-suffix': rule$d,
3318
- 'no-unreachable-types': rule$e,
3319
- 'no-unused-fields': rule$f,
3320
- 'require-deprecation-date': rule$g,
3321
- 'require-deprecation-reason': rule$h,
3322
- 'require-description': rule$i,
3323
- 'require-field-of-type-query-in-mutation-result': rule$j,
3324
- 'require-id-when-available': rule$k,
3325
- 'selection-set-depth': rule$l,
3326
- 'strict-id-in-types': rule$m,
3327
- 'unique-fragment-name': rule$n,
3328
- 'unique-operation-name': rule$o,
3417
+ 'no-root-type': rule$e,
3418
+ 'no-unreachable-types': rule$f,
3419
+ 'no-unused-fields': rule$g,
3420
+ 'require-deprecation-date': rule$h,
3421
+ 'require-deprecation-reason': rule$i,
3422
+ 'require-description': rule$j,
3423
+ 'require-field-of-type-query-in-mutation-result': rule$k,
3424
+ 'require-id-when-available': rule$l,
3425
+ 'selection-set-depth': rule$m,
3426
+ 'strict-id-in-types': rule$n,
3427
+ 'unique-fragment-name': rule$o,
3428
+ 'unique-operation-name': rule$p,
3329
3429
  };
3330
3430
 
3331
3431
  const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];