@graphql-eslint/eslint-plugin 3.2.0-alpha-4ca7218.0 → 3.3.0-alpha-b07557f.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
@@ -10,10 +10,10 @@ const fs = require('fs');
10
10
  const path = require('path');
11
11
  const utils = require('@graphql-tools/utils');
12
12
  const lowerCase = _interopDefault(require('lodash.lowercase'));
13
+ const graphqlConfig = require('graphql-config');
14
+ const codeFileLoader = require('@graphql-tools/code-file-loader');
13
15
  const depthLimit = _interopDefault(require('graphql-depth-limit'));
14
16
  const graphqlTagPluck = require('@graphql-tools/graphql-tag-pluck');
15
- const graphqlConfig$1 = require('graphql-config');
16
- const codeFileLoader = require('@graphql-tools/code-file-loader');
17
17
  const eslint = require('eslint');
18
18
  const codeFrame = require('@babel/code-frame');
19
19
 
@@ -174,6 +174,47 @@ const configs = {
174
174
  'operations-all': operationsAllConfig,
175
175
  };
176
176
 
177
+ let graphQLConfig;
178
+ function loadCachedGraphQLConfig(options) {
179
+ // We don't want cache config on test environment
180
+ // Otherwise schema and documents will be same for all tests
181
+ if (process.env.NODE_ENV !== 'test' && graphQLConfig) {
182
+ return graphQLConfig;
183
+ }
184
+ graphQLConfig = loadGraphQLConfig(options);
185
+ return graphQLConfig;
186
+ }
187
+ function loadGraphQLConfig(options) {
188
+ const onDiskConfig = options.skipGraphQLConfig
189
+ ? null
190
+ : graphqlConfig.loadConfigSync({
191
+ throwOnEmpty: false,
192
+ throwOnMissing: false,
193
+ extensions: [addCodeFileLoaderExtension],
194
+ });
195
+ const configOptions = options.projects
196
+ ? { projects: options.projects }
197
+ : {
198
+ schema: (options.schema || ''),
199
+ documents: options.documents || options.operations,
200
+ extensions: options.extensions,
201
+ include: options.include,
202
+ exclude: options.exclude,
203
+ };
204
+ graphQLConfig =
205
+ onDiskConfig ||
206
+ new graphqlConfig.GraphQLConfig({
207
+ config: configOptions,
208
+ filepath: 'virtual-config',
209
+ }, [addCodeFileLoaderExtension]);
210
+ return graphQLConfig;
211
+ }
212
+ const addCodeFileLoaderExtension = api => {
213
+ api.loaders.schema.register(new codeFileLoader.CodeFileLoader());
214
+ api.loaders.documents.register(new codeFileLoader.CodeFileLoader());
215
+ return { name: 'graphql-eslint-loaders' };
216
+ };
217
+
177
218
  function requireSiblingsOperations(ruleName, context) {
178
219
  if (!context.parserServices) {
179
220
  throw new Error(`Rule '${ruleName}' requires 'parserOptions.operations' to be set and loaded. See http://bit.ly/graphql-eslint-operations for more info`);
@@ -192,6 +233,32 @@ function requireGraphQLSchemaFromContext(ruleName, context) {
192
233
  }
193
234
  return context.parserServices.schema;
194
235
  }
236
+ const schemaToExtendCache = new Map();
237
+ function getGraphQLSchemaToExtend(context) {
238
+ // If parserOptions.schema not set or not loaded, there is no reason to make partial schema aka schemaToExtend
239
+ if (!context.parserServices.hasTypeInfo) {
240
+ return null;
241
+ }
242
+ const filename = context.getPhysicalFilename();
243
+ if (!schemaToExtendCache.has(filename)) {
244
+ const { schema, schemaOptions } = context.parserOptions;
245
+ const gqlConfig = loadGraphQLConfig({ schema });
246
+ const projectForFile = gqlConfig.getProjectForFile(filename);
247
+ let schemaToExtend;
248
+ try {
249
+ schemaToExtend = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', {
250
+ ...schemaOptions,
251
+ ignore: filename,
252
+ });
253
+ }
254
+ catch (_a) {
255
+ // If error throws just ignore it because maybe schema is located in 1 file
256
+ schemaToExtend = null;
257
+ }
258
+ schemaToExtendCache.set(filename, schemaToExtend);
259
+ }
260
+ return schemaToExtendCache.get(filename);
261
+ }
195
262
  function requireReachableTypesFromContext(ruleName, context) {
196
263
  const schema = requireGraphQLSchemaFromContext(ruleName, context);
197
264
  return context.parserServices.reachableTypes(schema);
@@ -285,14 +352,6 @@ const TYPES_KINDS = [
285
352
  graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
286
353
  graphql.Kind.UNION_TYPE_DEFINITION,
287
354
  ];
288
- var CaseStyle;
289
- (function (CaseStyle) {
290
- CaseStyle["camelCase"] = "camelCase";
291
- CaseStyle["pascalCase"] = "PascalCase";
292
- CaseStyle["snakeCase"] = "snake_case";
293
- CaseStyle["upperCase"] = "UPPER_CASE";
294
- CaseStyle["kebabCase"] = "kebab-case";
295
- })(CaseStyle || (CaseStyle = {}));
296
355
  const pascalCase = (str) => lowerCase(str)
297
356
  .split(' ')
298
357
  .map(word => word.charAt(0).toUpperCase() + word.slice(1))
@@ -303,15 +362,15 @@ const camelCase = (str) => {
303
362
  };
304
363
  const convertCase = (style, str) => {
305
364
  switch (style) {
306
- case CaseStyle.camelCase:
365
+ case 'camelCase':
307
366
  return camelCase(str);
308
- case CaseStyle.pascalCase:
367
+ case 'PascalCase':
309
368
  return pascalCase(str);
310
- case CaseStyle.snakeCase:
369
+ case 'snake_case':
311
370
  return lowerCase(str).replace(/ /g, '_');
312
- case CaseStyle.upperCase:
371
+ case 'UPPER_CASE':
313
372
  return lowerCase(str).replace(/ /g, '_').toUpperCase();
314
- case CaseStyle.kebabCase:
373
+ case 'kebab-case':
315
374
  return lowerCase(str).replace(/ /g, '-');
316
375
  }
317
376
  };
@@ -334,17 +393,32 @@ function getLocation(loc, fieldName = '', offset) {
334
393
  };
335
394
  }
