diff --git a/.changeset/yellow-elephants-study.md b/.changeset/yellow-elephants-study.md new file mode 100644 index 00000000000..a643f822922 --- /dev/null +++ b/.changeset/yellow-elephants-study.md @@ -0,0 +1,5 @@ +--- +'@graphql-eslint/eslint-plugin': minor +--- + +check for deprecated arguments and object field nodes in graphql operations in `no-deprecated` rule diff --git a/packages/plugin/src/rules/no-deprecated/index.test.ts b/packages/plugin/src/rules/no-deprecated/index.test.ts index f6ef7609327..dd74ec36d0c 100644 --- a/packages/plugin/src/rules/no-deprecated/index.test.ts +++ b/packages/plugin/src/rules/no-deprecated/index.test.ts @@ -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 { @@ -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: [ @@ -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)', }, ], }, @@ -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.)', + }, ], }, ], diff --git a/packages/plugin/src/rules/no-deprecated/index.ts b/packages/plugin/src/rules/no-deprecated/index.ts index 94023d8250a..503952a672d 100644 --- a/packages/plugin/src/rules/no-deprecated/index.ts +++ b/packages/plugin/src/rules/no-deprecated/index.ts @@ -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'; @@ -79,8 +79,7 @@ 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: [], }, @@ -88,21 +87,20 @@ export const rule: GraphQLESLintRule<[], true> = { requireGraphQLSchemaFromContext(RULE_ID, context); function report( - node: GraphQLESTreeNode, + node: GraphQLESTreeNode, 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), }, ], @@ -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); + } + } + }, }; }, }; diff --git a/packages/plugin/src/rules/no-deprecated/snapshot.md b/packages/plugin/src/rules/no-deprecated/snapshot.md index 41564657f1a..d1853daa689 100644 --- a/packages/plugin/src/rules/no-deprecated/snapshot.md +++ b/packages/plugin/src/rules/no-deprecated/snapshot.md @@ -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: ) } `; @@ -23,9 +23,9 @@ 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: ) } `; @@ -33,29 +33,59 @@ exports[`no-deprecated > invalid > Invalid #2 1`] = ` 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: { }) } `; diff --git a/packages/plugin/src/utils.ts b/packages/plugin/src/utils.ts index 927febba2ad..ec7c21d7f47 100644 --- a/packages/plugin/src/utils.ts +++ b/packages/plugin/src/utils.ts @@ -173,10 +173,11 @@ const DisplayNodeNameMap: Record = { [Kind.VARIABLE]: 'variable', } as const; -export function displayNodeName(node: GraphQLESTreeNode): string { +export function displayNodeName(node: GraphQLESTreeNode): 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): string { diff --git a/website/src/pages/docs/usage.mdx b/website/src/pages/docs/usage.mdx index 413192b5151..d922a50b479 100644 --- a/website/src/pages/docs/usage.mdx +++ b/website/src/pages/docs/usage.mdx @@ -60,45 +60,55 @@ export default [ } title="Usage with `.graphql` files" - href="/usage/graphql" + href="/docs/usage/graphql" arrow /> } title="Usage with code files `.js/.jsx`" - href="/usage/js" + href="/docs/usage/js" arrow /> } title="Usage to lint both schema/documents" - href="/usage/schema-and-documents" + href="/docs/usage/schema-and-documents" arrow /> } title="Usage to lint different schemas" - href="/usage/multiple-projects" + href="/docs/usage/multiple-projects" + arrow + /> + } + title="Programmatic usage" + href="/docs/usage/programmatic" arrow /> - } title="Programmatic usage" href="/usage/programmatic" arrow /> ### Advanced - } title="Usage with `.svelte` files" href="/usage/svelte" arrow /> - } title="Usage with `.vue` files" href="/usage/vue" arrow /> + } + title="Usage with `.svelte` files" + href="/docs/usage/svelte" + arrow + /> + } title="Usage with `.vue` files" href="/docs/usage/vue" arrow /> } title="Usage with `.astro` files" - href="/usage/astro" + href="/docs/usage/astro" arrow /> } title="Usage with `eslint-plugin-prettier`" - href="/usage/prettier" + href="/docs/usage/prettier" arrow />