@graphql-eslint/eslint-plugin 3.3.0-alpha-1c01242.0 → 3.4.0-alpha-d7fc26d.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/README.md +6 -6
- package/docs/rules/naming-convention.md +16 -0
- package/docs/rules/unique-enum-value-names.md +1 -1
- package/docs/rules/unique-field-definition-names.md +1 -1
- package/index.js +65 -47
- package/index.mjs +65 -47
- package/package.json +3 -2
- package/rules/naming-convention.d.ts +1 -0
- package/types.d.ts +0 -1
- package/utils.d.ts +4 -0
package/README.md
CHANGED
@@ -42,7 +42,7 @@ npm install --save-dev @graphql-eslint/eslint-plugin
|
|
42
42
|
|
43
43
|
> Make sure you have `graphql` dependency in your project.
|
44
44
|
|
45
|
-
|
45
|
+
## Configuration
|
46
46
|
|
47
47
|
To get started, define an override in your ESLint config to apply this plugin to `.graphql` files. Add the [rules](docs/README.md) you want applied.
|
48
48
|
|
@@ -65,7 +65,7 @@ To get started, define an override in your ESLint config to apply this plugin to
|
|
65
65
|
|
66
66
|
If your GraphQL definitions are defined only in `.graphql` files, and you're only using rules that apply to individual files, you should be good to go 👍. If you would like use a remote schema or use rules that apply across the entire collection of definitions at once, see [here](#using-a-remote-schema-or-rules-with-constraints-that-span-the-entire-schema).
|
67
67
|
|
68
|
-
|
68
|
+
### Apply this plugin to GraphQL definitions defined in code files
|
69
69
|
|
70
70
|
If you are defining GraphQL schema or GraphQL operations in code files, you'll want to define an additional override to extend the functionality of this plugin to the schema and operations in those files.
|
71
71
|
|
@@ -90,7 +90,7 @@ If you are defining GraphQL schema or GraphQL operations in code files, you'll w
|
|
90
90
|
|
91
91
|
Under the hood, specifying the `@graphql-eslint/graphql` processor for code files will cause `graphql-eslint/graphql` to extract the schema and operation definitions from these files into virtual GraphQL documents with `.graphql` extensions. This will allow the overrides you've defined for `.graphql` files, via `"files": ["*.graphql"]`, to get applied to the definitions defined in your code files.
|
92
92
|
|
93
|
-
|
93
|
+
### Extended linting rules with GraphQL Schema
|
94
94
|
|
95
95
|
Some rules require an understanding of the entire schema at once. For example, [no-unreachable-types](https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-unreachable-types.md#no-unreachable-types) checks that all types are reachable by root-level fields.
|
96
96
|
|
@@ -120,7 +120,7 @@ The parser allows you to specify a json file / graphql files(s) / url / raw stri
|
|
120
120
|
|
121
121
|
> Some rules require type information to operate, it's marked in the docs for each rule!
|
122
122
|
|
123
|
-
|
123
|
+
### Extended linting rules with siblings operations
|
124
124
|
|
125
125
|
While implementing this tool, we had to find solutions for a better integration of the GraphQL ecosystem and ESLint core.
|
126
126
|
|
@@ -180,7 +180,7 @@ You can find a list of [ESLint directives here](https://eslint.org/docs/2.13.1/u
|
|
180
180
|
|
181
181
|
You can find a complete list of [all available rules here](docs/README.md).
|
182
182
|
|
183
|
-
|
183
|
+
### Deprecated Rules
|
184
184
|
|
185
185
|
See [docs/deprecated-rules.md](docs/deprecated-rules.md).
|
186
186
|
|
@@ -201,7 +201,7 @@ See [docs/deprecated-rules.md](docs/deprecated-rules.md).
|
|
201
201
|
|
202
202
|
> If you are in a monorepo project, you probably need both sets of rules.
|
203
203
|
|
204
|
-
|
204
|
+
### Config usage
|
205
205
|
|
206
206
|
For example, to enable the `schema-recommended` config, enable it in your `.eslintrc` file with the `extends` option:
|
207
207
|
|
@@ -71,6 +71,18 @@ type Query {
|
|
71
71
|
}
|
72
72
|
```
|
73
73
|
|
74
|
+
### Correct
|
75
|
+
|
76
|
+
```graphql
|
77
|
+
# eslint @graphql-eslint/naming-convention: ['error', { FieldDefinition: { style: 'camelCase', ignorePattern: '^(EAN13|UPC|UK)' } }]
|
78
|
+
|
79
|
+
type Product {
|
80
|
+
EAN13: String
|
81
|
+
UPC: String
|
82
|
+
UKFlag: String
|
83
|
+
}
|
84
|
+
```
|
85
|
+
|
74
86
|
## Config Schema
|
75
87
|
|
76
88
|
> It's possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with allowed `ASTNode` names which are described below.
|
@@ -276,6 +288,10 @@ Additional restrictions:
|
|
276
288
|
* Minimum items: `1`
|
277
289
|
* Unique items: `true`
|
278
290
|
|
291
|
+
### `ignorePattern` (string)
|
292
|
+
|
293
|
+
Option to skip validation of some words, e.g. acronyms
|
294
|
+
|
279
295
|
## Resources
|
280
296
|
|
281
297
|
- [Rule source](../../packages/plugin/src/rules/naming-convention.ts)
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
- Category: `Schema`
|
4
4
|
- Rule name: `@graphql-eslint/unique-enum-value-names`
|
5
|
-
- Requires GraphQL Schema: `
|
5
|
+
- Requires GraphQL Schema: `true` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
|
6
6
|
- Requires GraphQL Operations: `false` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)
|
7
7
|
|
8
8
|
A GraphQL enum type is only valid if all its values are uniquely named.
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
- Category: `Schema`
|
6
6
|
- Rule name: `@graphql-eslint/unique-field-definition-names`
|
7
|
-
- Requires GraphQL Schema: `
|
7
|
+
- Requires GraphQL Schema: `true` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
|
8
8
|
- Requires GraphQL Operations: `false` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)
|
9
9
|
|
10
10
|
A GraphQL complex type is only valid if all its fields are uniquely named.
|
package/index.js
CHANGED
@@ -10,6 +10,7 @@ const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
11
11
|
const utils = require('@graphql-tools/utils');
|
12
12
|
const lowerCase = _interopDefault(require('lodash.lowercase'));
|
13
|
+
const chalk = _interopDefault(require('chalk'));
|
13
14
|
const depthLimit = _interopDefault(require('graphql-depth-limit'));
|
14
15
|
const graphqlTagPluck = require('@graphql-tools/graphql-tag-pluck');
|
15
16
|
const graphqlConfig = require('graphql-config');
|
@@ -192,6 +193,12 @@ function requireGraphQLSchemaFromContext(ruleName, context) {
|
|
192
193
|
}
|
193
194
|
return context.parserServices.schema;
|
194
195
|
}
|
196
|
+
const logger = {
|
197
|
+
// eslint-disable-next-line no-console
|
198
|
+
error: (...args) => console.error(chalk.red('error'), '[graphql-eslint]', chalk(...args)),
|
199
|
+
// eslint-disable-next-line no-console
|
200
|
+
warn: (...args) => console.warn(chalk.yellow('warning'), '[graphql-eslint]', chalk(...args)),
|
201
|
+
};
|
195
202
|
function requireReachableTypesFromContext(ruleName, context) {
|
196
203
|
const schema = requireGraphQLSchemaFromContext(ruleName, context);
|
197
204
|
return context.parserServices.reachableTypes(schema);
|
@@ -326,12 +333,12 @@ function getLocation(loc, fieldName = '', offset) {
|
|
326
333
|
};
|
327
334
|
}
|
328
335
|
|
329
|
-
function validateDocument(context, schema = null, documentNode, rule
|
336
|
+
function validateDocument(context, schema = null, documentNode, rule) {
|
330
337
|
if (documentNode.definitions.length === 0) {
|
331
338
|
return;
|
332
339
|
}
|
333
340
|
try {
|
334
|
-
const validationErrors = schema
|
341
|
+
const validationErrors = schema
|
335
342
|
? graphql.validate(schema, documentNode, [rule])
|
336
343
|
: validate.validateSDL(documentNode, schema, [rule]);
|
337
344
|
for (const error of validationErrors) {
|
@@ -442,17 +449,16 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
|
|
442
449
|
},
|
443
450
|
create(context) {
|
444
451
|
if (!ruleFn) {
|
445
|
-
|
446
|
-
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...`);
|
452
|
+
logger.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...`);
|
447
453
|
return {};
|
448
454
|
}
|
449
|
-
const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
|
450
455
|
return {
|
451
456
|
Document(node) {
|
457
|
+
const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
|
452
458
|
const documentNode = getDocumentNode
|
453
459
|
? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
|
454
460
|
: node.rawNode();
|
455
|
-
validateDocument(context, schema, documentNode, ruleFn
|
461
|
+
validateDocument(context, schema, documentNode, ruleFn);
|
456
462
|
},
|
457
463
|
};
|
458
464
|
},
|
@@ -604,7 +610,6 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
604
610
|
description: `A type extension is only valid if the type is defined and has the same kind.`,
|
605
611
|
recommended: false,
|
606
612
|
requiresSchema: true,
|
607
|
-
requiresSchemaToExtend: true,
|
608
613
|
}), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
|
609
614
|
category: ['Schema', 'Operations'],
|
610
615
|
description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
|
@@ -1376,6 +1381,17 @@ const rule$4 = {
|
|
1376
1381
|
type Query {
|
1377
1382
|
users: [User!]!
|
1378
1383
|
}
|
1384
|
+
`,
|
1385
|
+
},
|
1386
|
+
{
|
1387
|
+
title: 'Correct',
|
1388
|
+
usage: [{ FieldDefinition: { style: 'camelCase', ignorePattern: '^(EAN13|UPC|UK)' } }],
|
1389
|
+
code: /* GraphQL */ `
|
1390
|
+
type Product {
|
1391
|
+
EAN13: String
|
1392
|
+
UPC: String
|
1393
|
+
UKFlag: String
|
1394
|
+
}
|
1379
1395
|
`,
|
1380
1396
|
},
|
1381
1397
|
],
|
@@ -1445,6 +1461,10 @@ const rule$4 = {
|
|
1445
1461
|
minItems: 1,
|
1446
1462
|
items: { type: 'string' },
|
1447
1463
|
},
|
1464
|
+
ignorePattern: {
|
1465
|
+
type: 'string',
|
1466
|
+
description: 'Option to skip validation of some words, e.g. acronyms',
|
1467
|
+
},
|
1448
1468
|
},
|
1449
1469
|
},
|
1450
1470
|
},
|
@@ -1499,15 +1519,15 @@ const rule$4 = {
|
|
1499
1519
|
if (!node) {
|
1500
1520
|
return;
|
1501
1521
|
}
|
1502
|
-
const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style } = normalisePropertyOption(selector);
|
1522
|
+
const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style, ignorePattern } = normalisePropertyOption(selector);
|
1503
1523
|
const nodeType = KindToDisplayName[n.kind] || n.kind;
|
1504
1524
|
const nodeName = node.value;
|
1505
1525
|
const error = getError();
|
1506
1526
|
if (error) {
|
1507
1527
|
const { errorMessage, renameToName } = error;
|
1508
|
-
const [
|
1509
|
-
const [
|
1510
|
-
const suggestedName =
|
1528
|
+
const [leadingUnderscores] = nodeName.match(/^_*/);
|
1529
|
+
const [trailingUnderscores] = nodeName.match(/_*$/);
|
1530
|
+
const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
|
1511
1531
|
context.report({
|
1512
1532
|
loc: getLocation(node.loc, node.value),
|
1513
1533
|
message: `${nodeType} "${nodeName}" should ${errorMessage}`,
|
@@ -1521,6 +1541,9 @@ const rule$4 = {
|
|
1521
1541
|
}
|
1522
1542
|
function getError() {
|
1523
1543
|
const name = nodeName.replace(/(^_+)|(_+$)/g, '');
|
1544
|
+
if (ignorePattern && new RegExp(ignorePattern, 'u').test(name)) {
|
1545
|
+
return;
|
1546
|
+
}
|
1524
1547
|
if (prefix && !name.startsWith(prefix)) {
|
1525
1548
|
return {
|
1526
1549
|
errorMessage: `have "${prefix}" prefix`,
|
@@ -1547,8 +1570,12 @@ const rule$4 = {
|
|
1547
1570
|
renameToName: name.replace(new RegExp(`${forbiddenSuffix}$`), ''),
|
1548
1571
|
};
|
1549
1572
|
}
|
1573
|
+
// Style is optional
|
1574
|
+
if (!style) {
|
1575
|
+
return;
|
1576
|
+
}
|
1550
1577
|
const caseRegex = StyleToRegex[style];
|
1551
|
-
if (
|
1578
|
+
if (!caseRegex.test(name)) {
|
1552
1579
|
return {
|
1553
1580
|
errorMessage: `be in ${style} format`,
|
1554
1581
|
renameToName: convertCase(style, name),
|
@@ -3015,12 +3042,13 @@ const rule$j = {
|
|
3015
3042
|
},
|
3016
3043
|
};
|
3017
3044
|
|
3045
|
+
const RULE_ID$2 = 'selection-set-depth';
|
3018
3046
|
const rule$k = {
|
3019
3047
|
meta: {
|
3020
3048
|
docs: {
|
3021
3049
|
category: 'Operations',
|
3022
3050
|
description: `Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://github.com/stems/graphql-depth-limit).`,
|
3023
|
-
url:
|
3051
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
|
3024
3052
|
requiresSiblings: true,
|
3025
3053
|
examples: [
|
3026
3054
|
{
|
@@ -3094,11 +3122,10 @@ const rule$k = {
|
|
3094
3122
|
create(context) {
|
3095
3123
|
let siblings = null;
|
3096
3124
|
try {
|
3097
|
-
siblings = requireSiblingsOperations(
|
3125
|
+
siblings = requireSiblingsOperations(RULE_ID$2, context);
|
3098
3126
|
}
|
3099
3127
|
catch (e) {
|
3100
|
-
|
3101
|
-
console.warn(`Rule "selection-set-depth" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
|
3128
|
+
logger.warn(`Rule "${RULE_ID$2}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
|
3102
3129
|
}
|
3103
3130
|
const { maxDepth } = context.options[0];
|
3104
3131
|
const ignore = context.options[0].ignore || [];
|
@@ -3123,8 +3150,7 @@ const rule$k = {
|
|
3123
3150
|
});
|
3124
3151
|
}
|
3125
3152
|
catch (e) {
|
3126
|
-
|
3127
|
-
console.warn(`Rule "selection-set-depth" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`, e);
|
3153
|
+
logger.warn(`Rule "${RULE_ID$2}" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`, e);
|
3128
3154
|
}
|
3129
3155
|
},
|
3130
3156
|
};
|
@@ -3506,8 +3532,7 @@ function createGraphqlProcessor() {
|
|
3506
3532
|
}
|
3507
3533
|
}
|
3508
3534
|
catch (e) {
|
3509
|
-
|
3510
|
-
console.warn(`[graphql-eslint/processor]: Unable to process file "${filename}": `, e);
|
3535
|
+
logger.warn(`Unable to process file "${filename}"`, e);
|
3511
3536
|
}
|
3512
3537
|
}
|
3513
3538
|
return [text];
|
@@ -3547,20 +3572,25 @@ const schemaCache = new Map();
|
|
3547
3572
|
function getSchema(options = {}, gqlConfig) {
|
3548
3573
|
const realFilepath = options.filePath ? getOnDiskFilepath(options.filePath) : null;
|
3549
3574
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3550
|
-
const schemaKey = utils.asArray(projectForFile.schema)
|
3551
|
-
.sort()
|
3552
|
-
.join(',');
|
3575
|
+
const schemaKey = utils.asArray(projectForFile.schema).sort().join(',');
|
3553
3576
|
if (!schemaKey) {
|
3554
3577
|
return null;
|
3555
3578
|
}
|
3556
|
-
|
3557
|
-
|
3579
|
+
if (schemaCache.has(schemaKey)) {
|
3580
|
+
return schemaCache.get(schemaKey);
|
3581
|
+
}
|
3582
|
+
let schema;
|
3583
|
+
try {
|
3558
3584
|
schema = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', {
|
3559
3585
|
cache: loaderCache,
|
3560
|
-
...options.schemaOptions
|
3586
|
+
...options.schemaOptions,
|
3561
3587
|
});
|
3562
|
-
schemaCache.set(schemaKey, schema);
|
3563
3588
|
}
|
3589
|
+
catch (e) {
|
3590
|
+
schema = null;
|
3591
|
+
logger.error('Error while loading schema\n', e);
|
3592
|
+
}
|
3593
|
+
schemaCache.set(schemaKey, schema);
|
3564
3594
|
return schema;
|
3565
3595
|
}
|
3566
3596
|
|
@@ -3573,10 +3603,10 @@ const handleVirtualPath = (documents) => {
|
|
3573
3603
|
return source;
|
3574
3604
|
}
|
3575
3605
|
(_a = filepathMap[location]) !== null && _a !== void 0 ? _a : (filepathMap[location] = -1);
|
3576
|
-
const index = filepathMap[location] += 1;
|
3606
|
+
const index = (filepathMap[location] += 1);
|
3577
3607
|
return {
|
3578
3608
|
...source,
|
3579
|
-
location: path.resolve(location, `${index}_document.graphql`)
|
3609
|
+
location: path.resolve(location, `${index}_document.graphql`),
|
3580
3610
|
};
|
3581
3611
|
});
|
3582
3612
|
};
|
@@ -3585,9 +3615,7 @@ const siblingOperationsCache = new Map();
|
|
3585
3615
|
const getSiblings = (filePath, gqlConfig) => {
|
3586
3616
|
const realFilepath = filePath ? getOnDiskFilepath(filePath) : null;
|
3587
3617
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3588
|
-
const documentsKey = utils.asArray(projectForFile.documents)
|
3589
|
-
.sort()
|
3590
|
-
.join(',');
|
3618
|
+
const documentsKey = utils.asArray(projectForFile.documents).sort().join(',');
|
3591
3619
|
if (!documentsKey) {
|
3592
3620
|
return [];
|
3593
3621
|
}
|
@@ -3595,7 +3623,7 @@ const getSiblings = (filePath, gqlConfig) => {
|
|
3595
3623
|
if (!siblings) {
|
3596
3624
|
const documents = projectForFile.loadDocumentsSync(projectForFile.documents, {
|
3597
3625
|
skipGraphQLImport: true,
|
3598
|
-
cache: loaderCache
|
3626
|
+
cache: loaderCache,
|
3599
3627
|
});
|
3600
3628
|
siblings = handleVirtualPath(documents);
|
3601
3629
|
operationsCache.set(documentsKey, siblings);
|
@@ -3608,8 +3636,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3608
3636
|
let printed = false;
|
3609
3637
|
const noopWarn = () => {
|
3610
3638
|
if (!printed) {
|
3611
|
-
|
3612
|
-
console.warn(`getSiblingOperations was called without any operations. Make sure to set "parserOptions.operations" to make this feature available!`);
|
3639
|
+
logger.warn(`getSiblingOperations was called without any operations. Make sure to set "parserOptions.operations" to make this feature available!`);
|
3613
3640
|
printed = true;
|
3614
3641
|
}
|
3615
3642
|
return [];
|
@@ -3673,8 +3700,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3673
3700
|
const name = spread.name.value;
|
3674
3701
|
const fragmentInfo = getFragment(name);
|
3675
3702
|
if (fragmentInfo.length === 0) {
|
3676
|
-
|
3677
|
-
console.warn(`Unable to locate fragment named "${name}", please make sure it's loaded using "parserOptions.operations"`);
|
3703
|
+
logger.warn(`Unable to locate fragment named "${name}", please make sure it's loaded using "parserOptions.operations"`);
|
3678
3704
|
return;
|
3679
3705
|
}
|
3680
3706
|
const fragment = fragmentInfo[0];
|
@@ -3824,17 +3850,9 @@ function parse(code, options) {
|
|
3824
3850
|
}
|
3825
3851
|
function parseForESLint(code, options = {}) {
|
3826
3852
|
const gqlConfig = loadGraphQLConfig(options);
|
3827
|
-
|
3828
|
-
try {
|
3829
|
-
schema = getSchema(options, gqlConfig);
|
3830
|
-
}
|
3831
|
-
catch (e) {
|
3832
|
-
e.message = `[graphql-eslint] Error while loading schema: ${e.message}`;
|
3833
|
-
// eslint-disable-next-line no-console
|
3834
|
-
console.error(e);
|
3835
|
-
}
|
3853
|
+
const schema = getSchema(options, gqlConfig);
|
3836
3854
|
const parserServices = {
|
3837
|
-
hasTypeInfo:
|
3855
|
+
hasTypeInfo: schema !== null,
|
3838
3856
|
schema,
|
3839
3857
|
siblingOperations: getSiblingOperations(options, gqlConfig),
|
3840
3858
|
reachableTypes: getReachableTypes,
|
package/index.mjs
CHANGED
@@ -4,6 +4,7 @@ import { statSync, existsSync, readFileSync } from 'fs';
|
|
4
4
|
import { dirname, extname, basename, relative, resolve } from 'path';
|
5
5
|
import { asArray, parseGraphQLSDL } from '@graphql-tools/utils';
|
6
6
|
import lowerCase from 'lodash.lowercase';
|
7
|
+
import chalk from 'chalk';
|
7
8
|
import depthLimit from 'graphql-depth-limit';
|
8
9
|
import { parseCode } from '@graphql-tools/graphql-tag-pluck';
|
9
10
|
import { loadConfigSync, GraphQLConfig } from 'graphql-config';
|
@@ -186,6 +187,12 @@ function requireGraphQLSchemaFromContext(ruleName, context) {
|
|
186
187
|
}
|
187
188
|
return context.parserServices.schema;
|
188
189
|
}
|
190
|
+
const logger = {
|
191
|
+
// eslint-disable-next-line no-console
|
192
|
+
error: (...args) => console.error(chalk.red('error'), '[graphql-eslint]', chalk(...args)),
|
193
|
+
// eslint-disable-next-line no-console
|
194
|
+
warn: (...args) => console.warn(chalk.yellow('warning'), '[graphql-eslint]', chalk(...args)),
|
195
|
+
};
|
189
196
|
function requireReachableTypesFromContext(ruleName, context) {
|
190
197
|
const schema = requireGraphQLSchemaFromContext(ruleName, context);
|
191
198
|
return context.parserServices.reachableTypes(schema);
|
@@ -320,12 +327,12 @@ function getLocation(loc, fieldName = '', offset) {
|
|
320
327
|
};
|
321
328
|
}
|
322
329
|
|
323
|
-
function validateDocument(context, schema = null, documentNode, rule
|
330
|
+
function validateDocument(context, schema = null, documentNode, rule) {
|
324
331
|
if (documentNode.definitions.length === 0) {
|
325
332
|
return;
|
326
333
|
}
|
327
334
|
try {
|
328
|
-
const validationErrors = schema
|
335
|
+
const validationErrors = schema
|
329
336
|
? validate(schema, documentNode, [rule])
|
330
337
|
: validateSDL(documentNode, schema, [rule]);
|
331
338
|
for (const error of validationErrors) {
|
@@ -436,17 +443,16 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
|
|
436
443
|
},
|
437
444
|
create(context) {
|
438
445
|
if (!ruleFn) {
|
439
|
-
|
440
|
-
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...`);
|
446
|
+
logger.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...`);
|
441
447
|
return {};
|
442
448
|
}
|
443
|
-
const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
|
444
449
|
return {
|
445
450
|
Document(node) {
|
451
|
+
const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
|
446
452
|
const documentNode = getDocumentNode
|
447
453
|
? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
|
448
454
|
: node.rawNode();
|
449
|
-
validateDocument(context, schema, documentNode, ruleFn
|
455
|
+
validateDocument(context, schema, documentNode, ruleFn);
|
450
456
|
},
|
451
457
|
};
|
452
458
|
},
|
@@ -598,7 +604,6 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
598
604
|
description: `A type extension is only valid if the type is defined and has the same kind.`,
|
599
605
|
recommended: false,
|
600
606
|
requiresSchema: true,
|
601
|
-
requiresSchemaToExtend: true,
|
602
607
|
}), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
|
603
608
|
category: ['Schema', 'Operations'],
|
604
609
|
description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
|
@@ -1370,6 +1375,17 @@ const rule$4 = {
|
|
1370
1375
|
type Query {
|
1371
1376
|
users: [User!]!
|
1372
1377
|
}
|
1378
|
+
`,
|
1379
|
+
},
|
1380
|
+
{
|
1381
|
+
title: 'Correct',
|
1382
|
+
usage: [{ FieldDefinition: { style: 'camelCase', ignorePattern: '^(EAN13|UPC|UK)' } }],
|
1383
|
+
code: /* GraphQL */ `
|
1384
|
+
type Product {
|
1385
|
+
EAN13: String
|
1386
|
+
UPC: String
|
1387
|
+
UKFlag: String
|
1388
|
+
}
|
1373
1389
|
`,
|
1374
1390
|
},
|
1375
1391
|
],
|
@@ -1439,6 +1455,10 @@ const rule$4 = {
|
|
1439
1455
|
minItems: 1,
|
1440
1456
|
items: { type: 'string' },
|
1441
1457
|
},
|
1458
|
+
ignorePattern: {
|
1459
|
+
type: 'string',
|
1460
|
+
description: 'Option to skip validation of some words, e.g. acronyms',
|
1461
|
+
},
|
1442
1462
|
},
|
1443
1463
|
},
|
1444
1464
|
},
|
@@ -1493,15 +1513,15 @@ const rule$4 = {
|
|
1493
1513
|
if (!node) {
|
1494
1514
|
return;
|
1495
1515
|
}
|
1496
|
-
const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style } = normalisePropertyOption(selector);
|
1516
|
+
const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style, ignorePattern } = normalisePropertyOption(selector);
|
1497
1517
|
const nodeType = KindToDisplayName[n.kind] || n.kind;
|
1498
1518
|
const nodeName = node.value;
|
1499
1519
|
const error = getError();
|
1500
1520
|
if (error) {
|
1501
1521
|
const { errorMessage, renameToName } = error;
|
1502
|
-
const [
|
1503
|
-
const [
|
1504
|
-
const suggestedName =
|
1522
|
+
const [leadingUnderscores] = nodeName.match(/^_*/);
|
1523
|
+
const [trailingUnderscores] = nodeName.match(/_*$/);
|
1524
|
+
const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
|
1505
1525
|
context.report({
|
1506
1526
|
loc: getLocation(node.loc, node.value),
|
1507
1527
|
message: `${nodeType} "${nodeName}" should ${errorMessage}`,
|
@@ -1515,6 +1535,9 @@ const rule$4 = {
|
|
1515
1535
|
}
|
1516
1536
|
function getError() {
|
1517
1537
|
const name = nodeName.replace(/(^_+)|(_+$)/g, '');
|
1538
|
+
if (ignorePattern && new RegExp(ignorePattern, 'u').test(name)) {
|
1539
|
+
return;
|
1540
|
+
}
|
1518
1541
|
if (prefix && !name.startsWith(prefix)) {
|
1519
1542
|
return {
|
1520
1543
|
errorMessage: `have "${prefix}" prefix`,
|
@@ -1541,8 +1564,12 @@ const rule$4 = {
|
|
1541
1564
|
renameToName: name.replace(new RegExp(`${forbiddenSuffix}$`), ''),
|
1542
1565
|
};
|
1543
1566
|
}
|
1567
|
+
// Style is optional
|
1568
|
+
if (!style) {
|
1569
|
+
return;
|
1570
|
+
}
|
1544
1571
|
const caseRegex = StyleToRegex[style];
|
1545
|
-
if (
|
1572
|
+
if (!caseRegex.test(name)) {
|
1546
1573
|
return {
|
1547
1574
|
errorMessage: `be in ${style} format`,
|
1548
1575
|
renameToName: convertCase(style, name),
|
@@ -3009,12 +3036,13 @@ const rule$j = {
|
|
3009
3036
|
},
|
3010
3037
|
};
|
3011
3038
|
|
3039
|
+
const RULE_ID$2 = 'selection-set-depth';
|
3012
3040
|
const rule$k = {
|
3013
3041
|
meta: {
|
3014
3042
|
docs: {
|
3015
3043
|
category: 'Operations',
|
3016
3044
|
description: `Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://github.com/stems/graphql-depth-limit).`,
|
3017
|
-
url:
|
3045
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
|
3018
3046
|
requiresSiblings: true,
|
3019
3047
|
examples: [
|
3020
3048
|
{
|
@@ -3088,11 +3116,10 @@ const rule$k = {
|
|
3088
3116
|
create(context) {
|
3089
3117
|
let siblings = null;
|
3090
3118
|
try {
|
3091
|
-
siblings = requireSiblingsOperations(
|
3119
|
+
siblings = requireSiblingsOperations(RULE_ID$2, context);
|
3092
3120
|
}
|
3093
3121
|
catch (e) {
|
3094
|
-
|
3095
|
-
console.warn(`Rule "selection-set-depth" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
|
3122
|
+
logger.warn(`Rule "${RULE_ID$2}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
|
3096
3123
|
}
|
3097
3124
|
const { maxDepth } = context.options[0];
|
3098
3125
|
const ignore = context.options[0].ignore || [];
|
@@ -3117,8 +3144,7 @@ const rule$k = {
|
|
3117
3144
|
});
|
3118
3145
|
}
|
3119
3146
|
catch (e) {
|
3120
|
-
|
3121
|
-
console.warn(`Rule "selection-set-depth" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`, e);
|
3147
|
+
logger.warn(`Rule "${RULE_ID$2}" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`, e);
|
3122
3148
|
}
|
3123
3149
|
},
|
3124
3150
|
};
|
@@ -3500,8 +3526,7 @@ function createGraphqlProcessor() {
|
|
3500
3526
|
}
|
3501
3527
|
}
|
3502
3528
|
catch (e) {
|
3503
|
-
|
3504
|
-
console.warn(`[graphql-eslint/processor]: Unable to process file "${filename}": `, e);
|
3529
|
+
logger.warn(`Unable to process file "${filename}"`, e);
|
3505
3530
|
}
|
3506
3531
|
}
|
3507
3532
|
return [text];
|
@@ -3541,20 +3566,25 @@ const schemaCache = new Map();
|
|
3541
3566
|
function getSchema(options = {}, gqlConfig) {
|
3542
3567
|
const realFilepath = options.filePath ? getOnDiskFilepath(options.filePath) : null;
|
3543
3568
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3544
|
-
const schemaKey = asArray(projectForFile.schema)
|
3545
|
-
.sort()
|
3546
|
-
.join(',');
|
3569
|
+
const schemaKey = asArray(projectForFile.schema).sort().join(',');
|
3547
3570
|
if (!schemaKey) {
|
3548
3571
|
return null;
|
3549
3572
|
}
|
3550
|
-
|
3551
|
-
|
3573
|
+
if (schemaCache.has(schemaKey)) {
|
3574
|
+
return schemaCache.get(schemaKey);
|
3575
|
+
}
|
3576
|
+
let schema;
|
3577
|
+
try {
|
3552
3578
|
schema = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', {
|
3553
3579
|
cache: loaderCache,
|
3554
|
-
...options.schemaOptions
|
3580
|
+
...options.schemaOptions,
|
3555
3581
|
});
|
3556
|
-
schemaCache.set(schemaKey, schema);
|
3557
3582
|
}
|
3583
|
+
catch (e) {
|
3584
|
+
schema = null;
|
3585
|
+
logger.error('Error while loading schema\n', e);
|
3586
|
+
}
|
3587
|
+
schemaCache.set(schemaKey, schema);
|
3558
3588
|
return schema;
|
3559
3589
|
}
|
3560
3590
|
|
@@ -3567,10 +3597,10 @@ const handleVirtualPath = (documents) => {
|
|
3567
3597
|
return source;
|
3568
3598
|
}
|
3569
3599
|
(_a = filepathMap[location]) !== null && _a !== void 0 ? _a : (filepathMap[location] = -1);
|
3570
|
-
const index = filepathMap[location] += 1;
|
3600
|
+
const index = (filepathMap[location] += 1);
|
3571
3601
|
return {
|
3572
3602
|
...source,
|
3573
|
-
location: resolve(location, `${index}_document.graphql`)
|
3603
|
+
location: resolve(location, `${index}_document.graphql`),
|
3574
3604
|
};
|
3575
3605
|
});
|
3576
3606
|
};
|
@@ -3579,9 +3609,7 @@ const siblingOperationsCache = new Map();
|
|
3579
3609
|
const getSiblings = (filePath, gqlConfig) => {
|
3580
3610
|
const realFilepath = filePath ? getOnDiskFilepath(filePath) : null;
|
3581
3611
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3582
|
-
const documentsKey = asArray(projectForFile.documents)
|
3583
|
-
.sort()
|
3584
|
-
.join(',');
|
3612
|
+
const documentsKey = asArray(projectForFile.documents).sort().join(',');
|
3585
3613
|
if (!documentsKey) {
|
3586
3614
|
return [];
|
3587
3615
|
}
|
@@ -3589,7 +3617,7 @@ const getSiblings = (filePath, gqlConfig) => {
|
|
3589
3617
|
if (!siblings) {
|
3590
3618
|
const documents = projectForFile.loadDocumentsSync(projectForFile.documents, {
|
3591
3619
|
skipGraphQLImport: true,
|
3592
|
-
cache: loaderCache
|
3620
|
+
cache: loaderCache,
|
3593
3621
|
});
|
3594
3622
|
siblings = handleVirtualPath(documents);
|
3595
3623
|
operationsCache.set(documentsKey, siblings);
|
@@ -3602,8 +3630,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3602
3630
|
let printed = false;
|
3603
3631
|
const noopWarn = () => {
|
3604
3632
|
if (!printed) {
|
3605
|
-
|
3606
|
-
console.warn(`getSiblingOperations was called without any operations. Make sure to set "parserOptions.operations" to make this feature available!`);
|
3633
|
+
logger.warn(`getSiblingOperations was called without any operations. Make sure to set "parserOptions.operations" to make this feature available!`);
|
3607
3634
|
printed = true;
|
3608
3635
|
}
|
3609
3636
|
return [];
|
@@ -3667,8 +3694,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3667
3694
|
const name = spread.name.value;
|
3668
3695
|
const fragmentInfo = getFragment(name);
|
3669
3696
|
if (fragmentInfo.length === 0) {
|
3670
|
-
|
3671
|
-
console.warn(`Unable to locate fragment named "${name}", please make sure it's loaded using "parserOptions.operations"`);
|
3697
|
+
logger.warn(`Unable to locate fragment named "${name}", please make sure it's loaded using "parserOptions.operations"`);
|
3672
3698
|
return;
|
3673
3699
|
}
|
3674
3700
|
const fragment = fragmentInfo[0];
|
@@ -3818,17 +3844,9 @@ function parse(code, options) {
|
|
3818
3844
|
}
|
3819
3845
|
function parseForESLint(code, options = {}) {
|
3820
3846
|
const gqlConfig = loadGraphQLConfig(options);
|
3821
|
-
|
3822
|
-
try {
|
3823
|
-
schema = getSchema(options, gqlConfig);
|
3824
|
-
}
|
3825
|
-
catch (e) {
|
3826
|
-
e.message = `[graphql-eslint] Error while loading schema: ${e.message}`;
|
3827
|
-
// eslint-disable-next-line no-console
|
3828
|
-
console.error(e);
|
3829
|
-
}
|
3847
|
+
const schema = getSchema(options, gqlConfig);
|
3830
3848
|
const parserServices = {
|
3831
|
-
hasTypeInfo:
|
3849
|
+
hasTypeInfo: schema !== null,
|
3832
3850
|
schema,
|
3833
3851
|
siblingOperations: getSiblingOperations(options, gqlConfig),
|
3834
3852
|
reachableTypes: getReachableTypes,
|
package/package.json
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
{
|
2
2
|
"name": "@graphql-eslint/eslint-plugin",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.4.0-alpha-d7fc26d.0",
|
4
4
|
"description": "GraphQL plugin for ESLint",
|
5
5
|
"sideEffects": false,
|
6
6
|
"peerDependencies": {
|
7
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"
|
8
8
|
},
|
9
9
|
"dependencies": {
|
10
|
-
"@babel/code-frame": "7.16.
|
10
|
+
"@babel/code-frame": "7.16.7",
|
11
11
|
"@graphql-tools/code-file-loader": "7.2.3",
|
12
12
|
"@graphql-tools/graphql-tag-pluck": "7.1.4",
|
13
13
|
"@graphql-tools/utils": "8.5.5",
|
14
|
+
"chalk": "4.1.2",
|
14
15
|
"graphql-config": "4.1.0",
|
15
16
|
"graphql-depth-limit": "1.1.0",
|
16
17
|
"lodash.lowercase": "4.3.0"
|
package/types.d.ts
CHANGED
package/utils.d.ts
CHANGED
@@ -6,6 +6,10 @@ import { SiblingOperations } from './sibling-operations';
|
|
6
6
|
import { UsedFields, ReachableTypes } from './graphql-ast';
|
7
7
|
export declare function requireSiblingsOperations(ruleName: string, context: GraphQLESLintRuleContext): SiblingOperations | never;
|
8
8
|
export declare function requireGraphQLSchemaFromContext(ruleName: string, context: GraphQLESLintRuleContext): GraphQLSchema | never;
|
9
|
+
export declare const logger: {
|
10
|
+
error: (...args: any[]) => void;
|
11
|
+
warn: (...args: any[]) => void;
|
12
|
+
};
|
9
13
|
export declare function requireReachableTypesFromContext(ruleName: string, context: GraphQLESLintRuleContext): ReachableTypes | never;
|
10
14
|
export declare function requireUsedFieldsFromContext(ruleName: string, context: GraphQLESLintRuleContext): UsedFields | never;
|
11
15
|
export declare function extractTokens(source: Source): AST.Token[];
|