@graphql-eslint/eslint-plugin 3.11.0-alpha-20220828135635-ce6bf36 → 3.11.0-alpha-20220923192439-db921ff
Sign up to get free protection for your applications and to get access to all the features.
- 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> = {} & {
|