@graphql-eslint/eslint-plugin 3.16.0 → 3.16.1-alpha-20230226183026-b86ac84
Sign up to get free protection for your applications and to get access to all the features.
@@ -128,65 +128,83 @@ exports.rule = {
|
|
128
128
|
// Can't access to node.parent in GraphQL AST.Node, so pass as argument
|
129
129
|
parent, checkedFragmentSpreads = new Set()) {
|
130
130
|
const rawType = (0, index_js_1.getBaseType)(type);
|
131
|
-
|
132
|
-
|
133
|
-
if (!isObjectType && !isInterfaceType) {
|
134
|
-
return;
|
131
|
+
if (rawType instanceof graphql_1.GraphQLObjectType || rawType instanceof graphql_1.GraphQLInterfaceType) {
|
132
|
+
checkFields(rawType);
|
135
133
|
}
|
136
|
-
|
137
|
-
|
138
|
-
if (!hasIdFieldInType) {
|
139
|
-
return;
|
140
|
-
}
|
141
|
-
function hasIdField({ selections }) {
|
142
|
-
return selections.some(selection => {
|
143
|
-
if (selection.kind === graphql_1.Kind.FIELD) {
|
144
|
-
if (selection.alias && idNames.includes(selection.alias.value)) {
|
145
|
-
return true;
|
146
|
-
}
|
147
|
-
return idNames.includes(selection.name.value);
|
148
|
-
}
|
134
|
+
else if (rawType instanceof graphql_1.GraphQLUnionType) {
|
135
|
+
for (const selection of node.selections) {
|
149
136
|
if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT) {
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
if (foundSpread) {
|
155
|
-
const fragmentSpread = foundSpread.document;
|
156
|
-
checkedFragmentSpreads.add(fragmentSpread.name.value);
|
157
|
-
return hasIdField(fragmentSpread.selectionSet);
|
137
|
+
const types = rawType.getTypes();
|
138
|
+
const t = types.find(t => t.name === selection.typeCondition.name.value);
|
139
|
+
if (t) {
|
140
|
+
checkFields(t);
|
158
141
|
}
|
159
142
|
}
|
160
|
-
|
161
|
-
});
|
162
|
-
}
|
163
|
-
const hasId = hasIdField(node);
|
164
|
-
checkFragments(node);
|
165
|
-
if (hasId) {
|
166
|
-
return;
|
143
|
+
}
|
167
144
|
}
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
145
|
+
function checkFields(rawType) {
|
146
|
+
const fields = rawType.getFields();
|
147
|
+
const hasIdFieldInType = idNames.some(name => fields[name]);
|
148
|
+
if (!hasIdFieldInType) {
|
149
|
+
return;
|
150
|
+
}
|
151
|
+
function hasIdField({ selections }) {
|
152
|
+
return selections.some(selection => {
|
153
|
+
if (selection.kind === graphql_1.Kind.FIELD) {
|
154
|
+
if (selection.alias && idNames.includes(selection.alias.value)) {
|
155
|
+
return true;
|
156
|
+
}
|
157
|
+
return idNames.includes(selection.name.value);
|
158
|
+
}
|
159
|
+
if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT) {
|
160
|
+
return hasIdField(selection.selectionSet);
|
161
|
+
}
|
162
|
+
if (selection.kind === graphql_1.Kind.FRAGMENT_SPREAD) {
|
163
|
+
const [foundSpread] = siblings.getFragment(selection.name.value);
|
164
|
+
if (foundSpread) {
|
165
|
+
const fragmentSpread = foundSpread.document;
|
166
|
+
checkedFragmentSpreads.add(fragmentSpread.name.value);
|
167
|
+
return hasIdField(fragmentSpread.selectionSet);
|
168
|
+
}
|
169
|
+
}
|
170
|
+
return false;
|
171
|
+
});
|
172
|
+
}
|
173
|
+
const hasId = hasIdField(node);
|
174
|
+
checkFragments(node);
|
175
|
+
if (hasId) {
|
176
|
+
return;
|
177
|
+
}
|
178
|
+
const pluralSuffix = idNames.length > 1 ? 's' : '';
|
179
|
+
const fieldName = (0, utils_js_1.englishJoinWords)(idNames.map(name => `\`${(parent.alias || parent.name).value}.${name}\``));
|
180
|
+
const addition = checkedFragmentSpreads.size === 0
|
181
|
+
? ''
|
182
|
+
: ` or add to used fragment${checkedFragmentSpreads.size > 1 ? 's' : ''} ${(0, utils_js_1.englishJoinWords)([...checkedFragmentSpreads].map(name => `\`${name}\``))}`;
|
183
|
+
const problem = {
|
184
|
+
loc,
|
185
|
+
messageId: RULE_ID,
|
186
|
+
data: {
|
187
|
+
pluralSuffix,
|
188
|
+
fieldName,
|
189
|
+
addition,
|
190
|
+
},
|
191
|
+
};
|
192
|
+
// Don't provide suggestions for selections in fragments as fragment can be in a separate file
|
193
|
+
if ('type' in node) {
|
194
|
+
problem.suggest = idNames.map(idName => ({
|
195
|
+
desc: `Add \`${idName}\` selection`,
|
196
|
+
fix: fixer => {
|
197
|
+
let insertNode = node.selections[0];
|
198
|
+
insertNode =
|
199
|
+
insertNode.kind === graphql_1.Kind.INLINE_FRAGMENT
|
200
|
+
? insertNode.selectionSet.selections[0]
|
201
|
+
: insertNode;
|
202
|
+
return fixer.insertTextBefore(insertNode, `${idName} `);
|
203
|
+
},
|
204
|
+
}));
|
205
|
+
}
|
206
|
+
context.report(problem);
|
188
207
|
}
|
189
|
-
context.report(problem);
|
190
208
|
}
|
191
209
|
return {
|
192
210
|
[selector](node) {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { asArray } from '@graphql-tools/utils';
|
2
|
-
import { GraphQLInterfaceType, GraphQLObjectType, Kind, TypeInfo, visit, visitWithTypeInfo, } from 'graphql';
|
2
|
+
import { GraphQLInterfaceType, GraphQLObjectType, GraphQLUnionType, Kind, TypeInfo, visit, visitWithTypeInfo, } from 'graphql';
|
3
3
|
import { getBaseType } from '../estree-converter/index.js';
|
4
4
|
import { ARRAY_DEFAULT_OPTIONS, englishJoinWords, requireGraphQLSchemaFromContext, requireSiblingsOperations, } from '../utils.js';
|
5
5
|
const RULE_ID = 'require-id-when-available';
|
@@ -125,65 +125,83 @@ export const rule = {
|
|
125
125
|
// Can't access to node.parent in GraphQL AST.Node, so pass as argument
|
126
126
|
parent, checkedFragmentSpreads = new Set()) {
|
127
127
|
const rawType = getBaseType(type);
|
128
|
-
|
129
|
-
|
130
|
-
if (!isObjectType && !isInterfaceType) {
|
131
|
-
return;
|
128
|
+
if (rawType instanceof GraphQLObjectType || rawType instanceof GraphQLInterfaceType) {
|
129
|
+
checkFields(rawType);
|
132
130
|
}
|
133
|
-
|
134
|
-
|
135
|
-
if (!hasIdFieldInType) {
|
136
|
-
return;
|
137
|
-
}
|
138
|
-
function hasIdField({ selections }) {
|
139
|
-
return selections.some(selection => {
|
140
|
-
if (selection.kind === Kind.FIELD) {
|
141
|
-
if (selection.alias && idNames.includes(selection.alias.value)) {
|
142
|
-
return true;
|
143
|
-
}
|
144
|
-
return idNames.includes(selection.name.value);
|
145
|
-
}
|
131
|
+
else if (rawType instanceof GraphQLUnionType) {
|
132
|
+
for (const selection of node.selections) {
|
146
133
|
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
if (foundSpread) {
|
152
|
-
const fragmentSpread = foundSpread.document;
|
153
|
-
checkedFragmentSpreads.add(fragmentSpread.name.value);
|
154
|
-
return hasIdField(fragmentSpread.selectionSet);
|
134
|
+
const types = rawType.getTypes();
|
135
|
+
const t = types.find(t => t.name === selection.typeCondition.name.value);
|
136
|
+
if (t) {
|
137
|
+
checkFields(t);
|
155
138
|
}
|
156
139
|
}
|
157
|
-
|
158
|
-
});
|
159
|
-
}
|
160
|
-
const hasId = hasIdField(node);
|
161
|
-
checkFragments(node);
|
162
|
-
if (hasId) {
|
163
|
-
return;
|
140
|
+
}
|
164
141
|
}
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
142
|
+
function checkFields(rawType) {
|
143
|
+
const fields = rawType.getFields();
|
144
|
+
const hasIdFieldInType = idNames.some(name => fields[name]);
|
145
|
+
if (!hasIdFieldInType) {
|
146
|
+
return;
|
147
|
+
}
|
148
|
+
function hasIdField({ selections }) {
|
149
|
+
return selections.some(selection => {
|
150
|
+
if (selection.kind === Kind.FIELD) {
|
151
|
+
if (selection.alias && idNames.includes(selection.alias.value)) {
|
152
|
+
return true;
|
153
|
+
}
|
154
|
+
return idNames.includes(selection.name.value);
|
155
|
+
}
|
156
|
+
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
157
|
+
return hasIdField(selection.selectionSet);
|
158
|
+
}
|
159
|
+
if (selection.kind === Kind.FRAGMENT_SPREAD) {
|
160
|
+
const [foundSpread] = siblings.getFragment(selection.name.value);
|
161
|
+
if (foundSpread) {
|
162
|
+
const fragmentSpread = foundSpread.document;
|
163
|
+
checkedFragmentSpreads.add(fragmentSpread.name.value);
|
164
|
+
return hasIdField(fragmentSpread.selectionSet);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
return false;
|
168
|
+
});
|
169
|
+
}
|
170
|
+
const hasId = hasIdField(node);
|
171
|
+
checkFragments(node);
|
172
|
+
if (hasId) {
|
173
|
+
return;
|
174
|
+
}
|
175
|
+
const pluralSuffix = idNames.length > 1 ? 's' : '';
|
176
|
+
const fieldName = englishJoinWords(idNames.map(name => `\`${(parent.alias || parent.name).value}.${name}\``));
|
177
|
+
const addition = checkedFragmentSpreads.size === 0
|
178
|
+
? ''
|
179
|
+
: ` or add to used fragment${checkedFragmentSpreads.size > 1 ? 's' : ''} ${englishJoinWords([...checkedFragmentSpreads].map(name => `\`${name}\``))}`;
|
180
|
+
const problem = {
|
181
|
+
loc,
|
182
|
+
messageId: RULE_ID,
|
183
|
+
data: {
|
184
|
+
pluralSuffix,
|
185
|
+
fieldName,
|
186
|
+
addition,
|
187
|
+
},
|
188
|
+
};
|
189
|
+
// Don't provide suggestions for selections in fragments as fragment can be in a separate file
|
190
|
+
if ('type' in node) {
|
191
|
+
problem.suggest = idNames.map(idName => ({
|
192
|
+
desc: `Add \`${idName}\` selection`,
|
193
|
+
fix: fixer => {
|
194
|
+
let insertNode = node.selections[0];
|
195
|
+
insertNode =
|
196
|
+
insertNode.kind === Kind.INLINE_FRAGMENT
|
197
|
+
? insertNode.selectionSet.selections[0]
|
198
|
+
: insertNode;
|
199
|
+
return fixer.insertTextBefore(insertNode, `${idName} `);
|
200
|
+
},
|
201
|
+
}));
|
202
|
+
}
|
203
|
+
context.report(problem);
|
185
204
|
}
|
186
|
-
context.report(problem);
|
187
205
|
}
|
188
206
|
return {
|
189
207
|
[selector](node) {
|