Skip to content

Commit 893520f

Browse files
committed
feat(core): add useDoubleQuotes option for enum change messages
1 parent 8341829 commit 893520f

File tree

8 files changed

+288
-28
lines changed

8 files changed

+288
-28
lines changed

packages/core/__tests__/diff/enum.test.ts

Lines changed: 192 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,4 +274,195 @@ describe('enum', () => {
274274
expect(change.criticality.reason).toBeDefined();
275275
expect(change.message).toEqual(`Enum value 'C' was added to enum 'enumA'`);
276276
});
277-
});
277+
278+
describe('useDoubleQuotes option', () => {
279+
test('value added with double quotes', async () => {
280+
const a = buildSchema(/* GraphQL */ `
281+
type Query {
282+
fieldA: String
283+
}
284+
285+
enum enumA {
286+
A
287+
B
288+
}
289+
`);
290+
291+
const b = buildSchema(/* GraphQL */ `
292+
type Query {
293+
fieldA: String
294+
}
295+
296+
enum enumA {
297+
A
298+
B
299+
C
300+
}
301+
`);
302+
303+
const changes = await diff(a, b, [], { useDoubleQuotes: true });
304+
const change = findFirstChangeByPath(changes, 'enumA.C');
305+
306+
expect(changes.length).toEqual(1);
307+
expect(change.criticality.level).toEqual(CriticalityLevel.Dangerous);
308+
expect(change.message).toEqual(`Enum value "C" was added to enum "enumA"`);
309+
});
310+
311+
test('value removed with double quotes', async () => {
312+
const a = buildSchema(/* GraphQL */ `
313+
type Query {
314+
fieldA: String
315+
}
316+
317+
enum enumA {
318+
A
319+
B
320+
}
321+
`);
322+
323+
const b = buildSchema(/* GraphQL */ `
324+
type Query {
325+
fieldA: String
326+
}
327+
328+
enum enumA {
329+
A
330+
}
331+
`);
332+
333+
const changes = await diff(a, b, [], { useDoubleQuotes: true });
334+
const change = findFirstChangeByPath(changes, 'enumA.B');
335+
336+
expect(changes.length).toEqual(1);
337+
expect(change.criticality.level).toEqual(CriticalityLevel.Breaking);
338+
expect(change.message).toEqual(`Enum value "B" was removed from enum "enumA"`);
339+
});
340+
341+
test('deprecation reason changed with double quotes', async () => {
342+
const a = buildSchema(/* GraphQL */ `
343+
type Query {
344+
fieldA: String
345+
}
346+
347+
enum enumA {
348+
A @deprecated(reason: "Old Reason")
349+
B
350+
}
351+
`);
352+
353+
const b = buildSchema(/* GraphQL */ `
354+
type Query {
355+
fieldA: String
356+
}
357+
358+
enum enumA {
359+
A @deprecated(reason: "New Reason")
360+
B
361+
}
362+
`);
363+
364+
const changes = await diff(a, b, [], { useDoubleQuotes: true });
365+
const change = findFirstChangeByPath(changes, 'enumA.A');
366+
367+
expect(changes.length).toEqual(1);
368+
expect(change.criticality.level).toEqual(CriticalityLevel.NonBreaking);
369+
expect(change.message).toEqual(
370+
`Enum value "enumA.A" deprecation reason changed from "Old Reason" to "New Reason"`,
371+
);
372+
});
373+
374+
test('deprecation reason added with double quotes', async () => {
375+
const a = buildSchema(/* GraphQL */ `
376+
type Query {
377+
fieldA: String
378+
}
379+
380+
enum enumA {
381+
A
382+
B
383+
}
384+
`);
385+
386+
const b = buildSchema(/* GraphQL */ `
387+
type Query {
388+
fieldA: String
389+
}
390+
391+
enum enumA {
392+
A @deprecated(reason: "New Reason")
393+
B
394+
}
395+
`);
396+
397+
const changes = await diff(a, b, [], { useDoubleQuotes: true });
398+
const change = findFirstChangeByPath(changes, 'enumA.A');
399+
400+
expect(changes.length).toEqual(2);
401+
expect(change.criticality.level).toEqual(CriticalityLevel.NonBreaking);
402+
expect(change.message).toEqual(
403+
`Enum value "enumA.A" was deprecated with reason "New Reason"`,
404+
);
405+
});
406+
407+
test('deprecation reason removed with double quotes', async () => {
408+
const a = buildSchema(/* GraphQL */ `
409+
type Query {
410+
fieldA: String
411+
}
412+
413+
enum enumA {
414+
A @deprecated(reason: "New Reason")
415+
B
416+
}
417+
`);
418+
419+
const b = buildSchema(/* GraphQL */ `
420+
type Query {
421+
fieldA: String
422+
}
423+
424+
enum enumA {
425+
A
426+
B
427+
}
428+
`);
429+
430+
const changes = await diff(a, b, [], { useDoubleQuotes: true });
431+
const change = findFirstChangeByPath(changes, 'enumA.A');
432+
433+
expect(changes.length).toEqual(2);
434+
expect(change.criticality.level).toEqual(CriticalityLevel.NonBreaking);
435+
expect(change.message).toEqual(`Deprecation reason was removed from enum value "enumA.A"`);
436+
});
437+
438+
test('removal of a deprecated field with double quotes', async () => {
439+
const a = buildSchema(/* GraphQL */ `
440+
type Query {
441+
fieldA: String
442+
}
443+
444+
enum enumA {
445+
A @deprecated(reason: "New Reason")
446+
B
447+
}
448+
`);
449+
450+
const b = buildSchema(/* GraphQL */ `
451+
type Query {
452+
fieldA: String
453+
}
454+
455+
enum enumA {
456+
B
457+
}
458+
`);
459+
460+
const changes = await diff(a, b, [], { useDoubleQuotes: true });
461+
const change = findFirstChangeByPath(changes, 'enumA.A');
462+
463+
expect(changes.length).toEqual(1);
464+
expect(change.criticality.level).toEqual(CriticalityLevel.Breaking);
465+
expect(change.message).toEqual(`Enum value "A" (deprecated) was removed from enum "enumA"`);
466+
});
467+
});
468+
});

