@graphql-inspector/core
Version:
Tooling for GraphQL. Compare GraphQL Schemas, check documents, find breaking changes, find similar types.
196 lines (195 loc) • 7.46 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.safeChangeForField = safeChangeForField;
exports.safeChangeForInputValue = safeChangeForInputValue;
exports.getKind = getKind;
exports.getTypePrefix = getTypePrefix;
exports.isPrimitive = isPrimitive;
exports.isForIntrospection = isForIntrospection;
exports.findDeprecatedUsages = findDeprecatedUsages;
exports.removeFieldIfDirectives = removeFieldIfDirectives;
exports.removeDirectives = removeDirectives;
exports.getReachableTypes = getReachableTypes;
const graphql_1 = require("graphql");
const is_deprecated_js_1 = require("./is-deprecated.js");
function safeChangeForField(oldType, newType) {
if (!(0, graphql_1.isWrappingType)(oldType) && !(0, graphql_1.isWrappingType)(newType)) {
return oldType.toString() === newType.toString();
}
if ((0, graphql_1.isNonNullType)(newType)) {
const ofType = (0, graphql_1.isNonNullType)(oldType) ? oldType.ofType : oldType;
return safeChangeForField(ofType, newType.ofType);
}
if ((0, graphql_1.isListType)(oldType)) {
return (((0, graphql_1.isListType)(newType) && safeChangeForField(oldType.ofType, newType.ofType)) ||
((0, graphql_1.isNonNullType)(newType) && safeChangeForField(oldType, newType.ofType)));
}
return false;
}
function safeChangeForInputValue(oldType, newType) {
if (!(0, graphql_1.isWrappingType)(oldType) && !(0, graphql_1.isWrappingType)(newType)) {
return oldType.toString() === newType.toString();
}
if ((0, graphql_1.isListType)(oldType) && (0, graphql_1.isListType)(newType)) {
return safeChangeForInputValue(oldType.ofType, newType.ofType);
}
if ((0, graphql_1.isNonNullType)(oldType)) {
const ofType = (0, graphql_1.isNonNullType)(newType) ? newType.ofType : newType;
return safeChangeForInputValue(oldType.ofType, ofType);
}
return false;
}
function getKind(type) {
const node = type.astNode;
return node?.kind || '';
}
function getTypePrefix(type) {
const kind = getKind(type);
const kindsMap = {
[graphql_1.Kind.SCALAR_TYPE_DEFINITION]: 'scalar',
[graphql_1.Kind.OBJECT_TYPE_DEFINITION]: 'type',
[graphql_1.Kind.INTERFACE_TYPE_DEFINITION]: 'interface',
[graphql_1.Kind.UNION_TYPE_DEFINITION]: 'union',
[graphql_1.Kind.ENUM_TYPE_DEFINITION]: 'enum',
[graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'input',
};
return kindsMap[kind.toString()];
}
function isPrimitive(type) {
return ['String', 'Int', 'Float', 'Boolean', 'ID'].includes(typeof type === 'string' ? type : type.name);
}
function isForIntrospection(type) {
return [
'__Schema',
'__Type',
'__TypeKind',
'__Field',
'__InputValue',
'__EnumValue',
'__Directive',
'__DirectiveLocation',
].includes(typeof type === 'string' ? type : type.name);
}
function findDeprecatedUsages(schema, ast) {
const errors = [];
const typeInfo = new graphql_1.TypeInfo(schema);
(0, graphql_1.visit)(ast, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
Argument(node) {
const argument = typeInfo.getArgument();
if (argument) {
const reason = argument.deprecationReason;
if (reason) {
const fieldDef = typeInfo.getFieldDef();
if (fieldDef) {
errors.push(new graphql_1.GraphQLError(`The argument '${argument?.name}' of '${fieldDef.name}' is deprecated. ${reason}`, [node]));
}
}
}
},
Field(node) {
const fieldDef = typeInfo.getFieldDef();
if (fieldDef && (0, is_deprecated_js_1.isDeprecated)(fieldDef)) {
const parentType = typeInfo.getParentType();
if (parentType) {
const reason = fieldDef.deprecationReason;
errors.push(new graphql_1.GraphQLError(`The field '${parentType.name}.${fieldDef.name}' is deprecated.${reason ? ' ' + reason : ''}`, [node]));
}
}
},
EnumValue(node) {
const enumVal = typeInfo.getEnumValue();
if (enumVal && (0, is_deprecated_js_1.isDeprecated)(enumVal)) {
const type = (0, graphql_1.getNamedType)(typeInfo.getInputType());
if (type) {
const reason = enumVal.deprecationReason;
errors.push(new graphql_1.GraphQLError(`The enum value '${type.name}.${enumVal.name}' is deprecated.${reason ? ' ' + reason : ''}`, [node]));
}
}
},
}));
return errors;
}
function removeFieldIfDirectives(node, directiveNames) {
if (node.directives?.some(d => directiveNames.includes(d.name.value))) {
return null;
}
return node;
}
function removeDirectives(node, directiveNames) {
if (node.directives) {
return {
...node,
directives: node.directives.filter(d => !directiveNames.includes(d.name.value)),
};
}
return node;
}
function getReachableTypes(schema) {
const reachableTypes = new Set();
const collect = (type) => {
const typeName = type.name;
if (reachableTypes.has(typeName)) {
return;
}
reachableTypes.add(typeName);
if ((0, graphql_1.isScalarType)(type)) {
return;
}
if ((0, graphql_1.isInterfaceType)(type) || (0, graphql_1.isObjectType)(type)) {
if ((0, graphql_1.isInterfaceType)(type)) {
const { objects, interfaces } = schema.getImplementations(type);
for (const child of objects) {
collect(child);
}
for (const child of interfaces) {
collect(child);
}
}
const fields = type.getFields();
for (const fieldName in fields) {
const field = fields[fieldName];
collect(resolveOutputType(field.type));
const args = field.args;
for (const argName in args) {
const arg = args[argName];
collect(resolveInputType(arg.type));
}
}
}
else if ((0, graphql_1.isUnionType)(type)) {
const types = type.getTypes();
for (const child of types) {
collect(child);
}
}
else if ((0, graphql_1.isInputObjectType)(type)) {
const fields = type.getFields();
for (const fieldName in fields) {
const field = fields[fieldName];
collect(resolveInputType(field.type));
}
}
};
for (const type of [
schema.getQueryType(),
schema.getMutationType(),
schema.getSubscriptionType(),
]) {
if (type) {
collect(type);
}
}
return reachableTypes;
}
function resolveOutputType(output) {
if ((0, graphql_1.isListType)(output) || (0, graphql_1.isNonNullType)(output)) {
return resolveOutputType(output.ofType);
}
return output;
}
function resolveInputType(input) {
if ((0, graphql_1.isListType)(input) || (0, graphql_1.isNonNullType)(input)) {
return resolveInputType(input.ofType);
}
return input;
}
;