@graphql-eslint/eslint-plugin 3.0.1 → 3.2.0-alpha-2e742a6.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/README.md +57 -58
- package/docs/rules/known-fragment-names.md +9 -30
- package/docs/rules/no-undefined-variables.md +1 -1
- package/docs/rules/no-unreachable-types.md +0 -2
- package/docs/rules/no-unused-fields.md +0 -2
- package/docs/rules/no-unused-variables.md +1 -1
- package/index.js +185 -155
- package/index.mjs +187 -157
- package/package.json +1 -2
- package/rules/graphql-js-validation.d.ts +2 -5
package/index.mjs
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
import { Kind, validate, TokenKind, isScalarType, isNonNullType, isListType, isObjectType as isObjectType$1,
|
1
|
+
import { Kind, validate, TypeInfo, visitWithTypeInfo, visit, TokenKind, isScalarType, isNonNullType, isListType, isObjectType as isObjectType$1, GraphQLObjectType, GraphQLInterfaceType, isInterfaceType, Source, GraphQLError } from 'graphql';
|
2
2
|
import { validateSDL } from 'graphql/validation/validate';
|
3
|
-
import { processImport, parseImportLine } from '@graphql-tools/import';
|
4
3
|
import { statSync, existsSync, readFileSync } from 'fs';
|
5
|
-
import { dirname,
|
4
|
+
import { dirname, extname, basename, relative, resolve } from 'path';
|
6
5
|
import { asArray, parseGraphQLSDL } from '@graphql-tools/utils';
|
7
6
|
import lowerCase from 'lodash.lowercase';
|
8
7
|
import depthLimit from 'graphql-depth-limit';
|
@@ -329,34 +328,75 @@ function getLocation(loc, fieldName = '', offset) {
|
|
329
328
|
};
|
330
329
|
}
|
331
330
|
|
332
|
-
function
|
333
|
-
|
334
|
-
|
335
|
-
}
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
for (const error of validationErrors) {
|
342
|
-
const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
|
343
|
-
context.report({
|
344
|
-
loc: getLocation({ start: error.locations[0] }),
|
345
|
-
message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
|
346
|
-
});
|
347
|
-
}
|
348
|
-
}
|
349
|
-
catch (e) {
|
331
|
+
function validateDoc(sourceNode, context, schema, documentNode, rule) {
|
332
|
+
if (documentNode.definitions.length === 0) {
|
333
|
+
return;
|
334
|
+
}
|
335
|
+
try {
|
336
|
+
const validationErrors = schema
|
337
|
+
? validate(schema, documentNode, [rule])
|
338
|
+
: validateSDL(documentNode, null, [rule]);
|
339
|
+
for (const error of validationErrors) {
|
350
340
|
context.report({
|
351
|
-
|
352
|
-
message:
|
341
|
+
loc: getLocation({ start: error.locations[0] }),
|
342
|
+
message: error.message,
|
353
343
|
});
|
354
344
|
}
|
355
345
|
}
|
346
|
+
catch (e) {
|
347
|
+
context.report({
|
348
|
+
node: sourceNode,
|
349
|
+
message: e.message,
|
350
|
+
});
|
351
|
+
}
|
356
352
|
}
|
357
|
-
const
|
358
|
-
const
|
359
|
-
|
353
|
+
const getFragmentDefsAndFragmentSpreads = (schema, node) => {
|
354
|
+
const typeInfo = new TypeInfo(schema);
|
355
|
+
const fragmentDefs = new Set();
|
356
|
+
const fragmentSpreads = new Set();
|
357
|
+
const visitor = visitWithTypeInfo(typeInfo, {
|
358
|
+
FragmentDefinition(node) {
|
359
|
+
fragmentDefs.add(`${node.name.value}:${node.typeCondition.name.value}`);
|
360
|
+
},
|
361
|
+
FragmentSpread(node) {
|
362
|
+
const fieldDef = typeInfo.getFieldDef();
|
363
|
+
if (fieldDef) {
|
364
|
+
fragmentSpreads.add(`${node.name.value}:${typeInfo.getParentType().name}`);
|
365
|
+
}
|
366
|
+
},
|
367
|
+
});
|
368
|
+
visit(node, visitor);
|
369
|
+
return { fragmentDefs, fragmentSpreads };
|
370
|
+
};
|
371
|
+
const getMissingFragments = (schema, node) => {
|
372
|
+
const { fragmentDefs, fragmentSpreads } = getFragmentDefsAndFragmentSpreads(schema, node);
|
373
|
+
return [...fragmentSpreads].filter(name => !fragmentDefs.has(name));
|
374
|
+
};
|
375
|
+
const handleMissingFragments = ({ schema, node, ruleId, context }) => {
|
376
|
+
const missingFragments = getMissingFragments(schema, node);
|
377
|
+
if (missingFragments.length > 0) {
|
378
|
+
const siblings = requireSiblingsOperations(ruleId, context);
|
379
|
+
const fragmentsToAdd = [];
|
380
|
+
for (const missingFragment of missingFragments) {
|
381
|
+
const [fragmentName, fragmentTypeName] = missingFragment.split(':');
|
382
|
+
const fragments = siblings
|
383
|
+
.getFragment(fragmentName)
|
384
|
+
.map(source => source.document)
|
385
|
+
.filter(fragment => fragment.typeCondition.name.value === fragmentTypeName);
|
386
|
+
if (fragments.length > 1) {
|
387
|
+
// eslint-disable-next-line no-console
|
388
|
+
console.warn(`You have ${fragments.length} fragments that have same name ${fragmentName} and same type ${fragmentTypeName}. That can provoke unexpected result for "${ruleId}" rule.`);
|
389
|
+
}
|
390
|
+
fragmentsToAdd.push(fragments[0]);
|
391
|
+
}
|
392
|
+
if (fragmentsToAdd.length > 0) {
|
393
|
+
return {
|
394
|
+
kind: Kind.DOCUMENT,
|
395
|
+
definitions: [...node.definitions, ...fragmentsToAdd],
|
396
|
+
};
|
397
|
+
}
|
398
|
+
}
|
399
|
+
return node;
|
360
400
|
};
|
361
401
|
const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
362
402
|
var _a;
|
@@ -364,11 +404,11 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
|
364
404
|
try {
|
365
405
|
ruleFn = require(`graphql/validation/rules/${ruleName}Rule`)[`${ruleName}Rule`];
|
366
406
|
}
|
367
|
-
catch (
|
407
|
+
catch (_b) {
|
368
408
|
try {
|
369
409
|
ruleFn = require(`graphql/validation/rules/${ruleName}`)[`${ruleName}Rule`];
|
370
410
|
}
|
371
|
-
catch (
|
411
|
+
catch (_c) {
|
372
412
|
ruleFn = require('graphql/validation')[`${ruleName}Rule`];
|
373
413
|
}
|
374
414
|
}
|
@@ -390,30 +430,25 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
|
390
430
|
Document(node) {
|
391
431
|
if (!ruleFn) {
|
392
432
|
// eslint-disable-next-line no-console
|
393
|
-
console.warn(`You rule "${name}" depends on a GraphQL validation rule
|
433
|
+
console.warn(`You rule "${name}" depends on a GraphQL validation rule "${ruleName}" but it's not available in the "graphql-js" version you are using. Skipping...`);
|
394
434
|
return;
|
395
435
|
}
|
396
436
|
const schema = requiresSchema ? requireGraphQLSchemaFromContext(name, context) : null;
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
437
|
+
const documentNode = node.rawNode();
|
438
|
+
validateDoc(node, context, schema, getDocumentNode
|
439
|
+
? getDocumentNode({
|
440
|
+
schema,
|
441
|
+
node: documentNode,
|
442
|
+
ruleId: name,
|
443
|
+
context,
|
444
|
+
})
|
445
|
+
: documentNode, ruleFn);
|
403
446
|
},
|
404
447
|
};
|
405
448
|
},
|
406
449
|
},
|
407
450
|
};
|
408
451
|
};
|
409
|
-
const importFiles = (context) => {
|
410
|
-
const code = context.getSourceCode().text;
|
411
|
-
if (!isGraphQLImportFile(code)) {
|
412
|
-
return null;
|
413
|
-
}
|
414
|
-
// Import documents because file contains '#import' comments
|
415
|
-
return processImport(context.getFilename());
|
416
|
-
};
|
417
452
|
const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
|
418
453
|
category: 'Operations',
|
419
454
|
description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
|
@@ -432,14 +467,15 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
432
467
|
}), validationToRule('known-fragment-names', 'KnownFragmentNames', {
|
433
468
|
category: 'Operations',
|
434
469
|
description: `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.`,
|
470
|
+
requiresSiblings: true,
|
435
471
|
examples: [
|
436
472
|
{
|
437
|
-
title: 'Incorrect
|
473
|
+
title: 'Incorrect',
|
438
474
|
code: /* GraphQL */ `
|
439
475
|
query {
|
440
476
|
user {
|
441
477
|
id
|
442
|
-
...UserFields
|
478
|
+
...UserFields # fragment not defined in the document
|
443
479
|
}
|
444
480
|
}
|
445
481
|
`,
|
@@ -461,42 +497,24 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
461
497
|
`,
|
462
498
|
},
|
463
499
|
{
|
464
|
-
title: 'Correct (
|
500
|
+
title: 'Correct (`UserFields` fragment located in a separate file)',
|
465
501
|
code: /* GraphQL */ `
|
466
|
-
#
|
467
|
-
|
502
|
+
# user.gql
|
468
503
|
query {
|
469
504
|
user {
|
470
505
|
id
|
471
506
|
...UserFields
|
472
507
|
}
|
473
508
|
}
|
474
|
-
`,
|
475
|
-
},
|
476
|
-
{
|
477
|
-
title: "False positive case\n\nFor extracting documents from code under the hood we use [graphql-tag-pluck](https://graphql-tools.com/docs/graphql-tag-pluck) that [don't support string interpolation](https://stackoverflow.com/questions/62749847/graphql-codegen-dynamic-fields-with-interpolation/62751311#62751311) for this moment.",
|
478
|
-
code: `
|
479
|
-
const USER_FIELDS = gql\`
|
480
|
-
fragment UserFields on User {
|
481
|
-
id
|
482
|
-
}
|
483
|
-
\`
|
484
|
-
|
485
|
-
const GET_USER = /* GraphQL */ \`
|
486
|
-
# eslint @graphql-eslint/known-fragment-names: 'error'
|
487
|
-
|
488
|
-
query User {
|
489
|
-
user {
|
490
|
-
...UserFields
|
491
|
-
}
|
492
|
-
}
|
493
509
|
|
494
|
-
|
495
|
-
|
496
|
-
|
510
|
+
# user-fields.gql
|
511
|
+
fragment UserFields on User {
|
512
|
+
id
|
513
|
+
}
|
514
|
+
`,
|
497
515
|
},
|
498
516
|
],
|
499
|
-
},
|
517
|
+
}, handleMissingFragments), validationToRule('known-type-names', 'KnownTypeNames', {
|
500
518
|
category: ['Schema', 'Operations'],
|
501
519
|
description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
|
502
520
|
}), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
|
@@ -512,40 +530,47 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
512
530
|
}), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
|
513
531
|
category: 'Operations',
|
514
532
|
description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
|
515
|
-
|
533
|
+
requiresSiblings: true,
|
534
|
+
}, handleMissingFragments), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
|
516
535
|
category: 'Operations',
|
517
536
|
description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
|
518
537
|
requiresSiblings: true,
|
519
|
-
}, context => {
|
520
|
-
const siblings = requireSiblingsOperations(
|
521
|
-
const
|
522
|
-
|
523
|
-
|
524
|
-
filePath
|
525
|
-
|
526
|
-
}));
|
527
|
-
const getParentNode = (
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
.filter(isGraphQLImportFile)
|
532
|
-
.map(line => parseImportLine(line.replace('#', '')))
|
533
|
-
.some(o => filePath === join(dirname(docFilePath), o.from));
|
534
|
-
if (!isFileImported) {
|
535
|
-
continue;
|
536
|
-
}
|
537
|
-
// Import first file that import this file
|
538
|
-
const document = processImport(docFilePath);
|
539
|
-
// Import most top file that import this file
|
540
|
-
return getParentNode(docFilePath) || document;
|
538
|
+
}, ({ context, node, schema, ruleId }) => {
|
539
|
+
const siblings = requireSiblingsOperations(ruleId, context);
|
540
|
+
const filePathForDocumentsMap = [...siblings.getOperations(), ...siblings.getFragments()].reduce((map, { filePath, document }) => {
|
541
|
+
var _a;
|
542
|
+
(_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
|
543
|
+
map[filePath].push(document);
|
544
|
+
return map;
|
545
|
+
}, Object.create(null));
|
546
|
+
const getParentNode = (currentFilePath, node) => {
|
547
|
+
const { fragmentDefs } = getFragmentDefsAndFragmentSpreads(schema, node);
|
548
|
+
if (fragmentDefs.size === 0) {
|
549
|
+
return node;
|
541
550
|
}
|
542
|
-
|
551
|
+
// skip iteration over documents for current filepath
|
552
|
+
delete filePathForDocumentsMap[currentFilePath];
|
553
|
+
for (const [filePath, documents] of Object.entries(filePathForDocumentsMap)) {
|
554
|
+
const missingFragments = getMissingFragments(schema, {
|
555
|
+
kind: Kind.DOCUMENT,
|
556
|
+
definitions: documents,
|
557
|
+
});
|
558
|
+
const isCurrentFileImportFragment = missingFragments.some(fragment => fragmentDefs.has(fragment));
|
559
|
+
if (isCurrentFileImportFragment) {
|
560
|
+
return getParentNode(filePath, {
|
561
|
+
kind: Kind.DOCUMENT,
|
562
|
+
definitions: [...node.definitions, ...documents],
|
563
|
+
});
|
564
|
+
}
|
565
|
+
}
|
566
|
+
return node;
|
543
567
|
};
|
544
|
-
return getParentNode(context.getFilename());
|
568
|
+
return getParentNode(context.getFilename(), node);
|
545
569
|
}), validationToRule('no-unused-variables', 'NoUnusedVariables', {
|
546
570
|
category: 'Operations',
|
547
571
|
description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
|
548
|
-
|
572
|
+
requiresSiblings: true,
|
573
|
+
}, handleMissingFragments), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
|
549
574
|
category: 'Operations',
|
550
575
|
description: `A selection set is only valid if all fields (including spreading any fragments) either correspond to distinct response names or can be merged without ambiguity.`,
|
551
576
|
}), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
|
@@ -1839,7 +1864,7 @@ const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
|
|
1839
1864
|
const rule$9 = {
|
1840
1865
|
meta: {
|
1841
1866
|
messages: {
|
1842
|
-
[HASHTAG_COMMENT]:
|
1867
|
+
[HASHTAG_COMMENT]: `Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.`,
|
1843
1868
|
},
|
1844
1869
|
docs: {
|
1845
1870
|
description: 'Requires to use `"""` or `"` for adding a GraphQL description instead of `#`.\nAllows to use hashtag for comments, as long as it\'s not attached to an AST definition.',
|
@@ -1886,14 +1911,15 @@ const rule$9 = {
|
|
1886
1911
|
schema: [],
|
1887
1912
|
},
|
1888
1913
|
create(context) {
|
1914
|
+
const selector = `${Kind.DOCUMENT}[definitions.0.kind!=/^(${Kind.OPERATION_DEFINITION}|${Kind.FRAGMENT_DEFINITION})$/]`;
|
1889
1915
|
return {
|
1890
|
-
|
1916
|
+
[selector](node) {
|
1891
1917
|
const rawNode = node.rawNode();
|
1892
1918
|
let token = rawNode.loc.startToken;
|
1893
1919
|
while (token !== null) {
|
1894
1920
|
const { kind, prev, next, value, line, column } = token;
|
1895
1921
|
if (kind === TokenKind.COMMENT && prev && next) {
|
1896
|
-
const isEslintComment = value.
|
1922
|
+
const isEslintComment = value.trimStart().startsWith('eslint');
|
1897
1923
|
const linesAfter = next.line - line;
|
1898
1924
|
if (!isEslintComment && line !== prev.line && next.kind === TokenKind.NAME && linesAfter < 2) {
|
1899
1925
|
context.report({
|
@@ -2100,7 +2126,22 @@ const rule$c = {
|
|
2100
2126
|
};
|
2101
2127
|
|
2102
2128
|
const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
|
2103
|
-
const
|
2129
|
+
const RULE_ID = 'no-unreachable-types';
|
2130
|
+
const KINDS = [
|
2131
|
+
Kind.DIRECTIVE_DEFINITION,
|
2132
|
+
Kind.OBJECT_TYPE_DEFINITION,
|
2133
|
+
Kind.OBJECT_TYPE_EXTENSION,
|
2134
|
+
Kind.INTERFACE_TYPE_DEFINITION,
|
2135
|
+
Kind.INTERFACE_TYPE_EXTENSION,
|
2136
|
+
Kind.SCALAR_TYPE_DEFINITION,
|
2137
|
+
Kind.SCALAR_TYPE_EXTENSION,
|
2138
|
+
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
2139
|
+
Kind.INPUT_OBJECT_TYPE_EXTENSION,
|
2140
|
+
Kind.UNION_TYPE_DEFINITION,
|
2141
|
+
Kind.UNION_TYPE_EXTENSION,
|
2142
|
+
Kind.ENUM_TYPE_DEFINITION,
|
2143
|
+
Kind.ENUM_TYPE_EXTENSION,
|
2144
|
+
];
|
2104
2145
|
const rule$d = {
|
2105
2146
|
meta: {
|
2106
2147
|
messages: {
|
@@ -2109,7 +2150,7 @@ const rule$d = {
|
|
2109
2150
|
docs: {
|
2110
2151
|
description: `Requires all types to be reachable at some level by root level fields.`,
|
2111
2152
|
category: 'Schema',
|
2112
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${
|
2153
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
|
2113
2154
|
requiresSchema: true,
|
2114
2155
|
examples: [
|
2115
2156
|
{
|
@@ -2141,43 +2182,36 @@ const rule$d = {
|
|
2141
2182
|
],
|
2142
2183
|
recommended: true,
|
2143
2184
|
},
|
2144
|
-
fixable: 'code',
|
2145
2185
|
type: 'suggestion',
|
2146
2186
|
schema: [],
|
2187
|
+
hasSuggestions: true,
|
2147
2188
|
},
|
2148
2189
|
create(context) {
|
2149
|
-
const reachableTypes = requireReachableTypesFromContext(
|
2150
|
-
|
2151
|
-
const typeName = node.name.value;
|
2152
|
-
if (!reachableTypes.has(typeName)) {
|
2153
|
-
context.report({
|
2154
|
-
loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
|
2155
|
-
messageId: UNREACHABLE_TYPE,
|
2156
|
-
data: { typeName },
|
2157
|
-
fix: fixer => fixer.remove(node),
|
2158
|
-
});
|
2159
|
-
}
|
2160
|
-
}
|
2190
|
+
const reachableTypes = requireReachableTypesFromContext(RULE_ID, context);
|
2191
|
+
const selector = KINDS.join(',');
|
2161
2192
|
return {
|
2162
|
-
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
|
2168
|
-
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2173
|
-
|
2174
|
-
|
2193
|
+
[selector](node) {
|
2194
|
+
const typeName = node.name.value;
|
2195
|
+
if (!reachableTypes.has(typeName)) {
|
2196
|
+
context.report({
|
2197
|
+
loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
|
2198
|
+
messageId: UNREACHABLE_TYPE,
|
2199
|
+
data: { typeName },
|
2200
|
+
suggest: [
|
2201
|
+
{
|
2202
|
+
desc: `Remove ${typeName}`,
|
2203
|
+
fix: fixer => fixer.remove(node),
|
2204
|
+
},
|
2205
|
+
],
|
2206
|
+
});
|
2207
|
+
}
|
2208
|
+
},
|
2175
2209
|
};
|
2176
2210
|
},
|
2177
2211
|
};
|
2178
2212
|
|
2179
2213
|
const UNUSED_FIELD = 'UNUSED_FIELD';
|
2180
|
-
const
|
2214
|
+
const RULE_ID$1 = 'no-unused-fields';
|
2181
2215
|
const rule$e = {
|
2182
2216
|
meta: {
|
2183
2217
|
messages: {
|
@@ -2186,7 +2220,7 @@ const rule$e = {
|
|
2186
2220
|
docs: {
|
2187
2221
|
description: `Requires all fields to be used at some level by siblings operations.`,
|
2188
2222
|
category: 'Schema',
|
2189
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${
|
2223
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$1}.md`,
|
2190
2224
|
requiresSiblings: true,
|
2191
2225
|
requiresSchema: true,
|
2192
2226
|
examples: [
|
@@ -2233,12 +2267,12 @@ const rule$e = {
|
|
2233
2267
|
},
|
2234
2268
|
],
|
2235
2269
|
},
|
2236
|
-
fixable: 'code',
|
2237
2270
|
type: 'suggestion',
|
2238
2271
|
schema: [],
|
2272
|
+
hasSuggestions: true,
|
2239
2273
|
},
|
2240
2274
|
create(context) {
|
2241
|
-
const usedFields = requireUsedFieldsFromContext(
|
2275
|
+
const usedFields = requireUsedFieldsFromContext(RULE_ID$1, context);
|
2242
2276
|
return {
|
2243
2277
|
FieldDefinition(node) {
|
2244
2278
|
var _a;
|
@@ -2252,22 +2286,18 @@ const rule$e = {
|
|
2252
2286
|
loc: getLocation(node.loc, fieldName),
|
2253
2287
|
messageId: UNUSED_FIELD,
|
2254
2288
|
data: { fieldName },
|
2255
|
-
|
2256
|
-
|
2257
|
-
|
2258
|
-
|
2259
|
-
|
2260
|
-
|
2261
|
-
|
2262
|
-
|
2263
|
-
|
2264
|
-
|
2265
|
-
|
2266
|
-
|
2267
|
-
}
|
2268
|
-
// Remove whitespace before token
|
2269
|
-
return fixer.removeRange([tokenBefore.range[1], node.range[1]]);
|
2270
|
-
},
|
2289
|
+
suggest: [
|
2290
|
+
{
|
2291
|
+
desc: `Remove "${fieldName}" field`,
|
2292
|
+
fix(fixer) {
|
2293
|
+
const sourceCode = context.getSourceCode();
|
2294
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
2295
|
+
const tokenAfter = sourceCode.getTokenAfter(node);
|
2296
|
+
const isEmptyType = tokenBefore.type === '{' && tokenAfter.type === '}';
|
2297
|
+
return isEmptyType ? fixer.remove(node.parent) : fixer.remove(node);
|
2298
|
+
},
|
2299
|
+
},
|
2300
|
+
],
|
2271
2301
|
});
|
2272
2302
|
},
|
2273
2303
|
};
|
@@ -2630,14 +2660,14 @@ const rule$h = {
|
|
2630
2660
|
},
|
2631
2661
|
};
|
2632
2662
|
|
2633
|
-
const RULE_NAME
|
2663
|
+
const RULE_NAME = 'require-field-of-type-query-in-mutation-result';
|
2634
2664
|
const rule$i = {
|
2635
2665
|
meta: {
|
2636
2666
|
type: 'suggestion',
|
2637
2667
|
docs: {
|
2638
2668
|
category: 'Schema',
|
2639
2669
|
description: 'Allow the client in one round-trip not only to call mutation but also to get a wagon of data to update their application.\n> Currently, no errors are reported for result type `union`, `interface` and `scalar`.',
|
2640
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME
|
2670
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME}.md`,
|
2641
2671
|
requiresSchema: true,
|
2642
2672
|
examples: [
|
2643
2673
|
{
|
@@ -2672,7 +2702,7 @@ const rule$i = {
|
|
2672
2702
|
schema: [],
|
2673
2703
|
},
|
2674
2704
|
create(context) {
|
2675
|
-
const schema = requireGraphQLSchemaFromContext(RULE_NAME
|
2705
|
+
const schema = requireGraphQLSchemaFromContext(RULE_NAME, context);
|
2676
2706
|
const mutationType = schema.getMutationType();
|
2677
2707
|
const queryType = schema.getQueryType();
|
2678
2708
|
if (!mutationType || !queryType) {
|
@@ -3215,7 +3245,7 @@ const rule$l = {
|
|
3215
3245
|
},
|
3216
3246
|
};
|
3217
3247
|
|
3218
|
-
const RULE_NAME$
|
3248
|
+
const RULE_NAME$1 = 'unique-fragment-name';
|
3219
3249
|
const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
|
3220
3250
|
const checkNode = (context, node, ruleName, messageId) => {
|
3221
3251
|
const documentName = node.name.value;
|
@@ -3247,7 +3277,7 @@ const rule$m = {
|
|
3247
3277
|
docs: {
|
3248
3278
|
category: 'Operations',
|
3249
3279
|
description: `Enforce unique fragment names across your project.`,
|
3250
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$
|
3280
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$1}.md`,
|
3251
3281
|
requiresSiblings: true,
|
3252
3282
|
examples: [
|
3253
3283
|
{
|
@@ -3292,13 +3322,13 @@ const rule$m = {
|
|
3292
3322
|
create(context) {
|
3293
3323
|
return {
|
3294
3324
|
FragmentDefinition(node) {
|
3295
|
-
checkNode(context, node, RULE_NAME$
|
3325
|
+
checkNode(context, node, RULE_NAME$1, UNIQUE_FRAGMENT_NAME);
|
3296
3326
|
},
|
3297
3327
|
};
|
3298
3328
|
},
|
3299
3329
|
};
|
3300
3330
|
|
3301
|
-
const RULE_NAME$
|
3331
|
+
const RULE_NAME$2 = 'unique-operation-name';
|
3302
3332
|
const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
|
3303
3333
|
const rule$n = {
|
3304
3334
|
meta: {
|
@@ -3306,7 +3336,7 @@ const rule$n = {
|
|
3306
3336
|
docs: {
|
3307
3337
|
category: 'Operations',
|
3308
3338
|
description: `Enforce unique operation names across your project.`,
|
3309
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$
|
3339
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
|
3310
3340
|
requiresSiblings: true,
|
3311
3341
|
examples: [
|
3312
3342
|
{
|
@@ -3355,7 +3385,7 @@ const rule$n = {
|
|
3355
3385
|
create(context) {
|
3356
3386
|
return {
|
3357
3387
|
'OperationDefinition[name!=undefined]'(node) {
|
3358
|
-
checkNode(context, node, RULE_NAME$
|
3388
|
+
checkNode(context, node, RULE_NAME$2, UNIQUE_OPERATION_NAME);
|
3359
3389
|
},
|
3360
3390
|
};
|
3361
3391
|
},
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@graphql-eslint/eslint-plugin",
|
3
|
-
"version": "3.0.
|
3
|
+
"version": "3.2.0-alpha-2e742a6.0",
|
4
4
|
"sideEffects": false,
|
5
5
|
"peerDependencies": {
|
6
6
|
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
@@ -9,7 +9,6 @@
|
|
9
9
|
"@babel/code-frame": "7.16.0",
|
10
10
|
"@graphql-tools/code-file-loader": "7.2.2",
|
11
11
|
"@graphql-tools/graphql-tag-pluck": "7.1.4",
|
12
|
-
"@graphql-tools/import": "6.6.1",
|
13
12
|
"@graphql-tools/utils": "8.5.3",
|
14
13
|
"graphql-config": "4.1.0",
|
15
14
|
"graphql-depth-limit": "1.1.0",
|
@@ -1,5 +1,2 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
import { GraphQLESTreeNode } from '../estree-parser';
|
4
|
-
export declare function validateDoc(sourceNode: GraphQLESTreeNode<ASTNode>, context: GraphQLESLintRuleContext, schema: GraphQLSchema | null, documentNode: DocumentNode, rules: ReadonlyArray<ValidationRule>, ruleName?: string | null): void;
|
5
|
-
export declare const GRAPHQL_JS_VALIDATIONS: Record<string, GraphQLESLintRule<any[], false>>;
|
1
|
+
import { GraphQLESLintRule } from '../types';
|
2
|
+
export declare const GRAPHQL_JS_VALIDATIONS: Record<string, GraphQLESLintRule>;
|