Skip to content

Commit

Permalink
check for deprecated arguments and object field nodes in graphql oper…
Browse files Browse the repository at this point in the history
…ations in `no-deprecated` rule (#2719)

* check for deprecated arguments and object field nodes in graphql operations in `no-deprecated` rule

* check for deprecated arguments and object field nodes in graphql operations in `no-deprecated` rule

* fix lint

* upd snapshots
  • Loading branch information
dimaMachina authored Nov 19, 2024
1 parent 219506a commit 57d6edf
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 41 deletions.
5 changes: 5 additions & 0 deletions .changeset/yellow-elephants-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-eslint/eslint-plugin': minor
---

check for deprecated arguments and object field nodes in graphql operations in `no-deprecated` rule
45 changes: 38 additions & 7 deletions packages/plugin/src/rules/no-deprecated/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ import { ParserOptionsForTests, ruleTester } from '../../../__tests__/test-utils
import { rule } from './index.js';

const TEST_SCHEMA = /* GraphQL */ `
input TestInput {
a: Int @deprecated(reason: "Use 'b' instead.")
b: Boolean
}
type Query {
oldField: String @deprecated
oldFieldWithReason: String @deprecated(reason: "test")
newField: String!
testArgument(a: Int @deprecated(reason: "Use 'b' instead."), b: Boolean): Boolean
testObjectField(input: TestInput): Boolean
}
type Mutation {
Expand All @@ -29,7 +36,7 @@ const WITH_SCHEMA = {

ruleTester.run('no-deprecated', rule, {
valid: [
{ ...WITH_SCHEMA, code: 'query { newField }' },
{ ...WITH_SCHEMA, code: '{ newField }' },
{ ...WITH_SCHEMA, code: 'mutation { something(t: NEW) }' },
],
invalid: [
Expand All @@ -39,7 +46,7 @@ ruleTester.run('no-deprecated', rule, {
errors: [
{
message:
'This enum value is marked as deprecated in your GraphQL schema (reason: No longer supported)',
'Enum "OLD" is marked as deprecated in your GraphQL schema (reason: No longer supported)',
},
],
},
Expand All @@ -48,25 +55,49 @@ ruleTester.run('no-deprecated', rule, {
code: 'mutation { something(t: OLD_WITH_REASON) }',
errors: [
{
message: 'This enum value is marked as deprecated in your GraphQL schema (reason: test)',
message:
'Enum "OLD_WITH_REASON" is marked as deprecated in your GraphQL schema (reason: test)',
},
],
},
{
...WITH_SCHEMA,
code: '{ oldField }',
errors: [
{
message:
'Field "oldField" is marked as deprecated in your GraphQL schema (reason: No longer supported)',
},
],
},
{
...WITH_SCHEMA,
code: '{ oldFieldWithReason }',
errors: [
{
message:
'Field "oldFieldWithReason" is marked as deprecated in your GraphQL schema (reason: test)',
},
],
},
{
...WITH_SCHEMA,
code: 'query { oldField }',
code: '{ testArgument(a: 2) }',
errors: [
{
message:
'This field is marked as deprecated in your GraphQL schema (reason: No longer supported)',
'Argument "a" is marked as deprecated in your GraphQL schema (reason: Use \'b\' instead.)',
},
],
},
{
...WITH_SCHEMA,
code: 'query { oldFieldWithReason }',
code: '{ testObjectField(input: { a: 2 }) }',
errors: [
{ message: 'This field is marked as deprecated in your GraphQL schema (reason: test)' },
{
message:
'Object field "a" is marked as deprecated in your GraphQL schema (reason: Use \'b\' instead.)',
},
],
},
],
Expand Down
35 changes: 26 additions & 9 deletions packages/plugin/src/rules/no-deprecated/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EnumValueNode, FieldNode, Kind } from 'graphql';
import { ArgumentNode, EnumValueNode, FieldNode, ObjectFieldNode } from 'graphql';
import { GraphQLESTreeNode } from '../../estree-converter/index.js';
import { GraphQLESLintRule } from '../../types.js';
import { requireGraphQLSchemaFromContext } from '../../utils.js';
import { displayNodeName, requireGraphQLSchemaFromContext } from '../../utils.js';

const RULE_ID = 'no-deprecated';

Expand Down Expand Up @@ -79,30 +79,28 @@ export const rule: GraphQLESLintRule<[], true> = {
recommended: true,
},
messages: {
[RULE_ID]:
'This {{ type }} is marked as deprecated in your GraphQL schema (reason: {{ reason }})',
[RULE_ID]: '{{ type }} is marked as deprecated in your GraphQL schema (reason: {{ reason }})',
},
schema: [],
},
create(context) {
requireGraphQLSchemaFromContext(RULE_ID, context);

function report(
node: GraphQLESTreeNode<EnumValueNode | FieldNode, true>,
node: GraphQLESTreeNode<EnumValueNode | FieldNode | ArgumentNode | ObjectFieldNode, true>,
reason: string,
): void {
const nodeName = node.kind === Kind.ENUM ? node.value : node.name.value;
const nodeType = node.kind === Kind.ENUM ? 'enum value' : 'field';
const nodeType = displayNodeName(node);
context.report({
node,
messageId: RULE_ID,
data: {
type: nodeType,
type: nodeType[0].toUpperCase() + nodeType.slice(1),
reason,
},
suggest: [
{
desc: `Remove \`${nodeName}\` ${nodeType}`,
desc: `Remove ${nodeType}`,
fix: fixer => fixer.remove(node as any),
},
],
Expand All @@ -126,6 +124,25 @@ export const rule: GraphQLESLintRule<[], true> = {
report(node, reason);
}
},
Argument(node) {
const typeInfo = node.typeInfo();
const reason = typeInfo.argument?.deprecationReason;
if (reason) {
report(node, reason);
}
},
ObjectValue(node) {
const typeInfo = node.typeInfo();
// @ts-expect-error -- fixme
const fields = typeInfo.inputType!.getFields();
for (const field of node.fields) {
const fieldName = field.name.value;
const reason = fields[fieldName].deprecationReason;
if (reason) {
report(field, reason);
}
}
},
};
},
};
58 changes: 44 additions & 14 deletions packages/plugin/src/rules/no-deprecated/snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ exports[`no-deprecated > invalid > Invalid #1 1`] = `
#### ❌ Error

> 1 | mutation { something(t: OLD) }
| ^^^ This enum value is marked as deprecated in your GraphQL schema (reason: No longer supported)
| ^^^ Enum "OLD" is marked as deprecated in your GraphQL schema (reason: No longer supported)

#### 💡 Suggestion: Remove \`OLD\` enum value
#### 💡 Suggestion: Remove enum "OLD"

1 | mutation { something(t: ) }
`;
Expand All @@ -23,39 +23,69 @@ exports[`no-deprecated > invalid > Invalid #2 1`] = `
#### ❌ Error

> 1 | mutation { something(t: OLD_WITH_REASON) }
| ^^^^^^^^^^^^^^^ This enum value is marked as deprecated in your GraphQL schema (reason: test)
| ^^^^^^^^^^^^^^^ Enum "OLD_WITH_REASON" is marked as deprecated in your GraphQL schema (reason: test)

#### 💡 Suggestion: Remove \`OLD_WITH_REASON\` enum value
#### 💡 Suggestion: Remove enum "OLD_WITH_REASON"

1 | mutation { something(t: ) }
`;

