From ca29cdd11287c44480f1f06d8577f4f1ee1a5d96 Mon Sep 17 00:00:00 2001 From: Miles Bardon <33665681+Tohaker@users.noreply.github.com> Date: Mon, 23 Jan 2023 08:53:51 +0000 Subject: [PATCH] Marking breaking types as dangerous with the deprecated fields rule (#2240) * Marking breaking types as dangerous with the deprecated fields rule enabled * Adding example * Adding changeset * Adding unit tests for suppressRemovalOfDeprecatedFields rule --- .changeset/eighty-starfishes-refuse.md | 5 + integration_tests/2239/newSchema.graphql | 3 + integration_tests/2239/oldSchema.graphql | 8 ++ .../suppress-removal-of-deprecated-fields.ts | 113 ++++++++++++++++++ .../suppress-removal-of-deprecated-field.ts | 21 +++- 5 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 .changeset/eighty-starfishes-refuse.md create mode 100644 integration_tests/2239/newSchema.graphql create mode 100644 integration_tests/2239/oldSchema.graphql create mode 100644 packages/core/__tests__/diff/rules/suppress-removal-of-deprecated-fields.ts diff --git a/.changeset/eighty-starfishes-refuse.md b/.changeset/eighty-starfishes-refuse.md new file mode 100644 index 0000000000..af85dc8ad7 --- /dev/null +++ b/.changeset/eighty-starfishes-refuse.md @@ -0,0 +1,5 @@ +--- +'@graphql-inspector/core': minor +--- + +suppressRemovalOfDeprecatedField rule will now mark removed types as dangerous diff --git a/integration_tests/2239/newSchema.graphql b/integration_tests/2239/newSchema.graphql new file mode 100644 index 0000000000..1872386a8e --- /dev/null +++ b/integration_tests/2239/newSchema.graphql @@ -0,0 +1,3 @@ +type Query { + newQuery: Int! +} diff --git a/integration_tests/2239/oldSchema.graphql b/integration_tests/2239/oldSchema.graphql new file mode 100644 index 0000000000..254b54a4b0 --- /dev/null +++ b/integration_tests/2239/oldSchema.graphql @@ -0,0 +1,8 @@ +type Query { + oldQuery: OldType @deprecated(reason: "use newQuery") + newQuery: Int! +} + +type OldType { + field: String! +} diff --git a/packages/core/__tests__/diff/rules/suppress-removal-of-deprecated-fields.ts b/packages/core/__tests__/diff/rules/suppress-removal-of-deprecated-fields.ts new file mode 100644 index 0000000000..3f5224ea5c --- /dev/null +++ b/packages/core/__tests__/diff/rules/suppress-removal-of-deprecated-fields.ts @@ -0,0 +1,113 @@ +import { buildSchema } from 'graphql'; +import { suppressRemovalOfDeprecatedField } from '../../../src/diff/rules'; +import { CriticalityLevel, diff } from '../../../src/index'; +import { findFirstChangeByPath } from '../../../utils/testing'; + +describe('suppressRemovalOfDeprecatedFields rule', () => { + test('removed field on object', async () => { + const a = buildSchema(/* GraphQL */ ` + type Foo { + a: String! @deprecated(reason: "use b") + b: String! + } + `); + const b = buildSchema(/* GraphQL */ ` + type Foo { + b: String! + } + `); + + const changes = await diff(a, b, [suppressRemovalOfDeprecatedField]); + + const removed = findFirstChangeByPath(changes, 'Foo.a'); + + expect(removed.criticality.level).toBe(CriticalityLevel.Dangerous); + }); + + test('removed field on interface', async () => { + const a = buildSchema(/* GraphQL */ ` + interface Foo { + a: String! @deprecated(reason: "use b") + b: String! + } + `); + const b = buildSchema(/* GraphQL */ ` + interface Foo { + b: String! + } + `); + + const changes = await diff(a, b, [suppressRemovalOfDeprecatedField]); + + const removed = findFirstChangeByPath(changes, 'Foo.a'); + + expect(removed.criticality.level).toBe(CriticalityLevel.Dangerous); + }); + + test('removed enum', async () => { + const a = buildSchema(/* GraphQL */ ` + enum Foo { + a @deprecated(reason: "use b") + b + } + `); + const b = buildSchema(/* GraphQL */ ` + enum Foo { + b + } + `); + + const changes = await diff(a, b, [suppressRemovalOfDeprecatedField]); + + const removed = findFirstChangeByPath(changes, 'Foo.a'); + + expect(removed.criticality.level).toBe(CriticalityLevel.Dangerous); + }); + + test('removed input field', async () => { + const a = buildSchema(/* GraphQL */ ` + input Foo { + a: String! @deprecated(reason: "use b") + b: String! + } + `); + const b = buildSchema(/* GraphQL */ ` + input Foo { + b: String! + } + `); + + const changes = await diff(a, b, [suppressRemovalOfDeprecatedField]); + + const removed = findFirstChangeByPath(changes, 'Foo.a'); + + expect(removed.criticality.level).toBe(CriticalityLevel.Dangerous); + }); + + test('removed field with custom types', async () => { + const a = buildSchema(/* GraphQL */ ` + type Foo { + a: CustomType! @deprecated(reason: "use b") + b: String! + } + + type CustomType { + c: String! + d: String! + } + `); + const b = buildSchema(/* GraphQL */ ` + type Foo { + b: String! + } + `); + + const changes = await diff(a, b, [suppressRemovalOfDeprecatedField]); + + const removedField = findFirstChangeByPath(changes, 'Foo.a'); + const removedType = findFirstChangeByPath(changes, 'CustomType'); + + expect(removedField.criticality.level).toBe(CriticalityLevel.Dangerous); + expect(removedType.criticality.level).toBe(CriticalityLevel.Dangerous); + }); +}); diff --git a/packages/core/src/diff/rules/suppress-removal-of-deprecated-field.ts b/packages/core/src/diff/rules/suppress-removal-of-deprecated-field.ts index 44318422d9..de69df56a6 100644 --- a/packages/core/src/diff/rules/suppress-removal-of-deprecated-field.ts +++ b/packages/core/src/diff/rules/suppress-removal-of-deprecated-field.ts @@ -4,7 +4,7 @@ import { parsePath } from '../../utils/path'; import { ChangeType, CriticalityLevel } from './../changes/change'; import { Rule } from './types'; -export const suppressRemovalOfDeprecatedField: Rule = ({ changes, oldSchema }) => { +export const suppressRemovalOfDeprecatedField: Rule = ({ changes, oldSchema, newSchema }) => { return changes.map(change => { if ( change.type === ChangeType.FieldRemoved && @@ -75,6 +75,25 @@ export const suppressRemovalOfDeprecatedField: Rule = ({ changes, oldSchema }) = } } + if ( + change.type === ChangeType.TypeRemoved && + change.criticality.level === CriticalityLevel.Breaking && + change.path + ) { + const [typeName] = parsePath(change.path); + const type = newSchema.getType(typeName); + + if (!type) { + return { + ...change, + criticality: { + ...change.criticality, + level: CriticalityLevel.Dangerous, + }, + }; + } + } + return change; }); };