-
-
Notifications
You must be signed in to change notification settings - Fork 100
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add new rule
no-one-place-fragments
(#1334)
* add new rule `no-one-place-fragments` * flip incorrect/correct examples * add snapshots
- Loading branch information
1 parent
0f7afa5
commit abcfc14
Showing
9 changed files
with
212 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@graphql-eslint/eslint-plugin': minor | ||
--- | ||
|
||
add new rule `no-one-place-fragments` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# `no-one-place-fragments` | ||
|
||
- Category: `Operations` | ||
- Rule name: `@graphql-eslint/no-one-place-fragments` | ||
- Requires GraphQL Schema: `false` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema) | ||
- Requires GraphQL Operations: `true` | ||
[ℹ️](../../README.md#extended-linting-rules-with-siblings-operations) | ||
|
||
Disallow fragments that are used only in one place. | ||
|
||
## Usage Examples | ||
|
||
### Incorrect | ||
|
||
```graphql | ||
# eslint @graphql-eslint/no-one-place-fragments: 'error' | ||
|
||
fragment UserFields on User { | ||
id | ||
} | ||
|
||
{ | ||
user { | ||
...UserFields | ||
friends { | ||
...UserFields | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Correct | ||
|
||
```graphql | ||
# eslint @graphql-eslint/no-one-place-fragments: 'error' | ||
|
||
fragment UserFields on User { | ||
id | ||
} | ||
|
||
{ | ||
user { | ||
...UserFields | ||
} | ||
} | ||
``` | ||
|
||
## Resources | ||
|
||
- [Rule source](../../packages/plugin/src/rules/no-one-place-fragments.ts) | ||
- [Test source](../../packages/plugin/tests/no-one-place-fragments.spec.ts) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { GraphQLESLintRule } from '../types'; | ||
import { requireSiblingsOperations } from '@graphql-eslint/eslint-plugin'; | ||
import { CWD } from '../utils'; | ||
import { relative } from 'path'; | ||
import { GraphQLESTreeNode } from '../estree-converter'; | ||
import { NameNode, visit } from 'graphql'; | ||
|
||
const RULE_ID = 'no-one-place-fragments'; | ||
|
||
export const rule: GraphQLESLintRule = { | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
category: 'Operations', | ||
description: 'Disallow fragments that are used only in one place.', | ||
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`, | ||
examples: [ | ||
{ | ||
title: 'Incorrect', | ||
code: /* GraphQL */ ` | ||
fragment UserFields on User { | ||
id | ||
} | ||
{ | ||
user { | ||
...UserFields | ||
friends { | ||
...UserFields | ||
} | ||
} | ||
} | ||
`, | ||
}, | ||
{ | ||
title: 'Correct', | ||
code: /* GraphQL */ ` | ||
fragment UserFields on User { | ||
id | ||
} | ||
{ | ||
user { | ||
...UserFields | ||
} | ||
} | ||
`, | ||
}, | ||
], | ||
requiresSiblings: true, | ||
}, | ||
messages: { | ||
[RULE_ID]: 'Fragment `{{fragmentName}}` used only once. Inline him in "{{filePath}}".', | ||
}, | ||
schema: [], | ||
}, | ||
create(context) { | ||
const operations = requireSiblingsOperations(RULE_ID, context); | ||
const allDocuments = [...operations.getOperations(), ...operations.getFragments()]; | ||
|
||
const usedFragmentsMap: Record<string, string[]> = Object.create(null); | ||
|
||
for (const { document, filePath } of allDocuments) { | ||
const relativeFilePath = relative(CWD, filePath); | ||
visit(document, { | ||
FragmentSpread({ name }) { | ||
const spreadName = name.value; | ||
usedFragmentsMap[spreadName] ||= []; | ||
usedFragmentsMap[spreadName].push(relativeFilePath); | ||
}, | ||
}); | ||
} | ||
|
||
return { | ||
'FragmentDefinition > Name'(node: GraphQLESTreeNode<NameNode>) { | ||
const fragmentName = node.value; | ||
const fragmentUsage = usedFragmentsMap[fragmentName]; | ||
|
||
if (fragmentUsage.length === 1) { | ||
context.report({ | ||
node, | ||
messageId: RULE_ID, | ||
data: { fragmentName, filePath: fragmentUsage[0] }, | ||
}); | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
16 changes: 16 additions & 0 deletions
16
packages/plugin/tests/__snapshots__/no-one-place-fragments.spec.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Vitest Snapshot v1 | ||
|
||
exports[`should error fragment used in one place 1`] = ` | ||
#### ⌨️ Code | ||
|
||
1 | fragment UserFields on User { | ||
2 | id | ||
3 | firstName | ||
4 | } | ||
|
||
#### ❌ Error | ||
|
||
> 1 | fragment UserFields on User { | ||
| ^^^^^^^^^^ Fragment \`UserFields\` used only once. Inline him in "-877628611.graphql". | ||
2 | id | ||
`; |
12 changes: 12 additions & 0 deletions
12
packages/plugin/tests/mocks/no-one-place-fragments.graphql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
fragment UserFields on User { | ||
id | ||
} | ||
|
||
{ | ||
user { | ||
...UserFields | ||
friends { | ||
...UserFields | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { join } from 'path'; | ||
import { GraphQLRuleTester } from '../src'; | ||
import { rule } from '../src/rules/no-one-place-fragments'; | ||
|
||
const ruleTester = new GraphQLRuleTester(); | ||
|
||
ruleTester.runGraphQLTests('no-one-place-fragments', rule, { | ||
valid: [ | ||
{ | ||
name: 'ok when spread 2 times', | ||
code: ruleTester.fromMockFile('no-one-place-fragments.graphql'), | ||
parserOptions: { | ||
operations: join(__dirname, 'mocks/no-one-place-fragments.graphql'), | ||
}, | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
name: 'should error fragment used in one place', | ||
code: ruleTester.fromMockFile('user-fields.graphql'), | ||
errors: [ | ||
{ message: 'Fragment `UserFields` used only once. Inline him in "-877628611.graphql".' }, | ||
], | ||
parserOptions: { | ||
operations: /* GraphQL */ ` | ||
{ | ||
user { | ||
...UserFields | ||
} | ||
} | ||
`, | ||
}, | ||
}, | ||
], | ||
}); |