336
395
 
337
- function validateDocument(sourceNode, context, schema, documentNode, rule) {
396
+ function validateDocument(sourceNode, context, schema = null, documentNode, rule, isSchemaToExtend = false) {
338
397
  if (documentNode.definitions.length === 0) {
339
398
  return;
340
399
  }
341
400
  try {
342
- const validationErrors = schema
401
+ const validationErrors = schema && !isSchemaToExtend
343
402
  ? graphql.validate(schema, documentNode, [rule])
344
- : validate.validateSDL(documentNode, null, [rule]);
403
+ : validate.validateSDL(documentNode, schema, [rule]);
345
404
  for (const error of validationErrors) {
405
+ /*
406
+ * TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
407
+ * Example: loc.end always equal loc.start
408
+ * {
409
+ * token: {
410
+ * type: 'Name',
411
+ * loc: { start: { line: 4, column: 13 }, end: { line: 4, column: 13 } },
412
+ * value: 'veryBad',
413
+ * range: [ 40, 47 ]
414
+ * }
415
+ * }
416
+ */
417
+ const { line, column } = error.locations[0];
418
+ const ancestors = context.getAncestors();
419
+ const token = ancestors[0].tokens.find(token => token.loc.start.line === line && token.loc.start.column === column);
346
420
  context.report({
347
- loc: getLocation({ start: error.locations[0] }),
421
+ loc: getLocation({ start: error.locations[0] }, token === null || token === void 0 ? void 0 : token.value),
348
422
  message: error.message,
349
423
  });
350
424
  }
@@ -433,18 +507,24 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
433
507
  },
434
508
  },
435
509
  create(context) {
510
+ if (!ruleFn) {
511
+ // eslint-disable-next-line no-console
512
+ console.warn(`You rule "${ruleId}" depends on a GraphQL validation rule "${ruleName}" but it's not available in the "graphql-js" version you are using. Skipping...`);
513
+ return {};
514
+ }
515
+ let schema;
516
+ if (docs.requiresSchemaToExtend) {
517
+ schema = getGraphQLSchemaToExtend(context);
518
+ }
519
+ if (docs.requiresSchema) {
520
+ schema = requireGraphQLSchemaFromContext(ruleId, context);
521
+ }
436
522
  return {
437
523
  Document(node) {
438
- if (!ruleFn) {
439
- // eslint-disable-next-line no-console
440
- console.warn(`You rule "${ruleId}" depends on a GraphQL validation rule "${ruleName}" but it's not available in the "graphql-js" version you are using. Skipping...`);
441
- return;
442
- }
443
- const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
444
524
  const documentNode = getDocumentNode
445
525
  ? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
446
526
  : node.rawNode();
447
- validateDocument(node, context, schema, documentNode, ruleFn);
527
+ validateDocument(node, context, schema, documentNode, ruleFn, docs.requiresSchemaToExtend);
448
528
  },
449
529
  };
450
530
  },
@@ -594,7 +674,8 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
594
674
  }), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
