@graphql-eslint/eslint-plugin 3.2.0 → 3.3.0-alpha-db2c2cb.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/known-fragment-names.md +9 -30
- package/docs/rules/no-undefined-variables.md +1 -1
- package/docs/rules/no-unused-variables.md +1 -1
- package/index.js +132 -93
- package/index.mjs +134 -95
- package/package.json +8 -2
- package/rules/graphql-js-validation.d.ts +2 -5
- package/types.d.ts +2 -2
@@ -5,7 +5,7 @@
|
|
5
5
|
- Category: `Operations`
|
6
6
|
- Rule name: `@graphql-eslint/known-fragment-names`
|
7
7
|
- Requires GraphQL Schema: `true` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
|
8
|
-
- Requires GraphQL Operations: `
|
8
|
+
- Requires GraphQL Operations: `true` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)
|
9
9
|
|
10
10
|
A GraphQL document is only valid if all `...Fragment` fragment spreads refer to fragments defined in the same document.
|
11
11
|
|
@@ -13,7 +13,7 @@ A GraphQL document is only valid if all `...Fragment` fragment spreads refer to
|
|
13
13
|
|
14
14
|
## Usage Examples
|
15
15
|
|
16
|
-
### Incorrect
|
16
|
+
### Incorrect
|
17
17
|
|
18
18
|
```graphql
|
19
19
|
# eslint @graphql-eslint/known-fragment-names: 'error'
|
@@ -21,7 +21,7 @@ A GraphQL document is only valid if all `...Fragment` fragment spreads refer to
|
|
21
21
|
query {
|
22
22
|
user {
|
23
23
|
id
|
24
|
-
...UserFields
|
24
|
+
...UserFields # fragment not defined in the document
|
25
25
|
}
|
26
26
|
}
|
27
27
|
```
|
@@ -44,44 +44,23 @@ query {
|
|
44
44
|
}
|
45
45
|
```
|
46
46
|
|
47
|
-
### Correct (
|
47
|
+
### Correct (`UserFields` fragment located in a separate file)
|
48
48
|
|
49
49
|
```graphql
|
50
50
|
# eslint @graphql-eslint/known-fragment-names: 'error'
|
51
51
|
|
52
|
-
#
|
53
|
-
|
52
|
+
# user.gql
|
54
53
|
query {
|
55
54
|
user {
|
56
55
|
id
|
57
56
|
...UserFields
|
58
57
|
}
|
59
58
|
}
|
60
|
-
```
|
61
|
-
|
62
|
-
### False positive case
|
63
59
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
fragment UserFields on User {
|
69
|
-
id
|
70
|
-
}
|
71
|
-
`
|
72
|
-
|
73
|
-
const GET_USER = /* GraphQL */ `
|
74
|
-
# eslint @graphql-eslint/known-fragment-names: 'error'
|
75
|
-
|
76
|
-
query User {
|
77
|
-
user {
|
78
|
-
...UserFields
|
79
|
-
}
|
80
|
-
}
|
81
|
-
|
82
|
-
# Will give false positive error 'Unknown fragment "UserFields"'
|
83
|
-
${USER_FIELDS}
|
84
|
-
`
|
60
|
+
# user-fields.gql
|
61
|
+
fragment UserFields on User {
|
62
|
+
id
|
63
|
+
}
|
85
64
|
```
|
86
65
|
|
87
66
|
## Resources
|
@@ -5,7 +5,7 @@
|
|
5
5
|
- Category: `Operations`
|
6
6
|
- Rule name: `@graphql-eslint/no-undefined-variables`
|
7
7
|
- Requires GraphQL Schema: `true` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
|
8
|
-
- Requires GraphQL Operations: `
|
8
|
+
- Requires GraphQL Operations: `true` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)
|
9
9
|
|
10
10
|
A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.
|
11
11
|
|
@@ -5,7 +5,7 @@
|
|
5
5
|
- Category: `Operations`
|
6
6
|
- Rule name: `@graphql-eslint/no-unused-variables`
|
7
7
|
- Requires GraphQL Schema: `true` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
|
8
|
-
- Requires GraphQL Operations: `
|
8
|
+
- Requires GraphQL Operations: `true` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)
|
9
9
|
|
10
10
|
A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.
|
11
11
|
|
package/index.js
CHANGED
@@ -6,7 +6,6 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau
|
|
6
6
|
|
7
7
|
const graphql = require('graphql');
|
8
8
|
const validate = require('graphql/validation/validate');
|
9
|
-
const _import = require('@graphql-tools/import');
|
10
9
|
const fs = require('fs');
|
11
10
|
const path = require('path');
|
12
11
|
const utils = require('@graphql-tools/utils');
|
@@ -327,14 +326,14 @@ function getLocation(loc, fieldName = '', offset) {
|
|
327
326
|
};
|
328
327
|
}
|
329
328
|
|
330
|
-
function
|
329
|
+
function validateDocument(sourceNode, context, schema, documentNode, rule) {
|
331
330
|
if (documentNode.definitions.length === 0) {
|
332
331
|
return;
|
333
332
|
}
|
334
333
|
try {
|
335
334
|
const validationErrors = schema
|
336
|
-
? graphql.validate(schema, documentNode,
|
337
|
-
: validate.validateSDL(documentNode, null,
|
335
|
+
? graphql.validate(schema, documentNode, [rule])
|
336
|
+
: validate.validateSDL(documentNode, null, [rule]);
|
338
337
|
for (const error of validationErrors) {
|
339
338
|
/*
|
340
339
|
* TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
|
@@ -364,34 +363,79 @@ function validateDoc(sourceNode, context, schema, documentNode, rules) {
|
|
364
363
|
});
|
365
364
|
}
|
366
365
|
}
|
367
|
-
const
|
368
|
-
const
|
369
|
-
|
366
|
+
const getFragmentDefsAndFragmentSpreads = (schema, node) => {
|
367
|
+
const typeInfo = new graphql.TypeInfo(schema);
|
368
|
+
const fragmentDefs = new Set();
|
369
|
+
const fragmentSpreads = new Set();
|
370
|
+
const visitor = graphql.visitWithTypeInfo(typeInfo, {
|
371
|
+
FragmentDefinition(node) {
|
372
|
+
fragmentDefs.add(`${node.name.value}:${node.typeCondition.name.value}`);
|
373
|
+
},
|
374
|
+
FragmentSpread(node) {
|
375
|
+
const parentType = typeInfo.getParentType();
|
376
|
+
if (parentType) {
|
377
|
+
fragmentSpreads.add(`${node.name.value}:${parentType.name}`);
|
378
|
+
}
|
379
|
+
},
|
380
|
+
});
|
381
|
+
graphql.visit(node, visitor);
|
382
|
+
return { fragmentDefs, fragmentSpreads };
|
370
383
|
};
|
371
|
-
const
|
372
|
-
|
384
|
+
const getMissingFragments = (schema, node) => {
|
385
|
+
const { fragmentDefs, fragmentSpreads } = getFragmentDefsAndFragmentSpreads(schema, node);
|
386
|
+
return [...fragmentSpreads].filter(name => !fragmentDefs.has(name));
|
387
|
+
};
|
388
|
+
const handleMissingFragments = ({ ruleId, context, schema, node }) => {
|
389
|
+
const missingFragments = getMissingFragments(schema, node);
|
390
|
+
if (missingFragments.length > 0) {
|
391
|
+
const siblings = requireSiblingsOperations(ruleId, context);
|
392
|
+
const fragmentsToAdd = [];
|
393
|
+
for (const missingFragment of missingFragments) {
|
394
|
+
const [fragmentName, fragmentTypeName] = missingFragment.split(':');
|
395
|
+
const [foundFragment] = siblings
|
396
|
+
.getFragment(fragmentName)
|
397
|
+
.map(source => source.document)
|
398
|
+
.filter(fragment => fragment.typeCondition.name.value === fragmentTypeName);
|
399
|
+
if (foundFragment) {
|
400
|
+
fragmentsToAdd.push(foundFragment);
|
401
|
+
}
|
402
|
+
}
|
403
|
+
if (fragmentsToAdd.length > 0) {
|
404
|
+
// recall fn to make sure to add fragments inside fragments
|
405
|
+
return handleMissingFragments({
|
406
|
+
ruleId,
|
407
|
+
context,
|
408
|
+
schema,
|
409
|
+
node: {
|
410
|
+
kind: graphql.Kind.DOCUMENT,
|
411
|
+
definitions: [...node.definitions, ...fragmentsToAdd],
|
412
|
+
},
|
413
|
+
});
|
414
|
+
}
|
415
|
+
}
|
416
|
+
return node;
|
417
|
+
};
|
418
|
+
const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
|
373
419
|
let ruleFn = null;
|
374
420
|
try {
|
375
421
|
ruleFn = require(`graphql/validation/rules/${ruleName}Rule`)[`${ruleName}Rule`];
|
376
422
|
}
|
377
|
-
catch (
|
423
|
+
catch (_a) {
|
378
424
|
try {
|
379
425
|
ruleFn = require(`graphql/validation/rules/${ruleName}`)[`${ruleName}Rule`];
|
380
426
|
}
|
381
|
-
catch (
|
427
|
+
catch (_b) {
|
382
428
|
ruleFn = require('graphql/validation')[`${ruleName}Rule`];
|
383
429
|
}
|
384
430
|
}
|
385
|
-
const requiresSchema = (_a = docs.requiresSchema) !== null && _a !== void 0 ? _a : true;
|
386
431
|
return {
|
387
|
-
[
|
432
|
+
[ruleId]: {
|
388
433
|
meta: {
|
389
434
|
docs: {
|
390
435
|
recommended: true,
|
391
436
|
...docs,
|
392
437
|
graphQLJSRuleName: ruleName,
|
393
|
-
|
394
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
|
438
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${ruleId}.md`,
|
395
439
|
description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function. [You can find its source code here](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/${ruleName}Rule.ts).`,
|
396
440
|
},
|
397
441
|
},
|
@@ -400,56 +444,53 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
|
400
444
|
Document(node) {
|
401
445
|
if (!ruleFn) {
|
402
446
|
// eslint-disable-next-line no-console
|
403
|
-
console.warn(`You rule "${
|
447
|
+
console.warn(`You rule "${ruleId}" depends on a GraphQL validation rule "${ruleName}" but it's not available in the "graphql-js" version you are using. Skipping...`);
|
404
448
|
return;
|
405
449
|
}
|
406
|
-
const schema = requiresSchema ? requireGraphQLSchemaFromContext(
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
}
|
412
|
-
validateDoc(node, context, schema, documentNode || node.rawNode(), [ruleFn]);
|
450
|
+
const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
|
451
|
+
const documentNode = getDocumentNode
|
452
|
+
? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
|
453
|
+
: node.rawNode();
|
454
|
+
validateDocument(node, context, schema, documentNode, ruleFn);
|
413
455
|
},
|
414
456
|
};
|
415
457
|
},
|
416
458
|
},
|
417
459
|
};
|
418
460
|
};
|
419
|
-
const importFiles = (context) => {
|
420
|
-
const code = context.getSourceCode().text;
|
421
|
-
if (!isGraphQLImportFile(code)) {
|
422
|
-
return null;
|
423
|
-
}
|
424
|
-
// Import documents because file contains '#import' comments
|
425
|
-
return _import.processImport(context.getFilename());
|
426
|
-
};
|
427
461
|
const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
|
428
462
|
category: 'Operations',
|
429
463
|
description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
|
464
|
+
requiresSchema: true,
|
430
465
|
}), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
|
431
466
|
category: 'Operations',
|
432
467
|
description: 'A GraphQL document is only valid if all fields selected are defined by the parent type, or are an allowed meta field such as `__typename`.',
|
468
|
+
requiresSchema: true,
|
433
469
|
}), validationToRule('fragments-on-composite-type', 'FragmentsOnCompositeTypes', {
|
434
470
|
category: 'Operations',
|
435
471
|
description: `Fragments use a type condition to determine if they apply, since fragments can only be spread into a composite type (object, interface, or union), the type condition must also be a composite type.`,
|
472
|
+
requiresSchema: true,
|
436
473
|
}), validationToRule('known-argument-names', 'KnownArgumentNames', {
|
437
474
|
category: ['Schema', 'Operations'],
|
438
475
|
description: `A GraphQL field is only valid if all supplied arguments are defined by that field.`,
|
476
|
+
requiresSchema: true,
|
439
477
|
}), validationToRule('known-directives', 'KnownDirectives', {
|
440
478
|
category: ['Schema', 'Operations'],
|
441
479
|
description: `A GraphQL document is only valid if all \`@directives\` are known by the schema and legally positioned.`,
|
480
|
+
requiresSchema: true,
|
442
481
|
}), validationToRule('known-fragment-names', 'KnownFragmentNames', {
|
443
482
|
category: 'Operations',
|
444
483
|
description: `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.`,
|
484
|
+
requiresSchema: true,
|
485
|
+
requiresSiblings: true,
|
445
486
|
examples: [
|
446
487
|
{
|
447
|
-
title: 'Incorrect
|
488
|
+
title: 'Incorrect',
|
448
489
|
code: /* GraphQL */ `
|
449
490
|
query {
|
450
491
|
user {
|
451
492
|
id
|
452
|
-
...UserFields
|
493
|
+
...UserFields # fragment not defined in the document
|
453
494
|
}
|
454
495
|
}
|
455
496
|
`,
|
@@ -471,153 +512,151 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
471
512
|
`,
|
472
513
|
},
|
473
514
|
{
|
474
|
-
title: 'Correct (
|
515
|
+
title: 'Correct (`UserFields` fragment located in a separate file)',
|
475
516
|
code: /* GraphQL */ `
|
476
|
-
#
|
477
|
-
|
517
|
+
# user.gql
|
478
518
|
query {
|
479
519
|
user {
|
480
520
|
id
|
481
521
|
...UserFields
|
482
522
|
}
|
483
523
|
}
|
484
|
-
`,
|
485
|
-
},
|
486
|
-
{
|
487
|
-
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.",
|
488
|
-
code: `
|
489
|
-
const USER_FIELDS = gql\`
|
490
|
-
fragment UserFields on User {
|
491
|
-
id
|
492
|
-
}
|
493
|
-
\`
|
494
|
-
|
495
|
-
const GET_USER = /* GraphQL */ \`
|
496
|
-
# eslint @graphql-eslint/known-fragment-names: 'error'
|
497
|
-
|
498
|
-
query User {
|
499
|
-
user {
|
500
|
-
...UserFields
|
501
|
-
}
|
502
|
-
}
|
503
524
|
|
504
|
-
|
505
|
-
|
506
|
-
|
525
|
+
# user-fields.gql
|
526
|
+
fragment UserFields on User {
|
527
|
+
id
|
528
|
+
}
|
529
|
+
`,
|
507
530
|
},
|
508
531
|
],
|
509
|
-
},
|
532
|
+
}, handleMissingFragments), validationToRule('known-type-names', 'KnownTypeNames', {
|
510
533
|
category: ['Schema', 'Operations'],
|
511
534
|
description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
|
535
|
+
requiresSchema: true,
|
512
536
|
}), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
|
513
537
|
category: 'Operations',
|
514
538
|
description: `A GraphQL document is only valid if when it contains an anonymous operation (the query short-hand) that it contains only that one operation definition.`,
|
539
|
+
requiresSchema: true,
|
515
540
|
}), validationToRule('lone-schema-definition', 'LoneSchemaDefinition', {
|
516
541
|
category: 'Schema',
|
517
542
|
description: `A GraphQL document is only valid if it contains only one schema definition.`,
|
518
|
-
requiresSchema: false,
|
519
543
|
}), validationToRule('no-fragment-cycles', 'NoFragmentCycles', {
|
520
544
|
category: 'Operations',
|
521
545
|
description: `A GraphQL fragment is only valid when it does not have cycles in fragments usage.`,
|
546
|
+
requiresSchema: true,
|
522
547
|
}), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
|
523
548
|
category: 'Operations',
|
524
549
|
description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
|
525
|
-
|
550
|
+
requiresSchema: true,
|
551
|
+
requiresSiblings: true,
|
552
|
+
}, handleMissingFragments), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
|
526
553
|
category: 'Operations',
|
527
554
|
description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
|
555
|
+
requiresSchema: true,
|
528
556
|
requiresSiblings: true,
|
529
|
-
}, context => {
|
530
|
-
const siblings = requireSiblingsOperations(
|
531
|
-
const
|
532
|
-
|
533
|
-
|
534
|
-
filePath
|
535
|
-
|
536
|
-
}));
|
537
|
-
const getParentNode = (
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
.filter(isGraphQLImportFile)
|
542
|
-
.map(line => _import.parseImportLine(line.replace('#', '')))
|
543
|
-
.some(o => filePath === path.join(path.dirname(docFilePath), o.from));
|
544
|
-
if (!isFileImported) {
|
545
|
-
continue;
|
546
|
-
}
|
547
|
-
// Import first file that import this file
|
548
|
-
const document = _import.processImport(docFilePath);
|
549
|
-
// Import most top file that import this file
|
550
|
-
return getParentNode(docFilePath) || document;
|
557
|
+
}, ({ ruleId, context, schema, node }) => {
|
558
|
+
const siblings = requireSiblingsOperations(ruleId, context);
|
559
|
+
const FilePathToDocumentsMap = [...siblings.getOperations(), ...siblings.getFragments()].reduce((map, { filePath, document }) => {
|
560
|
+
var _a;
|
561
|
+
(_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
|
562
|
+
map[filePath].push(document);
|
563
|
+
return map;
|
564
|
+
}, Object.create(null));
|
565
|
+
const getParentNode = (currentFilePath, node) => {
|
566
|
+
const { fragmentDefs } = getFragmentDefsAndFragmentSpreads(schema, node);
|
567
|
+
if (fragmentDefs.size === 0) {
|
568
|
+
return node;
|
551
569
|
}
|
552
|
-
|
570
|
+
// skip iteration over documents for current filepath
|
571
|
+
delete FilePathToDocumentsMap[currentFilePath];
|
572
|
+
for (const [filePath, documents] of Object.entries(FilePathToDocumentsMap)) {
|
573
|
+
const missingFragments = getMissingFragments(schema, {
|
574
|
+
kind: graphql.Kind.DOCUMENT,
|
575
|
+
definitions: documents,
|
576
|
+
});
|
577
|
+
const isCurrentFileImportFragment = missingFragments.some(fragment => fragmentDefs.has(fragment));
|
578
|
+
if (isCurrentFileImportFragment) {
|
579
|
+
return getParentNode(filePath, {
|
580
|
+
kind: graphql.Kind.DOCUMENT,
|
581
|
+
definitions: [...node.definitions, ...documents],
|
582
|
+
});
|
583
|
+
}
|
584
|
+
}
|
585
|
+
return node;
|
553
586
|
};
|
554
|
-
return getParentNode(context.getFilename());
|
587
|
+
return getParentNode(context.getFilename(), node);
|
555
588
|
}), validationToRule('no-unused-variables', 'NoUnusedVariables', {
|
556
589
|
category: 'Operations',
|
557
590
|
description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
|
558
|
-
|
591
|
+
requiresSchema: true,
|
592
|
+
requiresSiblings: true,
|
593
|
+
}, handleMissingFragments), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
|
559
594
|
category: 'Operations',
|
560
595
|
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.`,
|
596
|
+
requiresSchema: true,
|
561
597
|
}), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
|
562
598
|
category: 'Operations',
|
563
599
|
description: `A fragment spread is only valid if the type condition could ever possibly be true: if there is a non-empty intersection of the possible parent types, and possible types which pass the type condition.`,
|
600
|
+
requiresSchema: true,
|
564
601
|
}), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
|
565
602
|
category: 'Schema',
|
566
603
|
description: `A type extension is only valid if the type is defined and has the same kind.`,
|
567
|
-
requiresSchema: false,
|
568
604
|
recommended: false, // TODO: enable after https://github.com/dotansimha/graphql-eslint/issues/787 will be fixed
|
569
605
|
}), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
|
570
606
|
category: ['Schema', 'Operations'],
|
571
607
|
description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
|
608
|
+
requiresSchema: true,
|
572
609
|
}), validationToRule('scalar-leafs', 'ScalarLeafs', {
|
573
610
|
category: 'Operations',
|
574
611
|
description: `A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.`,
|
612
|
+
requiresSchema: true,
|
575
613
|
}), validationToRule('one-field-subscriptions', 'SingleFieldSubscriptions', {
|
576
614
|
category: 'Operations',
|
577
615
|
description: `A GraphQL subscription is valid only if it contains a single root field.`,
|
616
|
+
requiresSchema: true,
|
578
617
|
}), validationToRule('unique-argument-names', 'UniqueArgumentNames', {
|
579
618
|
category: 'Operations',
|
580
619
|
description: `A GraphQL field or directive is only valid if all supplied arguments are uniquely named.`,
|
620
|
+
requiresSchema: true,
|
581
621
|
}), validationToRule('unique-directive-names', 'UniqueDirectiveNames', {
|
582
622
|
category: 'Schema',
|
583
623
|
description: `A GraphQL document is only valid if all defined directives have unique names.`,
|
584
|
-
requiresSchema: false,
|
585
624
|
}), validationToRule('unique-directive-names-per-location', 'UniqueDirectivesPerLocation', {
|
586
625
|
category: ['Schema', 'Operations'],
|
587
626
|
description: `A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.`,
|
627
|
+
requiresSchema: true,
|
588
628
|
}), validationToRule('unique-enum-value-names', 'UniqueEnumValueNames', {
|
589
629
|
category: 'Schema',
|
590
630
|
description: `A GraphQL enum type is only valid if all its values are uniquely named.`,
|
591
|
-
requiresSchema: false,
|
592
631
|
recommended: false,
|
593
632
|
}), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
|
594
633
|
category: 'Schema',
|
595
634
|
description: `A GraphQL complex type is only valid if all its fields are uniquely named.`,
|
596
|
-
requiresSchema: false,
|
597
635
|
}), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
|
598
636
|
category: 'Operations',
|
599
637
|
description: `A GraphQL input object value is only valid if all supplied fields are uniquely named.`,
|
600
|
-
requiresSchema: false,
|
601
638
|
}), validationToRule('unique-operation-types', 'UniqueOperationTypes', {
|
602
639
|
category: 'Schema',
|
603
640
|
description: `A GraphQL document is only valid if it has only one type per operation.`,
|
604
|
-
requiresSchema: false,
|
605
641
|
}), validationToRule('unique-type-names', 'UniqueTypeNames', {
|
606
642
|
category: 'Schema',
|
607
643
|
description: `A GraphQL document is only valid if all defined types have unique names.`,
|
608
|
-
requiresSchema: false,
|
609
644
|
}), validationToRule('unique-variable-names', 'UniqueVariableNames', {
|
610
645
|
category: 'Operations',
|
611
646
|
description: `A GraphQL operation is only valid if all its variables are uniquely named.`,
|
647
|
+
requiresSchema: true,
|
612
648
|
}), validationToRule('value-literals-of-correct-type', 'ValuesOfCorrectType', {
|
613
649
|
category: 'Operations',
|
614
650
|
description: `A GraphQL document is only valid if all value literals are of the type expected at their position.`,
|
651
|
+
requiresSchema: true,
|
615
652
|
}), validationToRule('variables-are-input-types', 'VariablesAreInputTypes', {
|
616
653
|
category: 'Operations',
|
617
654
|
description: `A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).`,
|
655
|
+
requiresSchema: true,
|
618
656
|
}), validationToRule('variables-in-allowed-position', 'VariablesInAllowedPosition', {
|
619
657
|
category: 'Operations',
|
620
658
|
description: `Variables passed to field arguments conform to type.`,
|
659
|
+
requiresSchema: true,
|
621
660
|
}));
|
622
661
|
|
623
662
|
const ALPHABETIZE = 'ALPHABETIZE';
|
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';
|
@@ -321,14 +320,14 @@ function getLocation(loc, fieldName = '', offset) {
|
|
321
320
|
};
|
322
321
|
}
|
323
322
|
|
324
|
-
function
|
323
|
+
function validateDocument(sourceNode, context, schema, documentNode, rule) {
|
325
324
|
if (documentNode.definitions.length === 0) {
|
326
325
|
return;
|
327
326
|
}
|
328
327
|
try {
|
329
328
|
const validationErrors = schema
|
330
|
-
? validate(schema, documentNode,
|
331
|
-
: validateSDL(documentNode, null,
|
329
|
+
? validate(schema, documentNode, [rule])
|
330
|
+
: validateSDL(documentNode, null, [rule]);
|
332
331
|
for (const error of validationErrors) {
|
333
332
|
/*
|
334
333
|
* TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
|
@@ -358,34 +357,79 @@ function validateDoc(sourceNode, context, schema, documentNode, rules) {
|
|
358
357
|
});
|
359
358
|
}
|
360
359
|
}
|
361
|
-
const
|
362
|
-
const
|
363
|
-
|
360
|
+
const getFragmentDefsAndFragmentSpreads = (schema, node) => {
|
361
|
+
const typeInfo = new TypeInfo(schema);
|
362
|
+
const fragmentDefs = new Set();
|
363
|
+
const fragmentSpreads = new Set();
|
364
|
+
const visitor = visitWithTypeInfo(typeInfo, {
|
365
|
+
FragmentDefinition(node) {
|
366
|
+
fragmentDefs.add(`${node.name.value}:${node.typeCondition.name.value}`);
|
367
|
+
},
|
368
|
+
FragmentSpread(node) {
|
369
|
+
const parentType = typeInfo.getParentType();
|
370
|
+
if (parentType) {
|
371
|
+
fragmentSpreads.add(`${node.name.value}:${parentType.name}`);
|
372
|
+
}
|
373
|
+
},
|
374
|
+
});
|
375
|
+
visit(node, visitor);
|
376
|
+
return { fragmentDefs, fragmentSpreads };
|
364
377
|
};
|
365
|
-
const
|
366
|
-
|
378
|
+
const getMissingFragments = (schema, node) => {
|
379
|
+
const { fragmentDefs, fragmentSpreads } = getFragmentDefsAndFragmentSpreads(schema, node);
|
380
|
+
return [...fragmentSpreads].filter(name => !fragmentDefs.has(name));
|
381
|
+
};
|
382
|
+
const handleMissingFragments = ({ ruleId, context, schema, node }) => {
|
383
|
+
const missingFragments = getMissingFragments(schema, node);
|
384
|
+
if (missingFragments.length > 0) {
|
385
|
+
const siblings = requireSiblingsOperations(ruleId, context);
|
386
|
+
const fragmentsToAdd = [];
|
387
|
+
for (const missingFragment of missingFragments) {
|
388
|
+
const [fragmentName, fragmentTypeName] = missingFragment.split(':');
|
389
|
+
const [foundFragment] = siblings
|
390
|
+
.getFragment(fragmentName)
|
391
|
+
.map(source => source.document)
|
392
|
+
.filter(fragment => fragment.typeCondition.name.value === fragmentTypeName);
|
393
|
+
if (foundFragment) {
|
394
|
+
fragmentsToAdd.push(foundFragment);
|
395
|
+
}
|
396
|
+
}
|
397
|
+
if (fragmentsToAdd.length > 0) {
|
398
|
+
// recall fn to make sure to add fragments inside fragments
|
399
|
+
return handleMissingFragments({
|
400
|
+
ruleId,
|
401
|
+
context,
|
402
|
+
schema,
|
403
|
+
node: {
|
404
|
+
kind: Kind.DOCUMENT,
|
405
|
+
definitions: [...node.definitions, ...fragmentsToAdd],
|
406
|
+
},
|
407
|
+
});
|
408
|
+
}
|
409
|
+
}
|
410
|
+
return node;
|
411
|
+
};
|
412
|
+
const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
|
367
413
|
let ruleFn = null;
|
368
414
|
try {
|
369
415
|
ruleFn = require(`graphql/validation/rules/${ruleName}Rule`)[`${ruleName}Rule`];
|
370
416
|
}
|
371
|
-
catch (
|
417
|
+
catch (_a) {
|
372
418
|
try {
|
373
419
|
ruleFn = require(`graphql/validation/rules/${ruleName}`)[`${ruleName}Rule`];
|
374
420
|
}
|
375
|
-
catch (
|
421
|
+
catch (_b) {
|
376
422
|
ruleFn = require('graphql/validation')[`${ruleName}Rule`];
|
377
423
|
}
|
378
424
|
}
|
379
|
-
const requiresSchema = (_a = docs.requiresSchema) !== null && _a !== void 0 ? _a : true;
|
380
425
|
return {
|
381
|
-
[
|
426
|
+
[ruleId]: {
|
382
427
|
meta: {
|
383
428
|
docs: {
|
384
429
|
recommended: true,
|
385
430
|
...docs,
|
386
431
|
graphQLJSRuleName: ruleName,
|
387
|
-
|
388
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
|
432
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${ruleId}.md`,
|
389
433
|
description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function. [You can find its source code here](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/${ruleName}Rule.ts).`,
|
390
434
|
},
|
391
435
|
},
|
@@ -394,56 +438,53 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
|
394
438
|
Document(node) {
|
395
439
|
if (!ruleFn) {
|
396
440
|
// eslint-disable-next-line no-console
|
397
|
-
console.warn(`You rule "${
|
441
|
+
console.warn(`You rule "${ruleId}" depends on a GraphQL validation rule "${ruleName}" but it's not available in the "graphql-js" version you are using. Skipping...`);
|
398
442
|
return;
|
399
443
|
}
|
400
|
-
const schema = requiresSchema ? requireGraphQLSchemaFromContext(
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
}
|
406
|
-
validateDoc(node, context, schema, documentNode || node.rawNode(), [ruleFn]);
|
444
|
+
const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
|
445
|
+
const documentNode = getDocumentNode
|
446
|
+
? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
|
447
|
+
: node.rawNode();
|
448
|
+
validateDocument(node, context, schema, documentNode, ruleFn);
|
407
449
|
},
|
408
450
|
};
|
409
451
|
},
|
410
452
|
},
|
411
453
|
};
|
412
454
|
};
|
413
|
-
const importFiles = (context) => {
|
414
|
-
const code = context.getSourceCode().text;
|
415
|
-
if (!isGraphQLImportFile(code)) {
|
416
|
-
return null;
|
417
|
-
}
|
418
|
-
// Import documents because file contains '#import' comments
|
419
|
-
return processImport(context.getFilename());
|
420
|
-
};
|
421
455
|
const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
|
422
456
|
category: 'Operations',
|
423
457
|
description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
|
458
|
+
requiresSchema: true,
|
424
459
|
}), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
|
425
460
|
category: 'Operations',
|
426
461
|
description: 'A GraphQL document is only valid if all fields selected are defined by the parent type, or are an allowed meta field such as `__typename`.',
|
462
|
+
requiresSchema: true,
|
427
463
|
}), validationToRule('fragments-on-composite-type', 'FragmentsOnCompositeTypes', {
|
428
464
|
category: 'Operations',
|
429
465
|
description: `Fragments use a type condition to determine if they apply, since fragments can only be spread into a composite type (object, interface, or union), the type condition must also be a composite type.`,
|
466
|
+
requiresSchema: true,
|
430
467
|
}), validationToRule('known-argument-names', 'KnownArgumentNames', {
|
431
468
|
category: ['Schema', 'Operations'],
|
432
469
|
description: `A GraphQL field is only valid if all supplied arguments are defined by that field.`,
|
470
|
+
requiresSchema: true,
|
433
471
|
}), validationToRule('known-directives', 'KnownDirectives', {
|
434
472
|
category: ['Schema', 'Operations'],
|
435
473
|
description: `A GraphQL document is only valid if all \`@directives\` are known by the schema and legally positioned.`,
|
474
|
+
requiresSchema: true,
|
436
475
|
}), validationToRule('known-fragment-names', 'KnownFragmentNames', {
|
437
476
|
category: 'Operations',
|
438
477
|
description: `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.`,
|
478
|
+
requiresSchema: true,
|
479
|
+
requiresSiblings: true,
|
439
480
|
examples: [
|
440
481
|
{
|
441
|
-
title: 'Incorrect
|
482
|
+
title: 'Incorrect',
|
442
483
|
code: /* GraphQL */ `
|
443
484
|
query {
|
444
485
|
user {
|
445
486
|
id
|
446
|
-
...UserFields
|
487
|
+
...UserFields # fragment not defined in the document
|
447
488
|
}
|
448
489
|
}
|
449
490
|
`,
|
@@ -465,153 +506,151 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
465
506
|
`,
|
466
507
|
},
|
467
508
|
{
|
468
|
-
title: 'Correct (
|
509
|
+
title: 'Correct (`UserFields` fragment located in a separate file)',
|
469
510
|
code: /* GraphQL */ `
|
470
|
-
#
|
471
|
-
|
511
|
+
# user.gql
|
472
512
|
query {
|
473
513
|
user {
|
474
514
|
id
|
475
515
|
...UserFields
|
476
516
|
}
|
477
517
|
}
|
478
|
-
`,
|
479
|
-
},
|
480
|
-
{
|
481
|
-
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.",
|
482
|
-
code: `
|
483
|
-
const USER_FIELDS = gql\`
|
484
|
-
fragment UserFields on User {
|
485
|
-
id
|
486
|
-
}
|
487
|
-
\`
|
488
|
-
|
489
|
-
const GET_USER = /* GraphQL */ \`
|
490
|
-
# eslint @graphql-eslint/known-fragment-names: 'error'
|
491
|
-
|
492
|
-
query User {
|
493
|
-
user {
|
494
|
-
...UserFields
|
495
|
-
}
|
496
|
-
}
|
497
518
|
|
498
|
-
|
499
|
-
|
500
|
-
|
519
|
+
# user-fields.gql
|
520
|
+
fragment UserFields on User {
|
521
|
+
id
|
522
|
+
}
|
523
|
+
`,
|
501
524
|
},
|
502
525
|
],
|
503
|
-
},
|
526
|
+
}, handleMissingFragments), validationToRule('known-type-names', 'KnownTypeNames', {
|
504
527
|
category: ['Schema', 'Operations'],
|
505
528
|
description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
|
529
|
+
requiresSchema: true,
|
506
530
|
}), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
|
507
531
|
category: 'Operations',
|
508
532
|
description: `A GraphQL document is only valid if when it contains an anonymous operation (the query short-hand) that it contains only that one operation definition.`,
|
533
|
+
requiresSchema: true,
|
509
534
|
}), validationToRule('lone-schema-definition', 'LoneSchemaDefinition', {
|
510
535
|
category: 'Schema',
|
511
536
|
description: `A GraphQL document is only valid if it contains only one schema definition.`,
|
512
|
-
requiresSchema: false,
|
513
537
|
}), validationToRule('no-fragment-cycles', 'NoFragmentCycles', {
|
514
538
|
category: 'Operations',
|
515
539
|
description: `A GraphQL fragment is only valid when it does not have cycles in fragments usage.`,
|
540
|
+
requiresSchema: true,
|
516
541
|
}), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
|
517
542
|
category: 'Operations',
|
518
543
|
description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
|
519
|
-
|
544
|
+
requiresSchema: true,
|
545
|
+
requiresSiblings: true,
|
546
|
+
}, handleMissingFragments), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
|
520
547
|
category: 'Operations',
|
521
548
|
description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
|
549
|
+
requiresSchema: true,
|
522
550
|
requiresSiblings: true,
|
523
|
-
}, context => {
|
524
|
-
const siblings = requireSiblingsOperations(
|
525
|
-
const
|
526
|
-
|
527
|
-
|
528
|
-
filePath
|
529
|
-
|
530
|
-
}));
|
531
|
-
const getParentNode = (
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
.filter(isGraphQLImportFile)
|
536
|
-
.map(line => parseImportLine(line.replace('#', '')))
|
537
|
-
.some(o => filePath === join(dirname(docFilePath), o.from));
|
538
|
-
if (!isFileImported) {
|
539
|
-
continue;
|
540
|
-
}
|
541
|
-
// Import first file that import this file
|
542
|
-
const document = processImport(docFilePath);
|
543
|
-
// Import most top file that import this file
|
544
|
-
return getParentNode(docFilePath) || document;
|
551
|
+
}, ({ ruleId, context, schema, node }) => {
|
552
|
+
const siblings = requireSiblingsOperations(ruleId, context);
|
553
|
+
const FilePathToDocumentsMap = [...siblings.getOperations(), ...siblings.getFragments()].reduce((map, { filePath, document }) => {
|
554
|
+
var _a;
|
555
|
+
(_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
|
556
|
+
map[filePath].push(document);
|
557
|
+
return map;
|
558
|
+
}, Object.create(null));
|
559
|
+
const getParentNode = (currentFilePath, node) => {
|
560
|
+
const { fragmentDefs } = getFragmentDefsAndFragmentSpreads(schema, node);
|
561
|
+
if (fragmentDefs.size === 0) {
|
562
|
+
return node;
|
545
563
|
}
|
546
|
-
|
564
|
+
// skip iteration over documents for current filepath
|
565
|
+
delete FilePathToDocumentsMap[currentFilePath];
|
566
|
+
for (const [filePath, documents] of Object.entries(FilePathToDocumentsMap)) {
|
567
|
+
const missingFragments = getMissingFragments(schema, {
|
568
|
+
kind: Kind.DOCUMENT,
|
569
|
+
definitions: documents,
|
570
|
+
});
|
571
|
+
const isCurrentFileImportFragment = missingFragments.some(fragment => fragmentDefs.has(fragment));
|
572
|
+
if (isCurrentFileImportFragment) {
|
573
|
+
return getParentNode(filePath, {
|
574
|
+
kind: Kind.DOCUMENT,
|
575
|
+
definitions: [...node.definitions, ...documents],
|
576
|
+
});
|
577
|
+
}
|
578
|
+
}
|
579
|
+
return node;
|
547
580
|
};
|
548
|
-
return getParentNode(context.getFilename());
|
581
|
+
return getParentNode(context.getFilename(), node);
|
549
582
|
}), validationToRule('no-unused-variables', 'NoUnusedVariables', {
|
550
583
|
category: 'Operations',
|
551
584
|
description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
|
552
|
-
|
585
|
+
requiresSchema: true,
|
586
|
+
requiresSiblings: true,
|
587
|
+
}, handleMissingFragments), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
|
553
588
|
category: 'Operations',
|
554
589
|
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.`,
|
590
|
+
requiresSchema: true,
|
555
591
|
}), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
|
556
592
|
category: 'Operations',
|
557
593
|
description: `A fragment spread is only valid if the type condition could ever possibly be true: if there is a non-empty intersection of the possible parent types, and possible types which pass the type condition.`,
|
594
|
+
requiresSchema: true,
|
558
595
|
}), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
|
559
596
|
category: 'Schema',
|
560
597
|
description: `A type extension is only valid if the type is defined and has the same kind.`,
|
561
|
-
requiresSchema: false,
|
562
598
|
recommended: false, // TODO: enable after https://github.com/dotansimha/graphql-eslint/issues/787 will be fixed
|
563
599
|
}), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
|
564
600
|
category: ['Schema', 'Operations'],
|
565
601
|
description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
|
602
|
+
requiresSchema: true,
|
566
603
|
}), validationToRule('scalar-leafs', 'ScalarLeafs', {
|
567
604
|
category: 'Operations',
|
568
605
|
description: `A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.`,
|
606
|
+
requiresSchema: true,
|
569
607
|
}), validationToRule('one-field-subscriptions', 'SingleFieldSubscriptions', {
|
570
608
|
category: 'Operations',
|
571
609
|
description: `A GraphQL subscription is valid only if it contains a single root field.`,
|
610
|
+
requiresSchema: true,
|
572
611
|
}), validationToRule('unique-argument-names', 'UniqueArgumentNames', {
|
573
612
|
category: 'Operations',
|
574
613
|
description: `A GraphQL field or directive is only valid if all supplied arguments are uniquely named.`,
|
614
|
+
requiresSchema: true,
|
575
615
|
}), validationToRule('unique-directive-names', 'UniqueDirectiveNames', {
|
576
616
|
category: 'Schema',
|
577
617
|
description: `A GraphQL document is only valid if all defined directives have unique names.`,
|
578
|
-
requiresSchema: false,
|
579
618
|
}), validationToRule('unique-directive-names-per-location', 'UniqueDirectivesPerLocation', {
|
580
619
|
category: ['Schema', 'Operations'],
|
581
620
|
description: `A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.`,
|
621
|
+
requiresSchema: true,
|
582
622
|
}), validationToRule('unique-enum-value-names', 'UniqueEnumValueNames', {
|
583
623
|
category: 'Schema',
|
584
624
|
description: `A GraphQL enum type is only valid if all its values are uniquely named.`,
|
585
|
-
requiresSchema: false,
|
586
625
|
recommended: false,
|
587
626
|
}), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
|
588
627
|
category: 'Schema',
|
589
628
|
description: `A GraphQL complex type is only valid if all its fields are uniquely named.`,
|
590
|
-
requiresSchema: false,
|
591
629
|
}), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
|
592
630
|
category: 'Operations',
|
593
631
|
description: `A GraphQL input object value is only valid if all supplied fields are uniquely named.`,
|
594
|
-
requiresSchema: false,
|
595
632
|
}), validationToRule('unique-operation-types', 'UniqueOperationTypes', {
|
596
633
|
category: 'Schema',
|
597
634
|
description: `A GraphQL document is only valid if it has only one type per operation.`,
|
598
|
-
requiresSchema: false,
|
599
635
|
}), validationToRule('unique-type-names', 'UniqueTypeNames', {
|
600
636
|
category: 'Schema',
|
601
637
|
description: `A GraphQL document is only valid if all defined types have unique names.`,
|
602
|
-
requiresSchema: false,
|
603
638
|
}), validationToRule('unique-variable-names', 'UniqueVariableNames', {
|
604
639
|
category: 'Operations',
|
605
640
|
description: `A GraphQL operation is only valid if all its variables are uniquely named.`,
|
641
|
+
requiresSchema: true,
|
606
642
|
}), validationToRule('value-literals-of-correct-type', 'ValuesOfCorrectType', {
|
607
643
|
category: 'Operations',
|
608
644
|
description: `A GraphQL document is only valid if all value literals are of the type expected at their position.`,
|
645
|
+
requiresSchema: true,
|
609
646
|
}), validationToRule('variables-are-input-types', 'VariablesAreInputTypes', {
|
610
647
|
category: 'Operations',
|
611
648
|
description: `A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).`,
|
649
|
+
requiresSchema: true,
|
612
650
|
}), validationToRule('variables-in-allowed-position', 'VariablesInAllowedPosition', {
|
613
651
|
category: 'Operations',
|
614
652
|
description: `Variables passed to field arguments conform to type.`,
|
653
|
+
requiresSchema: true,
|
615
654
|
}));
|
616
655
|
|
617
656
|
const ALPHABETIZE = 'ALPHABETIZE';
|
package/package.json
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@graphql-eslint/eslint-plugin",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.3.0-alpha-db2c2cb.0",
|
4
|
+
"description": "GraphQL plugin for ESLint",
|
4
5
|
"sideEffects": false,
|
5
6
|
"peerDependencies": {
|
6
7
|
"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,13 +10,18 @@
|
|
9
10
|
"@babel/code-frame": "7.16.0",
|
10
11
|
"@graphql-tools/code-file-loader": "7.2.3",
|
11
12
|
"@graphql-tools/graphql-tag-pluck": "7.1.4",
|
12
|
-
"@graphql-tools/import": "6.6.2",
|
13
13
|
"@graphql-tools/utils": "8.5.4",
|
14
14
|
"graphql-config": "4.1.0",
|
15
15
|
"graphql-depth-limit": "1.1.0",
|
16
16
|
"lodash.lowercase": "4.3.0"
|
17
17
|
},
|
18
18
|
"repository": "https://github.com/dotansimha/graphql-eslint",
|
19
|
+
"keywords": [
|
20
|
+
"eslint",
|
21
|
+
"eslintplugin",
|
22
|
+
"eslint-plugin",
|
23
|
+
"graphql"
|
24
|
+
],
|
19
25
|
"author": "Dotan Simha <dotansimha@gmail.com>",
|
20
26
|
"license": "MIT",
|
21
27
|
"main": "index.js",
|
@@ -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>): 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>;
|
package/types.d.ts
CHANGED
@@ -49,8 +49,8 @@ export declare type CategoryType = 'Schema' | 'Operations';
|
|
49
49
|
export declare type RuleDocsInfo<T> = {
|
50
50
|
docs: Omit<Rule.RuleMetaData['docs'], 'category'> & {
|
51
51
|
category: CategoryType | CategoryType[];
|
52
|
-
requiresSchema?:
|
53
|
-
requiresSiblings?:
|
52
|
+
requiresSchema?: true;
|
53
|
+
requiresSiblings?: true;
|
54
54
|
examples?: {
|
55
55
|
title: string;
|
56
56
|
code: string;
|