@graphql-eslint/eslint-plugin 3.8.0-alpha-85bee2b.0 → 3.8.0-alpha-db02c77.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/README.md CHANGED
@@ -7,7 +7,7 @@ Each rule has emojis denoting:
7
7
 
8
8
  <!-- 🚨 IMPORTANT! Do not manually modify this table. Run: `yarn generate:docs` -->
9
9
  <!-- prettier-ignore-start -->
10
- Name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|Description|&nbsp;&nbsp;&nbsp;&nbsp;Config&nbsp;&nbsp;&nbsp;&nbsp;|🚀 / 🔮
10
+ Name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|Description|&nbsp;&nbsp;&nbsp;&nbsp;Config&nbsp;&nbsp;&nbsp;&nbsp;|🚀&nbsp;/&nbsp;🔮
11
11
  -|-|:-:|:-:
12
12
  [alphabetize](rules/alphabetize.md)|Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.|![all][]|🚀
13
13
  [description-style](rules/description-style.md)|Require all comments to follow the same style (either block or inline).|![recommended][]|🚀
@@ -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
@@ -99,7 +99,7 @@ Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October
99
99
 
100
100
  Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#OperationDefinition).
101
101
 
102
- > You must use only comment syntax (`#`) and not description syntax (`"""` or `"`).
102
+ > You must use only comment syntax `#` and not description syntax `"""` or `"`.
103
103
 
104
104
  ### `ScalarTypeDefinition` (boolean)
105
105
 
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[beforeComments.length - 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];
@@ -2704,7 +2734,7 @@ const rule$h = {
2704
2734
  ...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => {
2705
2735
  let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
2706
2736
  if (kind === graphql.Kind.OPERATION_DEFINITION) {
2707
- description += '\n\n> You must use only comment syntax (`#`) and not description syntax (`"""` or `"`).';
2737
+ description += '\n\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
2708
2738
  }
2709
2739
  return [kind, { type: 'boolean', description }];
2710
2740
  })),
@@ -3975,19 +4005,30 @@ class GraphQLRuleTester extends eslint.RuleTester {
3975
4005
  }
3976
4006
  const linter = new eslint.Linter();
3977
4007
  linter.defineRule(name, rule);
4008
+ const hasOnlyTest = tests.invalid.some(t => t.only);
3978
4009
  for (const testCase of tests.invalid) {
4010
+ const { only, code, filename } = testCase;
4011
+ if (hasOnlyTest && !only) {
4012
+ continue;
4013
+ }
3979
4014
  const verifyConfig = getVerifyConfig(name, this.config, testCase);
3980
4015
  defineParser(linter, verifyConfig.parser);
3981
- const { code, filename } = testCase;
3982
4016
  const messages = linter.verify(code, verifyConfig, { filename });
3983
- for (const message of messages) {
4017
+ const messageForSnapshot = [];
4018
+ for (const [index, message] of messages.entries()) {
3984
4019
  if (message.fatal) {
3985
4020
  throw new Error(message.message);
3986
4021
  }
3987
- const messageForSnapshot = visualizeEslintMessage(code, message);
3988
- // eslint-disable-next-line no-undef
3989
- 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
+ }
3990
4029
  }
4030
+ // eslint-disable-next-line no-undef
4031
+ expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
3991
4032
  }
3992
4033
  }
3993
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[beforeComments.length - 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];
@@ -2698,7 +2728,7 @@ const rule$h = {
2698
2728
  ...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => {
2699
2729
  let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
2700
2730
  if (kind === Kind.OPERATION_DEFINITION) {
2701
- description += '\n\n> You must use only comment syntax (`#`) and not description syntax (`"""` or `"`).';
2731
+ description += '\n\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
2702
2732
  }
2703
2733
  return [kind, { type: 'boolean', description }];
2704
2734
  })),
@@ -3969,19 +3999,30 @@ class GraphQLRuleTester extends RuleTester {
3969
3999
  }
3970
4000
  const linter = new Linter();
3971
4001
  linter.defineRule(name, rule);
4002
+ const hasOnlyTest = tests.invalid.some(t => t.only);
3972
4003
  for (const testCase of tests.invalid) {
4004
+ const { only, code, filename } = testCase;
4005
+ if (hasOnlyTest && !only) {
4006
+ continue;
4007
+ }
3973
4008
  const verifyConfig = getVerifyConfig(name, this.config, testCase);
3974
4009
  defineParser(linter, verifyConfig.parser);
3975
- const { code, filename } = testCase;
3976
4010
  const messages = linter.verify(code, verifyConfig, { filename });
3977
- for (const message of messages) {
4011
+ const messageForSnapshot = [];
4012
+ for (const [index, message] of messages.entries()) {
3978
4013
  if (message.fatal) {
3979
4014
  throw new Error(message.message);
3980
4015
  }
3981
- const messageForSnapshot = visualizeEslintMessage(code, message);
3982
- // eslint-disable-next-line no-undef
3983
- 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
+ }
3984
4023
  }
4024
+ // eslint-disable-next-line no-undef
4025
+ expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
3985
4026
  }
3986
4027
  }
3987
4028
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "3.8.0-alpha-85bee2b.0",
3
+ "version": "3.8.0-alpha-db02c77.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>;
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
  };