Skip to content

Commit

Permalink
add new rule require-type-pattern-with-oneof (#1331)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimaMachina committed Dec 22, 2022
1 parent bab45cc commit 0f7afa5
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-donkeys-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-eslint/eslint-plugin': minor
---

add new rule `require-type-pattern-with-oneof`
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Name            &nbs
[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][]|📄|🚀|
[require-id-when-available](rules/require-id-when-available.md)|Enforce selecting specific fields when they are available on the GraphQL type.|![recommended][]|📦|🚀|💡
[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][]|📄|🚀|
[require-type-pattern-with-oneof](rules/require-type-pattern-with-oneof.md)|Enforce types with `@oneOf` directive have `error` and `ok` fields.|![all][]|📄|🚀|
[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][]|📦|🔮|💡
[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][]|📦|🚀|💡
[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][]|📄|🚀|
Expand Down
39 changes: 39 additions & 0 deletions docs/rules/require-type-pattern-with-oneof.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# `require-type-pattern-with-oneof`

- Category: `Schema`
- Rule name: `@graphql-eslint/require-type-pattern-with-oneof`
- Requires GraphQL Schema: `false` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
- Requires GraphQL Operations: `false`
[ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)

Enforce types with `@oneOf` directive have `error` and `ok` fields.

## Usage Examples

### Correct

```graphql
# eslint @graphql-eslint/require-type-pattern-with-oneof: 'error'

type Mutation {
doSomething: DoSomethingMutationResult!
}

interface Error {
message: String!
}

type DoSomethingMutationResult @oneOf {
ok: DoSomethingSuccess
error: Error
}

type DoSomethingSuccess {
# ...
}
```

## Resources

- [Rule source](../../packages/plugin/src/rules/require-type-pattern-with-oneof.ts)
- [Test source](../../packages/plugin/tests/require-type-pattern-with-oneof.spec.ts)
1 change: 1 addition & 0 deletions packages/plugin/src/configs/schema-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export default {
'@graphql-eslint/require-deprecation-date': 'error',
'@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
'@graphql-eslint/require-nullable-fields-with-oneof': 'error',
'@graphql-eslint/require-type-pattern-with-oneof': 'error',
},
};
2 changes: 2 additions & 0 deletions packages/plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { rule as requireDescription } from './require-description';
import { rule as requireFieldOfTypeQueryInMutationResult } from './require-field-of-type-query-in-mutation-result';
import { rule as requireIdWhenAvailable } from './require-id-when-available';
import { rule as requireNullableFieldsWithOneof } from './require-nullable-fields-with-oneof';
import { rule as requireTypePatternWithOneof } from './require-type-pattern-with-oneof';
import { rule as selectionSetDepth } from './selection-set-depth';
import { rule as strictIdInTypes } from './strict-id-in-types';
import { rule as uniqueFragmentName } from './unique-fragment-name';
Expand Down Expand Up @@ -62,6 +63,7 @@ export const rules = {
'require-field-of-type-query-in-mutation-result': requireFieldOfTypeQueryInMutationResult,
'require-id-when-available': requireIdWhenAvailable,
'require-nullable-fields-with-oneof': requireNullableFieldsWithOneof,
'require-type-pattern-with-oneof': requireTypePatternWithOneof,
'selection-set-depth': selectionSetDepth,
'strict-id-in-types': strictIdInTypes,
'unique-fragment-name': uniqueFragmentName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,19 @@ export const rule: GraphQLESLintRule = {
},
create(context) {
return {
'Directive[name.value=oneOf]'(node: {
'Directive[name.value=oneOf]'({
parent,
}: {
parent: GraphQLESTreeNode<InputObjectTypeDefinitionNode | ObjectTypeDefinitionNode>;
}) {
const isTypeOrInput = [
Kind.OBJECT_TYPE_DEFINITION,
Kind.INPUT_OBJECT_TYPE_DEFINITION,
].includes(node.parent.kind);
].includes(parent.kind);
if (!isTypeOrInput) {
return;
}
for (const field of node.parent.fields) {
for (const field of parent.fields) {
if (field.gqlType.kind === Kind.NON_NULL_TYPE) {
context.report({
node: field.name,
Expand Down
66 changes: 66 additions & 0 deletions packages/plugin/src/rules/require-type-pattern-with-oneof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { GraphQLESLintRule } from '../types';
import { GraphQLESTreeNode } from '../estree-converter';
import { ObjectTypeDefinitionNode } from 'graphql/index';

const RULE_ID = 'require-type-pattern-with-oneof';

export const rule: GraphQLESLintRule = {
meta: {
type: 'suggestion',
docs: {
category: 'Schema',
description: 'Enforce types with `@oneOf` directive have `error` and `ok` fields.',
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
examples: [
{
title: 'Correct',
code: /* GraphQL */ `
type Mutation {
doSomething: DoSomethingMutationResult!
}
interface Error {
message: String!
}
type DoSomethingMutationResult @oneOf {
ok: DoSomethingSuccess
error: Error
}
type DoSomethingSuccess {
# ...
}
`,
},
],
},
messages: {
[RULE_ID]: 'Type `{{typeName}}` should have `{{fieldName}}` field.',
},
schema: [],
},
create(context) {
return {
'Directive[name.value=oneOf][parent.kind=ObjectTypeDefinition]'({
parent,
}: {
parent: GraphQLESTreeNode<ObjectTypeDefinitionNode>;
}) {
const requiredFields = ['error', 'ok'];
for (const fieldName of requiredFields) {
if (!parent.fields.some(field => field.name.value === fieldName)) {
context.report({
node: parent.name,
messageId: RULE_ID,
data: {
typeName: parent.name.value,
fieldName,
},
});
}
}
},
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Vitest Snapshot v1

exports[`should validate \`error\` field 1`] = `
#### ⌨️ Code

1 | type T @oneOf {
2 | ok: Ok
3 | err: Error
4 | }

#### ❌ Error

> 1 | type T @oneOf {
| ^ Type \`T\` should have \`error\` field.
2 | ok: Ok
`;

exports[`should validate \`ok\` field 1`] = `
#### ⌨️ Code

1 | type T @oneOf {
2 | notok: Ok
3 | error: Error
4 | }

#### ❌ Error

> 1 | type T @oneOf {
| ^ Type \`T\` should have \`ok\` field.
2 | notok: Ok
`;
55 changes: 55 additions & 0 deletions packages/plugin/tests/require-type-pattern-with-oneof.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { GraphQLRuleTester } from '../src';
import { rule } from '../src/rules/require-type-pattern-with-oneof';

const ruleTester = new GraphQLRuleTester();

ruleTester.runGraphQLTests('require-type-pattern-with-oneof', rule, {
valid: [
/* GraphQL */ `
type T @oneOf {
ok: Ok
error: Error
}
`,
{
name: 'should ignore types without `@oneOf` directive',
code: /* GraphQL */ `
type T {
notok: Ok
err: Error
}
`,
},
{
name: 'should validate only `type` with `@oneOf` directive',
code: /* GraphQL */ `
input I {
notok: Ok
err: Error
}
`,
},
],
invalid: [
{
name: 'should validate `ok` field',
code: /* GraphQL */ `
type T @oneOf {
notok: Ok
error: Error
}
`,
errors: [{ message: 'Type `T` should have `ok` field.' }],
},
{
name: 'should validate `error` field',
code: /* GraphQL */ `
type T @oneOf {
ok: Ok
err: Error
}
`,
errors: [{ message: 'Type `T` should have `error` field.' }],
},
],
});

0 comments on commit 0f7afa5

Please sign in to comment.