Skip to content

Commit 0f7afa5

Browse files
authored
add new rule require-type-pattern-with-oneof (#1331)
1 parent bab45cc commit 0f7afa5

File tree

9 files changed

+205
-3
lines changed

9 files changed

+205
-3
lines changed

.changeset/fair-donkeys-heal.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+
add new rule `require-type-pattern-with-oneof`

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Name            &nbs
5959
[require-field-of-type-query-in-mutation-result](rules/require-field-of-type-query-in-mutation-result.md)|Allow the client in one round-trip not only to call mutation but also to get a wagon of data to update their application.|![all][]|📄|🚀|
6060
[require-id-when-available](rules/require-id-when-available.md)|Enforce selecting specific fields when they are available on the GraphQL type.|![recommended][]|📦|🚀|💡
6161
[require-nullable-fields-with-oneof](rules/require-nullable-fields-with-oneof.md)|Require are `input` or `type` fields be non nullable with `@oneOf` directive.|![all][]|📄|🚀|
62+
[require-type-pattern-with-oneof](rules/require-type-pattern-with-oneof.md)|Enforce types with `@oneOf` directive have `error` and `ok` fields.|![all][]|📄|🚀|
6263
[scalar-leafs](rules/scalar-leafs.md)|A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.|![recommended][]|📦|🔮|💡
6364
[selection-set-depth](rules/selection-set-depth.md)|Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://npmjs.com/package/graphql-depth-limit).|![recommended][]|📦|🚀|💡
6465
[strict-id-in-types](rules/strict-id-in-types.md)|Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.|![recommended][]|📄|🚀|
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# `require-type-pattern-with-oneof`
2+
3+
- Category: `Schema`
4+
- Rule name: `@graphql-eslint/require-type-pattern-with-oneof`
5+
- Requires GraphQL Schema: `false` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
6+
- Requires GraphQL Operations: `false`
7+
[ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)
8+
9+
Enforce types with `@oneOf` directive have `error` and `ok` fields.
10+
11+
## Usage Examples
12+
13+
### Correct
14+
15+
```graphql
16+
# eslint @graphql-eslint/require-type-pattern-with-oneof: 'error'
17+
18+
type Mutation {
19+
doSomething: DoSomethingMutationResult!
20+
}
21+
22+
interface Error {
23+
message: String!
24+
}
25+
26+
type DoSomethingMutationResult @oneOf {
27+
ok: DoSomethingSuccess
28+
error: Error
29+
}
30+
31+
type DoSomethingSuccess {
32+
# ...
33+
}
34+
```
35+
36+
## Resources
37+
38+
- [Rule source](../../packages/plugin/src/rules/require-type-pattern-with-oneof.ts)
39+
- [Test source](../../packages/plugin/tests/require-type-pattern-with-oneof.spec.ts)

packages/plugin/src/configs/schema-all.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ export default {
1818
'@graphql-eslint/require-deprecation-date': 'error',
1919
'@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
2020
'@graphql-eslint/require-nullable-fields-with-oneof': 'error',
21+
'@graphql-eslint/require-type-pattern-with-oneof': 'error',
2122
},
2223
};

