@graphql-eslint/eslint-plugin 3.3.0-alpha-d23e9e2.0 → 3.4.0-alpha-c01d913.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/graphql-config.d.ts +1 -0
- package/index.js +147 -75
- package/index.mjs +147 -75
- package/package.json +2 -1
- package/rules/naming-convention.d.ts +1 -0
- package/utils.d.ts +10 -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/graphql-config.d.ts
CHANGED
package/index.js
CHANGED
@@ -10,10 +10,11 @@ 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
|
14
|
-
const graphqlTagPluck = require('@graphql-tools/graphql-tag-pluck');
|
13
|
+
const chalk = _interopDefault(require('chalk'));
|
15
14
|
const graphqlConfig = require('graphql-config');
|
16
15
|
const codeFileLoader = require('@graphql-tools/code-file-loader');
|
16
|
+
const depthLimit = _interopDefault(require('graphql-depth-limit'));
|
17
|
+
const graphqlTagPluck = require('@graphql-tools/graphql-tag-pluck');
|
17
18
|
const eslint = require('eslint');
|
18
19
|
const codeFrame = require('@babel/code-frame');
|
19
20
|
|
@@ -174,6 +175,47 @@ const configs = {
|
|
174
175
|
'operations-all': operationsAllConfig,
|
175
176
|
};
|
176
177
|
|
178
|
+
let graphQLConfig;
|
179
|
+
function loadCachedGraphQLConfig(options) {
|
180
|
+
// We don't want cache config on test environment
|
181
|
+
// Otherwise schema and documents will be same for all tests
|
182
|
+
if (process.env.NODE_ENV !== 'test' && graphQLConfig) {
|
183
|
+
return graphQLConfig;
|
184
|
+
}
|
185
|
+
graphQLConfig = loadGraphQLConfig(options);
|
186
|
+
return graphQLConfig;
|
187
|
+
}
|
188
|
+
function loadGraphQLConfig(options) {
|
189
|
+
const onDiskConfig = options.skipGraphQLConfig
|
190
|
+
? null
|
191
|
+
: graphqlConfig.loadConfigSync({
|
192
|
+
throwOnEmpty: false,
|
193
|
+
throwOnMissing: false,
|
194
|
+
extensions: [addCodeFileLoaderExtension],
|
195
|
+
});
|
196
|
+
const configOptions = options.projects
|
197
|
+
? { projects: options.projects }
|
198
|
+
: {
|
199
|
+
schema: (options.schema || ''),
|
200
|
+
documents: options.documents || options.operations,
|
201
|
+
extensions: options.extensions,
|
202
|
+
include: options.include,
|
203
|
+
exclude: options.exclude,
|
204
|
+
};
|
205
|
+
graphQLConfig =
|
206
|
+
onDiskConfig ||
|
207
|
+
new graphqlConfig.GraphQLConfig({
|
208
|
+
config: configOptions,
|
209
|
+
filepath: 'virtual-config',
|
210
|
+
}, [addCodeFileLoaderExtension]);
|
211
|
+
return graphQLConfig;
|
212
|
+
}
|
213
|
+
const addCodeFileLoaderExtension = api => {
|
214
|
+
api.loaders.schema.register(new codeFileLoader.CodeFileLoader());
|
215
|
+
api.loaders.documents.register(new codeFileLoader.CodeFileLoader());
|
216
|
+
return { name: 'graphql-eslint-loaders' };
|
217
|
+
};
|
218
|
+
|
177
219
|
function requireSiblingsOperations(ruleName, context) {
|
178
220
|
if (!context.parserServices) {
|
179
221
|
throw new Error(`Rule '${ruleName}' requires 'parserOptions.operations' to be set and loaded. See http://bit.ly/graphql-eslint-operations for more info`);
|
@@ -192,6 +234,43 @@ function requireGraphQLSchemaFromContext(ruleName, context) {
|
|
192
234
|
}
|
193
235
|
return context.parserServices.schema;
|
194
236
|
}
|
237
|
+
const logger = {
|
238
|
+
// eslint-disable-next-line no-console
|
239
|
+
error: (...args) => console.error(chalk.red('error'), '[graphql-eslint]', chalk(...args)),
|
240
|
+
// eslint-disable-next-line no-console
|
241
|
+
warn: (...args) => console.warn(chalk.yellow('warning'), '[graphql-eslint]', chalk(...args)),
|
242
|
+
};
|
243
|
+
const schemaToExtendCache = new Map();
|
244
|
+
const getGraphQLSchemaToExtend = (ruleId, context) => {
|
245
|
+
// If schema is not loaded, there is no reason to make partial schema aka schemaToExtend
|
246
|
+
if (!context.parserServices.hasTypeInfo) {
|
247
|
+
if (!getGraphQLSchemaToExtend.warningPrintedMap[ruleId]) {
|
248
|
+
logger.warn(`Rule "${ruleId}" works best with schema loaded. See http://bit.ly/graphql-eslint-schema for more info`);
|
249
|
+
getGraphQLSchemaToExtend.warningPrintedMap[ruleId] = true;
|
250
|
+
}
|
251
|
+
return null;
|
252
|
+
}
|
253
|
+
const filename = context.getPhysicalFilename();
|
254
|
+
if (!schemaToExtendCache.has(filename)) {
|
255
|
+
const { schema, schemaOptions } = context.parserOptions;
|
256
|
+
const gqlConfig = loadGraphQLConfig({ schema });
|
257
|
+
const projectForFile = gqlConfig.getProjectForFile(filename);
|
258
|
+
let schemaToExtend;
|
259
|
+
try {
|
260
|
+
schemaToExtend = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', {
|
261
|
+
...schemaOptions,
|
262
|
+
ignore: filename,
|
263
|
+
});
|
264
|
+
}
|
265
|
+
catch (_a) {
|
266
|
+
// If error throws just ignore it because maybe schema is located in 1 file
|
267
|
+
schemaToExtend = null;
|
268
|
+
}
|
269
|
+
schemaToExtendCache.set(filename, schemaToExtend);
|
270
|
+
}
|
271
|
+
return schemaToExtendCache.get(filename);
|
272
|
+
};
|
273
|
+
getGraphQLSchemaToExtend.warningPrintedMap = Object.create(null);
|
195
274
|
function requireReachableTypesFromContext(ruleName, context) {
|
196
275
|
const schema = requireGraphQLSchemaFromContext(ruleName, context);
|
197
276
|
return context.parserServices.reachableTypes(schema);
|
@@ -442,11 +521,16 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
|
|
442
521
|
},
|
443
522
|
create(context) {
|
444
523
|
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...`);
|
524
|
+
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
525
|
return {};
|
448
526
|
}
|
449
|
-
|
527
|
+
let schema;
|
528
|
+
if (docs.requiresSchemaToExtend) {
|
529
|
+
schema = getGraphQLSchemaToExtend(ruleId, context);
|
530
|
+
}
|
531
|
+
else if (docs.requiresSchema) {
|
532
|
+
schema = requireGraphQLSchemaFromContext(ruleId, context);
|
533
|
+
}
|
450
534
|
return {
|
451
535
|
Document(node) {
|
452
536
|
const documentNode = getDocumentNode
|
@@ -632,9 +716,13 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
632
716
|
category: 'Schema',
|
633
717
|
description: `A GraphQL enum type is only valid if all its values are uniquely named.`,
|
634
718
|
recommended: false,
|
719
|
+
requiresSchema: true,
|
720
|
+
requiresSchemaToExtend: true,
|
635
721
|
}), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
|
636
722
|
category: 'Schema',
|
637
723
|
description: `A GraphQL complex type is only valid if all its fields are uniquely named.`,
|
724
|
+
requiresSchema: true,
|
725
|
+
requiresSchemaToExtend: true,
|
638
726
|
}), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
|
639
727
|
category: 'Operations',
|
640
728
|
description: `A GraphQL input object value is only valid if all supplied fields are uniquely named.`,
|
@@ -1376,6 +1464,17 @@ const rule$4 = {
|
|
1376
1464
|
type Query {
|
1377
1465
|
users: [User!]!
|
1378
1466
|
}
|
1467
|
+
`,
|
1468
|
+
},
|
1469
|
+
{
|
1470
|
+
title: 'Correct',
|
1471
|
+
usage: [{ FieldDefinition: { style: 'camelCase', ignorePattern: '^(EAN13|UPC|UK)' } }],
|
1472
|
+
code: /* GraphQL */ `
|
1473
|
+
type Product {
|
1474
|
+
EAN13: String
|
1475
|
+
UPC: String
|
1476
|
+
UKFlag: String
|
1477
|
+
}
|
1379
1478
|
`,
|
1380
1479
|
},
|
1381
1480
|
],
|
@@ -1445,6 +1544,10 @@ const rule$4 = {
|
|
1445
1544
|
minItems: 1,
|
1446
1545
|
items: { type: 'string' },
|
1447
1546
|
},
|
1547
|
+
ignorePattern: {
|
1548
|
+
type: 'string',
|
1549
|
+
description: 'Option to skip validation of some words, e.g. acronyms',
|
1550
|
+
},
|
1448
1551
|
},
|
1449
1552
|
},
|
1450
1553
|
},
|
@@ -1499,15 +1602,15 @@ const rule$4 = {
|
|
1499
1602
|
if (!node) {
|
1500
1603
|
return;
|
1501
1604
|
}
|
1502
|
-
const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style } = normalisePropertyOption(selector);
|
1605
|
+
const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style, ignorePattern } = normalisePropertyOption(selector);
|
1503
1606
|
const nodeType = KindToDisplayName[n.kind] || n.kind;
|
1504
1607
|
const nodeName = node.value;
|
1505
1608
|
const error = getError();
|
1506
1609
|
if (error) {
|
1507
1610
|
const { errorMessage, renameToName } = error;
|
1508
|
-
const [
|
1509
|
-
const [
|
1510
|
-
const suggestedName =
|
1611
|
+
const [leadingUnderscores] = nodeName.match(/^_*/);
|
1612
|
+
const [trailingUnderscores] = nodeName.match(/_*$/);
|
1613
|
+
const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
|
1511
1614
|
context.report({
|
1512
1615
|
loc: getLocation(node.loc, node.value),
|
1513
1616
|
message: `${nodeType} "${nodeName}" should ${errorMessage}`,
|
@@ -1521,6 +1624,9 @@ const rule$4 = {
|
|
1521
1624
|
}
|
1522
1625
|
function getError() {
|
1523
1626
|
const name = nodeName.replace(/(^_+)|(_+$)/g, '');
|
1627
|
+
if (ignorePattern && new RegExp(ignorePattern, 'u').test(name)) {
|
1628
|
+
return;
|
1629
|
+
}
|
1524
1630
|
if (prefix && !name.startsWith(prefix)) {
|
1525
1631
|
return {
|
1526
1632
|
errorMessage: `have "${prefix}" prefix`,
|
@@ -1547,8 +1653,12 @@ const rule$4 = {
|
|
1547
1653
|
renameToName: name.replace(new RegExp(`${forbiddenSuffix}$`), ''),
|
1548
1654
|
};
|
1549
1655
|
}
|
1656
|
+
// Style is optional
|
1657
|
+
if (!style) {
|
1658
|
+
return;
|
1659
|
+
}
|
1550
1660
|
const caseRegex = StyleToRegex[style];
|
1551
|
-
if (
|
1661
|
+
if (!caseRegex.test(name)) {
|
1552
1662
|
return {
|
1553
1663
|
errorMessage: `be in ${style} format`,
|
1554
1664
|
renameToName: convertCase(style, name),
|
@@ -3015,12 +3125,13 @@ const rule$j = {
|
|
3015
3125
|
},
|
3016
3126
|
};
|
3017
3127
|
|
3128
|
+
const RULE_ID$2 = 'selection-set-depth';
|
3018
3129
|
const rule$k = {
|
3019
3130
|
meta: {
|
3020
3131
|
docs: {
|
3021
3132
|
category: 'Operations',
|
3022
3133
|
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:
|
3134
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
|
3024
3135
|
requiresSiblings: true,
|
3025
3136
|
examples: [
|
3026
3137
|
{
|
@@ -3094,11 +3205,10 @@ const rule$k = {
|
|
3094
3205
|
create(context) {
|
3095
3206
|
let siblings = null;
|
3096
3207
|
try {
|
3097
|
-
siblings = requireSiblingsOperations(
|
3208
|
+
siblings = requireSiblingsOperations(RULE_ID$2, context);
|
3098
3209
|
}
|
3099
3210
|
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`);
|
3211
|
+
logger.warn(`Rule "${RULE_ID$2}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
|
3102
3212
|
}
|
3103
3213
|
const { maxDepth } = context.options[0];
|
3104
3214
|
const ignore = context.options[0].ignore || [];
|
@@ -3123,8 +3233,7 @@ const rule$k = {
|
|
3123
3233
|
});
|
3124
3234
|
}
|
3125
3235
|
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);
|
3236
|
+
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
3237
|
}
|
3129
3238
|
},
|
3130
3239
|
};
|
@@ -3506,8 +3615,7 @@ function createGraphqlProcessor() {
|
|
3506
3615
|
}
|
3507
3616
|
}
|
3508
3617
|
catch (e) {
|
3509
|
-
|
3510
|
-
console.warn(`[graphql-eslint/processor]: Unable to process file "${filename}": `, e);
|
3618
|
+
logger.warn(`Unable to process file "${filename}"`, e);
|
3511
3619
|
}
|
3512
3620
|
}
|
3513
3621
|
return [text];
|
@@ -3547,20 +3655,25 @@ const schemaCache = new Map();
|
|
3547
3655
|
function getSchema(options = {}, gqlConfig) {
|
3548
3656
|
const realFilepath = options.filePath ? getOnDiskFilepath(options.filePath) : null;
|
3549
3657
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3550
|
-
const schemaKey = utils.asArray(projectForFile.schema)
|
3551
|
-
.sort()
|
3552
|
-
.join(',');
|
3658
|
+
const schemaKey = utils.asArray(projectForFile.schema).sort().join(',');
|
3553
3659
|
if (!schemaKey) {
|
3554
3660
|
return null;
|
3555
3661
|
}
|
3556
|
-
|
3557
|
-
|
3662
|
+
if (schemaCache.has(schemaKey)) {
|
3663
|
+
return schemaCache.get(schemaKey);
|
3664
|
+
}
|
3665
|
+
let schema;
|
3666
|
+
try {
|
3558
3667
|
schema = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', {
|
3559
3668
|
cache: loaderCache,
|
3560
|
-
...options.schemaOptions
|
3669
|
+
...options.schemaOptions,
|
3561
3670
|
});
|
3562
|
-
schemaCache.set(schemaKey, schema);
|
3563
3671
|
}
|
3672
|
+
catch (e) {
|
3673
|
+
schema = null;
|
3674
|
+
logger.error('Error while loading schema\n', e);
|
3675
|
+
}
|
3676
|
+
schemaCache.set(schemaKey, schema);
|
3564
3677
|
return schema;
|
3565
3678
|
}
|
3566
3679
|
|
@@ -3573,10 +3686,10 @@ const handleVirtualPath = (documents) => {
|
|
3573
3686
|
return source;
|
3574
3687
|
}
|
3575
3688
|
(_a = filepathMap[location]) !== null && _a !== void 0 ? _a : (filepathMap[location] = -1);
|
3576
|
-
const index = filepathMap[location] += 1;
|
3689
|
+
const index = (filepathMap[location] += 1);
|
3577
3690
|
return {
|
3578
3691
|
...source,
|
3579
|
-
location: path.resolve(location, `${index}_document.graphql`)
|
3692
|
+
location: path.resolve(location, `${index}_document.graphql`),
|
3580
3693
|
};
|
3581
3694
|
});
|
3582
3695
|
};
|
@@ -3585,9 +3698,7 @@ const siblingOperationsCache = new Map();
|
|
3585
3698
|
const getSiblings = (filePath, gqlConfig) => {
|
3586
3699
|
const realFilepath = filePath ? getOnDiskFilepath(filePath) : null;
|
3587
3700
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3588
|
-
const documentsKey = utils.asArray(projectForFile.documents)
|
3589
|
-
.sort()
|
3590
|
-
.join(',');
|
3701
|
+
const documentsKey = utils.asArray(projectForFile.documents).sort().join(',');
|
3591
3702
|
if (!documentsKey) {
|
3592
3703
|
return [];
|
3593
3704
|
}
|
@@ -3595,7 +3706,7 @@ const getSiblings = (filePath, gqlConfig) => {
|
|
3595
3706
|
if (!siblings) {
|
3596
3707
|
const documents = projectForFile.loadDocumentsSync(projectForFile.documents, {
|
3597
3708
|
skipGraphQLImport: true,
|
3598
|
-
cache: loaderCache
|
3709
|
+
cache: loaderCache,
|
3599
3710
|
});
|
3600
3711
|
siblings = handleVirtualPath(documents);
|
3601
3712
|
operationsCache.set(documentsKey, siblings);
|
@@ -3608,8 +3719,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3608
3719
|
let printed = false;
|
3609
3720
|
const noopWarn = () => {
|
3610
3721
|
if (!printed) {
|
3611
|
-
|
3612
|
-
console.warn(`getSiblingOperations was called without any operations. Make sure to set "parserOptions.operations" to make this feature available!`);
|
3722
|
+
logger.warn(`getSiblingOperations was called without any operations. Make sure to set "parserOptions.operations" to make this feature available!`);
|
3613
3723
|
printed = true;
|
3614
3724
|
}
|
3615
3725
|
return [];
|
@@ -3673,8 +3783,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3673
3783
|
const name = spread.name.value;
|
3674
3784
|
const fragmentInfo = getFragment(name);
|
3675
3785
|
if (fragmentInfo.length === 0) {
|
3676
|
-
|
3677
|
-
console.warn(`Unable to locate fragment named "${name}", please make sure it's loaded using "parserOptions.operations"`);
|
3786
|
+
logger.warn(`Unable to locate fragment named "${name}", please make sure it's loaded using "parserOptions.operations"`);
|
3678
3787
|
return;
|
3679
3788
|
}
|
3680
3789
|
const fragment = fragmentInfo[0];
|
@@ -3704,43 +3813,6 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3704
3813
|
return siblingOperations;
|
3705
3814
|
}
|
3706
3815
|
|
3707
|
-
let graphQLConfig;
|
3708
|
-
function loadGraphQLConfig(options) {
|
3709
|
-
// We don't want cache config on test environment
|
3710
|
-
// Otherwise schema and documents will be same for all tests
|
3711
|
-
if (process.env.NODE_ENV !== 'test' && graphQLConfig) {
|
3712
|
-
return graphQLConfig;
|
3713
|
-
}
|
3714
|
-
const onDiskConfig = options.skipGraphQLConfig
|
3715
|
-
? null
|
3716
|
-
: graphqlConfig.loadConfigSync({
|
3717
|
-
throwOnEmpty: false,
|
3718
|
-
throwOnMissing: false,
|
3719
|
-
extensions: [addCodeFileLoaderExtension],
|
3720
|
-
});
|
3721
|
-
const configOptions = options.projects
|
3722
|
-
? { projects: options.projects }
|
3723
|
-
: {
|
3724
|
-
schema: (options.schema || ''),
|
3725
|
-
documents: options.documents || options.operations,
|
3726
|
-
extensions: options.extensions,
|
3727
|
-
include: options.include,
|
3728
|
-
exclude: options.exclude,
|
3729
|
-
};
|
3730
|
-
graphQLConfig =
|
3731
|
-
onDiskConfig ||
|
3732
|
-
new graphqlConfig.GraphQLConfig({
|
3733
|
-
config: configOptions,
|
3734
|
-
filepath: 'virtual-config',
|
3735
|
-
}, [addCodeFileLoaderExtension]);
|
3736
|
-
return graphQLConfig;
|
3737
|
-
}
|
3738
|
-
const addCodeFileLoaderExtension = api => {
|
3739
|
-
api.loaders.schema.register(new codeFileLoader.CodeFileLoader());
|
3740
|
-
api.loaders.documents.register(new codeFileLoader.CodeFileLoader());
|
3741
|
-
return { name: 'graphql-eslint-loaders' };
|
3742
|
-
};
|
3743
|
-
|
3744
3816
|
let reachableTypesCache;
|
3745
3817
|
function getReachableTypes(schema) {
|
3746
3818
|
// We don't want cache reachableTypes on test environment
|
@@ -3823,7 +3895,7 @@ function parse(code, options) {
|
|
3823
3895
|
return parseForESLint(code, options).ast;
|
3824
3896
|
}
|
3825
3897
|
function parseForESLint(code, options = {}) {
|
3826
|
-
const gqlConfig =
|
3898
|
+
const gqlConfig = loadCachedGraphQLConfig(options);
|
3827
3899
|
const schema = getSchema(options, gqlConfig);
|
3828
3900
|
const parserServices = {
|
3829
3901
|
hasTypeInfo: schema !== null,
|
@@ -3855,6 +3927,7 @@ function parseForESLint(code, options = {}) {
|
|
3855
3927
|
};
|
3856
3928
|
}
|
3857
3929
|
catch (e) {
|
3930
|
+
e.message = `[graphql-eslint] ${e.message}`;
|
3858
3931
|
// In case of GraphQL parser error, we report it to ESLint as a parser error that matches the requirements
|
3859
3932
|
// of ESLint. This will make sure to display it correctly in IDEs and lint results.
|
3860
3933
|
if (e instanceof graphql.GraphQLError) {
|
@@ -3862,11 +3935,10 @@ function parseForESLint(code, options = {}) {
|
|
3862
3935
|
index: e.positions[0],
|
3863
3936
|
lineNumber: e.locations[0].line,
|
3864
3937
|
column: e.locations[0].column,
|
3865
|
-
message:
|
3938
|
+
message: e.message,
|
3866
3939
|
};
|
3867
3940
|
throw eslintError;
|
3868
3941
|
}
|
3869
|
-
e.message = `[graphql-eslint]: ${e.message}`;
|
3870
3942
|
throw e;
|
3871
3943
|
}
|
3872
3944
|
}
|
package/index.mjs
CHANGED
@@ -4,10 +4,11 @@ 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
|
8
|
-
import { parseCode } from '@graphql-tools/graphql-tag-pluck';
|
7
|
+
import chalk from 'chalk';
|
9
8
|
import { loadConfigSync, GraphQLConfig } from 'graphql-config';
|
10
9
|
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
|
10
|
+
import depthLimit from 'graphql-depth-limit';
|
11
|
+
import { parseCode } from '@graphql-tools/graphql-tag-pluck';
|
11
12
|
import { RuleTester, Linter } from 'eslint';
|
12
13
|
import { codeFrameColumns } from '@babel/code-frame';
|
13
14
|
|
@@ -168,6 +169,47 @@ const configs = {
|
|
168
169
|
'operations-all': operationsAllConfig,
|
169
170
|
};
|
170
171
|
|
172
|
+
let graphQLConfig;
|
173
|
+
function loadCachedGraphQLConfig(options) {
|
174
|
+
// We don't want cache config on test environment
|
175
|
+
// Otherwise schema and documents will be same for all tests
|
176
|
+
if (process.env.NODE_ENV !== 'test' && graphQLConfig) {
|
177
|
+
return graphQLConfig;
|
178
|
+
}
|
179
|
+
graphQLConfig = loadGraphQLConfig(options);
|
180
|
+
return graphQLConfig;
|
181
|
+
}
|
182
|
+
function loadGraphQLConfig(options) {
|
183
|
+
const onDiskConfig = options.skipGraphQLConfig
|
184
|
+
? null
|
185
|
+
: loadConfigSync({
|
186
|
+
throwOnEmpty: false,
|
187
|
+
throwOnMissing: false,
|
188
|
+
extensions: [addCodeFileLoaderExtension],
|
189
|
+
});
|
190
|
+
const configOptions = options.projects
|
191
|
+
? { projects: options.projects }
|
192
|
+
: {
|
193
|
+
schema: (options.schema || ''),
|
194
|
+
documents: options.documents || options.operations,
|
195
|
+
extensions: options.extensions,
|
196
|
+
include: options.include,
|
197
|
+
exclude: options.exclude,
|
198
|
+
};
|
199
|
+
graphQLConfig =
|
200
|
+
onDiskConfig ||
|
201
|
+
new GraphQLConfig({
|
202
|
+
config: configOptions,
|
203
|
+
filepath: 'virtual-config',
|
204
|
+
}, [addCodeFileLoaderExtension]);
|
205
|
+
return graphQLConfig;
|
206
|
+
}
|
207
|
+
const addCodeFileLoaderExtension = api => {
|
208
|
+
api.loaders.schema.register(new CodeFileLoader());
|
209
|
+
api.loaders.documents.register(new CodeFileLoader());
|
210
|
+
return { name: 'graphql-eslint-loaders' };
|
211
|
+
};
|
212
|
+
|
171
213
|
function requireSiblingsOperations(ruleName, context) {
|
172
214
|
if (!context.parserServices) {
|
173
215
|
throw new Error(`Rule '${ruleName}' requires 'parserOptions.operations' to be set and loaded. See http://bit.ly/graphql-eslint-operations for more info`);
|
@@ -186,6 +228,43 @@ function requireGraphQLSchemaFromContext(ruleName, context) {
|
|
186
228
|
}
|
187
229
|
return context.parserServices.schema;
|
188
230
|
}
|
231
|
+
const logger = {
|
232
|
+
// eslint-disable-next-line no-console
|
233
|
+
error: (...args) => console.error(chalk.red('error'), '[graphql-eslint]', chalk(...args)),
|
234
|
+
// eslint-disable-next-line no-console
|
235
|
+
warn: (...args) => console.warn(chalk.yellow('warning'), '[graphql-eslint]', chalk(...args)),
|
236
|
+
};
|
237
|
+
const schemaToExtendCache = new Map();
|
238
|
+
const getGraphQLSchemaToExtend = (ruleId, context) => {
|
239
|
+
// If schema is not loaded, there is no reason to make partial schema aka schemaToExtend
|
240
|
+
if (!context.parserServices.hasTypeInfo) {
|
241
|
+
if (!getGraphQLSchemaToExtend.warningPrintedMap[ruleId]) {
|
242
|
+
logger.warn(`Rule "${ruleId}" works best with schema loaded. See http://bit.ly/graphql-eslint-schema for more info`);
|
243
|
+
getGraphQLSchemaToExtend.warningPrintedMap[ruleId] = true;
|
244
|
+
}
|
245
|
+
return null;
|
246
|
+
}
|
247
|
+
const filename = context.getPhysicalFilename();
|
248
|
+
if (!schemaToExtendCache.has(filename)) {
|
249
|
+
const { schema, schemaOptions } = context.parserOptions;
|
250
|
+
const gqlConfig = loadGraphQLConfig({ schema });
|
251
|
+
const projectForFile = gqlConfig.getProjectForFile(filename);
|
252
|
+
let schemaToExtend;
|
253
|
+
try {
|
254
|
+
schemaToExtend = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', {
|
255
|
+
...schemaOptions,
|
256
|
+
ignore: filename,
|
257
|
+
});
|
258
|
+
}
|
259
|
+
catch (_a) {
|
260
|
+
// If error throws just ignore it because maybe schema is located in 1 file
|
261
|
+
schemaToExtend = null;
|
262
|
+
}
|
263
|
+
schemaToExtendCache.set(filename, schemaToExtend);
|
264
|
+
}
|
265
|
+
return schemaToExtendCache.get(filename);
|
266
|
+
};
|
267
|
+
getGraphQLSchemaToExtend.warningPrintedMap = Object.create(null);
|
189
268
|
function requireReachableTypesFromContext(ruleName, context) {
|
190
269
|
const schema = requireGraphQLSchemaFromContext(ruleName, context);
|
191
270
|
return context.parserServices.reachableTypes(schema);
|
@@ -436,11 +515,16 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
|
|
436
515
|
},
|
437
516
|
create(context) {
|
438
517
|
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...`);
|
518
|
+
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
519
|
return {};
|
442
520
|
}
|
443
|
-
|
521
|
+
let schema;
|
522
|
+
if (docs.requiresSchemaToExtend) {
|
523
|
+
schema = getGraphQLSchemaToExtend(ruleId, context);
|
524
|
+
}
|
525
|
+
else if (docs.requiresSchema) {
|
526
|
+
schema = requireGraphQLSchemaFromContext(ruleId, context);
|
527
|
+
}
|
444
528
|
return {
|
445
529
|
Document(node) {
|
446
530
|
const documentNode = getDocumentNode
|
@@ -626,9 +710,13 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
626
710
|
category: 'Schema',
|
627
711
|
description: `A GraphQL enum type is only valid if all its values are uniquely named.`,
|
628
712
|
recommended: false,
|
713
|
+
requiresSchema: true,
|
714
|
+
requiresSchemaToExtend: true,
|
629
715
|
}), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
|
630
716
|
category: 'Schema',
|
631
717
|
description: `A GraphQL complex type is only valid if all its fields are uniquely named.`,
|
718
|
+
requiresSchema: true,
|
719
|
+
requiresSchemaToExtend: true,
|
632
720
|
}), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
|
633
721
|
category: 'Operations',
|
634
722
|
description: `A GraphQL input object value is only valid if all supplied fields are uniquely named.`,
|
@@ -1370,6 +1458,17 @@ const rule$4 = {
|
|
1370
1458
|
type Query {
|
1371
1459
|
users: [User!]!
|
1372
1460
|
}
|
1461
|
+
`,
|
1462
|
+
},
|
1463
|
+
{
|
1464
|
+
title: 'Correct',
|
1465
|
+
usage: [{ FieldDefinition: { style: 'camelCase', ignorePattern: '^(EAN13|UPC|UK)' } }],
|
1466
|
+
code: /* GraphQL */ `
|
1467
|
+
type Product {
|
1468
|
+
EAN13: String
|
1469
|
+
UPC: String
|
1470
|
+
UKFlag: String
|
1471
|
+
}
|
1373
1472
|
`,
|
1374
1473
|
},
|
1375
1474
|
],
|
@@ -1439,6 +1538,10 @@ const rule$4 = {
|
|
1439
1538
|
minItems: 1,
|
1440
1539
|
items: { type: 'string' },
|
1441
1540
|
},
|
1541
|
+
ignorePattern: {
|
1542
|
+
type: 'string',
|
1543
|
+
description: 'Option to skip validation of some words, e.g. acronyms',
|
1544
|
+
},
|
1442
1545
|
},
|
1443
1546
|
},
|
1444
1547
|
},
|
@@ -1493,15 +1596,15 @@ const rule$4 = {
|
|
1493
1596
|
if (!node) {
|
1494
1597
|
return;
|
1495
1598
|
}
|
1496
|
-
const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style } = normalisePropertyOption(selector);
|
1599
|
+
const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style, ignorePattern } = normalisePropertyOption(selector);
|
1497
1600
|
const nodeType = KindToDisplayName[n.kind] || n.kind;
|
1498
1601
|
const nodeName = node.value;
|
1499
1602
|
const error = getError();
|
1500
1603
|
if (error) {
|
1501
1604
|
const { errorMessage, renameToName } = error;
|
1502
|
-
const [
|
1503
|
-
const [
|
1504
|
-
const suggestedName =
|
1605
|
+
const [leadingUnderscores] = nodeName.match(/^_*/);
|
1606
|
+
const [trailingUnderscores] = nodeName.match(/_*$/);
|
1607
|
+
const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
|
1505
1608
|
context.report({
|
1506
1609
|
loc: getLocation(node.loc, node.value),
|
1507
1610
|
message: `${nodeType} "${nodeName}" should ${errorMessage}`,
|
@@ -1515,6 +1618,9 @@ const rule$4 = {
|
|
1515
1618
|
}
|
1516
1619
|
function getError() {
|
1517
1620
|
const name = nodeName.replace(/(^_+)|(_+$)/g, '');
|
1621
|
+
if (ignorePattern && new RegExp(ignorePattern, 'u').test(name)) {
|
1622
|
+
return;
|
1623
|
+
}
|
1518
1624
|
if (prefix && !name.startsWith(prefix)) {
|
1519
1625
|
return {
|
1520
1626
|
errorMessage: `have "${prefix}" prefix`,
|
@@ -1541,8 +1647,12 @@ const rule$4 = {
|
|
1541
1647
|
renameToName: name.replace(new RegExp(`${forbiddenSuffix}$`), ''),
|
1542
1648
|
};
|
1543
1649
|
}
|
1650
|
+
// Style is optional
|
1651
|
+
if (!style) {
|
1652
|
+
return;
|
1653
|
+
}
|
1544
1654
|
const caseRegex = StyleToRegex[style];
|
1545
|
-
if (
|
1655
|
+
if (!caseRegex.test(name)) {
|
1546
1656
|
return {
|
1547
1657
|
errorMessage: `be in ${style} format`,
|
1548
1658
|
renameToName: convertCase(style, name),
|
@@ -3009,12 +3119,13 @@ const rule$j = {
|
|
3009
3119
|
},
|
3010
3120
|
};
|
3011
3121
|
|
3122
|
+
const RULE_ID$2 = 'selection-set-depth';
|
3012
3123
|
const rule$k = {
|
3013
3124
|
meta: {
|
3014
3125
|
docs: {
|
3015
3126
|
category: 'Operations',
|
3016
3127
|
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:
|
3128
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
|
3018
3129
|
requiresSiblings: true,
|
3019
3130
|
examples: [
|
3020
3131
|
{
|
@@ -3088,11 +3199,10 @@ const rule$k = {
|
|
3088
3199
|
create(context) {
|
3089
3200
|
let siblings = null;
|
3090
3201
|
try {
|
3091
|
-
siblings = requireSiblingsOperations(
|
3202
|
+
siblings = requireSiblingsOperations(RULE_ID$2, context);
|
3092
3203
|
}
|
3093
3204
|
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`);
|
3205
|
+
logger.warn(`Rule "${RULE_ID$2}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
|
3096
3206
|
}
|
3097
3207
|
const { maxDepth } = context.options[0];
|
3098
3208
|
const ignore = context.options[0].ignore || [];
|
@@ -3117,8 +3227,7 @@ const rule$k = {
|
|
3117
3227
|
});
|
3118
3228
|
}
|
3119
3229
|
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);
|
3230
|
+
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
3231
|
}
|
3123
3232
|
},
|
3124
3233
|
};
|
@@ -3500,8 +3609,7 @@ function createGraphqlProcessor() {
|
|
3500
3609
|
}
|
3501
3610
|
}
|
3502
3611
|
catch (e) {
|
3503
|
-
|
3504
|
-
console.warn(`[graphql-eslint/processor]: Unable to process file "${filename}": `, e);
|
3612
|
+
logger.warn(`Unable to process file "${filename}"`, e);
|
3505
3613
|
}
|
3506
3614
|
}
|
3507
3615
|
return [text];
|
@@ -3541,20 +3649,25 @@ const schemaCache = new Map();
|
|
3541
3649
|
function getSchema(options = {}, gqlConfig) {
|
3542
3650
|
const realFilepath = options.filePath ? getOnDiskFilepath(options.filePath) : null;
|
3543
3651
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3544
|
-
const schemaKey = asArray(projectForFile.schema)
|
3545
|
-
.sort()
|
3546
|
-
.join(',');
|
3652
|
+
const schemaKey = asArray(projectForFile.schema).sort().join(',');
|
3547
3653
|
if (!schemaKey) {
|
3548
3654
|
return null;
|
3549
3655
|
}
|
3550
|
-
|
3551
|
-
|
3656
|
+
if (schemaCache.has(schemaKey)) {
|
3657
|
+
return schemaCache.get(schemaKey);
|
3658
|
+
}
|
3659
|
+
let schema;
|
3660
|
+
try {
|
3552
3661
|
schema = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', {
|
3553
3662
|
cache: loaderCache,
|
3554
|
-
...options.schemaOptions
|
3663
|
+
...options.schemaOptions,
|
3555
3664
|
});
|
3556
|
-
schemaCache.set(schemaKey, schema);
|
3557
3665
|
}
|
3666
|
+
catch (e) {
|
3667
|
+
schema = null;
|
3668
|
+
logger.error('Error while loading schema\n', e);
|
3669
|
+
}
|
3670
|
+
schemaCache.set(schemaKey, schema);
|
3558
3671
|
return schema;
|
3559
3672
|
}
|
3560
3673
|
|
@@ -3567,10 +3680,10 @@ const handleVirtualPath = (documents) => {
|
|
3567
3680
|
return source;
|
3568
3681
|
}
|
3569
3682
|
(_a = filepathMap[location]) !== null && _a !== void 0 ? _a : (filepathMap[location] = -1);
|
3570
|
-
const index = filepathMap[location] += 1;
|
3683
|
+
const index = (filepathMap[location] += 1);
|
3571
3684
|
return {
|
3572
3685
|
...source,
|
3573
|
-
location: resolve(location, `${index}_document.graphql`)
|
3686
|
+
location: resolve(location, `${index}_document.graphql`),
|
3574
3687
|
};
|
3575
3688
|
});
|
3576
3689
|
};
|
@@ -3579,9 +3692,7 @@ const siblingOperationsCache = new Map();
|
|
3579
3692
|
const getSiblings = (filePath, gqlConfig) => {
|
3580
3693
|
const realFilepath = filePath ? getOnDiskFilepath(filePath) : null;
|
3581
3694
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3582
|
-
const documentsKey = asArray(projectForFile.documents)
|
3583
|
-
.sort()
|
3584
|
-
.join(',');
|
3695
|
+
const documentsKey = asArray(projectForFile.documents).sort().join(',');
|
3585
3696
|
if (!documentsKey) {
|
3586
3697
|
return [];
|
3587
3698
|
}
|
@@ -3589,7 +3700,7 @@ const getSiblings = (filePath, gqlConfig) => {
|
|
3589
3700
|
if (!siblings) {
|
3590
3701
|
const documents = projectForFile.loadDocumentsSync(projectForFile.documents, {
|
3591
3702
|
skipGraphQLImport: true,
|
3592
|
-
cache: loaderCache
|
3703
|
+
cache: loaderCache,
|
3593
3704
|
});
|
3594
3705
|
siblings = handleVirtualPath(documents);
|
3595
3706
|
operationsCache.set(documentsKey, siblings);
|
@@ -3602,8 +3713,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3602
3713
|
let printed = false;
|
3603
3714
|
const noopWarn = () => {
|
3604
3715
|
if (!printed) {
|
3605
|
-
|
3606
|
-
console.warn(`getSiblingOperations was called without any operations. Make sure to set "parserOptions.operations" to make this feature available!`);
|
3716
|
+
logger.warn(`getSiblingOperations was called without any operations. Make sure to set "parserOptions.operations" to make this feature available!`);
|
3607
3717
|
printed = true;
|
3608
3718
|
}
|
3609
3719
|
return [];
|
@@ -3667,8 +3777,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3667
3777
|
const name = spread.name.value;
|
3668
3778
|
const fragmentInfo = getFragment(name);
|
3669
3779
|
if (fragmentInfo.length === 0) {
|
3670
|
-
|
3671
|
-
console.warn(`Unable to locate fragment named "${name}", please make sure it's loaded using "parserOptions.operations"`);
|
3780
|
+
logger.warn(`Unable to locate fragment named "${name}", please make sure it's loaded using "parserOptions.operations"`);
|
3672
3781
|
return;
|
3673
3782
|
}
|
3674
3783
|
const fragment = fragmentInfo[0];
|
@@ -3698,43 +3807,6 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3698
3807
|
return siblingOperations;
|
3699
3808
|
}
|
3700
3809
|
|
3701
|
-
let graphQLConfig;
|
3702
|
-
function loadGraphQLConfig(options) {
|
3703
|
-
// We don't want cache config on test environment
|
3704
|
-
// Otherwise schema and documents will be same for all tests
|
3705
|
-
if (process.env.NODE_ENV !== 'test' && graphQLConfig) {
|
3706
|
-
return graphQLConfig;
|
3707
|
-
}
|
3708
|
-
const onDiskConfig = options.skipGraphQLConfig
|
3709
|
-
? null
|
3710
|
-
: loadConfigSync({
|
3711
|
-
throwOnEmpty: false,
|
3712
|
-
throwOnMissing: false,
|
3713
|
-
extensions: [addCodeFileLoaderExtension],
|
3714
|
-
});
|
3715
|
-
const configOptions = options.projects
|
3716
|
-
? { projects: options.projects }
|
3717
|
-
: {
|
3718
|
-
schema: (options.schema || ''),
|
3719
|
-
documents: options.documents || options.operations,
|
3720
|
-
extensions: options.extensions,
|
3721
|
-
include: options.include,
|
3722
|
-
exclude: options.exclude,
|
3723
|
-
};
|
3724
|
-
graphQLConfig =
|
3725
|
-
onDiskConfig ||
|
3726
|
-
new GraphQLConfig({
|
3727
|
-
config: configOptions,
|
3728
|
-
filepath: 'virtual-config',
|
3729
|
-
}, [addCodeFileLoaderExtension]);
|
3730
|
-
return graphQLConfig;
|
3731
|
-
}
|
3732
|
-
const addCodeFileLoaderExtension = api => {
|
3733
|
-
api.loaders.schema.register(new CodeFileLoader());
|
3734
|
-
api.loaders.documents.register(new CodeFileLoader());
|
3735
|
-
return { name: 'graphql-eslint-loaders' };
|
3736
|
-
};
|
3737
|
-
|
3738
3810
|
let reachableTypesCache;
|
3739
3811
|
function getReachableTypes(schema) {
|
3740
3812
|
// We don't want cache reachableTypes on test environment
|
@@ -3817,7 +3889,7 @@ function parse(code, options) {
|
|
3817
3889
|
return parseForESLint(code, options).ast;
|
3818
3890
|
}
|
3819
3891
|
function parseForESLint(code, options = {}) {
|
3820
|
-
const gqlConfig =
|
3892
|
+
const gqlConfig = loadCachedGraphQLConfig(options);
|
3821
3893
|
const schema = getSchema(options, gqlConfig);
|
3822
3894
|
const parserServices = {
|
3823
3895
|
hasTypeInfo: schema !== null,
|
@@ -3849,6 +3921,7 @@ function parseForESLint(code, options = {}) {
|
|
3849
3921
|
};
|
3850
3922
|
}
|
3851
3923
|
catch (e) {
|
3924
|
+
e.message = `[graphql-eslint] ${e.message}`;
|
3852
3925
|
// In case of GraphQL parser error, we report it to ESLint as a parser error that matches the requirements
|
3853
3926
|
// of ESLint. This will make sure to display it correctly in IDEs and lint results.
|
3854
3927
|
if (e instanceof GraphQLError) {
|
@@ -3856,11 +3929,10 @@ function parseForESLint(code, options = {}) {
|
|
3856
3929
|
index: e.positions[0],
|
3857
3930
|
lineNumber: e.locations[0].line,
|
3858
3931
|
column: e.locations[0].column,
|
3859
|
-
message:
|
3932
|
+
message: e.message,
|
3860
3933
|
};
|
3861
3934
|
throw eslintError;
|
3862
3935
|
}
|
3863
|
-
e.message = `[graphql-eslint]: ${e.message}`;
|
3864
3936
|
throw e;
|
3865
3937
|
}
|
3866
3938
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@graphql-eslint/eslint-plugin",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.4.0-alpha-c01d913.0",
|
4
4
|
"description": "GraphQL plugin for ESLint",
|
5
5
|
"sideEffects": false,
|
6
6
|
"peerDependencies": {
|
@@ -11,6 +11,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/utils.d.ts
CHANGED
@@ -6,6 +6,15 @@ 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
|
+
};
|
13
|
+
declare type GetGraphQLSchemaToExtend = {
|
14
|
+
(ruleId: string, ctx: GraphQLESLintRuleContext): GraphQLSchema | null;
|
15
|
+
warningPrintedMap: Record<string, boolean>;
|
16
|
+
};
|
17
|
+
export declare const getGraphQLSchemaToExtend: GetGraphQLSchemaToExtend;
|
9
18
|
export declare function requireReachableTypesFromContext(ruleName: string, context: GraphQLESLintRuleContext): ReachableTypes | never;
|
10
19
|
export declare function requireUsedFieldsFromContext(ruleName: string, context: GraphQLESLintRuleContext): UsedFields | never;
|
11
20
|
export declare function extractTokens(source: Source): AST.Token[];
|
@@ -27,3 +36,4 @@ export declare function getLocation(loc: Partial<AST.SourceLocation>, fieldName?
|
|
27
36
|
offsetStart?: number;
|
28
37
|
offsetEnd?: number;
|
29
38
|
}): AST.SourceLocation;
|
39
|
+
export {};
|