exports[`no-deprecated > invalid > Invalid #3 1`] = `
#### ⌨️ Code

1 | query { oldField }
1 | { oldField }

#### ❌ Error

> 1 | query { oldField }
| ^^^^^^^^ This field is marked as deprecated in your GraphQL schema (reason: No longer supported)
> 1 | { oldField }
| ^^^^^^^^ Field "oldField" is marked as deprecated in your GraphQL schema (reason: No longer supported)

#### 💡 Suggestion: Remove \`oldField\` field
#### 💡 Suggestion: Remove field "oldField"

1 | query { }
1 | { }
`;

exports[`no-deprecated > invalid > Invalid #4 1`] = `
#### ⌨️ Code

1 | query { oldFieldWithReason }
1 | { oldFieldWithReason }

#### ❌ Error

> 1 | query { oldFieldWithReason }
| ^^^^^^^^^^^^^^^^^^ This field is marked as deprecated in your GraphQL schema (reason: test)
> 1 | { oldFieldWithReason }
| ^^^^^^^^^^^^^^^^^^ Field "oldFieldWithReason" is marked as deprecated in your GraphQL schema (reason: test)

#### 💡 Suggestion: Remove \`oldFieldWithReason\` field
#### 💡 Suggestion: Remove field "oldFieldWithReason"

1 | query { }
1 | { }
`;

