mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-10 05:56:26 +00:00
204 lines
5 KiB
TypeScript
204 lines
5 KiB
TypeScript
import is from '@sindresorhus/is';
|
|
import type {
|
|
ArgumentNode,
|
|
DefinitionNode,
|
|
DocumentNode,
|
|
FieldNode,
|
|
OperationDefinitionNode,
|
|
SelectionNode,
|
|
SelectionSetNode,
|
|
TypeNode,
|
|
ValueNode,
|
|
VariableDefinitionNode,
|
|
} from 'graphql/language';
|
|
import { Kind, parse } from 'graphql/language';
|
|
|
|
function isOperationDefinitionNode(
|
|
def: DefinitionNode,
|
|
): def is OperationDefinitionNode {
|
|
return def.kind === Kind.OPERATION_DEFINITION;
|
|
}
|
|
|
|
function isFieldNode(sel: SelectionNode): sel is FieldNode {
|
|
return sel.kind === Kind.FIELD;
|
|
}
|
|
|
|
interface Arguments {
|
|
[key: string]:
|
|
| string
|
|
| boolean
|
|
| null
|
|
| Arguments
|
|
| (string | boolean | null | Arguments)[];
|
|
}
|
|
|
|
type Variables = Record<string, string>;
|
|
|
|
interface SelectionSet {
|
|
__vars?: Variables;
|
|
__args?: Arguments;
|
|
[key: string]: undefined | null | SelectionSet | Arguments;
|
|
}
|
|
|
|
interface GraphqlSnapshot {
|
|
query?: SelectionSet;
|
|
mutation?: SelectionSet;
|
|
subscription?: SelectionSet;
|
|
variables?: Record<string, string>;
|
|
}
|
|
|
|
function getArguments(key: string, val: ValueNode): Arguments {
|
|
const result: Arguments = {};
|
|
const kind = val.kind;
|
|
if (
|
|
val.kind === Kind.INT ||
|
|
val.kind === Kind.FLOAT ||
|
|
val.kind === Kind.STRING ||
|
|
val.kind === Kind.BOOLEAN ||
|
|
val.kind === Kind.ENUM
|
|
) {
|
|
result[key] = val.value;
|
|
} else if (val.kind === Kind.OBJECT) {
|
|
let childResult: Arguments = {};
|
|
val.fields.forEach((fieldNode) => {
|
|
const childKey = fieldNode.name.value;
|
|
const childVal = getArguments(childKey, fieldNode.value);
|
|
childResult = { ...childResult, ...childVal };
|
|
});
|
|
result[key] = childResult;
|
|
} else if (val.kind === Kind.LIST) {
|
|
const results: Arguments[] = [];
|
|
val.values.forEach((fieldNode) => {
|
|
results.push(getArguments(key, fieldNode));
|
|
});
|
|
result[key] = results.map(({ [key]: x }) => x).flat();
|
|
} else if (val.kind === Kind.NULL) {
|
|
result[key] = null;
|
|
} else if (val.kind === Kind.VARIABLE) {
|
|
result[key] = `$${val.name.value}`;
|
|
} else {
|
|
result[key] = `<<${kind}>>`;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function simplifyArguments(
|
|
argNodes?: ReadonlyArray<ArgumentNode>,
|
|
): Arguments | null {
|
|
if (argNodes) {
|
|
let result: Arguments = {};
|
|
argNodes.forEach((argNode) => {
|
|
const name = argNode.name.value;
|
|
const valNode = argNode.value;
|
|
result = {
|
|
...result,
|
|
...getArguments(name, valNode),
|
|
};
|
|
});
|
|
return result;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function simplifySelectionSet(
|
|
selectionSet: SelectionSetNode,
|
|
parentArgs: Arguments | null,
|
|
parentVars: Variables | null,
|
|
): SelectionSet {
|
|
const result: SelectionSet = {};
|
|
|
|
selectionSet.selections.forEach((selectionNode) => {
|
|
if (isFieldNode(selectionNode)) {
|
|
const name = selectionNode.name.value;
|
|
const args = simplifyArguments(selectionNode.arguments);
|
|
const childSelectionSet = selectionNode.selectionSet;
|
|
if (parentVars && !is.emptyObject(parentVars)) {
|
|
result.__vars = parentVars;
|
|
}
|
|
if (parentArgs && !is.emptyObject(parentArgs)) {
|
|
result.__args = parentArgs;
|
|
}
|
|
result[name] = childSelectionSet
|
|
? simplifySelectionSet(childSelectionSet, args, null)
|
|
: null;
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
function getTypeName(typeNode: TypeNode): string {
|
|
const kind = typeNode.kind;
|
|
|
|
if (typeNode.kind === Kind.NAMED_TYPE) {
|
|
return typeNode.name.value;
|
|
}
|
|
|
|
const childTypeNode = typeNode.type;
|
|
const childTypeName = getTypeName(childTypeNode);
|
|
|
|
if (kind === Kind.LIST_TYPE) {
|
|
return `[${childTypeName}]`;
|
|
}
|
|
|
|
if (kind === Kind.NON_NULL_TYPE) {
|
|
return `${childTypeName}!`;
|
|
}
|
|
|
|
return `<<${kind}>>`;
|
|
}
|
|
|
|
function simplifyVariableDefinitions(
|
|
varNodes: ReadonlyArray<VariableDefinitionNode> | null,
|
|
): Variables {
|
|
const result: Variables = {};
|
|
if (varNodes) {
|
|
varNodes.forEach((varNode) => {
|
|
const key = `$${varNode.variable.name.value}`;
|
|
const typeNode = varNode.type;
|
|
const typeName = getTypeName(typeNode);
|
|
result[key] = typeName;
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function simplifyGraphqlTree(tree: DocumentNode): GraphqlSnapshot {
|
|
const result: GraphqlSnapshot = {};
|
|
|
|
const { definitions } = tree;
|
|
definitions.forEach((def) => {
|
|
if (isOperationDefinitionNode(def)) {
|
|
const { operation, selectionSet, variableDefinitions = null } = def;
|
|
const vars = simplifyVariableDefinitions(variableDefinitions);
|
|
result[operation] = simplifySelectionSet(selectionSet, null, vars);
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
export interface GraphqlSnapshotInput {
|
|
query: string;
|
|
variables: Record<string, string>;
|
|
}
|
|
|
|
export function makeGraphqlSnapshot(
|
|
requestBody: GraphqlSnapshotInput,
|
|
): GraphqlSnapshot | null {
|
|
try {
|
|
const { query: queryStr, variables } = requestBody;
|
|
if (!queryStr) {
|
|
return null;
|
|
}
|
|
const queryRawTree = parse(queryStr, { noLocation: true });
|
|
const queryTree = simplifyGraphqlTree(queryRawTree);
|
|
if (variables) {
|
|
return { variables, ...queryTree };
|
|
}
|
|
return queryTree;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|