Skip to content

Commit 7653e4c

Browse files
committed
migrate all missing rules and it's tests
1 parent 648cd89 commit 7653e4c

20 files changed

+2166
-63
lines changed

packages/plugin/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"prepack": "bob prepack"
1717
},
1818
"dependencies": {
19+
"prettier-linter-helpers": "1.0.0",
1920
"@graphql-tools/utils": "^6.2.4",
2021
"@graphql-tools/load": "^6.2.4",
2122
"@graphql-tools/graphql-file-loader": "^6.2.4",

packages/plugin/src/rules/avoid-operation-name-prefix.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { GraphQLESLintRule, GraphQLESlintRuleContext } from '../types';
22
import { GraphQLESTreeNode } from '../estree-parser/estree-ast';
33
import { OperationDefinitionNode, FragmentDefinitionNode } from 'graphql';
44

5-
type AvoidOperationNamePrefixConfig = {
6-
keywords: string[];
7-
caseSensitive?: boolean;
8-
};
5+
type AvoidOperationNamePrefixConfig = [
6+
{
7+
keywords: string[];
8+
caseSensitive?: boolean;
9+
}
10+
];
911

1012
const AVOID_OPERATION_NAME_PREFIX = 'AVOID_OPERATION_NAME_PREFIX';
1113

packages/plugin/src/rules/description-style.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { GraphQLESLintRule } from '../types';
22

3-
type DescriptionStyleRuleConfig = {
3+
type DescriptionStyleRuleConfig = [{
44
style: 'inline' | 'block';
5-
};
5+
}];
66

77
const rule: GraphQLESLintRule<DescriptionStyleRuleConfig> = {
88
meta: {

packages/plugin/src/rules/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import noCaseInsensitiveEnumValuesDuplicates from './no-case-insensitive-enum-va
77
import requireDescription from './require-description';
88
import requireIdWhenAvailable from './require-id-when-available';
99
import descriptionStyle from './description-style';
10+
import prettier from './prettier';
11+
import namingConvention from './naming-convention';
12+
import inputName from './input-name';
13+
import { GraphQLESLintRule } from '../types';
1014

11-
export const rules = {
15+
export const rules: Record<string, GraphQLESLintRule> = {
1216
'validate-against-schema': validate,
1317
'no-anonymous-operations': noAnonymousOperations,
1418
'no-operation-name-suffix': noOperationNameSuffix,
@@ -17,5 +21,8 @@ export const rules = {
1721
'no-case-insensitive-enum-values-duplicates': noCaseInsensitiveEnumValuesDuplicates,
1822
'require-description': requireDescription,
1923
'require-id-when-available': requireIdWhenAvailable,
20-
'description-style': descriptionStyle
24+
'description-style': descriptionStyle,
25+
prettier: prettier,
26+
'naming-convention': namingConvention,
27+
'input-name': inputName,
2128
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { GraphQLESLintRule } from '../types';
2+
3+
type InputNameRuleConfig = [
4+
{
5+
checkInputType?: boolean;
6+
}
7+
];
8+
9+
const rule: GraphQLESLintRule<InputNameRuleConfig> = {
10+
meta: {
11+
type: 'suggestion',
12+
docs: {
13+
description:
14+
'require mutation argument to be always called "input" and input type to be called Mutation name + "Input"',
15+
category: 'Stylistic Issues',
16+
recommended: false,
17+
url: 'https://github.com/ilyavolodin/graphql-eslint/blob/master/docs/rules/input-name.md',
18+
},
19+
schema: [
20+
{
21+
type: 'object',
22+
properties: {
23+
checkInputType: {
24+
type: 'boolean',
25+
default: 'true',
26+
},
27+
},
28+
additionalProperties: false,
29+
},
30+
],
31+
},
32+
create(context) {
33+
const listeners = {
34+
'FieldDefinition > InputValueDefinition': node => {
35+
if (node.name.value !== 'input') {
36+
context.report({
37+
node: node.name,
38+
message: `Input "${node.name.value}" should be called "input"`,
39+
});
40+
}
41+
},
42+
};
43+
if (context.options && context.options[0] && context.options[0].checkInputType) {
44+
listeners['FieldDefinition > InputValueDefinition NamedType'] = node => {
45+
const findInputType = item => {
46+
let currentNode = item;
47+
while (currentNode.type !== 'InputValueDefinition') {
48+
currentNode = currentNode.parent;
49+
}
50+
return currentNode;
51+
};
52+
53+
const mutationName = `${findInputType(node).parent.name.value}Input`;
54+
55+
if (node.name.value !== mutationName) {
56+
context.report({ node, message: `InputType "${node.name.value}" name should be "${mutationName}"` });
57+
}
58+
};
59+
}
60+
61+
return listeners;
62+
},
63+
};
64+
65+
export default rule;
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { Kind } from 'graphql';
2+
import { GraphQLESLintRule } from '../types';
3+
4+
const formats = {
5+
camelCase: /^[a-z][^_]*$/g,
6+
PascalCase: /^[A-Z][^_]*$/g,
7+
snake_case: /^[a-z_]*$/g,
8+
UPPER_CASE: /^[A-Z_]*$/g,
9+
};
10+
11+
function checkNameFormat(value, style, leadingUnderscore, trailingUnderscore) {
12+
let name = value;
13+
if (leadingUnderscore === 'allow') {
14+
[, name] = name.match(/^_*(.*)$/);
15+
}
16+
if (trailingUnderscore === 'allow') {
17+
name = name.replace(/_*$/, '');
18+
}
19+
return new RegExp(formats[style]).test(name);
20+
}
21+
22+
const schemaOption = {
23+
type: 'string',
24+
enum: ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE'],
25+
};
26+
27+
type ValidNaming = 'camelCase' | 'PascalCase' | 'snake_case' | 'UPPER_CASE';
28+
29+
type NamingConventionRuleConfig = [
30+
{
31+
leadingUnderscore?: 'allow' | 'forbid';
32+
trailingUnderscore?: 'allow' | 'forbid';
33+
[Kind.FIELD_DEFINITION]?: ValidNaming;
34+
[Kind.ENUM_VALUE_DEFINITION]?: ValidNaming;
35+
[Kind.INPUT_VALUE_DEFINITION]?: ValidNaming;
36+
[Kind.OBJECT_TYPE_DEFINITION]?: ValidNaming;
37+
[Kind.INTERFACE_TYPE_DEFINITION]?: ValidNaming;
38+
[Kind.ENUM_TYPE_DEFINITION]?: ValidNaming;
39+
[Kind.UNION_TYPE_DEFINITION]?: ValidNaming;
40+
[Kind.SCALAR_TYPE_DEFINITION]?: ValidNaming;
41+
[Kind.OPERATION_DEFINITION]?: ValidNaming;
42+
[Kind.FRAGMENT_DEFINITION]?: ValidNaming;
43+
[Kind.INPUT_OBJECT_TYPE_DEFINITION]?: ValidNaming;
44+
}
45+
];
46+
47+
const rule: GraphQLESLintRule<NamingConventionRuleConfig> = {
48+
meta: {
49+
type: 'suggestion',
50+
docs: {
51+
description: 'requires description around GraphQL nodes',
52+
category: 'Best practices',
53+
recommended: true,
54+
url: 'https://github.com/ilyavolodin/graphql-eslint/blob/master/docs/rules/naming-convention.md',
55+
},
56+
schema: [
57+
{
58+
type: 'object',
59+
properties: {
60+
[Kind.FIELD_DEFINITION]: schemaOption,
61+
[Kind.INPUT_OBJECT_TYPE_DEFINITION]: schemaOption,
62+
[Kind.ENUM_VALUE_DEFINITION]: schemaOption,
63+
[Kind.INPUT_VALUE_DEFINITION]: schemaOption,
64+
[Kind.OBJECT_TYPE_DEFINITION]: schemaOption,
65+
[Kind.INTERFACE_TYPE_DEFINITION]: schemaOption,
66+
[Kind.ENUM_TYPE_DEFINITION]: schemaOption,
67+
[Kind.UNION_TYPE_DEFINITION]: schemaOption,
68+
[Kind.SCALAR_TYPE_DEFINITION]: schemaOption,
69+
[Kind.OPERATION_DEFINITION]: schemaOption,
70+
[Kind.FRAGMENT_DEFINITION]: schemaOption,
71+
leadingUnderscore: {
72+
type: 'string',
73+
enum: ['allow', 'forbid'],
74+
default: 'forbid',
75+
},
76+
trailingUnderscore: {
77+
type: 'string',
78+
enum: ['allow', 'forbid'],
79+
default: 'forbid',
80+
},
81+
},
82+
},
83+
],
84+
},
85+
create(context) {
86+
const options: NamingConventionRuleConfig[number] = {
87+
leadingUnderscore: 'forbid',
88+
trailingUnderscore: 'forbid',
89+
...(context.options[0] || {}),
90+
};
91+
92+
const checkNode = (node, style, nodeType) => {
93+
if (!checkNameFormat(node.value, style, options.leadingUnderscore, options.trailingUnderscore)) {
94+
context.report({
95+
node,
96+
message: '{{nodeType}} name "{{nodeName}}" should be in {{format}} format',
97+
data: {
98+
format: style,
99+
nodeType,
100+
nodeName: node.value,
101+
},
102+
});
103+
}
104+
};
105+
106+
return {
107+
Name: node => {
108+
if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
109+
context.report({
110+
node,
111+
message: 'Leading underscores are not allowed',
112+
});
113+
}
114+
if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
115+
context.report({ node, message: 'Trailing underscores are not allowed' });
116+
}
117+
},
118+
ObjectTypeDefinition: node => {
119+
if (options.ObjectTypeDefinition) {
120+
checkNode(node.name, options.ObjectTypeDefinition, 'Type');
121+
}
122+
},
123+
InterfaceTypeDefinition: node => {
124+
if (options.InterfaceTypeDefinition) {
125+
checkNode(node.name, options.InterfaceTypeDefinition, 'Interface');
126+
}
127+
},
128+
EnumTypeDefinition: node => {
129+
if (options.EnumTypeDefinition) {
130+
checkNode(node.name, options.EnumTypeDefinition, 'Enumerator');
131+
}
132+
},
133+
InputObjectTypeDefinition: node => {
134+
if (options.InputObjectTypeDefinition) {
135+
checkNode(node.name, options.InputObjectTypeDefinition, 'Input type');
136+
}
137+
},
138+
FieldDefinition: node => {
139+
if (options.FieldDefinition) {
140+
checkNode(node.name, options.FieldDefinition, 'Field');
141+
}
142+
},
143+
EnumValueDefinition: node => {
144+
if (options.EnumValueDefinition) {
145+
checkNode(node.name, options.EnumValueDefinition, 'Enumeration value');
146+
}
147+
},
148+
InputValueDefinition: node => {
149+
if (options.InputValueDefinition) {
150+
checkNode(node.name, options.InputValueDefinition, 'Input property');
151+
}
152+
},
153+
FragmentDefinition: node => {
154+
if (options.FragmentDefinition) {
155+
checkNode(node.name, options.FragmentDefinition, 'Fragment');
156+
}
157+
},
158+
ScalarTypeDefinition: node => {
159+
if (options.ScalarTypeDefinition) {
160+
checkNode(node.name, options.ScalarTypeDefinition, 'Scalar');
161+
}
162+
},
163+
UnionTypeDefinition: node => {
164+
if (options.UnionTypeDefinition) {
165+
checkNode(node.name, options.UnionTypeDefinition, 'Scalar');
166+
}
167+
},
168+
};
169+
},
170+
};
171+
172+
export default rule;

0 commit comments

Comments
 (0)