@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.
- package/docs/rules/alphabetize.md +4 -0
- package/docs/rules/match-document-filename.md +1 -2
- package/index.js +56 -22
- package/index.mjs +56 -22
- package/package.json +2 -1
- package/rules/alphabetize.d.ts +1 -1
- package/rules/index.d.ts +1 -7
- package/rules/match-document-filename.d.ts +1 -2
- 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)
|
@@ -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
|
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:
|
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.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'
|
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
|
-
|
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
|
-
|
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
|
-
|
3995
|
-
|
3996
|
-
|
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:
|
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.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'
|
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
|
-
|
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
|
-
|
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
|
-
|
3989
|
-
|
3990
|
-
|
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-
|
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"
|
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>;
|
@@ -1,6 +1,5 @@
|
|
1
|
-
import { CaseStyle
|
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
|
};
|