@graphql-eslint/eslint-plugin 3.8.0-alpha-998ffa8.0 → 3.8.0-alpha-a8fcc7b.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.
@@ -1,5 +1,7 @@
1
1
  # `alphabetize`
2
2
 
3
+ 🔧 The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#--fix) can automatically fix some of the problems reported by this rule.
4
+
3
5
  - Category: `Schema & Operations`
4
6
  - Rule name: `@graphql-eslint/alphabetize`
5
7
  - Requires GraphQL Schema: `false` [â„šī¸](../../README.md#extended-linting-rules-with-graphql-schema)
@@ -7,6 +9,8 @@
7
9
 
8
10
  Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.
9
11
 
12
+ > Note: autofix will work only for fields without comments (between or around)
13
+
10
14
  ## Usage Examples
11
15
 
12
16
  ### Incorrect
@@ -131,7 +131,7 @@ The schema defines the following additional types:
131
131
 
132
132
  ## `asString` (enum)
133
133
 
134
- One of: `camelCase`, `PascalCase`, `snake_case`, `UPPER_CASE`, `kebab-case`, `documentStyle`
134
+ One of: `camelCase`, `PascalCase`, `snake_case`, `UPPER_CASE`, `kebab-case`
135
135
 
136
136
  ## `asObject` (object)
137
137
 
@@ -146,7 +146,6 @@ This element must be one of the following enum values:
146
146
  - `snake_case`
147
147
  - `UPPER_CASE`
148
148
  - `kebab-case`
149
- - `documentStyle`
150
149
 
151
150
  ### `suffix` (string)
152
151
 
package/index.js CHANGED
@@ -17,6 +17,7 @@ const graphqlConfig = require('graphql-config');
17
17
  const codeFileLoader = require('@graphql-tools/code-file-loader');
18
18
  const eslint = require('eslint');
19
19
  const codeFrame = require('@babel/code-frame');
20
+ const dedent = _interopDefault(require('dedent'));
20
21
 
21
22
  const base = {
22
23
  parser: '@graphql-eslint/eslint-plugin',
@@ -688,9 +689,13 @@ const argumentsEnum = [
688
689
  const rule = {
689
690
  meta: {
690
691
  type: 'suggestion',
692
+ fixable: 'code',
691
693
  docs: {
692
694
  category: ['Schema', 'Operations'],
693
- description: 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
695
+ description: [
696
+ 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
697
+ '> Note: autofix will work only for fields without comments (between or around)',
698
+ ].join('\n\n'),
694
699
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
695
700
  examples: [
696
701
  {
@@ -848,14 +853,20 @@ const rule = {
848
853
  },
849
854
  create(context) {
850
855
  var _a, _b, _c, _d, _e;
856
+ function isOnSameLineNodeAndComment(beforeNode, afterNode) {
857
+ return beforeNode.loc.end.line === afterNode.loc.start.line;
858
+ }
851
859
  function checkNodes(nodes) {
852
- let prevName = null;
853
- for (const node of nodes) {
854
- const currName = node.name.value;
855
- if (prevName && prevName > currName) {
856
- const isVariableNode = node.kind === graphql.Kind.VARIABLE;
860
+ // Starts from 1, ignore nodes.length <= 1
861
+ for (let i = 1; i < nodes.length; i += 1) {
862
+ const prevNode = nodes[i - 1];
863
+ const currNode = nodes[i];
864
+ const prevName = prevNode.name.value;
865
+ const currName = currNode.name.value;
866
+ if (prevName.localeCompare(currName) === 1) {
867
+ const isVariableNode = currNode.kind === graphql.Kind.VARIABLE;
857
868
  context.report({
858
- loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
869
+ loc: getLocation(currNode.loc, currName, { offsetEnd: isVariableNode ? 0 : 1 }),
859
870
  messageId: ALPHABETIZE,
860
871
  data: isVariableNode
861
872
  ? {
@@ -863,9 +874,28 @@ const rule = {
863
874
  prevName: `$${prevName}`,
864
875
  }
865
876
  : { currName, prevName },
877
+ *fix(fixer) {
878
+ const prev = prevNode;
879
+ const curr = currNode;
880
+ const sourceCode = context.getSourceCode();
881
+ const beforeComments = sourceCode.getCommentsBefore(prev);
882
+ if (beforeComments.length > 0) {
883
+ const tokenBefore = sourceCode.getTokenBefore(prev);
884
+ const lastBeforeComment = beforeComments.at(-1);
885
+ if (!tokenBefore || !isOnSameLineNodeAndComment(tokenBefore, lastBeforeComment))
886
+ return;
887
+ }
888
+ const betweenComments = sourceCode.getCommentsBefore(curr);
889
+ if (betweenComments.length > 0)
890
+ return;
891
+ const [firstAfterComment] = sourceCode.getCommentsAfter(curr);
892
+ if (firstAfterComment && isOnSameLineNodeAndComment(curr, firstAfterComment))
893
+ return;
894
+ yield fixer.replaceText(prev, sourceCode.getText(curr));
895
+ yield fixer.replaceText(curr, sourceCode.getText(prev));
896
+ },
866
897
  });
867
898
  }
868
- prevName = currName;
869
899
  }
870
900
  }
871
901
  const opts = context.options[0];
@@ -1098,7 +1128,7 @@ const rule$2 = {
1098
1128
  const MATCH_EXTENSION = 'MATCH_EXTENSION';
1099
1129
  const MATCH_STYLE = 'MATCH_STYLE';
1100
1130
  const ACCEPTED_EXTENSIONS = ['.gql', '.graphql'];
1101
- const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case', 'documentStyle'];
1131
+ const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case'];
1102
1132
  const schemaOption = {
1103
1133
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1104
1134
  };
@@ -1272,14 +1302,7 @@ const rule$3 = {
1272
1302
  option = { style: option };
1273
1303
  }
1274
1304
  const expectedExtension = options.fileExtension || fileExtension;
1275
- let expectedFilename;
1276
- if (option.style) {
1277
- expectedFilename = option.style === 'documentStyle' ? docName : convertCase(option.style, docName);
1278
- }
1279
- else {
1280
- expectedFilename = filename;
1281
- }
1282
- expectedFilename += (option.suffix || '') + expectedExtension;
1305
+ const expectedFilename = (option.style ? convertCase(option.style, docName) : filename) + (option.suffix || '') + expectedExtension;
1283
1306
  const filenameWithExtension = filename + expectedExtension;
1284
1307
  if (expectedFilename !== filenameWithExtension) {
1285
1308
  context.report({
@@ -3982,19 +4005,30 @@ class GraphQLRuleTester extends eslint.RuleTester {
3982
4005
  }
3983
4006
  const linter = new eslint.Linter();
3984
4007
  linter.defineRule(name, rule);
4008
+ const hasOnlyTest = tests.invalid.some(t => t.only);
3985
4009
  for (const testCase of tests.invalid) {
4010
+ const { only, code, filename } = testCase;
4011
+ if (hasOnlyTest && !only) {
4012
+ continue;
4013
+ }
3986
4014
  const verifyConfig = getVerifyConfig(name, this.config, testCase);
3987
4015
  defineParser(linter, verifyConfig.parser);
3988
- const { code, filename } = testCase;
3989
4016
  const messages = linter.verify(code, verifyConfig, { filename });
3990
- for (const message of messages) {
4017
+ const messageForSnapshot = [];
4018
+ for (const [index, message] of messages.entries()) {
3991
4019
  if (message.fatal) {
3992
4020
  throw new Error(message.message);
3993
4021
  }
3994
- const messageForSnapshot = visualizeEslintMessage(code, message);
3995
- // eslint-disable-next-line no-undef
3996
- expect(messageForSnapshot).toMatchSnapshot();
4022
+ messageForSnapshot.push(`Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
4023
+ }
4024
+ if (rule.meta.fixable) {
4025
+ const { fixed, output } = linter.verifyAndFix(code, verifyConfig, { filename });
4026
+ if (fixed) {
4027
+ messageForSnapshot.push('Autofix output', dedent(output));
4028
+ }
3997
4029
  }
4030
+ // eslint-disable-next-line no-undef
4031
+ expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
3998
4032
  }
3999
4033
  }
4000
4034
  }
package/index.mjs CHANGED
@@ -11,6 +11,7 @@ import { loadConfigSync, GraphQLConfig } from 'graphql-config';
11
11
  import { CodeFileLoader } from '@graphql-tools/code-file-loader';
12
12
  import { RuleTester, Linter } from 'eslint';
13
13
  import { codeFrameColumns } from '@babel/code-frame';
14
+ import dedent from 'dedent';
14
15
 
15
16
  const base = {
16
17
  parser: '@graphql-eslint/eslint-plugin',
@@ -682,9 +683,13 @@ const argumentsEnum = [
682
683
  const rule = {
683
684
  meta: {
684
685
  type: 'suggestion',
686
+ fixable: 'code',
685
687
  docs: {
686
688
  category: ['Schema', 'Operations'],
687
- description: 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
689
+ description: [
690
+ 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
691
+ '> Note: autofix will work only for fields without comments (between or around)',
692
+ ].join('\n\n'),
688
693
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
689
694
  examples: [
690
695
  {
@@ -842,14 +847,20 @@ const rule = {
842
847
  },
843
848
  create(context) {
844
849
  var _a, _b, _c, _d, _e;
850
+ function isOnSameLineNodeAndComment(beforeNode, afterNode) {
851
+ return beforeNode.loc.end.line === afterNode.loc.start.line;
852
+ }
845
853
  function checkNodes(nodes) {
846
- let prevName = null;
847
- for (const node of nodes) {
848
- const currName = node.name.value;
849
- if (prevName && prevName > currName) {
850
- const isVariableNode = node.kind === Kind.VARIABLE;
854
+ // Starts from 1, ignore nodes.length <= 1
855
+ for (let i = 1; i < nodes.length; i += 1) {
856
+ const prevNode = nodes[i - 1];
857
+ const currNode = nodes[i];
858
+ const prevName = prevNode.name.value;
859
+ const currName = currNode.name.value;
860
+ if (prevName.localeCompare(currName) === 1) {
861
+ const isVariableNode = currNode.kind === Kind.VARIABLE;
851
862
  context.report({
852
- loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
863
+ loc: getLocation(currNode.loc, currName, { offsetEnd: isVariableNode ? 0 : 1 }),
853
864
  messageId: ALPHABETIZE,
854
865
  data: isVariableNode
855
866
  ? {
@@ -857,9 +868,28 @@ const rule = {
857
868
  prevName: `$${prevName}`,
858
869
  }
859
870
  : { currName, prevName },
871
+ *fix(fixer) {
872
+ const prev = prevNode;
873
+ const curr = currNode;
874
+ const sourceCode = context.getSourceCode();
875
+ const beforeComments = sourceCode.getCommentsBefore(prev);
876
+ if (beforeComments.length > 0) {
877
+ const tokenBefore = sourceCode.getTokenBefore(prev);
878
+ const lastBeforeComment = beforeComments.at(-1);
879
+ if (!tokenBefore || !isOnSameLineNodeAndComment(tokenBefore, lastBeforeComment))
880
+ return;
881
+ }
882
+ const betweenComments = sourceCode.getCommentsBefore(curr);
883
+ if (betweenComments.length > 0)
884
+ return;
885
+ const [firstAfterComment] = sourceCode.getCommentsAfter(curr);
886
+ if (firstAfterComment && isOnSameLineNodeAndComment(curr, firstAfterComment))
887
+ return;
888
+ yield fixer.replaceText(prev, sourceCode.getText(curr));
889
+ yield fixer.replaceText(curr, sourceCode.getText(prev));
890
+ },
860
891
  });
861
892
  }
862
- prevName = currName;
863
893
  }
864
894
  }
865
895
  const opts = context.options[0];
@@ -1092,7 +1122,7 @@ const rule$2 = {
1092
1122
  const MATCH_EXTENSION = 'MATCH_EXTENSION';
1093
1123
  const MATCH_STYLE = 'MATCH_STYLE';
1094
1124
  const ACCEPTED_EXTENSIONS = ['.gql', '.graphql'];
1095
- const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case', 'documentStyle'];
1125
+ const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case'];
1096
1126
  const schemaOption = {
1097
1127
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1098
1128
  };
@@ -1266,14 +1296,7 @@ const rule$3 = {
1266
1296
  option = { style: option };
1267
1297
  }
1268
1298
  const expectedExtension = options.fileExtension || fileExtension;
1269
- let expectedFilename;
1270
- if (option.style) {
1271
- expectedFilename = option.style === 'documentStyle' ? docName : convertCase(option.style, docName);
1272
- }
1273
- else {
1274
- expectedFilename = filename;
1275
- }
1276
- expectedFilename += (option.suffix || '') + expectedExtension;
1299
+ const expectedFilename = (option.style ? convertCase(option.style, docName) : filename) + (option.suffix || '') + expectedExtension;
1277
1300
  const filenameWithExtension = filename + expectedExtension;
1278
1301
  if (expectedFilename !== filenameWithExtension) {
1279
1302
  context.report({
@@ -3976,19 +3999,30 @@ class GraphQLRuleTester extends RuleTester {
3976
3999
  }
3977
4000
  const linter = new Linter();
3978
4001
  linter.defineRule(name, rule);
4002
+ const hasOnlyTest = tests.invalid.some(t => t.only);
3979
4003
  for (const testCase of tests.invalid) {
4004
+ const { only, code, filename } = testCase;
4005
+ if (hasOnlyTest && !only) {
4006
+ continue;
4007
+ }
3980
4008
  const verifyConfig = getVerifyConfig(name, this.config, testCase);
3981
4009
  defineParser(linter, verifyConfig.parser);
3982
- const { code, filename } = testCase;
3983
4010
  const messages = linter.verify(code, verifyConfig, { filename });
3984
- for (const message of messages) {
4011
+ const messageForSnapshot = [];
4012
+ for (const [index, message] of messages.entries()) {
3985
4013
  if (message.fatal) {
3986
4014
  throw new Error(message.message);
3987
4015
  }
3988
- const messageForSnapshot = visualizeEslintMessage(code, message);
3989
- // eslint-disable-next-line no-undef
3990
- expect(messageForSnapshot).toMatchSnapshot();
4016
+ messageForSnapshot.push(`Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
4017
+ }
4018
+ if (rule.meta.fixable) {
4019
+ const { fixed, output } = linter.verifyAndFix(code, verifyConfig, { filename });
4020
+ if (fixed) {
4021
+ messageForSnapshot.push('Autofix output', dedent(output));
4022
+ }
3991
4023
  }
4024
+ // eslint-disable-next-line no-undef
4025
+ expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
3992
4026
  }
3993
4027
  }
3994
4028
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "3.8.0-alpha-998ffa8.0",
3
+ "version": "3.8.0-alpha-a8fcc7b.0",
4
4
  "description": "GraphQL plugin for ESLint",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
@@ -12,6 +12,7 @@
12
12
  "@graphql-tools/graphql-tag-pluck": "7.1.5",
13
13
  "@graphql-tools/utils": "8.6.1",
14
14
  "chalk": "4.1.2",
15
+ "dedent": "0.7.0",
15
16
  "graphql-config": "4.1.0",
16
17
  "graphql-depth-limit": "1.1.0",
17
18
  "lodash.lowercase": "4.3.0"
@@ -4,7 +4,7 @@ declare const valuesEnum: ['EnumTypeDefinition'];
4
4
  declare const selectionsEnum: ('OperationDefinition' | 'FragmentDefinition')[];
5
5
  declare const variablesEnum: ['OperationDefinition'];
6
6
  declare const argumentsEnum: ('FieldDefinition' | 'Field' | 'DirectiveDefinition' | 'Directive')[];
7
- declare type AlphabetizeConfig = {
7
+ export declare type AlphabetizeConfig = {
8
8
  fields?: typeof fieldsEnum;
9
9
  values?: typeof valuesEnum;
10
10
  selections?: typeof selectionsEnum;
package/rules/index.d.ts CHANGED
@@ -1,11 +1,5 @@
1
1
  export declare const rules: {
2
- alphabetize: import("..").GraphQLESLintRule<[{
3
- fields?: ("ObjectTypeDefinition" | "InterfaceTypeDefinition" | "InputObjectTypeDefinition")[];
4
- values?: ["EnumTypeDefinition"];
5
- selections?: ("OperationDefinition" | "FragmentDefinition")[];
6
- variables?: ["OperationDefinition"];
7
- arguments?: ("Field" | "Directive" | "FieldDefinition" | "DirectiveDefinition")[];
8
- }], false>;
2
+ alphabetize: import("..").GraphQLESLintRule<[import("./alphabetize").AlphabetizeConfig], false>;
9
3
  'description-style': import("..").GraphQLESLintRule<[{
10
4
  style: "block" | "inline";
11
5
  }], false>;
@@ -1,6 +1,5 @@
1
- import { CaseStyle as _CaseStyle } from '../utils';
1
+ import { CaseStyle } from '../utils';
2
2
  import { GraphQLESLintRule } from '../types';
3
- declare type CaseStyle = _CaseStyle | 'documentStyle';
4
3
  declare const ACCEPTED_EXTENSIONS: ['.gql', '.graphql'];
5
4
  declare type PropertySchema = {
6
5
  style?: CaseStyle;
package/testkit.d.ts CHANGED
@@ -6,7 +6,6 @@ export declare type GraphQLESLintRuleListener<WithTypeInfo extends boolean = fal
6
6
  [K in keyof ASTKindToNode]?: (node: GraphQLESTreeNode<ASTKindToNode[K], WithTypeInfo>) => void;
7
7
  } & Record<string, any>;
8
8
  export declare type GraphQLValidTestCase<Options> = Omit<RuleTester.ValidTestCase, 'options' | 'parserOptions'> & {
9
- name?: string;
10
9
  options?: Options;
11
10
  parserOptions?: ParserOptions;
12
11
  };