packages/plugin/src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { rule as requireDescription } from './require-description';
2929
import { rule as requireFieldOfTypeQueryInMutationResult } from './require-field-of-type-query-in-mutation-result';
3030
import { rule as requireIdWhenAvailable } from './require-id-when-available';
3131
import { rule as requireNullableFieldsWithOneof } from './require-nullable-fields-with-oneof';
32+
import { rule as requireTypePatternWithOneof } from './require-type-pattern-with-oneof';
3233
import { rule as selectionSetDepth } from './selection-set-depth';
3334
import { rule as strictIdInTypes } from './strict-id-in-types';
3435
import { rule as uniqueFragmentName } from './unique-fragment-name';
@@ -62,6 +63,7 @@ export const rules = {
6263
'require-field-of-type-query-in-mutation-result': requireFieldOfTypeQueryInMutationResult,
6364
'require-id-when-available': requireIdWhenAvailable,
6465
'require-nullable-fields-with-oneof': requireNullableFieldsWithOneof,
66+
'require-type-pattern-with-oneof': requireTypePatternWithOneof,
6567
'selection-set-depth': selectionSetDepth,
6668
'strict-id-in-types': strictIdInTypes,
6769
'unique-fragment-name': uniqueFragmentName,

packages/plugin/src/rules/require-nullable-fields-with-oneof.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,19 @@ export const rule: GraphQLESLintRule = {
3939
},
4040
create(context) {
4141
return {
42-
'Directive[name.value=oneOf]'(node: {
42+
'Directive[name.value=oneOf]'({
43+
parent,
44+
}: {
4345
parent: GraphQLESTreeNode<InputObjectTypeDefinitionNode | ObjectTypeDefinitionNode>;
4446
}) {
4547
const isTypeOrInput = [
4648
Kind.OBJECT_TYPE_DEFINITION,
4749
Kind.INPUT_OBJECT_TYPE_DEFINITION,
48-
].includes(node.parent.kind);
50+
].includes(parent.kind);
4951
if (!isTypeOrInput) {
5052
return;
5153
}
52-
for (const field of node.parent.fields) {
54+
for (const field of parent.fields) {
5355
if (field.gqlType.kind === Kind.NON_NULL_TYPE) {
5456
context.report({
5557
node: field.name,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { GraphQLESLintRule } from '../types';
2+
import { GraphQLESTreeNode } from '../estree-converter';
3+
import { ObjectTypeDefinitionNode } from 'graphql/index';
4+
5+
const RULE_ID = 'require-type-pattern-with-oneof';
6+
7+
export const rule: GraphQLESLintRule = {
8+
meta: {
9+
type: 'suggestion',
10+
docs: {
11+
category: 'Schema',
12+
description: 'Enforce types with `@oneOf` directive have `error` and `ok` fields.',
13+
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
14+
examples: [
15+
{
16+
title: 'Correct',
17+
code: /* GraphQL */ `
18+
type Mutation {
19+
doSomething: DoSomethingMutationResult!
20+
}
21+
22+
interface Error {
23+
message: String!
24+
}
25+
26+
type DoSomethingMutationResult @oneOf {
27+
ok: DoSomethingSuccess
28+
error: Error
29+
}
30+
31+
type DoSomethingSuccess {
32+
# ...
33+
}
34+
`,
35+
},
36+
],
37+
},
38+
messages: {
39+
[RULE_ID]: 'Type `{{typeName}}` should have `{{fieldName}}` field.',
40+
},
41+
schema: [],
42+
},
43+
create(context) {
44+
return {
45+
'Directive[name.value=oneOf][parent.kind=ObjectTypeDefinition]'({
46+
parent,
47+
}: {
48+
parent: GraphQLESTreeNode<ObjectTypeDefinitionNode>;
49+
}) {
50+
const requiredFields = ['error', 'ok'];
51+
for (const fieldName of requiredFields) {
52+
if (!parent.fields.some(field => field.name.value === fieldName)) {
53+
context.report({
54+
node: parent.name,
55+
messageId: RULE_ID,
56+
data: {
57+
typeName: parent.name.value,
58+
fieldName,
59+
},
60+
});
61+
}
62+
}
63+
},
64+
};
65+
},
66+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Vitest Snapshot v1
2+
3+
exports[`should validate \`error\` field 1`] = `
4+
#### ⌨️ Code
5+
6+
1 | type T @oneOf {
7+
2 | ok: Ok
8+
3 | err: Error
9+
4 | }
10+
11+
#### ❌ Error
12+
13+
> 1 | type T @oneOf {
14+
| ^ Type \`T\` should have \`error\` field.
15+
2 | ok: Ok
16+
`;
17+
18+
exports[`should validate \`ok\` field 1`] = `
19+
#### ⌨️ Code
20+
21+
1 | type T @oneOf {
22+
2 | notok: Ok
23+
3 | error: Error
24+
4 | }
25+
26+
#### ❌ Error
27+
28+
> 1 | type T @oneOf {
29+
| ^ Type \`T\` should have \`ok\` field.
30+
2 | notok: Ok
31+
`;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { GraphQLRuleTester } from '../src';
2+
import { rule } from '../src/rules/require-type-pattern-with-oneof';
3+
4+
const ruleTester = new GraphQLRuleTester();
5+
6+
ruleTester.runGraphQLTests('require-type-pattern-with-oneof', rule, {
7+
valid: [
8+
/* GraphQL */ `
9+
type T @oneOf {
10+
ok: Ok
11+
error: Error
12+
}
13+
`,
14+
{
15+
name: 'should ignore types without `@oneOf` directive',
16+
code: /* GraphQL */ `
17+
type T {
18+
notok: Ok
19+
err: Error
20+
}
21+
`,
22+
},
23+
{
24+
name: 'should validate only `type` with `@oneOf` directive',
25+
code: /* GraphQL */ `
26+
input I {
27+
notok: Ok
28+
err: Error
29+
}
30+
`,
31+
},
32+
],
33+
invalid: [
34+
{
35+
name: 'should validate `ok` field',
36+
code: /* GraphQL */ `
37+
type T @oneOf {
38+
notok: Ok
39+
error: Error
40+
}
41+
`,
42+
errors: [{ message: 'Type `T` should have `ok` field.' }],
43+
},
44+
{
45+
name: 'should validate `error` field',
46+
code: /* GraphQL */ `
47+
type T @oneOf {
48+
ok: Ok
49+
err: Error
50+
}
51+
`,
52+
errors: [{ message: 'Type `T` should have `error` field.' }],
53+
},
54+
],
55+
});

0 commit comments

Comments
 (0)