@graphql-eslint/eslint-plugin 3.4.0 → 3.7.0-alpha-a9b0ecf.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/docs/rules/require-description.md +15 -0
- package/docs/rules/require-id-when-available.md +2 -2
- package/docs/rules/strict-id-in-types.md +1 -1
- package/index.js +100 -65
- package/index.mjs +100 -65
- package/package.json +3 -3
- package/rules/index.d.ts +3 -22
- package/rules/require-description.d.ts +2 -2
- package/rules/require-id-when-available.d.ts +2 -2
- package/rules/strict-id-in-types.d.ts +5 -6
@@ -37,6 +37,17 @@ type someTypeName {
|
|
37
37
|
}
|
38
38
|
```
|
39
39
|
|
40
|
+
### Correct
|
41
|
+
|
42
|
+
```graphql
|
43
|
+
# eslint @graphql-eslint/require-description: ['error', { OperationDefinition: true }]
|
44
|
+
|
45
|
+
# Enforce description on operations
|
46
|
+
query {
|
47
|
+
foo
|
48
|
+
}
|
49
|
+
```
|
50
|
+
|
40
51
|
## Config Schema
|
41
52
|
|
42
53
|
The schema defines the following properties:
|
@@ -84,6 +95,10 @@ Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October
|
|
84
95
|
|
85
96
|
Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#ObjectTypeDefinition).
|
86
97
|
|
98
|
+
### `OperationDefinition` (boolean)
|
99
|
+
|
100
|
+
Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#OperationDefinition).
|
101
|
+
|
87
102
|
### `ScalarTypeDefinition` (boolean)
|
88
103
|
|
89
104
|
Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#ScalarTypeDefinition).
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
- Category: `Schema`
|
6
6
|
- Rule name: `@graphql-eslint/strict-id-in-types`
|
7
|
-
- Requires GraphQL Schema: `
|
7
|
+
- Requires GraphQL Schema: `true` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
|
8
8
|
- Requires GraphQL Operations: `false` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)
|
9
9
|
|
10
10
|
Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.
|
package/index.js
CHANGED
@@ -2625,20 +2625,21 @@ const rule$g = {
|
|
2625
2625
|
},
|
2626
2626
|
};
|
2627
2627
|
|
2628
|
-
const
|
2628
|
+
const RULE_ID$2 = 'require-description';
|
2629
2629
|
const ALLOWED_KINDS$1 = [
|
2630
2630
|
...TYPES_KINDS,
|
2631
2631
|
graphql.Kind.FIELD_DEFINITION,
|
2632
2632
|
graphql.Kind.INPUT_VALUE_DEFINITION,
|
2633
2633
|
graphql.Kind.ENUM_VALUE_DEFINITION,
|
2634
2634
|
graphql.Kind.DIRECTIVE_DEFINITION,
|
2635
|
+
graphql.Kind.OPERATION_DEFINITION,
|
2635
2636
|
];
|
2636
2637
|
const rule$h = {
|
2637
2638
|
meta: {
|
2638
2639
|
docs: {
|
2639
2640
|
category: 'Schema',
|
2640
2641
|
description: 'Enforce descriptions in your type definitions.',
|
2641
|
-
url:
|
2642
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
|
2642
2643
|
examples: [
|
2643
2644
|
{
|
2644
2645
|
title: 'Incorrect',
|
@@ -2662,6 +2663,16 @@ const rule$h = {
|
|
2662
2663
|
"""
|
2663
2664
|
name: String
|
2664
2665
|
}
|
2666
|
+
`,
|
2667
|
+
},
|
2668
|
+
{
|
2669
|
+
title: 'Correct',
|
2670
|
+
usage: [{ OperationDefinition: true }],
|
2671
|
+
code: /* GraphQL */ `
|
2672
|
+
# Enforce description on operations
|
2673
|
+
query {
|
2674
|
+
foo
|
2675
|
+
}
|
2665
2676
|
`,
|
2666
2677
|
},
|
2667
2678
|
],
|
@@ -2675,7 +2686,7 @@ const rule$h = {
|
|
2675
2686
|
},
|
2676
2687
|
type: 'suggestion',
|
2677
2688
|
messages: {
|
2678
|
-
[
|
2689
|
+
[RULE_ID$2]: 'Description is required for nodes of type "{{ nodeType }}"',
|
2679
2690
|
},
|
2680
2691
|
schema: {
|
2681
2692
|
type: 'array',
|
@@ -2702,7 +2713,7 @@ const rule$h = {
|
|
2702
2713
|
},
|
2703
2714
|
},
|
2704
2715
|
create(context) {
|
2705
|
-
const { types, ...restOptions } = context.options[0];
|
2716
|
+
const { types, ...restOptions } = context.options[0] || {};
|
2706
2717
|
const kinds = new Set(types ? TYPES_KINDS : []);
|
2707
2718
|
for (const [kind, isEnabled] of Object.entries(restOptions)) {
|
2708
2719
|
if (isEnabled) {
|
@@ -2716,11 +2727,26 @@ const rule$h = {
|
|
2716
2727
|
return {
|
2717
2728
|
[selector](node) {
|
2718
2729
|
var _a;
|
2719
|
-
|
2720
|
-
|
2730
|
+
let description = '';
|
2731
|
+
const isOperation = node.kind === graphql.Kind.OPERATION_DEFINITION;
|
2732
|
+
if (isOperation) {
|
2733
|
+
const rawNode = node.rawNode();
|
2734
|
+
const { prev, line } = rawNode.loc.startToken;
|
2735
|
+
if (prev.kind === graphql.TokenKind.COMMENT) {
|
2736
|
+
const value = prev.value.trim();
|
2737
|
+
const linesBefore = line - prev.line;
|
2738
|
+
if (!value.startsWith('eslint') && linesBefore === 1) {
|
2739
|
+
description = value;
|
2740
|
+
}
|
2741
|
+
}
|
2742
|
+
}
|
2743
|
+
else {
|
2744
|
+
description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value.trim()) || '';
|
2745
|
+
}
|
2746
|
+
if (description.length === 0) {
|
2721
2747
|
context.report({
|
2722
|
-
loc: getLocation(node.name.loc, node.name.value),
|
2723
|
-
messageId:
|
2748
|
+
loc: isOperation ? getLocation(node.loc, node.operation) : getLocation(node.name.loc, node.name.value),
|
2749
|
+
messageId: RULE_ID$2,
|
2724
2750
|
data: {
|
2725
2751
|
nodeType: node.kind,
|
2726
2752
|
},
|
@@ -2896,7 +2922,8 @@ const convertNode = (typeInfo) => (node, key, parent) => {
|
|
2896
2922
|
}
|
2897
2923
|
};
|
2898
2924
|
|
2899
|
-
const
|
2925
|
+
const RULE_ID$3 = 'require-id-when-available';
|
2926
|
+
const MESSAGE_ID = 'REQUIRE_ID_WHEN_AVAILABLE';
|
2900
2927
|
const DEFAULT_ID_FIELD_NAME = 'id';
|
2901
2928
|
const rule$j = {
|
2902
2929
|
meta: {
|
@@ -2904,7 +2931,7 @@ const rule$j = {
|
|
2904
2931
|
docs: {
|
2905
2932
|
category: 'Operations',
|
2906
2933
|
description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
|
2907
|
-
url:
|
2934
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$3}.md`,
|
2908
2935
|
requiresSchema: true,
|
2909
2936
|
requiresSiblings: true,
|
2910
2937
|
examples: [
|
@@ -2918,7 +2945,7 @@ const rule$j = {
|
|
2918
2945
|
}
|
2919
2946
|
|
2920
2947
|
# Query
|
2921
|
-
query
|
2948
|
+
query {
|
2922
2949
|
user {
|
2923
2950
|
name
|
2924
2951
|
}
|
@@ -2935,7 +2962,7 @@ const rule$j = {
|
|
2935
2962
|
}
|
2936
2963
|
|
2937
2964
|
# Query
|
2938
|
-
query
|
2965
|
+
query {
|
2939
2966
|
user {
|
2940
2967
|
id
|
2941
2968
|
name
|
@@ -2947,7 +2974,7 @@ const rule$j = {
|
|
2947
2974
|
recommended: true,
|
2948
2975
|
},
|
2949
2976
|
messages: {
|
2950
|
-
[
|
2977
|
+
[MESSAGE_ID]: [
|
2951
2978
|
`Field {{ fieldName }} must be selected when it's available on a type. Please make sure to include it in your selection set!`,
|
2952
2979
|
`If you are using fragments, make sure that all used fragments {{ checkedFragments }}specifies the field {{ fieldName }}.`,
|
2953
2980
|
].join('\n'),
|
@@ -2978,14 +3005,16 @@ const rule$j = {
|
|
2978
3005
|
},
|
2979
3006
|
},
|
2980
3007
|
create(context) {
|
2981
|
-
requireGraphQLSchemaFromContext(
|
2982
|
-
const siblings = requireSiblingsOperations(
|
3008
|
+
requireGraphQLSchemaFromContext(RULE_ID$3, context);
|
3009
|
+
const siblings = requireSiblingsOperations(RULE_ID$3, context);
|
2983
3010
|
const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
|
2984
|
-
const idNames =
|
3011
|
+
const idNames = utils.asArray(fieldName);
|
2985
3012
|
const isFound = (s) => s.kind === graphql.Kind.FIELD && idNames.includes(s.name.value);
|
3013
|
+
// Skip check selections in FragmentDefinition
|
3014
|
+
const selector = 'OperationDefinition SelectionSet[parent.kind!=OperationDefinition]';
|
2986
3015
|
return {
|
2987
|
-
|
2988
|
-
var _a
|
3016
|
+
[selector](node) {
|
3017
|
+
var _a;
|
2989
3018
|
const typeInfo = node.typeInfo();
|
2990
3019
|
if (!typeInfo.gqlType) {
|
2991
3020
|
return;
|
@@ -3002,53 +3031,50 @@ const rule$j = {
|
|
3002
3031
|
return;
|
3003
3032
|
}
|
3004
3033
|
const checkedFragmentSpreads = new Set();
|
3005
|
-
let found = false;
|
3006
3034
|
for (const selection of node.selections) {
|
3007
3035
|
if (isFound(selection)) {
|
3008
|
-
|
3036
|
+
return;
|
3009
3037
|
}
|
3010
|
-
|
3011
|
-
|
3038
|
+
if (selection.kind === graphql.Kind.INLINE_FRAGMENT && selection.selectionSet.selections.some(isFound)) {
|
3039
|
+
return;
|
3012
3040
|
}
|
3013
|
-
|
3041
|
+
if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
|
3014
3042
|
const [foundSpread] = siblings.getFragment(selection.name.value);
|
3015
3043
|
if (foundSpread) {
|
3016
3044
|
checkedFragmentSpreads.add(foundSpread.document.name.value);
|
3017
|
-
|
3045
|
+
if (foundSpread.document.selectionSet.selections.some(isFound)) {
|
3046
|
+
return;
|
3047
|
+
}
|
3018
3048
|
}
|
3019
3049
|
}
|
3020
|
-
if (found) {
|
3021
|
-
break;
|
3022
|
-
}
|
3023
3050
|
}
|
3024
3051
|
const { parent } = node;
|
3025
|
-
const hasIdFieldInInterfaceSelectionSet = parent &&
|
3026
|
-
parent.kind === graphql.Kind.
|
3027
|
-
parent.parent
|
3028
|
-
|
3029
|
-
|
3030
|
-
if (!found && !hasIdFieldInInterfaceSelectionSet) {
|
3031
|
-
context.report({
|
3032
|
-
loc: getLocation(node.loc),
|
3033
|
-
messageId: REQUIRE_ID_WHEN_AVAILABLE,
|
3034
|
-
data: {
|
3035
|
-
checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${[...checkedFragmentSpreads].join(', ')}) `,
|
3036
|
-
fieldName: idNames.map(name => `"${name}"`).join(' or '),
|
3037
|
-
},
|
3038
|
-
});
|
3052
|
+
const hasIdFieldInInterfaceSelectionSet = (parent === null || parent === void 0 ? void 0 : parent.kind) === graphql.Kind.INLINE_FRAGMENT &&
|
3053
|
+
((_a = parent.parent) === null || _a === void 0 ? void 0 : _a.kind) === graphql.Kind.SELECTION_SET &&
|
3054
|
+
parent.parent.selections.some(isFound);
|
3055
|
+
if (hasIdFieldInInterfaceSelectionSet) {
|
3056
|
+
return;
|
3039
3057
|
}
|
3058
|
+
context.report({
|
3059
|
+
loc: getLocation(node.loc),
|
3060
|
+
messageId: MESSAGE_ID,
|
3061
|
+
data: {
|
3062
|
+
checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${[...checkedFragmentSpreads].join(', ')}) `,
|
3063
|
+
fieldName: idNames.map(name => `"${name}"`).join(' or '),
|
3064
|
+
},
|
3065
|
+
});
|
3040
3066
|
},
|
3041
3067
|
};
|
3042
3068
|
},
|
3043
3069
|
};
|
3044
3070
|
|
3045
|
-
const RULE_ID$
|
3071
|
+
const RULE_ID$4 = 'selection-set-depth';
|
3046
3072
|
const rule$k = {
|
3047
3073
|
meta: {
|
3048
3074
|
docs: {
|
3049
3075
|
category: 'Operations',
|
3050
3076
|
description: `Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://github.com/stems/graphql-depth-limit).`,
|
3051
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$
|
3077
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$4}.md`,
|
3052
3078
|
requiresSiblings: true,
|
3053
3079
|
examples: [
|
3054
3080
|
{
|
@@ -3122,10 +3148,10 @@ const rule$k = {
|
|
3122
3148
|
create(context) {
|
3123
3149
|
let siblings = null;
|
3124
3150
|
try {
|
3125
|
-
siblings = requireSiblingsOperations(RULE_ID$
|
3151
|
+
siblings = requireSiblingsOperations(RULE_ID$4, context);
|
3126
3152
|
}
|
3127
3153
|
catch (e) {
|
3128
|
-
logger.warn(`Rule "${RULE_ID$
|
3154
|
+
logger.warn(`Rule "${RULE_ID$4}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
|
3129
3155
|
}
|
3130
3156
|
const { maxDepth } = context.options[0];
|
3131
3157
|
const ignore = context.options[0].ignore || [];
|
@@ -3150,35 +3176,33 @@ const rule$k = {
|
|
3150
3176
|
});
|
3151
3177
|
}
|
3152
3178
|
catch (e) {
|
3153
|
-
logger.warn(`Rule "${RULE_ID$
|
3179
|
+
logger.warn(`Rule "${RULE_ID$4}" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`, e);
|
3154
3180
|
}
|
3155
3181
|
},
|
3156
3182
|
};
|
3157
3183
|
},
|
3158
3184
|
};
|
3159
3185
|
|
3160
|
-
const
|
3161
|
-
const rawNode = node.rawNode();
|
3162
|
-
if (exceptions.types && exceptions.types.includes(rawNode.name.value)) {
|
3163
|
-
return true;
|
3164
|
-
}
|
3165
|
-
if (exceptions.suffixes && exceptions.suffixes.some(suffix => rawNode.name.value.endsWith(suffix))) {
|
3166
|
-
return true;
|
3167
|
-
}
|
3168
|
-
return false;
|
3169
|
-
};
|
3186
|
+
const RULE_ID$5 = 'strict-id-in-types';
|
3170
3187
|
const rule$l = {
|
3171
3188
|
meta: {
|
3172
3189
|
type: 'suggestion',
|
3173
3190
|
docs: {
|
3174
|
-
description:
|
3191
|
+
description: `Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.`,
|
3175
3192
|
category: 'Schema',
|
3176
3193
|
recommended: true,
|
3177
|
-
url:
|
3194
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$5}.md`,
|
3195
|
+
requiresSchema: true,
|
3178
3196
|
examples: [
|
3179
3197
|
{
|
3180
3198
|
title: 'Incorrect',
|
3181
|
-
usage: [
|
3199
|
+
usage: [
|
3200
|
+
{
|
3201
|
+
acceptedIdNames: ['id', '_id'],
|
3202
|
+
acceptedIdTypes: ['ID'],
|
3203
|
+
exceptions: { suffixes: ['Payload'] },
|
3204
|
+
},
|
3205
|
+
],
|
3182
3206
|
code: /* GraphQL */ `
|
3183
3207
|
# Incorrect field name
|
3184
3208
|
type InvalidFieldName {
|
@@ -3280,6 +3304,9 @@ const rule$l = {
|
|
3280
3304
|
},
|
3281
3305
|
},
|
3282
3306
|
},
|
3307
|
+
messages: {
|
3308
|
+
[RULE_ID$5]: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }}; Accepted type(s): {{ acceptedTypesString }}.`,
|
3309
|
+
},
|
3283
3310
|
},
|
3284
3311
|
create(context) {
|
3285
3312
|
const options = {
|
@@ -3288,9 +3315,18 @@ const rule$l = {
|
|
3288
3315
|
exceptions: {},
|
3289
3316
|
...context.options[0],
|
3290
3317
|
};
|
3318
|
+
const schema = requireGraphQLSchemaFromContext(RULE_ID$5, context);
|
3319
|
+
const rootTypeNames = [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()]
|
3320
|
+
.filter(Boolean)
|
3321
|
+
.map(type => type.name);
|
3322
|
+
const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
|
3291
3323
|
return {
|
3292
|
-
|
3293
|
-
|
3324
|
+
[selector](node) {
|
3325
|
+
var _a, _b;
|
3326
|
+
const typeName = node.name.value;
|
3327
|
+
const shouldIgnoreNode = ((_a = options.exceptions.types) === null || _a === void 0 ? void 0 : _a.includes(typeName)) ||
|
3328
|
+
((_b = options.exceptions.suffixes) === null || _b === void 0 ? void 0 : _b.some(suffix => typeName.endsWith(suffix)));
|
3329
|
+
if (shouldIgnoreNode) {
|
3294
3330
|
return;
|
3295
3331
|
}
|
3296
3332
|
const validIds = node.fields.filter(field => {
|
@@ -3303,18 +3339,17 @@ const rule$l = {
|
|
3303
3339
|
}
|
3304
3340
|
return isValidIdName && isValidIdType;
|
3305
3341
|
});
|
3306
|
-
const typeName = node.name.value;
|
3307
3342
|
// Usually, there should be only one unique identifier field per type.
|
3308
3343
|
// Some clients allow multiple fields to be used. If more people need this,
|
3309
3344
|
// we can extend this rule later.
|
3310
3345
|
if (validIds.length !== 1) {
|
3311
3346
|
context.report({
|
3312
3347
|
loc: getLocation(node.name.loc, typeName),
|
3313
|
-
|
3348
|
+
messageId: RULE_ID$5,
|
3314
3349
|
data: {
|
3315
3350
|
typeName,
|
3316
|
-
acceptedNamesString: options.acceptedIdNames.join(','),
|
3317
|
-
acceptedTypesString: options.acceptedIdTypes.join(','),
|
3351
|
+
acceptedNamesString: options.acceptedIdNames.join(', '),
|
3352
|
+
acceptedTypesString: options.acceptedIdTypes.join(', '),
|
3318
3353
|
},
|
3319
3354
|
});
|
3320
3355
|
}
|
package/index.mjs
CHANGED
@@ -2619,20 +2619,21 @@ const rule$g = {
|
|
2619
2619
|
},
|
2620
2620
|
};
|
2621
2621
|
|
2622
|
-
const
|
2622
|
+
const RULE_ID$2 = 'require-description';
|
2623
2623
|
const ALLOWED_KINDS$1 = [
|
2624
2624
|
...TYPES_KINDS,
|
2625
2625
|
Kind.FIELD_DEFINITION,
|
2626
2626
|
Kind.INPUT_VALUE_DEFINITION,
|
2627
2627
|
Kind.ENUM_VALUE_DEFINITION,
|
2628
2628
|
Kind.DIRECTIVE_DEFINITION,
|
2629
|
+
Kind.OPERATION_DEFINITION,
|
2629
2630
|
];
|
2630
2631
|
const rule$h = {
|
2631
2632
|
meta: {
|
2632
2633
|
docs: {
|
2633
2634
|
category: 'Schema',
|
2634
2635
|
description: 'Enforce descriptions in your type definitions.',
|
2635
|
-
url:
|
2636
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
|
2636
2637
|
examples: [
|
2637
2638
|
{
|
2638
2639
|
title: 'Incorrect',
|
@@ -2656,6 +2657,16 @@ const rule$h = {
|
|
2656
2657
|
"""
|
2657
2658
|
name: String
|
2658
2659
|
}
|
2660
|
+
`,
|
2661
|
+
},
|
2662
|
+
{
|
2663
|
+
title: 'Correct',
|
2664
|
+
usage: [{ OperationDefinition: true }],
|
2665
|
+
code: /* GraphQL */ `
|
2666
|
+
# Enforce description on operations
|
2667
|
+
query {
|
2668
|
+
foo
|
2669
|
+
}
|
2659
2670
|
`,
|
2660
2671
|
},
|
2661
2672
|
],
|
@@ -2669,7 +2680,7 @@ const rule$h = {
|
|
2669
2680
|
},
|
2670
2681
|
type: 'suggestion',
|
2671
2682
|
messages: {
|
2672
|
-
[
|
2683
|
+
[RULE_ID$2]: 'Description is required for nodes of type "{{ nodeType }}"',
|
2673
2684
|
},
|
2674
2685
|
schema: {
|
2675
2686
|
type: 'array',
|
@@ -2696,7 +2707,7 @@ const rule$h = {
|
|
2696
2707
|
},
|
2697
2708
|
},
|
2698
2709
|
create(context) {
|
2699
|
-
const { types, ...restOptions } = context.options[0];
|
2710
|
+
const { types, ...restOptions } = context.options[0] || {};
|
2700
2711
|
const kinds = new Set(types ? TYPES_KINDS : []);
|
2701
2712
|
for (const [kind, isEnabled] of Object.entries(restOptions)) {
|
2702
2713
|
if (isEnabled) {
|
@@ -2710,11 +2721,26 @@ const rule$h = {
|
|
2710
2721
|
return {
|
2711
2722
|
[selector](node) {
|
2712
2723
|
var _a;
|
2713
|
-
|
2714
|
-
|
2724
|
+
let description = '';
|
2725
|
+
const isOperation = node.kind === Kind.OPERATION_DEFINITION;
|
2726
|
+
if (isOperation) {
|
2727
|
+
const rawNode = node.rawNode();
|
2728
|
+
const { prev, line } = rawNode.loc.startToken;
|
2729
|
+
if (prev.kind === TokenKind.COMMENT) {
|
2730
|
+
const value = prev.value.trim();
|
2731
|
+
const linesBefore = line - prev.line;
|
2732
|
+
if (!value.startsWith('eslint') && linesBefore === 1) {
|
2733
|
+
description = value;
|
2734
|
+
}
|
2735
|
+
}
|
2736
|
+
}
|
2737
|
+
else {
|
2738
|
+
description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value.trim()) || '';
|
2739
|
+
}
|
2740
|
+
if (description.length === 0) {
|
2715
2741
|
context.report({
|
2716
|
-
loc: getLocation(node.name.loc, node.name.value),
|
2717
|
-
messageId:
|
2742
|
+
loc: isOperation ? getLocation(node.loc, node.operation) : getLocation(node.name.loc, node.name.value),
|
2743
|
+
messageId: RULE_ID$2,
|
2718
2744
|
data: {
|
2719
2745
|
nodeType: node.kind,
|
2720
2746
|
},
|
@@ -2890,7 +2916,8 @@ const convertNode = (typeInfo) => (node, key, parent) => {
|
|
2890
2916
|
}
|
2891
2917
|
};
|
2892
2918
|
|
2893
|
-
const
|
2919
|
+
const RULE_ID$3 = 'require-id-when-available';
|
2920
|
+
const MESSAGE_ID = 'REQUIRE_ID_WHEN_AVAILABLE';
|
2894
2921
|
const DEFAULT_ID_FIELD_NAME = 'id';
|
2895
2922
|
const rule$j = {
|
2896
2923
|
meta: {
|
@@ -2898,7 +2925,7 @@ const rule$j = {
|
|
2898
2925
|
docs: {
|
2899
2926
|
category: 'Operations',
|
2900
2927
|
description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
|
2901
|
-
url:
|
2928
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$3}.md`,
|
2902
2929
|
requiresSchema: true,
|
2903
2930
|
requiresSiblings: true,
|
2904
2931
|
examples: [
|
@@ -2912,7 +2939,7 @@ const rule$j = {
|
|
2912
2939
|
}
|
2913
2940
|
|
2914
2941
|
# Query
|
2915
|
-
query
|
2942
|
+
query {
|
2916
2943
|
user {
|
2917
2944
|
name
|
2918
2945
|
}
|
@@ -2929,7 +2956,7 @@ const rule$j = {
|
|
2929
2956
|
}
|
2930
2957
|
|
2931
2958
|
# Query
|
2932
|
-
query
|
2959
|
+
query {
|
2933
2960
|
user {
|
2934
2961
|
id
|
2935
2962
|
name
|
@@ -2941,7 +2968,7 @@ const rule$j = {
|
|
2941
2968
|
recommended: true,
|
2942
2969
|
},
|
2943
2970
|
messages: {
|
2944
|
-
[
|
2971
|
+
[MESSAGE_ID]: [
|
2945
2972
|
`Field {{ fieldName }} must be selected when it's available on a type. Please make sure to include it in your selection set!`,
|
2946
2973
|
`If you are using fragments, make sure that all used fragments {{ checkedFragments }}specifies the field {{ fieldName }}.`,
|
2947
2974
|
].join('\n'),
|
@@ -2972,14 +2999,16 @@ const rule$j = {
|
|
2972
2999
|
},
|
2973
3000
|
},
|
2974
3001
|
create(context) {
|
2975
|
-
requireGraphQLSchemaFromContext(
|
2976
|
-
const siblings = requireSiblingsOperations(
|
3002
|
+
requireGraphQLSchemaFromContext(RULE_ID$3, context);
|
3003
|
+
const siblings = requireSiblingsOperations(RULE_ID$3, context);
|
2977
3004
|
const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
|
2978
|
-
const idNames =
|
3005
|
+
const idNames = asArray(fieldName);
|
2979
3006
|
const isFound = (s) => s.kind === Kind.FIELD && idNames.includes(s.name.value);
|
3007
|
+
// Skip check selections in FragmentDefinition
|
3008
|
+
const selector = 'OperationDefinition SelectionSet[parent.kind!=OperationDefinition]';
|
2980
3009
|
return {
|
2981
|
-
|
2982
|
-
var _a
|
3010
|
+
[selector](node) {
|
3011
|
+
var _a;
|
2983
3012
|
const typeInfo = node.typeInfo();
|
2984
3013
|
if (!typeInfo.gqlType) {
|
2985
3014
|
return;
|
@@ -2996,53 +3025,50 @@ const rule$j = {
|
|
2996
3025
|
return;
|
2997
3026
|
}
|
2998
3027
|
const checkedFragmentSpreads = new Set();
|
2999
|
-
let found = false;
|
3000
3028
|
for (const selection of node.selections) {
|
3001
3029
|
if (isFound(selection)) {
|
3002
|
-
|
3030
|
+
return;
|
3003
3031
|
}
|
3004
|
-
|
3005
|
-
|
3032
|
+
if (selection.kind === Kind.INLINE_FRAGMENT && selection.selectionSet.selections.some(isFound)) {
|
3033
|
+
return;
|
3006
3034
|
}
|
3007
|
-
|
3035
|
+
if (selection.kind === Kind.FRAGMENT_SPREAD) {
|
3008
3036
|
const [foundSpread] = siblings.getFragment(selection.name.value);
|
3009
3037
|
if (foundSpread) {
|
3010
3038
|
checkedFragmentSpreads.add(foundSpread.document.name.value);
|
3011
|
-
|
3039
|
+
if (foundSpread.document.selectionSet.selections.some(isFound)) {
|
3040
|
+
return;
|
3041
|
+
}
|
3012
3042
|
}
|
3013
3043
|
}
|
3014
|
-
if (found) {
|
3015
|
-
break;
|
3016
|
-
}
|
3017
3044
|
}
|
3018
3045
|
const { parent } = node;
|
3019
|
-
const hasIdFieldInInterfaceSelectionSet = parent &&
|
3020
|
-
parent.kind === Kind.
|
3021
|
-
parent.parent
|
3022
|
-
|
3023
|
-
|
3024
|
-
if (!found && !hasIdFieldInInterfaceSelectionSet) {
|
3025
|
-
context.report({
|
3026
|
-
loc: getLocation(node.loc),
|
3027
|
-
messageId: REQUIRE_ID_WHEN_AVAILABLE,
|
3028
|
-
data: {
|
3029
|
-
checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${[...checkedFragmentSpreads].join(', ')}) `,
|
3030
|
-
fieldName: idNames.map(name => `"${name}"`).join(' or '),
|
3031
|
-
},
|
3032
|
-
});
|
3046
|
+
const hasIdFieldInInterfaceSelectionSet = (parent === null || parent === void 0 ? void 0 : parent.kind) === Kind.INLINE_FRAGMENT &&
|
3047
|
+
((_a = parent.parent) === null || _a === void 0 ? void 0 : _a.kind) === Kind.SELECTION_SET &&
|
3048
|
+
parent.parent.selections.some(isFound);
|
3049
|
+
if (hasIdFieldInInterfaceSelectionSet) {
|
3050
|
+
return;
|
3033
3051
|
}
|
3052
|
+
context.report({
|
3053
|
+
loc: getLocation(node.loc),
|
3054
|
+
messageId: MESSAGE_ID,
|
3055
|
+
data: {
|
3056
|
+
checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${[...checkedFragmentSpreads].join(', ')}) `,
|
3057
|
+
fieldName: idNames.map(name => `"${name}"`).join(' or '),
|
3058
|
+
},
|
3059
|
+
});
|
3034
3060
|
},
|
3035
3061
|
};
|
3036
3062
|
},
|
3037
3063
|
};
|
3038
3064
|
|
3039
|
-
const RULE_ID$
|
3065
|
+
const RULE_ID$4 = 'selection-set-depth';
|
3040
3066
|
const rule$k = {
|
3041
3067
|
meta: {
|
3042
3068
|
docs: {
|
3043
3069
|
category: 'Operations',
|
3044
3070
|
description: `Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://github.com/stems/graphql-depth-limit).`,
|
3045
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$
|
3071
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$4}.md`,
|
3046
3072
|
requiresSiblings: true,
|
3047
3073
|
examples: [
|
3048
3074
|
{
|
@@ -3116,10 +3142,10 @@ const rule$k = {
|
|
3116
3142
|
create(context) {
|
3117
3143
|
let siblings = null;
|
3118
3144
|
try {
|
3119
|
-
siblings = requireSiblingsOperations(RULE_ID$
|
3145
|
+
siblings = requireSiblingsOperations(RULE_ID$4, context);
|
3120
3146
|
}
|
3121
3147
|
catch (e) {
|
3122
|
-
logger.warn(`Rule "${RULE_ID$
|
3148
|
+
logger.warn(`Rule "${RULE_ID$4}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
|
3123
3149
|
}
|
3124
3150
|
const { maxDepth } = context.options[0];
|
3125
3151
|
const ignore = context.options[0].ignore || [];
|
@@ -3144,35 +3170,33 @@ const rule$k = {
|
|
3144
3170
|
});
|
3145
3171
|
}
|
3146
3172
|
catch (e) {
|
3147
|
-
logger.warn(`Rule "${RULE_ID$
|
3173
|
+
logger.warn(`Rule "${RULE_ID$4}" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`, e);
|
3148
3174
|
}
|
3149
3175
|
},
|
3150
3176
|
};
|
3151
3177
|
},
|
3152
3178
|
};
|
3153
3179
|
|
3154
|
-
const
|
3155
|
-
const rawNode = node.rawNode();
|
3156
|
-
if (exceptions.types && exceptions.types.includes(rawNode.name.value)) {
|
3157
|
-
return true;
|
3158
|
-
}
|
3159
|
-
if (exceptions.suffixes && exceptions.suffixes.some(suffix => rawNode.name.value.endsWith(suffix))) {
|
3160
|
-
return true;
|
3161
|
-
}
|
3162
|
-
return false;
|
3163
|
-
};
|
3180
|
+
const RULE_ID$5 = 'strict-id-in-types';
|
3164
3181
|
const rule$l = {
|
3165
3182
|
meta: {
|
3166
3183
|
type: 'suggestion',
|
3167
3184
|
docs: {
|
3168
|
-
description:
|
3185
|
+
description: `Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.`,
|
3169
3186
|
category: 'Schema',
|
3170
3187
|
recommended: true,
|
3171
|
-
url:
|
3188
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$5}.md`,
|
3189
|
+
requiresSchema: true,
|
3172
3190
|
examples: [
|
3173
3191
|
{
|
3174
3192
|
title: 'Incorrect',
|
3175
|
-
usage: [
|
3193
|
+
usage: [
|
3194
|
+
{
|
3195
|
+
acceptedIdNames: ['id', '_id'],
|
3196
|
+
acceptedIdTypes: ['ID'],
|
3197
|
+
exceptions: { suffixes: ['Payload'] },
|
3198
|
+
},
|
3199
|
+
],
|
3176
3200
|
code: /* GraphQL */ `
|
3177
3201
|
# Incorrect field name
|
3178
3202
|
type InvalidFieldName {
|
@@ -3274,6 +3298,9 @@ const rule$l = {
|
|
3274
3298
|
},
|
3275
3299
|
},
|
3276
3300
|
},
|
3301
|
+
messages: {
|
3302
|
+
[RULE_ID$5]: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }}; Accepted type(s): {{ acceptedTypesString }}.`,
|
3303
|
+
},
|
3277
3304
|
},
|
3278
3305
|
create(context) {
|
3279
3306
|
const options = {
|
@@ -3282,9 +3309,18 @@ const rule$l = {
|
|
3282
3309
|
exceptions: {},
|
3283
3310
|
...context.options[0],
|
3284
3311
|
};
|
3312
|
+
const schema = requireGraphQLSchemaFromContext(RULE_ID$5, context);
|
3313
|
+
const rootTypeNames = [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()]
|
3314
|
+
.filter(Boolean)
|
3315
|
+
.map(type => type.name);
|
3316
|
+
const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
|
3285
3317
|
return {
|
3286
|
-
|
3287
|
-
|
3318
|
+
[selector](node) {
|
3319
|
+
var _a, _b;
|
3320
|
+
const typeName = node.name.value;
|
3321
|
+
const shouldIgnoreNode = ((_a = options.exceptions.types) === null || _a === void 0 ? void 0 : _a.includes(typeName)) ||
|
3322
|
+
((_b = options.exceptions.suffixes) === null || _b === void 0 ? void 0 : _b.some(suffix => typeName.endsWith(suffix)));
|
3323
|
+
if (shouldIgnoreNode) {
|
3288
3324
|
return;
|
3289
3325
|
}
|
3290
3326
|
const validIds = node.fields.filter(field => {
|
@@ -3297,18 +3333,17 @@ const rule$l = {
|
|
3297
3333
|
}
|
3298
3334
|
return isValidIdName && isValidIdType;
|
3299
3335
|
});
|
3300
|
-
const typeName = node.name.value;
|
3301
3336
|
// Usually, there should be only one unique identifier field per type.
|
3302
3337
|
// Some clients allow multiple fields to be used. If more people need this,
|
3303
3338
|
// we can extend this rule later.
|
3304
3339
|
if (validIds.length !== 1) {
|
3305
3340
|
context.report({
|
3306
3341
|
loc: getLocation(node.name.loc, typeName),
|
3307
|
-
|
3342
|
+
messageId: RULE_ID$5,
|
3308
3343
|
data: {
|
3309
3344
|
typeName,
|
3310
|
-
acceptedNamesString: options.acceptedIdNames.join(','),
|
3311
|
-
acceptedTypesString: options.acceptedIdTypes.join(','),
|
3345
|
+
acceptedNamesString: options.acceptedIdNames.join(', '),
|
3346
|
+
acceptedTypesString: options.acceptedIdTypes.join(', '),
|
3312
3347
|
},
|
3313
3348
|
});
|
3314
3349
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@graphql-eslint/eslint-plugin",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.7.0-alpha-a9b0ecf.0",
|
4
4
|
"description": "GraphQL plugin for ESLint",
|
5
5
|
"sideEffects": false,
|
6
6
|
"peerDependencies": {
|
@@ -9,8 +9,8 @@
|
|
9
9
|
"dependencies": {
|
10
10
|
"@babel/code-frame": "7.16.7",
|
11
11
|
"@graphql-tools/code-file-loader": "7.2.3",
|
12
|
-
"@graphql-tools/graphql-tag-pluck": "7.1.
|
13
|
-
"@graphql-tools/utils": "8.
|
12
|
+
"@graphql-tools/graphql-tag-pluck": "7.1.5",
|
13
|
+
"@graphql-tools/utils": "8.6.1",
|
14
14
|
"chalk": "4.1.2",
|
15
15
|
"graphql-config": "4.1.0",
|
16
16
|
"graphql-depth-limit": "1.1.0",
|
package/rules/index.d.ts
CHANGED
@@ -33,33 +33,14 @@ export declare const rules: {
|
|
33
33
|
argumentName?: string;
|
34
34
|
}], false>;
|
35
35
|
'require-deprecation-reason': import("..").GraphQLESLintRule<any[], false>;
|
36
|
-
'require-description': import("..").GraphQLESLintRule<[
|
37
|
-
types?: boolean;
|
38
|
-
} & {
|
39
|
-
ScalarTypeDefinition?: boolean;
|
40
|
-
ObjectTypeDefinition?: boolean;
|
41
|
-
FieldDefinition?: boolean;
|
42
|
-
InputValueDefinition?: boolean;
|
43
|
-
InterfaceTypeDefinition?: boolean;
|
44
|
-
UnionTypeDefinition?: boolean;
|
45
|
-
EnumTypeDefinition?: boolean;
|
46
|
-
EnumValueDefinition?: boolean;
|
47
|
-
InputObjectTypeDefinition?: boolean;
|
48
|
-
DirectiveDefinition?: boolean;
|
49
|
-
}], false>;
|
36
|
+
'require-description': import("..").GraphQLESLintRule<[import("./require-description").RequireDescriptionRuleConfig], false>;
|
50
37
|
'require-field-of-type-query-in-mutation-result': import("..").GraphQLESLintRule<any[], false>;
|
51
|
-
'require-id-when-available': import("..").GraphQLESLintRule<[
|
52
|
-
fieldName: string;
|
53
|
-
}], true>;
|
38
|
+
'require-id-when-available': import("..").GraphQLESLintRule<[import("./require-id-when-available").RequireIdWhenAvailableRuleConfig], true>;
|
54
39
|
'selection-set-depth': import("..").GraphQLESLintRule<[{
|
55
40
|
maxDepth: number;
|
56
41
|
ignore?: string[];
|
57
42
|
}], false>;
|
58
|
-
'strict-id-in-types': import("..").GraphQLESLintRule<[
|
59
|
-
acceptedIdNames?: string[];
|
60
|
-
acceptedIdTypes?: string[];
|
61
|
-
exceptions?: import("./strict-id-in-types").ExceptionRule;
|
62
|
-
}], false>;
|
43
|
+
'strict-id-in-types': import("..").GraphQLESLintRule<[import("./strict-id-in-types").StrictIdInTypesRuleConfig], false>;
|
63
44
|
'unique-fragment-name': import("..").GraphQLESLintRule<any[], false>;
|
64
45
|
'unique-operation-name': import("..").GraphQLESLintRule<any[], false>;
|
65
46
|
};
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import { Kind } from 'graphql';
|
2
2
|
import { GraphQLESLintRule } from '../types';
|
3
|
-
declare const ALLOWED_KINDS: readonly [Kind.OBJECT_TYPE_DEFINITION, Kind.INTERFACE_TYPE_DEFINITION, Kind.ENUM_TYPE_DEFINITION, Kind.SCALAR_TYPE_DEFINITION, Kind.INPUT_OBJECT_TYPE_DEFINITION, Kind.UNION_TYPE_DEFINITION, Kind.FIELD_DEFINITION, Kind.INPUT_VALUE_DEFINITION, Kind.ENUM_VALUE_DEFINITION, Kind.DIRECTIVE_DEFINITION];
|
3
|
+
declare const ALLOWED_KINDS: readonly [Kind.OBJECT_TYPE_DEFINITION, Kind.INTERFACE_TYPE_DEFINITION, Kind.ENUM_TYPE_DEFINITION, Kind.SCALAR_TYPE_DEFINITION, Kind.INPUT_OBJECT_TYPE_DEFINITION, Kind.UNION_TYPE_DEFINITION, Kind.FIELD_DEFINITION, Kind.INPUT_VALUE_DEFINITION, Kind.ENUM_VALUE_DEFINITION, Kind.DIRECTIVE_DEFINITION, Kind.OPERATION_DEFINITION];
|
4
4
|
declare type AllowedKind = typeof ALLOWED_KINDS[number];
|
5
|
-
declare type RequireDescriptionRuleConfig = {
|
5
|
+
export declare type RequireDescriptionRuleConfig = {
|
6
6
|
types?: boolean;
|
7
7
|
} & {
|
8
8
|
[key in AllowedKind]?: boolean;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { GraphQLESLintRule } from '../types';
|
2
|
-
declare type RequireIdWhenAvailableRuleConfig = {
|
3
|
-
fieldName: string;
|
2
|
+
export declare type RequireIdWhenAvailableRuleConfig = {
|
3
|
+
fieldName: string | string[];
|
4
4
|
};
|
5
5
|
declare const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true>;
|
6
6
|
export default rule;
|
@@ -1,12 +1,11 @@
|
|
1
1
|
import { GraphQLESLintRule } from '../types';
|
2
|
-
export
|
3
|
-
types?: string[];
|
4
|
-
suffixes?: string[];
|
5
|
-
}
|
6
|
-
declare type StrictIdInTypesRuleConfig = {
|
2
|
+
export declare type StrictIdInTypesRuleConfig = {
|
7
3
|
acceptedIdNames?: string[];
|
8
4
|
acceptedIdTypes?: string[];
|
9
|
-
exceptions?:
|
5
|
+
exceptions?: {
|
6
|
+
types?: string[];
|
7
|
+
suffixes?: string[];
|
8
|
+
};
|
10
9
|
};
|
11
10
|
declare const rule: GraphQLESLintRule<[StrictIdInTypesRuleConfig]>;
|
12
11
|
export default rule;
|