exports[`no-deprecated > invalid > Invalid #5 1`] = `
#### ⌨️ Code

1 | { testArgument(a: 2) }

#### ❌ Error

> 1 | { testArgument(a: 2) }
| ^^^ Argument "a" is marked as deprecated in your GraphQL schema (reason: Use 'b' instead.)

#### 💡 Suggestion: Remove argument "a"

1 | { testArgument() }
`;

exports[`no-deprecated > invalid > Invalid #6 1`] = `
#### ⌨️ Code

1 | { testObjectField(input: { a: 2 }) }

#### ❌ Error

> 1 | { testObjectField(input: { a: 2 }) }
| ^^^ Object field "a" is marked as deprecated in your GraphQL schema (reason: Use 'b' instead.)

#### 💡 Suggestion: Remove object field "a"

1 | { testObjectField(input: { }) }
`;
5 changes: 3 additions & 2 deletions packages/plugin/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,11 @@ const DisplayNodeNameMap: Record<Kind, string> = {
[Kind.VARIABLE]: 'variable',
} as const;

export function displayNodeName(node: GraphQLESTreeNode<ASTNode>): string {
export function displayNodeName(node: GraphQLESTreeNode<ASTNode, boolean>): string {
return `${
node.kind === Kind.OPERATION_DEFINITION ? node.operation : DisplayNodeNameMap[node.kind]
} "${('alias' in node && node.alias?.value) || ('name' in node && node.name?.value)}"`;
// @ts-expect-error -- fixme
} "${('alias' in node && node.alias?.value) || ('name' in node && node.name?.value) || node.value}"`;
}

export function getNodeName(node: GraphQLESTreeNode<ASTNode>): string {
Expand Down
28 changes: 19 additions & 9 deletions website/src/pages/docs/usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,45 +60,55 @@ export default [
<Cards.Card
icon={<GraphQLIcon />}
title="Usage with `.graphql` files"
href="/usage/graphql"
href="/docs/usage/graphql"
arrow
/>
<Cards.Card
icon={<JSIcon />}
title="Usage with code files `.js/.jsx`"
href="/usage/js"
href="/docs/usage/js"
arrow
/>
<Cards.Card
icon={<HalfIcon />}
title="Usage to lint both schema/documents"
href="/usage/schema-and-documents"
href="/docs/usage/schema-and-documents"
arrow
/>
<Cards.Card
icon={<StackIcon />}
title="Usage to lint different schemas"
href="/usage/multiple-projects"
href="/docs/usage/multiple-projects"
arrow
/>
<Cards.Card
icon={<GearIcon />}
title="Programmatic usage"
href="/docs/usage/programmatic"
arrow
/>
<Cards.Card icon={<GearIcon />} title="Programmatic usage" href="/usage/programmatic" arrow />
</Cards>

### Advanced

<Cards num={2}>
<Cards.Card icon={<SvelteIcon />} title="Usage with `.svelte` files" href="/usage/svelte" arrow />
<Cards.Card icon={<VueIcon />} title="Usage with `.vue` files" href="/usage/vue" arrow />
<Cards.Card
icon={<SvelteIcon />}
title="Usage with `.svelte` files"
href="/docs/usage/svelte"
arrow
/>
<Cards.Card icon={<VueIcon />} title="Usage with `.vue` files" href="/docs/usage/vue" arrow />
<Cards.Card
icon={<AstroIcon className="dark:fill-white" />}
title="Usage with `.astro` files"
href="/usage/astro"
href="/docs/usage/astro"
arrow
/>
<Cards.Card
icon={<PrettierIcon />}
title="Usage with `eslint-plugin-prettier`"
href="/usage/prettier"
href="/docs/usage/prettier"
arrow
/>
</Cards>

0 comments on commit 57d6edf

Please sign in to comment.