@graphql-eslint/eslint-plugin 4.1.0-alpha-20241128213211-47212e29bd7289aab6adc9e573221d04e2818bac → 4.1.0-alpha-20241129082104-137773e2da031621a4bbb9323212b09179e1076a

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/cjs/index.d.cts CHANGED
@@ -98,10 +98,10 @@ declare const _default: {
98
98
  style?: ("camelCase" | "PascalCase" | "snake_case" | "UPPER_CASE") | undefined;
99
99
  suffix?: string | undefined;
100
100
  prefix?: string | undefined;
101
- forbiddenPattern?: {
101
+ forbiddenPatterns?: {
102
102
  [x: string]: unknown;
103
103
  }[] | undefined;
104
- requiredPattern?: {
104
+ requiredPatterns?: {
105
105
  [x: string]: unknown;
106
106
  }[] | undefined;
107
107
  forbiddenPrefixes?: string[] | undefined;
package/cjs/meta.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});const version = "4.1.0-alpha-20241128213211-47212e29bd7289aab6adc9e573221d04e2818bac";
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});const version = "4.1.0-alpha-20241129082104-137773e2da031621a4bbb9323212b09179e1076a";
2
2
 
3
3
 
4
4
  exports.version = version;
@@ -61,10 +61,10 @@ declare const rules: {
61
61
  style?: ("camelCase" | "PascalCase" | "snake_case" | "UPPER_CASE") | undefined;
62
62
  suffix?: string | undefined;
63
63
  prefix?: string | undefined;
64
- forbiddenPattern?: {
64
+ forbiddenPatterns?: {
65
65
  [x: string]: unknown;
66
66
  }[] | undefined;
67
- requiredPattern?: {
67
+ requiredPatterns?: {
68
68
  [x: string]: unknown;
69
69
  }[] | undefined;
70
70
  forbiddenPrefixes?: string[] | undefined;
@@ -28,7 +28,7 @@ declare const schema: {
28
28
  readonly suffix: {
29
29
  readonly type: "string";
30
30
  };
31
- readonly forbiddenPattern: {
31
+ readonly forbiddenPatterns: {
32
32
  readonly items: {
33
33
  readonly type: "object";
34
34
  };
@@ -37,7 +37,7 @@ declare const schema: {
37
37
  readonly uniqueItems: true;
38
38
  readonly minItems: 1;
39
39
  };
40
- readonly requiredPattern: {
40
+ readonly requiredPatterns: {
41
41
  readonly items: {
42
42
  readonly type: "object";
43
43
  };
@@ -47,14 +47,14 @@ const KindToDisplayName = {
47
47
  style: { enum: ALLOWED_STYLES },
48
48
  prefix: { type: "string" },
49
49
  suffix: { type: "string" },
50
- forbiddenPattern: {
50
+ forbiddenPatterns: {
51
51
  ..._utilsjs.ARRAY_DEFAULT_OPTIONS,
52
52
  items: {
53
53
  type: "object"
54
54
  },
55
55
  description: "Should be of instance of `RegEx`"
56
56
  },
57
- requiredPattern: {
57
+ requiredPatterns: {
58
58
  ..._utilsjs.ARRAY_DEFAULT_OPTIONS,
59
59
  items: {
60
60
  type: "object"
@@ -63,19 +63,19 @@ const KindToDisplayName = {
63
63
  },
64
64
  forbiddenPrefixes: {
65
65
  ..._utilsjs.ARRAY_DEFAULT_OPTIONS,
66
- description: descriptionPrefixesSuffixes("forbiddenPattern")
66
+ description: descriptionPrefixesSuffixes("forbiddenPatterns")
67
67
  },
68
68
  forbiddenSuffixes: {
69
69
  ..._utilsjs.ARRAY_DEFAULT_OPTIONS,
70
- description: descriptionPrefixesSuffixes("forbiddenPattern")
70
+ description: descriptionPrefixesSuffixes("forbiddenPatterns")
71
71
  },
72
72
  requiredPrefixes: {
73
73
  ..._utilsjs.ARRAY_DEFAULT_OPTIONS,
74
- description: descriptionPrefixesSuffixes("requiredPattern")
74
+ description: descriptionPrefixesSuffixes("requiredPatterns")
75
75
  },
76
76
  requiredSuffixes: {
77
77
  ..._utilsjs.ARRAY_DEFAULT_OPTIONS,
78
- description: descriptionPrefixesSuffixes("requiredPattern")
78
+ description: descriptionPrefixesSuffixes("requiredPatterns")
79
79
  },
80
80
  ignorePattern: {
81
81
  type: "string",
@@ -101,7 +101,9 @@ ${_utilsjs.TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
101
101
  kind,
102
102
  {
103
103
  ...schemaOption,
104
- description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`
104
+ description: `> [!NOTE]
105
+ >
106
+ > Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`
105
107
  }
106
108
  ])
107
109
  ),
@@ -356,8 +358,8 @@ ${_utilsjs.TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
356
358
  ignorePattern,
357
359
  requiredPrefixes,
358
360
  requiredSuffixes,
359
- forbiddenPattern,
360
- requiredPattern
361
+ forbiddenPatterns,
362
+ requiredPatterns
361
363
  } = normalisePropertyOption(selector), nodeName = node.value, error = getError();
362
364
  if (error) {
363
365
  const { errorMessage, renameToNames } = error, [leadingUnderscores] = nodeName.match(/^_*/), [trailingUnderscores] = nodeName.match(/_*$/), suggestedNames = renameToNames.map(
@@ -383,15 +385,15 @@ ${_utilsjs.TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
383
385
  errorMessage: `have "${suffix}" suffix`,
384
386
  renameToNames: [name + suffix]
385
387
  };
386
- const forbidden = _optionalChain([forbiddenPattern, 'optionalAccess', _ => _.find, 'call', _2 => _2((pattern) => pattern.test(name))]);
388
+ const forbidden = _optionalChain([forbiddenPatterns, 'optionalAccess', _ => _.find, 'call', _2 => _2((pattern) => pattern.test(name))]);
387
389
  if (forbidden)
388
390
  return {
389
391
  errorMessage: `not contain the forbidden pattern "${forbidden}"`,
390
392
  renameToNames: [name.replace(forbidden, "")]
391
393
  };
392
- if (requiredPattern && !requiredPattern.some((pattern) => pattern.test(name)))
394
+ if (requiredPatterns && !requiredPatterns.some((pattern) => pattern.test(name)))
393
395
  return {
394
- errorMessage: `contain the required pattern: ${_utilsjs.englishJoinWords.call(void 0, requiredPattern.map((re) => re.source))}`,
396
+ errorMessage: `contain the required pattern: ${_utilsjs.englishJoinWords.call(void 0, requiredPatterns.map((re) => re.source))}`,
395
397
  renameToNames: []
396
398
  };
397
399
  const forbiddenPrefix = _optionalChain([forbiddenPrefixes, 'optionalAccess', _3 => _3.find, 'call', _4 => _4((prefix2) => name.startsWith(prefix2))]);
@@ -82,9 +82,7 @@ const RULE_ID = "no-unused-fields", RELAY_SCHEMA = (
82
82
  "```json",
83
83
  JSON.stringify(RELAY_DEFAULT_IGNORED_FIELD_SELECTORS, null, 2),
84
84
  "```",
85
- "",
86
- "> These fields are defined by ESLint [`selectors`](https://eslint.org/docs/developer-guide/selectors).",
87
- "> Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector."
85
+ _utilsjs.eslintSelectorsTip
88
86
  ].join(`
89
87
  `),
90
88
  items: {
@@ -15,8 +15,9 @@ type RuleOptions = [
15
15
  {
16
16
  [key in AllowedKind]?: boolean;
17
17
  } & {
18
- types?: boolean;
19
- rootField?: boolean;
18
+ types?: true;
19
+ rootField?: true;
20
+ ignoredSelectors?: string[];
20
21
  }
21
22
  ];
22
23
  declare const rule: GraphQLESLintRule<RuleOptions>;
@@ -1,5 +1,12 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _graphql = require('graphql');
2
2
  var _utils = require('@graphql-tools/utils');
3
+
4
+
5
+
6
+
7
+
8
+
9
+
3
10
  var _utilsjs = require('../../utils.js');
4
11
  const RULE_ID = "require-description", ALLOWED_KINDS = [
5
12
  ..._utilsjs.TYPES_KINDS,
@@ -19,18 +26,34 @@ const RULE_ID = "require-description", ALLOWED_KINDS = [
19
26
  properties: {
20
27
  types: {
21
28
  type: "boolean",
29
+ enum: [!0],
22
30
  description: `Includes:
23
31
  ${_utilsjs.TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
24
32
  `)}`
25
33
  },
26
34
  rootField: {
27
35
  type: "boolean",
36
+ enum: [!0],
28
37
  description: "Definitions within `Query`, `Mutation`, and `Subscription` root types."
29
38
  },
39
+ ignoredSelectors: {
40
+ ..._utilsjs.ARRAY_DEFAULT_OPTIONS,
41
+ description: ["Ignore specific selectors", _utilsjs.eslintSelectorsTip].join(`
42
+ `)
43
+ },
30
44
  ...Object.fromEntries(
31
45
  [...ALLOWED_KINDS].sort().map((kind) => {
32
- let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
33
- return kind === _graphql.Kind.OPERATION_DEFINITION && (description += '\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.'), [kind, { type: "boolean", description }];
46
+ let description = `> [!NOTE]
47
+ >
48
+ > Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
49
+ return kind === _graphql.Kind.OPERATION_DEFINITION && (description += [
50
+ "",
51
+ "",
52
+ "> [!WARNING]",
53
+ ">",
54
+ '> You must use only comment syntax `#` and not description syntax `"""` or `"`.'
55
+ ].join(`
56
+ `)), [kind, { type: "boolean", description }];
34
57
  })
35
58
  )
36
59
  }
@@ -101,6 +124,36 @@ ${_utilsjs.TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
101
124
  }
102
125
  `
103
126
  )
127
+ },
128
+ {
129
+ title: "Correct",
130
+ usage: [
131
+ {
132
+ ignoredSelectors: [
133
+ "[type=ObjectTypeDefinition][name.value=PageInfo]",
134
+ "[type=ObjectTypeDefinition][name.value=/(Connection|Edge)$/]"
135
+ ]
136
+ }
137
+ ],
138
+ code: (
139
+ /* GraphQL */
140
+ `
141
+ type FriendConnection {
142
+ edges: [FriendEdge]
143
+ pageInfo: PageInfo!
144
+ }
145
+ type FriendEdge {
146
+ cursor: String!
147
+ node: Friend!
148
+ }
149
+ type PageInfo {
150
+ hasPreviousPage: Boolean!
151
+ hasNextPage: Boolean!
152
+ startCursor: String
153
+ endCursor: String
154
+ }
155
+ `
156
+ )
104
157
  }
105
158
  ],
106
159
  configOptions: [
@@ -119,7 +172,8 @@ ${_utilsjs.TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
119
172
  schema
120
173
  },
121
174
  create(context) {
122
- const { types, rootField, ...restOptions } = context.options[0] || {}, kinds = new Set(types ? _utilsjs.TYPES_KINDS : []);
175
+ let { types, rootField, ignoredSelectors = [], ...restOptions } = context.options[0] || {};
176
+ const kinds = new Set(types ? _utilsjs.TYPES_KINDS : []);
123
177
  for (const [kind, isEnabled] of Object.entries(restOptions))
124
178
  isEnabled ? kinds.add(kind) : kinds.delete(kind);
125
179
  if (rootField) {
@@ -130,10 +184,11 @@ ${_utilsjs.TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
130
184
  ].join(",")})$/] > FieldDefinition`
131
185
  );
132
186
  }
133
- if (!kinds.size)
134
- throw new Error("At least one kind must be enabled");
187
+ let selector = `:matches(${[...kinds]})`;
188
+ for (const str of ignoredSelectors)
189
+ selector += `:not(${str})`;
135
190
  return {
136
- [[...kinds].join(",")](node) {
191
+ [selector](node) {
137
192
  let description = "";
138
193
  const isOperation = node.kind === _graphql.Kind.OPERATION_DEFINITION;
139
194
  if (isOperation) {
package/cjs/utils.d.cts CHANGED
@@ -41,5 +41,6 @@ type Truthy<T> = T extends '' | 0 | false | null | undefined ? never : T;
41
41
  declare function truthy<T>(value: T): value is Truthy<T>;
42
42
  declare function displayNodeName(node: GraphQLESTreeNode<ASTNode, boolean>): string;
43
43
  declare function getNodeName(node: GraphQLESTreeNode<ASTNode>): string;
44
+ declare const eslintSelectorsTip = "> [!TIP]\n>\n> These fields are defined by ESLint [`selectors`](https://eslint.org/docs/developer-guide/selectors).\n> Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector.";
44
45
 
45
- export { ARRAY_DEFAULT_OPTIONS, CWD, type CaseStyle, REPORT_ON_FIRST_CHARACTER, TYPES_KINDS, VIRTUAL_DOCUMENT_REGEX, camelCase, convertCase, displayNodeName, englishJoinWords, getLocation, getNodeName, getTypeName, logger, pascalCase, requireGraphQLOperations, requireGraphQLSchema, slash, truthy };
46
+ export { ARRAY_DEFAULT_OPTIONS, CWD, type CaseStyle, REPORT_ON_FIRST_CHARACTER, TYPES_KINDS, VIRTUAL_DOCUMENT_REGEX, camelCase, convertCase, displayNodeName, englishJoinWords, eslintSelectorsTip, getLocation, getNodeName, getTypeName, logger, pascalCase, requireGraphQLOperations, requireGraphQLSchema, slash, truthy };
package/cjs/utils.js CHANGED
@@ -144,6 +144,10 @@ function getNodeName(node) {
144
144
  }
145
145
  return "";
146
146
  }
147
+ const eslintSelectorsTip = `> [!TIP]
148
+ >
149
+ > These fields are defined by ESLint [\`selectors\`](https://eslint.org/docs/developer-guide/selectors).
150
+ > Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector.`;
147
151
 
148
152
 
149
153
 
@@ -163,4 +167,5 @@ function getNodeName(node) {
163
167
 
164
168
 
165
169
 
166
- exports.ARRAY_DEFAULT_OPTIONS = ARRAY_DEFAULT_OPTIONS; exports.CWD = CWD; exports.REPORT_ON_FIRST_CHARACTER = REPORT_ON_FIRST_CHARACTER; exports.TYPES_KINDS = TYPES_KINDS; exports.VIRTUAL_DOCUMENT_REGEX = VIRTUAL_DOCUMENT_REGEX; exports.camelCase = camelCase; exports.convertCase = convertCase; exports.displayNodeName = displayNodeName; exports.englishJoinWords = englishJoinWords; exports.getLocation = getLocation; exports.getNodeName = getNodeName; exports.getTypeName = getTypeName; exports.logger = logger; exports.pascalCase = pascalCase; exports.requireGraphQLOperations = requireGraphQLOperations; exports.requireGraphQLSchema = requireGraphQLSchema; exports.slash = slash; exports.truthy = truthy;
170
+
171
+ exports.ARRAY_DEFAULT_OPTIONS = ARRAY_DEFAULT_OPTIONS; exports.CWD = CWD; exports.REPORT_ON_FIRST_CHARACTER = REPORT_ON_FIRST_CHARACTER; exports.TYPES_KINDS = TYPES_KINDS; exports.VIRTUAL_DOCUMENT_REGEX = VIRTUAL_DOCUMENT_REGEX; exports.camelCase = camelCase; exports.convertCase = convertCase; exports.displayNodeName = displayNodeName; exports.englishJoinWords = englishJoinWords; exports.eslintSelectorsTip = eslintSelectorsTip; exports.getLocation = getLocation; exports.getNodeName = getNodeName; exports.getTypeName = getTypeName; exports.logger = logger; exports.pascalCase = pascalCase; exports.requireGraphQLOperations = requireGraphQLOperations; exports.requireGraphQLSchema = requireGraphQLSchema; exports.slash = slash; exports.truthy = truthy;
package/esm/index.d.ts CHANGED
@@ -98,10 +98,10 @@ declare const _default: {
98
98
  style?: ("camelCase" | "PascalCase" | "snake_case" | "UPPER_CASE") | undefined;
99
99
  suffix?: string | undefined;
100
100
  prefix?: string | undefined;
101
- forbiddenPattern?: {
101
+ forbiddenPatterns?: {
102
102
  [x: string]: unknown;
103
103
  }[] | undefined;
104
- requiredPattern?: {
104
+ requiredPatterns?: {
105
105
  [x: string]: unknown;
106
106
  }[] | undefined;
107
107
  forbiddenPrefixes?: string[] | undefined;
package/esm/meta.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "4.1.0-alpha-20241128213211-47212e29bd7289aab6adc9e573221d04e2818bac";
1
+ const version = "4.1.0-alpha-20241129082104-137773e2da031621a4bbb9323212b09179e1076a";
2
2
  export {
3
3
  version
4
4
  };
@@ -61,10 +61,10 @@ declare const rules: {
61
61
  style?: ("camelCase" | "PascalCase" | "snake_case" | "UPPER_CASE") | undefined;
62
62
  suffix?: string | undefined;
63
63
  prefix?: string | undefined;
64
- forbiddenPattern?: {
64
+ forbiddenPatterns?: {
65
65
  [x: string]: unknown;
66
66
  }[] | undefined;
67
- requiredPattern?: {
67
+ requiredPatterns?: {
68
68
  [x: string]: unknown;
69
69
  }[] | undefined;
70
70
  forbiddenPrefixes?: string[] | undefined;
@@ -28,7 +28,7 @@ declare const schema: {
28
28
  readonly suffix: {
29
29
  readonly type: "string";
30
30
  };
31
- readonly forbiddenPattern: {
31
+ readonly forbiddenPatterns: {
32
32
  readonly items: {
33
33
  readonly type: "object";
34
34
  };
@@ -37,7 +37,7 @@ declare const schema: {
37
37
  readonly uniqueItems: true;
38
38
  readonly minItems: 1;
39
39
  };
40
- readonly requiredPattern: {
40
+ readonly requiredPatterns: {
41
41
  readonly items: {
42
42
  readonly type: "object";
43
43
  };
@@ -47,14 +47,14 @@ const KindToDisplayName = {
47
47
  style: { enum: ALLOWED_STYLES },
48
48
  prefix: { type: "string" },
49
49
  suffix: { type: "string" },
50
- forbiddenPattern: {
50
+ forbiddenPatterns: {
51
51
  ...ARRAY_DEFAULT_OPTIONS,
52
52
  items: {
53
53
  type: "object"
54
54
  },
55
55
  description: "Should be of instance of `RegEx`"
56
56
  },
57
- requiredPattern: {
57
+ requiredPatterns: {
58
58
  ...ARRAY_DEFAULT_OPTIONS,
59
59
  items: {
60
60
  type: "object"
@@ -63,19 +63,19 @@ const KindToDisplayName = {
63
63
  },
64
64
  forbiddenPrefixes: {
65
65
  ...ARRAY_DEFAULT_OPTIONS,
66
- description: descriptionPrefixesSuffixes("forbiddenPattern")
66
+ description: descriptionPrefixesSuffixes("forbiddenPatterns")
67
67
  },
68
68
  forbiddenSuffixes: {
69
69
  ...ARRAY_DEFAULT_OPTIONS,
70
- description: descriptionPrefixesSuffixes("forbiddenPattern")
70
+ description: descriptionPrefixesSuffixes("forbiddenPatterns")
71
71
  },
72
72
  requiredPrefixes: {
73
73
  ...ARRAY_DEFAULT_OPTIONS,
74
- description: descriptionPrefixesSuffixes("requiredPattern")
74
+ description: descriptionPrefixesSuffixes("requiredPatterns")
75
75
  },
76
76
  requiredSuffixes: {
77
77
  ...ARRAY_DEFAULT_OPTIONS,
78
- description: descriptionPrefixesSuffixes("requiredPattern")
78
+ description: descriptionPrefixesSuffixes("requiredPatterns")
79
79
  },
80
80
  ignorePattern: {
81
81
  type: "string",
@@ -101,7 +101,9 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
101
101
  kind,
102
102
  {
103
103
  ...schemaOption,
104
- description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`
104
+ description: `> [!NOTE]
105
+ >
106
+ > Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`
105
107
  }
106
108
  ])
107
109
  ),
@@ -356,8 +358,8 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
356
358
  ignorePattern,
357
359
  requiredPrefixes,
358
360
  requiredSuffixes,
359
- forbiddenPattern,
360
- requiredPattern
361
+ forbiddenPatterns,
362
+ requiredPatterns
361
363
  } = normalisePropertyOption(selector), nodeName = node.value, error = getError();
362
364
  if (error) {
363
365
  const { errorMessage, renameToNames } = error, [leadingUnderscores] = nodeName.match(/^_*/), [trailingUnderscores] = nodeName.match(/_*$/), suggestedNames = renameToNames.map(
@@ -383,15 +385,15 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
383
385
  errorMessage: `have "${suffix}" suffix`,
384
386
  renameToNames: [name + suffix]
385
387
  };
386
- const forbidden = forbiddenPattern?.find((pattern) => pattern.test(name));
388
+ const forbidden = forbiddenPatterns?.find((pattern) => pattern.test(name));
387
389
  if (forbidden)
388
390
  return {
389
391
  errorMessage: `not contain the forbidden pattern "${forbidden}"`,
390
392
  renameToNames: [name.replace(forbidden, "")]
391
393
  };
392
- if (requiredPattern && !requiredPattern.some((pattern) => pattern.test(name)))
394
+ if (requiredPatterns && !requiredPatterns.some((pattern) => pattern.test(name)))
393
395
  return {
394
- errorMessage: `contain the required pattern: ${englishJoinWords(requiredPattern.map((re) => re.source))}`,
396
+ errorMessage: `contain the required pattern: ${englishJoinWords(requiredPatterns.map((re) => re.source))}`,
395
397
  renameToNames: []
396
398
  };
397
399
  const forbiddenPrefix = forbiddenPrefixes?.find((prefix2) => name.startsWith(prefix2));
@@ -1,6 +1,6 @@
1
1
  import { TypeInfo, visit, visitWithTypeInfo } from "graphql";
2
2
  import { ModuleCache } from "../../cache.js";
3
- import { requireGraphQLOperations, requireGraphQLSchema } from "../../utils.js";
3
+ import { eslintSelectorsTip, requireGraphQLOperations, requireGraphQLSchema } from "../../utils.js";
4
4
  const RULE_ID = "no-unused-fields", RELAY_SCHEMA = (
5
5
  /* GraphQL */
6
6
  `
@@ -82,9 +82,7 @@ const RULE_ID = "no-unused-fields", RELAY_SCHEMA = (
82
82
  "```json",
83
83
  JSON.stringify(RELAY_DEFAULT_IGNORED_FIELD_SELECTORS, null, 2),
84
84
  "```",
85
- "",
86
- "> These fields are defined by ESLint [`selectors`](https://eslint.org/docs/developer-guide/selectors).",
87
- "> Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector."
85
+ eslintSelectorsTip
88
86
  ].join(`
89
87
  `),
90
88
  items: {
@@ -15,8 +15,9 @@ type RuleOptions = [
15
15
  {
16
16
  [key in AllowedKind]?: boolean;
17
17
  } & {
18
- types?: boolean;
19
- rootField?: boolean;
18
+ types?: true;
19
+ rootField?: true;
20
+ ignoredSelectors?: string[];
20
21
  }
21
22
  ];
22
23
  declare const rule: GraphQLESLintRule<RuleOptions>;
@@ -1,6 +1,13 @@
1
1
  import { Kind, TokenKind } from "graphql";
2
2
  import { getRootTypeNames } from "@graphql-tools/utils";
3
- import { getLocation, getNodeName, requireGraphQLSchema, TYPES_KINDS } from "../../utils.js";
3
+ import {
4
+ ARRAY_DEFAULT_OPTIONS,
5
+ eslintSelectorsTip,
6
+ getLocation,
7
+ getNodeName,
8
+ requireGraphQLSchema,
9
+ TYPES_KINDS
10
+ } from "../../utils.js";
4
11
  const RULE_ID = "require-description", ALLOWED_KINDS = [
5
12
  ...TYPES_KINDS,
6
13
  Kind.DIRECTIVE_DEFINITION,
@@ -19,18 +26,34 @@ const RULE_ID = "require-description", ALLOWED_KINDS = [
19
26
  properties: {
20
27
  types: {
21
28
  type: "boolean",
29
+ enum: [!0],
22
30
  description: `Includes:
23
31
  ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
24
32
  `)}`
25
33
  },
26
34
  rootField: {
27
35
  type: "boolean",
36
+ enum: [!0],
28
37
  description: "Definitions within `Query`, `Mutation`, and `Subscription` root types."
29
38
  },
39
+ ignoredSelectors: {
40
+ ...ARRAY_DEFAULT_OPTIONS,
41
+ description: ["Ignore specific selectors", eslintSelectorsTip].join(`
42
+ `)
43
+ },
30
44
  ...Object.fromEntries(
31
45
  [...ALLOWED_KINDS].sort().map((kind) => {
32
- let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
33
- return kind === Kind.OPERATION_DEFINITION && (description += '\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.'), [kind, { type: "boolean", description }];
46
+ let description = `> [!NOTE]
47
+ >
48
+ > Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
49
+ return kind === Kind.OPERATION_DEFINITION && (description += [
50
+ "",
51
+ "",
52
+ "> [!WARNING]",
53
+ ">",
54
+ '> You must use only comment syntax `#` and not description syntax `"""` or `"`.'
55
+ ].join(`
56
+ `)), [kind, { type: "boolean", description }];
34
57
  })
35
58
  )
36
59
  }
@@ -101,6 +124,36 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
101
124
  }
102
125
  `
103
126
  )
127
+ },
128
+ {
129
+ title: "Correct",
130
+ usage: [
131
+ {
132
+ ignoredSelectors: [
133
+ "[type=ObjectTypeDefinition][name.value=PageInfo]",
134
+ "[type=ObjectTypeDefinition][name.value=/(Connection|Edge)$/]"
135
+ ]
136
+ }
137
+ ],
138
+ code: (
139
+ /* GraphQL */
140
+ `
141
+ type FriendConnection {
142
+ edges: [FriendEdge]
143
+ pageInfo: PageInfo!
144
+ }
145
+ type FriendEdge {
146
+ cursor: String!
147
+ node: Friend!
148
+ }
149
+ type PageInfo {
150
+ hasPreviousPage: Boolean!
151
+ hasNextPage: Boolean!
152
+ startCursor: String
153
+ endCursor: String
154
+ }
155
+ `
156
+ )
104
157
  }
105
158
  ],
106
159
  configOptions: [
@@ -119,7 +172,8 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
119
172
  schema
120
173
  },
121
174
  create(context) {
122
- const { types, rootField, ...restOptions } = context.options[0] || {}, kinds = new Set(types ? TYPES_KINDS : []);
175
+ let { types, rootField, ignoredSelectors = [], ...restOptions } = context.options[0] || {};
176
+ const kinds = new Set(types ? TYPES_KINDS : []);
123
177
  for (const [kind, isEnabled] of Object.entries(restOptions))
124
178
  isEnabled ? kinds.add(kind) : kinds.delete(kind);
125
179
  if (rootField) {
@@ -130,10 +184,11 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
130
184
  ].join(",")})$/] > FieldDefinition`
131
185
  );
132
186
  }
133
- if (!kinds.size)
134
- throw new Error("At least one kind must be enabled");
187
+ let selector = `:matches(${[...kinds]})`;
188
+ for (const str of ignoredSelectors)
189
+ selector += `:not(${str})`;
135
190
  return {
136
- [[...kinds].join(",")](node) {
191
+ [selector](node) {
137
192
  let description = "";
138
193
  const isOperation = node.kind === Kind.OPERATION_DEFINITION;
139
194
  if (isOperation) {
package/esm/utils.d.ts CHANGED
@@ -41,5 +41,6 @@ type Truthy<T> = T extends '' | 0 | false | null | undefined ? never : T;
41
41
  declare function truthy<T>(value: T): value is Truthy<T>;
42
42
  declare function displayNodeName(node: GraphQLESTreeNode<ASTNode, boolean>): string;
43
43
  declare function getNodeName(node: GraphQLESTreeNode<ASTNode>): string;
44
+ declare const eslintSelectorsTip = "> [!TIP]\n>\n> These fields are defined by ESLint [`selectors`](https://eslint.org/docs/developer-guide/selectors).\n> Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector.";
44
45
 
45
- export { ARRAY_DEFAULT_OPTIONS, CWD, type CaseStyle, REPORT_ON_FIRST_CHARACTER, TYPES_KINDS, VIRTUAL_DOCUMENT_REGEX, camelCase, convertCase, displayNodeName, englishJoinWords, getLocation, getNodeName, getTypeName, logger, pascalCase, requireGraphQLOperations, requireGraphQLSchema, slash, truthy };
46
+ export { ARRAY_DEFAULT_OPTIONS, CWD, type CaseStyle, REPORT_ON_FIRST_CHARACTER, TYPES_KINDS, VIRTUAL_DOCUMENT_REGEX, camelCase, convertCase, displayNodeName, englishJoinWords, eslintSelectorsTip, getLocation, getNodeName, getTypeName, logger, pascalCase, requireGraphQLOperations, requireGraphQLSchema, slash, truthy };
package/esm/utils.js CHANGED
@@ -144,6 +144,10 @@ function getNodeName(node) {
144
144
  }
145
145
  return "";
146
146
  }
147
+ const eslintSelectorsTip = `> [!TIP]
148
+ >
149
+ > These fields are defined by ESLint [\`selectors\`](https://eslint.org/docs/developer-guide/selectors).
150
+ > Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector.`;
147
151
  export {
148
152
  ARRAY_DEFAULT_OPTIONS,
149
153
  CWD,
@@ -154,6 +158,7 @@ export {
154
158
  convertCase,
155
159
  displayNodeName,
156
160
  englishJoinWords,
161
+ eslintSelectorsTip,
157
162
  getLocation,
158
163
  getNodeName,
159
164
  getTypeName,
package/index.browser.js CHANGED
@@ -150,7 +150,7 @@ function convertToESTree(node, schema16) {
150
150
  }
151
151
 
152
152
  // src/meta.ts
153
- var version = "4.1.0-alpha-20241128213211-47212e29bd7289aab6adc9e573221d04e2818bac";
153
+ var version = "4.1.0-alpha-20241129082104-137773e2da031621a4bbb9323212b09179e1076a";
154
154
 
155
155
  // src/siblings.ts
156
156
  import {
@@ -305,6 +305,10 @@ function getNodeName(node) {
305
305
  }
306
306
  return "";
307
307
  }
308
+ var eslintSelectorsTip = `> [!TIP]
309
+ >
310
+ > These fields are defined by ESLint [\`selectors\`](https://eslint.org/docs/developer-guide/selectors).
311
+ > Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector.`;
308
312
 
309
313
  // src/siblings.ts
310
314
  var siblingOperationsCache = /* @__PURE__ */ new Map();
@@ -1934,14 +1938,14 @@ var KindToDisplayName = {
1934
1938
  style: { enum: ALLOWED_STYLES },
1935
1939
  prefix: { type: "string" },
1936
1940
  suffix: { type: "string" },
1937
- forbiddenPattern: {
1941
+ forbiddenPatterns: {
1938
1942
  ...ARRAY_DEFAULT_OPTIONS,
1939
1943
  items: {
1940
1944
  type: "object"
1941
1945
  },
1942
1946
  description: "Should be of instance of `RegEx`"
1943
1947
  },
1944
- requiredPattern: {
1948
+ requiredPatterns: {
1945
1949
  ...ARRAY_DEFAULT_OPTIONS,
1946
1950
  items: {
1947
1951
  type: "object"
@@ -1950,19 +1954,19 @@ var KindToDisplayName = {
1950
1954
  },
1951
1955
  forbiddenPrefixes: {
1952
1956
  ...ARRAY_DEFAULT_OPTIONS,
1953
- description: descriptionPrefixesSuffixes("forbiddenPattern")
1957
+ description: descriptionPrefixesSuffixes("forbiddenPatterns")
1954
1958
  },
1955
1959
  forbiddenSuffixes: {
1956
1960
  ...ARRAY_DEFAULT_OPTIONS,
1957
- description: descriptionPrefixesSuffixes("forbiddenPattern")
1961
+ description: descriptionPrefixesSuffixes("forbiddenPatterns")
1958
1962
  },
1959
1963
  requiredPrefixes: {
1960
1964
  ...ARRAY_DEFAULT_OPTIONS,
1961
- description: descriptionPrefixesSuffixes("requiredPattern")
1965
+ description: descriptionPrefixesSuffixes("requiredPatterns")
1962
1966
  },
1963
1967
  requiredSuffixes: {
1964
1968
  ...ARRAY_DEFAULT_OPTIONS,
1965
- description: descriptionPrefixesSuffixes("requiredPattern")
1969
+ description: descriptionPrefixesSuffixes("requiredPatterns")
1966
1970
  },
1967
1971
  ignorePattern: {
1968
1972
  type: "string",
@@ -1988,7 +1992,9 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
1988
1992
  kind,
1989
1993
  {
1990
1994
  ...schemaOption2,
1991
- description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`
1995
+ description: `> [!NOTE]
1996
+ >
1997
+ > Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`
1992
1998
  }
1993
1999
  ])
1994
2000
  ),
@@ -2243,8 +2249,8 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
2243
2249
  ignorePattern,
2244
2250
  requiredPrefixes,
2245
2251
  requiredSuffixes,
2246
- forbiddenPattern,
2247
- requiredPattern
2252
+ forbiddenPatterns,
2253
+ requiredPatterns
2248
2254
  } = normalisePropertyOption(selector), nodeName = node.value, error = getError();
2249
2255
  if (error) {
2250
2256
  let { errorMessage, renameToNames } = error, [leadingUnderscores] = nodeName.match(/^_*/), [trailingUnderscores] = nodeName.match(/_*$/), suggestedNames = renameToNames.map(
@@ -2270,15 +2276,15 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
2270
2276
  errorMessage: `have "${suffix}" suffix`,
2271
2277
  renameToNames: [name + suffix]
2272
2278
  };
2273
- let forbidden = forbiddenPattern?.find((pattern) => pattern.test(name));
2279
+ let forbidden = forbiddenPatterns?.find((pattern) => pattern.test(name));
2274
2280
  if (forbidden)
2275
2281
  return {
2276
2282
  errorMessage: `not contain the forbidden pattern "${forbidden}"`,
2277
2283
  renameToNames: [name.replace(forbidden, "")]
2278
2284
  };
2279
- if (requiredPattern && !requiredPattern.some((pattern) => pattern.test(name)))
2285
+ if (requiredPatterns && !requiredPatterns.some((pattern) => pattern.test(name)))
2280
2286
  return {
2281
- errorMessage: `contain the required pattern: ${englishJoinWords(requiredPattern.map((re) => re.source))}`,
2287
+ errorMessage: `contain the required pattern: ${englishJoinWords(requiredPatterns.map((re) => re.source))}`,
2282
2288
  renameToNames: []
2283
2289
  };
2284
2290
  let forbiddenPrefix = forbiddenPrefixes?.find((prefix2) => name.startsWith(prefix2));
@@ -3281,9 +3287,7 @@ var RULE_ID10 = "no-unused-fields", RELAY_SCHEMA = (
3281
3287
  "```json",
3282
3288
  JSON.stringify(RELAY_DEFAULT_IGNORED_FIELD_SELECTORS, null, 2),
3283
3289
  "```",
3284
- "",
3285
- "> These fields are defined by ESLint [`selectors`](https://eslint.org/docs/developer-guide/selectors).",
3286
- "> Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector."
3290
+ eslintSelectorsTip
3287
3291
  ].join(`
3288
3292
  `),
3289
3293
  items: {
@@ -4033,18 +4037,34 @@ var RULE_ID14 = "require-description", ALLOWED_KINDS2 = [
4033
4037
  properties: {
4034
4038
  types: {
4035
4039
  type: "boolean",
4040
+ enum: [!0],
4036
4041
  description: `Includes:
4037
4042
  ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
4038
4043
  `)}`
4039
4044
  },
4040
4045
  rootField: {
4041
4046
  type: "boolean",
4047
+ enum: [!0],
4042
4048
  description: "Definitions within `Query`, `Mutation`, and `Subscription` root types."
4043
4049
  },
4050
+ ignoredSelectors: {
4051
+ ...ARRAY_DEFAULT_OPTIONS,
4052
+ description: ["Ignore specific selectors", eslintSelectorsTip].join(`
4053
+ `)
4054
+ },
4044
4055
  ...Object.fromEntries(
4045
4056
  [...ALLOWED_KINDS2].sort().map((kind) => {
4046
- let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
4047
- return kind === Kind17.OPERATION_DEFINITION && (description += '\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.'), [kind, { type: "boolean", description }];
4057
+ let description = `> [!NOTE]
4058
+ >
4059
+ > Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
4060
+ return kind === Kind17.OPERATION_DEFINITION && (description += [
4061
+ "",
4062
+ "",
4063
+ "> [!WARNING]",
4064
+ ">",
4065
+ '> You must use only comment syntax `#` and not description syntax `"""` or `"`.'
4066
+ ].join(`
4067
+ `)), [kind, { type: "boolean", description }];
4048
4068
  })
4049
4069
  )
4050
4070
  }
@@ -4115,6 +4135,36 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
4115
4135
  }
4116
4136
  `
4117
4137
  )
4138
+ },
4139
+ {
4140
+ title: "Correct",
4141
+ usage: [
4142
+ {
4143
+ ignoredSelectors: [
4144
+ "[type=ObjectTypeDefinition][name.value=PageInfo]",
4145
+ "[type=ObjectTypeDefinition][name.value=/(Connection|Edge)$/]"
4146
+ ]
4147
+ }
4148
+ ],
4149
+ code: (
4150
+ /* GraphQL */
4151
+ `
4152
+ type FriendConnection {
4153
+ edges: [FriendEdge]
4154
+ pageInfo: PageInfo!
4155
+ }
4156
+ type FriendEdge {
4157
+ cursor: String!
4158
+ node: Friend!
4159
+ }
4160
+ type PageInfo {
4161
+ hasPreviousPage: Boolean!
4162
+ hasNextPage: Boolean!
4163
+ startCursor: String
4164
+ endCursor: String
4165
+ }
4166
+ `
4167
+ )
4118
4168
  }
4119
4169
  ],
4120
4170
  configOptions: [
@@ -4133,7 +4183,7 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
4133
4183
  schema: schema12
4134
4184
  },
4135
4185
  create(context) {
4136
- let { types, rootField, ...restOptions } = context.options[0] || {}, kinds = new Set(types ? TYPES_KINDS : []);
4186
+ let { types, rootField, ignoredSelectors = [], ...restOptions } = context.options[0] || {}, kinds = new Set(types ? TYPES_KINDS : []);
4137
4187
  for (let [kind, isEnabled] of Object.entries(restOptions))
4138
4188
  isEnabled ? kinds.add(kind) : kinds.delete(kind);
4139
4189
  if (rootField) {
@@ -4144,10 +4194,11 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
4144
4194
  ].join(",")})$/] > FieldDefinition`
4145
4195
  );
4146
4196
  }
4147
- if (!kinds.size)
4148
- throw new Error("At least one kind must be enabled");
4197
+ let selector = `:matches(${[...kinds]})`;
4198
+ for (let str of ignoredSelectors)
4199
+ selector += `:not(${str})`;
4149
4200
  return {
4150
- [[...kinds].join(",")](node) {
4201
+ [selector](node) {
4151
4202
  let description = "", isOperation = node.kind === Kind17.OPERATION_DEFINITION;
4152
4203
  if (isOperation) {
4153
4204
  let rawNode = node.rawNode(), { prev, line } = rawNode.loc.startToken;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "4.1.0-alpha-20241128213211-47212e29bd7289aab6adc9e573221d04e2818bac",
3
+ "version": "4.1.0-alpha-20241129082104-137773e2da031621a4bbb9323212b09179e1076a",
4
4
  "type": "module",
5
5
  "description": "GraphQL plugin for ESLint",
6
6
  "repository": "https://github.com/dimaMachina/graphql-eslint",