@graphql-eslint/eslint-plugin 3.11.0-alpha-20220828135426-e9545b1 → 3.11.0-alpha-20220923192439-db921ff
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/README.md +2 -2
- package/configs/schema-all.json +2 -11
- package/docs/README.md +1 -0
- package/docs/custom-rules.md +1 -1
- package/docs/rules/require-deprecation-date.md +2 -1
- package/index.js +67 -55
- package/index.mjs +67 -55
- package/package.json +1 -1
- package/rules/alphabetize.d.ts +0 -2
- package/types.d.ts +19 -16
package/README.md
CHANGED
@@ -9,7 +9,7 @@ This project integrates GraphQL and ESLint, for a better developer experience.
|
|
9
9
|
## Key Features
|
10
10
|
|
11
11
|
- 🚀 Integrates with ESLint core (as a ESTree parser)
|
12
|
-
- 🚀 Works on `.graphql` files, `gql` usages and `/*
|
12
|
+
- 🚀 Works on `.graphql` files, `gql` usages and `/* GraphQL */` magic comments
|
13
13
|
- 🚀 Lints both GraphQL schema and GraphQL operations
|
14
14
|
- 🚀 Extended type info for more advanced usages
|
15
15
|
- 🚀 Supports ESLint directives (for example: `eslint-disable-next-line`)
|
@@ -188,7 +188,7 @@ See [docs/deprecated-rules.md](docs/deprecated-rules.md).
|
|
188
188
|
|[`relay`](packages/plugin/src/configs/relay.json)|enables rules from Relay specification for schema (SDL) development|
|
189
189
|
<!-- prettier-ignore-end -->
|
190
190
|
|
191
|
-
> If you are in a project that develops the GraphQL schema, you'll need `schema` rules.
|
191
|
+
> If you are in a project that develops the GraphQL schema, you'll need `schema` rules.
|
192
192
|
|
193
193
|
> If you are in a project that develops GraphQL operations (query/mutation/subscription), you'll need `operations` rules.
|
194
194
|
|
package/configs/schema-all.json
CHANGED
@@ -4,18 +4,9 @@
|
|
4
4
|
"@graphql-eslint/alphabetize": [
|
5
5
|
"error",
|
6
6
|
{
|
7
|
-
"fields": [
|
8
|
-
"ObjectTypeDefinition",
|
9
|
-
"InterfaceTypeDefinition",
|
10
|
-
"InputObjectTypeDefinition"
|
11
|
-
],
|
7
|
+
"fields": ["ObjectTypeDefinition", "InterfaceTypeDefinition", "InputObjectTypeDefinition"],
|
12
8
|
"values": ["EnumTypeDefinition"],
|
13
|
-
"arguments": [
|
14
|
-
"FieldDefinition",
|
15
|
-
"Field",
|
16
|
-
"DirectiveDefinition",
|
17
|
-
"Directive"
|
18
|
-
]
|
9
|
+
"arguments": ["FieldDefinition", "Field", "DirectiveDefinition", "Directive"]
|
19
10
|
}
|
20
11
|
],
|
21
12
|
"@graphql-eslint/input-name": "error",
|
package/docs/README.md
CHANGED
@@ -71,5 +71,6 @@ Name &nbs
|
|
71
71
|
[variables-are-input-types](rules/variables-are-input-types.md)|A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).|![recommended][]|🔮|
|
72
72
|
[variables-in-allowed-position](rules/variables-in-allowed-position.md)|Variables passed to field arguments conform to type.|![recommended][]|🔮|
|
73
73
|
<!-- prettier-ignore-end -->
|
74
|
+
|
74
75
|
[recommended]: https://img.shields.io/badge/-recommended-green.svg
|
75
76
|
[all]: https://img.shields.io/badge/-all-blue.svg
|
package/docs/custom-rules.md
CHANGED
@@ -39,7 +39,8 @@ type User {
|
|
39
39
|
# eslint @graphql-eslint/require-deprecation-date: 'error'
|
40
40
|
|
41
41
|
type User {
|
42
|
-
firstname: String
|
42
|
+
firstname: String
|
43
|
+
@deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
|
43
44
|
firstName: String
|
44
45
|
}
|
45
46
|
```
|
package/index.js
CHANGED
@@ -114,7 +114,7 @@ const getOnDiskFilepath = (filepath) => {
|
|
114
114
|
}
|
115
115
|
return filepath;
|
116
116
|
};
|
117
|
-
const getTypeName = (node) =>
|
117
|
+
const getTypeName = (node) => 'type' in node ? getTypeName(node.type) : node.name.value;
|
118
118
|
const TYPES_KINDS = [
|
119
119
|
graphql.Kind.OBJECT_TYPE_DEFINITION,
|
120
120
|
graphql.Kind.INTERFACE_TYPE_DEFINITION,
|
@@ -189,7 +189,9 @@ function validateDocument(context, schema = null, documentNode, rule) {
|
|
189
189
|
if (token) {
|
190
190
|
loc =
|
191
191
|
// if cursor on `@` symbol than use next node
|
192
|
-
token.type === '@'
|
192
|
+
token.type === '@'
|
193
|
+
? sourceCode.getNodeByRangeIndex(token.range[1] + 1).loc
|
194
|
+
: token.loc;
|
193
195
|
}
|
194
196
|
context.report({
|
195
197
|
loc,
|
@@ -279,7 +281,9 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode, schema = [])
|
|
279
281
|
}
|
280
282
|
return {
|
281
283
|
Document(node) {
|
282
|
-
const schema = docs.requiresSchema
|
284
|
+
const schema = docs.requiresSchema
|
285
|
+
? requireGraphQLSchemaFromContext(ruleId, context)
|
286
|
+
: null;
|
283
287
|
const documentNode = getDocumentNode
|
284
288
|
? getDocumentNode({ ruleId, context, node: node.rawNode() })
|
285
289
|
: node.rawNode();
|
@@ -425,7 +429,10 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
425
429
|
requiresSiblings: true,
|
426
430
|
}, ({ ruleId, context, node }) => {
|
427
431
|
const siblings = requireSiblingsOperations(ruleId, context);
|
428
|
-
const FilePathToDocumentsMap = [
|
432
|
+
const FilePathToDocumentsMap = [
|
433
|
+
...siblings.getOperations(),
|
434
|
+
...siblings.getFragments(),
|
435
|
+
].reduce((map, { filePath, document }) => {
|
429
436
|
var _a;
|
430
437
|
(_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
|
431
438
|
map[filePath].push(document);
|
@@ -705,14 +712,6 @@ const rule = {
|
|
705
712
|
description: 'Definitions – `type`, `interface`, `enum`, `scalar`, `input`, `union` and `directive`.',
|
706
713
|
default: false,
|
707
714
|
},
|
708
|
-
ignorePrefix: {
|
709
|
-
type: 'array',
|
710
|
-
default: [],
|
711
|
-
},
|
712
|
-
ignoreSuffix: {
|
713
|
-
type: 'array',
|
714
|
-
default: [],
|
715
|
-
},
|
716
715
|
},
|
717
716
|
},
|
718
717
|
},
|
@@ -751,7 +750,9 @@ const rule = {
|
|
751
750
|
const [firstBeforeComment] = getBeforeComments(node);
|
752
751
|
const [firstAfterComment] = sourceCode.getCommentsAfter(node);
|
753
752
|
const from = firstBeforeComment || node;
|
754
|
-
const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment)
|
753
|
+
const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment)
|
754
|
+
? firstAfterComment
|
755
|
+
: node;
|
755
756
|
return [from.range[0], to.range[1]];
|
756
757
|
}
|
757
758
|
function checkNodes(nodes) {
|
@@ -759,38 +760,24 @@ const rule = {
|
|
759
760
|
// Starts from 1, ignore nodes.length <= 1
|
760
761
|
for (let i = 1; i < nodes.length; i += 1) {
|
761
762
|
const currNode = nodes[i];
|
762
|
-
const currName = ('alias' in currNode && ((_a = currNode.alias) === null || _a === void 0 ? void 0 : _a.value)) ||
|
763
|
+
const currName = ('alias' in currNode && ((_a = currNode.alias) === null || _a === void 0 ? void 0 : _a.value)) ||
|
764
|
+
('name' in currNode && ((_b = currNode.name) === null || _b === void 0 ? void 0 : _b.value));
|
763
765
|
if (!currName) {
|
764
766
|
// we don't move unnamed current nodes
|
765
767
|
continue;
|
766
768
|
}
|
767
769
|
const prevNode = nodes[i - 1];
|
768
|
-
const prevName = ('alias' in prevNode && ((_c = prevNode.alias) === null || _c === void 0 ? void 0 : _c.value)) ||
|
770
|
+
const prevName = ('alias' in prevNode && ((_c = prevNode.alias) === null || _c === void 0 ? void 0 : _c.value)) ||
|
771
|
+
('name' in prevNode && ((_d = prevNode.name) === null || _d === void 0 ? void 0 : _d.value));
|
769
772
|
if (prevName) {
|
770
|
-
if ((opts.ignorePrefix || []).length > 0) {
|
771
|
-
const shouldSkipIgnorePrefix = opts.ignorePrefix.some(prefix => prefix === prevName || prefix === currName || prevName.startsWith(prefix) || currName.startsWith(prefix));
|
772
|
-
if (shouldSkipIgnorePrefix) {
|
773
|
-
continue;
|
774
|
-
}
|
775
|
-
if (!shouldSkipIgnorePrefix) {
|
776
|
-
console.error(`${opts.ignorePrefix} is not match to "${prevName}" or "${currName}"`);
|
777
|
-
}
|
778
|
-
}
|
779
|
-
if ((opts.ignoreSuffix || []).length > 0) {
|
780
|
-
const shouldSkipIgnoreSuffix = opts.ignoreSuffix.some(suffix => suffix === prevName || suffix === currName || prevName.endsWith(suffix) || currName.endsWith(suffix));
|
781
|
-
if (shouldSkipIgnoreSuffix) {
|
782
|
-
continue;
|
783
|
-
}
|
784
|
-
if (!shouldSkipIgnoreSuffix) {
|
785
|
-
console.error(`${opts.ignoreSuffix} is not match to "${prevName}" or "${currName}"`);
|
786
|
-
}
|
787
|
-
}
|
788
773
|
// Compare with lexicographic order
|
789
774
|
const compareResult = prevName.localeCompare(currName);
|
790
775
|
const shouldSort = compareResult === 1;
|
791
776
|
if (!shouldSort) {
|
792
777
|
const isSameName = compareResult === 0;
|
793
|
-
if (!isSameName ||
|
778
|
+
if (!isSameName ||
|
779
|
+
!prevNode.kind.endsWith('Extension') ||
|
780
|
+
currNode.kind.endsWith('Extension')) {
|
794
781
|
continue;
|
795
782
|
}
|
796
783
|
}
|
@@ -815,8 +802,14 @@ const rule = {
|
|
815
802
|
const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
|
816
803
|
const listeners = {};
|
817
804
|
const kinds = [
|
818
|
-
fields.has(graphql.Kind.OBJECT_TYPE_DEFINITION) && [
|
819
|
-
|
805
|
+
fields.has(graphql.Kind.OBJECT_TYPE_DEFINITION) && [
|
806
|
+
graphql.Kind.OBJECT_TYPE_DEFINITION,
|
807
|
+
graphql.Kind.OBJECT_TYPE_EXTENSION,
|
808
|
+
],
|
809
|
+
fields.has(graphql.Kind.INTERFACE_TYPE_DEFINITION) && [
|
810
|
+
graphql.Kind.INTERFACE_TYPE_DEFINITION,
|
811
|
+
graphql.Kind.INTERFACE_TYPE_EXTENSION,
|
812
|
+
],
|
820
813
|
fields.has(graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
|
821
814
|
graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
822
815
|
graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
|
@@ -1014,7 +1007,8 @@ const rule$2 = {
|
|
1014
1007
|
checkMutations: true,
|
1015
1008
|
...context.options[0],
|
1016
1009
|
};
|
1017
|
-
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) ||
|
1010
|
+
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) ||
|
1011
|
+
(options.checkQueries && isQueryType(node));
|
1018
1012
|
const listeners = {
|
1019
1013
|
'FieldDefinition > InputValueDefinition[name.value!=input] > Name'(node) {
|
1020
1014
|
if (shouldCheckType(node.parent.parent.parent)) {
|
@@ -1250,7 +1244,8 @@ const rule$3 = {
|
|
1250
1244
|
const expectedExtension = options.fileExtension || fileExtension;
|
1251
1245
|
let expectedFilename;
|
1252
1246
|
if (option.style) {
|
1253
|
-
expectedFilename =
|
1247
|
+
expectedFilename =
|
1248
|
+
option.style === 'matchDocumentStyle' ? docName : convertCase(option.style, docName);
|
1254
1249
|
}
|
1255
1250
|
else {
|
1256
1251
|
expectedFilename = filename;
|
@@ -1564,12 +1559,10 @@ const rule$4 = {
|
|
1564
1559
|
};
|
1565
1560
|
const listeners = {};
|
1566
1561
|
if (!allowLeadingUnderscore) {
|
1567
|
-
listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
|
1568
|
-
checkUnderscore(true);
|
1562
|
+
listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(true);
|
1569
1563
|
}
|
1570
1564
|
if (!allowTrailingUnderscore) {
|
1571
|
-
listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
|
1572
|
-
checkUnderscore(false);
|
1565
|
+
listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(false);
|
1573
1566
|
}
|
1574
1567
|
const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
|
1575
1568
|
for (const selector of selectors) {
|
@@ -1617,7 +1610,9 @@ const rule$5 = {
|
|
1617
1610
|
return {
|
1618
1611
|
'OperationDefinition[name=undefined]'(node) {
|
1619
1612
|
const [firstSelection] = node.selectionSet.selections;
|
1620
|
-
const suggestedName = firstSelection.kind === graphql.Kind.FIELD
|
1613
|
+
const suggestedName = firstSelection.kind === graphql.Kind.FIELD
|
1614
|
+
? (firstSelection.alias || firstSelection.name).value
|
1615
|
+
: node.operation;
|
1621
1616
|
context.report({
|
1622
1617
|
loc: getLocation(node.loc.start, node.operation),
|
1623
1618
|
messageId: RULE_ID$1,
|
@@ -1678,7 +1673,8 @@ const rule$6 = {
|
|
1678
1673
|
const selector = [graphql.Kind.ENUM_TYPE_DEFINITION, graphql.Kind.ENUM_TYPE_EXTENSION].join(',');
|
1679
1674
|
return {
|
1680
1675
|
[selector](node) {
|
1681
|
-
const duplicates = node.values.filter((item, index, array) => array.findIndex(v => v.name.value.toLowerCase() === item.name.value.toLowerCase()) !==
|
1676
|
+
const duplicates = node.values.filter((item, index, array) => array.findIndex(v => v.name.value.toLowerCase() === item.name.value.toLowerCase()) !==
|
1677
|
+
index);
|
1682
1678
|
for (const duplicate of duplicates) {
|
1683
1679
|
const enumName = duplicate.name.value;
|
1684
1680
|
context.report({
|
@@ -1988,7 +1984,10 @@ const rule$9 = {
|
|
1988
1984
|
if (kind === graphql.TokenKind.COMMENT && prev && next) {
|
1989
1985
|
const isEslintComment = value.trimStart().startsWith('eslint');
|
1990
1986
|
const linesAfter = next.line - line;
|
1991
|
-
if (!isEslintComment &&
|
1987
|
+
if (!isEslintComment &&
|
1988
|
+
line !== prev.line &&
|
1989
|
+
next.kind === graphql.TokenKind.NAME &&
|
1990
|
+
linesAfter < 2) {
|
1992
1991
|
context.report({
|
1993
1992
|
messageId: HASHTAG_COMMENT,
|
1994
1993
|
loc: {
|
@@ -2697,7 +2696,8 @@ const rule$g = {
|
|
2697
2696
|
}
|
2698
2697
|
},
|
2699
2698
|
':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType'(node) {
|
2700
|
-
const isListType = node.kind === graphql.Kind.LIST_TYPE ||
|
2699
|
+
const isListType = node.kind === graphql.Kind.LIST_TYPE ||
|
2700
|
+
(node.kind === graphql.Kind.NON_NULL_TYPE && node.gqlType.kind === graphql.Kind.LIST_TYPE);
|
2701
2701
|
if (!isListType) {
|
2702
2702
|
context.report({ node, messageId: EDGES_FIELD_MUST_RETURN_LIST_TYPE });
|
2703
2703
|
}
|
@@ -2822,7 +2822,8 @@ const rule$h = {
|
|
2822
2822
|
listTypeCanWrapOnlyEdgeType: true,
|
2823
2823
|
...context.options[0],
|
2824
2824
|
};
|
2825
|
-
const isNamedOrNonNullNamed = (node) => node.kind === graphql.Kind.NAMED_TYPE ||
|
2825
|
+
const isNamedOrNonNullNamed = (node) => node.kind === graphql.Kind.NAMED_TYPE ||
|
2826
|
+
(node.kind === graphql.Kind.NON_NULL_TYPE && node.gqlType.kind === graphql.Kind.NAMED_TYPE);
|
2826
2827
|
const checkNodeField = (node) => {
|
2827
2828
|
const nodeField = node.fields.find(field => field.name.value === 'node');
|
2828
2829
|
const message = 'return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types.';
|
@@ -2966,7 +2967,8 @@ const rule$i = {
|
|
2966
2967
|
type.gqlType.name.value === 'Boolean';
|
2967
2968
|
}
|
2968
2969
|
else if (type.kind === graphql.Kind.NAMED_TYPE) {
|
2969
|
-
isAllowedType =
|
2970
|
+
isAllowedType =
|
2971
|
+
type.name.value === 'String' || graphql.isScalarType(schema.getType(type.name.value));
|
2970
2972
|
}
|
2971
2973
|
}
|
2972
2974
|
if (!isAllowedType) {
|
@@ -3183,7 +3185,8 @@ const rule$j = {
|
|
3183
3185
|
title: 'Correct',
|
3184
3186
|
code: /* GraphQL */ `
|
3185
3187
|
type User {
|
3186
|
-
firstname: String
|
3188
|
+
firstname: String
|
3189
|
+
@deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
|
3187
3190
|
firstName: String
|
3188
3191
|
}
|
3189
3192
|
`,
|
@@ -3422,7 +3425,8 @@ const rule$l = {
|
|
3422
3425
|
...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => {
|
3423
3426
|
let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
|
3424
3427
|
if (kind === graphql.Kind.OPERATION_DEFINITION) {
|
3425
|
-
description +=
|
3428
|
+
description +=
|
3429
|
+
'\n\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
|
3426
3430
|
}
|
3427
3431
|
return [kind, { type: 'boolean', description }];
|
3428
3432
|
})),
|
@@ -3740,7 +3744,6 @@ const RULE_ID$d = 'selection-set-depth';
|
|
3740
3744
|
const rule$o = {
|
3741
3745
|
meta: {
|
3742
3746
|
type: 'suggestion',
|
3743
|
-
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- optional since we can't provide fixes for fragments located in separate files
|
3744
3747
|
hasSuggestions: true,
|
3745
3748
|
docs: {
|
3746
3749
|
category: 'Operations',
|
@@ -3977,7 +3980,11 @@ const rule$p = {
|
|
3977
3980
|
...context.options[0],
|
3978
3981
|
};
|
3979
3982
|
const schema = requireGraphQLSchemaFromContext(RULE_ID$e, context);
|
3980
|
-
const rootTypeNames = [
|
3983
|
+
const rootTypeNames = [
|
3984
|
+
schema.getQueryType(),
|
3985
|
+
schema.getMutationType(),
|
3986
|
+
schema.getSubscriptionType(),
|
3987
|
+
]
|
3981
3988
|
.filter(Boolean)
|
3982
3989
|
.map(type => type.name);
|
3983
3990
|
const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
|
@@ -3995,7 +4002,8 @@ const rule$p = {
|
|
3995
4002
|
const isValidIdName = options.acceptedIdNames.includes(fieldNode.name.value);
|
3996
4003
|
// To be a valid type, it must be non-null and one of the accepted types.
|
3997
4004
|
let isValidIdType = false;
|
3998
|
-
if (fieldNode.type.kind === graphql.Kind.NON_NULL_TYPE &&
|
4005
|
+
if (fieldNode.type.kind === graphql.Kind.NON_NULL_TYPE &&
|
4006
|
+
fieldNode.type.type.kind === graphql.Kind.NAMED_TYPE) {
|
3999
4007
|
isValidIdType = options.acceptedIdTypes.includes(fieldNode.type.type.name.value);
|
4000
4008
|
}
|
4001
4009
|
return isValidIdName && isValidIdType;
|
@@ -4020,7 +4028,9 @@ const RULE_ID$f = 'unique-fragment-name';
|
|
4020
4028
|
const checkNode = (context, node, ruleId) => {
|
4021
4029
|
const documentName = node.name.value;
|
4022
4030
|
const siblings = requireSiblingsOperations(ruleId, context);
|
4023
|
-
const siblingDocuments = node.kind === graphql.Kind.FRAGMENT_DEFINITION
|
4031
|
+
const siblingDocuments = node.kind === graphql.Kind.FRAGMENT_DEFINITION
|
4032
|
+
? siblings.getFragment(documentName)
|
4033
|
+
: siblings.getOperation(documentName);
|
4024
4034
|
const filepath = context.getFilename();
|
4025
4035
|
const conflictingDocuments = siblingDocuments.filter(f => {
|
4026
4036
|
var _a;
|
@@ -4416,7 +4426,9 @@ function parseForESLint(code, options = {}) {
|
|
4416
4426
|
const filePath = options.filePath || '';
|
4417
4427
|
const realFilepath = filePath && getOnDiskFilepath(filePath);
|
4418
4428
|
const gqlConfig = loadGraphQLConfig(options);
|
4419
|
-
const projectForFile = realFilepath
|
4429
|
+
const projectForFile = realFilepath
|
4430
|
+
? gqlConfig.getProjectForFile(realFilepath)
|
4431
|
+
: gqlConfig.getDefault();
|
4420
4432
|
const schema = getSchema(projectForFile, options);
|
4421
4433
|
const siblingOperations = getSiblingOperations(projectForFile);
|
4422
4434
|
const { document } = utils.parseGraphQLSDL(filePath, code, {
|
package/index.mjs
CHANGED
@@ -108,7 +108,7 @@ const getOnDiskFilepath = (filepath) => {
|
|
108
108
|
}
|
109
109
|
return filepath;
|
110
110
|
};
|
111
|
-
const getTypeName = (node) =>
|
111
|
+
const getTypeName = (node) => 'type' in node ? getTypeName(node.type) : node.name.value;
|
112
112
|
const TYPES_KINDS = [
|
113
113
|
Kind.OBJECT_TYPE_DEFINITION,
|
114
114
|
Kind.INTERFACE_TYPE_DEFINITION,
|
@@ -183,7 +183,9 @@ function validateDocument(context, schema = null, documentNode, rule) {
|
|
183
183
|
if (token) {
|
184
184
|
loc =
|
185
185
|
// if cursor on `@` symbol than use next node
|
186
|
-
token.type === '@'
|
186
|
+
token.type === '@'
|
187
|
+
? sourceCode.getNodeByRangeIndex(token.range[1] + 1).loc
|
188
|
+
: token.loc;
|
187
189
|
}
|
188
190
|
context.report({
|
189
191
|
loc,
|
@@ -273,7 +275,9 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode, schema = [])
|
|
273
275
|
}
|
274
276
|
return {
|
275
277
|
Document(node) {
|
276
|
-
const schema = docs.requiresSchema
|
278
|
+
const schema = docs.requiresSchema
|
279
|
+
? requireGraphQLSchemaFromContext(ruleId, context)
|
280
|
+
: null;
|
277
281
|
const documentNode = getDocumentNode
|
278
282
|
? getDocumentNode({ ruleId, context, node: node.rawNode() })
|
279
283
|
: node.rawNode();
|
@@ -419,7 +423,10 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
419
423
|
requiresSiblings: true,
|
420
424
|
}, ({ ruleId, context, node }) => {
|
421
425
|
const siblings = requireSiblingsOperations(ruleId, context);
|
422
|
-
const FilePathToDocumentsMap = [
|
426
|
+
const FilePathToDocumentsMap = [
|
427
|
+
...siblings.getOperations(),
|
428
|
+
...siblings.getFragments(),
|
429
|
+
].reduce((map, { filePath, document }) => {
|
423
430
|
var _a;
|
424
431
|
(_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
|
425
432
|
map[filePath].push(document);
|
@@ -699,14 +706,6 @@ const rule = {
|
|
699
706
|
description: 'Definitions – `type`, `interface`, `enum`, `scalar`, `input`, `union` and `directive`.',
|
700
707
|
default: false,
|
701
708
|
},
|
702
|
-
ignorePrefix: {
|
703
|
-
type: 'array',
|
704
|
-
default: [],
|
705
|
-
},
|
706
|
-
ignoreSuffix: {
|
707
|
-
type: 'array',
|
708
|
-
default: [],
|
709
|
-
},
|
710
709
|
},
|
711
710
|
},
|
712
711
|
},
|
@@ -745,7 +744,9 @@ const rule = {
|
|
745
744
|
const [firstBeforeComment] = getBeforeComments(node);
|
746
745
|
const [firstAfterComment] = sourceCode.getCommentsAfter(node);
|
747
746
|
const from = firstBeforeComment || node;
|
748
|
-
const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment)
|
747
|
+
const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment)
|
748
|
+
? firstAfterComment
|
749
|
+
: node;
|
749
750
|
return [from.range[0], to.range[1]];
|
750
751
|
}
|
751
752
|
function checkNodes(nodes) {
|
@@ -753,38 +754,24 @@ const rule = {
|
|
753
754
|
// Starts from 1, ignore nodes.length <= 1
|
754
755
|
for (let i = 1; i < nodes.length; i += 1) {
|
755
756
|
const currNode = nodes[i];
|
756
|
-
const currName = ('alias' in currNode && ((_a = currNode.alias) === null || _a === void 0 ? void 0 : _a.value)) ||
|
757
|
+
const currName = ('alias' in currNode && ((_a = currNode.alias) === null || _a === void 0 ? void 0 : _a.value)) ||
|
758
|
+
('name' in currNode && ((_b = currNode.name) === null || _b === void 0 ? void 0 : _b.value));
|
757
759
|
if (!currName) {
|
758
760
|
// we don't move unnamed current nodes
|
759
761
|
continue;
|
760
762
|
}
|
761
763
|
const prevNode = nodes[i - 1];
|
762
|
-
const prevName = ('alias' in prevNode && ((_c = prevNode.alias) === null || _c === void 0 ? void 0 : _c.value)) ||
|
764
|
+
const prevName = ('alias' in prevNode && ((_c = prevNode.alias) === null || _c === void 0 ? void 0 : _c.value)) ||
|
765
|
+
('name' in prevNode && ((_d = prevNode.name) === null || _d === void 0 ? void 0 : _d.value));
|
763
766
|
if (prevName) {
|
764
|
-
if ((opts.ignorePrefix || []).length > 0) {
|
765
|
-
const shouldSkipIgnorePrefix = opts.ignorePrefix.some(prefix => prefix === prevName || prefix === currName || prevName.startsWith(prefix) || currName.startsWith(prefix));
|
766
|
-
if (shouldSkipIgnorePrefix) {
|
767
|
-
continue;
|
768
|
-
}
|
769
|
-
if (!shouldSkipIgnorePrefix) {
|
770
|
-
console.error(`${opts.ignorePrefix} is not match to "${prevName}" or "${currName}"`);
|
771
|
-
}
|
772
|
-
}
|
773
|
-
if ((opts.ignoreSuffix || []).length > 0) {
|
774
|
-
const shouldSkipIgnoreSuffix = opts.ignoreSuffix.some(suffix => suffix === prevName || suffix === currName || prevName.endsWith(suffix) || currName.endsWith(suffix));
|
775
|
-
if (shouldSkipIgnoreSuffix) {
|
776
|
-
continue;
|
777
|
-
}
|
778
|
-
if (!shouldSkipIgnoreSuffix) {
|
779
|
-
console.error(`${opts.ignoreSuffix} is not match to "${prevName}" or "${currName}"`);
|
780
|
-
}
|
781
|
-
}
|
782
767
|
// Compare with lexicographic order
|
783
768
|
const compareResult = prevName.localeCompare(currName);
|
784
769
|
const shouldSort = compareResult === 1;
|
785
770
|
if (!shouldSort) {
|
786
771
|
const isSameName = compareResult === 0;
|
787
|
-
if (!isSameName ||
|
772
|
+
if (!isSameName ||
|
773
|
+
!prevNode.kind.endsWith('Extension') ||
|
774
|
+
currNode.kind.endsWith('Extension')) {
|
788
775
|
continue;
|
789
776
|
}
|
790
777
|
}
|
@@ -809,8 +796,14 @@ const rule = {
|
|
809
796
|
const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
|
810
797
|
const listeners = {};
|
811
798
|
const kinds = [
|
812
|
-
fields.has(Kind.OBJECT_TYPE_DEFINITION) && [
|
813
|
-
|
799
|
+
fields.has(Kind.OBJECT_TYPE_DEFINITION) && [
|
800
|
+
Kind.OBJECT_TYPE_DEFINITION,
|
801
|
+
Kind.OBJECT_TYPE_EXTENSION,
|
802
|
+
],
|
803
|
+
fields.has(Kind.INTERFACE_TYPE_DEFINITION) && [
|
804
|
+
Kind.INTERFACE_TYPE_DEFINITION,
|
805
|
+
Kind.INTERFACE_TYPE_EXTENSION,
|
806
|
+
],
|
814
807
|
fields.has(Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
|
815
808
|
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
816
809
|
Kind.INPUT_OBJECT_TYPE_EXTENSION,
|
@@ -1008,7 +1001,8 @@ const rule$2 = {
|
|
1008
1001
|
checkMutations: true,
|
1009
1002
|
...context.options[0],
|
1010
1003
|
};
|
1011
|
-
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) ||
|
1004
|
+
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) ||
|
1005
|
+
(options.checkQueries && isQueryType(node));
|
1012
1006
|
const listeners = {
|
1013
1007
|
'FieldDefinition > InputValueDefinition[name.value!=input] > Name'(node) {
|
1014
1008
|
if (shouldCheckType(node.parent.parent.parent)) {
|
@@ -1244,7 +1238,8 @@ const rule$3 = {
|
|
1244
1238
|
const expectedExtension = options.fileExtension || fileExtension;
|
1245
1239
|
let expectedFilename;
|
1246
1240
|
if (option.style) {
|
1247
|
-
expectedFilename =
|
1241
|
+
expectedFilename =
|
1242
|
+
option.style === 'matchDocumentStyle' ? docName : convertCase(option.style, docName);
|
1248
1243
|
}
|
1249
1244
|
else {
|
1250
1245
|
expectedFilename = filename;
|
@@ -1558,12 +1553,10 @@ const rule$4 = {
|
|
1558
1553
|
};
|
1559
1554
|
const listeners = {};
|
1560
1555
|
if (!allowLeadingUnderscore) {
|
1561
|
-
listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
|
1562
|
-
checkUnderscore(true);
|
1556
|
+
listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(true);
|
1563
1557
|
}
|
1564
1558
|
if (!allowTrailingUnderscore) {
|
1565
|
-
listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
|
1566
|
-
checkUnderscore(false);
|
1559
|
+
listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(false);
|
1567
1560
|
}
|
1568
1561
|
const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
|
1569
1562
|
for (const selector of selectors) {
|
@@ -1611,7 +1604,9 @@ const rule$5 = {
|
|
1611
1604
|
return {
|
1612
1605
|
'OperationDefinition[name=undefined]'(node) {
|
1613
1606
|
const [firstSelection] = node.selectionSet.selections;
|
1614
|
-
const suggestedName = firstSelection.kind === Kind.FIELD
|
1607
|
+
const suggestedName = firstSelection.kind === Kind.FIELD
|
1608
|
+
? (firstSelection.alias || firstSelection.name).value
|
1609
|
+
: node.operation;
|
1615
1610
|
context.report({
|
1616
1611
|
loc: getLocation(node.loc.start, node.operation),
|
1617
1612
|
messageId: RULE_ID$1,
|
@@ -1672,7 +1667,8 @@ const rule$6 = {
|
|
1672
1667
|
const selector = [Kind.ENUM_TYPE_DEFINITION, Kind.ENUM_TYPE_EXTENSION].join(',');
|
1673
1668
|
return {
|
1674
1669
|
[selector](node) {
|
1675
|
-
const duplicates = node.values.filter((item, index, array) => array.findIndex(v => v.name.value.toLowerCase() === item.name.value.toLowerCase()) !==
|
1670
|
+
const duplicates = node.values.filter((item, index, array) => array.findIndex(v => v.name.value.toLowerCase() === item.name.value.toLowerCase()) !==
|
1671
|
+
index);
|
1676
1672
|
for (const duplicate of duplicates) {
|
1677
1673
|
const enumName = duplicate.name.value;
|
1678
1674
|
context.report({
|
@@ -1982,7 +1978,10 @@ const rule$9 = {
|
|
1982
1978
|
if (kind === TokenKind.COMMENT && prev && next) {
|
1983
1979
|
const isEslintComment = value.trimStart().startsWith('eslint');
|
1984
1980
|
const linesAfter = next.line - line;
|
1985
|
-
if (!isEslintComment &&
|
1981
|
+
if (!isEslintComment &&
|
1982
|
+
line !== prev.line &&
|
1983
|
+
next.kind === TokenKind.NAME &&
|
1984
|
+
linesAfter < 2) {
|
1986
1985
|
context.report({
|
1987
1986
|
messageId: HASHTAG_COMMENT,
|
1988
1987
|
loc: {
|
@@ -2691,7 +2690,8 @@ const rule$g = {
|
|
2691
2690
|
}
|
2692
2691
|
},
|
2693
2692
|
':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType'(node) {
|
2694
|
-
const isListType = node.kind === Kind.LIST_TYPE ||
|
2693
|
+
const isListType = node.kind === Kind.LIST_TYPE ||
|
2694
|
+
(node.kind === Kind.NON_NULL_TYPE && node.gqlType.kind === Kind.LIST_TYPE);
|
2695
2695
|
if (!isListType) {
|
2696
2696
|
context.report({ node, messageId: EDGES_FIELD_MUST_RETURN_LIST_TYPE });
|
2697
2697
|
}
|
@@ -2816,7 +2816,8 @@ const rule$h = {
|
|
2816
2816
|
listTypeCanWrapOnlyEdgeType: true,
|
2817
2817
|
...context.options[0],
|
2818
2818
|
};
|
2819
|
-
const isNamedOrNonNullNamed = (node) => node.kind === Kind.NAMED_TYPE ||
|
2819
|
+
const isNamedOrNonNullNamed = (node) => node.kind === Kind.NAMED_TYPE ||
|
2820
|
+
(node.kind === Kind.NON_NULL_TYPE && node.gqlType.kind === Kind.NAMED_TYPE);
|
2820
2821
|
const checkNodeField = (node) => {
|
2821
2822
|
const nodeField = node.fields.find(field => field.name.value === 'node');
|
2822
2823
|
const message = 'return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types.';
|
@@ -2960,7 +2961,8 @@ const rule$i = {
|
|
2960
2961
|
type.gqlType.name.value === 'Boolean';
|
2961
2962
|
}
|
2962
2963
|
else if (type.kind === Kind.NAMED_TYPE) {
|
2963
|
-
isAllowedType =
|
2964
|
+
isAllowedType =
|
2965
|
+
type.name.value === 'String' || isScalarType(schema.getType(type.name.value));
|
2964
2966
|
}
|
2965
2967
|
}
|
2966
2968
|
if (!isAllowedType) {
|
@@ -3177,7 +3179,8 @@ const rule$j = {
|
|
3177
3179
|
title: 'Correct',
|
3178
3180
|
code: /* GraphQL */ `
|
3179
3181
|
type User {
|
3180
|
-
firstname: String
|
3182
|
+
firstname: String
|
3183
|
+
@deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
|
3181
3184
|
firstName: String
|
3182
3185
|
}
|
3183
3186
|
`,
|
@@ -3416,7 +3419,8 @@ const rule$l = {
|
|
3416
3419
|
...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => {
|
3417
3420
|
let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
|
3418
3421
|
if (kind === Kind.OPERATION_DEFINITION) {
|
3419
|
-
description +=
|
3422
|
+
description +=
|
3423
|
+
'\n\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
|
3420
3424
|
}
|
3421
3425
|
return [kind, { type: 'boolean', description }];
|
3422
3426
|
})),
|
@@ -3734,7 +3738,6 @@ const RULE_ID$d = 'selection-set-depth';
|
|
3734
3738
|
const rule$o = {
|
3735
3739
|
meta: {
|
3736
3740
|
type: 'suggestion',
|
3737
|
-
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- optional since we can't provide fixes for fragments located in separate files
|
3738
3741
|
hasSuggestions: true,
|
3739
3742
|
docs: {
|
3740
3743
|
category: 'Operations',
|
@@ -3971,7 +3974,11 @@ const rule$p = {
|
|
3971
3974
|
...context.options[0],
|
3972
3975
|
};
|
3973
3976
|
const schema = requireGraphQLSchemaFromContext(RULE_ID$e, context);
|
3974
|
-
const rootTypeNames = [
|
3977
|
+
const rootTypeNames = [
|
3978
|
+
schema.getQueryType(),
|
3979
|
+
schema.getMutationType(),
|
3980
|
+
schema.getSubscriptionType(),
|
3981
|
+
]
|
3975
3982
|
.filter(Boolean)
|
3976
3983
|
.map(type => type.name);
|
3977
3984
|
const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
|
@@ -3989,7 +3996,8 @@ const rule$p = {
|
|
3989
3996
|
const isValidIdName = options.acceptedIdNames.includes(fieldNode.name.value);
|
3990
3997
|
// To be a valid type, it must be non-null and one of the accepted types.
|
3991
3998
|
let isValidIdType = false;
|
3992
|
-
if (fieldNode.type.kind === Kind.NON_NULL_TYPE &&
|
3999
|
+
if (fieldNode.type.kind === Kind.NON_NULL_TYPE &&
|
4000
|
+
fieldNode.type.type.kind === Kind.NAMED_TYPE) {
|
3993
4001
|
isValidIdType = options.acceptedIdTypes.includes(fieldNode.type.type.name.value);
|
3994
4002
|
}
|
3995
4003
|
return isValidIdName && isValidIdType;
|
@@ -4014,7 +4022,9 @@ const RULE_ID$f = 'unique-fragment-name';
|
|
4014
4022
|
const checkNode = (context, node, ruleId) => {
|
4015
4023
|
const documentName = node.name.value;
|
4016
4024
|
const siblings = requireSiblingsOperations(ruleId, context);
|
4017
|
-
const siblingDocuments = node.kind === Kind.FRAGMENT_DEFINITION
|
4025
|
+
const siblingDocuments = node.kind === Kind.FRAGMENT_DEFINITION
|
4026
|
+
? siblings.getFragment(documentName)
|
4027
|
+
: siblings.getOperation(documentName);
|
4018
4028
|
const filepath = context.getFilename();
|
4019
4029
|
const conflictingDocuments = siblingDocuments.filter(f => {
|
4020
4030
|
var _a;
|
@@ -4410,7 +4420,9 @@ function parseForESLint(code, options = {}) {
|
|
4410
4420
|
const filePath = options.filePath || '';
|
4411
4421
|
const realFilepath = filePath && getOnDiskFilepath(filePath);
|
4412
4422
|
const gqlConfig = loadGraphQLConfig(options);
|
4413
|
-
const projectForFile = realFilepath
|
4423
|
+
const projectForFile = realFilepath
|
4424
|
+
? gqlConfig.getProjectForFile(realFilepath)
|
4425
|
+
: gqlConfig.getDefault();
|
4414
4426
|
const schema = getSchema(projectForFile, options);
|
4415
4427
|
const siblingOperations = getSiblingOperations(projectForFile);
|
4416
4428
|
const { document } = parseGraphQLSDL(filePath, code, {
|
package/package.json
CHANGED
package/rules/alphabetize.d.ts
CHANGED
@@ -11,8 +11,6 @@ export declare type AlphabetizeConfig = {
|
|
11
11
|
variables?: typeof variablesEnum;
|
12
12
|
arguments?: typeof argumentsEnum;
|
13
13
|
definitions?: boolean;
|
14
|
-
ignorePrefix?: string[];
|
15
|
-
ignoreSuffix?: string[];
|
16
14
|
};
|
17
15
|
declare const rule: GraphQLESLintRule<[AlphabetizeConfig]>;
|
18
16
|
export default rule;
|
package/types.d.ts
CHANGED
@@ -47,26 +47,29 @@ export declare type GraphQLESLintRuleContext<Options = any[]> = Omit<Rule.RuleCo
|
|
47
47
|
};
|
48
48
|
export declare type CategoryType = 'Schema' | 'Operations';
|
49
49
|
export declare type RuleDocsInfo<T> = {
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
isDisabledForAllConfig?: true;
|
50
|
+
description: string;
|
51
|
+
category: CategoryType | CategoryType[];
|
52
|
+
recommended?: boolean;
|
53
|
+
url: string;
|
54
|
+
requiresSchema?: true;
|
55
|
+
requiresSiblings?: true;
|
56
|
+
examples?: {
|
57
|
+
title: string;
|
58
|
+
code: string;
|
59
|
+
usage?: T;
|
60
|
+
}[];
|
61
|
+
configOptions?: T | {
|
62
|
+
schema?: T;
|
63
|
+
operations?: T;
|
65
64
|
};
|
65
|
+
graphQLJSRuleName?: string;
|
66
|
+
isDisabledForAllConfig?: true;
|
66
67
|
};
|
67
68
|
export declare type GraphQLESLintRule<Options = any[], WithTypeInfo extends boolean = false> = {
|
68
69
|
create(context: GraphQLESLintRuleContext<Options>): GraphQLESLintRuleListener<WithTypeInfo>;
|
69
|
-
meta: Omit<Rule.RuleMetaData, 'docs'> &
|
70
|
+
meta: Omit<Rule.RuleMetaData, 'docs'> & {
|
71
|
+
docs: RuleDocsInfo<Options>;
|
72
|
+
};
|
70
73
|
};
|
71
74
|
export declare type ValueOf<T> = T[keyof T];
|
72
75
|
declare type Id<T> = {} & {
|