@graphql-eslint/eslint-plugin 2.3.2-alpha-759bc9e.0 → 2.3.2-alpha-5497183.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/avoid-duplicate-fields.md +9 -7
- package/index.js +53 -80
- package/index.mjs +53 -80
- package/package.json +1 -1
- package/rules/avoid-duplicate-fields.d.ts +1 -1
- package/rules/index.d.ts +1 -1
@@ -14,11 +14,11 @@ Checks for duplicate fields in selection set, variables in operation definition,
|
|
14
14
|
```graphql
|
15
15
|
# eslint @graphql-eslint/avoid-duplicate-fields: 'error'
|
16
16
|
|
17
|
-
query
|
17
|
+
query {
|
18
18
|
user {
|
19
|
-
name
|
19
|
+
name
|
20
20
|
email
|
21
|
-
name #
|
21
|
+
name # duplicate field
|
22
22
|
}
|
23
23
|
}
|
24
24
|
```
|
@@ -28,7 +28,7 @@ query getUserDetails {
|
|
28
28
|
```graphql
|
29
29
|
# eslint @graphql-eslint/avoid-duplicate-fields: 'error'
|
30
30
|
|
31
|
-
query
|
31
|
+
query {
|
32
32
|
users(
|
33
33
|
first: 100
|
34
34
|
skip: 50
|
@@ -45,9 +45,11 @@ query getUsers {
|
|
45
45
|
```graphql
|
46
46
|
# eslint @graphql-eslint/avoid-duplicate-fields: 'error'
|
47
47
|
|
48
|
-
query
|
49
|
-
|
50
|
-
|
48
|
+
query (
|
49
|
+
$first: Int!
|
50
|
+
$first: Int! # duplicate variable
|
51
|
+
) {
|
52
|
+
users(first: $first, skip: 50) {
|
51
53
|
id
|
52
54
|
}
|
53
55
|
}
|
package/index.js
CHANGED
@@ -744,35 +744,22 @@ const rule = {
|
|
744
744
|
};
|
745
745
|
|
746
746
|
const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
|
747
|
-
const ensureUnique = () => {
|
748
|
-
const set = new Set();
|
749
|
-
return {
|
750
|
-
add: (item, onError) => {
|
751
|
-
if (set.has(item)) {
|
752
|
-
onError();
|
753
|
-
}
|
754
|
-
else {
|
755
|
-
set.add(item);
|
756
|
-
}
|
757
|
-
},
|
758
|
-
};
|
759
|
-
};
|
760
747
|
const rule$1 = {
|
761
748
|
meta: {
|
762
749
|
type: 'suggestion',
|
763
750
|
docs: {
|
764
|
-
description:
|
751
|
+
description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
|
765
752
|
category: 'Stylistic Issues',
|
766
753
|
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
|
767
754
|
examples: [
|
768
755
|
{
|
769
756
|
title: 'Incorrect',
|
770
757
|
code: /* GraphQL */ `
|
771
|
-
query
|
758
|
+
query {
|
772
759
|
user {
|
773
|
-
name
|
760
|
+
name
|
774
761
|
email
|
775
|
-
name #
|
762
|
+
name # duplicate field
|
776
763
|
}
|
777
764
|
}
|
778
765
|
`,
|
@@ -780,7 +767,7 @@ const rule$1 = {
|
|
780
767
|
{
|
781
768
|
title: 'Incorrect',
|
782
769
|
code: /* GraphQL */ `
|
783
|
-
query
|
770
|
+
query {
|
784
771
|
users(
|
785
772
|
first: 100
|
786
773
|
skip: 50
|
@@ -795,9 +782,11 @@ const rule$1 = {
|
|
795
782
|
{
|
796
783
|
title: 'Incorrect',
|
797
784
|
code: /* GraphQL */ `
|
798
|
-
query
|
799
|
-
|
800
|
-
|
785
|
+
query (
|
786
|
+
$first: Int!
|
787
|
+
$first: Int! # duplicate variable
|
788
|
+
) {
|
789
|
+
users(first: $first, skip: 50) {
|
801
790
|
id
|
802
791
|
}
|
803
792
|
}
|
@@ -806,58 +795,47 @@ const rule$1 = {
|
|
806
795
|
],
|
807
796
|
},
|
808
797
|
messages: {
|
809
|
-
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times
|
798
|
+
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
|
810
799
|
},
|
811
800
|
schema: [],
|
812
801
|
},
|
813
802
|
create(context) {
|
803
|
+
function checkNode(usedFields, fieldName, type, node) {
|
804
|
+
if (usedFields.has(fieldName)) {
|
805
|
+
context.report({
|
806
|
+
loc: getLocation((node.kind === graphql.Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
|
807
|
+
offsetEnd: node.kind === graphql.Kind.VARIABLE_DEFINITION ? 0 : 1,
|
808
|
+
}),
|
809
|
+
messageId: AVOID_DUPLICATE_FIELDS,
|
810
|
+
data: {
|
811
|
+
type,
|
812
|
+
fieldName,
|
813
|
+
},
|
814
|
+
});
|
815
|
+
}
|
816
|
+
else {
|
817
|
+
usedFields.add(fieldName);
|
818
|
+
}
|
819
|
+
}
|
814
820
|
return {
|
815
821
|
OperationDefinition(node) {
|
816
|
-
const
|
817
|
-
for (const
|
818
|
-
|
819
|
-
context.report({
|
820
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
821
|
-
data: {
|
822
|
-
type: 'Operation variable',
|
823
|
-
fieldName: arg.variable.name.value,
|
824
|
-
},
|
825
|
-
node: arg,
|
826
|
-
});
|
827
|
-
});
|
822
|
+
const set = new Set();
|
823
|
+
for (const varDef of node.variableDefinitions) {
|
824
|
+
checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
|
828
825
|
}
|
829
826
|
},
|
830
827
|
Field(node) {
|
831
|
-
const
|
832
|
-
for (const arg of node.arguments
|
833
|
-
|
834
|
-
context.report({
|
835
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
836
|
-
data: {
|
837
|
-
type: 'Field argument',
|
838
|
-
fieldName: arg.name.value,
|
839
|
-
},
|
840
|
-
node: arg,
|
841
|
-
});
|
842
|
-
});
|
828
|
+
const set = new Set();
|
829
|
+
for (const arg of node.arguments) {
|
830
|
+
checkNode(set, arg.name.value, 'Field argument', arg);
|
843
831
|
}
|
844
832
|
},
|
845
833
|
SelectionSet(node) {
|
846
834
|
var _a;
|
847
|
-
const
|
848
|
-
for (const selection of node.selections
|
835
|
+
const set = new Set();
|
836
|
+
for (const selection of node.selections) {
|
849
837
|
if (selection.kind === graphql.Kind.FIELD) {
|
850
|
-
|
851
|
-
uniqueCheck.add(nameToCheck, () => {
|
852
|
-
context.report({
|
853
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
854
|
-
data: {
|
855
|
-
type: 'Field',
|
856
|
-
fieldName: nameToCheck,
|
857
|
-
},
|
858
|
-
node: selection,
|
859
|
-
});
|
860
|
-
});
|
838
|
+
checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
|
861
839
|
}
|
862
840
|
}
|
863
841
|
},
|
@@ -2030,10 +2008,7 @@ const rule$c = {
|
|
2030
2008
|
if (!isEslintComment && line !== prev.line && next.kind === graphql.TokenKind.NAME && linesAfter < 2) {
|
2031
2009
|
context.report({
|
2032
2010
|
messageId: HASHTAG_COMMENT,
|
2033
|
-
loc: {
|
2034
|
-
start: { line, column },
|
2035
|
-
end: { line, column },
|
2036
|
-
},
|
2011
|
+
loc: getLocation({ start: { line, column } }),
|
2037
2012
|
});
|
2038
2013
|
}
|
2039
2014
|
}
|
@@ -2516,15 +2491,17 @@ const rule$h = {
|
|
2516
2491
|
},
|
2517
2492
|
create(context) {
|
2518
2493
|
return {
|
2519
|
-
|
2520
|
-
|
2521
|
-
|
2522
|
-
|
2523
|
-
|
2524
|
-
|
2525
|
-
|
2526
|
-
|
2527
|
-
|
2494
|
+
Directive(node) {
|
2495
|
+
if (node && node.name && node.name.value === 'deprecated') {
|
2496
|
+
const args = node.arguments || [];
|
2497
|
+
const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
|
2498
|
+
const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
|
2499
|
+
if (!value) {
|
2500
|
+
context.report({
|
2501
|
+
node: node.name,
|
2502
|
+
message: 'Directive "@deprecated" must have a reason!',
|
2503
|
+
});
|
2504
|
+
}
|
2528
2505
|
}
|
2529
2506
|
},
|
2530
2507
|
};
|
@@ -2681,22 +2658,18 @@ const rule$j = {
|
|
2681
2658
|
if (!mutationType || !queryType) {
|
2682
2659
|
return {};
|
2683
2660
|
}
|
2684
|
-
const selector = [
|
2685
|
-
`:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
2686
|
-
'>',
|
2687
|
-
graphql.Kind.FIELD_DEFINITION,
|
2688
|
-
graphql.Kind.NAMED_TYPE,
|
2689
|
-
].join(' ');
|
2661
|
+
const selector = `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${graphql.Kind.FIELD_DEFINITION}`;
|
2690
2662
|
return {
|
2691
2663
|
[selector](node) {
|
2692
|
-
const
|
2664
|
+
const rawNode = node.rawNode();
|
2665
|
+
const typeName = getTypeName(rawNode);
|
2693
2666
|
const graphQLType = schema.getType(typeName);
|
2694
2667
|
if (graphql.isObjectType(graphQLType)) {
|
2695
2668
|
const { fields } = graphQLType.astNode;
|
2696
2669
|
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
2697
2670
|
if (!hasQueryType) {
|
2698
2671
|
context.report({
|
2699
|
-
|
2672
|
+
node,
|
2700
2673
|
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
|
2701
2674
|
});
|
2702
2675
|
}
|
package/index.mjs
CHANGED
@@ -738,35 +738,22 @@ const rule = {
|
|
738
738
|
};
|
739
739
|
|
740
740
|
const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
|
741
|
-
const ensureUnique = () => {
|
742
|
-
const set = new Set();
|
743
|
-
return {
|
744
|
-
add: (item, onError) => {
|
745
|
-
if (set.has(item)) {
|
746
|
-
onError();
|
747
|
-
}
|
748
|
-
else {
|
749
|
-
set.add(item);
|
750
|
-
}
|
751
|
-
},
|
752
|
-
};
|
753
|
-
};
|
754
741
|
const rule$1 = {
|
755
742
|
meta: {
|
756
743
|
type: 'suggestion',
|
757
744
|
docs: {
|
758
|
-
description:
|
745
|
+
description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
|
759
746
|
category: 'Stylistic Issues',
|
760
747
|
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
|
761
748
|
examples: [
|
762
749
|
{
|
763
750
|
title: 'Incorrect',
|
764
751
|
code: /* GraphQL */ `
|
765
|
-
query
|
752
|
+
query {
|
766
753
|
user {
|
767
|
-
name
|
754
|
+
name
|
768
755
|
email
|
769
|
-
name #
|
756
|
+
name # duplicate field
|
770
757
|
}
|
771
758
|
}
|
772
759
|
`,
|
@@ -774,7 +761,7 @@ const rule$1 = {
|
|
774
761
|
{
|
775
762
|
title: 'Incorrect',
|
776
763
|
code: /* GraphQL */ `
|
777
|
-
query
|
764
|
+
query {
|
778
765
|
users(
|
779
766
|
first: 100
|
780
767
|
skip: 50
|
@@ -789,9 +776,11 @@ const rule$1 = {
|
|
789
776
|
{
|
790
777
|
title: 'Incorrect',
|
791
778
|
code: /* GraphQL */ `
|
792
|
-
query
|
793
|
-
|
794
|
-
|
779
|
+
query (
|
780
|
+
$first: Int!
|
781
|
+
$first: Int! # duplicate variable
|
782
|
+
) {
|
783
|
+
users(first: $first, skip: 50) {
|
795
784
|
id
|
796
785
|
}
|
797
786
|
}
|
@@ -800,58 +789,47 @@ const rule$1 = {
|
|
800
789
|
],
|
801
790
|
},
|
802
791
|
messages: {
|
803
|
-
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times
|
792
|
+
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
|
804
793
|
},
|
805
794
|
schema: [],
|
806
795
|
},
|
807
796
|
create(context) {
|
797
|
+
function checkNode(usedFields, fieldName, type, node) {
|
798
|
+
if (usedFields.has(fieldName)) {
|
799
|
+
context.report({
|
800
|
+
loc: getLocation((node.kind === Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
|
801
|
+
offsetEnd: node.kind === Kind.VARIABLE_DEFINITION ? 0 : 1,
|
802
|
+
}),
|
803
|
+
messageId: AVOID_DUPLICATE_FIELDS,
|
804
|
+
data: {
|
805
|
+
type,
|
806
|
+
fieldName,
|
807
|
+
},
|
808
|
+
});
|
809
|
+
}
|
810
|
+
else {
|
811
|
+
usedFields.add(fieldName);
|
812
|
+
}
|
813
|
+
}
|
808
814
|
return {
|
809
815
|
OperationDefinition(node) {
|
810
|
-
const
|
811
|
-
for (const
|
812
|
-
|
813
|
-
context.report({
|
814
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
815
|
-
data: {
|
816
|
-
type: 'Operation variable',
|
817
|
-
fieldName: arg.variable.name.value,
|
818
|
-
},
|
819
|
-
node: arg,
|
820
|
-
});
|
821
|
-
});
|
816
|
+
const set = new Set();
|
817
|
+
for (const varDef of node.variableDefinitions) {
|
818
|
+
checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
|
822
819
|
}
|
823
820
|
},
|
824
821
|
Field(node) {
|
825
|
-
const
|
826
|
-
for (const arg of node.arguments
|
827
|
-
|
828
|
-
context.report({
|
829
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
830
|
-
data: {
|
831
|
-
type: 'Field argument',
|
832
|
-
fieldName: arg.name.value,
|
833
|
-
},
|
834
|
-
node: arg,
|
835
|
-
});
|
836
|
-
});
|
822
|
+
const set = new Set();
|
823
|
+
for (const arg of node.arguments) {
|
824
|
+
checkNode(set, arg.name.value, 'Field argument', arg);
|
837
825
|
}
|
838
826
|
},
|
839
827
|
SelectionSet(node) {
|
840
828
|
var _a;
|
841
|
-
const
|
842
|
-
for (const selection of node.selections
|
829
|
+
const set = new Set();
|
830
|
+
for (const selection of node.selections) {
|
843
831
|
if (selection.kind === Kind.FIELD) {
|
844
|
-
|
845
|
-
uniqueCheck.add(nameToCheck, () => {
|
846
|
-
context.report({
|
847
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
848
|
-
data: {
|
849
|
-
type: 'Field',
|
850
|
-
fieldName: nameToCheck,
|
851
|
-
},
|
852
|
-
node: selection,
|
853
|
-
});
|
854
|
-
});
|
832
|
+
checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
|
855
833
|
}
|
856
834
|
}
|
857
835
|
},
|
@@ -2024,10 +2002,7 @@ const rule$c = {
|
|
2024
2002
|
if (!isEslintComment && line !== prev.line && next.kind === TokenKind.NAME && linesAfter < 2) {
|
2025
2003
|
context.report({
|
2026
2004
|
messageId: HASHTAG_COMMENT,
|
2027
|
-
loc: {
|
2028
|
-
start: { line, column },
|
2029
|
-
end: { line, column },
|
2030
|
-
},
|
2005
|
+
loc: getLocation({ start: { line, column } }),
|
2031
2006
|
});
|
2032
2007
|
}
|
2033
2008
|
}
|
@@ -2510,15 +2485,17 @@ const rule$h = {
|
|
2510
2485
|
},
|
2511
2486
|
create(context) {
|
2512
2487
|
return {
|
2513
|
-
|
2514
|
-
|
2515
|
-
|
2516
|
-
|
2517
|
-
|
2518
|
-
|
2519
|
-
|
2520
|
-
|
2521
|
-
|
2488
|
+
Directive(node) {
|
2489
|
+
if (node && node.name && node.name.value === 'deprecated') {
|
2490
|
+
const args = node.arguments || [];
|
2491
|
+
const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
|
2492
|
+
const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
|
2493
|
+
if (!value) {
|
2494
|
+
context.report({
|
2495
|
+
node: node.name,
|
2496
|
+
message: 'Directive "@deprecated" must have a reason!',
|
2497
|
+
});
|
2498
|
+
}
|
2522
2499
|
}
|
2523
2500
|
},
|
2524
2501
|
};
|
@@ -2675,22 +2652,18 @@ const rule$j = {
|
|
2675
2652
|
if (!mutationType || !queryType) {
|
2676
2653
|
return {};
|
2677
2654
|
}
|
2678
|
-
const selector = [
|
2679
|
-
`:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
2680
|
-
'>',
|
2681
|
-
Kind.FIELD_DEFINITION,
|
2682
|
-
Kind.NAMED_TYPE,
|
2683
|
-
].join(' ');
|
2655
|
+
const selector = `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${Kind.FIELD_DEFINITION}`;
|
2684
2656
|
return {
|
2685
2657
|
[selector](node) {
|
2686
|
-
const
|
2658
|
+
const rawNode = node.rawNode();
|
2659
|
+
const typeName = getTypeName(rawNode);
|
2687
2660
|
const graphQLType = schema.getType(typeName);
|
2688
2661
|
if (isObjectType$1(graphQLType)) {
|
2689
2662
|
const { fields } = graphQLType.astNode;
|
2690
2663
|
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
2691
2664
|
if (!hasQueryType) {
|
2692
2665
|
context.report({
|
2693
|
-
|
2666
|
+
node,
|
2694
2667
|
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
|
2695
2668
|
});
|
2696
2669
|
}
|
package/package.json
CHANGED
package/rules/index.d.ts
CHANGED
@@ -6,7 +6,7 @@ export declare const rules: {
|
|
6
6
|
variables?: "OperationDefinition"[];
|
7
7
|
arguments?: ("Field" | "Directive" | "FieldDefinition" | "DirectiveDefinition")[];
|
8
8
|
}], false>;
|
9
|
-
'avoid-duplicate-fields': import("..").GraphQLESLintRule<[], false>;
|
9
|
+
'avoid-duplicate-fields': import("..").GraphQLESLintRule<any[], false>;
|
10
10
|
'avoid-operation-name-prefix': import("..").GraphQLESLintRule<import("./avoid-operation-name-prefix").AvoidOperationNamePrefixConfig, false>;
|
11
11
|
'avoid-scalar-result-type-on-mutation': import("..").GraphQLESLintRule<any[], false>;
|
12
12
|
'avoid-typename-prefix': import("..").GraphQLESLintRule<any[], false>;
|