packages/core/__tests__/utils/string.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { safeString } from '../../src/utils/string.js';
1+
import { fmt, safeString } from '../../src/utils/string.js';
22

33
test('scalars', () => {
44
expect(safeString(0)).toBe('0');
@@ -33,3 +33,32 @@ test('array', () => {
3333
'[ { foo: 42 } ]',
3434
);
3535
});
36+
37+
describe('fmt', () => {
38+
test('uses single quotes by default', () => {
39+
expect(fmt('test')).toBe("'test'");
40+
expect(fmt('enumA.B')).toBe("'enumA.B'");
41+
});
42+
43+
test('uses single quotes when config is undefined', () => {
44+
expect(fmt('test', undefined)).toBe("'test'");
45+
expect(fmt('enumA.B', undefined)).toBe("'enumA.B'");
46+
});
47+
48+
test('uses single quotes when useDoubleQuotes is false', () => {
49+
expect(fmt('test', { useDoubleQuotes: false })).toBe("'test'");
50+
expect(fmt('enumA.B', { useDoubleQuotes: false })).toBe("'enumA.B'");
51+
});
52+
53+
test('uses double quotes when useDoubleQuotes is true', () => {
54+
expect(fmt('test', { useDoubleQuotes: true })).toBe('"test"');
55+
expect(fmt('enumA.B', { useDoubleQuotes: true })).toBe('"enumA.B"');
56+
});
57+
58+
test('formats various string types correctly', () => {
59+
expect(fmt('', { useDoubleQuotes: true })).toBe('""');
60+
expect(fmt('', { useDoubleQuotes: false })).toBe("''");
61+
expect(fmt('Old Reason', { useDoubleQuotes: true })).toBe('"Old Reason"');
62+
expect(fmt('Old Reason', { useDoubleQuotes: false })).toBe("'Old Reason'");
63+
});
64+
});

packages/core/src/diff/changes/enum.ts

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import {
1111
EnumValueDescriptionChangedChange,
1212
EnumValueRemovedChange,
1313
} from './change.js';
14+
import { ConsiderUsageConfig } from '../rules/consider-usage.js';
15+
import { fmt } from '../../utils/string.js';
1416

1517
function buildEnumValueRemovedMessage(args: EnumValueRemovedChange['meta']) {
16-
return `Enum value '${args.removedEnumValueName}' ${
17-
args.isEnumValueDeprecated ? '(deprecated) ' : ''
18-
}was removed from enum '${args.enumName}'`;
18+
return `Enum value '${args.removedEnumValueName}' ${args.isEnumValueDeprecated ? '(deprecated) ' : ''}was removed from enum '${args.enumName}'`;
1919
}
2020

2121
const enumValueRemovedCriticalityBreakingReason = `Removing an enum value will cause existing queries that use this enum value to error.`;
@@ -79,25 +79,24 @@ export function enumValueAdded(
7979
});
8080
}
8181