595
675
  category: 'Schema',
596
676
  description: `A type extension is only valid if the type is defined and has the same kind.`,
597
- recommended: false, // TODO: enable after https://github.com/dotansimha/graphql-eslint/issues/787 will be fixed
677
+ recommended: false,
678
+ requiresSchemaToExtend: true,
598
679
  }), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
599
680
  category: ['Schema', 'Operations'],
600
681
  description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
@@ -1083,13 +1164,7 @@ const rule$2 = {
1083
1164
  const MATCH_EXTENSION = 'MATCH_EXTENSION';
1084
1165
  const MATCH_STYLE = 'MATCH_STYLE';
1085
1166
  const ACCEPTED_EXTENSIONS = ['.gql', '.graphql'];
1086
- const CASE_STYLES = [
1087
- CaseStyle.camelCase,
1088
- CaseStyle.pascalCase,
1089
- CaseStyle.snakeCase,
1090
- CaseStyle.upperCase,
1091
- CaseStyle.kebabCase,
1092
- ];
1167
+ const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case'];
1093
1168
  const schemaOption = {
1094
1169
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1095
1170
  };
@@ -1113,7 +1188,7 @@ const rule$3 = {
1113
1188
  },
1114
1189
  {
1115
1190
  title: 'Correct',
1116
- usage: [{ query: CaseStyle.snakeCase }],
1191
+ usage: [{ query: 'snake_case' }],
1117
1192
  code: /* GraphQL */ `
1118
1193
  # user_by_id.gql
1119
1194
  query UserById {
@@ -1127,7 +1202,7 @@ const rule$3 = {
1127
1202
  },
1128
1203
  {
1129
1204
  title: 'Correct',
1130
- usage: [{ fragment: { style: CaseStyle.kebabCase, suffix: '.fragment' } }],
1205
+ usage: [{ fragment: { style: 'kebab-case', suffix: '.fragment' } }],
1131
1206
  code: /* GraphQL */ `
1132
1207
  # user-fields.fragment.gql
1133
1208
  fragment user_fields on User {
@@ -1138,7 +1213,7 @@ const rule$3 = {
1138
1213
  },
1139
1214
  {
1140
1215
  title: 'Correct',
1141
- usage: [{ mutation: { style: CaseStyle.pascalCase, suffix: 'Mutation' } }],
1216
+ usage: [{ mutation: { style: 'PascalCase', suffix: 'Mutation' } }],
1142
1217
  code: /* GraphQL */ `
1143
1218
  # DeleteUserMutation.gql
1144
1219
  mutation DELETE_USER {
@@ -1158,7 +1233,7 @@ const rule$3 = {
1158
1233
  },
1159
1234
  {
1160
1235
  title: 'Incorrect',
1161
- usage: [{ query: CaseStyle.pascalCase }],
1236
+ usage: [{ query: 'PascalCase' }],
1162
1237
  code: /* GraphQL */ `
1163
1238
  # user-by-id.gql
1164
1239
  query UserById {
@@ -1173,10 +1248,10 @@ const rule$3 = {
1173
1248
  ],
1174
1249
  configOptions: [
1175
1250
  {
1176
- query: CaseStyle.kebabCase,
1177
- mutation: CaseStyle.kebabCase,
1178
- subscription: CaseStyle.kebabCase,
1179
- fragment: CaseStyle.kebabCase,
1251
+ query: 'kebab-case',
1252
+ mutation: 'kebab-case',
1253
+ subscription: 'kebab-case',
1254
+ fragment: 'kebab-case',
1180
1255
  },
1181
1256
  ],
1182
1257
  },
@@ -1193,25 +1268,22 @@ const rule$3 = {
1193
1268
  asObject: {
1194
1269
  type: 'object',
1195
1270
  additionalProperties: false,
1271
+ minProperties: 1,
1196
1272
  properties: {
1197
- style: {
1198
- enum: CASE_STYLES,
1199
- },
1200
- suffix: {
1201
- type: 'string',
1202
- },
1273
+ style: { enum: CASE_STYLES },
1274
+ suffix: { type: 'string' },
1203
1275
  },
1204
1276
  },
1205
1277
  },
1206
1278
  type: 'array',
1279
+ minItems: 1,
1207
1280
  maxItems: 1,
1208
1281
  items: {
1209
1282
  type: 'object',
1210
1283
  additionalProperties: false,
1284
+ minProperties: 1,
1211
1285
  properties: {
1212
- fileExtension: {
1213
- enum: ACCEPTED_EXTENSIONS,
1214
- },
1286
+ fileExtension: { enum: ACCEPTED_EXTENSIONS },
1215
1287
  query: schemaOption,
1216
1288
  mutation: schemaOption,
1217
1289
  subscription: schemaOption,
@@ -1266,7 +1338,7 @@ const rule$3 = {
1266
1338
  option = { style: option };
1267
1339
  }
1268
1340
  const expectedExtension = options.fileExtension || fileExtension;
1269
- const expectedFilename = convertCase(option.style, docName) + (option.suffix || '') + expectedExtension;
1341
+ const expectedFilename = (option.style ? convertCase(option.style, docName) : filename) + (option.suffix || '') + expectedExtension;
1270
1342
  const filenameWithExtension = filename + expectedExtension;
1271
1343
  if (expectedFilename !== filenameWithExtension) {
1272
1344
  context.report({
@@ -1418,6 +1490,7 @@ const rule$4 = {
1418
1490
  ],
1419
1491
  },
1420
1492
  },
1493
+ hasSuggestions: true,
1421
1494
  schema: {
1422
1495
  definitions: {
1423
1496
  asString: {
@@ -1492,65 +1565,90 @@ const rule$4 = {
1492
1565
  const style = restOptions[kind] || types;
1493
1566
  return typeof style === 'object' ? style : { style };
1494
1567
  }
1495
- const checkNode = (selector) => (node) => {
1496
- const { name } = node.kind === graphql.Kind.VARIABLE_DEFINITION ? node.variable : node;
1497
- if (!name) {
1568
+ const checkNode = (selector) => (n) => {
1569
+ const { name: node } = n.kind === graphql.Kind.VARIABLE_DEFINITION ? n.variable : n;
1570
+ if (!node) {
1498
1571
  return;
1499
1572
  }
1500
1573
  const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style } = normalisePropertyOption(selector);
1501
- const nodeType = KindToDisplayName[node.kind] || node.kind;
1502
- const nodeName = name.value;
1503
- const errorMessage = getErrorMessage();
1504
- if (errorMessage) {
1574
+ const nodeType = KindToDisplayName[n.kind] || n.kind;
1575
+ const nodeName = node.value;
1576
+ const error = getError();
1577
+ if (error) {
1578
+ const { errorMessage, renameToName } = error;
1579
+ const [leadingUnderscore] = nodeName.match(/^_*/);
1580
+ const [trailingUnderscore] = nodeName.match(/_*$/);
1581
+ const suggestedName = leadingUnderscore + renameToName + trailingUnderscore;
1505
1582
  context.report({
1506
- loc: getLocation(name.loc, name.value),
1583
+ loc: getLocation(node.loc, node.value),
1507
1584
  message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1585
+ suggest: [
1586
+ {
1587
+ desc: `Rename to "${suggestedName}"`,
1588
+ fix: fixer => fixer.replaceText(node, suggestedName),
1589
+ },
1590
+ ],
1508
1591
  });
1509
1592
  }
1510
- function getErrorMessage() {
1511
- let name = nodeName;
1512
- if (allowLeadingUnderscore) {
1513
- name = name.replace(/^_*/, '');
1514
- }
1515
- if (allowTrailingUnderscore) {
1516
- name = name.replace(/_*$/, '');
1517
- }
1593
+ function getError() {
1594
+ const name = nodeName.replace(/(^_+)|(_+$)/g, '');
1518
1595
  if (prefix && !name.startsWith(prefix)) {
1519
- return `have "${prefix}" prefix`;
1596
+ return {
1597
+ errorMessage: `have "${prefix}" prefix`,
1598
+ renameToName: prefix + name,
1599
+ };
1520
1600
  }
1521
1601
  if (suffix && !name.endsWith(suffix)) {
1522
- return `have "${suffix}" suffix`;
1602
+ return {
1603
+ errorMessage: `have "${suffix}" suffix`,
1604
+ renameToName: name + suffix,
1605
+ };
1523
1606
  }
1524
1607
  const forbiddenPrefix = forbiddenPrefixes === null || forbiddenPrefixes === void 0 ? void 0 : forbiddenPrefixes.find(prefix => name.startsWith(prefix));
1525
1608
  if (forbiddenPrefix) {
1526
- return `not have "${forbiddenPrefix}" prefix`;
1609
+ return {
1610
+ errorMessage: `not have "${forbiddenPrefix}" prefix`,
1611
+ renameToName: name.replace(new RegExp(`^${forbiddenPrefix}`), ''),
1612
+ };
1527
1613
  }
1528
1614
  const forbiddenSuffix = forbiddenSuffixes === null || forbiddenSuffixes === void 0 ? void 0 : forbiddenSuffixes.find(suffix => name.endsWith(suffix));
1529
1615
  if (forbiddenSuffix) {
1530
- return `not have "${forbiddenSuffix}" suffix`;
1531
- }
1532
- if (style && !ALLOWED_STYLES.includes(style)) {
1533
- return `be in one of the following options: ${ALLOWED_STYLES.join(', ')}`;
1616
+ return {
1617
+ errorMessage: `not have "${forbiddenSuffix}" suffix`,
1618
+ renameToName: name.replace(new RegExp(`${forbiddenSuffix}$`), ''),
1619
+ };
1534
1620
  }
1535
1621
  const caseRegex = StyleToRegex[style];
1536
1622
  if (caseRegex && !caseRegex.test(name)) {
1537
- return `be in ${style} format`;
1623
+ return {
1624
+ errorMessage: `be in ${style} format`,
1625
+ renameToName: convertCase(style, name),
1626
+ };
1538
1627
  }
1539
1628
  }
1540
1629
  };
1541
- const checkUnderscore = (node) => {
1630
+ const checkUnderscore = (isLeading) => (node) => {
1542
1631
  const name = node.value;
1632
+ const renameToName = name.replace(new RegExp(isLeading ? '^_+' : '_+$'), '');
1543
1633
  context.report({
1544
1634
  loc: getLocation(node.loc, name),
1545
- message: `${name.startsWith('_') ? 'Leading' : 'Trailing'} underscores are not allowed`,
1635
+ message: `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`,
1636
+ suggest: [
1637
+ {
1638
+ desc: `Rename to "${renameToName}"`,
1639
+ fix: fixer => fixer.replaceText(node, renameToName),
1640
+ },
1641
+ ],
1546
1642
  });
1547
1643
  };
1548
1644
  const listeners = {};
1549
1645
  if (!allowLeadingUnderscore) {
1550
- listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1646
+ listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
1647
+ checkUnderscore(true);
1551
1648
  }
1552
1649
  if (!allowTrailingUnderscore) {
1553
- listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1650
+ listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
1651
+ checkUnderscore(false);
1554
1652
  }
1555
1653
  const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
1556
1654
  for (const selector of selectors) {
@@ -2893,9 +2991,22 @@ const rule$j = {
2893
2991
  recommended: true,
2894
2992
  },
2895
2993
  messages: {
2896
- [REQUIRE_ID_WHEN_AVAILABLE]: `Field "{{ fieldName }}" must be selected when it's available on a type. Please make sure to include it in your selection set!\nIf you are using fragments, make sure that all used fragments {{ checkedFragments }} specifies the field "{{ fieldName }}".`,
2994
+ [REQUIRE_ID_WHEN_AVAILABLE]: [
2995
+ `Field {{ fieldName }} must be selected when it's available on a type. Please make sure to include it in your selection set!`,
2996
+ `If you are using fragments, make sure that all used fragments {{ checkedFragments }}specifies the field {{ fieldName }}.`,
2997
+ ].join('\n'),
2897
2998
  },
2898
2999
  schema: {
3000
+ definitions: {
3001
+ asString: {
3002
+ type: 'string',
3003
+ },
3004
+ asArray: {
3005
+ type: 'array',
3006
+ minItems: 1,
3007
+ uniqueItems: true,
3008
+ },
3009
+ },
2899
3010
  type: 'array',
2900
3011
  maxItems: 1,
2901
3012
  items: {
@@ -2903,7 +3014,7 @@ const rule$j = {
2903
3014
  additionalProperties: false,
2904
3015
  properties: {
2905
3016
  fieldName: {
2906
- type: 'string',
3017
+ oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asArray' }],
2907
3018
  default: DEFAULT_ID_FIELD_NAME,
2908
3019
  },
2909
3020
  },
@@ -2911,69 +3022,64 @@ const rule$j = {
2911
3022
  },
2912
3023
  },
2913
3024
  create(context) {
3025
+ requireGraphQLSchemaFromContext('require-id-when-available', context);
3026
+ const siblings = requireSiblingsOperations('require-id-when-available', context);
3027
+ const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
3028
+ const idNames = Array.isArray(fieldName) ? fieldName : [fieldName];
3029
+ const isFound = (s) => s.kind === graphql.Kind.FIELD && idNames.includes(s.name.value);
2914
3030
  return {
2915
3031
  SelectionSet(node) {
2916
3032
  var _a, _b;
2917
- requireGraphQLSchemaFromContext('require-id-when-available', context);
2918
- const siblings = requireSiblingsOperations('require-id-when-available', context);
2919
- const fieldName = (context.options[0] || {}).fieldName || DEFAULT_ID_FIELD_NAME;
2920
- if (!node.selections || node.selections.length === 0) {
3033
+ const typeInfo = node.typeInfo();
3034
+ if (!typeInfo.gqlType) {
2921
3035
  return;
2922
3036
  }
2923
- const typeInfo = node.typeInfo();
2924
- if (typeInfo && typeInfo.gqlType) {
2925
- const rawType = getBaseType(typeInfo.gqlType);
2926
- if (rawType instanceof graphql.GraphQLObjectType || rawType instanceof graphql.GraphQLInterfaceType) {
2927
- const fields = rawType.getFields();
2928
- const hasIdFieldInType = !!fields[fieldName];
2929
- const checkedFragmentSpreads = new Set();
2930
- if (hasIdFieldInType) {
2931
- let found = false;
2932
- for (const selection of node.selections) {
2933
- if (selection.kind === 'Field' && selection.name.value === fieldName) {
2934
- found = true;
2935
- }
2936
- else if (selection.kind === 'InlineFragment') {
2937
- found = (((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
2938
- }
2939
- else if (selection.kind === 'FragmentSpread') {
2940
- const foundSpread = siblings.getFragment(selection.name.value);
2941
- if (foundSpread[0]) {
2942
- checkedFragmentSpreads.add(foundSpread[0].document.name.value);
2943
- found = (((_b = foundSpread[0].document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
2944
- }
2945
- }
2946
- if (found) {
2947
- break;
2948
- }
2949
- }
2950
- const { parent } = node;
2951
- const hasIdFieldInInterfaceSelectionSet = parent &&
2952
- parent.kind === 'InlineFragment' &&
2953
- parent.parent &&
2954
- parent.parent.kind === 'SelectionSet' &&
2955
- parent.parent.selections.some(s => s.kind === 'Field' && s.name.value === fieldName);
2956
- if (!found && !hasIdFieldInInterfaceSelectionSet) {
2957
- context.report({
2958
- loc: {
2959
- start: {
2960
- line: node.loc.start.line,
2961
- column: node.loc.start.column - 1,
2962
- },
2963
- end: {
2964
- line: node.loc.end.line,
2965
- column: node.loc.end.column - 1,
2966
- },
2967
- },
2968
- messageId: REQUIRE_ID_WHEN_AVAILABLE,
2969
- data: {
2970
- checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${Array.from(checkedFragmentSpreads).join(', ')})`,
2971
- fieldName,
2972
- },
2973
- });
2974
- }
3037
+ const rawType = getBaseType(typeInfo.gqlType);
3038
+ const isObjectType = rawType instanceof graphql.GraphQLObjectType;
3039
+ const isInterfaceType = rawType instanceof graphql.GraphQLInterfaceType;
3040
+ if (!isObjectType && !isInterfaceType) {
3041
+ return;
3042
+ }
3043
+ const fields = rawType.getFields();
3044
+ const hasIdFieldInType = idNames.some(name => fields[name]);
3045
+ if (!hasIdFieldInType) {
3046
+ return;
3047
+ }
3048
+ const checkedFragmentSpreads = new Set();
3049
+ let found = false;
3050
+ for (const selection of node.selections) {
3051
+ if (isFound(selection)) {
3052
+ found = true;
3053
+ }
3054
+ else if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
3055
+ found = (_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections.some(s => isFound(s));
3056
+ }
3057
+ else if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
3058
+ const [foundSpread] = siblings.getFragment(selection.name.value);
3059
+ if (foundSpread) {
3060
+ checkedFragmentSpreads.add(foundSpread.document.name.value);
3061
+ found = (_b = foundSpread.document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections.some(s => isFound(s));
2975
3062
  }
2976
3063
  }
3064
+ if (found) {
3065
+ break;
3066
+ }
3067
+ }
3068
+ const { parent } = node;
3069
+ const hasIdFieldInInterfaceSelectionSet = parent &&
3070
+ parent.kind === graphql.Kind.INLINE_FRAGMENT &&
3071
+ parent.parent &&
3072
+ parent.parent.kind === graphql.Kind.SELECTION_SET &&
3073
+ parent.parent.selections.some(s => isFound(s));
3074
+ if (!found && !hasIdFieldInInterfaceSelectionSet) {
3075
+ context.report({
3076
+ loc: getLocation(node.loc),
3077
+ messageId: REQUIRE_ID_WHEN_AVAILABLE,
3078
+ data: {
3079
+ checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${[...checkedFragmentSpreads].join(', ')}) `,
3080
+ fieldName: idNames.map(name => `"${name}"`).join(' or '),
3081
+ },
3082
+ });
2977
3083
  }
2978
3084
  },
2979
3085
  };
@@ -3065,7 +3171,7 @@ const rule$k = {
3065
3171
  // eslint-disable-next-line no-console
3066
3172
  console.warn(`Rule "selection-set-depth" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
3067
3173
  }
3068
- const maxDepth = context.options[0].maxDepth;
3174
+ const { maxDepth } = context.options[0];
3069
3175
  const ignore = context.options[0].ignore || [];
3070
3176
  const checkFn = depthLimit(maxDepth, { ignore });
3071
3177
  return {
@@ -3669,42 +3775,6 @@ function getSiblingOperations(options, gqlConfig) {
3669
3775
  return siblingOperations;
3670
3776
  }
3671
3777
 
3672
- let graphqlConfig;
3673
- function loadGraphqlConfig(options) {
3674
- // We don't want cache config on test environment
3675
- // Otherwise schema and documents will be same for all tests
3676
- if (process.env.NODE_ENV !== 'test' && graphqlConfig) {
3677
- return graphqlConfig;
3678
- }
3679
- const onDiskConfig = options.skipGraphQLConfig
3680
- ? null
3681
- : graphqlConfig$1.loadConfigSync({
3682
- throwOnEmpty: false,
3683
- throwOnMissing: false,
3684
- extensions: [addCodeFileLoaderExtension],
3685
- });
3686
- graphqlConfig =
3687
- onDiskConfig ||
3688
- new graphqlConfig$1.GraphQLConfig({
3689
- config: options.projects
3690
- ? { projects: options.projects }
3691
- : {
3692
- schema: (options.schema || ''),
3693
- documents: options.documents || options.operations,
3694
- extensions: options.extensions,
3695
- include: options.include,
3696
- exclude: options.exclude,
3697
- },
3698
- filepath: 'virtual-config',
3699
- }, [addCodeFileLoaderExtension]);
3700
- return graphqlConfig;
3701
- }
3702
- const addCodeFileLoaderExtension = api => {
3703
- api.loaders.schema.register(new codeFileLoader.CodeFileLoader());
3704
- api.loaders.documents.register(new codeFileLoader.CodeFileLoader());
3705
- return { name: 'graphql-eslint-loaders' };
3706
- };
3707
-
3708
3778
  let reachableTypesCache;
3709
3779
  function getReachableTypes(schema) {
3710
3780
  // We don't want cache reachableTypes on test environment
@@ -3787,7 +3857,7 @@ function parse(code, options) {
3787
3857
  return parseForESLint(code, options).ast;
3788
3858
  }
3789
3859
  function parseForESLint(code, options = {}) {
3790
- const gqlConfig = loadGraphqlConfig(options);
3860
+ const gqlConfig = loadCachedGraphQLConfig(options);
3791
3861
  const schema = getSchema(options, gqlConfig);
3792
3862
  const parserServices = {
3793
3863
  hasTypeInfo: schema !== null,