@graphql-eslint/eslint-plugin 3.3.0-alpha-0df1b98.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.
@@ -23,7 +23,7 @@ type User {
23
23
  }
24
24
 
25
25
  # Query
26
- query {
26
+ query user {
27
27
  user {
28
28
  name
29
29
  }
@@ -42,7 +42,7 @@ type User {
42
42
  }
43
43
 
44
44
  # Query
45
- query {
45
+ query user {
46
46
  user {
47
47
  id
48
48
  name
@@ -1,3 +1,4 @@
1
1
  import { GraphQLConfig } from 'graphql-config';
2
2
  import { ParserOptions } from './types';
3
- export declare function loadGraphqlConfig(options: ParserOptions): GraphQLConfig;
3
+ export declare function loadCachedGraphQLConfig(options: ParserOptions): GraphQLConfig;
4
+ export declare function loadGraphQLConfig(options: ParserOptions): GraphQLConfig;
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);
@@ -326,14 +393,14 @@ function getLocation(loc, fieldName = '', offset) {
326
393
  };
327
394
  }
328
395
 
329
- function validateDocument(sourceNode, context, schema, documentNode, rule) {
396
+ function validateDocument(sourceNode, context, schema = null, documentNode, rule, isSchemaToExtend = false) {
330
397
  if (documentNode.definitions.length === 0) {
331
398
  return;
332
399
  }
333
400
  try {
334
- const validationErrors = schema
401
+ const validationErrors = schema && !isSchemaToExtend
335
402
  ? graphql.validate(schema, documentNode, [rule])
336
- : validate.validateSDL(documentNode, null, [rule]);
403
+ : validate.validateSDL(documentNode, schema, [rule]);
337
404
  for (const error of validationErrors) {
338
405
  /*
339
406
  * TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
@@ -440,18 +507,24 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
440
507
  },
441
508
  },
442
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
+ }
443
522
  return {
444
523
  Document(node) {
445
- if (!ruleFn) {
446
- // eslint-disable-next-line no-console
447
- 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...`);
448
- return;
449
- }
450
- const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
451
524
  const documentNode = getDocumentNode
452
525
  ? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
453
526
  : node.rawNode();
454
- validateDocument(node, context, schema, documentNode, ruleFn);
527
+ validateDocument(node, context, schema, documentNode, ruleFn, docs.requiresSchemaToExtend);
455
528
  },
456
529
  };
457
530
  },
@@ -601,7 +674,8 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
601
674
  }), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
602
675
  category: 'Schema',
603
676
  description: `A type extension is only valid if the type is defined and has the same kind.`,
604
- recommended: false, // TODO: enable after https://github.com/dotansimha/graphql-eslint/issues/787 will be fixed
677
+ recommended: false,
678
+ requiresSchemaToExtend: true,
605
679
  }), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
606
680
  category: ['Schema', 'Operations'],
607
681
  description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
@@ -2866,8 +2940,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2866
2940
  }
2867
2941
  };
2868
2942
 
2869
- const RULE_ID$2 = 'require-id-when-available';
2870
- const MESSAGE_ID = 'REQUIRE_ID_WHEN_AVAILABLE';
2943
+ const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2871
2944
  const DEFAULT_ID_FIELD_NAME = 'id';
2872
2945
  const rule$j = {
2873
2946
  meta: {
@@ -2875,7 +2948,7 @@ const rule$j = {
2875
2948
  docs: {
2876
2949
  category: 'Operations',
2877
2950
  description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
2878
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
2951
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-id-when-available.md',
2879
2952
  requiresSchema: true,
2880
2953
  requiresSiblings: true,
2881
2954
  examples: [
@@ -2889,7 +2962,7 @@ const rule$j = {
2889
2962
  }
2890
2963
 
2891
2964
  # Query
2892
- query {
2965
+ query user {
2893
2966
  user {
2894
2967
  name
2895
2968
  }
@@ -2906,7 +2979,7 @@ const rule$j = {
2906
2979
  }
2907
2980
 
2908
2981
  # Query
2909
- query {
2982
+ query user {
2910
2983
  user {
2911
2984
  id
2912
2985
  name
@@ -2918,7 +2991,7 @@ const rule$j = {
2918
2991
  recommended: true,
2919
2992
  },
2920
2993
  messages: {
2921
- [MESSAGE_ID]: [
2994
+ [REQUIRE_ID_WHEN_AVAILABLE]: [
2922
2995
  `Field {{ fieldName }} must be selected when it's available on a type. Please make sure to include it in your selection set!`,
2923
2996
  `If you are using fragments, make sure that all used fragments {{ checkedFragments }}specifies the field {{ fieldName }}.`,
2924
2997
  ].join('\n'),
@@ -2949,16 +3022,14 @@ const rule$j = {
2949
3022
  },
2950
3023
  },
2951
3024
  create(context) {
2952
- requireGraphQLSchemaFromContext(RULE_ID$2, context);
2953
- const siblings = requireSiblingsOperations(RULE_ID$2, context);
3025
+ requireGraphQLSchemaFromContext('require-id-when-available', context);
3026
+ const siblings = requireSiblingsOperations('require-id-when-available', context);
2954
3027
  const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
2955
- const idNames = utils.asArray(fieldName);
3028
+ const idNames = Array.isArray(fieldName) ? fieldName : [fieldName];
2956
3029
  const isFound = (s) => s.kind === graphql.Kind.FIELD && idNames.includes(s.name.value);
2957
- // Skip check selections in FragmentDefinition
2958
- const selector = 'OperationDefinition SelectionSet[parent.kind!=OperationDefinition]';
2959
3030
  return {
2960
- [selector](node) {
2961
- var _a;
3031
+ SelectionSet(node) {
3032
+ var _a, _b;
2962
3033
  const typeInfo = node.typeInfo();
2963
3034
  if (!typeInfo.gqlType) {
2964
3035
  return;
@@ -2975,38 +3046,41 @@ const rule$j = {
2975
3046
  return;
2976
3047
  }
2977
3048
  const checkedFragmentSpreads = new Set();
3049
+ let found = false;
2978
3050
  for (const selection of node.selections) {
2979
3051
  if (isFound(selection)) {
2980
- return;
3052
+ found = true;
2981
3053
  }
2982
- if (selection.kind === graphql.Kind.INLINE_FRAGMENT && selection.selectionSet.selections.some(isFound)) {
2983
- return;
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));
2984
3056
  }
2985
- if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
3057
+ else if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
2986
3058
  const [foundSpread] = siblings.getFragment(selection.name.value);
2987
3059
  if (foundSpread) {
2988
3060
  checkedFragmentSpreads.add(foundSpread.document.name.value);
2989
- if (foundSpread.document.selectionSet.selections.some(isFound)) {
2990
- return;
2991
- }
3061
+ found = (_b = foundSpread.document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections.some(s => isFound(s));
2992
3062
  }
2993
3063
  }
3064
+ if (found) {
3065
+ break;
3066
+ }
2994
3067
  }
2995
3068
  const { parent } = node;
2996
- const hasIdFieldInInterfaceSelectionSet = (parent === null || parent === void 0 ? void 0 : parent.kind) === graphql.Kind.INLINE_FRAGMENT &&
2997
- ((_a = parent.parent) === null || _a === void 0 ? void 0 : _a.kind) === graphql.Kind.SELECTION_SET &&
2998
- parent.parent.selections.some(isFound);
2999
- if (hasIdFieldInInterfaceSelectionSet) {
3000
- return;
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
+ });
3001
3083
  }
3002
- context.report({
3003
- loc: getLocation(node.loc),
3004
- messageId: MESSAGE_ID,
3005
- data: {
3006
- checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${[...checkedFragmentSpreads].join(', ')}) `,
3007
- fieldName: idNames.map(name => `"${name}"`).join(' or '),
3008
- },
3009
- });
3010
3084
  },
3011
3085
  };
3012
3086
  },
@@ -3701,42 +3775,6 @@ function getSiblingOperations(options, gqlConfig) {
3701
3775
  return siblingOperations;
3702
3776
  }
3703
3777
 
3704
- let graphqlConfig;
3705
- function loadGraphqlConfig(options) {
3706
- // We don't want cache config on test environment
3707
- // Otherwise schema and documents will be same for all tests
3708
- if (process.env.NODE_ENV !== 'test' && graphqlConfig) {
3709
- return graphqlConfig;
3710
- }
3711
- const onDiskConfig = options.skipGraphQLConfig
3712
- ? null
3713
- : graphqlConfig$1.loadConfigSync({
3714
- throwOnEmpty: false,
3715
- throwOnMissing: false,
3716
- extensions: [addCodeFileLoaderExtension],
3717
- });
3718
- graphqlConfig =
3719
- onDiskConfig ||
3720
- new graphqlConfig$1.GraphQLConfig({
3721
- config: options.projects
3722
- ? { projects: options.projects }
3723
- : {
3724
- schema: (options.schema || ''),
3725
- documents: options.documents || options.operations,
3726
- extensions: options.extensions,
3727
- include: options.include,
3728
- exclude: options.exclude,
3729
- },
3730
- filepath: 'virtual-config',
3731
- }, [addCodeFileLoaderExtension]);
3732
- return graphqlConfig;
3733
- }
3734
- const addCodeFileLoaderExtension = api => {
3735
- api.loaders.schema.register(new codeFileLoader.CodeFileLoader());
3736
- api.loaders.documents.register(new codeFileLoader.CodeFileLoader());
3737
- return { name: 'graphql-eslint-loaders' };
3738
- };
3739
-
3740
3778
  let reachableTypesCache;
3741
3779
  function getReachableTypes(schema) {
3742
3780
  // We don't want cache reachableTypes on test environment
@@ -3819,7 +3857,7 @@ function parse(code, options) {
3819
3857
  return parseForESLint(code, options).ast;
3820
3858
  }
3821
3859
  function parseForESLint(code, options = {}) {
3822
- const gqlConfig = loadGraphqlConfig(options);
3860
+ const gqlConfig = loadCachedGraphQLConfig(options);
3823
3861
  const schema = getSchema(options, gqlConfig);
3824
3862
  const parserServices = {
3825
3863
  hasTypeInfo: schema !== null,
package/index.mjs CHANGED
@@ -4,10 +4,10 @@ import { statSync, existsSync, readFileSync } from 'fs';
4
4
  import { dirname, extname, basename, relative, resolve } from 'path';
5
5
  import { asArray, parseGraphQLSDL } from '@graphql-tools/utils';
6
6
  import lowerCase from 'lodash.lowercase';
7
- import depthLimit from 'graphql-depth-limit';
8
- import { parseCode } from '@graphql-tools/graphql-tag-pluck';
9
7
  import { loadConfigSync, GraphQLConfig } from 'graphql-config';
10
8
  import { CodeFileLoader } from '@graphql-tools/code-file-loader';
9
+ import depthLimit from 'graphql-depth-limit';
10
+ import { parseCode } from '@graphql-tools/graphql-tag-pluck';
11
11
  import { RuleTester, Linter } from 'eslint';
12
12
  import { codeFrameColumns } from '@babel/code-frame';
13
13
 
@@ -168,6 +168,47 @@ const configs = {
168
168
  'operations-all': operationsAllConfig,
169
169
  };
170
170
 
171
+ let graphQLConfig;
172
+ function loadCachedGraphQLConfig(options) {
173
+ // We don't want cache config on test environment
174
+ // Otherwise schema and documents will be same for all tests
175
+ if (process.env.NODE_ENV !== 'test' && graphQLConfig) {
176
+ return graphQLConfig;
177
+ }
178
+ graphQLConfig = loadGraphQLConfig(options);
179
+ return graphQLConfig;
180
+ }
181
+ function loadGraphQLConfig(options) {
182
+ const onDiskConfig = options.skipGraphQLConfig
183
+ ? null
184
+ : loadConfigSync({
185
+ throwOnEmpty: false,
186
+ throwOnMissing: false,
187
+ extensions: [addCodeFileLoaderExtension],
188
+ });
189
+ const configOptions = options.projects
190
+ ? { projects: options.projects }
191
+ : {
192
+ schema: (options.schema || ''),
193
+ documents: options.documents || options.operations,
194
+ extensions: options.extensions,
195
+ include: options.include,
196
+ exclude: options.exclude,
197
+ };
198
+ graphQLConfig =
199
+ onDiskConfig ||
200
+ new GraphQLConfig({
201
+ config: configOptions,
202
+ filepath: 'virtual-config',
203
+ }, [addCodeFileLoaderExtension]);
204
+ return graphQLConfig;
205
+ }
206
+ const addCodeFileLoaderExtension = api => {
207
+ api.loaders.schema.register(new CodeFileLoader());
208
+ api.loaders.documents.register(new CodeFileLoader());
209
+ return { name: 'graphql-eslint-loaders' };
210
+ };
211
+
171
212
  function requireSiblingsOperations(ruleName, context) {
172
213
  if (!context.parserServices) {
173
214
  throw new Error(`Rule '${ruleName}' requires 'parserOptions.operations' to be set and loaded. See http://bit.ly/graphql-eslint-operations for more info`);
@@ -186,6 +227,32 @@ function requireGraphQLSchemaFromContext(ruleName, context) {
186
227
  }
187
228
  return context.parserServices.schema;
188
229
  }
230
+ const schemaToExtendCache = new Map();
231
+ function getGraphQLSchemaToExtend(context) {
232
+ // If parserOptions.schema not set or not loaded, there is no reason to make partial schema aka schemaToExtend
233
+ if (!context.parserServices.hasTypeInfo) {
234
+ return null;
235
+ }
236
+ const filename = context.getPhysicalFilename();
237
+ if (!schemaToExtendCache.has(filename)) {
238
+ const { schema, schemaOptions } = context.parserOptions;
239
+ const gqlConfig = loadGraphQLConfig({ schema });
240
+ const projectForFile = gqlConfig.getProjectForFile(filename);
241
+ let schemaToExtend;
242
+ try {
243
+ schemaToExtend = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', {
244
+ ...schemaOptions,
245
+ ignore: filename,
246
+ });
247
+ }
248
+ catch (_a) {
249
+ // If error throws just ignore it because maybe schema is located in 1 file
250
+ schemaToExtend = null;
251
+ }
252
+ schemaToExtendCache.set(filename, schemaToExtend);
253
+ }
254
+ return schemaToExtendCache.get(filename);
255
+ }
189
256
  function requireReachableTypesFromContext(ruleName, context) {
190
257
  const schema = requireGraphQLSchemaFromContext(ruleName, context);
191
258
  return context.parserServices.reachableTypes(schema);
@@ -320,14 +387,14 @@ function getLocation(loc, fieldName = '', offset) {
320
387
  };
321
388
  }
322
389
 
323
- function validateDocument(sourceNode, context, schema, documentNode, rule) {
390
+ function validateDocument(sourceNode, context, schema = null, documentNode, rule, isSchemaToExtend = false) {
324
391
  if (documentNode.definitions.length === 0) {
325
392
  return;
326
393
  }
327
394
  try {
328
- const validationErrors = schema
395
+ const validationErrors = schema && !isSchemaToExtend
329
396
  ? validate(schema, documentNode, [rule])
330
- : validateSDL(documentNode, null, [rule]);
397
+ : validateSDL(documentNode, schema, [rule]);
331
398
  for (const error of validationErrors) {
332
399
  /*
333
400
  * TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
@@ -434,18 +501,24 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
434
501
  },
435
502
  },
436
503
  create(context) {
504
+ if (!ruleFn) {
505
+ // eslint-disable-next-line no-console
506
+ 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...`);
507
+ return {};
508
+ }
509
+ let schema;
510
+ if (docs.requiresSchemaToExtend) {
511
+ schema = getGraphQLSchemaToExtend(context);
512
+ }
513
+ if (docs.requiresSchema) {
514
+ schema = requireGraphQLSchemaFromContext(ruleId, context);
515
+ }
437
516
  return {
438
517
  Document(node) {
439
- if (!ruleFn) {
440
- // eslint-disable-next-line no-console
441
- 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...`);
442
- return;
443
- }
444
- const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
445
518
  const documentNode = getDocumentNode
446
519
  ? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
447
520
  : node.rawNode();
448
- validateDocument(node, context, schema, documentNode, ruleFn);
521
+ validateDocument(node, context, schema, documentNode, ruleFn, docs.requiresSchemaToExtend);
449
522
  },
450
523
  };
451
524
  },
@@ -595,7 +668,8 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
595
668
  }), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
596
669
  category: 'Schema',
597
670
  description: `A type extension is only valid if the type is defined and has the same kind.`,
598
- recommended: false, // TODO: enable after https://github.com/dotansimha/graphql-eslint/issues/787 will be fixed
671
+ recommended: false,
672
+ requiresSchemaToExtend: true,
599
673
  }), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
600
674
  category: ['Schema', 'Operations'],
601
675
  description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
@@ -2860,8 +2934,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2860
2934
  }
2861
2935
  };
2862
2936
 
2863
- const RULE_ID$2 = 'require-id-when-available';
2864
- const MESSAGE_ID = 'REQUIRE_ID_WHEN_AVAILABLE';
2937
+ const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2865
2938
  const DEFAULT_ID_FIELD_NAME = 'id';
2866
2939
  const rule$j = {
2867
2940
  meta: {
@@ -2869,7 +2942,7 @@ const rule$j = {
2869
2942
  docs: {
2870
2943
  category: 'Operations',
2871
2944
  description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
2872
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
2945
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-id-when-available.md',
2873
2946
  requiresSchema: true,
2874
2947
  requiresSiblings: true,
2875
2948
  examples: [
@@ -2883,7 +2956,7 @@ const rule$j = {
2883
2956
  }
2884
2957
 
2885
2958
  # Query
2886
- query {
2959
+ query user {
2887
2960
  user {
2888
2961
  name
2889
2962
  }
@@ -2900,7 +2973,7 @@ const rule$j = {
2900
2973
  }
2901
2974
 
2902
2975
  # Query
2903
- query {
2976
+ query user {
2904
2977
  user {
2905
2978
  id
2906
2979
  name
@@ -2912,7 +2985,7 @@ const rule$j = {
2912
2985
  recommended: true,
2913
2986
  },
2914
2987
  messages: {
2915
- [MESSAGE_ID]: [
2988
+ [REQUIRE_ID_WHEN_AVAILABLE]: [
2916
2989
  `Field {{ fieldName }} must be selected when it's available on a type. Please make sure to include it in your selection set!`,
2917
2990
  `If you are using fragments, make sure that all used fragments {{ checkedFragments }}specifies the field {{ fieldName }}.`,
2918
2991
  ].join('\n'),
@@ -2943,16 +3016,14 @@ const rule$j = {
2943
3016
  },
2944
3017
  },
2945
3018
  create(context) {
2946
- requireGraphQLSchemaFromContext(RULE_ID$2, context);
2947
- const siblings = requireSiblingsOperations(RULE_ID$2, context);
3019
+ requireGraphQLSchemaFromContext('require-id-when-available', context);
3020
+ const siblings = requireSiblingsOperations('require-id-when-available', context);
2948
3021
  const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
2949
- const idNames = asArray(fieldName);
3022
+ const idNames = Array.isArray(fieldName) ? fieldName : [fieldName];
2950
3023
  const isFound = (s) => s.kind === Kind.FIELD && idNames.includes(s.name.value);
2951
- // Skip check selections in FragmentDefinition
2952
- const selector = 'OperationDefinition SelectionSet[parent.kind!=OperationDefinition]';
2953
3024
  return {
2954
- [selector](node) {
2955
- var _a;
3025
+ SelectionSet(node) {
3026
+ var _a, _b;
2956
3027
  const typeInfo = node.typeInfo();
2957
3028
  if (!typeInfo.gqlType) {
2958
3029
  return;
@@ -2969,38 +3040,41 @@ const rule$j = {
2969
3040
  return;
2970
3041
  }
2971
3042
  const checkedFragmentSpreads = new Set();
3043
+ let found = false;
2972
3044
  for (const selection of node.selections) {
2973
3045
  if (isFound(selection)) {
2974
- return;
3046
+ found = true;
2975
3047
  }
2976
- if (selection.kind === Kind.INLINE_FRAGMENT && selection.selectionSet.selections.some(isFound)) {
2977
- return;
3048
+ else if (selection.kind === Kind.INLINE_FRAGMENT) {
3049
+ found = (_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections.some(s => isFound(s));
2978
3050
  }
2979
- if (selection.kind === Kind.FRAGMENT_SPREAD) {
3051
+ else if (selection.kind === Kind.FRAGMENT_SPREAD) {
2980
3052
  const [foundSpread] = siblings.getFragment(selection.name.value);
2981
3053
  if (foundSpread) {
2982
3054
  checkedFragmentSpreads.add(foundSpread.document.name.value);
2983
- if (foundSpread.document.selectionSet.selections.some(isFound)) {
2984
- return;
2985
- }
3055
+ found = (_b = foundSpread.document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections.some(s => isFound(s));
2986
3056
  }
2987
3057
  }
3058
+ if (found) {
3059
+ break;
3060
+ }
2988
3061
  }
2989
3062
  const { parent } = node;
2990
- const hasIdFieldInInterfaceSelectionSet = (parent === null || parent === void 0 ? void 0 : parent.kind) === Kind.INLINE_FRAGMENT &&
2991
- ((_a = parent.parent) === null || _a === void 0 ? void 0 : _a.kind) === Kind.SELECTION_SET &&
2992
- parent.parent.selections.some(isFound);
2993
- if (hasIdFieldInInterfaceSelectionSet) {
2994
- return;
3063
+ const hasIdFieldInInterfaceSelectionSet = parent &&
3064
+ parent.kind === Kind.INLINE_FRAGMENT &&
3065
+ parent.parent &&
3066
+ parent.parent.kind === Kind.SELECTION_SET &&
3067
+ parent.parent.selections.some(s => isFound(s));
3068
+ if (!found && !hasIdFieldInInterfaceSelectionSet) {
3069
+ context.report({
3070
+ loc: getLocation(node.loc),
3071
+ messageId: REQUIRE_ID_WHEN_AVAILABLE,
3072
+ data: {
3073
+ checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${[...checkedFragmentSpreads].join(', ')}) `,
3074
+ fieldName: idNames.map(name => `"${name}"`).join(' or '),
3075
+ },
3076
+ });
2995
3077
  }
2996
- context.report({
2997
- loc: getLocation(node.loc),
2998
- messageId: MESSAGE_ID,
2999
- data: {
3000
- checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${[...checkedFragmentSpreads].join(', ')}) `,
3001
- fieldName: idNames.map(name => `"${name}"`).join(' or '),
3002
- },
3003
- });
3004
3078
  },
3005
3079
  };
3006
3080
  },
@@ -3695,42 +3769,6 @@ function getSiblingOperations(options, gqlConfig) {
3695
3769
  return siblingOperations;
3696
3770
  }
3697
3771
 
3698
- let graphqlConfig;
3699
- function loadGraphqlConfig(options) {
3700
- // We don't want cache config on test environment
3701
- // Otherwise schema and documents will be same for all tests
3702
- if (process.env.NODE_ENV !== 'test' && graphqlConfig) {
3703
- return graphqlConfig;
3704
- }
3705
- const onDiskConfig = options.skipGraphQLConfig
3706
- ? null
3707
- : loadConfigSync({
3708
- throwOnEmpty: false,
3709
- throwOnMissing: false,
3710
- extensions: [addCodeFileLoaderExtension],
3711
- });
3712
- graphqlConfig =
3713
- onDiskConfig ||
3714
- new GraphQLConfig({
3715
- config: options.projects
3716
- ? { projects: options.projects }
3717
- : {
3718
- schema: (options.schema || ''),
3719
- documents: options.documents || options.operations,
3720
- extensions: options.extensions,
3721
- include: options.include,
3722
- exclude: options.exclude,
3723
- },
3724
- filepath: 'virtual-config',
3725
- }, [addCodeFileLoaderExtension]);
3726
- return graphqlConfig;
3727
- }
3728
- const addCodeFileLoaderExtension = api => {
3729
- api.loaders.schema.register(new CodeFileLoader());
3730
- api.loaders.documents.register(new CodeFileLoader());
3731
- return { name: 'graphql-eslint-loaders' };
3732
- };
3733
-
3734
3772
  let reachableTypesCache;
3735
3773
  function getReachableTypes(schema) {
3736
3774
  // We don't want cache reachableTypes on test environment
@@ -3813,7 +3851,7 @@ function parse(code, options) {
3813
3851
  return parseForESLint(code, options).ast;
3814
3852
  }
3815
3853
  function parseForESLint(code, options = {}) {
3816
- const gqlConfig = loadGraphqlConfig(options);
3854
+ const gqlConfig = loadCachedGraphQLConfig(options);
3817
3855
  const schema = getSchema(options, gqlConfig);
3818
3856
  const parserServices = {
3819
3857
  hasTypeInfo: schema !== null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "3.3.0-alpha-0df1b98.0",
3
+ "version": "3.3.0-alpha-b07557f.0",
4
4
  "description": "GraphQL plugin for ESLint",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
package/rules/index.d.ts CHANGED
@@ -48,7 +48,9 @@ export declare const rules: {
48
48
  DirectiveDefinition?: boolean;
49
49
  }], false>;
50
50
  'require-field-of-type-query-in-mutation-result': import("..").GraphQLESLintRule<any[], false>;
51
- 'require-id-when-available': import("..").GraphQLESLintRule<[import("./require-id-when-available").RequireIdWhenAvailableRuleConfig], true>;
51
+ 'require-id-when-available': import("..").GraphQLESLintRule<[{
52
+ fieldName: string;
53
+ }], true>;
52
54
  'selection-set-depth': import("..").GraphQLESLintRule<[{
53
55
  maxDepth: number;
54
56
  ignore?: string[];
@@ -1,6 +1,6 @@
1
1
  import { GraphQLESLintRule } from '../types';
2
- export declare type RequireIdWhenAvailableRuleConfig = {
3
- fieldName: string | string[];
2
+ declare type RequireIdWhenAvailableRuleConfig = {
3
+ fieldName: string;
4
4
  };
5
5
  declare const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true>;
6
6
  export default rule;
package/types.d.ts CHANGED
@@ -51,6 +51,7 @@ export declare type RuleDocsInfo<T> = {
51
51
  category: CategoryType | CategoryType[];
52
52
  requiresSchema?: true;
53
53
  requiresSiblings?: true;
54
+ requiresSchemaToExtend?: true;
54
55
  examples?: {
55
56
  title: string;
56
57
  code: string;
package/utils.d.ts CHANGED
@@ -6,6 +6,7 @@ import { SiblingOperations } from './sibling-operations';
6
6
  import { UsedFields, ReachableTypes } from './graphql-ast';
7
7
  export declare function requireSiblingsOperations(ruleName: string, context: GraphQLESLintRuleContext): SiblingOperations | never;
8
8
  export declare function requireGraphQLSchemaFromContext(ruleName: string, context: GraphQLESLintRuleContext): GraphQLSchema | never;
9
+ export declare function getGraphQLSchemaToExtend(context: GraphQLESLintRuleContext): GraphQLSchema | null;
9
10
  export declare function requireReachableTypesFromContext(ruleName: string, context: GraphQLESLintRuleContext): ReachableTypes | never;
10
11
  export declare function requireUsedFieldsFromContext(ruleName: string, context: GraphQLESLintRuleContext): UsedFields | never;
11
12
  export declare function extractTokens(source: Source): AST.Token[];