diff --git a/src/utilities/__tests__/findSchemaChanges-test.ts b/src/utilities/__tests__/findSchemaChanges-test.ts index 60e5465dd5..8c21781554 100644 --- a/src/utilities/__tests__/findSchemaChanges-test.ts +++ b/src/utilities/__tests__/findSchemaChanges-test.ts @@ -22,6 +22,23 @@ describe('findSchemaChanges', () => { ]); }); + it('should detect a type changing description', () => { + const newSchema = buildSchema(` + "New Description" + type Type1 + `); + + const oldSchema = buildSchema(` + type Type1 + `); + expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([ + { + type: SafeChangeType.DESCRIPTION_CHANGED, + description: 'Description of Type1 has changed to "New Description".', + }, + ]); + }); + it('should detect if a field was added', () => { const oldSchema = buildSchema(` type Query { @@ -43,6 +60,30 @@ describe('findSchemaChanges', () => { ]); }); + it('should detect a field changing description', () => { + const oldSchema = buildSchema(` + type Query { + foo: String + bar: String + } + `); + + const newSchema = buildSchema(` + type Query { + foo: String + "New Description" + bar: String + } + `); + expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([ + { + type: SafeChangeType.DESCRIPTION_CHANGED, + description: + 'Description of field Query.bar has changed to "New Description".', + }, + ]); + }); + it('should detect if a default value was added', () => { const oldSchema = buildSchema(` type Query { @@ -84,6 +125,30 @@ describe('findSchemaChanges', () => { ]); }); + it('should detect if an arg value changes description', () => { + const oldSchema = buildSchema(` + type Query { + foo(x: String!): String + } + `); + + const newSchema = buildSchema(` + type Query { + foo( + "New Description" + x: String! + ): String + } + `); + expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([ + { + type: SafeChangeType.DESCRIPTION_CHANGED, + description: + 'Description of argument Query.foo(x) has changed to "New Description".', + }, + ]); + }); + it('should detect if a directive was added', () => { const oldSchema = buildSchema(` type Query { @@ -106,6 +171,31 @@ describe('findSchemaChanges', () => { ]); }); + it('should detect if a directive changes description', () => { + const oldSchema = buildSchema(` + directive @Foo on FIELD_DEFINITION + + type Query { + foo: String + } + `); + + const newSchema = buildSchema(` + "New Description" + directive @Foo on FIELD_DEFINITION + + type Query { + foo: String + } + `); + expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([ + { + description: 'Description of @Foo has changed to "New Description".', + type: SafeChangeType.DESCRIPTION_CHANGED, + }, + ]); + }); + it('should detect if a directive becomes repeatable', () => { const oldSchema = buildSchema(` directive @Foo on FIELD_DEFINITION @@ -177,4 +267,94 @@ describe('findSchemaChanges', () => { }, ]); }); + + it('should detect if a directive arg changes description', () => { + const oldSchema = buildSchema(` + directive @Foo( + arg1: String + ) on FIELD_DEFINITION + + type Query { + foo: String + } + `); + + const newSchema = buildSchema(` + directive @Foo( + "New Description" + arg1: String + ) on FIELD_DEFINITION + + type Query { + foo: String + } + `); + expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([ + { + description: + 'Description of @Foo(Foo) has changed to "New Description".', + type: SafeChangeType.DESCRIPTION_CHANGED, + }, + ]); + }); + + it('should detect if an enum member changes description', () => { + const oldSchema = buildSchema(` + enum Foo { + TEST + } + + type Query { + foo: String + } + `); + + const newSchema = buildSchema(` + enum Foo { + "New Description" + TEST + } + + type Query { + foo: String + } + `); + expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([ + { + description: + 'Description of enum value Foo.TEST has changed to "New Description".', + type: SafeChangeType.DESCRIPTION_CHANGED, + }, + ]); + }); + + it('should detect if an input field changes description', () => { + const oldSchema = buildSchema(` + input Foo { + x: String + } + + type Query { + foo: String + } + `); + + const newSchema = buildSchema(` + input Foo { + "New Description" + x: String + } + + type Query { + foo: String + } + `); + expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([ + { + description: + 'Description of input-field Foo.x has changed to "New Description".', + type: SafeChangeType.DESCRIPTION_CHANGED, + }, + ]); + }); }); diff --git a/src/utilities/findSchemaChanges.ts b/src/utilities/findSchemaChanges.ts index e6c0ff555e..b9c49aa59e 100644 --- a/src/utilities/findSchemaChanges.ts +++ b/src/utilities/findSchemaChanges.ts @@ -70,6 +70,7 @@ export type DangerousChangeType = (typeof DangerousChangeType)[keyof typeof DangerousChangeType]; export const SafeChangeType = { + DESCRIPTION_CHANGED: 'DESCRIPTION_CHANGED' as const, TYPE_ADDED: 'TYPE_ADDED' as const, OPTIONAL_INPUT_FIELD_ADDED: 'OPTIONAL_INPUT_FIELD_ADDED' as const, OPTIONAL_ARG_ADDED: 'OPTIONAL_ARG_ADDED' as const, @@ -194,6 +195,15 @@ function findDirectiveChanges( }); } + for (const [oldArg, newArg] of argsDiff.persisted) { + if (oldArg.description !== newArg.description) { + schemaChanges.push({ + type: SafeChangeType.DESCRIPTION_CHANGED, + description: `Description of @${oldDirective.name}(${oldDirective.name}) has changed to "${newArg.description}".`, + }); + } + } + if (oldDirective.isRepeatable && !newDirective.isRepeatable) { schemaChanges.push({ type: BreakingChangeType.DIRECTIVE_REPEATABLE_REMOVED, @@ -206,6 +216,13 @@ function findDirectiveChanges( }); } + if (oldDirective.description !== newDirective.description) { + schemaChanges.push({ + type: SafeChangeType.DESCRIPTION_CHANGED, + description: `Description of @${oldDirective.name} has changed to "${newDirective.description}".`, + }); + } + for (const location of oldDirective.locations) { if (!newDirective.locations.includes(location)) { schemaChanges.push({ @@ -256,6 +273,13 @@ function findTypeChanges( } for (const [oldType, newType] of typesDiff.persisted) { + if (oldType.description !== newType.description) { + schemaChanges.push({ + type: SafeChangeType.DESCRIPTION_CHANGED, + description: `Description of ${oldType.name} has changed to "${newType.description}".`, + }); + } + if (isEnumType(oldType) && isEnumType(newType)) { schemaChanges.push(...findEnumTypeChanges(oldType, newType)); } else if (isUnionType(oldType) && isUnionType(newType)) { @@ -328,7 +352,7 @@ function findInputObjectTypeChanges( `Field ${oldType}.${oldField.name} changed type from ` + `${String(oldField.type)} to ${String(newField.type)}.`, }); - } else { + } else if (oldField.type.toString() !== newField.type.toString()) { schemaChanges.push({ type: SafeChangeType.FIELD_CHANGED_KIND_SAFE, description: @@ -336,6 +360,13 @@ function findInputObjectTypeChanges( `${String(oldField.type)} to ${String(newField.type)}.`, }); } + + if (oldField.description !== newField.description) { + schemaChanges.push({ + type: SafeChangeType.DESCRIPTION_CHANGED, + description: `Description of input-field ${newType}.${newField.name} has changed to "${newField.description}".`, + }); + } } return schemaChanges; @@ -368,7 +399,7 @@ function findUnionTypeChanges( function findEnumTypeChanges( oldType: GraphQLEnumType, newType: GraphQLEnumType, -): Array { +): Array { const schemaChanges = []; const valuesDiff = diff(oldType.getValues(), newType.getValues()); @@ -386,6 +417,15 @@ function findEnumTypeChanges( }); } + for (const [oldValue, newValue] of valuesDiff.persisted) { + if (oldValue.description !== newValue.description) { + schemaChanges.push({ + type: SafeChangeType.DESCRIPTION_CHANGED, + description: `Description of enum value ${oldType}.${oldValue.name} has changed to "${newValue.description}".`, + }); + } + } + return schemaChanges; } @@ -459,6 +499,13 @@ function findFieldChanges( `${String(oldField.type)} to ${String(newField.type)}.`, }); } + + if (oldField.description !== newField.description) { + schemaChanges.push({ + type: SafeChangeType.DESCRIPTION_CHANGED, + description: `Description of field ${oldType}.${oldField.name} has changed to "${newField.description}".`, + }); + } } return schemaChanges; @@ -484,6 +531,7 @@ function findArgChanges( oldArg.type, newArg.type, ); + if (!isSafe) { schemaChanges.push({ type: BreakingChangeType.ARG_CHANGED_KIND, @@ -520,7 +568,7 @@ function findArgChanges( type: SafeChangeType.ARG_DEFAULT_VALUE_ADDED, description: `${oldType}.${oldField.name}(${oldArg.name}:) added a defaultValue ${newValueStr}.`, }); - } else { + } else if (oldArg.type.toString() !== newArg.type.toString()) { schemaChanges.push({ type: SafeChangeType.ARG_CHANGED_KIND_SAFE, description: @@ -528,6 +576,13 @@ function findArgChanges( `${String(oldArg.type)} to ${String(newArg.type)}.`, }); } + + if (oldArg.description !== newArg.description) { + schemaChanges.push({ + type: SafeChangeType.DESCRIPTION_CHANGED, + description: `Description of argument ${oldType}.${oldField.name}(${oldArg.name}) has changed to "${newArg.description}".`, + }); + } } for (const newArg of argsDiff.added) {