@graphql-eslint/eslint-plugin 3.14.3 → 3.14.4-alpha-20221229152648-0a153c6
Sign up to get free protection for your applications and to get access to all the features.
- package/cjs/cache.js +3 -2
- package/cjs/documents.js +8 -5
- package/cjs/estree-converter/converter.js +1 -1
- package/cjs/parser.js +4 -2
- package/cjs/processor.js +4 -2
- package/cjs/rules/alphabetize.js +5 -3
- package/cjs/rules/graphql-js-validation.js +9 -6
- package/cjs/rules/input-name.js +5 -4
- package/cjs/rules/naming-convention.js +2 -2
- package/cjs/rules/no-case-insensitive-enum-values-duplicates.js +3 -2
- package/cjs/rules/no-duplicate-fields.js +2 -2
- package/cjs/rules/no-hashtag-description.js +3 -0
- package/cjs/rules/no-root-type.js +1 -1
- package/cjs/rules/no-typename-prefix.js +1 -1
- package/cjs/rules/no-unreachable-types.js +1 -1
- package/cjs/rules/relay-arguments.js +2 -1
- package/cjs/rules/relay-connection-types.js +2 -2
- package/cjs/rules/relay-edge-types.js +7 -4
- package/cjs/rules/relay-page-info.js +2 -1
- package/cjs/rules/require-deprecation-date.js +2 -2
- package/cjs/rules/require-deprecation-reason.js +2 -1
- package/cjs/rules/require-description.js +1 -1
- package/cjs/rules/require-field-of-type-query-in-mutation-result.js +1 -1
- package/cjs/rules/require-id-when-available.js +2 -1
- package/cjs/rules/require-nullable-fields-with-oneof.js +2 -2
- package/cjs/rules/require-type-pattern-with-oneof.js +2 -1
- package/cjs/rules/strict-id-in-types.js +4 -4
- package/cjs/rules/unique-fragment-name.js +1 -0
- package/cjs/schema.js +3 -1
- package/cjs/testkit.js +4 -7
- package/cjs/utils.js +11 -5
- package/esm/cache.js +3 -2
- package/esm/documents.js +8 -5
- package/esm/estree-converter/converter.js +1 -1
- package/esm/graphql-config.js +1 -1
- package/esm/parser.js +4 -2
- package/esm/processor.js +5 -3
- package/esm/rules/alphabetize.js +6 -4
- package/esm/rules/graphql-js-validation.js +9 -6
- package/esm/rules/input-name.js +5 -4
- package/esm/rules/naming-convention.js +3 -3
- package/esm/rules/no-case-insensitive-enum-values-duplicates.js +3 -2
- package/esm/rules/no-duplicate-fields.js +2 -2
- package/esm/rules/no-hashtag-description.js +3 -0
- package/esm/rules/no-root-type.js +2 -2
- package/esm/rules/no-typename-prefix.js +1 -1
- package/esm/rules/no-unreachable-types.js +1 -1
- package/esm/rules/relay-arguments.js +2 -1
- package/esm/rules/relay-connection-types.js +2 -2
- package/esm/rules/relay-edge-types.js +7 -4
- package/esm/rules/relay-page-info.js +2 -1
- package/esm/rules/require-deprecation-date.js +2 -2
- package/esm/rules/require-deprecation-reason.js +2 -1
- package/esm/rules/require-description.js +1 -1
- package/esm/rules/require-field-of-type-query-in-mutation-result.js +1 -1
- package/esm/rules/require-id-when-available.js +2 -1
- package/esm/rules/require-nullable-fields-with-oneof.js +2 -2
- package/esm/rules/require-type-pattern-with-oneof.js +2 -1
- package/esm/rules/strict-id-in-types.js +5 -5
- package/esm/rules/unique-fragment-name.js +1 -0
- package/esm/schema.js +3 -1
- package/esm/testkit.js +4 -7
- package/esm/utils.js +9 -4
- package/package.json +1 -1
- package/typings/cache.d.cts +1 -1
- package/typings/cache.d.ts +1 -1
- package/typings/estree-converter/types.d.cts +4 -4
- package/typings/estree-converter/types.d.ts +4 -4
- package/typings/estree-converter/utils.d.cts +2 -2
- package/typings/estree-converter/utils.d.ts +2 -2
- package/typings/rules/index.d.cts +68 -68
- package/typings/rules/index.d.ts +68 -68
- package/typings/testkit.d.cts +6 -4
- package/typings/testkit.d.ts +6 -4
- package/typings/types.d.cts +4 -3
- package/typings/types.d.ts +4 -3
- package/typings/utils.d.cts +8 -5
- package/typings/utils.d.ts +8 -5
package/esm/documents.js
CHANGED
@@ -9,7 +9,7 @@ const handleVirtualPath = (documents) => {
|
|
9
9
|
const filepathMap = Object.create(null);
|
10
10
|
return documents.map(source => {
|
11
11
|
var _a;
|
12
|
-
const
|
12
|
+
const location = source.location;
|
13
13
|
if (['.gql', '.graphql'].some(extension => location.endsWith(extension))) {
|
14
14
|
return source;
|
15
15
|
}
|
@@ -70,15 +70,17 @@ export function getDocuments(project) {
|
|
70
70
|
// Since the siblings array is cached, we can use it as cache key.
|
71
71
|
// We should get the same array reference each time we get
|
72
72
|
// to this point for the same graphql project
|
73
|
-
|
74
|
-
|
73
|
+
const value = siblingOperationsCache.get(siblings);
|
74
|
+
if (value) {
|
75
|
+
return value;
|
75
76
|
}
|
76
77
|
let fragmentsCache = null;
|
77
78
|
const getFragments = () => {
|
79
|
+
var _a;
|
78
80
|
if (fragmentsCache === null) {
|
79
81
|
const result = [];
|
80
82
|
for (const source of siblings) {
|
81
|
-
for (const definition of source.document.definitions) {
|
83
|
+
for (const definition of ((_a = source.document) === null || _a === void 0 ? void 0 : _a.definitions) || []) {
|
82
84
|
if (definition.kind === Kind.FRAGMENT_DEFINITION) {
|
83
85
|
result.push({
|
84
86
|
filePath: source.location,
|
@@ -93,10 +95,11 @@ export function getDocuments(project) {
|
|
93
95
|
};
|
94
96
|
let cachedOperations = null;
|
95
97
|
const getOperations = () => {
|
98
|
+
var _a;
|
96
99
|
if (cachedOperations === null) {
|
97
100
|
const result = [];
|
98
101
|
for (const source of siblings) {
|
99
|
-
for (const definition of source.document.definitions) {
|
102
|
+
for (const definition of ((_a = source.document) === null || _a === void 0 ? void 0 : _a.definitions) || []) {
|
100
103
|
if (definition.kind === Kind.OPERATION_DEFINITION) {
|
101
104
|
result.push({
|
102
105
|
filePath: source.location,
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { TypeInfo, visit, visitWithTypeInfo, Kind, } from 'graphql';
|
2
2
|
import { convertLocation } from './utils.js';
|
3
3
|
export function convertToESTree(node, schema) {
|
4
|
-
const typeInfo = schema
|
4
|
+
const typeInfo = schema && new TypeInfo(schema);
|
5
5
|
const visitor = {
|
6
6
|
leave(node, key, parent) {
|
7
7
|
const leadingComments = 'description' in node && node.description
|
package/esm/graphql-config.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { dirname } from 'path';
|
2
2
|
import debugFactory from 'debug';
|
3
|
-
import { GraphQLConfig, loadConfigSync } from 'graphql-config';
|
3
|
+
import { GraphQLConfig, loadConfigSync, } from 'graphql-config';
|
4
4
|
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
|
5
5
|
const debug = debugFactory('graphql-eslint:graphql-config');
|
6
6
|
let graphQLConfig;
|
package/esm/parser.js
CHANGED
@@ -21,7 +21,7 @@ export function parseForESLint(code, options) {
|
|
21
21
|
const realFilepath = filePath.replace(VIRTUAL_DOCUMENT_REGEX, '');
|
22
22
|
const project = gqlConfig.getProjectForFile(realFilepath);
|
23
23
|
const schema = getSchema(project, options.schemaOptions);
|
24
|
-
const rootTree = convertToESTree(document, schema instanceof GraphQLSchema ? schema :
|
24
|
+
const rootTree = convertToESTree(document, schema instanceof GraphQLSchema ? schema : undefined);
|
25
25
|
return {
|
26
26
|
services: {
|
27
27
|
schema,
|
@@ -39,7 +39,9 @@ export function parseForESLint(code, options) {
|
|
39
39
|
};
|
40
40
|
}
|
41
41
|
catch (error) {
|
42
|
-
error
|
42
|
+
if (error instanceof Error) {
|
43
|
+
error.message = `[graphql-eslint] ${error.message}`;
|
44
|
+
}
|
43
45
|
// In case of GraphQL parser error, we report it to ESLint as a parser error that matches the requirements
|
44
46
|
// of ESLint. This will make sure to display it correctly in IDEs and lint results.
|
45
47
|
if (error instanceof GraphQLError) {
|
package/esm/processor.js
CHANGED
@@ -2,7 +2,7 @@ import { relative } from 'path';
|
|
2
2
|
import { gqlPluckFromCodeStringSync, } from '@graphql-tools/graphql-tag-pluck';
|
3
3
|
import { asArray } from '@graphql-tools/utils';
|
4
4
|
import { loadOnDiskGraphQLConfig } from './graphql-config.js';
|
5
|
-
import { CWD, REPORT_ON_FIRST_CHARACTER } from './utils.js';
|
5
|
+
import { CWD, REPORT_ON_FIRST_CHARACTER, truthy } from './utils.js';
|
6
6
|
const blocksMap = new Map();
|
7
7
|
let onDiskConfig;
|
8
8
|
let onDiskConfigLoaded = false;
|
@@ -23,7 +23,7 @@ export const processor = {
|
|
23
23
|
...modules.map(({ identifier }) => identifier),
|
24
24
|
...asArray(globalGqlIdentifierName),
|
25
25
|
gqlMagicComment,
|
26
|
-
].filter(
|
26
|
+
].filter(truthy)),
|
27
27
|
];
|
28
28
|
}
|
29
29
|
if (keywords.every(keyword => !code.includes(keyword))) {
|
@@ -45,7 +45,9 @@ export const processor = {
|
|
45
45
|
return [...blocks, code /* source code must be provided and be last */];
|
46
46
|
}
|
47
47
|
catch (error) {
|
48
|
-
error
|
48
|
+
if (error instanceof Error) {
|
49
|
+
error.message = `[graphql-eslint] Error while preprocessing "${relative(CWD, filePath)}" file\n\n${error.message}`;
|
50
|
+
}
|
49
51
|
// eslint-disable-next-line no-console
|
50
52
|
console.error(error);
|
51
53
|
// in case of parsing error return code as is
|
package/esm/rules/alphabetize.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Kind, } from 'graphql';
|
2
2
|
import lowerCase from 'lodash.lowercase';
|
3
|
-
import { ARRAY_DEFAULT_OPTIONS } from '../utils.js';
|
3
|
+
import { ARRAY_DEFAULT_OPTIONS, truthy } from '../utils.js';
|
4
4
|
const RULE_ID = 'alphabetize';
|
5
5
|
const fieldsEnum = [
|
6
6
|
Kind.OBJECT_TYPE_DEFINITION,
|
@@ -224,7 +224,7 @@ export const rule = {
|
|
224
224
|
: node;
|
225
225
|
return [from.range[0], to.range[1]];
|
226
226
|
}
|
227
|
-
function checkNodes(nodes) {
|
227
|
+
function checkNodes(nodes = []) {
|
228
228
|
var _a, _b, _c, _d;
|
229
229
|
// Starts from 1, ignore nodes.length <= 1
|
230
230
|
for (let i = 1; i < nodes.length; i += 1) {
|
@@ -269,6 +269,7 @@ export const rule = {
|
|
269
269
|
}
|
270
270
|
}
|
271
271
|
context.report({
|
272
|
+
// @ts-expect-error can't be undefined
|
272
273
|
node: ('alias' in currNode && currNode.alias) || currNode.name,
|
273
274
|
messageId: RULE_ID,
|
274
275
|
data: {
|
@@ -301,7 +302,7 @@ export const rule = {
|
|
301
302
|
Kind.INPUT_OBJECT_TYPE_EXTENSION,
|
302
303
|
],
|
303
304
|
]
|
304
|
-
.filter(
|
305
|
+
.filter(truthy)
|
305
306
|
.flat();
|
306
307
|
const fieldsSelector = kinds.join(',');
|
307
308
|
const hasEnumValues = ((_b = opts.values) === null || _b === void 0 ? void 0 : _b[0]) === Kind.ENUM_TYPE_DEFINITION;
|
@@ -326,7 +327,8 @@ export const rule = {
|
|
326
327
|
}
|
327
328
|
if (hasVariables) {
|
328
329
|
listeners.OperationDefinition = (node) => {
|
329
|
-
|
330
|
+
var _a;
|
331
|
+
checkNodes((_a = node.variableDefinitions) === null || _a === void 0 ? void 0 : _a.map(varDef => varDef.variable));
|
330
332
|
};
|
331
333
|
}
|
332
334
|
if (argumentsSelector) {
|
@@ -45,10 +45,10 @@ function validateDocument({ context, schema = null, documentNode, rule, hasDidYo
|
|
45
45
|
});
|
46
46
|
}
|
47
47
|
}
|
48
|
-
catch (
|
48
|
+
catch (error) {
|
49
49
|
context.report({
|
50
50
|
loc: REPORT_ON_FIRST_CHARACTER,
|
51
|
-
message:
|
51
|
+
message: error.message,
|
52
52
|
});
|
53
53
|
}
|
54
54
|
}
|
@@ -185,10 +185,13 @@ export const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule({
|
|
185
185
|
if (ignoreClientDirectives.length === 0) {
|
186
186
|
return documentNode;
|
187
187
|
}
|
188
|
-
const filterDirectives = (node) =>
|
189
|
-
|
190
|
-
|
191
|
-
|
188
|
+
const filterDirectives = (node) => {
|
189
|
+
var _a;
|
190
|
+
return ({
|
191
|
+
...node,
|
192
|
+
directives: (_a = node.directives) === null || _a === void 0 ? void 0 : _a.filter(directive => !ignoreClientDirectives.includes(directive.name.value)),
|
193
|
+
});
|
194
|
+
};
|
192
195
|
return visit(documentNode, {
|
193
196
|
Field: filterDirectives,
|
194
197
|
OperationDefinition: filterDirectives,
|
package/esm/rules/input-name.js
CHANGED
@@ -76,12 +76,12 @@ export const rule = {
|
|
76
76
|
const options = {
|
77
77
|
checkInputType: false,
|
78
78
|
caseSensitiveInputType: true,
|
79
|
-
checkQueries: false,
|
80
79
|
checkMutations: true,
|
81
80
|
...context.options[0],
|
82
81
|
};
|
83
|
-
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) ||
|
84
|
-
(options.checkQueries && isQueryType(node))
|
82
|
+
const shouldCheckType = (node) => (options.checkMutations && isMutationType(node)) ||
|
83
|
+
(options.checkQueries && isQueryType(node)) ||
|
84
|
+
false;
|
85
85
|
const listeners = {
|
86
86
|
'FieldDefinition > InputValueDefinition[name.value!=input] > Name'(node) {
|
87
87
|
if (shouldCheckType(node.parent.parent.parent)) {
|
@@ -101,9 +101,10 @@ export const rule = {
|
|
101
101
|
};
|
102
102
|
if (options.checkInputType) {
|
103
103
|
listeners['FieldDefinition > InputValueDefinition NamedType'] = (node) => {
|
104
|
-
const findInputType = item => {
|
104
|
+
const findInputType = (item) => {
|
105
105
|
let currentNode = item;
|
106
106
|
while (currentNode.type !== Kind.INPUT_VALUE_DEFINITION) {
|
107
|
+
// @ts-expect-error TODO try fix type error
|
107
108
|
currentNode = currentNode.parent;
|
108
109
|
}
|
109
110
|
return currentNode;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Kind } from 'graphql';
|
2
|
-
import { TYPES_KINDS, convertCase, ARRAY_DEFAULT_OPTIONS } from '../utils.js';
|
2
|
+
import { TYPES_KINDS, convertCase, ARRAY_DEFAULT_OPTIONS, truthy } from '../utils.js';
|
3
3
|
const KindToDisplayName = {
|
4
4
|
// types
|
5
5
|
[Kind.OBJECT_TYPE_DEFINITION]: 'Type',
|
@@ -212,7 +212,7 @@ export const rule = {
|
|
212
212
|
const options = context.options[0] || {};
|
213
213
|
const { allowLeadingUnderscore, allowTrailingUnderscore, types, ...restOptions } = options;
|
214
214
|
function normalisePropertyOption(kind) {
|
215
|
-
const style = restOptions[kind] || types;
|
215
|
+
const style = (restOptions[kind] || types);
|
216
216
|
return typeof style === 'object' ? style : { style };
|
217
217
|
}
|
218
218
|
function report(node, message, suggestedName) {
|
@@ -298,7 +298,7 @@ export const rule = {
|
|
298
298
|
if (!allowTrailingUnderscore) {
|
299
299
|
listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(false);
|
300
300
|
}
|
301
|
-
const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(
|
301
|
+
const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(truthy));
|
302
302
|
for (const selector of selectors) {
|
303
303
|
listeners[selector] = checkNode(selector);
|
304
304
|
}
|
@@ -37,9 +37,10 @@ export const rule = {
|
|
37
37
|
const selector = [Kind.ENUM_TYPE_DEFINITION, Kind.ENUM_TYPE_EXTENSION].join(',');
|
38
38
|
return {
|
39
39
|
[selector](node) {
|
40
|
-
|
40
|
+
var _a;
|
41
|
+
const duplicates = (_a = node.values) === null || _a === void 0 ? void 0 : _a.filter((item, index, array) => array.findIndex(v => v.name.value.toLowerCase() === item.name.value.toLowerCase()) !==
|
41
42
|
index);
|
42
|
-
for (const duplicate of duplicates) {
|
43
|
+
for (const duplicate of duplicates || []) {
|
43
44
|
const enumName = duplicate.name.value;
|
44
45
|
context.report({
|
45
46
|
node: duplicate.name,
|
@@ -86,13 +86,13 @@ export const rule = {
|
|
86
86
|
return {
|
87
87
|
OperationDefinition(node) {
|
88
88
|
const set = new Set();
|
89
|
-
for (const varDef of node.variableDefinitions) {
|
89
|
+
for (const varDef of node.variableDefinitions || []) {
|
90
90
|
checkNode(set, varDef.variable.name);
|
91
91
|
}
|
92
92
|
},
|
93
93
|
Field(node) {
|
94
94
|
const set = new Set();
|
95
|
-
for (const arg of node.arguments) {
|
95
|
+
for (const arg of node.arguments || []) {
|
96
96
|
checkNode(set, arg.name);
|
97
97
|
}
|
98
98
|
},
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { ARRAY_DEFAULT_OPTIONS, requireGraphQLSchemaFromContext } from '../utils.js';
|
1
|
+
import { ARRAY_DEFAULT_OPTIONS, requireGraphQLSchemaFromContext, truthy } from '../utils.js';
|
2
2
|
const schema = {
|
3
3
|
type: 'array',
|
4
4
|
minItems: 1,
|
@@ -57,7 +57,7 @@ export const rule = {
|
|
57
57
|
disallow.has('mutation') && schema.getMutationType(),
|
58
58
|
disallow.has('subscription') && schema.getSubscriptionType(),
|
59
59
|
]
|
60
|
-
.filter(
|
60
|
+
.filter(truthy)
|
61
61
|
.map(type => type.name)
|
62
62
|
.join('|');
|
63
63
|
if (!rootTypeNames) {
|
@@ -37,7 +37,7 @@ export const rule = {
|
|
37
37
|
'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
|
38
38
|
const typeName = node.name.value;
|
39
39
|
const lowerTypeName = typeName.toLowerCase();
|
40
|
-
for (const field of node.fields) {
|
40
|
+
for (const field of node.fields || []) {
|
41
41
|
const fieldName = field.name.value;
|
42
42
|
if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
|
43
43
|
context.report({
|
@@ -48,7 +48,7 @@ function getReachableTypes(schema) {
|
|
48
48
|
visit(astNode, visitor);
|
49
49
|
}
|
50
50
|
}
|
51
|
-
else if (type.astNode) {
|
51
|
+
else if (type === null || type === void 0 ? void 0 : type.astNode) {
|
52
52
|
// astNode can be undefined for ID, String, Boolean
|
53
53
|
visit(type.astNode, visitor);
|
54
54
|
}
|
@@ -69,11 +69,12 @@ export const rule = {
|
|
69
69
|
const { includeBoth = true } = context.options[0] || {};
|
70
70
|
return {
|
71
71
|
'FieldDefinition > .gqlType Name[value=/Connection$/]'(node) {
|
72
|
+
var _a;
|
72
73
|
let fieldNode = node.parent;
|
73
74
|
while (fieldNode.kind !== Kind.FIELD_DEFINITION) {
|
74
75
|
fieldNode = fieldNode.parent;
|
75
76
|
}
|
76
|
-
const args = Object.fromEntries(fieldNode.arguments.map(argument => [argument.name.value, argument]));
|
77
|
+
const args = Object.fromEntries(((_a = fieldNode.arguments) === null || _a === void 0 ? void 0 : _a.map(argument => [argument.name.value, argument])) || []);
|
77
78
|
const hasForwardPagination = Boolean(args.first && args.after);
|
78
79
|
const hasBackwardPagination = Boolean(args.last && args.before);
|
79
80
|
if (!hasForwardPagination && !hasBackwardPagination) {
|
@@ -17,8 +17,8 @@ export const NON_OBJECT_TYPES = [
|
|
17
17
|
Kind.INTERFACE_TYPE_EXTENSION,
|
18
18
|
];
|
19
19
|
const notConnectionTypesSelector = `:matches(${NON_OBJECT_TYPES})[name.value=/Connection$/] > .name`;
|
20
|
-
const hasEdgesField = (node) => node.fields.some(field => field.name.value === 'edges');
|
21
|
-
const hasPageInfoField = (node) => node.fields.some(field => field.name.value === 'pageInfo');
|
20
|
+
const hasEdgesField = (node) => { var _a; return (_a = node.fields) === null || _a === void 0 ? void 0 : _a.some(field => field.name.value === 'edges'); };
|
21
|
+
const hasPageInfoField = (node) => { var _a; return (_a = node.fields) === null || _a === void 0 ? void 0 : _a.some(field => field.name.value === 'pageInfo'); };
|
22
22
|
export const rule = {
|
23
23
|
meta: {
|
24
24
|
type: 'problem',
|
@@ -16,12 +16,13 @@ function getEdgeTypes(schema) {
|
|
16
16
|
const edgeTypes = new Set();
|
17
17
|
const visitor = {
|
18
18
|
ObjectTypeDefinition(node) {
|
19
|
+
var _a;
|
19
20
|
const typeName = node.name.value;
|
20
21
|
const hasConnectionSuffix = typeName.endsWith('Connection');
|
21
22
|
if (!hasConnectionSuffix) {
|
22
23
|
return;
|
23
24
|
}
|
24
|
-
const edges = node.fields.find(field => field.name.value === 'edges');
|
25
|
+
const edges = (_a = node.fields) === null || _a === void 0 ? void 0 : _a.find(field => field.name.value === 'edges');
|
25
26
|
if (edges) {
|
26
27
|
const edgesTypeName = getTypeName(edges);
|
27
28
|
const edgesType = schema.getType(edgesTypeName);
|
@@ -113,7 +114,8 @@ export const rule = {
|
|
113
114
|
const isNamedOrNonNullNamed = (node) => node.kind === Kind.NAMED_TYPE ||
|
114
115
|
(node.kind === Kind.NON_NULL_TYPE && node.gqlType.kind === Kind.NAMED_TYPE);
|
115
116
|
const checkNodeField = (node) => {
|
116
|
-
|
117
|
+
var _a, _b;
|
118
|
+
const nodeField = (_a = node.fields) === null || _a === void 0 ? void 0 : _a.find(field => field.name.value === 'node');
|
117
119
|
const message = 'return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types.';
|
118
120
|
if (!nodeField) {
|
119
121
|
context.report({
|
@@ -130,14 +132,15 @@ export const rule = {
|
|
130
132
|
if (!isObjectType(type)) {
|
131
133
|
return;
|
132
134
|
}
|
133
|
-
const implementsNode = type.astNode.interfaces.some(n => n.name.value === 'Node');
|
135
|
+
const implementsNode = (_b = type.astNode.interfaces) === null || _b === void 0 ? void 0 : _b.some(n => n.name.value === 'Node');
|
134
136
|
if (!implementsNode) {
|
135
137
|
context.report({ node: node.name, messageId: MESSAGE_SHOULD_IMPLEMENTS_NODE });
|
136
138
|
}
|
137
139
|
}
|
138
140
|
};
|
139
141
|
const checkCursorField = (node) => {
|
140
|
-
|
142
|
+
var _a;
|
143
|
+
const cursorField = (_a = node.fields) === null || _a === void 0 ? void 0 : _a.find(field => field.name.value === 'cursor');
|
141
144
|
const message = 'return either a String, Scalar, or a non-null wrapper wrapper around one of those types.';
|
142
145
|
if (!cursorField) {
|
143
146
|
context.report({
|
@@ -58,7 +58,8 @@ export const rule = {
|
|
58
58
|
context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE });
|
59
59
|
},
|
60
60
|
'ObjectTypeDefinition[name.value=PageInfo]'(node) {
|
61
|
-
|
61
|
+
var _a;
|
62
|
+
const fieldMap = Object.fromEntries(((_a = node.fields) === null || _a === void 0 ? void 0 : _a.map(field => [field.name.value, field])) || []);
|
62
63
|
const checkField = (fieldName, typeName) => {
|
63
64
|
const field = fieldMap[fieldName];
|
64
65
|
let isAllowedType = false;
|
@@ -68,9 +68,9 @@ export const rule = {
|
|
68
68
|
create(context) {
|
69
69
|
return {
|
70
70
|
'Directive[name.value=deprecated]'(node) {
|
71
|
-
var _a;
|
71
|
+
var _a, _b;
|
72
72
|
const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
|
73
|
-
const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
|
73
|
+
const deletionDateNode = (_b = node.arguments) === null || _b === void 0 ? void 0 : _b.find(arg => arg.name.value === argName);
|
74
74
|
if (!deletionDateNode) {
|
75
75
|
context.report({
|
76
76
|
node: node.name,
|
@@ -39,7 +39,8 @@ export const rule = {
|
|
39
39
|
create(context) {
|
40
40
|
return {
|
41
41
|
'Directive[name.value=deprecated]'(node) {
|
42
|
-
|
42
|
+
var _a;
|
43
|
+
const reasonArgument = (_a = node.arguments) === null || _a === void 0 ? void 0 : _a.find(arg => arg.name.value === 'reason');
|
43
44
|
const value = reasonArgument && String(valueFromNode(reasonArgument.value)).trim();
|
44
45
|
if (!value) {
|
45
46
|
context.report({
|
@@ -164,7 +164,7 @@ export const rule = {
|
|
164
164
|
if (isOperation) {
|
165
165
|
const rawNode = node.rawNode();
|
166
166
|
const { prev, line } = rawNode.loc.startToken;
|
167
|
-
if (prev.kind === TokenKind.COMMENT) {
|
167
|
+
if ((prev === null || prev === void 0 ? void 0 : prev.kind) === TokenKind.COMMENT) {
|
168
168
|
const value = prev.value.trim();
|
169
169
|
const linesBefore = line - prev.line;
|
170
170
|
if (!value.startsWith('eslint') && linesBefore === 1) {
|
@@ -55,7 +55,7 @@ export const rule = {
|
|
55
55
|
const graphQLType = schema.getType(typeName);
|
56
56
|
if (isObjectType(graphQLType)) {
|
57
57
|
const { fields } = graphQLType.astNode;
|
58
|
-
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
58
|
+
const hasQueryType = fields === null || fields === void 0 ? void 0 : fields.some(field => getTypeName(field) === queryType.name);
|
59
59
|
if (!hasQueryType) {
|
60
60
|
context.report({
|
61
61
|
node,
|
@@ -105,7 +105,8 @@ export const rule = {
|
|
105
105
|
}
|
106
106
|
const checkedFragmentSpreads = new Set();
|
107
107
|
const visitor = visitWithTypeInfo(typeInfo, {
|
108
|
-
SelectionSet(node, key,
|
108
|
+
SelectionSet(node, key, _parent) {
|
109
|
+
const parent = _parent;
|
109
110
|
if (parent.kind === Kind.FRAGMENT_DEFINITION) {
|
110
111
|
checkedFragmentSpreads.add(parent.name.value);
|
111
112
|
}
|
@@ -35,7 +35,7 @@ export const rule = {
|
|
35
35
|
},
|
36
36
|
create(context) {
|
37
37
|
return {
|
38
|
-
'Directive[name.value=oneOf]'({ parent
|
38
|
+
'Directive[name.value=oneOf]'({ parent }) {
|
39
39
|
const isTypeOrInput = [
|
40
40
|
Kind.OBJECT_TYPE_DEFINITION,
|
41
41
|
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
@@ -43,7 +43,7 @@ export const rule = {
|
|
43
43
|
if (!isTypeOrInput) {
|
44
44
|
return;
|
45
45
|
}
|
46
|
-
for (const field of parent.fields) {
|
46
|
+
for (const field of parent.fields || []) {
|
47
47
|
if (field.gqlType.kind === Kind.NON_NULL_TYPE) {
|
48
48
|
context.report({
|
49
49
|
node: field.name,
|
@@ -38,9 +38,10 @@ export const rule = {
|
|
38
38
|
create(context) {
|
39
39
|
return {
|
40
40
|
'Directive[name.value=oneOf][parent.kind=ObjectTypeDefinition]'({ parent, }) {
|
41
|
+
var _a;
|
41
42
|
const requiredFields = ['error', 'ok'];
|
42
43
|
for (const fieldName of requiredFields) {
|
43
|
-
if (!parent.fields.some(field => field.name.value === fieldName)) {
|
44
|
+
if (!((_a = parent.fields) === null || _a === void 0 ? void 0 : _a.some(field => field.name.value === fieldName))) {
|
44
45
|
context.report({
|
45
46
|
node: parent.name,
|
46
47
|
messageId: RULE_ID,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Kind } from 'graphql';
|
2
|
-
import { ARRAY_DEFAULT_OPTIONS, requireGraphQLSchemaFromContext, englishJoinWords, } from '../utils.js';
|
2
|
+
import { ARRAY_DEFAULT_OPTIONS, requireGraphQLSchemaFromContext, englishJoinWords, truthy, } from '../utils.js';
|
3
3
|
const RULE_ID = 'strict-id-in-types';
|
4
4
|
const schema = {
|
5
5
|
type: 'array',
|
@@ -119,19 +119,19 @@ export const rule = {
|
|
119
119
|
schema.getMutationType(),
|
120
120
|
schema.getSubscriptionType(),
|
121
121
|
]
|
122
|
-
.filter(
|
122
|
+
.filter(truthy)
|
123
123
|
.map(type => type.name);
|
124
124
|
const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
|
125
125
|
return {
|
126
126
|
[selector](node) {
|
127
|
-
var _a, _b;
|
127
|
+
var _a, _b, _c;
|
128
128
|
const typeName = node.name.value;
|
129
129
|
const shouldIgnoreNode = ((_a = options.exceptions.types) === null || _a === void 0 ? void 0 : _a.includes(typeName)) ||
|
130
130
|
((_b = options.exceptions.suffixes) === null || _b === void 0 ? void 0 : _b.some(suffix => typeName.endsWith(suffix)));
|
131
131
|
if (shouldIgnoreNode) {
|
132
132
|
return;
|
133
133
|
}
|
134
|
-
const validIds = node.fields.filter(field => {
|
134
|
+
const validIds = (_c = node.fields) === null || _c === void 0 ? void 0 : _c.filter(field => {
|
135
135
|
const fieldNode = field.rawNode();
|
136
136
|
const isValidIdName = options.acceptedIdNames.includes(fieldNode.name.value);
|
137
137
|
// To be a valid type, it must be non-null and one of the accepted types.
|
@@ -145,7 +145,7 @@ export const rule = {
|
|
145
145
|
// Usually, there should be only one unique identifier field per type.
|
146
146
|
// Some clients allow multiple fields to be used. If more people need this,
|
147
147
|
// we can extend this rule later.
|
148
|
-
if (validIds.length !== 1) {
|
148
|
+
if ((validIds === null || validIds === void 0 ? void 0 : validIds.length) !== 1) {
|
149
149
|
const pluralNamesSuffix = options.acceptedIdNames.length > 1 ? 's' : '';
|
150
150
|
const pluralTypesSuffix = options.acceptedIdTypes.length > 1 ? 's' : '';
|
151
151
|
context.report({
|
package/esm/schema.js
CHANGED
@@ -30,7 +30,9 @@ export function getSchema(project, schemaOptions) {
|
|
30
30
|
schemaCache.set(schemaKey, schema);
|
31
31
|
}
|
32
32
|
catch (error) {
|
33
|
-
error
|
33
|
+
if (error instanceof Error) {
|
34
|
+
error.message = chalk.red(`Error while loading schema: ${error.message}`);
|
35
|
+
}
|
34
36
|
schema = error;
|
35
37
|
}
|
36
38
|
return schema;
|
package/esm/testkit.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { createRequire } from 'module';
|
2
2
|
const require = createRequire(import.meta.url);
|
3
|
-
/* eslint-env
|
3
|
+
/* eslint-env vitest */
|
4
4
|
import { readFileSync } from 'fs';
|
5
5
|
import { resolve } from 'path';
|
6
6
|
import { RuleTester, Linter } from 'eslint';
|
@@ -59,7 +59,7 @@ export class GraphQLRuleTester extends RuleTester {
|
|
59
59
|
// continue;
|
60
60
|
// }
|
61
61
|
//
|
62
|
-
// const verifyConfig = getVerifyConfig(ruleId, this.config, testCase);
|
62
|
+
// const verifyConfig = getVerifyConfig<Options>(ruleId, this.config, testCase);
|
63
63
|
// defineParser(linter, verifyConfig.parser);
|
64
64
|
//
|
65
65
|
// const messages = linter.verify(code, verifyConfig, { filename });
|
@@ -93,11 +93,10 @@ export class GraphQLRuleTester extends RuleTester {
|
|
93
93
|
}
|
94
94
|
const codeWithMessage = printCode(code, message, 1);
|
95
95
|
messageForSnapshot.push(printWithIndex('#### ❌ Error', index, messages.length), indentCode(codeWithMessage));
|
96
|
-
const { suggestions } = message;
|
97
96
|
// Don't print suggestions in snapshots for too big codes
|
98
|
-
if (suggestions && (code.match(/\n/g) || '').length < 1000) {
|
97
|
+
if (message.suggestions && (code.match(/\n/g) || '').length < 1000) {
|
99
98
|
for (const [i, suggestion] of message.suggestions.entries()) {
|
100
|
-
const title = printWithIndex('#### 💡 Suggestion', i, suggestions.length, suggestion.desc);
|
99
|
+
const title = printWithIndex('#### 💡 Suggestion', i, message.suggestions.length, suggestion.desc);
|
101
100
|
const output = applyFix(code, suggestion.fix);
|
102
101
|
const codeFrame = printCode(output, { line: 0, column: 0 });
|
103
102
|
messageForSnapshot.push(title, indentCode(codeFrame, 2));
|
@@ -110,9 +109,7 @@ export class GraphQLRuleTester extends RuleTester {
|
|
110
109
|
messageForSnapshot.push('#### 🔧 Autofix output', indentCode(printCode(output)));
|
111
110
|
}
|
112
111
|
}
|
113
|
-
// @ts-expect-error -- we should import `vitest` but somebody could use globals from `jest`
|
114
112
|
it(name || `Invalid #${idx + 1}`, () => {
|
115
|
-
// @ts-expect-error -- ^ same
|
116
113
|
expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
|
117
114
|
});
|
118
115
|
}
|
package/esm/utils.js
CHANGED
@@ -19,15 +19,17 @@ export function requireGraphQLSchemaFromContext(ruleId, context) {
|
|
19
19
|
return schema;
|
20
20
|
}
|
21
21
|
export const logger = {
|
22
|
+
error: (...args) =>
|
22
23
|
// eslint-disable-next-line no-console
|
23
|
-
|
24
|
+
console.error(chalk.red('error'), '[graphql-eslint]', chalk(...args)),
|
25
|
+
warn: (...args) =>
|
24
26
|
// eslint-disable-next-line no-console
|
25
|
-
|
27
|
+
console.warn(chalk.yellow('warning'), '[graphql-eslint]', chalk(...args)),
|
26
28
|
};
|
27
29
|
export const normalizePath = (path) => (path || '').replace(/\\/g, '/');
|
28
30
|
export const VIRTUAL_DOCUMENT_REGEX = /\/\d+_document.graphql$/;
|
29
31
|
export const CWD = process.cwd();
|
30
|
-
export const getTypeName = (node) => 'type' in node ? getTypeName(node.type) : node.name.value;
|
32
|
+
export const getTypeName = (node) => 'type' in node ? getTypeName(node.type) : 'name' in node && node.name ? node.name.value : '';
|
31
33
|
export const TYPES_KINDS = [
|
32
34
|
Kind.OBJECT_TYPE_DEFINITION,
|
33
35
|
Kind.INTERFACE_TYPE_DEFINITION,
|
@@ -80,4 +82,7 @@ export const ARRAY_DEFAULT_OPTIONS = {
|
|
80
82
|
type: 'string',
|
81
83
|
},
|
82
84
|
};
|
83
|
-
export const englishJoinWords = words => new Intl.ListFormat('en-US', { type: 'disjunction' }).format(words);
|
85
|
+
export const englishJoinWords = (words) => new Intl.ListFormat('en-US', { type: 'disjunction' }).format(words);
|
86
|
+
export function truthy(value) {
|
87
|
+
return Boolean(value);
|
88
|
+
}
|