@graphql-eslint/eslint-plugin 3.0.0-alpha-580615a.0 → 3.0.0-alpha-7462f3d.0
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/configs/all.d.ts +3 -5
- package/configs/index.d.ts +4 -7
- package/configs/recommended.d.ts +1 -2
- package/docs/README.md +3 -5
- package/docs/deprecated-rules.md +21 -0
- package/docs/rules/{avoid-duplicate-fields.md → no-duplicate-fields.md} +7 -7
- package/docs/rules/no-root-type.md +6 -0
- package/docs/rules/{avoid-scalar-result-type-on-mutation.md → no-scalar-result-type-on-mutation.md} +6 -6
- package/docs/rules/{avoid-typename-prefix.md → no-typename-prefix.md} +6 -6
- package/index.js +298 -457
- package/index.mjs +299 -458
- package/package.json +1 -1
- package/rules/index.d.ts +3 -5
- package/rules/{avoid-duplicate-fields.d.ts → no-duplicate-fields.d.ts} +0 -0
- package/rules/{avoid-scalar-result-type-on-mutation.d.ts → no-scalar-result-type-on-mutation.d.ts} +0 -0
- package/rules/{avoid-typename-prefix.d.ts → no-typename-prefix.d.ts} +0 -0
- package/docs/rules/avoid-operation-name-prefix.md +0 -50
- package/docs/rules/no-operation-name-suffix.md +0 -38
- package/rules/avoid-operation-name-prefix.d.ts +0 -9
- package/rules/no-operation-name-suffix.d.ts +0 -3
package/index.js
CHANGED
@@ -25,7 +25,6 @@ const recommendedConfig = {
|
|
25
25
|
parser: '@graphql-eslint/eslint-plugin',
|
26
26
|
plugins: ['@graphql-eslint'],
|
27
27
|
rules: {
|
28
|
-
'@graphql-eslint/avoid-typename-prefix': 'error',
|
29
28
|
'@graphql-eslint/executable-definitions': 'error',
|
30
29
|
'@graphql-eslint/fields-on-correct-type': 'error',
|
31
30
|
'@graphql-eslint/fragments-on-composite-type': 'error',
|
@@ -66,7 +65,7 @@ const recommendedConfig = {
|
|
66
65
|
'@graphql-eslint/no-anonymous-operations': 'error',
|
67
66
|
'@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
|
68
67
|
'@graphql-eslint/no-fragment-cycles': 'error',
|
69
|
-
'@graphql-eslint/no-
|
68
|
+
'@graphql-eslint/no-typename-prefix': 'error',
|
70
69
|
'@graphql-eslint/no-undefined-variables': 'error',
|
71
70
|
'@graphql-eslint/no-unused-fragments': 'error',
|
72
71
|
'@graphql-eslint/no-unused-variables': 'error',
|
@@ -110,15 +109,14 @@ const allConfig = {
|
|
110
109
|
arguments: ['FieldDefinition', 'Field', 'DirectiveDefinition', 'Directive'],
|
111
110
|
},
|
112
111
|
],
|
113
|
-
'@graphql-eslint/avoid-duplicate-fields': 'error',
|
114
|
-
'@graphql-eslint/avoid-operation-name-prefix': 'error',
|
115
|
-
'@graphql-eslint/avoid-scalar-result-type-on-mutation': 'error',
|
116
112
|
'@graphql-eslint/description-style': 'error',
|
117
113
|
'@graphql-eslint/input-name': 'error',
|
118
114
|
'@graphql-eslint/match-document-filename': 'error',
|
119
115
|
'@graphql-eslint/no-deprecated': 'error',
|
116
|
+
'@graphql-eslint/no-duplicate-fields': 'error',
|
120
117
|
'@graphql-eslint/no-hashtag-description': 'error',
|
121
118
|
'@graphql-eslint/no-root-type': ['error', { disallow: ['subscription'] }],
|
119
|
+
'@graphql-eslint/no-scalar-result-type-on-mutation': 'error',
|
122
120
|
'@graphql-eslint/no-unreachable-types': 'error',
|
123
121
|
'@graphql-eslint/no-unused-fields': 'error',
|
124
122
|
'@graphql-eslint/require-deprecation-date': 'error',
|
@@ -792,307 +790,7 @@ const rule = {
|
|
792
790
|
},
|
793
791
|
};
|
794
792
|
|
795
|
-
const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
|
796
793
|
const rule$1 = {
|
797
|
-
meta: {
|
798
|
-
type: 'suggestion',
|
799
|
-
docs: {
|
800
|
-
description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
|
801
|
-
category: 'Stylistic Issues',
|
802
|
-
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
|
803
|
-
examples: [
|
804
|
-
{
|
805
|
-
title: 'Incorrect',
|
806
|
-
code: /* GraphQL */ `
|
807
|
-
query {
|
808
|
-
user {
|
809
|
-
name
|
810
|
-
email
|
811
|
-
name # duplicate field
|
812
|
-
}
|
813
|
-
}
|
814
|
-
`,
|
815
|
-
},
|
816
|
-
{
|
817
|
-
title: 'Incorrect',
|
818
|
-
code: /* GraphQL */ `
|
819
|
-
query {
|
820
|
-
users(
|
821
|
-
first: 100
|
822
|
-
skip: 50
|
823
|
-
after: "cji629tngfgou0b73kt7vi5jo"
|
824
|
-
first: 100 # duplicate argument
|
825
|
-
) {
|
826
|
-
id
|
827
|
-
}
|
828
|
-
}
|
829
|
-
`,
|
830
|
-
},
|
831
|
-
{
|
832
|
-
title: 'Incorrect',
|
833
|
-
code: /* GraphQL */ `
|
834
|
-
query (
|
835
|
-
$first: Int!
|
836
|
-
$first: Int! # duplicate variable
|
837
|
-
) {
|
838
|
-
users(first: $first, skip: 50) {
|
839
|
-
id
|
840
|
-
}
|
841
|
-
}
|
842
|
-
`,
|
843
|
-
},
|
844
|
-
],
|
845
|
-
},
|
846
|
-
messages: {
|
847
|
-
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
|
848
|
-
},
|
849
|
-
schema: [],
|
850
|
-
},
|
851
|
-
create(context) {
|
852
|
-
function checkNode(usedFields, fieldName, type, node) {
|
853
|
-
if (usedFields.has(fieldName)) {
|
854
|
-
context.report({
|
855
|
-
loc: getLocation((node.kind === graphql.Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
|
856
|
-
offsetEnd: node.kind === graphql.Kind.VARIABLE_DEFINITION ? 0 : 1,
|
857
|
-
}),
|
858
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
859
|
-
data: {
|
860
|
-
type,
|
861
|
-
fieldName,
|
862
|
-
},
|
863
|
-
});
|
864
|
-
}
|
865
|
-
else {
|
866
|
-
usedFields.add(fieldName);
|
867
|
-
}
|
868
|
-
}
|
869
|
-
return {
|
870
|
-
OperationDefinition(node) {
|
871
|
-
const set = new Set();
|
872
|
-
for (const varDef of node.variableDefinitions) {
|
873
|
-
checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
|
874
|
-
}
|
875
|
-
},
|
876
|
-
Field(node) {
|
877
|
-
const set = new Set();
|
878
|
-
for (const arg of node.arguments) {
|
879
|
-
checkNode(set, arg.name.value, 'Field argument', arg);
|
880
|
-
}
|
881
|
-
},
|
882
|
-
SelectionSet(node) {
|
883
|
-
var _a;
|
884
|
-
const set = new Set();
|
885
|
-
for (const selection of node.selections) {
|
886
|
-
if (selection.kind === graphql.Kind.FIELD) {
|
887
|
-
checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
|
888
|
-
}
|
889
|
-
}
|
890
|
-
},
|
891
|
-
};
|
892
|
-
},
|
893
|
-
};
|
894
|
-
|
895
|
-
const AVOID_OPERATION_NAME_PREFIX = 'AVOID_OPERATION_NAME_PREFIX';
|
896
|
-
const rule$2 = {
|
897
|
-
meta: {
|
898
|
-
type: 'suggestion',
|
899
|
-
docs: {
|
900
|
-
description: 'Enforce/avoid operation name prefix, useful if you wish to avoid prefix in your root fields, or avoid using REST terminology in your schema.',
|
901
|
-
category: 'Stylistic Issues',
|
902
|
-
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-operation-name-prefix.md',
|
903
|
-
examples: [
|
904
|
-
{
|
905
|
-
title: 'Incorrect',
|
906
|
-
usage: [{ keywords: ['get'] }],
|
907
|
-
code: /* GraphQL */ `
|
908
|
-
query getUserDetails {
|
909
|
-
# ...
|
910
|
-
}`,
|
911
|
-
},
|
912
|
-
{
|
913
|
-
title: 'Correct',
|
914
|
-
usage: [{ keywords: ['get'] }],
|
915
|
-
code: /* GraphQL */ `
|
916
|
-
query userDetails {
|
917
|
-
# ...
|
918
|
-
}`,
|
919
|
-
},
|
920
|
-
],
|
921
|
-
},
|
922
|
-
messages: {
|
923
|
-
[AVOID_OPERATION_NAME_PREFIX]: `Forbidden operation name prefix: "{{ invalidPrefix }}"`,
|
924
|
-
},
|
925
|
-
schema: [
|
926
|
-
{
|
927
|
-
additionalProperties: false,
|
928
|
-
type: 'object',
|
929
|
-
required: ['keywords'],
|
930
|
-
properties: {
|
931
|
-
caseSensitive: {
|
932
|
-
default: false,
|
933
|
-
type: 'boolean',
|
934
|
-
},
|
935
|
-
keywords: {
|
936
|
-
additionalItems: false,
|
937
|
-
type: 'array',
|
938
|
-
minItems: 1,
|
939
|
-
items: {
|
940
|
-
type: 'string',
|
941
|
-
},
|
942
|
-
},
|
943
|
-
},
|
944
|
-
},
|
945
|
-
],
|
946
|
-
},
|
947
|
-
create(context) {
|
948
|
-
return {
|
949
|
-
'OperationDefinition, FragmentDefinition'(node) {
|
950
|
-
const config = context.options[0] || { keywords: [], caseSensitive: false };
|
951
|
-
const caseSensitive = config.caseSensitive;
|
952
|
-
const keywords = config.keywords || [];
|
953
|
-
if (node && node.name && node.name.value !== '') {
|
954
|
-
for (const keyword of keywords) {
|
955
|
-
const testKeyword = caseSensitive ? keyword : keyword.toLowerCase();
|
956
|
-
const testName = caseSensitive ? node.name.value : node.name.value.toLowerCase();
|
957
|
-
if (testName.startsWith(testKeyword)) {
|
958
|
-
const { start } = node.name.loc;
|
959
|
-
context.report({
|
960
|
-
loc: {
|
961
|
-
start: {
|
962
|
-
line: start.line,
|
963
|
-
column: start.column - 1,
|
964
|
-
},
|
965
|
-
end: {
|
966
|
-
line: start.line,
|
967
|
-
column: start.column - 1 + testKeyword.length,
|
968
|
-
},
|
969
|
-
},
|
970
|
-
data: {
|
971
|
-
invalidPrefix: keyword,
|
972
|
-
},
|
973
|
-
messageId: AVOID_OPERATION_NAME_PREFIX,
|
974
|
-
});
|
975
|
-
}
|
976
|
-
}
|
977
|
-
}
|
978
|
-
},
|
979
|
-
};
|
980
|
-
},
|
981
|
-
};
|
982
|
-
|
983
|
-
const rule$3 = {
|
984
|
-
meta: {
|
985
|
-
type: 'suggestion',
|
986
|
-
docs: {
|
987
|
-
category: 'Best Practices',
|
988
|
-
description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
|
989
|
-
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-scalar-result-type-on-mutation.md',
|
990
|
-
requiresSchema: true,
|
991
|
-
examples: [
|
992
|
-
{
|
993
|
-
title: 'Incorrect',
|
994
|
-
code: /* GraphQL */ `
|
995
|
-
type Mutation {
|
996
|
-
createUser: Boolean
|
997
|
-
}
|
998
|
-
`,
|
999
|
-
},
|
1000
|
-
{
|
1001
|
-
title: 'Correct',
|
1002
|
-
code: /* GraphQL */ `
|
1003
|
-
type Mutation {
|
1004
|
-
createUser: User!
|
1005
|
-
}
|
1006
|
-
`,
|
1007
|
-
},
|
1008
|
-
],
|
1009
|
-
},
|
1010
|
-
schema: [],
|
1011
|
-
},
|
1012
|
-
create(context) {
|
1013
|
-
const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
|
1014
|
-
const mutationType = schema.getMutationType();
|
1015
|
-
if (!mutationType) {
|
1016
|
-
return {};
|
1017
|
-
}
|
1018
|
-
const selector = [
|
1019
|
-
`:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
1020
|
-
'>',
|
1021
|
-
graphql.Kind.FIELD_DEFINITION,
|
1022
|
-
graphql.Kind.NAMED_TYPE,
|
1023
|
-
].join(' ');
|
1024
|
-
return {
|
1025
|
-
[selector](node) {
|
1026
|
-
const typeName = node.name.value;
|
1027
|
-
const graphQLType = schema.getType(typeName);
|
1028
|
-
if (graphql.isScalarType(graphQLType)) {
|
1029
|
-
context.report({
|
1030
|
-
loc: getLocation(node.loc, typeName),
|
1031
|
-
message: `Unexpected scalar result type "${typeName}"`,
|
1032
|
-
});
|
1033
|
-
}
|
1034
|
-
},
|
1035
|
-
};
|
1036
|
-
},
|
1037
|
-
};
|
1038
|
-
|
1039
|
-
const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
|
1040
|
-
const rule$4 = {
|
1041
|
-
meta: {
|
1042
|
-
type: 'suggestion',
|
1043
|
-
docs: {
|
1044
|
-
category: 'Best Practices',
|
1045
|
-
description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
|
1046
|
-
recommended: true,
|
1047
|
-
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-typename-prefix.md',
|
1048
|
-
examples: [
|
1049
|
-
{
|
1050
|
-
title: 'Incorrect',
|
1051
|
-
code: /* GraphQL */ `
|
1052
|
-
type User {
|
1053
|
-
userId: ID!
|
1054
|
-
}
|
1055
|
-
`,
|
1056
|
-
},
|
1057
|
-
{
|
1058
|
-
title: 'Correct',
|
1059
|
-
code: /* GraphQL */ `
|
1060
|
-
type User {
|
1061
|
-
id: ID!
|
1062
|
-
}
|
1063
|
-
`,
|
1064
|
-
},
|
1065
|
-
],
|
1066
|
-
},
|
1067
|
-
messages: {
|
1068
|
-
[AVOID_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
|
1069
|
-
},
|
1070
|
-
schema: [],
|
1071
|
-
},
|
1072
|
-
create(context) {
|
1073
|
-
return {
|
1074
|
-
'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
|
1075
|
-
const typeName = node.name.value;
|
1076
|
-
const lowerTypeName = typeName.toLowerCase();
|
1077
|
-
for (const field of node.fields) {
|
1078
|
-
const fieldName = field.name.value;
|
1079
|
-
if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
|
1080
|
-
context.report({
|
1081
|
-
data: {
|
1082
|
-
fieldName,
|
1083
|
-
typeName,
|
1084
|
-
},
|
1085
|
-
messageId: AVOID_TYPENAME_PREFIX,
|
1086
|
-
loc: getLocation(field.loc, lowerTypeName),
|
1087
|
-
});
|
1088
|
-
}
|
1089
|
-
}
|
1090
|
-
},
|
1091
|
-
};
|
1092
|
-
},
|
1093
|
-
};
|
1094
|
-
|
1095
|
-
const rule$5 = {
|
1096
794
|
meta: {
|
1097
795
|
type: 'suggestion',
|
1098
796
|
docs: {
|
@@ -1155,7 +853,7 @@ const rule$5 = {
|
|
1155
853
|
const isObjectType = (node) => [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
|
1156
854
|
const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
|
1157
855
|
const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
|
1158
|
-
const rule$
|
856
|
+
const rule$2 = {
|
1159
857
|
meta: {
|
1160
858
|
type: 'suggestion',
|
1161
859
|
docs: {
|
@@ -1281,7 +979,7 @@ const CASE_STYLES = [
|
|
1281
979
|
const schemaOption = {
|
1282
980
|
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
|
1283
981
|
};
|
1284
|
-
const rule$
|
982
|
+
const rule$3 = {
|
1285
983
|
meta: {
|
1286
984
|
type: 'suggestion',
|
1287
985
|
docs: {
|
@@ -1499,7 +1197,7 @@ const ALLOWED_STYLES = Object.keys(StyleToRegex);
|
|
1499
1197
|
const schemaOption$1 = {
|
1500
1198
|
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
|
1501
1199
|
};
|
1502
|
-
const rule$
|
1200
|
+
const rule$4 = {
|
1503
1201
|
meta: {
|
1504
1202
|
type: 'suggestion',
|
1505
1203
|
docs: {
|
@@ -1712,7 +1410,7 @@ const rule$8 = {
|
|
1712
1410
|
};
|
1713
1411
|
|
1714
1412
|
const NO_ANONYMOUS_OPERATIONS = 'NO_ANONYMOUS_OPERATIONS';
|
1715
|
-
const rule$
|
1413
|
+
const rule$5 = {
|
1716
1414
|
meta: {
|
1717
1415
|
type: 'suggestion',
|
1718
1416
|
docs: {
|
@@ -1760,7 +1458,7 @@ const rule$9 = {
|
|
1760
1458
|
};
|
1761
1459
|
|
1762
1460
|
const ERROR_MESSAGE_ID = 'NO_CASE_INSENSITIVE_ENUM_VALUES_DUPLICATES';
|
1763
|
-
const rule$
|
1461
|
+
const rule$6 = {
|
1764
1462
|
meta: {
|
1765
1463
|
type: 'suggestion',
|
1766
1464
|
docs: {
|
@@ -1816,7 +1514,7 @@ const rule$a = {
|
|
1816
1514
|
};
|
1817
1515
|
|
1818
1516
|
const NO_DEPRECATED = 'NO_DEPRECATED';
|
1819
|
-
const rule$
|
1517
|
+
const rule$7 = {
|
1820
1518
|
meta: {
|
1821
1519
|
type: 'suggestion',
|
1822
1520
|
docs: {
|
@@ -1867,20 +1565,116 @@ const rule$b = {
|
|
1867
1565
|
`,
|
1868
1566
|
},
|
1869
1567
|
{
|
1870
|
-
title: 'Correct',
|
1568
|
+
title: 'Correct',
|
1569
|
+
code: /* GraphQL */ `
|
1570
|
+
# In your schema
|
1571
|
+
type User {
|
1572
|
+
id: ID!
|
1573
|
+
name: String! @deprecated(reason: "old field, please use fullName instead")
|
1574
|
+
fullName: String!
|
1575
|
+
}
|
1576
|
+
|
1577
|
+
# Query
|
1578
|
+
query user {
|
1579
|
+
user {
|
1580
|
+
id
|
1581
|
+
fullName
|
1582
|
+
}
|
1583
|
+
}
|
1584
|
+
`,
|
1585
|
+
},
|
1586
|
+
],
|
1587
|
+
},
|
1588
|
+
messages: {
|
1589
|
+
[NO_DEPRECATED]: `This {{ type }} is marked as deprecated in your GraphQL schema {{ reason }}`,
|
1590
|
+
},
|
1591
|
+
schema: [],
|
1592
|
+
},
|
1593
|
+
create(context) {
|
1594
|
+
return {
|
1595
|
+
EnumValue(node) {
|
1596
|
+
requireGraphQLSchemaFromContext('no-deprecated', context);
|
1597
|
+
const typeInfo = node.typeInfo();
|
1598
|
+
if (typeInfo && typeInfo.enumValue) {
|
1599
|
+
if (typeInfo.enumValue.deprecationReason) {
|
1600
|
+
const enumValueName = node.value;
|
1601
|
+
context.report({
|
1602
|
+
loc: getLocation(node.loc, enumValueName),
|
1603
|
+
messageId: NO_DEPRECATED,
|
1604
|
+
data: {
|
1605
|
+
type: 'enum value',
|
1606
|
+
reason: typeInfo.enumValue.deprecationReason ? `(reason: ${typeInfo.enumValue.deprecationReason})` : '',
|
1607
|
+
},
|
1608
|
+
});
|
1609
|
+
}
|
1610
|
+
}
|
1611
|
+
},
|
1612
|
+
Field(node) {
|
1613
|
+
requireGraphQLSchemaFromContext('no-deprecated', context);
|
1614
|
+
const typeInfo = node.typeInfo();
|
1615
|
+
if (typeInfo && typeInfo.fieldDef) {
|
1616
|
+
if (typeInfo.fieldDef.deprecationReason) {
|
1617
|
+
const fieldName = node.name.value;
|
1618
|
+
context.report({
|
1619
|
+
loc: getLocation(node.loc, fieldName),
|
1620
|
+
messageId: NO_DEPRECATED,
|
1621
|
+
data: {
|
1622
|
+
type: 'field',
|
1623
|
+
reason: typeInfo.fieldDef.deprecationReason ? `(reason: ${typeInfo.fieldDef.deprecationReason})` : '',
|
1624
|
+
},
|
1625
|
+
});
|
1626
|
+
}
|
1627
|
+
}
|
1628
|
+
},
|
1629
|
+
};
|
1630
|
+
},
|
1631
|
+
};
|
1632
|
+
|
1633
|
+
const NO_DUPLICATE_FIELDS = 'NO_DUPLICATE_FIELDS';
|
1634
|
+
const rule$8 = {
|
1635
|
+
meta: {
|
1636
|
+
type: 'suggestion',
|
1637
|
+
docs: {
|
1638
|
+
description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
|
1639
|
+
category: 'Stylistic Issues',
|
1640
|
+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-duplicate-fields.md',
|
1641
|
+
examples: [
|
1642
|
+
{
|
1643
|
+
title: 'Incorrect',
|
1644
|
+
code: /* GraphQL */ `
|
1645
|
+
query {
|
1646
|
+
user {
|
1647
|
+
name
|
1648
|
+
email
|
1649
|
+
name # duplicate field
|
1650
|
+
}
|
1651
|
+
}
|
1652
|
+
`,
|
1653
|
+
},
|
1654
|
+
{
|
1655
|
+
title: 'Incorrect',
|
1656
|
+
code: /* GraphQL */ `
|
1657
|
+
query {
|
1658
|
+
users(
|
1659
|
+
first: 100
|
1660
|
+
skip: 50
|
1661
|
+
after: "cji629tngfgou0b73kt7vi5jo"
|
1662
|
+
first: 100 # duplicate argument
|
1663
|
+
) {
|
1664
|
+
id
|
1665
|
+
}
|
1666
|
+
}
|
1667
|
+
`,
|
1668
|
+
},
|
1669
|
+
{
|
1670
|
+
title: 'Incorrect',
|
1871
1671
|
code: /* GraphQL */ `
|
1872
|
-
|
1873
|
-
|
1874
|
-
|
1875
|
-
|
1876
|
-
|
1877
|
-
}
|
1878
|
-
|
1879
|
-
# Query
|
1880
|
-
query user {
|
1881
|
-
user {
|
1672
|
+
query (
|
1673
|
+
$first: Int!
|
1674
|
+
$first: Int! # duplicate variable
|
1675
|
+
) {
|
1676
|
+
users(first: $first, skip: 50) {
|
1882
1677
|
id
|
1883
|
-
fullName
|
1884
1678
|
}
|
1885
1679
|
}
|
1886
1680
|
`,
|
@@ -1888,43 +1682,47 @@ const rule$b = {
|
|
1888
1682
|
],
|
1889
1683
|
},
|
1890
1684
|
messages: {
|
1891
|
-
[
|
1685
|
+
[NO_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
|
1892
1686
|
},
|
1893
1687
|
schema: [],
|
1894
1688
|
},
|
1895
1689
|
create(context) {
|
1690
|
+
function checkNode(usedFields, fieldName, type, node) {
|
1691
|
+
if (usedFields.has(fieldName)) {
|
1692
|
+
context.report({
|
1693
|
+
loc: getLocation((node.kind === graphql.Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
|
1694
|
+
offsetEnd: node.kind === graphql.Kind.VARIABLE_DEFINITION ? 0 : 1,
|
1695
|
+
}),
|
1696
|
+
messageId: NO_DUPLICATE_FIELDS,
|
1697
|
+
data: {
|
1698
|
+
type,
|
1699
|
+
fieldName,
|
1700
|
+
},
|
1701
|
+
});
|
1702
|
+
}
|
1703
|
+
else {
|
1704
|
+
usedFields.add(fieldName);
|
1705
|
+
}
|
1706
|
+
}
|
1896
1707
|
return {
|
1897
|
-
|
1898
|
-
|
1899
|
-
const
|
1900
|
-
|
1901
|
-
if (typeInfo.enumValue.deprecationReason) {
|
1902
|
-
const enumValueName = node.value;
|
1903
|
-
context.report({
|
1904
|
-
loc: getLocation(node.loc, enumValueName),
|
1905
|
-
messageId: NO_DEPRECATED,
|
1906
|
-
data: {
|
1907
|
-
type: 'enum value',
|
1908
|
-
reason: typeInfo.enumValue.deprecationReason ? `(reason: ${typeInfo.enumValue.deprecationReason})` : '',
|
1909
|
-
},
|
1910
|
-
});
|
1911
|
-
}
|
1708
|
+
OperationDefinition(node) {
|
1709
|
+
const set = new Set();
|
1710
|
+
for (const varDef of node.variableDefinitions) {
|
1711
|
+
checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
|
1912
1712
|
}
|
1913
1713
|
},
|
1914
1714
|
Field(node) {
|
1915
|
-
|
1916
|
-
const
|
1917
|
-
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
|
1924
|
-
|
1925
|
-
|
1926
|
-
},
|
1927
|
-
});
|
1715
|
+
const set = new Set();
|
1716
|
+
for (const arg of node.arguments) {
|
1717
|
+
checkNode(set, arg.name.value, 'Field argument', arg);
|
1718
|
+
}
|
1719
|
+
},
|
1720
|
+
SelectionSet(node) {
|
1721
|
+
var _a;
|
1722
|
+
const set = new Set();
|
1723
|
+
for (const selection of node.selections) {
|
1724
|
+
if (selection.kind === graphql.Kind.FIELD) {
|
1725
|
+
checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
|
1928
1726
|
}
|
1929
1727
|
}
|
1930
1728
|
},
|
@@ -1933,7 +1731,7 @@ const rule$b = {
|
|
1933
1731
|
};
|
1934
1732
|
|
1935
1733
|
const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
|
1936
|
-
const rule$
|
1734
|
+
const rule$9 = {
|
1937
1735
|
meta: {
|
1938
1736
|
messages: {
|
1939
1737
|
[HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
|
@@ -2005,75 +1803,8 @@ const rule$c = {
|
|
2005
1803
|
},
|
2006
1804
|
};
|
2007
1805
|
|
2008
|
-
const NO_OPERATION_NAME_SUFFIX = 'NO_OPERATION_NAME_SUFFIX';
|
2009
|
-
const rule$d = {
|
2010
|
-
meta: {
|
2011
|
-
fixable: 'code',
|
2012
|
-
type: 'suggestion',
|
2013
|
-
docs: {
|
2014
|
-
category: 'Stylistic Issues',
|
2015
|
-
recommended: true,
|
2016
|
-
description: `Makes sure you are not adding the operation type to the name of the operation.`,
|
2017
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-operation-name-suffix.md`,
|
2018
|
-
examples: [
|
2019
|
-
{
|
2020
|
-
title: 'Incorrect',
|
2021
|
-
code: /* GraphQL */ `
|
2022
|
-
query userQuery {
|
2023
|
-
# ...
|
2024
|
-
}
|
2025
|
-
`,
|
2026
|
-
},
|
2027
|
-
{
|
2028
|
-
title: 'Correct',
|
2029
|
-
code: /* GraphQL */ `
|
2030
|
-
query user {
|
2031
|
-
# ...
|
2032
|
-
}
|
2033
|
-
`,
|
2034
|
-
},
|
2035
|
-
],
|
2036
|
-
},
|
2037
|
-
messages: {
|
2038
|
-
[NO_OPERATION_NAME_SUFFIX]: `Unnecessary "{{ invalidSuffix }}" suffix in your operation name!`,
|
2039
|
-
},
|
2040
|
-
schema: [],
|
2041
|
-
},
|
2042
|
-
create(context) {
|
2043
|
-
return {
|
2044
|
-
'OperationDefinition, FragmentDefinition'(node) {
|
2045
|
-
var _a;
|
2046
|
-
const name = ((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '';
|
2047
|
-
if (name.length > 0) {
|
2048
|
-
const invalidSuffix = 'operation' in node ? node.operation : 'fragment';
|
2049
|
-
if (name.toLowerCase().endsWith(invalidSuffix)) {
|
2050
|
-
const { start, end } = node.name.loc;
|
2051
|
-
context.report({
|
2052
|
-
loc: {
|
2053
|
-
start: {
|
2054
|
-
column: start.column - 1 + name.length - invalidSuffix.length,
|
2055
|
-
line: start.line,
|
2056
|
-
},
|
2057
|
-
end: {
|
2058
|
-
column: end.column - 1 + name.length,
|
2059
|
-
line: end.line,
|
2060
|
-
},
|
2061
|
-
},
|
2062
|
-
data: {
|
2063
|
-
invalidSuffix,
|
2064
|
-
},
|
2065
|
-
fix: fixer => fixer.removeRange([node.name.range[1] - invalidSuffix.length, node.name.range[1]]),
|
2066
|
-
messageId: NO_OPERATION_NAME_SUFFIX,
|
2067
|
-
});
|
2068
|
-
}
|
2069
|
-
}
|
2070
|
-
},
|
2071
|
-
};
|
2072
|
-
},
|
2073
|
-
};
|
2074
|
-
|
2075
1806
|
const ROOT_TYPES = ['query', 'mutation', 'subscription'];
|
2076
|
-
const rule$
|
1807
|
+
const rule$a = {
|
2077
1808
|
meta: {
|
2078
1809
|
type: 'suggestion',
|
2079
1810
|
docs: {
|
@@ -2163,9 +1894,121 @@ const rule$e = {
|
|
2163
1894
|
},
|
2164
1895
|
};
|
2165
1896
|
|
1897
|
+
const rule$b = {
|
1898
|
+
meta: {
|
1899
|
+
type: 'suggestion',
|
1900
|
+
docs: {
|
1901
|
+
category: 'Best Practices',
|
1902
|
+
description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
|
1903
|
+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-scalar-result-type-on-mutation.md',
|
1904
|
+
requiresSchema: true,
|
1905
|
+
examples: [
|
1906
|
+
{
|
1907
|
+
title: 'Incorrect',
|
1908
|
+
code: /* GraphQL */ `
|
1909
|
+
type Mutation {
|
1910
|
+
createUser: Boolean
|
1911
|
+
}
|
1912
|
+
`,
|
1913
|
+
},
|
1914
|
+
{
|
1915
|
+
title: 'Correct',
|
1916
|
+
code: /* GraphQL */ `
|
1917
|
+
type Mutation {
|
1918
|
+
createUser: User!
|
1919
|
+
}
|
1920
|
+
`,
|
1921
|
+
},
|
1922
|
+
],
|
1923
|
+
},
|
1924
|
+
schema: [],
|
1925
|
+
},
|
1926
|
+
create(context) {
|
1927
|
+
const schema = requireGraphQLSchemaFromContext('no-scalar-result-type-on-mutation', context);
|
1928
|
+
const mutationType = schema.getMutationType();
|
1929
|
+
if (!mutationType) {
|
1930
|
+
return {};
|
1931
|
+
}
|
1932
|
+
const selector = [
|
1933
|
+
`:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
1934
|
+
'>',
|
1935
|
+
graphql.Kind.FIELD_DEFINITION,
|
1936
|
+
graphql.Kind.NAMED_TYPE,
|
1937
|
+
].join(' ');
|
1938
|
+
return {
|
1939
|
+
[selector](node) {
|
1940
|
+
const typeName = node.name.value;
|
1941
|
+
const graphQLType = schema.getType(typeName);
|
1942
|
+
if (graphql.isScalarType(graphQLType)) {
|
1943
|
+
context.report({
|
1944
|
+
loc: getLocation(node.loc, typeName),
|
1945
|
+
message: `Unexpected scalar result type "${typeName}"`,
|
1946
|
+
});
|
1947
|
+
}
|
1948
|
+
},
|
1949
|
+
};
|
1950
|
+
},
|
1951
|
+
};
|
1952
|
+
|
1953
|
+
const NO_TYPENAME_PREFIX = 'NO_TYPENAME_PREFIX';
|
1954
|
+
const rule$c = {
|
1955
|
+
meta: {
|
1956
|
+
type: 'suggestion',
|
1957
|
+
docs: {
|
1958
|
+
category: 'Best Practices',
|
1959
|
+
description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
|
1960
|
+
recommended: true,
|
1961
|
+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-typename-prefix.md',
|
1962
|
+
examples: [
|
1963
|
+
{
|
1964
|
+
title: 'Incorrect',
|
1965
|
+
code: /* GraphQL */ `
|
1966
|
+
type User {
|
1967
|
+
userId: ID!
|
1968
|
+
}
|
1969
|
+
`,
|
1970
|
+
},
|
1971
|
+
{
|
1972
|
+
title: 'Correct',
|
1973
|
+
code: /* GraphQL */ `
|
1974
|
+
type User {
|
1975
|
+
id: ID!
|
1976
|
+
}
|
1977
|
+
`,
|
1978
|
+
},
|
1979
|
+
],
|
1980
|
+
},
|
1981
|
+
messages: {
|
1982
|
+
[NO_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
|
1983
|
+
},
|
1984
|
+
schema: [],
|
1985
|
+
},
|
1986
|
+
create(context) {
|
1987
|
+
return {
|
1988
|
+
'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
|
1989
|
+
const typeName = node.name.value;
|
1990
|
+
const lowerTypeName = typeName.toLowerCase();
|
1991
|
+
for (const field of node.fields) {
|
1992
|
+
const fieldName = field.name.value;
|
1993
|
+
if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
|
1994
|
+
context.report({
|
1995
|
+
data: {
|
1996
|
+
fieldName,
|
1997
|
+
typeName,
|
1998
|
+
},
|
1999
|
+
messageId: NO_TYPENAME_PREFIX,
|
2000
|
+
loc: getLocation(field.loc, lowerTypeName),
|
2001
|
+
});
|
2002
|
+
}
|
2003
|
+
}
|
2004
|
+
},
|
2005
|
+
};
|
2006
|
+
},
|
2007
|
+
};
|
2008
|
+
|
2166
2009
|
const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
|
2167
2010
|
const RULE_NAME = 'no-unreachable-types';
|
2168
|
-
const rule$
|
2011
|
+
const rule$d = {
|
2169
2012
|
meta: {
|
2170
2013
|
messages: {
|
2171
2014
|
[UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
|
@@ -2241,7 +2084,7 @@ const rule$f = {
|
|
2241
2084
|
|
2242
2085
|
const UNUSED_FIELD = 'UNUSED_FIELD';
|
2243
2086
|
const RULE_NAME$1 = 'no-unused-fields';
|
2244
|
-
const rule$
|
2087
|
+
const rule$e = {
|
2245
2088
|
meta: {
|
2246
2089
|
messages: {
|
2247
2090
|
[UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
|
@@ -2430,7 +2273,7 @@ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
|
|
2430
2273
|
const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
|
2431
2274
|
const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
|
2432
2275
|
const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
|
2433
|
-
const rule$
|
2276
|
+
const rule$f = {
|
2434
2277
|
meta: {
|
2435
2278
|
type: 'suggestion',
|
2436
2279
|
docs: {
|
@@ -2533,7 +2376,7 @@ const rule$h = {
|
|
2533
2376
|
},
|
2534
2377
|
};
|
2535
2378
|
|
2536
|
-
const rule$
|
2379
|
+
const rule$g = {
|
2537
2380
|
meta: {
|
2538
2381
|
docs: {
|
2539
2382
|
description: `Require all deprecation directives to specify a reason.`,
|
@@ -2595,7 +2438,7 @@ const ALLOWED_KINDS$1 = [
|
|
2595
2438
|
graphql.Kind.ENUM_VALUE_DEFINITION,
|
2596
2439
|
graphql.Kind.DIRECTIVE_DEFINITION,
|
2597
2440
|
];
|
2598
|
-
const rule$
|
2441
|
+
const rule$h = {
|
2599
2442
|
meta: {
|
2600
2443
|
docs: {
|
2601
2444
|
category: 'Best Practices',
|
@@ -2694,7 +2537,7 @@ const rule$j = {
|
|
2694
2537
|
};
|
2695
2538
|
|
2696
2539
|
const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
|
2697
|
-
const rule$
|
2540
|
+
const rule$i = {
|
2698
2541
|
meta: {
|
2699
2542
|
type: 'suggestion',
|
2700
2543
|
docs: {
|
@@ -2862,7 +2705,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
|
|
2862
2705
|
|
2863
2706
|
const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
|
2864
2707
|
const DEFAULT_ID_FIELD_NAME = 'id';
|
2865
|
-
const rule$
|
2708
|
+
const rule$j = {
|
2866
2709
|
meta: {
|
2867
2710
|
type: 'suggestion',
|
2868
2711
|
docs: {
|
@@ -2998,7 +2841,7 @@ const rule$l = {
|
|
2998
2841
|
},
|
2999
2842
|
};
|
3000
2843
|
|
3001
|
-
const rule$
|
2844
|
+
const rule$k = {
|
3002
2845
|
meta: {
|
3003
2846
|
docs: {
|
3004
2847
|
category: 'Best Practices',
|
@@ -3120,7 +2963,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
|
|
3120
2963
|
}
|
3121
2964
|
return false;
|
3122
2965
|
};
|
3123
|
-
const rule$
|
2966
|
+
const rule$l = {
|
3124
2967
|
meta: {
|
3125
2968
|
type: 'suggestion',
|
3126
2969
|
docs: {
|
@@ -3297,7 +3140,7 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3297
3140
|
});
|
3298
3141
|
}
|
3299
3142
|
};
|
3300
|
-
const rule$
|
3143
|
+
const rule$m = {
|
3301
3144
|
meta: {
|
3302
3145
|
type: 'suggestion',
|
3303
3146
|
docs: {
|
@@ -3356,7 +3199,7 @@ const rule$o = {
|
|
3356
3199
|
|
3357
3200
|
const RULE_NAME$4 = 'unique-operation-name';
|
3358
3201
|
const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
|
3359
|
-
const rule$
|
3202
|
+
const rule$n = {
|
3360
3203
|
meta: {
|
3361
3204
|
type: 'suggestion',
|
3362
3205
|
docs: {
|
@@ -3423,31 +3266,29 @@ const rule$p = {
|
|
3423
3266
|
const rules = {
|
3424
3267
|
...GRAPHQL_JS_VALIDATIONS,
|
3425
3268
|
alphabetize: rule,
|
3426
|
-
'
|
3427
|
-
'
|
3428
|
-
'
|
3429
|
-
'
|
3430
|
-
'
|
3431
|
-
'
|
3432
|
-
'
|
3433
|
-
'
|
3434
|
-
'no-
|
3435
|
-
'no-
|
3436
|
-
'no-
|
3437
|
-
'no-
|
3438
|
-
'no-
|
3439
|
-
'no-
|
3440
|
-
'
|
3441
|
-
'
|
3442
|
-
'require-
|
3443
|
-
'require-
|
3444
|
-
'require-
|
3445
|
-
'
|
3446
|
-
'
|
3447
|
-
'
|
3448
|
-
'
|
3449
|
-
'unique-fragment-name': rule$o,
|
3450
|
-
'unique-operation-name': rule$p,
|
3269
|
+
'description-style': rule$1,
|
3270
|
+
'input-name': rule$2,
|
3271
|
+
'match-document-filename': rule$3,
|
3272
|
+
'naming-convention': rule$4,
|
3273
|
+
'no-anonymous-operations': rule$5,
|
3274
|
+
'no-case-insensitive-enum-values-duplicates': rule$6,
|
3275
|
+
'no-deprecated': rule$7,
|
3276
|
+
'no-duplicate-fields': rule$8,
|
3277
|
+
'no-hashtag-description': rule$9,
|
3278
|
+
'no-root-type': rule$a,
|
3279
|
+
'no-scalar-result-type-on-mutation': rule$b,
|
3280
|
+
'no-typename-prefix': rule$c,
|
3281
|
+
'no-unreachable-types': rule$d,
|
3282
|
+
'no-unused-fields': rule$e,
|
3283
|
+
'require-deprecation-date': rule$f,
|
3284
|
+
'require-deprecation-reason': rule$g,
|
3285
|
+
'require-description': rule$h,
|
3286
|
+
'require-field-of-type-query-in-mutation-result': rule$i,
|
3287
|
+
'require-id-when-available': rule$j,
|
3288
|
+
'selection-set-depth': rule$k,
|
3289
|
+
'strict-id-in-types': rule$l,
|
3290
|
+
'unique-fragment-name': rule$m,
|
3291
|
+
'unique-operation-name': rule$n,
|
3451
3292
|
};
|
3452
3293
|
|
3453
3294
|
const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
|