Skip to content

Commit 63dc00a

Browse files
committed
NEW RULE: avoid-typename-prefix
1 parent 61251e7 commit 63dc00a

File tree

4 files changed

+149
-0
lines changed

4 files changed

+149
-0
lines changed

.changeset/grumpy-tables-drum.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-eslint/eslint-plugin': minor
3+
---
4+
5+
NEW RULE: avoid-typename-prefix
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { FieldDefinitionNode } from 'graphql';
2+
import { GraphQLESTreeNode } from '../estree-parser';
3+
import { GraphQLESLintRule, GraphQLESlintRuleContext } from '../types';
4+
5+
const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
6+
7+
function checkNode(
8+
context: GraphQLESlintRuleContext<any>,
9+
typeName: string,
10+
fields: GraphQLESTreeNode<FieldDefinitionNode>[]
11+
) {
12+
const lowerTypeName = (typeName || '').toLowerCase();
13+
14+
for (const field of fields) {
15+
const fieldName = field.name.value || '';
16+
17+
if (fieldName && lowerTypeName && fieldName.toLowerCase().startsWith(lowerTypeName)) {
18+
context.report({
19+
node: field.name,
20+
data: {
21+
fieldName,
22+
typeName,
23+
},
24+
messageId: AVOID_TYPENAME_PREFIX,
25+
});
26+
}
27+
}
28+
}
29+
30+
const rule: GraphQLESLintRule = {
31+
meta: {
32+
type: 'suggestion',
33+
docs: {
34+
category: 'Best Practices',
35+
description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
36+
recommended: true,
37+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-typename-prefix.md',
38+
requiresSiblings: false,
39+
requiresSchema: false,
40+
examples: [
41+
{
42+
title: 'Incorrect',
43+
code: /* GraphQL */ `
44+
type User {
45+
userId: ID!
46+
}
47+
`,
48+
},
49+
{
50+
title: 'Correct',
51+
code: /* GraphQL */ `
52+
type User {
53+
id: ID!
54+
}
55+
`,
56+
},
57+
],
58+
},
59+
messages: {
60+
[AVOID_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
61+
},
62+
},
63+
create(context) {
64+
return {
65+
ObjectTypeDefinition(node) {
66+
checkNode(context, node.name.value, node.fields);
67+
},
68+
ObjectTypeExtension(node) {
69+
checkNode(context, node.name.value, node.fields);
70+
},
71+
InterfaceTypeDefinition(node) {
72+
checkNode(context, node.name.value, node.fields);
73+
},
74+
InterfaceTypeExtension(node) {
75+
checkNode(context, node.name.value, node.fields);
76+
},
77+
};
78+
},
79+
};
80+
81+
export default rule;

packages/plugin/src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ import noHashtagDescription from './no-hashtag-description';
1616
import selectionSetDepth from './selection-set-depth';
1717
import avoidDuplicateFields from './avoid-duplicate-fields';
1818
import strictIdInTypes from './strict-id-in-types';
19+
import avoidTypenamePrefix from './avoid-typename-prefix';
1920
import { GRAPHQL_JS_VALIDATIONS } from './graphql-js-validation';
2021

2122
export const rules = {
23+
'avoid-typename-prefix': avoidTypenamePrefix,
2224
'no-unreachable-types': noUnreachableTypes,
2325
'no-deprecated': noDeprecated,
2426
'unique-fragment-name': uniqueFragmentName,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { GraphQLRuleTester } from '../src/testkit';
2+
import rule from '../src/rules/avoid-typename-prefix';
3+
4+
const ruleTester = new GraphQLRuleTester();
5+
6+
ruleTester.runGraphQLTests('avoid-typename-prefix', rule, {
7+
valid: [
8+
{
9+
code: /* GraphQL */ `
10+
type User {
11+
id: ID!
12+
}
13+
`,
14+
},
15+
{
16+
code: /* GraphQL */ `
17+
interface Node {
18+
id: ID!
19+
}
20+
`,
21+
},
22+
{
23+
code: /* GraphQL */ `
24+
type User {
25+
# eslint-disable-next-line
26+
userId: ID!
27+
}
28+
`,
29+
},
30+
],
31+
invalid: [
32+
{
33+
code: /* GraphQL */ `
34+
type User {
35+
userId: ID!
36+
}
37+
`,
38+
errors: [{ message: 'Field "userId" starts with the name of the parent type "User"' }],
39+
},
40+
{
41+
code: /* GraphQL */ `
42+
type User {
43+
userId: ID!
44+
userName: String!
45+
}
46+
`,
47+
errors: [
48+
{ message: 'Field "userId" starts with the name of the parent type "User"' },
49+
{ message: 'Field "userName" starts with the name of the parent type "User"' },
50+
],
51+
},
52+
{
53+
code: /* GraphQL */ `
54+
interface Node {
55+
nodeId: ID!
56+
}
57+
`,
58+
errors: [{ message: 'Field "nodeId" starts with the name of the parent type "Node"' }],
59+
},
60+
],
61+
});

0 commit comments

Comments
 (0)