@graphql-eslint/eslint-plugin 3.14.0-alpha-20221221142641-4e1a924 → 3.14.0-alpha-20221222124206-b82954b
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/README.md +309 -0
- package/cjs/cache.js +30 -0
- package/cjs/configs/base.js +7 -0
- package/cjs/configs/index.js +16 -0
- package/cjs/configs/operations-all.js +31 -0
- package/cjs/configs/operations-recommended.js +56 -0
- package/cjs/configs/relay.js +12 -0
- package/cjs/configs/schema-all.js +23 -0
- package/cjs/configs/schema-recommended.js +52 -0
- package/cjs/documents.js +149 -0
- package/cjs/estree-converter/converter.js +62 -0
- package/cjs/estree-converter/index.js +6 -0
- package/cjs/estree-converter/types.js +2 -0
- package/cjs/estree-converter/utils.js +109 -0
- package/cjs/graphql-config.js +55 -0
- package/cjs/index.js +17 -0
- package/cjs/parser.js +61 -0
- package/cjs/processor.js +78 -0
- package/cjs/rules/alphabetize.js +348 -0
- package/cjs/rules/description-style.js +78 -0
- package/cjs/rules/graphql-js-validation.js +499 -0
- package/cjs/rules/index.js +68 -0
- package/cjs/rules/input-name.js +136 -0
- package/cjs/rules/lone-executable-definition.js +88 -0
- package/cjs/rules/match-document-filename.js +235 -0
- package/cjs/rules/naming-convention.js +310 -0
- package/cjs/rules/no-anonymous-operations.js +67 -0
- package/cjs/rules/no-case-insensitive-enum-values-duplicates.js +61 -0
- package/cjs/rules/no-deprecated.js +124 -0
- package/cjs/rules/no-duplicate-fields.js +112 -0
- package/cjs/rules/no-hashtag-description.js +89 -0
- package/cjs/rules/no-root-type.js +86 -0
- package/cjs/rules/no-scalar-result-type-on-mutation.js +66 -0
- package/cjs/rules/no-typename-prefix.js +65 -0
- package/cjs/rules/no-unreachable-types.js +158 -0
- package/cjs/rules/no-unused-fields.js +130 -0
- package/cjs/rules/relay-arguments.js +121 -0
- package/cjs/rules/relay-connection-types.js +107 -0
- package/cjs/rules/relay-edge-types.js +189 -0
- package/cjs/rules/relay-page-info.js +100 -0
- package/cjs/rules/require-deprecation-date.js +123 -0
- package/cjs/rules/require-deprecation-reason.js +56 -0
- package/cjs/rules/require-description.js +193 -0
- package/cjs/rules/require-field-of-type-query-in-mutation-result.js +72 -0
- package/cjs/rules/require-id-when-available.js +199 -0
- package/cjs/rules/selection-set-depth.js +135 -0
- package/cjs/rules/strict-id-in-types.js +162 -0
- package/cjs/rules/unique-fragment-name.js +90 -0
- package/cjs/rules/unique-operation-name.js +65 -0
- package/cjs/schema.js +42 -0
- package/cjs/testkit.js +183 -0
- package/cjs/types.js +2 -0
- package/cjs/utils.js +96 -0
- package/docs/README.md +82 -0
- package/docs/custom-rules.md +184 -0
- package/docs/deprecated-rules.md +24 -0
- package/docs/parser-options.md +95 -0
- package/docs/parser.md +67 -0
- package/docs/rules/alphabetize.md +194 -0
- package/docs/rules/description-style.md +57 -0
- package/docs/rules/executable-definitions.md +20 -0
- package/docs/rules/fields-on-correct-type.md +23 -0
- package/docs/rules/fragments-on-composite-type.md +20 -0
- package/docs/rules/input-name.md +80 -0
- package/docs/rules/known-argument-names.md +23 -0
- package/docs/rules/known-directives.md +48 -0
- package/docs/rules/known-fragment-names.md +72 -0
- package/docs/rules/known-type-names.md +24 -0
- package/docs/rules/lone-anonymous-operation.md +20 -0
- package/docs/rules/lone-executable-definition.md +59 -0
- package/docs/rules/lone-schema-definition.md +19 -0
- package/docs/rules/match-document-filename.md +181 -0
- package/docs/rules/naming-convention.md +320 -0
- package/docs/rules/no-anonymous-operations.md +43 -0
- package/docs/rules/no-case-insensitive-enum-values-duplicates.md +46 -0
- package/docs/rules/no-deprecated.md +88 -0
- package/docs/rules/no-duplicate-fields.md +69 -0
- package/docs/rules/no-fragment-cycles.md +19 -0
- package/docs/rules/no-hashtag-description.md +62 -0
- package/docs/rules/no-root-type.md +55 -0
- package/docs/rules/no-scalar-result-type-on-mutation.md +39 -0
- package/docs/rules/no-typename-prefix.md +42 -0
- package/docs/rules/no-undefined-variables.md +20 -0
- package/docs/rules/no-unreachable-types.md +52 -0
- package/docs/rules/no-unused-fields.md +64 -0
- package/docs/rules/no-unused-fragments.md +20 -0
- package/docs/rules/no-unused-variables.md +20 -0
- package/docs/rules/one-field-subscriptions.md +19 -0
- package/docs/rules/overlapping-fields-can-be-merged.md +20 -0
- package/docs/rules/possible-fragment-spread.md +21 -0
- package/docs/rules/possible-type-extension.md +19 -0
- package/docs/rules/provided-required-arguments.md +21 -0
- package/docs/rules/relay-arguments.md +59 -0
- package/docs/rules/relay-connection-types.md +43 -0
- package/docs/rules/relay-edge-types.md +60 -0
- package/docs/rules/relay-page-info.md +34 -0
- package/docs/rules/require-deprecation-date.md +59 -0
- package/docs/rules/require-deprecation-reason.md +49 -0
- package/docs/rules/require-description.md +147 -0
- package/docs/rules/require-field-of-type-query-in-mutation-result.md +50 -0
- package/docs/rules/require-id-when-available.md +91 -0
- package/docs/rules/scalar-leafs.md +23 -0
- package/docs/rules/selection-set-depth.md +86 -0
- package/docs/rules/strict-id-in-types.md +129 -0
- package/docs/rules/unique-argument-names.md +19 -0
- package/docs/rules/unique-directive-names-per-location.md +21 -0
- package/docs/rules/unique-directive-names.md +19 -0
- package/docs/rules/unique-enum-value-names.md +16 -0
- package/docs/rules/unique-field-definition-names.md +19 -0
- package/docs/rules/unique-fragment-name.md +52 -0
- package/docs/rules/unique-input-field-names.md +19 -0
- package/docs/rules/unique-operation-name.md +56 -0
- package/docs/rules/unique-operation-types.md +19 -0
- package/docs/rules/unique-type-names.md +19 -0
- package/docs/rules/unique-variable-names.md +19 -0
- package/docs/rules/value-literals-of-correct-type.md +22 -0
- package/docs/rules/variables-are-input-types.md +20 -0
- package/docs/rules/variables-in-allowed-position.md +19 -0
- package/package.json +8 -11
- package/{cache.d.ts → typings/cache.d.cts} +0 -0
- package/typings/cache.d.ts +10 -0
- package/typings/configs/base.d.cts +5 -0
- package/typings/configs/base.d.ts +5 -0
- package/typings/configs/index.d.cts +139 -0
- package/typings/configs/index.d.ts +139 -0
- package/typings/configs/operations-all.d.cts +20 -0
- package/typings/configs/operations-all.d.ts +20 -0
- package/typings/configs/operations-recommended.d.cts +50 -0
- package/typings/configs/operations-recommended.d.ts +50 -0
- package/typings/configs/relay.d.cts +10 -0
- package/typings/configs/relay.d.ts +10 -0
- package/typings/configs/schema-all.d.cts +15 -0
- package/typings/configs/schema-all.d.ts +15 -0
- package/typings/configs/schema-recommended.d.cts +47 -0
- package/typings/configs/schema-recommended.d.ts +47 -0
- package/{documents.d.ts → typings/documents.d.cts} +0 -0
- package/typings/documents.d.ts +21 -0
- package/{estree-converter/converter.d.ts → typings/estree-converter/converter.d.cts} +0 -0
- package/typings/estree-converter/converter.d.ts +3 -0
- package/{estree-converter/index.d.ts → typings/estree-converter/index.d.cts} +0 -0
- package/typings/estree-converter/index.d.ts +3 -0
- package/{estree-converter/types.d.ts → typings/estree-converter/types.d.cts} +0 -0
- package/typings/estree-converter/types.d.ts +40 -0
- package/{estree-converter/utils.d.ts → typings/estree-converter/utils.d.cts} +0 -0
- package/typings/estree-converter/utils.d.ts +13 -0
- package/{graphql-config.d.ts → typings/graphql-config.d.cts} +0 -0
- package/typings/graphql-config.d.ts +4 -0
- package/typings/index.d.cts +9 -0
- package/{index.d.ts → typings/index.d.ts} +1 -5
- package/{parser.d.ts → typings/parser.d.cts} +0 -0
- package/typings/parser.d.ts +2 -0
- package/{processor.d.ts → typings/processor.d.cts} +0 -0
- package/typings/processor.d.ts +6 -0
- package/{rules/alphabetize.d.ts → typings/rules/alphabetize.d.cts} +0 -0
- package/typings/rules/alphabetize.d.ts +76 -0
- package/{rules/description-style.d.ts → typings/rules/description-style.d.cts} +0 -0
- package/typings/rules/description-style.d.ts +20 -0
- package/{rules/graphql-js-validation.d.ts → typings/rules/graphql-js-validation.d.cts} +0 -0
- package/typings/rules/graphql-js-validation.d.ts +2 -0
- package/{rules/index.d.ts → typings/rules/index.d.cts} +0 -0
- package/typings/rules/index.d.ts +104 -0
- package/{rules/input-name.d.ts → typings/rules/input-name.d.cts} +0 -0
- package/typings/rules/input-name.d.ts +35 -0
- package/{rules/lone-executable-definition.d.ts → typings/rules/lone-executable-definition.d.cts} +0 -0
- package/typings/rules/lone-executable-definition.d.ts +26 -0
- package/{rules/match-document-filename.d.ts → typings/rules/match-document-filename.d.cts} +0 -0
- package/typings/rules/match-document-filename.d.ts +72 -0
- package/{rules/naming-convention.d.ts → typings/rules/naming-convention.d.cts} +0 -0
- package/typings/rules/naming-convention.d.ts +83 -0
- package/{rules/no-anonymous-operations.d.ts → typings/rules/no-anonymous-operations.d.cts} +0 -0
- package/{rules/no-case-insensitive-enum-values-duplicates.d.ts → typings/rules/no-anonymous-operations.d.ts} +0 -0
- package/{rules/no-hashtag-description.d.ts → typings/rules/no-case-insensitive-enum-values-duplicates.d.cts} +0 -0
- package/{rules/no-scalar-result-type-on-mutation.d.ts → typings/rules/no-case-insensitive-enum-values-duplicates.d.ts} +0 -0
- package/{rules/no-deprecated.d.ts → typings/rules/no-deprecated.d.cts} +0 -0
- package/typings/rules/no-deprecated.d.ts +2 -0
- package/{rules/no-duplicate-fields.d.ts → typings/rules/no-duplicate-fields.d.cts} +0 -0
- package/{rules/relay-page-info.d.ts → typings/rules/no-duplicate-fields.d.ts} +0 -0
- package/{rules/no-typename-prefix.d.ts → typings/rules/no-hashtag-description.d.cts} +0 -0
- package/{rules/no-unreachable-types.d.ts → typings/rules/no-hashtag-description.d.ts} +0 -0
- package/{rules/no-root-type.d.ts → typings/rules/no-root-type.d.cts} +0 -0
- package/typings/rules/no-root-type.d.ts +25 -0
- package/{rules/no-unused-fields.d.ts → typings/rules/no-scalar-result-type-on-mutation.d.cts} +0 -0
- package/{rules/unique-operation-name.d.ts → typings/rules/no-scalar-result-type-on-mutation.d.ts} +0 -0
- package/typings/rules/no-typename-prefix.d.cts +2 -0
- package/typings/rules/no-typename-prefix.d.ts +2 -0
- package/typings/rules/no-unreachable-types.d.cts +2 -0
- package/typings/rules/no-unreachable-types.d.ts +2 -0
- package/typings/rules/no-unused-fields.d.cts +2 -0
- package/typings/rules/no-unused-fields.d.ts +2 -0
- package/{rules/relay-arguments.d.ts → typings/rules/relay-arguments.d.cts} +0 -0
- package/typings/rules/relay-arguments.d.ts +21 -0
- package/{rules/relay-connection-types.d.ts → typings/rules/relay-connection-types.d.cts} +0 -0
- package/typings/rules/relay-connection-types.d.ts +4 -0
- package/{rules/relay-edge-types.d.ts → typings/rules/relay-edge-types.d.cts} +0 -0
- package/typings/rules/relay-edge-types.d.ts +31 -0
- package/{rules/require-deprecation-reason.d.ts → typings/rules/relay-page-info.d.cts} +0 -0
- package/{rules/require-field-of-type-query-in-mutation-result.d.ts → typings/rules/relay-page-info.d.ts} +0 -0
- package/{rules/require-deprecation-date.d.ts → typings/rules/require-deprecation-date.d.cts} +0 -0
- package/typings/rules/require-deprecation-date.d.ts +18 -0
- package/typings/rules/require-deprecation-reason.d.cts +2 -0
- package/typings/rules/require-deprecation-reason.d.ts +2 -0
- package/{rules/require-description.d.ts → typings/rules/require-description.d.cts} +0 -0
- package/typings/rules/require-description.d.ts +14 -0
- package/typings/rules/require-field-of-type-query-in-mutation-result.d.cts +2 -0
- package/typings/rules/require-field-of-type-query-in-mutation-result.d.ts +2 -0
- package/{rules/require-id-when-available.d.ts → typings/rules/require-id-when-available.d.cts} +0 -0
- package/typings/rules/require-id-when-available.d.ts +36 -0
- package/{rules/selection-set-depth.d.ts → typings/rules/selection-set-depth.d.cts} +0 -0
- package/typings/rules/selection-set-depth.d.ts +28 -0
- package/{rules/strict-id-in-types.d.ts → typings/rules/strict-id-in-types.d.cts} +0 -0
- package/typings/rules/strict-id-in-types.d.ts +57 -0
- package/{rules/unique-fragment-name.d.ts → typings/rules/unique-fragment-name.d.cts} +0 -0
- package/typings/rules/unique-fragment-name.d.ts +5 -0
- package/typings/rules/unique-operation-name.d.cts +2 -0
- package/typings/rules/unique-operation-name.d.ts +2 -0
- package/{schema.d.ts → typings/schema.d.cts} +0 -0
- package/typings/schema.d.ts +3 -0
- package/{testkit.d.ts → typings/testkit.d.cts} +0 -0
- package/typings/testkit.d.ts +27 -0
- package/{types.d.ts → typings/types.d.cts} +0 -0
- package/typings/types.d.ts +81 -0
- package/{utils.d.ts → typings/utils.d.cts} +0 -0
- package/typings/utils.d.ts +34 -0
- package/configs/base.json +0 -4
- package/configs/operations-all.json +0 -25
- package/configs/operations-recommended.json +0 -50
- package/configs/relay.json +0 -9
- package/configs/schema-all.json +0 -17
- package/configs/schema-recommended.json +0 -49
- package/index.js +0 -4995
- package/index.mjs +0 -4983
@@ -0,0 +1,162 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.rule = void 0;
|
4
|
+
const graphql_1 = require("graphql");
|
5
|
+
const utils_1 = require("../utils");
|
6
|
+
const RULE_ID = 'strict-id-in-types';
|
7
|
+
const schema = {
|
8
|
+
type: 'array',
|
9
|
+
maxItems: 1,
|
10
|
+
items: {
|
11
|
+
type: 'object',
|
12
|
+
additionalProperties: false,
|
13
|
+
properties: {
|
14
|
+
acceptedIdNames: {
|
15
|
+
...utils_1.ARRAY_DEFAULT_OPTIONS,
|
16
|
+
default: ['id'],
|
17
|
+
},
|
18
|
+
acceptedIdTypes: {
|
19
|
+
...utils_1.ARRAY_DEFAULT_OPTIONS,
|
20
|
+
default: ['ID'],
|
21
|
+
},
|
22
|
+
exceptions: {
|
23
|
+
type: 'object',
|
24
|
+
additionalProperties: false,
|
25
|
+
properties: {
|
26
|
+
types: {
|
27
|
+
...utils_1.ARRAY_DEFAULT_OPTIONS,
|
28
|
+
description: 'This is used to exclude types with names that match one of the specified values.',
|
29
|
+
},
|
30
|
+
suffixes: {
|
31
|
+
...utils_1.ARRAY_DEFAULT_OPTIONS,
|
32
|
+
description: 'This is used to exclude types with names with suffixes that match one of the specified values.',
|
33
|
+
},
|
34
|
+
},
|
35
|
+
},
|
36
|
+
},
|
37
|
+
},
|
38
|
+
};
|
39
|
+
exports.rule = {
|
40
|
+
meta: {
|
41
|
+
type: 'suggestion',
|
42
|
+
docs: {
|
43
|
+
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.',
|
44
|
+
category: 'Schema',
|
45
|
+
recommended: true,
|
46
|
+
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
|
47
|
+
requiresSchema: true,
|
48
|
+
examples: [
|
49
|
+
{
|
50
|
+
title: 'Incorrect',
|
51
|
+
usage: [
|
52
|
+
{
|
53
|
+
acceptedIdNames: ['id', '_id'],
|
54
|
+
acceptedIdTypes: ['ID'],
|
55
|
+
exceptions: { suffixes: ['Payload'] },
|
56
|
+
},
|
57
|
+
],
|
58
|
+
code: /* GraphQL */ `
|
59
|
+
# Incorrect field name
|
60
|
+
type InvalidFieldName {
|
61
|
+
key: ID!
|
62
|
+
}
|
63
|
+
|
64
|
+
# Incorrect field type
|
65
|
+
type InvalidFieldType {
|
66
|
+
id: String!
|
67
|
+
}
|
68
|
+
|
69
|
+
# Incorrect exception suffix
|
70
|
+
type InvalidSuffixResult {
|
71
|
+
data: String!
|
72
|
+
}
|
73
|
+
|
74
|
+
# Too many unique identifiers. Must only contain one.
|
75
|
+
type InvalidFieldName {
|
76
|
+
id: ID!
|
77
|
+
_id: ID!
|
78
|
+
}
|
79
|
+
`,
|
80
|
+
},
|
81
|
+
{
|
82
|
+
title: 'Correct',
|
83
|
+
usage: [
|
84
|
+
{
|
85
|
+
acceptedIdNames: ['id', '_id'],
|
86
|
+
acceptedIdTypes: ['ID'],
|
87
|
+
exceptions: { types: ['Error'], suffixes: ['Payload'] },
|
88
|
+
},
|
89
|
+
],
|
90
|
+
code: /* GraphQL */ `
|
91
|
+
type User {
|
92
|
+
id: ID!
|
93
|
+
}
|
94
|
+
|
95
|
+
type Post {
|
96
|
+
_id: ID!
|
97
|
+
}
|
98
|
+
|
99
|
+
type CreateUserPayload {
|
100
|
+
data: String!
|
101
|
+
}
|
102
|
+
|
103
|
+
type Error {
|
104
|
+
message: String!
|
105
|
+
}
|
106
|
+
`,
|
107
|
+
},
|
108
|
+
],
|
109
|
+
},
|
110
|
+
schema,
|
111
|
+
},
|
112
|
+
create(context) {
|
113
|
+
const options = {
|
114
|
+
acceptedIdNames: ['id'],
|
115
|
+
acceptedIdTypes: ['ID'],
|
116
|
+
exceptions: {},
|
117
|
+
...context.options[0],
|
118
|
+
};
|
119
|
+
const schema = (0, utils_1.requireGraphQLSchemaFromContext)(RULE_ID, context);
|
120
|
+
const rootTypeNames = [
|
121
|
+
schema.getQueryType(),
|
122
|
+
schema.getMutationType(),
|
123
|
+
schema.getSubscriptionType(),
|
124
|
+
]
|
125
|
+
.filter(Boolean)
|
126
|
+
.map(type => type.name);
|
127
|
+
const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
|
128
|
+
return {
|
129
|
+
[selector](node) {
|
130
|
+
var _a, _b;
|
131
|
+
const typeName = node.name.value;
|
132
|
+
const shouldIgnoreNode = ((_a = options.exceptions.types) === null || _a === void 0 ? void 0 : _a.includes(typeName)) ||
|
133
|
+
((_b = options.exceptions.suffixes) === null || _b === void 0 ? void 0 : _b.some(suffix => typeName.endsWith(suffix)));
|
134
|
+
if (shouldIgnoreNode) {
|
135
|
+
return;
|
136
|
+
}
|
137
|
+
const validIds = node.fields.filter(field => {
|
138
|
+
const fieldNode = field.rawNode();
|
139
|
+
const isValidIdName = options.acceptedIdNames.includes(fieldNode.name.value);
|
140
|
+
// To be a valid type, it must be non-null and one of the accepted types.
|
141
|
+
let isValidIdType = false;
|
142
|
+
if (fieldNode.type.kind === graphql_1.Kind.NON_NULL_TYPE &&
|
143
|
+
fieldNode.type.type.kind === graphql_1.Kind.NAMED_TYPE) {
|
144
|
+
isValidIdType = options.acceptedIdTypes.includes(fieldNode.type.type.name.value);
|
145
|
+
}
|
146
|
+
return isValidIdName && isValidIdType;
|
147
|
+
});
|
148
|
+
// Usually, there should be only one unique identifier field per type.
|
149
|
+
// Some clients allow multiple fields to be used. If more people need this,
|
150
|
+
// we can extend this rule later.
|
151
|
+
if (validIds.length !== 1) {
|
152
|
+
const pluralNamesSuffix = options.acceptedIdNames.length > 1 ? 's' : '';
|
153
|
+
const pluralTypesSuffix = options.acceptedIdTypes.length > 1 ? 's' : '';
|
154
|
+
context.report({
|
155
|
+
node: node.name,
|
156
|
+
message: `${typeName} must have exactly one non-nullable unique identifier. Accepted name${pluralNamesSuffix}: ${(0, utils_1.englishJoinWords)(options.acceptedIdNames)}. Accepted type${pluralTypesSuffix}: ${(0, utils_1.englishJoinWords)(options.acceptedIdTypes)}.`,
|
157
|
+
});
|
158
|
+
}
|
159
|
+
},
|
160
|
+
};
|
161
|
+
},
|
162
|
+
};
|
@@ -0,0 +1,90 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.rule = exports.checkNode = void 0;
|
4
|
+
const path_1 = require("path");
|
5
|
+
const graphql_1 = require("graphql");
|
6
|
+
const utils_1 = require("../utils");
|
7
|
+
const RULE_ID = 'unique-fragment-name';
|
8
|
+
const checkNode = (context, node, ruleId) => {
|
9
|
+
const documentName = node.name.value;
|
10
|
+
const siblings = (0, utils_1.requireSiblingsOperations)(ruleId, context);
|
11
|
+
const siblingDocuments = node.kind === graphql_1.Kind.FRAGMENT_DEFINITION
|
12
|
+
? siblings.getFragment(documentName)
|
13
|
+
: siblings.getOperation(documentName);
|
14
|
+
const filepath = context.getFilename();
|
15
|
+
const conflictingDocuments = siblingDocuments.filter(f => {
|
16
|
+
var _a;
|
17
|
+
const isSameName = ((_a = f.document.name) === null || _a === void 0 ? void 0 : _a.value) === documentName;
|
18
|
+
const isSamePath = (0, utils_1.normalizePath)(f.filePath) === (0, utils_1.normalizePath)(filepath);
|
19
|
+
return isSameName && !isSamePath;
|
20
|
+
});
|
21
|
+
if (conflictingDocuments.length > 0) {
|
22
|
+
context.report({
|
23
|
+
messageId: ruleId,
|
24
|
+
data: {
|
25
|
+
documentName,
|
26
|
+
summary: conflictingDocuments
|
27
|
+
.map(f => `\t${(0, path_1.relative)(utils_1.CWD, f.filePath.replace(utils_1.VIRTUAL_DOCUMENT_REGEX, ''))}`)
|
28
|
+
.join('\n'),
|
29
|
+
},
|
30
|
+
node: node.name,
|
31
|
+
});
|
32
|
+
}
|
33
|
+
};
|
34
|
+
exports.checkNode = checkNode;
|
35
|
+
exports.rule = {
|
36
|
+
meta: {
|
37
|
+
type: 'suggestion',
|
38
|
+
docs: {
|
39
|
+
category: 'Operations',
|
40
|
+
description: 'Enforce unique fragment names across your project.',
|
41
|
+
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
|
42
|
+
requiresSiblings: true,
|
43
|
+
examples: [
|
44
|
+
{
|
45
|
+
title: 'Incorrect',
|
46
|
+
code: /* GraphQL */ `
|
47
|
+
# user.fragment.graphql
|
48
|
+
fragment UserFields on User {
|
49
|
+
id
|
50
|
+
name
|
51
|
+
fullName
|
52
|
+
}
|
53
|
+
|
54
|
+
# user-fields.graphql
|
55
|
+
fragment UserFields on User {
|
56
|
+
id
|
57
|
+
}
|
58
|
+
`,
|
59
|
+
},
|
60
|
+
{
|
61
|
+
title: 'Correct',
|
62
|
+
code: /* GraphQL */ `
|
63
|
+
# user.fragment.graphql
|
64
|
+
fragment AllUserFields on User {
|
65
|
+
id
|
66
|
+
name
|
67
|
+
fullName
|
68
|
+
}
|
69
|
+
|
70
|
+
# user-fields.graphql
|
71
|
+
fragment UserFields on User {
|
72
|
+
id
|
73
|
+
}
|
74
|
+
`,
|
75
|
+
},
|
76
|
+
],
|
77
|
+
},
|
78
|
+
messages: {
|
79
|
+
[RULE_ID]: 'Fragment named "{{ documentName }}" already defined in:\n{{ summary }}',
|
80
|
+
},
|
81
|
+
schema: [],
|
82
|
+
},
|
83
|
+
create(context) {
|
84
|
+
return {
|
85
|
+
FragmentDefinition(node) {
|
86
|
+
(0, exports.checkNode)(context, node, RULE_ID);
|
87
|
+
},
|
88
|
+
};
|
89
|
+
},
|
90
|
+
};
|
@@ -0,0 +1,65 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.rule = void 0;
|
4
|
+
const unique_fragment_name_1 = require("./unique-fragment-name");
|
5
|
+
const RULE_ID = 'unique-operation-name';
|
6
|
+
exports.rule = {
|
7
|
+
meta: {
|
8
|
+
type: 'suggestion',
|
9
|
+
docs: {
|
10
|
+
category: 'Operations',
|
11
|
+
description: 'Enforce unique operation names across your project.',
|
12
|
+
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
|
13
|
+
requiresSiblings: true,
|
14
|
+
examples: [
|
15
|
+
{
|
16
|
+
title: 'Incorrect',
|
17
|
+
code: /* GraphQL */ `
|
18
|
+
# foo.query.graphql
|
19
|
+
query user {
|
20
|
+
user {
|
21
|
+
id
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
# bar.query.graphql
|
26
|
+
query user {
|
27
|
+
me {
|
28
|
+
id
|
29
|
+
}
|
30
|
+
}
|
31
|
+
`,
|
32
|
+
},
|
33
|
+
{
|
34
|
+
title: 'Correct',
|
35
|
+
code: /* GraphQL */ `
|
36
|
+
# foo.query.graphql
|
37
|
+
query user {
|
38
|
+
user {
|
39
|
+
id
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
# bar.query.graphql
|
44
|
+
query me {
|
45
|
+
me {
|
46
|
+
id
|
47
|
+
}
|
48
|
+
}
|
49
|
+
`,
|
50
|
+
},
|
51
|
+
],
|
52
|
+
},
|
53
|
+
messages: {
|
54
|
+
[RULE_ID]: 'Operation named "{{ documentName }}" already defined in:\n{{ summary }}',
|
55
|
+
},
|
56
|
+
schema: [],
|
57
|
+
},
|
58
|
+
create(context) {
|
59
|
+
return {
|
60
|
+
'OperationDefinition[name!=undefined]'(node) {
|
61
|
+
(0, unique_fragment_name_1.checkNode)(context, node, RULE_ID);
|
62
|
+
},
|
63
|
+
};
|
64
|
+
},
|
65
|
+
};
|
package/cjs/schema.js
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.getSchema = void 0;
|
4
|
+
const tslib_1 = require("tslib");
|
5
|
+
const graphql_1 = require("graphql");
|
6
|
+
const debug_1 = tslib_1.__importDefault(require("debug"));
|
7
|
+
const fast_glob_1 = tslib_1.__importDefault(require("fast-glob"));
|
8
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
9
|
+
const cache_1 = require("./cache");
|
10
|
+
const schemaCache = new cache_1.ModuleCache();
|
11
|
+
const debug = (0, debug_1.default)('graphql-eslint:schema');
|
12
|
+
function getSchema(project, schemaOptions) {
|
13
|
+
const schemaKey = project.schema;
|
14
|
+
if (!schemaKey) {
|
15
|
+
return null;
|
16
|
+
}
|
17
|
+
const cache = schemaCache.get(schemaKey);
|
18
|
+
if (cache) {
|
19
|
+
return cache;
|
20
|
+
}
|
21
|
+
let schema;
|
22
|
+
try {
|
23
|
+
debug('Loading schema from %o', project.schema);
|
24
|
+
schema = project.loadSchemaSync(project.schema, 'GraphQLSchema', {
|
25
|
+
...schemaOptions,
|
26
|
+
pluckConfig: project.extensions.pluckConfig,
|
27
|
+
});
|
28
|
+
if (debug.enabled) {
|
29
|
+
debug('Schema loaded: %o', schema instanceof graphql_1.GraphQLSchema);
|
30
|
+
const schemaPaths = fast_glob_1.default.sync(project.schema, { absolute: true });
|
31
|
+
debug('Schema pointers %O', schemaPaths);
|
32
|
+
}
|
33
|
+
// Do not set error to cache, since cache reload will be done after some `lifetime` seconds
|
34
|
+
schemaCache.set(schemaKey, schema);
|
35
|
+
}
|
36
|
+
catch (error) {
|
37
|
+
error.message = chalk_1.default.red(`Error while loading schema: ${error.message}`);
|
38
|
+
schema = error;
|
39
|
+
}
|
40
|
+
return schema;
|
41
|
+
}
|
42
|
+
exports.getSchema = getSchema;
|
package/cjs/testkit.js
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.GraphQLRuleTester = void 0;
|
4
|
+
/* eslint-env jest */
|
5
|
+
const fs_1 = require("fs");
|
6
|
+
const path_1 = require("path");
|
7
|
+
const eslint_1 = require("eslint");
|
8
|
+
const code_frame_1 = require("@babel/code-frame");
|
9
|
+
function indentCode(code, indent = 4) {
|
10
|
+
return code.replace(/^/gm, ' '.repeat(indent));
|
11
|
+
}
|
12
|
+
// A simple version of `SourceCodeFixer.applyFixes`
|
13
|
+
// https://github.com/eslint/eslint/issues/14936#issuecomment-906746754
|
14
|
+
function applyFix(code, { range, text }) {
|
15
|
+
return [code.slice(0, range[0]), text, code.slice(range[1])].join('');
|
16
|
+
}
|
17
|
+
class GraphQLRuleTester extends eslint_1.RuleTester {
|
18
|
+
constructor(parserOptions = {}) {
|
19
|
+
const config = {
|
20
|
+
parser: require.resolve('@graphql-eslint/eslint-plugin'),
|
21
|
+
parserOptions: {
|
22
|
+
...parserOptions,
|
23
|
+
skipGraphQLConfig: true,
|
24
|
+
},
|
25
|
+
};
|
26
|
+
super(config);
|
27
|
+
this.config = config;
|
28
|
+
}
|
29
|
+
fromMockFile(path) {
|
30
|
+
return (0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, `../tests/mocks/${path}`), 'utf-8');
|
31
|
+
}
|
32
|
+
runGraphQLTests(ruleId, rule, tests) {
|
33
|
+
const ruleTests = eslint_1.Linter.version.startsWith('8')
|
34
|
+
? tests
|
35
|
+
: {
|
36
|
+
valid: tests.valid.map(test => {
|
37
|
+
if (typeof test === 'string') {
|
38
|
+
return test;
|
39
|
+
}
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
41
|
+
const { name, ...testCaseOptions } = test;
|
42
|
+
return testCaseOptions;
|
43
|
+
}),
|
44
|
+
invalid: tests.invalid.map(test => {
|
45
|
+
// ESLint 7 throws an error on CI - Unexpected top-level property "name"
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
47
|
+
const { name, ...testCaseOptions } = test;
|
48
|
+
return testCaseOptions;
|
49
|
+
}),
|
50
|
+
};
|
51
|
+
super.run(ruleId, rule, ruleTests);
|
52
|
+
const linter = new eslint_1.Linter();
|
53
|
+
linter.defineRule(ruleId, rule);
|
54
|
+
const hasOnlyTest = [...tests.valid, ...tests.invalid].some(t => typeof t !== 'string' && t.only);
|
55
|
+
// for (const [index, testCase] of tests.valid.entries()) {
|
56
|
+
// const { name, code, filename, only }: RuleTester.ValidTestCase =
|
57
|
+
// typeof testCase === 'string' ? { code: testCase } : testCase;
|
58
|
+
//
|
59
|
+
// if (hasOnlyTest && !only) {
|
60
|
+
// continue;
|
61
|
+
// }
|
62
|
+
//
|
63
|
+
// const verifyConfig = getVerifyConfig(ruleId, this.config, testCase);
|
64
|
+
// defineParser(linter, verifyConfig.parser);
|
65
|
+
//
|
66
|
+
// const messages = linter.verify(code, verifyConfig, { filename });
|
67
|
+
// const codeFrame = printCode(code, { line: 0, column: 0 });
|
68
|
+
//
|
69
|
+
// it(name || `Valid #${index + 1}\n${codeFrame}`, () => {
|
70
|
+
// expect(messages).toEqual([]);
|
71
|
+
// });
|
72
|
+
// }
|
73
|
+
for (const [idx, testCase] of tests.invalid.entries()) {
|
74
|
+
const { only, filename, options, name } = testCase;
|
75
|
+
if (hasOnlyTest && !only) {
|
76
|
+
continue;
|
77
|
+
}
|
78
|
+
const code = removeTrailingBlankLines(testCase.code);
|
79
|
+
const verifyConfig = getVerifyConfig(ruleId, this.config, testCase);
|
80
|
+
defineParser(linter, verifyConfig.parser);
|
81
|
+
const messages = linter.verify(code, verifyConfig, filename);
|
82
|
+
if (messages.length === 0) {
|
83
|
+
throw new Error('Invalid case should have at least one error.');
|
84
|
+
}
|
85
|
+
const codeFrame = indentCode(printCode(code, { line: 0, column: 0 }));
|
86
|
+
const messageForSnapshot = ['#### ⌨️ Code', codeFrame];
|
87
|
+
if (options) {
|
88
|
+
const opts = JSON.stringify(options, null, 2).slice(1, -1);
|
89
|
+
messageForSnapshot.push('#### ⚙️ Options', indentCode(removeTrailingBlankLines(opts), 2));
|
90
|
+
}
|
91
|
+
for (const [index, message] of messages.entries()) {
|
92
|
+
if (message.fatal) {
|
93
|
+
throw new Error(message.message);
|
94
|
+
}
|
95
|
+
const codeWithMessage = printCode(code, message, 1);
|
96
|
+
messageForSnapshot.push(printWithIndex('#### ❌ Error', index, messages.length), indentCode(codeWithMessage));
|
97
|
+
const { suggestions } = message;
|
98
|
+
// Don't print suggestions in snapshots for too big codes
|
99
|
+
if (suggestions && (code.match(/\n/g) || '').length < 1000) {
|
100
|
+
for (const [i, suggestion] of message.suggestions.entries()) {
|
101
|
+
const title = printWithIndex('#### 💡 Suggestion', i, suggestions.length, suggestion.desc);
|
102
|
+
const output = applyFix(code, suggestion.fix);
|
103
|
+
const codeFrame = printCode(output, { line: 0, column: 0 });
|
104
|
+
messageForSnapshot.push(title, indentCode(codeFrame, 2));
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}
|
108
|
+
if (rule.meta.fixable) {
|
109
|
+
const { fixed, output } = linter.verifyAndFix(code, verifyConfig, filename);
|
110
|
+
if (fixed) {
|
111
|
+
messageForSnapshot.push('#### 🔧 Autofix output', indentCode(printCode(output)));
|
112
|
+
}
|
113
|
+
}
|
114
|
+
// @ts-expect-error -- we should import `vitest` but somebody could use globals from `jest`
|
115
|
+
it(name || `Invalid #${idx + 1}`, () => {
|
116
|
+
// @ts-expect-error -- ^ same
|
117
|
+
expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
|
118
|
+
});
|
119
|
+
}
|
120
|
+
}
|
121
|
+
}
|
122
|
+
exports.GraphQLRuleTester = GraphQLRuleTester;
|
123
|
+
function removeTrailingBlankLines(text) {
|
124
|
+
return text.replace(/^\s*\n/, '').trimEnd();
|
125
|
+
}
|
126
|
+
function printWithIndex(title, index, total, description) {
|
127
|
+
if (total > 1) {
|
128
|
+
title += ` ${index + 1}/${total}`;
|
129
|
+
}
|
130
|
+
if (description) {
|
131
|
+
title += `: ${description}`;
|
132
|
+
}
|
133
|
+
return title;
|
134
|
+
}
|
135
|
+
function getVerifyConfig(ruleId, testerConfig, testCase) {
|
136
|
+
const { parser = testerConfig.parser, parserOptions, options } = testCase;
|
137
|
+
return {
|
138
|
+
...testerConfig,
|
139
|
+
parser,
|
140
|
+
parserOptions: {
|
141
|
+
...testerConfig.parserOptions,
|
142
|
+
...parserOptions,
|
143
|
+
},
|
144
|
+
rules: {
|
145
|
+
[ruleId]: Array.isArray(options) ? ['error', ...options] : 'error',
|
146
|
+
},
|
147
|
+
};
|
148
|
+
}
|
149
|
+
const parsers = new WeakMap();
|
150
|
+
function defineParser(linter, parser) {
|
151
|
+
if (!parser) {
|
152
|
+
return;
|
153
|
+
}
|
154
|
+
if (!parsers.has(linter)) {
|
155
|
+
parsers.set(linter, new Set());
|
156
|
+
}
|
157
|
+
const defined = parsers.get(linter);
|
158
|
+
if (!defined.has(parser)) {
|
159
|
+
defined.add(parser);
|
160
|
+
linter.defineParser(parser, require(parser));
|
161
|
+
}
|
162
|
+
}
|
163
|
+
function printCode(code, result = {}, linesOffset = Number.POSITIVE_INFINITY) {
|
164
|
+
const { line, column, endLine, endColumn, message } = result;
|
165
|
+
const location = {};
|
166
|
+
if (typeof line === 'number' && typeof column === 'number') {
|
167
|
+
location.start = {
|
168
|
+
line,
|
169
|
+
column,
|
170
|
+
};
|
171
|
+
}
|
172
|
+
if (typeof endLine === 'number' && typeof endColumn === 'number') {
|
173
|
+
location.end = {
|
174
|
+
line: endLine,
|
175
|
+
column: endColumn,
|
176
|
+
};
|
177
|
+
}
|
178
|
+
return (0, code_frame_1.codeFrameColumns)(code, location, {
|
179
|
+
linesAbove: linesOffset,
|
180
|
+
linesBelow: linesOffset,
|
181
|
+
message,
|
182
|
+
});
|
183
|
+
}
|
package/cjs/types.js
ADDED
package/cjs/utils.js
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.englishJoinWords = exports.ARRAY_DEFAULT_OPTIONS = exports.REPORT_ON_FIRST_CHARACTER = exports.getLocation = exports.convertCase = exports.camelCase = exports.pascalCase = exports.TYPES_KINDS = exports.getTypeName = exports.CWD = exports.VIRTUAL_DOCUMENT_REGEX = exports.normalizePath = exports.logger = exports.requireGraphQLSchemaFromContext = exports.requireSiblingsOperations = void 0;
|
4
|
+
const tslib_1 = require("tslib");
|
5
|
+
const graphql_1 = require("graphql");
|
6
|
+
const lodash_lowercase_1 = tslib_1.__importDefault(require("lodash.lowercase"));
|
7
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
8
|
+
function requireSiblingsOperations(ruleId, context) {
|
9
|
+
const { siblingOperations } = context.parserServices;
|
10
|
+
if (!siblingOperations.available) {
|
11
|
+
throw new Error(`Rule \`${ruleId}\` requires \`parserOptions.operations\` to be set and loaded. See https://bit.ly/graphql-eslint-operations for more info`);
|
12
|
+
}
|
13
|
+
return siblingOperations;
|
14
|
+
}
|
15
|
+
exports.requireSiblingsOperations = requireSiblingsOperations;
|
16
|
+
function requireGraphQLSchemaFromContext(ruleId, context) {
|
17
|
+
const { schema } = context.parserServices;
|
18
|
+
if (!schema) {
|
19
|
+
throw new Error(`Rule \`${ruleId}\` requires \`parserOptions.schema\` to be set and loaded. See https://bit.ly/graphql-eslint-schema for more info`);
|
20
|
+
}
|
21
|
+
else if (schema instanceof Error) {
|
22
|
+
throw schema;
|
23
|
+
}
|
24
|
+
return schema;
|
25
|
+
}
|
26
|
+
exports.requireGraphQLSchemaFromContext = requireGraphQLSchemaFromContext;
|
27
|
+
exports.logger = {
|
28
|
+
// eslint-disable-next-line no-console
|
29
|
+
error: (...args) => console.error(chalk_1.default.red('error'), '[graphql-eslint]', (0, chalk_1.default)(...args)),
|
30
|
+
// eslint-disable-next-line no-console
|
31
|
+
warn: (...args) => console.warn(chalk_1.default.yellow('warning'), '[graphql-eslint]', (0, chalk_1.default)(...args)),
|
32
|
+
};
|
33
|
+
const normalizePath = (path) => (path || '').replace(/\\/g, '/');
|
34
|
+
exports.normalizePath = normalizePath;
|
35
|
+
exports.VIRTUAL_DOCUMENT_REGEX = /\/\d+_document.graphql$/;
|
36
|
+
exports.CWD = process.cwd();
|
37
|
+
const getTypeName = (node) => 'type' in node ? (0, exports.getTypeName)(node.type) : node.name.value;
|
38
|
+
exports.getTypeName = getTypeName;
|
39
|
+
exports.TYPES_KINDS = [
|
40
|
+
graphql_1.Kind.OBJECT_TYPE_DEFINITION,
|
41
|
+
graphql_1.Kind.INTERFACE_TYPE_DEFINITION,
|
42
|
+
graphql_1.Kind.ENUM_TYPE_DEFINITION,
|
43
|
+
graphql_1.Kind.SCALAR_TYPE_DEFINITION,
|
44
|
+
graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
45
|
+
graphql_1.Kind.UNION_TYPE_DEFINITION,
|
46
|
+
];
|
47
|
+
const pascalCase = (str) => (0, lodash_lowercase_1.default)(str)
|
48
|
+
.split(' ')
|
49
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
50
|
+
.join('');
|
51
|
+
exports.pascalCase = pascalCase;
|
52
|
+
const camelCase = (str) => {
|
53
|
+
const result = (0, exports.pascalCase)(str);
|
54
|
+
return result.charAt(0).toLowerCase() + result.slice(1);
|
55
|
+
};
|
56
|
+
exports.camelCase = camelCase;
|
57
|
+
const convertCase = (style, str) => {
|
58
|
+
switch (style) {
|
59
|
+
case 'camelCase':
|
60
|
+
return (0, exports.camelCase)(str);
|
61
|
+
case 'PascalCase':
|
62
|
+
return (0, exports.pascalCase)(str);
|
63
|
+
case 'snake_case':
|
64
|
+
return (0, lodash_lowercase_1.default)(str).replace(/ /g, '_');
|
65
|
+
case 'UPPER_CASE':
|
66
|
+
return (0, lodash_lowercase_1.default)(str).replace(/ /g, '_').toUpperCase();
|
67
|
+
case 'kebab-case':
|
68
|
+
return (0, lodash_lowercase_1.default)(str).replace(/ /g, '-');
|
69
|
+
}
|
70
|
+
};
|
71
|
+
exports.convertCase = convertCase;
|
72
|
+
function getLocation(start, fieldName = '') {
|
73
|
+
const { line, column } = start;
|
74
|
+
return {
|
75
|
+
start: {
|
76
|
+
line,
|
77
|
+
column,
|
78
|
+
},
|
79
|
+
end: {
|
80
|
+
line,
|
81
|
+
column: column + fieldName.length,
|
82
|
+
},
|
83
|
+
};
|
84
|
+
}
|
85
|
+
exports.getLocation = getLocation;
|
86
|
+
exports.REPORT_ON_FIRST_CHARACTER = { column: 0, line: 1 };
|
87
|
+
exports.ARRAY_DEFAULT_OPTIONS = {
|
88
|
+
type: 'array',
|
89
|
+
uniqueItems: true,
|
90
|
+
minItems: 1,
|
91
|
+
items: {
|
92
|
+
type: 'string',
|
93
|
+
},
|
94
|
+
};
|
95
|
+
const englishJoinWords = words => new Intl.ListFormat('en-US', { type: 'disjunction' }).format(words);
|
96
|
+
exports.englishJoinWords = englishJoinWords;
|