@graphql-eslint/eslint-plugin 2.3.1 → 2.3.2-alpha-99be3d2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/custom-rules.md +1 -1
- package/index.js +100 -22
- package/index.mjs +100 -22
- package/package.json +1 -1
- package/testkit.d.ts +5 -3
- package/utils.d.ts +4 -0
package/docs/custom-rules.md
CHANGED
@@ -58,7 +58,7 @@ This is useful if you wish to use other GraphQL tools that works with the origin
|
|
58
58
|
Here's an example for using original `graphql-js` validate method to validate `OperationDefinition`:
|
59
59
|
|
60
60
|
```ts
|
61
|
-
import { validate } from 'graphql
|
61
|
+
import { validate } from 'graphql';
|
62
62
|
import { requireGraphQLSchemaFromContext } from '@graphql-eslint/eslint-plugin';
|
63
63
|
|
64
64
|
export const rule = {
|
package/index.js
CHANGED
@@ -15,6 +15,8 @@ const depthLimit = _interopDefault(require('graphql-depth-limit'));
|
|
15
15
|
const graphqlTagPluck = require('@graphql-tools/graphql-tag-pluck');
|
16
16
|
const graphqlConfig$1 = require('graphql-config');
|
17
17
|
const codeFileLoader = require('@graphql-tools/code-file-loader');
|
18
|
+
const eslint = require('eslint');
|
19
|
+
const codeFrame = require('@babel/code-frame');
|
18
20
|
|
19
21
|
/*
|
20
22
|
* 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
|
@@ -242,6 +244,24 @@ const convertCase = (style, str) => {
|
|
242
244
|
return lowerCase(str).replace(/ /g, '-');
|
243
245
|
}
|
244
246
|
};
|
247
|
+
function getLocation(loc, fieldName = '', offset) {
|
248
|
+
const { start } = loc;
|
249
|
+
/*
|
250
|
+
* ESLint has 0-based column number
|
251
|
+
* https://eslint.org/docs/developer-guide/working-with-rules#contextreport
|
252
|
+
*/
|
253
|
+
const { offsetStart = 1, offsetEnd = 1 } = offset !== null && offset !== void 0 ? offset : {};
|
254
|
+
return {
|
255
|
+
start: {
|
256
|
+
line: start.line,
|
257
|
+
column: start.column - offsetStart,
|
258
|
+
},
|
259
|
+
end: {
|
260
|
+
line: start.line,
|
261
|
+
column: start.column - offsetEnd + fieldName.length,
|
262
|
+
},
|
263
|
+
};
|
264
|
+
}
|
245
265
|
|
246
266
|
function extractRuleName(stack) {
|
247
267
|
const match = (stack || '').match(/validation[/\\\\]rules[/\\\\](.*?)\.js:/) || [];
|
@@ -1370,7 +1390,8 @@ const rule$7 = {
|
|
1370
1390
|
var _a;
|
1371
1391
|
if (options.fileExtension && options.fileExtension !== fileExtension) {
|
1372
1392
|
context.report({
|
1373
|
-
|
1393
|
+
// Report on first character
|
1394
|
+
loc: { column: 0, line: 1 },
|
1374
1395
|
messageId: MATCH_EXTENSION,
|
1375
1396
|
data: {
|
1376
1397
|
fileExtension,
|
@@ -1402,7 +1423,8 @@ const rule$7 = {
|
|
1402
1423
|
const filenameWithExtension = filename + expectedExtension;
|
1403
1424
|
if (expectedFilename !== filenameWithExtension) {
|
1404
1425
|
context.report({
|
1405
|
-
|
1426
|
+
// Report on first character
|
1427
|
+
loc: { column: 0, line: 1 },
|
1406
1428
|
messageId: MATCH_STYLE,
|
1407
1429
|
data: {
|
1408
1430
|
expectedFilename,
|
@@ -3185,11 +3207,7 @@ const rule$m = {
|
|
3185
3207
|
const RULE_NAME$3 = 'unique-fragment-name';
|
3186
3208
|
const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
|
3187
3209
|
const checkNode = (context, node, ruleName, messageId) => {
|
3188
|
-
|
3189
|
-
const documentName = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value;
|
3190
|
-
if (!documentName) {
|
3191
|
-
return;
|
3192
|
-
}
|
3210
|
+
const documentName = node.name.value;
|
3193
3211
|
const siblings = requireSiblingsOperations(ruleName, context);
|
3194
3212
|
const siblingDocuments = node.kind === graphql.Kind.FRAGMENT_DEFINITION ? siblings.getFragment(documentName) : siblings.getOperation(documentName);
|
3195
3213
|
const filepath = context.getFilename();
|
@@ -3200,7 +3218,6 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3200
3218
|
return isSameName && !isSamePath;
|
3201
3219
|
});
|
3202
3220
|
if (conflictingDocuments.length > 0) {
|
3203
|
-
const { start, end } = node.name.loc;
|
3204
3221
|
context.report({
|
3205
3222
|
messageId,
|
3206
3223
|
data: {
|
@@ -3209,16 +3226,7 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3209
3226
|
.map(f => `\t${path.relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
|
3210
3227
|
.join('\n'),
|
3211
3228
|
},
|
3212
|
-
loc:
|
3213
|
-
start: {
|
3214
|
-
line: start.line,
|
3215
|
-
column: start.column - 1,
|
3216
|
-
},
|
3217
|
-
end: {
|
3218
|
-
line: end.line,
|
3219
|
-
column: end.column - 1,
|
3220
|
-
},
|
3221
|
-
},
|
3229
|
+
loc: getLocation(node.name.loc, documentName),
|
3222
3230
|
});
|
3223
3231
|
}
|
3224
3232
|
};
|
@@ -3335,7 +3343,7 @@ const rule$o = {
|
|
3335
3343
|
},
|
3336
3344
|
create(context) {
|
3337
3345
|
return {
|
3338
|
-
OperationDefinition(node) {
|
3346
|
+
'OperationDefinition[name!=undefined]'(node) {
|
3339
3347
|
checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
|
3340
3348
|
},
|
3341
3349
|
};
|
@@ -3765,22 +3773,92 @@ function parseForESLint(code, options = {}) {
|
|
3765
3773
|
}
|
3766
3774
|
}
|
3767
3775
|
|
3768
|
-
class GraphQLRuleTester extends
|
3776
|
+
class GraphQLRuleTester extends eslint.RuleTester {
|
3769
3777
|
constructor(parserOptions = {}) {
|
3770
|
-
|
3778
|
+
const config = {
|
3771
3779
|
parser: require.resolve('@graphql-eslint/eslint-plugin'),
|
3772
3780
|
parserOptions: {
|
3773
3781
|
...parserOptions,
|
3774
3782
|
skipGraphQLConfig: true,
|
3775
3783
|
},
|
3776
|
-
}
|
3784
|
+
};
|
3785
|
+
super(config);
|
3786
|
+
this.config = config;
|
3777
3787
|
}
|
3778
3788
|
fromMockFile(path$1) {
|
3779
3789
|
return fs.readFileSync(path.resolve(__dirname, `../tests/mocks/${path$1}`), 'utf-8');
|
3780
3790
|
}
|
3781
3791
|
runGraphQLTests(name, rule, tests) {
|
3782
3792
|
super.run(name, rule, tests);
|
3793
|
+
// Skip snapshot testing if `expect` variable is not defined
|
3794
|
+
if (typeof expect === 'undefined') {
|
3795
|
+
return;
|
3796
|
+
}
|
3797
|
+
const linter = new eslint.Linter();
|
3798
|
+
linter.defineRule(name, rule);
|
3799
|
+
for (const testCase of tests.invalid) {
|
3800
|
+
const verifyConfig = getVerifyConfig(name, this.config, testCase);
|
3801
|
+
defineParser(linter, verifyConfig.parser);
|
3802
|
+
const { code, filename } = testCase;
|
3803
|
+
const messages = linter.verify(code, verifyConfig, { filename });
|
3804
|
+
for (const message of messages) {
|
3805
|
+
if (message.fatal) {
|
3806
|
+
throw new Error(message.message);
|
3807
|
+
}
|
3808
|
+
const messageForSnapshot = visualizeEslintMessage(code, message);
|
3809
|
+
// eslint-disable-next-line no-undef
|
3810
|
+
expect(messageForSnapshot).toMatchSnapshot();
|
3811
|
+
}
|
3812
|
+
}
|
3813
|
+
}
|
3814
|
+
}
|
3815
|
+
function getVerifyConfig(ruleId, testerConfig, testCase) {
|
3816
|
+
const { options, parserOptions, parser = testerConfig.parser } = testCase;
|
3817
|
+
return {
|
3818
|
+
...testerConfig,
|
3819
|
+
parser,
|
3820
|
+
parserOptions: {
|
3821
|
+
...testerConfig.parserOptions,
|
3822
|
+
...parserOptions,
|
3823
|
+
},
|
3824
|
+
rules: {
|
3825
|
+
[ruleId]: ['error', ...(Array.isArray(options) ? options : [])],
|
3826
|
+
},
|
3827
|
+
};
|
3828
|
+
}
|
3829
|
+
const parsers = new WeakMap();
|
3830
|
+
function defineParser(linter, parser) {
|
3831
|
+
if (!parser) {
|
3832
|
+
return;
|
3833
|
+
}
|
3834
|
+
if (!parsers.has(linter)) {
|
3835
|
+
parsers.set(linter, new Set());
|
3836
|
+
}
|
3837
|
+
const defined = parsers.get(linter);
|
3838
|
+
if (!defined.has(parser)) {
|
3839
|
+
defined.add(parser);
|
3840
|
+
linter.defineParser(parser, require(parser));
|
3841
|
+
}
|
3842
|
+
}
|
3843
|
+
function visualizeEslintMessage(text, result) {
|
3844
|
+
const { line, column, endLine, endColumn, message } = result;
|
3845
|
+
const location = {
|
3846
|
+
start: {
|
3847
|
+
line,
|
3848
|
+
column,
|
3849
|
+
},
|
3850
|
+
};
|
3851
|
+
if (typeof endLine === 'number' && typeof endColumn === 'number') {
|
3852
|
+
location.end = {
|
3853
|
+
line: endLine,
|
3854
|
+
column: endColumn,
|
3855
|
+
};
|
3783
3856
|
}
|
3857
|
+
return codeFrame.codeFrameColumns(text, location, {
|
3858
|
+
linesAbove: Number.POSITIVE_INFINITY,
|
3859
|
+
linesBelow: Number.POSITIVE_INFINITY,
|
3860
|
+
message,
|
3861
|
+
});
|
3784
3862
|
}
|
3785
3863
|
|
3786
3864
|
exports.GraphQLRuleTester = GraphQLRuleTester;
|
package/index.mjs
CHANGED
@@ -9,6 +9,8 @@ import depthLimit from 'graphql-depth-limit';
|
|
9
9
|
import { parseCode } from '@graphql-tools/graphql-tag-pluck';
|
10
10
|
import { loadConfigSync, GraphQLConfig } from 'graphql-config';
|
11
11
|
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
|
12
|
+
import { RuleTester, Linter } from 'eslint';
|
13
|
+
import { codeFrameColumns } from '@babel/code-frame';
|
12
14
|
|
13
15
|
/*
|
14
16
|
* 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
|
@@ -236,6 +238,24 @@ const convertCase = (style, str) => {
|
|
236
238
|
return lowerCase(str).replace(/ /g, '-');
|
237
239
|
}
|
238
240
|
};
|
241
|
+
function getLocation(loc, fieldName = '', offset) {
|
242
|
+
const { start } = loc;
|
243
|
+
/*
|
244
|
+
* ESLint has 0-based column number
|
245
|
+
* https://eslint.org/docs/developer-guide/working-with-rules#contextreport
|
246
|
+
*/
|
247
|
+
const { offsetStart = 1, offsetEnd = 1 } = offset !== null && offset !== void 0 ? offset : {};
|
248
|
+
return {
|
249
|
+
start: {
|
250
|
+
line: start.line,
|
251
|
+
column: start.column - offsetStart,
|
252
|
+
},
|
253
|
+
end: {
|
254
|
+
line: start.line,
|
255
|
+
column: start.column - offsetEnd + fieldName.length,
|
256
|
+
},
|
257
|
+
};
|
258
|
+
}
|
239
259
|
|
240
260
|
function extractRuleName(stack) {
|
241
261
|
const match = (stack || '').match(/validation[/\\\\]rules[/\\\\](.*?)\.js:/) || [];
|
@@ -1364,7 +1384,8 @@ const rule$7 = {
|
|
1364
1384
|
var _a;
|
1365
1385
|
if (options.fileExtension && options.fileExtension !== fileExtension) {
|
1366
1386
|
context.report({
|
1367
|
-
|
1387
|
+
// Report on first character
|
1388
|
+
loc: { column: 0, line: 1 },
|
1368
1389
|
messageId: MATCH_EXTENSION,
|
1369
1390
|
data: {
|
1370
1391
|
fileExtension,
|
@@ -1396,7 +1417,8 @@ const rule$7 = {
|
|
1396
1417
|
const filenameWithExtension = filename + expectedExtension;
|
1397
1418
|
if (expectedFilename !== filenameWithExtension) {
|
1398
1419
|
context.report({
|
1399
|
-
|
1420
|
+
// Report on first character
|
1421
|
+
loc: { column: 0, line: 1 },
|
1400
1422
|
messageId: MATCH_STYLE,
|
1401
1423
|
data: {
|
1402
1424
|
expectedFilename,
|
@@ -3179,11 +3201,7 @@ const rule$m = {
|
|
3179
3201
|
const RULE_NAME$3 = 'unique-fragment-name';
|
3180
3202
|
const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
|
3181
3203
|
const checkNode = (context, node, ruleName, messageId) => {
|
3182
|
-
|
3183
|
-
const documentName = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value;
|
3184
|
-
if (!documentName) {
|
3185
|
-
return;
|
3186
|
-
}
|
3204
|
+
const documentName = node.name.value;
|
3187
3205
|
const siblings = requireSiblingsOperations(ruleName, context);
|
3188
3206
|
const siblingDocuments = node.kind === Kind.FRAGMENT_DEFINITION ? siblings.getFragment(documentName) : siblings.getOperation(documentName);
|
3189
3207
|
const filepath = context.getFilename();
|
@@ -3194,7 +3212,6 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3194
3212
|
return isSameName && !isSamePath;
|
3195
3213
|
});
|
3196
3214
|
if (conflictingDocuments.length > 0) {
|
3197
|
-
const { start, end } = node.name.loc;
|
3198
3215
|
context.report({
|
3199
3216
|
messageId,
|
3200
3217
|
data: {
|
@@ -3203,16 +3220,7 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3203
3220
|
.map(f => `\t${relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
|
3204
3221
|
.join('\n'),
|
3205
3222
|
},
|
3206
|
-
loc:
|
3207
|
-
start: {
|
3208
|
-
line: start.line,
|
3209
|
-
column: start.column - 1,
|
3210
|
-
},
|
3211
|
-
end: {
|
3212
|
-
line: end.line,
|
3213
|
-
column: end.column - 1,
|
3214
|
-
},
|
3215
|
-
},
|
3223
|
+
loc: getLocation(node.name.loc, documentName),
|
3216
3224
|
});
|
3217
3225
|
}
|
3218
3226
|
};
|
@@ -3329,7 +3337,7 @@ const rule$o = {
|
|
3329
3337
|
},
|
3330
3338
|
create(context) {
|
3331
3339
|
return {
|
3332
|
-
OperationDefinition(node) {
|
3340
|
+
'OperationDefinition[name!=undefined]'(node) {
|
3333
3341
|
checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
|
3334
3342
|
},
|
3335
3343
|
};
|
@@ -3759,22 +3767,92 @@ function parseForESLint(code, options = {}) {
|
|
3759
3767
|
}
|
3760
3768
|
}
|
3761
3769
|
|
3762
|
-
class GraphQLRuleTester extends
|
3770
|
+
class GraphQLRuleTester extends RuleTester {
|
3763
3771
|
constructor(parserOptions = {}) {
|
3764
|
-
|
3772
|
+
const config = {
|
3765
3773
|
parser: require.resolve('@graphql-eslint/eslint-plugin'),
|
3766
3774
|
parserOptions: {
|
3767
3775
|
...parserOptions,
|
3768
3776
|
skipGraphQLConfig: true,
|
3769
3777
|
},
|
3770
|
-
}
|
3778
|
+
};
|
3779
|
+
super(config);
|
3780
|
+
this.config = config;
|
3771
3781
|
}
|
3772
3782
|
fromMockFile(path) {
|
3773
3783
|
return readFileSync(resolve(__dirname, `../tests/mocks/${path}`), 'utf-8');
|
3774
3784
|
}
|
3775
3785
|
runGraphQLTests(name, rule, tests) {
|
3776
3786
|
super.run(name, rule, tests);
|
3787
|
+
// Skip snapshot testing if `expect` variable is not defined
|
3788
|
+
if (typeof expect === 'undefined') {
|
3789
|
+
return;
|
3790
|
+
}
|
3791
|
+
const linter = new Linter();
|
3792
|
+
linter.defineRule(name, rule);
|
3793
|
+
for (const testCase of tests.invalid) {
|
3794
|
+
const verifyConfig = getVerifyConfig(name, this.config, testCase);
|
3795
|
+
defineParser(linter, verifyConfig.parser);
|
3796
|
+
const { code, filename } = testCase;
|
3797
|
+
const messages = linter.verify(code, verifyConfig, { filename });
|
3798
|
+
for (const message of messages) {
|
3799
|
+
if (message.fatal) {
|
3800
|
+
throw new Error(message.message);
|
3801
|
+
}
|
3802
|
+
const messageForSnapshot = visualizeEslintMessage(code, message);
|
3803
|
+
// eslint-disable-next-line no-undef
|
3804
|
+
expect(messageForSnapshot).toMatchSnapshot();
|
3805
|
+
}
|
3806
|
+
}
|
3807
|
+
}
|
3808
|
+
}
|
3809
|
+
function getVerifyConfig(ruleId, testerConfig, testCase) {
|
3810
|
+
const { options, parserOptions, parser = testerConfig.parser } = testCase;
|
3811
|
+
return {
|
3812
|
+
...testerConfig,
|
3813
|
+
parser,
|
3814
|
+
parserOptions: {
|
3815
|
+
...testerConfig.parserOptions,
|
3816
|
+
...parserOptions,
|
3817
|
+
},
|
3818
|
+
rules: {
|
3819
|
+
[ruleId]: ['error', ...(Array.isArray(options) ? options : [])],
|
3820
|
+
},
|
3821
|
+
};
|
3822
|
+
}
|
3823
|
+
const parsers = new WeakMap();
|
3824
|
+
function defineParser(linter, parser) {
|
3825
|
+
if (!parser) {
|
3826
|
+
return;
|
3827
|
+
}
|
3828
|
+
if (!parsers.has(linter)) {
|
3829
|
+
parsers.set(linter, new Set());
|
3830
|
+
}
|
3831
|
+
const defined = parsers.get(linter);
|
3832
|
+
if (!defined.has(parser)) {
|
3833
|
+
defined.add(parser);
|
3834
|
+
linter.defineParser(parser, require(parser));
|
3835
|
+
}
|
3836
|
+
}
|
3837
|
+
function visualizeEslintMessage(text, result) {
|
3838
|
+
const { line, column, endLine, endColumn, message } = result;
|
3839
|
+
const location = {
|
3840
|
+
start: {
|
3841
|
+
line,
|
3842
|
+
column,
|
3843
|
+
},
|
3844
|
+
};
|
3845
|
+
if (typeof endLine === 'number' && typeof endColumn === 'number') {
|
3846
|
+
location.end = {
|
3847
|
+
line: endLine,
|
3848
|
+
column: endColumn,
|
3849
|
+
};
|
3777
3850
|
}
|
3851
|
+
return codeFrameColumns(text, location, {
|
3852
|
+
linesAbove: Number.POSITIVE_INFINITY,
|
3853
|
+
linesBelow: Number.POSITIVE_INFINITY,
|
3854
|
+
message,
|
3855
|
+
});
|
3778
3856
|
}
|
3779
3857
|
|
3780
3858
|
export { GraphQLRuleTester, configs, convertDescription, convertLocation, convertRange, convertToESTree, extractCommentsFromAst, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };
|
package/package.json
CHANGED
package/testkit.d.ts
CHANGED
@@ -13,8 +13,11 @@ export declare type GraphQLInvalidTestCase<T> = GraphQLValidTestCase<T> & {
|
|
13
13
|
errors: number | Array<RuleTester.TestCaseError | string>;
|
14
14
|
output?: string | null;
|
15
15
|
};
|
16
|
-
declare
|
17
|
-
|
16
|
+
export declare class GraphQLRuleTester extends RuleTester {
|
17
|
+
config: {
|
18
|
+
parser: string;
|
19
|
+
parserOptions: ParserOptions;
|
20
|
+
};
|
18
21
|
constructor(parserOptions?: ParserOptions);
|
19
22
|
fromMockFile(path: string): string;
|
20
23
|
runGraphQLTests<Config>(name: string, rule: GraphQLESLintRule, tests: {
|
@@ -22,4 +25,3 @@ export declare class GraphQLRuleTester extends GraphQLRuleTester_base {
|
|
22
25
|
invalid: GraphQLInvalidTestCase<Config>[];
|
23
26
|
}): void;
|
24
27
|
}
|
25
|
-
export {};
|
package/utils.d.ts
CHANGED
@@ -33,4 +33,8 @@ export declare enum CaseStyle {
|
|
33
33
|
}
|
34
34
|
export declare const camelCase: (str: string) => string;
|
35
35
|
export declare const convertCase: (style: CaseStyle, str: string) => string;
|
36
|
+
export declare function getLocation(loc: Partial<AST.SourceLocation>, fieldName?: string, offset?: {
|
37
|
+
offsetStart?: number;
|
38
|
+
offsetEnd?: number;
|
39
|
+
}): AST.SourceLocation;
|
36
40
|
export {};
|