Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
Loading