Skip to content

Commit ca29cdd

Browse files
authored
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
1 parent 7eb5e8d commit ca29cdd

File tree

5 files changed

+149
-1
lines changed

5 files changed

+149
-1
lines changed

Diff for: .changeset/eighty-starfishes-refuse.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-inspector/core': minor
3+
---
4+
5+
suppressRemovalOfDeprecatedField rule will now mark removed types as dangerous

Diff for: integration_tests/2239/newSchema.graphql

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
type Query {
2+
newQuery: Int!
3+
}

Diff for: integration_tests/2239/oldSchema.graphql

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
type Query {
2+
oldQuery: OldType @deprecated(reason: "use newQuery")
3+
newQuery: Int!
4+
}
5+
6+
type OldType {
7+
field: String!
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { buildSchema } from 'graphql';
2+
import { suppressRemovalOfDeprecatedField } from '../../../src/diff/rules';
3+
import { CriticalityLevel, diff } from '../../../src/index';
4+
import { findFirstChangeByPath } from '../../../utils/testing';
5+
6+
describe('suppressRemovalOfDeprecatedFields rule', () => {
7+
test('removed field on object', async () => {
8+
const a = buildSchema(/* GraphQL */ `
9+
type Foo {
10+
a: String! @deprecated(reason: "use b")
11+
b: String!
12+
}
13+
`);
14+
const b = buildSchema(/* GraphQL */ `
15+
type Foo {
16+
b: String!
17+
}
18+
`);
19+
20+
const changes = await diff(a, b, [suppressRemovalOfDeprecatedField]);
21+
22+
const removed = findFirstChangeByPath(changes, 'Foo.a');
23+
24+
expect(removed.criticality.level).toBe(CriticalityLevel.Dangerous);
25+
});
26+
27+
test('removed field on interface', async () => {
28+
const a = buildSchema(/* GraphQL */ `
29+
interface Foo {
30+
a: String! @deprecated(reason: "use b")
31+
b: String!
32+
}
33+
`);
34+
const b = buildSchema(/* GraphQL */ `
35+
interface Foo {
36+
b: String!
37+
}
38+
`);
39+
40+
const changes = await diff(a, b, [suppressRemovalOfDeprecatedField]);
41+
42+
const removed = findFirstChangeByPath(changes, 'Foo.a');
43+
44+
expect(removed.criticality.level).toBe(CriticalityLevel.Dangerous);
45+
});
46+
47+
test('removed enum', async () => {
48+
const a = buildSchema(/* GraphQL */ `
49+
enum Foo {
50+
a @deprecated(reason: "use b")
51+
b
52+
}
53+
`);
54+
const b = buildSchema(/* GraphQL */ `
55+
enum Foo {
56+
b
57+
}
58+
`);
59+
60+
const changes = await diff(a, b, [suppressRemovalOfDeprecatedField]);
61+
62+
const removed = findFirstChangeByPath(changes, 'Foo.a');
63+
64+
expect(removed.criticality.level).toBe(CriticalityLevel.Dangerous);
65+
});
66+
67+
test('removed input field', async () => {
68+
const a = buildSchema(/* GraphQL */ `
69+
input Foo {
70+
a: String! @deprecated(reason: "use b")
71+
b: String!
72+
}
73+
`);
74+
const b = buildSchema(/* GraphQL */ `
75+
input Foo {
76+
b: String!
77+
}
78+
`);
79+
80+
const changes = await diff(a, b, [suppressRemovalOfDeprecatedField]);
81+
82+
const removed = findFirstChangeByPath(changes, 'Foo.a');
83+
84+
expect(removed.criticality.level).toBe(CriticalityLevel.Dangerous);
85+
});
86+
87+
test('removed field with custom types', async () => {
88+
const a = buildSchema(/* GraphQL */ `
89+
type Foo {
90+
a: CustomType! @deprecated(reason: "use b")
91+
b: String!
92+
}
93+
94+
type CustomType {
95+
c: String!
96+
d: String!
97+
}
98+
`);
99+
const b = buildSchema(/* GraphQL */ `
100+
type Foo {
101+
b: String!
102+
}
103+
`);
104+
105+
const changes = await diff(a, b, [suppressRemovalOfDeprecatedField]);
106+
107+
const removedField = findFirstChangeByPath(changes, 'Foo.a');
108+
const removedType = findFirstChangeByPath(changes, 'CustomType');
109+
110+
expect(removedField.criticality.level).toBe(CriticalityLevel.Dangerous);
111+
expect(removedType.criticality.level).toBe(CriticalityLevel.Dangerous);
112+
});
113+
});

Diff for: packages/core/src/diff/rules/suppress-removal-of-deprecated-field.ts

+20-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { parsePath } from '../../utils/path';
44
import { ChangeType, CriticalityLevel } from './../changes/change';
55
import { Rule } from './types';
66

7-
export const suppressRemovalOfDeprecatedField: Rule = ({ changes, oldSchema }) => {
7+
export const suppressRemovalOfDeprecatedField: Rule = ({ changes, oldSchema, newSchema }) => {
88
return changes.map(change => {
99
if (
1010
change.type === ChangeType.FieldRemoved &&
@@ -75,6 +75,25 @@ export const suppressRemovalOfDeprecatedField: Rule = ({ changes, oldSchema }) =
7575
}
7676
}
7777

78+
if (
79+
change.type === ChangeType.TypeRemoved &&
80+
change.criticality.level === CriticalityLevel.Breaking &&
81+
change.path
82+
) {
83+
const [typeName] = parsePath(change.path);
84+
const type = newSchema.getType(typeName);
85+
86+
if (!type) {
87+
return {
88+
...change,
89+
criticality: {
90+
...change.criticality,
91+
level: CriticalityLevel.Dangerous,
92+
},
93+
};
94+
}
95+
}
96+
7897
return change;
7998
});
8099
};

0 commit comments

Comments
 (0)