@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.
- package/docs/rules/alphabetize.md +2 -0
- package/index.js +67 -22
- package/index.mjs +67 -22
- package/package.json +2 -1
- package/rules/alphabetize.d.ts +1 -1
- package/rules/index.d.ts +1 -7
- package/testkit.d.ts +0 -1
@@ -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:
|
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
|
-
|
853
|
-
for (
|
854
|
-
const
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
3988
|
-
|
3989
|
-
|
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:
|
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
|
-
|
847
|
-
for (
|
848
|
-
const
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
3982
|
-
|
3983
|
-
|
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-
|
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"
|
package/rules/alphabetize.d.ts
CHANGED
@@ -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
|
};
|