@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.
@@ -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-js';
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
- node: documentNode,
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
- node: documentNode,
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
- var _a;
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 require('eslint').RuleTester {
3776
+ class GraphQLRuleTester extends eslint.RuleTester {
3769
3777
  constructor(parserOptions = {}) {
3770
- super({
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
- node: documentNode,
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
- node: documentNode,
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
- var _a;
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 require('eslint').RuleTester {
3770
+ class GraphQLRuleTester extends RuleTester {
3763
3771
  constructor(parserOptions = {}) {
3764
- super({
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "2.3.1",
3
+ "version": "2.3.2-alpha-99be3d2.0",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "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"
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 const GraphQLRuleTester_base: any;
17
- export declare class GraphQLRuleTester extends GraphQLRuleTester_base {
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 {};