Skip to content

Commit

Permalink
NEW RULE: avoid-duplicate-fields (#245)
Browse files Browse the repository at this point in the history
* NEW RULE: avoid-duplicate-fields

* added more tests
  • Loading branch information
dotansimha committed Dec 23, 2020
1 parent 4942b58 commit 5e1bbe6
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/clean-apricots-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-eslint/eslint-plugin': minor
---

NEW RULE: avoid-duplicate-fields
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
- [`require-description`](./rules/require-description.md)
- [`require-id-when-available`](./rules/require-id-when-available.md)
- [`description-style`](./rules/description-style.md)
- [`prettier`](./rules/prettier.md)
- [`avoid-duplicate-fields`](./rules/avoid-duplicate-fields.md)
- [`naming-convention`](./rules/naming-convention.md)
- [`input-name`](./rules/input-name.md)
- [`prettier`](./rules/prettier.md)
- [`executable-definitions`](./rules/executable-definitions.md)
- [`fields-on-correct-type`](./rules/fields-on-correct-type.md)
- [`fragments-on-composite-type`](./rules/fragments-on-composite-type.md)
Expand Down
54 changes: 54 additions & 0 deletions docs/rules/avoid-duplicate-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# `avoid-duplicate-fields`

- Category: `Stylistic Issues`
- Rule name: `@graphql-eslint/avoid-duplicate-fields`
- Requires GraphQL Schema: `false` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
- Requires GraphQL Operations: `false` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)

Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.

## Usage Examples

### Incorrect

```graphql
# eslint @graphql-eslint/avoid-duplicate-fields: ["error"]

query getUserDetails {
user {
name # first
email
name # second
}
}
```

### Incorrect

```graphql
# eslint @graphql-eslint/avoid-duplicate-fields: ["error"]

query getUsers {
users(
first: 100
skip: 50
after: "cji629tngfgou0b73kt7vi5jo"
first: 100 # duplicate argument
) {
id
}
}
```

### Incorrect

```graphql
# eslint @graphql-eslint/avoid-duplicate-fields: ["error"]

query getUsers($first: Int!, $first: Int!) {
# Duplicate variable
users(first: 100, skip: 50, after: "cji629tngfgou0b73kt7vi5jo") {
id
}
}
```
133 changes: 133 additions & 0 deletions packages/plugin/src/rules/avoid-duplicate-fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { Kind } from 'graphql';
import { GraphQLESLintRule } from '../types';

const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';

const ensureUnique = () => {
const set = new Set<string>();

return {
add: (item: string, onError: () => void) => {
if (set.has(item)) {
onError();
} else {
set.add(item);
}
},
};
};

const rule: GraphQLESLintRule<[], false> = {
meta: {
type: 'suggestion',
docs: {
requiresSchema: false,
requiresSiblings: false,
description:
'Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.',
category: 'Stylistic Issues',
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
examples: [
{
title: 'Incorrect',
code: /* GraphQL */ `
query getUserDetails {
user {
name # first
email
name # second
}
}
`,
},
{
title: 'Incorrect',
code: /* GraphQL */ `
query getUsers {
users(
first: 100
skip: 50
after: "cji629tngfgou0b73kt7vi5jo"
first: 100 # duplicate argument
) {
id
}
}
`,
},
{
title: 'Incorrect',
code: /* GraphQL */ `
query getUsers($first: Int!, $first: Int!) {
# Duplicate variable
users(first: 100, skip: 50, after: "cji629tngfgou0b73kt7vi5jo") {
id
}
}
`,
},
],
},
messages: {
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times.`,
},
},
create(context) {
return {
OperationDefinition(node) {
const uniqueCheck = ensureUnique();

for (const arg of node.variableDefinitions || []) {
uniqueCheck.add(arg.variable.name.value, () => {
context.report({
messageId: AVOID_DUPLICATE_FIELDS,
data: {
type: 'Operation variable',
fieldName: arg.variable.name.value,
},
node: arg,
});
});
}
},
Field(node) {
const uniqueCheck = ensureUnique();

for (const arg of node.arguments || []) {
uniqueCheck.add(arg.name.value, () => {
context.report({
messageId: AVOID_DUPLICATE_FIELDS,
data: {
type: 'Field argument',
fieldName: arg.name.value,
},
node: arg,
});
});
}
},
SelectionSet(node) {
const uniqueCheck = ensureUnique();

for (const selection of node.selections || []) {
if (selection.kind === Kind.FIELD) {
const nameToCheck = selection.alias?.value || selection.name.value;

uniqueCheck.add(nameToCheck, () => {
context.report({
messageId: AVOID_DUPLICATE_FIELDS,
data: {
type: 'Field',
fieldName: nameToCheck,
},
node: selection,
});
});
}
}
},
};
},
};

export default rule;
4 changes: 3 additions & 1 deletion packages/plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import uniqueOperationName from './unique-operation-name';
import noDeprecated from './no-deprecated';
import noHashtagDescription from './no-hashtag-description';
import selectionSetDepth from './selection-set-depth';
import avoidDuplicateFields from './avoid-duplicate-fields';
import { GraphQLESLintRule } from '../types';
import { GRAPHQL_JS_VALIDATIONS } from './graphql-js-validation';

Expand All @@ -33,8 +34,9 @@ export const rules: Record<string, GraphQLESLintRule> = {
'require-description': requireDescription,
'require-id-when-available': requireIdWhenAvailable,
'description-style': descriptionStyle,
prettier: prettier,
'avoid-duplicate-fields': avoidDuplicateFields,
'naming-convention': namingConvention,
'input-name': inputName,
prettier,
...GRAPHQL_JS_VALIDATIONS,
};
54 changes: 54 additions & 0 deletions packages/plugin/tests/avoid-duplicate-fields.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { GraphQLRuleTester } from '../src/testkit';
import rule from '../src/rules/avoid-duplicate-fields';

const ruleTester = new GraphQLRuleTester();

ruleTester.runGraphQLTests('avoid-duplicate-fields', rule, {
valid: [],
invalid: [
{
code: /* GraphQL */ `
query test($v: String, $t: String, $v: String) {
id
}
`,
errors: [{ message: 'Operation variable "v" defined multiple times.' }],
},
{
code: /* GraphQL */ `
query test {
users(first: 100, after: 10, filter: "test", first: 50) {
id
}
}
`,
errors: [{ message: 'Field argument "first" defined multiple times.' }],
},
{
code: /* GraphQL */ `
query test {
users {
id
name
email
name
}
}
`,
errors: [{ message: 'Field "name" defined multiple times.' }],
},
{
code: /* GraphQL */ `
query test {
users {
id
name
email
email: somethingElse
}
}
`,
errors: [{ message: 'Field "email" defined multiple times.' }],
},
],
});

0 comments on commit 5e1bbe6

Please sign in to comment.