82-
function buildEnumValueDescriptionChangedMessage(args: EnumValueDescriptionChangedChange['meta']) {
82+
function buildEnumValueDescriptionChangedMessage(args: EnumValueDescriptionChangedChange['meta'], config?: ConsiderUsageConfig) {
83+
const oldDesc = fmt(args.oldEnumValueDescription ?? 'undefined', config);
84+
const newDesc = fmt(args.newEnumValueDescription ?? 'undefined', config);
8385
return args.oldEnumValueDescription === null
84-
? `Description '${args.newEnumValueDescription ?? 'undefined'}' was added to enum value '${
85-
args.enumName
86-
}.${args.enumValueName}'`
87-
: `Description for enum value '${args.enumName}.${args.enumValueName}' changed from '${
88-
args.oldEnumValueDescription ?? 'undefined'
89-
}' to '${args.newEnumValueDescription ?? 'undefined'}'`;
86+
? `Description '${newDesc}' was added to enum value '${args.enumName}.${args.enumValueName}'`
87+
: `Description for enum value '${args.enumName}.${args.enumValueName}' changed from '${oldDesc}' to '${newDesc}'`;
9088
}
9189

9290
export function enumValueDescriptionChangedFromMeta(
9391
args: EnumValueDescriptionChangedChange,
92+
config?: ConsiderUsageConfig
9493
): Change<typeof ChangeType.EnumValueDescriptionChanged> {
9594
return {
9695
criticality: {
9796
level: CriticalityLevel.NonBreaking,
9897
},
9998
type: ChangeType.EnumValueDescriptionChanged,
100-
message: buildEnumValueDescriptionChangedMessage(args.meta),
99+
message: buildEnumValueDescriptionChangedMessage(args.meta, config),
101100
path: [args.meta.enumName, args.meta.enumValueName].join('.'),
102101
meta: args.meta,
103102
} as const;
@@ -107,6 +106,7 @@ export function enumValueDescriptionChanged(
107106
newEnum: GraphQLEnumType,
108107
oldValue: GraphQLEnumValue,
109108
newValue: GraphQLEnumValue,
109+
config?: ConsiderUsageConfig
110110
): Change<typeof ChangeType.EnumValueDescriptionChanged> {
111111
return enumValueDescriptionChangedFromMeta({
112112
type: ChangeType.EnumValueDescriptionChanged,
@@ -116,24 +116,28 @@ export function enumValueDescriptionChanged(
116116
oldEnumValueDescription: oldValue.description ?? null,
117117
newEnumValueDescription: newValue.description ?? null,
118118
},
119-
});
119+
}, config);
120120
}
121121

