@graphql-eslint/eslint-plugin 3.8.0-alpha-c1d1797.0 → 3.8.0-alpha-2d2b247.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)
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,10 @@ 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: `Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.`,
694
696
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
695
697
  examples: [
696
698
  {
@@ -848,24 +850,56 @@ const rule = {
848
850
  },
849
851
  create(context) {
850
852
  var _a, _b, _c, _d, _e;
853
+ const sourceCode = context.getSourceCode();
854
+ function isNodeAndCommentOnSameLine(node, comment) {
855
+ return node.loc.end.line === comment.loc.start.line;
856
+ }
857
+ function getBeforeComments(node) {
858
+ const commentsBefore = sourceCode.getCommentsBefore(node);
859
+ if (commentsBefore.length === 0) {
860
+ return [];
861
+ }
862
+ const tokenBefore = sourceCode.getTokenBefore(node);
863
+ if (tokenBefore) {
864
+ return commentsBefore.filter(comment => !isNodeAndCommentOnSameLine(tokenBefore, comment));
865
+ }
866
+ return commentsBefore;
867
+ }
868
+ function getRangeWithComments(node) {
869
+ const [firstBeforeComment] = getBeforeComments(node);
870
+ const [firstAfterComment] = sourceCode.getCommentsAfter(node);
871
+ const from = firstBeforeComment || node;
872
+ const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment) ? firstAfterComment : node;
873
+ return [from.range[0], to.range[1]];
874
+ }
851
875
  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;
857
- context.report({
858
- loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
859
- messageId: ALPHABETIZE,
860
- data: isVariableNode
861
- ? {
862
- currName: `$${currName}`,
863
- prevName: `$${prevName}`,
864
- }
865
- : { currName, prevName },
866
- });
876
+ // Starts from 1, ignore nodes.length <= 1
877
+ for (let i = 1; i < nodes.length; i += 1) {
878
+ const prevNode = nodes[i - 1];
879
+ const currNode = nodes[i];
880
+ const prevName = prevNode.name.value;
881
+ const currName = currNode.name.value;
882
+ // Compare with lexicographic order
883
+ if (prevName.localeCompare(currName) !== 1) {
884
+ continue;
867
885
  }
868
- prevName = currName;
886
+ const isVariableNode = currNode.kind === graphql.Kind.VARIABLE;
887
+ context.report({
888
+ loc: getLocation(currNode.name.loc, currName, { offsetStart: isVariableNode ? 2 : 1 }),
889
+ messageId: ALPHABETIZE,
890
+ data: isVariableNode
891
+ ? {
892
+ currName: `$${currName}`,
893
+ prevName: `$${prevName}`,
894
+ }
895
+ : { currName, prevName },
896
+ *fix(fixer) {
897
+ const prevRange = getRangeWithComments(prevNode);
898
+ const currRange = getRangeWithComments(currNode);
899
+ yield fixer.replaceTextRange(prevRange, sourceCode.getText({ range: currRange }));
900
+ yield fixer.replaceTextRange(currRange, sourceCode.getText({ range: prevRange }));
901
+ },
902
+ });
869
903
  }
870
904
  }
871
905
  const opts = context.options[0];
@@ -3975,19 +4009,30 @@ class GraphQLRuleTester extends eslint.RuleTester {
3975
4009
  }
3976
4010
  const linter = new eslint.Linter();
3977
4011
  linter.defineRule(name, rule);
4012
+ const hasOnlyTest = tests.invalid.some(t => t.only);
3978
4013
  for (const testCase of tests.invalid) {
4014
+ const { only, code, filename } = testCase;
4015
+ if (hasOnlyTest && !only) {
4016
+ continue;
4017
+ }
3979
4018
  const verifyConfig = getVerifyConfig(name, this.config, testCase);
3980
4019
  defineParser(linter, verifyConfig.parser);
3981
- const { code, filename } = testCase;
3982
4020
  const messages = linter.verify(code, verifyConfig, { filename });
3983
- for (const message of messages) {
4021
+ const messageForSnapshot = [];
4022
+ for (const [index, message] of messages.entries()) {
3984
4023
  if (message.fatal) {
3985
4024
  throw new Error(message.message);
3986
4025
  }
3987
- const messageForSnapshot = visualizeEslintMessage(code, message);
3988
- // eslint-disable-next-line no-undef
3989
- expect(messageForSnapshot).toMatchSnapshot();
4026
+ messageForSnapshot.push(`Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
4027
+ }
4028
+ if (rule.meta.fixable) {
4029
+ const { fixed, output } = linter.verifyAndFix(code, verifyConfig, { filename });
4030
+ if (fixed) {
4031
+ messageForSnapshot.push('Autofix output', dedent(output));
4032
+ }
3990
4033
  }
4034
+ // eslint-disable-next-line no-undef
4035
+ expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
3991
4036
  }
3992
4037
  }
3993
4038
  }
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,10 @@ 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: `Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.`,
688
690
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
689
691
  examples: [
690
692
  {
@@ -842,24 +844,56 @@ const rule = {
842
844
  },
843
845
  create(context) {
844
846
  var _a, _b, _c, _d, _e;
847
+ const sourceCode = context.getSourceCode();
848
+ function isNodeAndCommentOnSameLine(node, comment) {
849
+ return node.loc.end.line === comment.loc.start.line;
850
+ }
851
+ function getBeforeComments(node) {
852
+ const commentsBefore = sourceCode.getCommentsBefore(node);
853
+ if (commentsBefore.length === 0) {
854
+ return [];
855
+ }
856
+ const tokenBefore = sourceCode.getTokenBefore(node);
857
+ if (tokenBefore) {
858
+ return commentsBefore.filter(comment => !isNodeAndCommentOnSameLine(tokenBefore, comment));
859
+ }
860
+ return commentsBefore;
861
+ }
862
+ function getRangeWithComments(node) {
863
+ const [firstBeforeComment] = getBeforeComments(node);
864
+ const [firstAfterComment] = sourceCode.getCommentsAfter(node);
865
+ const from = firstBeforeComment || node;
866
+ const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment) ? firstAfterComment : node;
867
+ return [from.range[0], to.range[1]];
868
+ }
845
869
  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;
851
- context.report({
852
- loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
853
- messageId: ALPHABETIZE,
854
- data: isVariableNode
855
- ? {
856
- currName: `$${currName}`,
857
- prevName: `$${prevName}`,
858
- }
859
- : { currName, prevName },
860
- });
870
+ // Starts from 1, ignore nodes.length <= 1
871
+ for (let i = 1; i < nodes.length; i += 1) {
872
+ const prevNode = nodes[i - 1];
873
+ const currNode = nodes[i];
874
+ const prevName = prevNode.name.value;
875
+ const currName = currNode.name.value;
876
+ // Compare with lexicographic order
877
+ if (prevName.localeCompare(currName) !== 1) {
878
+ continue;
861
879
  }
862
- prevName = currName;
880
+ const isVariableNode = currNode.kind === Kind.VARIABLE;
881
+ context.report({
882
+ loc: getLocation(currNode.name.loc, currName, { offsetStart: isVariableNode ? 2 : 1 }),
883
+ messageId: ALPHABETIZE,
884
+ data: isVariableNode
885
+ ? {
886
+ currName: `$${currName}`,
887
+ prevName: `$${prevName}`,
888
+ }
889
+ : { currName, prevName },
890
+ *fix(fixer) {
891
+ const prevRange = getRangeWithComments(prevNode);
892
+ const currRange = getRangeWithComments(currNode);
893
+ yield fixer.replaceTextRange(prevRange, sourceCode.getText({ range: currRange }));
894
+ yield fixer.replaceTextRange(currRange, sourceCode.getText({ range: prevRange }));
895
+ },
896
+ });
863
897
  }
864
898
  }
865
899
  const opts = context.options[0];
@@ -3969,19 +4003,30 @@ class GraphQLRuleTester extends RuleTester {
3969
4003
  }
3970
4004
  const linter = new Linter();
3971
4005
  linter.defineRule(name, rule);
4006
+ const hasOnlyTest = tests.invalid.some(t => t.only);
3972
4007
  for (const testCase of tests.invalid) {
4008
+ const { only, code, filename } = testCase;
4009
+ if (hasOnlyTest && !only) {
4010
+ continue;
4011
+ }
3973
4012
  const verifyConfig = getVerifyConfig(name, this.config, testCase);
3974
4013
  defineParser(linter, verifyConfig.parser);
3975
- const { code, filename } = testCase;
3976
4014
  const messages = linter.verify(code, verifyConfig, { filename });
3977
- for (const message of messages) {
4015
+ const messageForSnapshot = [];
4016
+ for (const [index, message] of messages.entries()) {
3978
4017
  if (message.fatal) {
3979
4018
  throw new Error(message.message);
3980
4019
  }
3981
- const messageForSnapshot = visualizeEslintMessage(code, message);
3982
- // eslint-disable-next-line no-undef
3983
- expect(messageForSnapshot).toMatchSnapshot();
4020
+ messageForSnapshot.push(`Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
4021
+ }
4022
+ if (rule.meta.fixable) {
4023
+ const { fixed, output } = linter.verifyAndFix(code, verifyConfig, { filename });
4024
+ if (fixed) {
4025
+ messageForSnapshot.push('Autofix output', dedent(output));
4026
+ }
3984
4027
  }
4028
+ // eslint-disable-next-line no-undef
4029
+ expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
3985
4030
  }
3986
4031
  }
3987
4032
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "3.8.0-alpha-c1d1797.0",
3
+ "version": "3.8.0-alpha-2d2b247.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
  };