@graphql-eslint/eslint-plugin 3.3.0 → 3.4.0-alpha-d977284.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/possible-type-extension.md +1 -1
- package/graphql-config.d.ts +1 -1
- package/index.js +64 -56
- package/index.mjs +61 -53
- package/package.json +3 -2
- 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
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
- Category: `Schema`
|
4
4
|
- Rule name: `@graphql-eslint/possible-type-extension`
|
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 type extension is only valid if the type is defined and has the same kind.
|
package/graphql-config.d.ts
CHANGED
package/index.js
CHANGED
@@ -10,9 +10,10 @@ 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
|
-
const graphqlConfig
|
16
|
+
const graphqlConfig = require('graphql-config');
|
16
17
|
const codeFileLoader = require('@graphql-tools/code-file-loader');
|
17
18
|
const eslint = require('eslint');
|
18
19
|
const codeFrame = require('@babel/code-frame');
|
@@ -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,14 +333,14 @@ function getLocation(loc, fieldName = '', offset) {
|
|
326
333
|
};
|
327
334
|
}
|
328
335
|
|
329
|
-
function validateDocument(
|
336
|
+
function validateDocument(context, schema = null, documentNode, rule) {
|
330
337
|
if (documentNode.definitions.length === 0) {
|
331
338
|
return;
|
332
339
|
}
|
333
340
|
try {
|
334
341
|
const validationErrors = schema
|
335
342
|
? graphql.validate(schema, documentNode, [rule])
|
336
|
-
: validate.validateSDL(documentNode,
|
343
|
+
: validate.validateSDL(documentNode, schema, [rule]);
|
337
344
|
for (const error of validationErrors) {
|
338
345
|
/*
|
339
346
|
* TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
|
@@ -358,7 +365,8 @@ function validateDocument(sourceNode, context, schema, documentNode, rule) {
|
|
358
365
|
}
|
359
366
|
catch (e) {
|
360
367
|
context.report({
|
361
|
-
|
368
|
+
// Report on first character
|
369
|
+
loc: { column: 0, line: 1 },
|
362
370
|
message: e.message,
|
363
371
|
});
|
364
372
|
}
|
@@ -440,18 +448,17 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
|
|
440
448
|
},
|
441
449
|
},
|
442
450
|
create(context) {
|
451
|
+
if (!ruleFn) {
|
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...`);
|
453
|
+
return {};
|
454
|
+
}
|
443
455
|
return {
|
444
456
|
Document(node) {
|
445
|
-
if (!ruleFn) {
|
446
|
-
// eslint-disable-next-line no-console
|
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...`);
|
448
|
-
return;
|
449
|
-
}
|
450
457
|
const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
|
451
458
|
const documentNode = getDocumentNode
|
452
459
|
? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
|
453
460
|
: node.rawNode();
|
454
|
-
validateDocument(
|
461
|
+
validateDocument(context, schema, documentNode, ruleFn);
|
455
462
|
},
|
456
463
|
};
|
457
464
|
},
|
@@ -601,7 +608,8 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
601
608
|
}), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
|
602
609
|
category: 'Schema',
|
603
610
|
description: `A type extension is only valid if the type is defined and has the same kind.`,
|
604
|
-
recommended: false,
|
611
|
+
recommended: false,
|
612
|
+
requiresSchema: true,
|
605
613
|
}), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
|
606
614
|
category: ['Schema', 'Operations'],
|
607
615
|
description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
|
@@ -3034,12 +3042,13 @@ const rule$j = {
|
|
3034
3042
|
},
|
3035
3043
|
};
|
3036
3044
|
|
3045
|
+
const RULE_ID$2 = 'selection-set-depth';
|
3037
3046
|
const rule$k = {
|
3038
3047
|
meta: {
|
3039
3048
|
docs: {
|
3040
3049
|
category: 'Operations',
|
3041
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).`,
|
3042
|
-
url:
|
3051
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
|
3043
3052
|
requiresSiblings: true,
|
3044
3053
|
examples: [
|
3045
3054
|
{
|
@@ -3113,11 +3122,10 @@ const rule$k = {
|
|
3113
3122
|
create(context) {
|
3114
3123
|
let siblings = null;
|
3115
3124
|
try {
|
3116
|
-
siblings = requireSiblingsOperations(
|
3125
|
+
siblings = requireSiblingsOperations(RULE_ID$2, context);
|
3117
3126
|
}
|
3118
3127
|
catch (e) {
|
3119
|
-
|
3120
|
-
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`);
|
3121
3129
|
}
|
3122
3130
|
const { maxDepth } = context.options[0];
|
3123
3131
|
const ignore = context.options[0].ignore || [];
|
@@ -3142,8 +3150,7 @@ const rule$k = {
|
|
3142
3150
|
});
|
3143
3151
|
}
|
3144
3152
|
catch (e) {
|
3145
|
-
|
3146
|
-
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);
|
3147
3154
|
}
|
3148
3155
|
},
|
3149
3156
|
};
|
@@ -3525,8 +3532,7 @@ function createGraphqlProcessor() {
|
|
3525
3532
|
}
|
3526
3533
|
}
|
3527
3534
|
catch (e) {
|
3528
|
-
|
3529
|
-
console.warn(`[graphql-eslint/processor]: Unable to process file "${filename}": `, e);
|
3535
|
+
logger.warn(`Unable to process file "${filename}"`, e);
|
3530
3536
|
}
|
3531
3537
|
}
|
3532
3538
|
return [text];
|
@@ -3566,20 +3572,25 @@ const schemaCache = new Map();
|
|
3566
3572
|
function getSchema(options = {}, gqlConfig) {
|
3567
3573
|
const realFilepath = options.filePath ? getOnDiskFilepath(options.filePath) : null;
|
3568
3574
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3569
|
-
const schemaKey = utils.asArray(projectForFile.schema)
|
3570
|
-
.sort()
|
3571
|
-
.join(',');
|
3575
|
+
const schemaKey = utils.asArray(projectForFile.schema).sort().join(',');
|
3572
3576
|
if (!schemaKey) {
|
3573
3577
|
return null;
|
3574
3578
|
}
|
3575
|
-
|
3576
|
-
|
3579
|
+
if (schemaCache.has(schemaKey)) {
|
3580
|
+
return schemaCache.get(schemaKey);
|
3581
|
+
}
|
3582
|
+
let schema;
|
3583
|
+
try {
|
3577
3584
|
schema = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', {
|
3578
3585
|
cache: loaderCache,
|
3579
|
-
...options.schemaOptions
|
3586
|
+
...options.schemaOptions,
|
3580
3587
|
});
|
3581
|
-
schemaCache.set(schemaKey, schema);
|
3582
3588
|
}
|
3589
|
+
catch (e) {
|
3590
|
+
schema = null;
|
3591
|
+
logger.error('Error while loading schema\n', e);
|
3592
|
+
}
|
3593
|
+
schemaCache.set(schemaKey, schema);
|
3583
3594
|
return schema;
|
3584
3595
|
}
|
3585
3596
|
|
@@ -3592,10 +3603,10 @@ const handleVirtualPath = (documents) => {
|
|
3592
3603
|
return source;
|
3593
3604
|
}
|
3594
3605
|
(_a = filepathMap[location]) !== null && _a !== void 0 ? _a : (filepathMap[location] = -1);
|
3595
|
-
const index = filepathMap[location] += 1;
|
3606
|
+
const index = (filepathMap[location] += 1);
|
3596
3607
|
return {
|
3597
3608
|
...source,
|
3598
|
-
location: path.resolve(location, `${index}_document.graphql`)
|
3609
|
+
location: path.resolve(location, `${index}_document.graphql`),
|
3599
3610
|
};
|
3600
3611
|
});
|
3601
3612
|
};
|
@@ -3604,9 +3615,7 @@ const siblingOperationsCache = new Map();
|
|
3604
3615
|
const getSiblings = (filePath, gqlConfig) => {
|
3605
3616
|
const realFilepath = filePath ? getOnDiskFilepath(filePath) : null;
|
3606
3617
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3607
|
-
const documentsKey = utils.asArray(projectForFile.documents)
|
3608
|
-
.sort()
|
3609
|
-
.join(',');
|
3618
|
+
const documentsKey = utils.asArray(projectForFile.documents).sort().join(',');
|
3610
3619
|
if (!documentsKey) {
|
3611
3620
|
return [];
|
3612
3621
|
}
|
@@ -3614,7 +3623,7 @@ const getSiblings = (filePath, gqlConfig) => {
|
|
3614
3623
|
if (!siblings) {
|
3615
3624
|
const documents = projectForFile.loadDocumentsSync(projectForFile.documents, {
|
3616
3625
|
skipGraphQLImport: true,
|
3617
|
-
cache: loaderCache
|
3626
|
+
cache: loaderCache,
|
3618
3627
|
});
|
3619
3628
|
siblings = handleVirtualPath(documents);
|
3620
3629
|
operationsCache.set(documentsKey, siblings);
|
@@ -3627,8 +3636,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3627
3636
|
let printed = false;
|
3628
3637
|
const noopWarn = () => {
|
3629
3638
|
if (!printed) {
|
3630
|
-
|
3631
|
-
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!`);
|
3632
3640
|
printed = true;
|
3633
3641
|
}
|
3634
3642
|
return [];
|
@@ -3692,8 +3700,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3692
3700
|
const name = spread.name.value;
|
3693
3701
|
const fragmentInfo = getFragment(name);
|
3694
3702
|
if (fragmentInfo.length === 0) {
|
3695
|
-
|
3696
|
-
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"`);
|
3697
3704
|
return;
|
3698
3705
|
}
|
3699
3706
|
const fragment = fragmentInfo[0];
|
@@ -3723,35 +3730,36 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3723
3730
|
return siblingOperations;
|
3724
3731
|
}
|
3725
3732
|
|
3726
|
-
let
|
3727
|
-
function
|
3733
|
+
let graphQLConfig;
|
3734
|
+
function loadGraphQLConfig(options) {
|
3728
3735
|
// We don't want cache config on test environment
|
3729
3736
|
// Otherwise schema and documents will be same for all tests
|
3730
|
-
if (process.env.NODE_ENV !== 'test' &&
|
3731
|
-
return
|
3737
|
+
if (process.env.NODE_ENV !== 'test' && graphQLConfig) {
|
3738
|
+
return graphQLConfig;
|
3732
3739
|
}
|
3733
3740
|
const onDiskConfig = options.skipGraphQLConfig
|
3734
3741
|
? null
|
3735
|
-
: graphqlConfig
|
3742
|
+
: graphqlConfig.loadConfigSync({
|
3736
3743
|
throwOnEmpty: false,
|
3737
3744
|
throwOnMissing: false,
|
3738
3745
|
extensions: [addCodeFileLoaderExtension],
|
3739
3746
|
});
|
3740
|
-
|
3747
|
+
const configOptions = options.projects
|
3748
|
+
? { projects: options.projects }
|
3749
|
+
: {
|
3750
|
+
schema: (options.schema || ''),
|
3751
|
+
documents: options.documents || options.operations,
|
3752
|
+
extensions: options.extensions,
|
3753
|
+
include: options.include,
|
3754
|
+
exclude: options.exclude,
|
3755
|
+
};
|
3756
|
+
graphQLConfig =
|
3741
3757
|
onDiskConfig ||
|
3742
|
-
new graphqlConfig
|
3743
|
-
config:
|
3744
|
-
? { projects: options.projects }
|
3745
|
-
: {
|
3746
|
-
schema: (options.schema || ''),
|
3747
|
-
documents: options.documents || options.operations,
|
3748
|
-
extensions: options.extensions,
|
3749
|
-
include: options.include,
|
3750
|
-
exclude: options.exclude,
|
3751
|
-
},
|
3758
|
+
new graphqlConfig.GraphQLConfig({
|
3759
|
+
config: configOptions,
|
3752
3760
|
filepath: 'virtual-config',
|
3753
3761
|
}, [addCodeFileLoaderExtension]);
|
3754
|
-
return
|
3762
|
+
return graphQLConfig;
|
3755
3763
|
}
|
3756
3764
|
const addCodeFileLoaderExtension = api => {
|
3757
3765
|
api.loaders.schema.register(new codeFileLoader.CodeFileLoader());
|
@@ -3841,7 +3849,7 @@ function parse(code, options) {
|
|
3841
3849
|
return parseForESLint(code, options).ast;
|
3842
3850
|
}
|
3843
3851
|
function parseForESLint(code, options = {}) {
|
3844
|
-
const gqlConfig =
|
3852
|
+
const gqlConfig = loadGraphQLConfig(options);
|
3845
3853
|
const schema = getSchema(options, gqlConfig);
|
3846
3854
|
const parserServices = {
|
3847
3855
|
hasTypeInfo: schema !== null,
|
@@ -3873,6 +3881,7 @@ function parseForESLint(code, options = {}) {
|
|
3873
3881
|
};
|
3874
3882
|
}
|
3875
3883
|
catch (e) {
|
3884
|
+
e.message = `[graphql-eslint] ${e.message}`;
|
3876
3885
|
// In case of GraphQL parser error, we report it to ESLint as a parser error that matches the requirements
|
3877
3886
|
// of ESLint. This will make sure to display it correctly in IDEs and lint results.
|
3878
3887
|
if (e instanceof graphql.GraphQLError) {
|
@@ -3880,11 +3889,10 @@ function parseForESLint(code, options = {}) {
|
|
3880
3889
|
index: e.positions[0],
|
3881
3890
|
lineNumber: e.locations[0].line,
|
3882
3891
|
column: e.locations[0].column,
|
3883
|
-
message:
|
3892
|
+
message: e.message,
|
3884
3893
|
};
|
3885
3894
|
throw eslintError;
|
3886
3895
|
}
|
3887
|
-
e.message = `[graphql-eslint]: ${e.message}`;
|
3888
3896
|
throw e;
|
3889
3897
|
}
|
3890
3898
|
}
|
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,14 +327,14 @@ function getLocation(loc, fieldName = '', offset) {
|
|
320
327
|
};
|
321
328
|
}
|
322
329
|
|
323
|
-
function validateDocument(
|
330
|
+
function validateDocument(context, schema = null, documentNode, rule) {
|
324
331
|
if (documentNode.definitions.length === 0) {
|
325
332
|
return;
|
326
333
|
}
|
327
334
|
try {
|
328
335
|
const validationErrors = schema
|
329
336
|
? validate(schema, documentNode, [rule])
|
330
|
-
: validateSDL(documentNode,
|
337
|
+
: validateSDL(documentNode, schema, [rule]);
|
331
338
|
for (const error of validationErrors) {
|
332
339
|
/*
|
333
340
|
* TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
|
@@ -352,7 +359,8 @@ function validateDocument(sourceNode, context, schema, documentNode, rule) {
|
|
352
359
|
}
|
353
360
|
catch (e) {
|
354
361
|
context.report({
|
355
|
-
|
362
|
+
// Report on first character
|
363
|
+
loc: { column: 0, line: 1 },
|
356
364
|
message: e.message,
|
357
365
|
});
|
358
366
|
}
|
@@ -434,18 +442,17 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
|
|
434
442
|
},
|
435
443
|
},
|
436
444
|
create(context) {
|
445
|
+
if (!ruleFn) {
|
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...`);
|
447
|
+
return {};
|
448
|
+
}
|
437
449
|
return {
|
438
450
|
Document(node) {
|
439
|
-
if (!ruleFn) {
|
440
|
-
// eslint-disable-next-line no-console
|
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...`);
|
442
|
-
return;
|
443
|
-
}
|
444
451
|
const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
|
445
452
|
const documentNode = getDocumentNode
|
446
453
|
? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
|
447
454
|
: node.rawNode();
|
448
|
-
validateDocument(
|
455
|
+
validateDocument(context, schema, documentNode, ruleFn);
|
449
456
|
},
|
450
457
|
};
|
451
458
|
},
|
@@ -595,7 +602,8 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
595
602
|
}), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
|
596
603
|
category: 'Schema',
|
597
604
|
description: `A type extension is only valid if the type is defined and has the same kind.`,
|
598
|
-
recommended: false,
|
605
|
+
recommended: false,
|
606
|
+
requiresSchema: true,
|
599
607
|
}), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
|
600
608
|
category: ['Schema', 'Operations'],
|
601
609
|
description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
|
@@ -3028,12 +3036,13 @@ const rule$j = {
|
|
3028
3036
|
},
|
3029
3037
|
};
|
3030
3038
|
|
3039
|
+
const RULE_ID$2 = 'selection-set-depth';
|
3031
3040
|
const rule$k = {
|
3032
3041
|
meta: {
|
3033
3042
|
docs: {
|
3034
3043
|
category: 'Operations',
|
3035
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).`,
|
3036
|
-
url:
|
3045
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
|
3037
3046
|
requiresSiblings: true,
|
3038
3047
|
examples: [
|
3039
3048
|
{
|
@@ -3107,11 +3116,10 @@ const rule$k = {
|
|
3107
3116
|
create(context) {
|
3108
3117
|
let siblings = null;
|
3109
3118
|
try {
|
3110
|
-
siblings = requireSiblingsOperations(
|
3119
|
+
siblings = requireSiblingsOperations(RULE_ID$2, context);
|
3111
3120
|
}
|
3112
3121
|
catch (e) {
|
3113
|
-
|
3114
|
-
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`);
|
3115
3123
|
}
|
3116
3124
|
const { maxDepth } = context.options[0];
|
3117
3125
|
const ignore = context.options[0].ignore || [];
|
@@ -3136,8 +3144,7 @@ const rule$k = {
|
|
3136
3144
|
});
|
3137
3145
|
}
|
3138
3146
|
catch (e) {
|
3139
|
-
|
3140
|
-
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);
|
3141
3148
|
}
|
3142
3149
|
},
|
3143
3150
|
};
|
@@ -3519,8 +3526,7 @@ function createGraphqlProcessor() {
|
|
3519
3526
|
}
|
3520
3527
|
}
|
3521
3528
|
catch (e) {
|
3522
|
-
|
3523
|
-
console.warn(`[graphql-eslint/processor]: Unable to process file "${filename}": `, e);
|
3529
|
+
logger.warn(`Unable to process file "${filename}"`, e);
|
3524
3530
|
}
|
3525
3531
|
}
|
3526
3532
|
return [text];
|
@@ -3560,20 +3566,25 @@ const schemaCache = new Map();
|
|
3560
3566
|
function getSchema(options = {}, gqlConfig) {
|
3561
3567
|
const realFilepath = options.filePath ? getOnDiskFilepath(options.filePath) : null;
|
3562
3568
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3563
|
-
const schemaKey = asArray(projectForFile.schema)
|
3564
|
-
.sort()
|
3565
|
-
.join(',');
|
3569
|
+
const schemaKey = asArray(projectForFile.schema).sort().join(',');
|
3566
3570
|
if (!schemaKey) {
|
3567
3571
|
return null;
|
3568
3572
|
}
|
3569
|
-
|
3570
|
-
|
3573
|
+
if (schemaCache.has(schemaKey)) {
|
3574
|
+
return schemaCache.get(schemaKey);
|
3575
|
+
}
|
3576
|
+
let schema;
|
3577
|
+
try {
|
3571
3578
|
schema = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', {
|
3572
3579
|
cache: loaderCache,
|
3573
|
-
...options.schemaOptions
|
3580
|
+
...options.schemaOptions,
|
3574
3581
|
});
|
3575
|
-
schemaCache.set(schemaKey, schema);
|
3576
3582
|
}
|
3583
|
+
catch (e) {
|
3584
|
+
schema = null;
|
3585
|
+
logger.error('Error while loading schema\n', e);
|
3586
|
+
}
|
3587
|
+
schemaCache.set(schemaKey, schema);
|
3577
3588
|
return schema;
|
3578
3589
|
}
|
3579
3590
|
|
@@ -3586,10 +3597,10 @@ const handleVirtualPath = (documents) => {
|
|
3586
3597
|
return source;
|
3587
3598
|
}
|
3588
3599
|
(_a = filepathMap[location]) !== null && _a !== void 0 ? _a : (filepathMap[location] = -1);
|
3589
|
-
const index = filepathMap[location] += 1;
|
3600
|
+
const index = (filepathMap[location] += 1);
|
3590
3601
|
return {
|
3591
3602
|
...source,
|
3592
|
-
location: resolve(location, `${index}_document.graphql`)
|
3603
|
+
location: resolve(location, `${index}_document.graphql`),
|
3593
3604
|
};
|
3594
3605
|
});
|
3595
3606
|
};
|
@@ -3598,9 +3609,7 @@ const siblingOperationsCache = new Map();
|
|
3598
3609
|
const getSiblings = (filePath, gqlConfig) => {
|
3599
3610
|
const realFilepath = filePath ? getOnDiskFilepath(filePath) : null;
|
3600
3611
|
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
|
3601
|
-
const documentsKey = asArray(projectForFile.documents)
|
3602
|
-
.sort()
|
3603
|
-
.join(',');
|
3612
|
+
const documentsKey = asArray(projectForFile.documents).sort().join(',');
|
3604
3613
|
if (!documentsKey) {
|
3605
3614
|
return [];
|
3606
3615
|
}
|
@@ -3608,7 +3617,7 @@ const getSiblings = (filePath, gqlConfig) => {
|
|
3608
3617
|
if (!siblings) {
|
3609
3618
|
const documents = projectForFile.loadDocumentsSync(projectForFile.documents, {
|
3610
3619
|
skipGraphQLImport: true,
|
3611
|
-
cache: loaderCache
|
3620
|
+
cache: loaderCache,
|
3612
3621
|
});
|
3613
3622
|
siblings = handleVirtualPath(documents);
|
3614
3623
|
operationsCache.set(documentsKey, siblings);
|
@@ -3621,8 +3630,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3621
3630
|
let printed = false;
|
3622
3631
|
const noopWarn = () => {
|
3623
3632
|
if (!printed) {
|
3624
|
-
|
3625
|
-
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!`);
|
3626
3634
|
printed = true;
|
3627
3635
|
}
|
3628
3636
|
return [];
|
@@ -3686,8 +3694,7 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3686
3694
|
const name = spread.name.value;
|
3687
3695
|
const fragmentInfo = getFragment(name);
|
3688
3696
|
if (fragmentInfo.length === 0) {
|
3689
|
-
|
3690
|
-
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"`);
|
3691
3698
|
return;
|
3692
3699
|
}
|
3693
3700
|
const fragment = fragmentInfo[0];
|
@@ -3717,12 +3724,12 @@ function getSiblingOperations(options, gqlConfig) {
|
|
3717
3724
|
return siblingOperations;
|
3718
3725
|
}
|
3719
3726
|
|
3720
|
-
let
|
3721
|
-
function
|
3727
|
+
let graphQLConfig;
|
3728
|
+
function loadGraphQLConfig(options) {
|
3722
3729
|
// We don't want cache config on test environment
|
3723
3730
|
// Otherwise schema and documents will be same for all tests
|
3724
|
-
if (process.env.NODE_ENV !== 'test' &&
|
3725
|
-
return
|
3731
|
+
if (process.env.NODE_ENV !== 'test' && graphQLConfig) {
|
3732
|
+
return graphQLConfig;
|
3726
3733
|
}
|
3727
3734
|
const onDiskConfig = options.skipGraphQLConfig
|
3728
3735
|
? null
|
@@ -3731,21 +3738,22 @@ function loadGraphqlConfig(options) {
|
|
3731
3738
|
throwOnMissing: false,
|
3732
3739
|
extensions: [addCodeFileLoaderExtension],
|
3733
3740
|
});
|
3734
|
-
|
3741
|
+
const configOptions = options.projects
|
3742
|
+
? { projects: options.projects }
|
3743
|
+
: {
|
3744
|
+
schema: (options.schema || ''),
|
3745
|
+
documents: options.documents || options.operations,
|
3746
|
+
extensions: options.extensions,
|
3747
|
+
include: options.include,
|
3748
|
+
exclude: options.exclude,
|
3749
|
+
};
|
3750
|
+
graphQLConfig =
|
3735
3751
|
onDiskConfig ||
|
3736
3752
|
new GraphQLConfig({
|
3737
|
-
config:
|
3738
|
-
? { projects: options.projects }
|
3739
|
-
: {
|
3740
|
-
schema: (options.schema || ''),
|
3741
|
-
documents: options.documents || options.operations,
|
3742
|
-
extensions: options.extensions,
|
3743
|
-
include: options.include,
|
3744
|
-
exclude: options.exclude,
|
3745
|
-
},
|
3753
|
+
config: configOptions,
|
3746
3754
|
filepath: 'virtual-config',
|
3747
3755
|
}, [addCodeFileLoaderExtension]);
|
3748
|
-
return
|
3756
|
+
return graphQLConfig;
|
3749
3757
|
}
|
3750
3758
|
const addCodeFileLoaderExtension = api => {
|
3751
3759
|
api.loaders.schema.register(new CodeFileLoader());
|
@@ -3835,7 +3843,7 @@ function parse(code, options) {
|
|
3835
3843
|
return parseForESLint(code, options).ast;
|
3836
3844
|
}
|
3837
3845
|
function parseForESLint(code, options = {}) {
|
3838
|
-
const gqlConfig =
|
3846
|
+
const gqlConfig = loadGraphQLConfig(options);
|
3839
3847
|
const schema = getSchema(options, gqlConfig);
|
3840
3848
|
const parserServices = {
|
3841
3849
|
hasTypeInfo: schema !== null,
|
@@ -3867,6 +3875,7 @@ function parseForESLint(code, options = {}) {
|
|
3867
3875
|
};
|
3868
3876
|
}
|
3869
3877
|
catch (e) {
|
3878
|
+
e.message = `[graphql-eslint] ${e.message}`;
|
3870
3879
|
// In case of GraphQL parser error, we report it to ESLint as a parser error that matches the requirements
|
3871
3880
|
// of ESLint. This will make sure to display it correctly in IDEs and lint results.
|
3872
3881
|
if (e instanceof GraphQLError) {
|
@@ -3874,11 +3883,10 @@ function parseForESLint(code, options = {}) {
|
|
3874
3883
|
index: e.positions[0],
|
3875
3884
|
lineNumber: e.locations[0].line,
|
3876
3885
|
column: e.locations[0].column,
|
3877
|
-
message:
|
3886
|
+
message: e.message,
|
3878
3887
|
};
|
3879
3888
|
throw eslintError;
|
3880
3889
|
}
|
3881
|
-
e.message = `[graphql-eslint]: ${e.message}`;
|
3882
3890
|
throw e;
|
3883
3891
|
}
|
3884
3892
|
}
|
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-d977284.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/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[];
|