122122
function buildEnumValueDeprecationChangedMessage(
123123
args: EnumValueDeprecationReasonChangedChange['meta'],
124+
config?: ConsiderUsageConfig
124125
) {
125-
return `Enum value '${args.enumName}.${args.enumValueName}' deprecation reason changed from '${args.oldEnumValueDeprecationReason}' to '${args.newEnumValueDeprecationReason}'`;
126+
const oldReason = fmt(args.oldEnumValueDeprecationReason, config);
127+
const newReason = fmt(args.newEnumValueDeprecationReason, config);
128+
return `Enum value '${args.enumName}.${args.enumValueName}' deprecation reason changed from '${oldReason}' to '${newReason}'`;
126129
}
127130

128131
export function enumValueDeprecationReasonChangedFromMeta(
129132
args: EnumValueDeprecationReasonChangedChange,
133+
config?: ConsiderUsageConfig
130134
) {
131135
return {
132136
criticality: {
133137
level: CriticalityLevel.NonBreaking,
134138
},
135139
type: ChangeType.EnumValueDeprecationReasonChanged,
136-
message: buildEnumValueDeprecationChangedMessage(args.meta),
140+
message: buildEnumValueDeprecationChangedMessage(args.meta, config),
137141
path: [args.meta.enumName, args.meta.enumValueName].join('.'),
138142
meta: args.meta,
139143
} as const;
@@ -143,6 +147,7 @@ export function enumValueDeprecationReasonChanged(
143147
newEnum: GraphQLEnumType,
144148
oldValue: GraphQLEnumValue,
145149
newValue: GraphQLEnumValue,
150+
config?: ConsiderUsageConfig,
146151
): Change<typeof ChangeType.EnumValueDeprecationReasonChanged> {
147152
return enumValueDeprecationReasonChangedFromMeta({
148153
type: ChangeType.EnumValueDeprecationReasonChanged,
@@ -152,24 +157,27 @@ export function enumValueDeprecationReasonChanged(
152157
oldEnumValueDeprecationReason: oldValue.deprecationReason ?? '',
153158
newEnumValueDeprecationReason: newValue.deprecationReason ?? '',
154159
},
155-
});
160+
}, config);
156161
}
157162

158163
function buildEnumValueDeprecationReasonAddedMessage(
159164
args: EnumValueDeprecationReasonAddedChange['meta'],
165+
config?: ConsiderUsageConfig,
160166
) {
161-
return `Enum value '${args.enumName}.${args.enumValueName}' was deprecated with reason '${args.addedValueDeprecationReason}'`;
167+
const reason = fmt(args.addedValueDeprecationReason, config);
168+
return `Enum value '${args.enumName}.${args.enumValueName}' was deprecated with reason '${reason}'`;
162169
}
163170

164171
export function enumValueDeprecationReasonAddedFromMeta(
165172
args: EnumValueDeprecationReasonAddedChange,
173+
config?: ConsiderUsageConfig,
166174
) {
167175
return {
168176
criticality: {
169177
level: CriticalityLevel.NonBreaking,
170178
},
171179
type: ChangeType.EnumValueDeprecationReasonAdded,
172-
message: buildEnumValueDeprecationReasonAddedMessage(args.meta),
180+
message: buildEnumValueDeprecationReasonAddedMessage(args.meta, config),
173181
path: [args.meta.enumName, args.meta.enumValueName].join('.'),
174182
meta: args.meta,
175183
} as const;
@@ -179,6 +187,7 @@ export function enumValueDeprecationReasonAdded(
179187
newEnum: GraphQLEnumType,
180188
oldValue: GraphQLEnumValue,
181189
newValue: GraphQLEnumValue,
190+
config?: ConsiderUsageConfig,
182191
): Change<typeof ChangeType.EnumValueDeprecationReasonAdded> {
183192
return enumValueDeprecationReasonAddedFromMeta({
184193
type: ChangeType.EnumValueDeprecationReasonAdded,
@@ -187,7 +196,7 @@ export function enumValueDeprecationReasonAdded(
187196
enumValueName: oldValue.name,
188197
addedValueDeprecationReason: newValue.deprecationReason ?? '',
189198
},
190-
});
199+
}, config);
191200
}
192201

193202
function buildEnumValueDeprecationReasonRemovedMessage(

0 commit comments

Comments
 (0)