@graphql-eslint/eslint-plugin 3.11.0-alpha-20220828135635-ce6bf36 → 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 -49
- package/index.mjs +67 -49
- 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,32 +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
|
-
}
|
776
|
-
if ((opts.ignoreSuffix || []).length > 0) {
|
777
|
-
const shouldSkipIgnoreSuffix = opts.ignoreSuffix.some(suffix => suffix === prevName || suffix === currName || prevName.endsWith(suffix) || currName.endsWith(suffix));
|
778
|
-
if (shouldSkipIgnoreSuffix) {
|
779
|
-
continue;
|
780
|
-
}
|
781
|
-
}
|
782
773
|
// Compare with lexicographic order
|
783
774
|
const compareResult = prevName.localeCompare(currName);
|
784
775
|
const shouldSort = compareResult === 1;
|
785
776
|
if (!shouldSort) {
|
786
777
|
const isSameName = compareResult === 0;
|
787
|
-
if (!isSameName ||
|
778
|
+
if (!isSameName ||
|
779
|
+
!prevNode.kind.endsWith('Extension') ||
|
780
|
+
currNode.kind.endsWith('Extension')) {
|
788
781
|
continue;
|
789
782
|
}
|
790
783
|
}
|
@@ -809,8 +802,14 @@ const rule = {
|
|
809
802
|
const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
|
810
803
|
const listeners = {};
|
811
804
|
const kinds = [
|
812
|
-
fields.has(graphql.Kind.OBJECT_TYPE_DEFINITION) && [
|
813
|
-
|
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
|
+
],
|
814
813
|
fields.has(graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
|
815
814
|
graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
816
815
|
graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
|
@@ -1008,7 +1007,8 @@ const rule$2 = {
|
|
1008
1007
|
checkMutations: true,
|
1009
1008
|
...context.options[0],
|
1010
1009
|
};
|
1011
|
-
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) ||
|
1010
|
+
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) ||
|
1011
|
+
(options.checkQueries && isQueryType(node));
|
1012
1012
|
const listeners = {
|
1013
1013
|
'FieldDefinition > InputValueDefinition[name.value!=input] > Name'(node) {
|
1014
1014
|
if (shouldCheckType(node.parent.parent.parent)) {
|
@@ -1244,7 +1244,8 @@ const rule$3 = {
|
|
1244
1244
|
const expectedExtension = options.fileExtension || fileExtension;
|
1245
1245
|
let expectedFilename;
|
1246
1246
|
if (option.style) {
|
1247
|
-
expectedFilename =
|
1247
|
+
expectedFilename =
|
1248
|
+
option.style === 'matchDocumentStyle' ? docName : convertCase(option.style, docName);
|
1248
1249
|
}
|
1249
1250
|
else {
|
1250
1251
|
expectedFilename = filename;
|
@@ -1558,12 +1559,10 @@ const rule$4 = {
|
|
1558
1559
|
};
|
1559
1560
|
const listeners = {};
|
1560
1561
|
if (!allowLeadingUnderscore) {
|
1561
|
-
listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
|
1562
|
-
checkUnderscore(true);
|
1562
|
+
listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(true);
|
1563
1563
|
}
|
1564
1564
|
if (!allowTrailingUnderscore) {
|
1565
|
-
listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
|
1566
|
-
checkUnderscore(false);
|
1565
|
+
listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(false);
|
1567
1566
|
}
|
1568
1567
|
const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
|
1569
1568
|
for (const selector of selectors) {
|
@@ -1611,7 +1610,9 @@ const rule$5 = {
|
|
1611
1610
|
return {
|
1612
1611
|
'OperationDefinition[name=undefined]'(node) {
|
1613
1612
|
const [firstSelection] = node.selectionSet.selections;
|
1614
|
-
const suggestedName = firstSelection.kind === graphql.Kind.FIELD
|
1613
|
+
const suggestedName = firstSelection.kind === graphql.Kind.FIELD
|
1614
|
+
? (firstSelection.alias || firstSelection.name).value
|
1615
|
+
: node.operation;
|
1615
1616
|
context.report({
|
1616
1617
|
loc: getLocation(node.loc.start, node.operation),
|
1617
1618
|
messageId: RULE_ID$1,
|
@@ -1672,7 +1673,8 @@ const rule$6 = {
|
|
1672
1673
|
const selector = [graphql.Kind.ENUM_TYPE_DEFINITION, graphql.Kind.ENUM_TYPE_EXTENSION].join(',');
|
1673
1674
|
return {
|
1674
1675
|
[selector](node) {
|
1675
|
-
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);
|
1676
1678
|
for (const duplicate of duplicates) {
|
1677
1679
|
const enumName = duplicate.name.value;
|
1678
1680
|
context.report({
|
@@ -1982,7 +1984,10 @@ const rule$9 = {
|
|
1982
1984
|
if (kind === graphql.TokenKind.COMMENT && prev && next) {
|
1983
1985
|
const isEslintComment = value.trimStart().startsWith('eslint');
|
1984
1986
|
const linesAfter = next.line - line;
|
1985
|
-
if (!isEslintComment &&
|
1987
|
+
if (!isEslintComment &&
|
1988
|
+
line !== prev.line &&
|
1989
|
+
next.kind === graphql.TokenKind.NAME &&
|
1990
|
+
linesAfter < 2) {
|
1986
1991
|
context.report({
|
1987
1992
|
messageId: HASHTAG_COMMENT,
|
1988
1993
|
loc: {
|
@@ -2691,7 +2696,8 @@ const rule$g = {
|
|
2691
2696
|
}
|
2692
2697
|
},
|
2693
2698
|
':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType'(node) {
|
2694
|
-
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);
|
2695
2701
|
if (!isListType) {
|
2696
2702
|
context.report({ node, messageId: EDGES_FIELD_MUST_RETURN_LIST_TYPE });
|
2697
2703
|
}
|
@@ -2816,7 +2822,8 @@ const rule$h = {
|
|
2816
2822
|
listTypeCanWrapOnlyEdgeType: true,
|
2817
2823
|
...context.options[0],
|
2818
2824
|
};
|
2819
|
-
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);
|
2820
2827
|
const checkNodeField = (node) => {
|
2821
2828
|
const nodeField = node.fields.find(field => field.name.value === 'node');
|
2822
2829
|
const message = 'return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types.';
|
@@ -2960,7 +2967,8 @@ const rule$i = {
|
|
2960
2967
|
type.gqlType.name.value === 'Boolean';
|
2961
2968
|
}
|
2962
2969
|
else if (type.kind === graphql.Kind.NAMED_TYPE) {
|
2963
|
-
isAllowedType =
|
2970
|
+
isAllowedType =
|
2971
|
+
type.name.value === 'String' || graphql.isScalarType(schema.getType(type.name.value));
|
2964
2972
|
}
|
2965
2973
|
}
|
2966
2974
|
if (!isAllowedType) {
|
@@ -3177,7 +3185,8 @@ const rule$j = {
|
|
3177
3185
|
title: 'Correct',
|
3178
3186
|
code: /* GraphQL */ `
|
3179
3187
|
type User {
|
3180
|
-
firstname: String
|
3188
|
+
firstname: String
|
3189
|
+
@deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
|
3181
3190
|
firstName: String
|
3182
3191
|
}
|
3183
3192
|
`,
|
@@ -3416,7 +3425,8 @@ const rule$l = {
|
|
3416
3425
|
...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => {
|
3417
3426
|
let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
|
3418
3427
|
if (kind === graphql.Kind.OPERATION_DEFINITION) {
|
3419
|
-
description +=
|
3428
|
+
description +=
|
3429
|
+
'\n\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
|
3420
3430
|
}
|
3421
3431
|
return [kind, { type: 'boolean', description }];
|
3422
3432
|
})),
|
@@ -3734,7 +3744,6 @@ const RULE_ID$d = 'selection-set-depth';
|
|
3734
3744
|
const rule$o = {
|
3735
3745
|
meta: {
|
3736
3746
|
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
3747
|
hasSuggestions: true,
|
3739
3748
|
docs: {
|
3740
3749
|
category: 'Operations',
|
@@ -3971,7 +3980,11 @@ const rule$p = {
|
|
3971
3980
|
...context.options[0],
|
3972
3981
|
};
|
3973
3982
|
const schema = requireGraphQLSchemaFromContext(RULE_ID$e, context);
|
3974
|
-
const rootTypeNames = [
|
3983
|
+
const rootTypeNames = [
|
3984
|
+
schema.getQueryType(),
|
3985
|
+
schema.getMutationType(),
|
3986
|
+
schema.getSubscriptionType(),
|
3987
|
+
]
|
3975
3988
|
.filter(Boolean)
|
3976
3989
|
.map(type => type.name);
|
3977
3990
|
const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
|
@@ -3989,7 +4002,8 @@ const rule$p = {
|
|
3989
4002
|
const isValidIdName = options.acceptedIdNames.includes(fieldNode.name.value);
|
3990
4003
|
// To be a valid type, it must be non-null and one of the accepted types.
|
3991
4004
|
let isValidIdType = false;
|
3992
|
-
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) {
|
3993
4007
|
isValidIdType = options.acceptedIdTypes.includes(fieldNode.type.type.name.value);
|
3994
4008
|
}
|
3995
4009
|
return isValidIdName && isValidIdType;
|
@@ -4014,7 +4028,9 @@ const RULE_ID$f = 'unique-fragment-name';
|
|
4014
4028
|
const checkNode = (context, node, ruleId) => {
|
4015
4029
|
const documentName = node.name.value;
|
4016
4030
|
const siblings = requireSiblingsOperations(ruleId, context);
|
4017
|
-
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);
|
4018
4034
|
const filepath = context.getFilename();
|
4019
4035
|
const conflictingDocuments = siblingDocuments.filter(f => {
|
4020
4036
|
var _a;
|
@@ -4410,7 +4426,9 @@ function parseForESLint(code, options = {}) {
|
|
4410
4426
|
const filePath = options.filePath || '';
|
4411
4427
|
const realFilepath = filePath && getOnDiskFilepath(filePath);
|
4412
4428
|
const gqlConfig = loadGraphQLConfig(options);
|
4413
|
-
const projectForFile = realFilepath
|
4429
|
+
const projectForFile = realFilepath
|
4430
|
+
? gqlConfig.getProjectForFile(realFilepath)
|
4431
|
+
: gqlConfig.getDefault();
|
4414
4432
|
const schema = getSchema(projectForFile, options);
|
4415
4433
|
const siblingOperations = getSiblingOperations(projectForFile);
|
4416
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,32 +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
|
-
}
|
770
|
-
if ((opts.ignoreSuffix || []).length > 0) {
|
771
|
-
const shouldSkipIgnoreSuffix = opts.ignoreSuffix.some(suffix => suffix === prevName || suffix === currName || prevName.endsWith(suffix) || currName.endsWith(suffix));
|
772
|
-
if (shouldSkipIgnoreSuffix) {
|
773
|
-
continue;
|
774
|
-
}
|
775
|
-
}
|
776
767
|
// Compare with lexicographic order
|
777
768
|
const compareResult = prevName.localeCompare(currName);
|
778
769
|
const shouldSort = compareResult === 1;
|
779
770
|
if (!shouldSort) {
|
780
771
|
const isSameName = compareResult === 0;
|
781
|
-
if (!isSameName ||
|
772
|
+
if (!isSameName ||
|
773
|
+
!prevNode.kind.endsWith('Extension') ||
|
774
|
+
currNode.kind.endsWith('Extension')) {
|
782
775
|
continue;
|
783
776
|
}
|
784
777
|
}
|
@@ -803,8 +796,14 @@ const rule = {
|
|
803
796
|
const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
|
804
797
|
const listeners = {};
|
805
798
|
const kinds = [
|
806
|
-
fields.has(Kind.OBJECT_TYPE_DEFINITION) && [
|
807
|
-
|
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
|
+
],
|
808
807
|
fields.has(Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
|
809
808
|
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
810
809
|
Kind.INPUT_OBJECT_TYPE_EXTENSION,
|
@@ -1002,7 +1001,8 @@ const rule$2 = {
|
|
1002
1001
|
checkMutations: true,
|
1003
1002
|
...context.options[0],
|
1004
1003
|
};
|
1005
|
-
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) ||
|
1004
|
+
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) ||
|
1005
|
+
(options.checkQueries && isQueryType(node));
|
1006
1006
|
const listeners = {
|
1007
1007
|
'FieldDefinition > InputValueDefinition[name.value!=input] > Name'(node) {
|
1008
1008
|
if (shouldCheckType(node.parent.parent.parent)) {
|
@@ -1238,7 +1238,8 @@ const rule$3 = {
|
|
1238
1238
|
const expectedExtension = options.fileExtension || fileExtension;
|
1239
1239
|
let expectedFilename;
|
1240
1240
|
if (option.style) {
|
1241
|
-
expectedFilename =
|
1241
|
+
expectedFilename =
|
1242
|
+
option.style === 'matchDocumentStyle' ? docName : convertCase(option.style, docName);
|
1242
1243
|
}
|
1243
1244
|
else {
|
1244
1245
|
expectedFilename = filename;
|
@@ -1552,12 +1553,10 @@ const rule$4 = {
|
|
1552
1553
|
};
|
1553
1554
|
const listeners = {};
|
1554
1555
|
if (!allowLeadingUnderscore) {
|
1555
|
-
listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
|
1556
|
-
checkUnderscore(true);
|
1556
|
+
listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(true);
|
1557
1557
|
}
|
1558
1558
|
if (!allowTrailingUnderscore) {
|
1559
|
-
listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
|
1560
|
-
checkUnderscore(false);
|
1559
|
+
listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(false);
|
1561
1560
|
}
|
1562
1561
|
const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
|
1563
1562
|
for (const selector of selectors) {
|
@@ -1605,7 +1604,9 @@ const rule$5 = {
|
|
1605
1604
|
return {
|
1606
1605
|
'OperationDefinition[name=undefined]'(node) {
|
1607
1606
|
const [firstSelection] = node.selectionSet.selections;
|
1608
|
-
const suggestedName = firstSelection.kind === Kind.FIELD
|
1607
|
+
const suggestedName = firstSelection.kind === Kind.FIELD
|
1608
|
+
? (firstSelection.alias || firstSelection.name).value
|
1609
|
+
: node.operation;
|
1609
1610
|
context.report({
|
1610
1611
|
loc: getLocation(node.loc.start, node.operation),
|
1611
1612
|
messageId: RULE_ID$1,
|
@@ -1666,7 +1667,8 @@ const rule$6 = {
|
|
1666
1667
|
const selector = [Kind.ENUM_TYPE_DEFINITION, Kind.ENUM_TYPE_EXTENSION].join(',');
|
1667
1668
|
return {
|
1668
1669
|
[selector](node) {
|
1669
|
-
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);
|
1670
1672
|
for (const duplicate of duplicates) {
|
1671
1673
|
const enumName = duplicate.name.value;
|
1672
1674
|
context.report({
|
@@ -1976,7 +1978,10 @@ const rule$9 = {
|
|
1976
1978
|
if (kind === TokenKind.COMMENT && prev && next) {
|
1977
1979
|
const isEslintComment = value.trimStart().startsWith('eslint');
|
1978
1980
|
const linesAfter = next.line - line;
|
1979
|
-
if (!isEslintComment &&
|
1981
|
+
if (!isEslintComment &&
|
1982
|
+
line !== prev.line &&
|
1983
|
+
next.kind === TokenKind.NAME &&
|
1984
|
+
linesAfter < 2) {
|
1980
1985
|
context.report({
|
1981
1986
|
messageId: HASHTAG_COMMENT,
|
1982
1987
|
loc: {
|
@@ -2685,7 +2690,8 @@ const rule$g = {
|
|
2685
2690
|
}
|
2686
2691
|
},
|
2687
2692
|
':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType'(node) {
|
2688
|
-
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);
|
2689
2695
|
if (!isListType) {
|
2690
2696
|
context.report({ node, messageId: EDGES_FIELD_MUST_RETURN_LIST_TYPE });
|
2691
2697
|
}
|
@@ -2810,7 +2816,8 @@ const rule$h = {
|
|
2810
2816
|
listTypeCanWrapOnlyEdgeType: true,
|
2811
2817
|
...context.options[0],
|
2812
2818
|
};
|
2813
|
-
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);
|
2814
2821
|
const checkNodeField = (node) => {
|
2815
2822
|
const nodeField = node.fields.find(field => field.name.value === 'node');
|
2816
2823
|
const message = 'return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types.';
|
@@ -2954,7 +2961,8 @@ const rule$i = {
|
|
2954
2961
|
type.gqlType.name.value === 'Boolean';
|
2955
2962
|
}
|
2956
2963
|
else if (type.kind === Kind.NAMED_TYPE) {
|
2957
|
-
isAllowedType =
|
2964
|
+
isAllowedType =
|
2965
|
+
type.name.value === 'String' || isScalarType(schema.getType(type.name.value));
|
2958
2966
|
}
|
2959
2967
|
}
|
2960
2968
|
if (!isAllowedType) {
|
@@ -3171,7 +3179,8 @@ const rule$j = {
|
|
3171
3179
|
title: 'Correct',
|
3172
3180
|
code: /* GraphQL */ `
|
3173
3181
|
type User {
|
3174
|
-
firstname: String
|
3182
|
+
firstname: String
|
3183
|
+
@deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
|
3175
3184
|
firstName: String
|
3176
3185
|
}
|
3177
3186
|
`,
|
@@ -3410,7 +3419,8 @@ const rule$l = {
|
|
3410
3419
|
...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => {
|
3411
3420
|
let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
|
3412
3421
|
if (kind === Kind.OPERATION_DEFINITION) {
|
3413
|
-
description +=
|
3422
|
+
description +=
|
3423
|
+
'\n\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
|
3414
3424
|
}
|
3415
3425
|
return [kind, { type: 'boolean', description }];
|
3416
3426
|
})),
|
@@ -3728,7 +3738,6 @@ const RULE_ID$d = 'selection-set-depth';
|
|
3728
3738
|
const rule$o = {
|
3729
3739
|
meta: {
|
3730
3740
|
type: 'suggestion',
|
3731
|
-
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- optional since we can't provide fixes for fragments located in separate files
|
3732
3741
|
hasSuggestions: true,
|
3733
3742
|
docs: {
|
3734
3743
|
category: 'Operations',
|
@@ -3965,7 +3974,11 @@ const rule$p = {
|
|
3965
3974
|
...context.options[0],
|
3966
3975
|
};
|
3967
3976
|
const schema = requireGraphQLSchemaFromContext(RULE_ID$e, context);
|
3968
|
-
const rootTypeNames = [
|
3977
|
+
const rootTypeNames = [
|
3978
|
+
schema.getQueryType(),
|
3979
|
+
schema.getMutationType(),
|
3980
|
+
schema.getSubscriptionType(),
|
3981
|
+
]
|
3969
3982
|
.filter(Boolean)
|
3970
3983
|
.map(type => type.name);
|
3971
3984
|
const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
|
@@ -3983,7 +3996,8 @@ const rule$p = {
|
|
3983
3996
|
const isValidIdName = options.acceptedIdNames.includes(fieldNode.name.value);
|
3984
3997
|
// To be a valid type, it must be non-null and one of the accepted types.
|
3985
3998
|
let isValidIdType = false;
|
3986
|
-
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) {
|
3987
4001
|
isValidIdType = options.acceptedIdTypes.includes(fieldNode.type.type.name.value);
|
3988
4002
|
}
|
3989
4003
|
return isValidIdName && isValidIdType;
|
@@ -4008,7 +4022,9 @@ const RULE_ID$f = 'unique-fragment-name';
|
|
4008
4022
|
const checkNode = (context, node, ruleId) => {
|
4009
4023
|
const documentName = node.name.value;
|
4010
4024
|
const siblings = requireSiblingsOperations(ruleId, context);
|
4011
|
-
const siblingDocuments = node.kind === Kind.FRAGMENT_DEFINITION
|
4025
|
+
const siblingDocuments = node.kind === Kind.FRAGMENT_DEFINITION
|
4026
|
+
? siblings.getFragment(documentName)
|
4027
|
+
: siblings.getOperation(documentName);
|
4012
4028
|
const filepath = context.getFilename();
|
4013
4029
|
const conflictingDocuments = siblingDocuments.filter(f => {
|
4014
4030
|
var _a;
|
@@ -4404,7 +4420,9 @@ function parseForESLint(code, options = {}) {
|
|
4404
4420
|
const filePath = options.filePath || '';
|
4405
4421
|
const realFilepath = filePath && getOnDiskFilepath(filePath);
|
4406
4422
|
const gqlConfig = loadGraphQLConfig(options);
|
4407
|
-
const projectForFile = realFilepath
|
4423
|
+
const projectForFile = realFilepath
|
4424
|
+
? gqlConfig.getProjectForFile(realFilepath)
|
4425
|
+
: gqlConfig.getDefault();
|
4408
4426
|
const schema = getSchema(projectForFile, options);
|
4409
4427
|
const siblingOperations = getSiblingOperations(projectForFile);
|
4410
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> = {} & {
|