@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.mjs
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Kind, validate,
|
1
|
+
import { Kind, validate, TokenKind, isScalarType, isNonNullType, isListType, isObjectType as isObjectType$1, visit, visitWithTypeInfo, GraphQLObjectType, GraphQLInterfaceType, TypeInfo, isInterfaceType, Source, GraphQLError } from 'graphql';
|
2
2
|
import { validateSDL } from 'graphql/validation/validate';
|
3
3
|
import { processImport, parseImportLine } from '@graphql-tools/import';
|
4
4
|
import { statSync, existsSync, readFileSync } from 'fs';
|
@@ -19,7 +19,6 @@ const recommendedConfig = {
|
|
19
19
|
parser: '@graphql-eslint/eslint-plugin',
|
20
20
|
plugins: ['@graphql-eslint'],
|
21
21
|
rules: {
|
22
|
-
'@graphql-eslint/avoid-typename-prefix': 'error',
|
23
22
|
'@graphql-eslint/executable-definitions': 'error',
|
24
23
|
'@graphql-eslint/fields-on-correct-type': 'error',
|
25
24
|
'@graphql-eslint/fragments-on-composite-type': 'error',
|
@@ -60,7 +59,7 @@ const recommendedConfig = {
|
|
60
59
|
'@graphql-eslint/no-anonymous-operations': 'error',
|
61
60
|
'@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
|
62
61
|
'@graphql-eslint/no-fragment-cycles': 'error',
|
63
|
-
'@graphql-eslint/no-
|
62
|
+
'@graphql-eslint/no-typename-prefix': 'error',
|
64
63
|
'@graphql-eslint/no-undefined-variables': 'error',
|
65
64
|
'@graphql-eslint/no-unused-fragments': 'error',
|
66
65
|
'@graphql-eslint/no-unused-variables': 'error',
|
@@ -104,15 +103,14 @@ const allConfig = {
|
|
104
103
|
arguments: ['FieldDefinition', 'Field', 'DirectiveDefinition', 'Directive'],
|
105
104
|
},
|
106
105
|
],
|
107
|
-
'@graphql-eslint/avoid-duplicate-fields': 'error',
|
108
|
-
'@graphql-eslint/avoid-operation-name-prefix': 'error',
|
109
|
-
'@graphql-eslint/avoid-scalar-result-type-on-mutation': 'error',
|
110
106
|
'@graphql-eslint/description-style': 'error',
|
111
107
|
'@graphql-eslint/input-name': 'error',
|
112
108
|
'@graphql-eslint/match-document-filename': 'error',
|
113
109
|
'@graphql-eslint/no-deprecated': 'error',
|
110
|
+
'@graphql-eslint/no-duplicate-fields': 'error',
|
114
111
|
'@graphql-eslint/no-hashtag-description': 'error',
|
115
112
|
'@graphql-eslint/no-root-type': ['error', { disallow: ['subscription'] }],
|
113
|
+
'@graphql-eslint/no-scalar-result-type-on-mutation': 'error',
|
116
114
|
'@graphql-eslint/no-unreachable-types': 'error',
|
117
115
|
'@graphql-eslint/no-unused-fields': 'error',
|
118
116
|
'@graphql-eslint/require-deprecation-date': 'error',
|
@@ -786,307 +784,7 @@ const rule = {
|
|
786
784
|
},
|
787
785
|
};
|
788
786
|
|
789
|
-
const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
|
790
787
|
const rule$1 = {
|
791
|
-
meta: {
|
792
|
-
type: 'suggestion',
|
793
|
-
docs: {
|
794
|
-
description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
|
795
|
-
category: 'Stylistic Issues',
|
796
|
-
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
|
797
|
-
examples: [
|
798
|
-
{
|
799
|
-
title: 'Incorrect',
|
800
|
-
code: /* GraphQL */ `
|
801
|
-
query {
|
802
|
-
user {
|
803
|
-
name
|
804
|
-
email
|
805
|
-
name # duplicate field
|
806
|
-
}
|
807
|
-
}
|
808
|
-
`,
|
809
|
-
},
|
810
|
-
{
|
811
|
-
title: 'Incorrect',
|
812
|
-
code: /* GraphQL */ `
|
813
|
-
query {
|
814
|
-
users(
|
815
|
-
first: 100
|
816
|
-
skip: 50
|
817
|
-
after: "cji629tngfgou0b73kt7vi5jo"
|
818
|
-
first: 100 # duplicate argument
|
819
|
-
) {
|
820
|
-
id
|
821
|
-
}
|
822
|
-
}
|
823
|
-
`,
|
824
|
-
},
|
825
|
-
{
|
826
|
-
title: 'Incorrect',
|
827
|
-
code: /* GraphQL */ `
|
828
|
-
query (
|
829
|
-
$first: Int!
|
830
|
-
$first: Int! # duplicate variable
|
831
|
-
) {
|
832
|
-
users(first: $first, skip: 50) {
|
833
|
-
id
|
834
|
-
}
|
835
|
-
}
|
836
|
-
`,
|
837
|
-
},
|
838
|
-
],
|
839
|
-
},
|
840
|
-
messages: {
|
841
|
-
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
|
842
|
-
},
|
843
|
-
schema: [],
|
844
|
-
},
|
845
|
-
create(context) {
|
846
|
-
function checkNode(usedFields, fieldName, type, node) {
|
847
|
-
if (usedFields.has(fieldName)) {
|
848
|
-
context.report({
|
849
|
-
loc: getLocation((node.kind === Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
|
850
|
-
offsetEnd: node.kind === Kind.VARIABLE_DEFINITION ? 0 : 1,
|
851
|
-
}),
|
852
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
853
|
-
data: {
|
854
|
-
type,
|
855
|
-
fieldName,
|
856
|
-
},
|
857
|
-
});
|
858
|
-
}
|
859
|
-
else {
|
860
|
-
usedFields.add(fieldName);
|
861
|
-
}
|
862
|
-
}
|
863
|
-
return {
|
864
|
-
OperationDefinition(node) {
|
865
|
-
const set = new Set();
|
866
|
-
for (const varDef of node.variableDefinitions) {
|
867
|
-
checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
|
868
|
-
}
|
869
|
-
},
|
870
|
-
Field(node) {
|
871
|
-
const set = new Set();
|
872
|
-
for (const arg of node.arguments) {
|
873
|
-
checkNode(set, arg.name.value, 'Field argument', arg);
|
874
|
-
}
|
875
|
-
},
|
876
|
-
SelectionSet(node) {
|
877
|
-
var _a;
|
878
|
-
const set = new Set();
|
879
|
-
for (const selection of node.selections) {
|
880
|
-
if (selection.kind === Kind.FIELD) {
|
881
|
-
checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
|
882
|
-
}
|
883
|
-
}
|
884
|
-
},
|
885
|
-
};
|
886
|
-
},
|
887
|
-
};
|
888
|
-
|
889
|
-
const AVOID_OPERATION_NAME_PREFIX = 'AVOID_OPERATION_NAME_PREFIX';
|
890
|
-
const rule$2 = {
|
891
|
-
meta: {
|
892
|
-
type: 'suggestion',
|
893
|
-
docs: {
|
894
|
-
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.',
|
895
|
-
category: 'Stylistic Issues',
|
896
|
-
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-operation-name-prefix.md',
|
897
|
-
examples: [
|
898
|
-
{
|
899
|
-
title: 'Incorrect',
|
900
|
-
usage: [{ keywords: ['get'] }],
|
901
|
-
code: /* GraphQL */ `
|
902
|
-
query getUserDetails {
|
903
|
-
# ...
|
904
|
-
}`,
|
905
|
-
},
|
906
|
-
{
|
907
|
-
title: 'Correct',
|
908
|
-
usage: [{ keywords: ['get'] }],
|
909
|
-
code: /* GraphQL */ `
|
910
|
-
query userDetails {
|
911
|
-
# ...
|
912
|
-
}`,
|
913
|
-
},
|
914
|
-
],
|
915
|
-
},
|
916
|
-
messages: {
|
917
|
-
[AVOID_OPERATION_NAME_PREFIX]: `Forbidden operation name prefix: "{{ invalidPrefix }}"`,
|
918
|
-
},
|
919
|
-
schema: [
|
920
|
-
{
|
921
|
-
additionalProperties: false,
|
922
|
-
type: 'object',
|
923
|
-
required: ['keywords'],
|
924
|
-
properties: {
|
925
|
-
caseSensitive: {
|
926
|
-
default: false,
|
927
|
-
type: 'boolean',
|
928
|
-
},
|
929
|
-
keywords: {
|
930
|
-
additionalItems: false,
|
931
|
-
type: 'array',
|
932
|
-
minItems: 1,
|
933
|
-
items: {
|
934
|
-
type: 'string',
|
935
|
-
},
|
936
|
-
},
|
937
|
-
},
|
938
|
-
},
|
939
|
-
],
|
940
|
-
},
|
941
|
-
create(context) {
|
942
|
-
return {
|
943
|
-
'OperationDefinition, FragmentDefinition'(node) {
|
944
|
-
const config = context.options[0] || { keywords: [], caseSensitive: false };
|
945
|
-
const caseSensitive = config.caseSensitive;
|
946
|
-
const keywords = config.keywords || [];
|
947
|
-
if (node && node.name && node.name.value !== '') {
|
948
|
-
for (const keyword of keywords) {
|
949
|
-
const testKeyword = caseSensitive ? keyword : keyword.toLowerCase();
|
950
|
-
const testName = caseSensitive ? node.name.value : node.name.value.toLowerCase();
|
951
|
-
if (testName.startsWith(testKeyword)) {
|
952
|
-
const { start } = node.name.loc;
|
953
|
-
context.report({
|
954
|
-
loc: {
|
955
|
-
start: {
|
956
|
-
line: start.line,
|
957
|
-
column: start.column - 1,
|
958
|
-
},
|
959
|
-
end: {
|
960
|
-
line: start.line,
|
961
|
-
column: start.column - 1 + testKeyword.length,
|
962
|
-
},
|
963
|
-
},
|
964
|
-
data: {
|
965
|
-
invalidPrefix: keyword,
|
966
|
-
},
|
967
|
-
messageId: AVOID_OPERATION_NAME_PREFIX,
|
968
|
-
});
|
969
|
-
}
|
970
|
-
}
|
971
|
-
}
|
972
|
-
},
|
973
|
-
};
|
974
|
-
},
|
975
|
-
};
|
976
|
-
|
977
|
-
const rule$3 = {
|
978
|
-
meta: {
|
979
|
-
type: 'suggestion',
|
980
|
-
docs: {
|
981
|
-
category: 'Best Practices',
|
982
|
-
description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
|
983
|
-
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-scalar-result-type-on-mutation.md',
|
984
|
-
requiresSchema: true,
|
985
|
-
examples: [
|
986
|
-
{
|
987
|
-
title: 'Incorrect',
|
988
|
-
code: /* GraphQL */ `
|
989
|
-
type Mutation {
|
990
|
-
createUser: Boolean
|
991
|
-
}
|
992
|
-
`,
|
993
|
-
},
|
994
|
-
{
|
995
|
-
title: 'Correct',
|
996
|
-
code: /* GraphQL */ `
|
997
|
-
type Mutation {
|
998
|
-
createUser: User!
|
999
|
-
}
|
1000
|
-
`,
|
1001
|
-
},
|
1002
|
-
],
|
1003
|
-
},
|
1004
|
-
schema: [],
|
1005
|
-
},
|
1006
|
-
create(context) {
|
1007
|
-
const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
|
1008
|
-
const mutationType = schema.getMutationType();
|
1009
|
-
if (!mutationType) {
|
1010
|
-
return {};
|
1011
|
-
}
|
1012
|
-
const selector = [
|
1013
|
-
`:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
1014
|
-
'>',
|
1015
|
-
Kind.FIELD_DEFINITION,
|
1016
|
-
Kind.NAMED_TYPE,
|
1017
|
-
].join(' ');
|
1018
|
-
return {
|
1019
|
-
[selector](node) {
|
1020
|
-
const typeName = node.name.value;
|
1021
|
-
const graphQLType = schema.getType(typeName);
|
1022
|
-
if (isScalarType(graphQLType)) {
|
1023
|
-
context.report({
|
1024
|
-
loc: getLocation(node.loc, typeName),
|
1025
|
-
message: `Unexpected scalar result type "${typeName}"`,
|
1026
|
-
});
|
1027
|
-
}
|
1028
|
-
},
|
1029
|
-
};
|
1030
|
-
},
|
1031
|
-
};
|
1032
|
-
|
1033
|
-
const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
|
1034
|
-
const rule$4 = {
|
1035
|
-
meta: {
|
1036
|
-
type: 'suggestion',
|
1037
|
-
docs: {
|
1038
|
-
category: 'Best Practices',
|
1039
|
-
description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
|
1040
|
-
recommended: true,
|
1041
|
-
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-typename-prefix.md',
|
1042
|
-
examples: [
|
1043
|
-
{
|
1044
|
-
title: 'Incorrect',
|
1045
|
-
code: /* GraphQL */ `
|
1046
|
-
type User {
|
1047
|
-
userId: ID!
|
1048
|
-
}
|
1049
|
-
`,
|
1050
|
-
},
|
1051
|
-
{
|
1052
|
-
title: 'Correct',
|
1053
|
-
code: /* GraphQL */ `
|
1054
|
-
type User {
|
1055
|
-
id: ID!
|
1056
|
-
}
|
1057
|
-
`,
|
1058
|
-
},
|
1059
|
-
],
|
1060
|
-
},
|
1061
|
-
messages: {
|
1062
|
-
[AVOID_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
|
1063
|
-
},
|
1064
|
-
schema: [],
|
1065
|
-
},
|
1066
|
-
create(context) {
|
1067
|
-
return {
|
1068
|
-
'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
|
1069
|
-
const typeName = node.name.value;
|
1070
|
-
const lowerTypeName = typeName.toLowerCase();
|
1071
|
-
for (const field of node.fields) {
|
1072
|
-
const fieldName = field.name.value;
|
1073
|
-
if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
|
1074
|
-
context.report({
|
1075
|
-
data: {
|
1076
|
-
fieldName,
|
1077
|
-
typeName,
|
1078
|
-
},
|
1079
|
-
messageId: AVOID_TYPENAME_PREFIX,
|
1080
|
-
loc: getLocation(field.loc, lowerTypeName),
|
1081
|
-
});
|
1082
|
-
}
|
1083
|
-
}
|
1084
|
-
},
|
1085
|
-
};
|
1086
|
-
},
|
1087
|
-
};
|
1088
|
-
|
1089
|
-
const rule$5 = {
|
1090
788
|
meta: {
|
1091
789
|
type: 'suggestion',
|
1092
790
|
docs: {
|
@@ -1149,7 +847,7 @@ const rule$5 = {
|
|
1149
847
|
const isObjectType = (node) => [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
|
1150
848
|
const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
|
1151
849
|
const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
|
1152
|
-
const rule$
|
850
|
+
const rule$2 = {
|
1153
851
|
meta: {
|
1154
852
|
type: 'suggestion',
|
1155
853
|
docs: {
|
@@ -1275,7 +973,7 @@ const CASE_STYLES = [
|
|
1275
973
|
const schemaOption = {
|
1276
974
|
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
|
1277
975
|
};
|
1278
|
-
const rule$
|
976
|
+
const rule$3 = {
|
1279
977
|
meta: {
|
1280
978
|
type: 'suggestion',
|
1281
979
|
docs: {
|
@@ -1493,7 +1191,7 @@ const ALLOWED_STYLES = Object.keys(StyleToRegex);
|
|
1493
1191
|
const schemaOption$1 = {
|
1494
1192
|
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
|
1495
1193
|
};
|
1496
|
-
const rule$
|
1194
|
+
const rule$4 = {
|
1497
1195
|
meta: {
|
1498
1196
|
type: 'suggestion',
|
1499
1197
|
docs: {
|
@@ -1706,7 +1404,7 @@ const rule$8 = {
|
|
1706
1404
|
};
|
1707
1405
|
|
1708
1406
|
const NO_ANONYMOUS_OPERATIONS = 'NO_ANONYMOUS_OPERATIONS';
|
1709
|
-
const rule$
|
1407
|
+
const rule$5 = {
|
1710
1408
|
meta: {
|
1711
1409
|
type: 'suggestion',
|
1712
1410
|
docs: {
|
@@ -1754,7 +1452,7 @@ const rule$9 = {
|
|
1754
1452
|
};
|
1755
1453
|
|
1756
1454
|
const ERROR_MESSAGE_ID = 'NO_CASE_INSENSITIVE_ENUM_VALUES_DUPLICATES';
|
1757
|
-
const rule$
|
1455
|
+
const rule$6 = {
|
1758
1456
|
meta: {
|
1759
1457
|
type: 'suggestion',
|
1760
1458
|
docs: {
|
@@ -1810,7 +1508,7 @@ const rule$a = {
|
|
1810
1508
|
};
|
1811
1509
|
|
1812
1510
|
const NO_DEPRECATED = 'NO_DEPRECATED';
|
1813
|
-
const rule$
|
1511
|
+
const rule$7 = {
|
1814
1512
|
meta: {
|
1815
1513
|
type: 'suggestion',
|
1816
1514
|
docs: {
|
@@ -1861,20 +1559,116 @@ const rule$b = {
|
|
1861
1559
|
`,
|
1862
1560
|
},
|
1863
1561
|
{
|
1864
|
-
title: 'Correct',
|
1562
|
+
title: 'Correct',
|
1563
|
+
code: /* GraphQL */ `
|
1564
|
+
# In your schema
|
1565
|
+
type User {
|
1566
|
+
id: ID!
|
1567
|
+
name: String! @deprecated(reason: "old field, please use fullName instead")
|
1568
|
+
fullName: String!
|
1569
|
+
}
|
1570
|
+
|
1571
|
+
# Query
|
1572
|
+
query user {
|
1573
|
+
user {
|
1574
|
+
id
|
1575
|
+
fullName
|
1576
|
+
}
|
1577
|
+
}
|
1578
|
+
`,
|
1579
|
+
},
|
1580
|
+
],
|
1581
|
+
},
|
1582
|
+
messages: {
|
1583
|
+
[NO_DEPRECATED]: `This {{ type }} is marked as deprecated in your GraphQL schema {{ reason }}`,
|
1584
|
+
},
|
1585
|
+
schema: [],
|
1586
|
+
},
|
1587
|
+
create(context) {
|
1588
|
+
return {
|
1589
|
+
EnumValue(node) {
|
1590
|
+
requireGraphQLSchemaFromContext('no-deprecated', context);
|
1591
|
+
const typeInfo = node.typeInfo();
|
1592
|
+
if (typeInfo && typeInfo.enumValue) {
|
1593
|
+
if (typeInfo.enumValue.deprecationReason) {
|
1594
|
+
const enumValueName = node.value;
|
1595
|
+
context.report({
|
1596
|
+
loc: getLocation(node.loc, enumValueName),
|
1597
|
+
messageId: NO_DEPRECATED,
|
1598
|
+
data: {
|
1599
|
+
type: 'enum value',
|
1600
|
+
reason: typeInfo.enumValue.deprecationReason ? `(reason: ${typeInfo.enumValue.deprecationReason})` : '',
|
1601
|
+
},
|
1602
|
+
});
|
1603
|
+
}
|
1604
|
+
}
|
1605
|
+
},
|
1606
|
+
Field(node) {
|
1607
|
+
requireGraphQLSchemaFromContext('no-deprecated', context);
|
1608
|
+
const typeInfo = node.typeInfo();
|
1609
|
+
if (typeInfo && typeInfo.fieldDef) {
|
1610
|
+
if (typeInfo.fieldDef.deprecationReason) {
|
1611
|
+
const fieldName = node.name.value;
|
1612
|
+
context.report({
|
1613
|
+
loc: getLocation(node.loc, fieldName),
|
1614
|
+
messageId: NO_DEPRECATED,
|
1615
|
+
data: {
|
1616
|
+
type: 'field',
|
1617
|
+
reason: typeInfo.fieldDef.deprecationReason ? `(reason: ${typeInfo.fieldDef.deprecationReason})` : '',
|
1618
|
+
},
|
1619
|
+
});
|
1620
|
+
}
|
1621
|
+
}
|
1622
|
+
},
|
1623
|
+
};
|
1624
|
+
},
|
1625
|
+
};
|
1626
|
+
|
1627
|
+
const NO_DUPLICATE_FIELDS = 'NO_DUPLICATE_FIELDS';
|
1628
|
+
const rule$8 = {
|
1629
|
+
meta: {
|
1630
|
+
type: 'suggestion',
|
1631
|
+
docs: {
|
1632
|
+
description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
|
1633
|
+
category: 'Stylistic Issues',
|
1634
|
+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-duplicate-fields.md',
|
1635
|
+
examples: [
|
1636
|
+
{
|
1637
|
+
title: 'Incorrect',
|
1638
|
+
code: /* GraphQL */ `
|
1639
|
+
query {
|
1640
|
+
user {
|
1641
|
+
name
|
1642
|
+
email
|
1643
|
+
name # duplicate field
|
1644
|
+
}
|
1645
|
+
}
|
1646
|
+
`,
|
1647
|
+
},
|
1648
|
+
{
|
1649
|
+
title: 'Incorrect',
|
1650
|
+
code: /* GraphQL */ `
|
1651
|
+
query {
|
1652
|
+
users(
|
1653
|
+
first: 100
|
1654
|
+
skip: 50
|
1655
|
+
after: "cji629tngfgou0b73kt7vi5jo"
|
1656
|
+
first: 100 # duplicate argument
|
1657
|
+
) {
|
1658
|
+
id
|
1659
|
+
}
|
1660
|
+
}
|
1661
|
+
`,
|
1662
|
+
},
|
1663
|
+
{
|
1664
|
+
title: 'Incorrect',
|
1865
1665
|
code: /* GraphQL */ `
|
1866
|
-
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
|
1871
|
-
}
|
1872
|
-
|
1873
|
-
# Query
|
1874
|
-
query user {
|
1875
|
-
user {
|
1666
|
+
query (
|
1667
|
+
$first: Int!
|
1668
|
+
$first: Int! # duplicate variable
|
1669
|
+
) {
|
1670
|
+
users(first: $first, skip: 50) {
|
1876
1671
|
id
|
1877
|
-
fullName
|
1878
1672
|
}
|
1879
1673
|
}
|
1880
1674
|
`,
|
@@ -1882,43 +1676,47 @@ const rule$b = {
|
|
1882
1676
|
],
|
1883
1677
|
},
|
1884
1678
|
messages: {
|
1885
|
-
[
|
1679
|
+
[NO_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
|
1886
1680
|
},
|
1887
1681
|
schema: [],
|
1888
1682
|
},
|
1889
1683
|
create(context) {
|
1684
|
+
function checkNode(usedFields, fieldName, type, node) {
|
1685
|
+
if (usedFields.has(fieldName)) {
|
1686
|
+
context.report({
|
1687
|
+
loc: getLocation((node.kind === Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
|
1688
|
+
offsetEnd: node.kind === Kind.VARIABLE_DEFINITION ? 0 : 1,
|
1689
|
+
}),
|
1690
|
+
messageId: NO_DUPLICATE_FIELDS,
|
1691
|
+
data: {
|
1692
|
+
type,
|
1693
|
+
fieldName,
|
1694
|
+
},
|
1695
|
+
});
|
1696
|
+
}
|
1697
|
+
else {
|
1698
|
+
usedFields.add(fieldName);
|
1699
|
+
}
|
1700
|
+
}
|
1890
1701
|
return {
|
1891
|
-
|
1892
|
-
|
1893
|
-
const
|
1894
|
-
|
1895
|
-
if (typeInfo.enumValue.deprecationReason) {
|
1896
|
-
const enumValueName = node.value;
|
1897
|
-
context.report({
|
1898
|
-
loc: getLocation(node.loc, enumValueName),
|
1899
|
-
messageId: NO_DEPRECATED,
|
1900
|
-
data: {
|
1901
|
-
type: 'enum value',
|
1902
|
-
reason: typeInfo.enumValue.deprecationReason ? `(reason: ${typeInfo.enumValue.deprecationReason})` : '',
|
1903
|
-
},
|
1904
|
-
});
|
1905
|
-
}
|
1702
|
+
OperationDefinition(node) {
|
1703
|
+
const set = new Set();
|
1704
|
+
for (const varDef of node.variableDefinitions) {
|
1705
|
+
checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
|
1906
1706
|
}
|
1907
1707
|
},
|
1908
1708
|
Field(node) {
|
1909
|
-
|
1910
|
-
const
|
1911
|
-
|
1912
|
-
|
1913
|
-
|
1914
|
-
|
1915
|
-
|
1916
|
-
|
1917
|
-
|
1918
|
-
|
1919
|
-
|
1920
|
-
},
|
1921
|
-
});
|
1709
|
+
const set = new Set();
|
1710
|
+
for (const arg of node.arguments) {
|
1711
|
+
checkNode(set, arg.name.value, 'Field argument', arg);
|
1712
|
+
}
|
1713
|
+
},
|
1714
|
+
SelectionSet(node) {
|
1715
|
+
var _a;
|
1716
|
+
const set = new Set();
|
1717
|
+
for (const selection of node.selections) {
|
1718
|
+
if (selection.kind === Kind.FIELD) {
|
1719
|
+
checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
|
1922
1720
|
}
|
1923
1721
|
}
|
1924
1722
|
},
|
@@ -1927,7 +1725,7 @@ const rule$b = {
|
|
1927
1725
|
};
|
1928
1726
|
|
1929
1727
|
const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
|
1930
|
-
const rule$
|
1728
|
+
const rule$9 = {
|
1931
1729
|
meta: {
|
1932
1730
|
messages: {
|
1933
1731
|
[HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
|
@@ -1999,75 +1797,8 @@ const rule$c = {
|
|
1999
1797
|
},
|
2000
1798
|
};
|
2001
1799
|
|
2002
|
-
const NO_OPERATION_NAME_SUFFIX = 'NO_OPERATION_NAME_SUFFIX';
|
2003
|
-
const rule$d = {
|
2004
|
-
meta: {
|
2005
|
-
fixable: 'code',
|
2006
|
-
type: 'suggestion',
|
2007
|
-
docs: {
|
2008
|
-
category: 'Stylistic Issues',
|
2009
|
-
recommended: true,
|
2010
|
-
description: `Makes sure you are not adding the operation type to the name of the operation.`,
|
2011
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-operation-name-suffix.md`,
|
2012
|
-
examples: [
|
2013
|
-
{
|
2014
|
-
title: 'Incorrect',
|
2015
|
-
code: /* GraphQL */ `
|
2016
|
-
query userQuery {
|
2017
|
-
# ...
|
2018
|
-
}
|
2019
|
-
`,
|
2020
|
-
},
|
2021
|
-
{
|
2022
|
-
title: 'Correct',
|
2023
|
-
code: /* GraphQL */ `
|
2024
|
-
query user {
|
2025
|
-
# ...
|
2026
|
-
}
|
2027
|
-
`,
|
2028
|
-
},
|
2029
|
-
],
|
2030
|
-
},
|
2031
|
-
messages: {
|
2032
|
-
[NO_OPERATION_NAME_SUFFIX]: `Unnecessary "{{ invalidSuffix }}" suffix in your operation name!`,
|
2033
|
-
},
|
2034
|
-
schema: [],
|
2035
|
-
},
|
2036
|
-
create(context) {
|
2037
|
-
return {
|
2038
|
-
'OperationDefinition, FragmentDefinition'(node) {
|
2039
|
-
var _a;
|
2040
|
-
const name = ((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '';
|
2041
|
-
if (name.length > 0) {
|
2042
|
-
const invalidSuffix = 'operation' in node ? node.operation : 'fragment';
|
2043
|
-
if (name.toLowerCase().endsWith(invalidSuffix)) {
|
2044
|
-
const { start, end } = node.name.loc;
|
2045
|
-
context.report({
|
2046
|
-
loc: {
|
2047
|
-
start: {
|
2048
|
-
column: start.column - 1 + name.length - invalidSuffix.length,
|
2049
|
-
line: start.line,
|
2050
|
-
},
|
2051
|
-
end: {
|
2052
|
-
column: end.column - 1 + name.length,
|
2053
|
-
line: end.line,
|
2054
|
-
},
|
2055
|
-
},
|
2056
|
-
data: {
|
2057
|
-
invalidSuffix,
|
2058
|
-
},
|
2059
|
-
fix: fixer => fixer.removeRange([node.name.range[1] - invalidSuffix.length, node.name.range[1]]),
|
2060
|
-
messageId: NO_OPERATION_NAME_SUFFIX,
|
2061
|
-
});
|
2062
|
-
}
|
2063
|
-
}
|
2064
|
-
},
|
2065
|
-
};
|
2066
|
-
},
|
2067
|
-
};
|
2068
|
-
|
2069
1800
|
const ROOT_TYPES = ['query', 'mutation', 'subscription'];
|
2070
|
-
const rule$
|
1801
|
+
const rule$a = {
|
2071
1802
|
meta: {
|
2072
1803
|
type: 'suggestion',
|
2073
1804
|
docs: {
|
@@ -2157,9 +1888,121 @@ const rule$e = {
|
|
2157
1888
|
},
|
2158
1889
|
};
|
2159
1890
|
|
1891
|
+
const rule$b = {
|
1892
|
+
meta: {
|
1893
|
+
type: 'suggestion',
|
1894
|
+
docs: {
|
1895
|
+
category: 'Best Practices',
|
1896
|
+
description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
|
1897
|
+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-scalar-result-type-on-mutation.md',
|
1898
|
+
requiresSchema: true,
|
1899
|
+
examples: [
|
1900
|
+
{
|
1901
|
+
title: 'Incorrect',
|
1902
|
+
code: /* GraphQL */ `
|
1903
|
+
type Mutation {
|
1904
|
+
createUser: Boolean
|
1905
|
+
}
|
1906
|
+
`,
|
1907
|
+
},
|
1908
|
+
{
|
1909
|
+
title: 'Correct',
|
1910
|
+
code: /* GraphQL */ `
|
1911
|
+
type Mutation {
|
1912
|
+
createUser: User!
|
1913
|
+
}
|
1914
|
+
`,
|
1915
|
+
},
|
1916
|
+
],
|
1917
|
+
},
|
1918
|
+
schema: [],
|
1919
|
+
},
|
1920
|
+
create(context) {
|
1921
|
+
const schema = requireGraphQLSchemaFromContext('no-scalar-result-type-on-mutation', context);
|
1922
|
+
const mutationType = schema.getMutationType();
|
1923
|
+
if (!mutationType) {
|
1924
|
+
return {};
|
1925
|
+
}
|
1926
|
+
const selector = [
|
1927
|
+
`:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
1928
|
+
'>',
|
1929
|
+
Kind.FIELD_DEFINITION,
|
1930
|
+
Kind.NAMED_TYPE,
|
1931
|
+
].join(' ');
|
1932
|
+
return {
|
1933
|
+
[selector](node) {
|
1934
|
+
const typeName = node.name.value;
|
1935
|
+
const graphQLType = schema.getType(typeName);
|
1936
|
+
if (isScalarType(graphQLType)) {
|
1937
|
+
context.report({
|
1938
|
+
loc: getLocation(node.loc, typeName),
|
1939
|
+
message: `Unexpected scalar result type "${typeName}"`,
|
1940
|
+
});
|
1941
|
+
}
|
1942
|
+
},
|
1943
|
+
};
|
1944
|
+
},
|
1945
|
+
};
|
1946
|
+
|
1947
|
+
const NO_TYPENAME_PREFIX = 'NO_TYPENAME_PREFIX';
|
1948
|
+
const rule$c = {
|
1949
|
+
meta: {
|
1950
|
+
type: 'suggestion',
|
1951
|
+
docs: {
|
1952
|
+
category: 'Best Practices',
|
1953
|
+
description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
|
1954
|
+
recommended: true,
|
1955
|
+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-typename-prefix.md',
|
1956
|
+
examples: [
|
1957
|
+
{
|
1958
|
+
title: 'Incorrect',
|
1959
|
+
code: /* GraphQL */ `
|
1960
|
+
type User {
|
1961
|
+
userId: ID!
|
1962
|
+
}
|
1963
|
+
`,
|
1964
|
+
},
|
1965
|
+
{
|
1966
|
+
title: 'Correct',
|
1967
|
+
code: /* GraphQL */ `
|
1968
|
+
type User {
|
1969
|
+
id: ID!
|
1970
|
+
}
|
1971
|
+
`,
|
1972
|
+
},
|
1973
|
+
],
|
1974
|
+
},
|
1975
|
+
messages: {
|
1976
|
+
[NO_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
|
1977
|
+
},
|
1978
|
+
schema: [],
|
1979
|
+
},
|
1980
|
+
create(context) {
|
1981
|
+
return {
|
1982
|
+
'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
|
1983
|
+
const typeName = node.name.value;
|
1984
|
+
const lowerTypeName = typeName.toLowerCase();
|
1985
|
+
for (const field of node.fields) {
|
1986
|
+
const fieldName = field.name.value;
|
1987
|
+
if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
|
1988
|
+
context.report({
|
1989
|
+
data: {
|
1990
|
+
fieldName,
|
1991
|
+
typeName,
|
1992
|
+
},
|
1993
|
+
messageId: NO_TYPENAME_PREFIX,
|
1994
|
+
loc: getLocation(field.loc, lowerTypeName),
|
1995
|
+
});
|
1996
|
+
}
|
1997
|
+
}
|
1998
|
+
},
|
1999
|
+
};
|
2000
|
+
},
|
2001
|
+
};
|
2002
|
+
|
2160
2003
|
const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
|
2161
2004
|
const RULE_NAME = 'no-unreachable-types';
|
2162
|
-
const rule$
|
2005
|
+
const rule$d = {
|
2163
2006
|
meta: {
|
2164
2007
|
messages: {
|
2165
2008
|
[UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
|
@@ -2235,7 +2078,7 @@ const rule$f = {
|
|
2235
2078
|
|
2236
2079
|
const UNUSED_FIELD = 'UNUSED_FIELD';
|
2237
2080
|
const RULE_NAME$1 = 'no-unused-fields';
|
2238
|
-
const rule$
|
2081
|
+
const rule$e = {
|
2239
2082
|
meta: {
|
2240
2083
|
messages: {
|
2241
2084
|
[UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
|
@@ -2424,7 +2267,7 @@ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
|
|
2424
2267
|
const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
|
2425
2268
|
const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
|
2426
2269
|
const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
|
2427
|
-
const rule$
|
2270
|
+
const rule$f = {
|
2428
2271
|
meta: {
|
2429
2272
|
type: 'suggestion',
|
2430
2273
|
docs: {
|
@@ -2527,7 +2370,7 @@ const rule$h = {
|
|
2527
2370
|
},
|
2528
2371
|
};
|
2529
2372
|
|
2530
|
-
const rule$
|
2373
|
+
const rule$g = {
|
2531
2374
|
meta: {
|
2532
2375
|
docs: {
|
2533
2376
|
description: `Require all deprecation directives to specify a reason.`,
|
@@ -2589,7 +2432,7 @@ const ALLOWED_KINDS$1 = [
|
|
2589
2432
|
Kind.ENUM_VALUE_DEFINITION,
|
2590
2433
|
Kind.DIRECTIVE_DEFINITION,
|
2591
2434
|
];
|
2592
|
-
const rule$
|
2435
|
+
const rule$h = {
|
2593
2436
|
meta: {
|
2594
2437
|
docs: {
|
2595
2438
|
category: 'Best Practices',
|
@@ -2688,7 +2531,7 @@ const rule$j = {
|
|
2688
2531
|
};
|
2689
2532
|
|
2690
2533
|
const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
|
2691
|
-
const rule$
|
2534
|
+
const rule$i = {
|
2692
2535
|
meta: {
|
2693
2536
|
type: 'suggestion',
|
2694
2537
|
docs: {
|
@@ -2856,7 +2699,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
|
|
2856
2699
|
|
2857
2700
|
const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
|
2858
2701
|
const DEFAULT_ID_FIELD_NAME = 'id';
|
2859
|
-
const rule$
|
2702
|
+
const rule$j = {
|
2860
2703
|
meta: {
|
2861
2704
|
type: 'suggestion',
|
2862
2705
|
docs: {
|
@@ -2992,7 +2835,7 @@ const rule$l = {
|
|
2992
2835
|
},
|
2993
2836
|
};
|
2994
2837
|
|
2995
|
-
const rule$
|
2838
|
+
const rule$k = {
|
2996
2839
|
meta: {
|
2997
2840
|
docs: {
|
2998
2841
|
category: 'Best Practices',
|
@@ -3114,7 +2957,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
|
|
3114
2957
|
}
|
3115
2958
|
return false;
|
3116
2959
|
};
|
3117
|
-
const rule$
|
2960
|
+
const rule$l = {
|
3118
2961
|
meta: {
|
3119
2962
|
type: 'suggestion',
|
3120
2963
|
docs: {
|
@@ -3291,7 +3134,7 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3291
3134
|
});
|
3292
3135
|
}
|
3293
3136
|
};
|
3294
|
-
const rule$
|
3137
|
+
const rule$m = {
|
3295
3138
|
meta: {
|
3296
3139
|
type: 'suggestion',
|
3297
3140
|
docs: {
|
@@ -3350,7 +3193,7 @@ const rule$o = {
|
|
3350
3193
|
|
3351
3194
|
const RULE_NAME$4 = 'unique-operation-name';
|
3352
3195
|
const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
|
3353
|
-
const rule$
|
3196
|
+
const rule$n = {
|
3354
3197
|
meta: {
|
3355
3198
|
type: 'suggestion',
|
3356
3199
|
docs: {
|
@@ -3417,31 +3260,29 @@ const rule$p = {
|
|
3417
3260
|
const rules = {
|
3418
3261
|
...GRAPHQL_JS_VALIDATIONS,
|
3419
3262
|
alphabetize: rule,
|
3420
|
-
'
|
3421
|
-
'
|
3422
|
-
'
|
3423
|
-
'
|
3424
|
-
'
|
3425
|
-
'
|
3426
|
-
'
|
3427
|
-
'
|
3428
|
-
'no-
|
3429
|
-
'no-
|
3430
|
-
'no-
|
3431
|
-
'no-
|
3432
|
-
'no-
|
3433
|
-
'no-
|
3434
|
-
'
|
3435
|
-
'
|
3436
|
-
'require-
|
3437
|
-
'require-
|
3438
|
-
'require-
|
3439
|
-
'
|
3440
|
-
'
|
3441
|
-
'
|
3442
|
-
'
|
3443
|
-
'unique-fragment-name': rule$o,
|
3444
|
-
'unique-operation-name': rule$p,
|
3263
|
+
'description-style': rule$1,
|
3264
|
+
'input-name': rule$2,
|
3265
|
+
'match-document-filename': rule$3,
|
3266
|
+
'naming-convention': rule$4,
|
3267
|
+
'no-anonymous-operations': rule$5,
|
3268
|
+
'no-case-insensitive-enum-values-duplicates': rule$6,
|
3269
|
+
'no-deprecated': rule$7,
|
3270
|
+
'no-duplicate-fields': rule$8,
|
3271
|
+
'no-hashtag-description': rule$9,
|
3272
|
+
'no-root-type': rule$a,
|
3273
|
+
'no-scalar-result-type-on-mutation': rule$b,
|
3274
|
+
'no-typename-prefix': rule$c,
|
3275
|
+
'no-unreachable-types': rule$d,
|
3276
|
+
'no-unused-fields': rule$e,
|
3277
|
+
'require-deprecation-date': rule$f,
|
3278
|
+
'require-deprecation-reason': rule$g,
|
3279
|
+
'require-description': rule$h,
|
3280
|
+
'require-field-of-type-query-in-mutation-result': rule$i,
|
3281
|
+
'require-id-when-available': rule$j,
|
3282
|
+
'selection-set-depth': rule$k,
|
3283
|
+
'strict-id-in-types': rule$l,
|
3284
|
+
'unique-fragment-name': rule$m,
|
3285
|
+
'unique-operation-name': rule$n,
|
3445
3286
|
};
|
3446
3287
|
|
3447
3288
|
const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
|