@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 +1 -1
- package/docs/rules/alphabetize.md +4 -0
- package/docs/rules/require-description.md +1 -1
- package/index.js +55 -14
- package/index.mjs +55 -14
- 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
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 |Description| Config  
|
10
|
+
Name |Description| Config |đ / đŽ
|
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
|
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:
|
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
|
-
|
853
|
-
for (
|
854
|
-
const
|
855
|
-
|
856
|
-
|
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(
|
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
|
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
|
-
|
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
|
-
|
3988
|
-
|
3989
|
-
|
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:
|
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
|
-
|
847
|
-
for (
|
848
|
-
const
|
849
|
-
|
850
|
-
|
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(
|
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
|
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
|
-
|
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
|
-
|
3982
|
-
|
3983
|
-
|
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-
|
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"
|
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
|
};
|