gqlcheck
Version:
Performs additional checks on your GraphQL documents and operations to ensure they conform to your rules, whilst allow-listing existing operations and their constituent parts (and allowing overrides on a per-field basis). Rules include max selection set d
172 lines (171 loc) • 6.65 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeAndOperationPathInfo = void 0;
const tslib_1 = require("tslib");
const assert = tslib_1.__importStar(require("node:assert"));
const graphql_1 = require("graphql");
class TypeAndOperationPathInfo extends graphql_1.TypeInfo {
operationPathParts = [];
_introspectionDepth = 0;
currentRoot = null;
enter(node) {
if (node.kind === graphql_1.Kind.FRAGMENT_DEFINITION ||
node.kind === graphql_1.Kind.OPERATION_DEFINITION) {
if (this.currentRoot) {
throw new Error("There should be no root at this time");
}
this.currentRoot = node;
}
this.enterOperationPath(node);
if (node.kind === graphql_1.Kind.FRAGMENT_DEFINITION &&
node.typeCondition.name.value.startsWith("__")) {
this._introspectionDepth++;
}
else if (node.kind === graphql_1.Kind.FIELD && node.name.value.startsWith("__")) {
this._introspectionDepth++;
}
const result = super.enter(node);
return result;
}
leave(node) {
const result = super.leave(node);
if (node.kind === graphql_1.Kind.FRAGMENT_DEFINITION &&
node.typeCondition.name.value.startsWith("__")) {
this._introspectionDepth--;
}
else if (node.kind === graphql_1.Kind.FIELD && node.name.value.startsWith("__")) {
this._introspectionDepth--;
}
this.leaveOperationPath(node);
if (node.kind === graphql_1.Kind.FRAGMENT_DEFINITION ||
node.kind === graphql_1.Kind.OPERATION_DEFINITION) {
if (!this.currentRoot) {
throw new Error("There should have been a root when leaving a fragment/operation");
}
this.currentRoot = null;
}
return result;
}
getCurrentRoot() {
return this.currentRoot;
}
enterOperationPath(node) {
switch (node.kind) {
case graphql_1.Kind.SELECTION_SET: {
// Noop
break;
}
case graphql_1.Kind.FIELD: {
const fieldName = node.name.value;
const alias = node.alias?.value;
//const namedType = getNamedType(this.getType());
//const typeName = namedType ? namedType.name : "???";
const join = this.operationPathParts[this.operationPathParts.length - 1].endsWith(".")
? ""
: ">";
this.operationPathParts.push(`${join}${alias ? `${alias}:` : ""}${fieldName}`);
break;
}
case graphql_1.Kind.DIRECTIVE: {
this.operationPathParts.push(`@${node.name.value}`);
break;
}
case graphql_1.Kind.OPERATION_DEFINITION: {
const opKind = node.operation;
const opName = node.name?.value;
assert.equal(this.operationPathParts.length, 0, "Path should be empty when entering an operation");
if (opName) {
this.operationPathParts.push(`${opName}:${opKind}`);
}
else {
this.operationPathParts.push(opKind);
}
break;
}
case graphql_1.Kind.INLINE_FRAGMENT: {
if (node.typeCondition) {
this.operationPathParts.push(`${node.typeCondition.name.value}.`);
}
else {
// TODO: what to use for anonymous inline spread without type
// condition? May be useful for directives on fragments.
}
break;
}
case graphql_1.Kind.FRAGMENT_DEFINITION: {
this.operationPathParts.push(`${node.name.value}:${node.typeCondition.name.value}.`);
break;
}
case graphql_1.Kind.VARIABLE_DEFINITION: {
this.operationPathParts.push(`($${node.variable.name.value}:)`);
break;
}
case graphql_1.Kind.ARGUMENT: {
this.operationPathParts.push(`(${node.name.value}:)`);
break;
}
case graphql_1.Kind.LIST: {
//const listType: unknown = getNullableType(this.getInputType());
break;
}
case graphql_1.Kind.OBJECT_FIELD: {
//const objectType: unknown = getNamedType(this.getInputType());
break;
}
case graphql_1.Kind.ENUM: {
//const enumType: unknown = getNamedType(this.getInputType());
break;
}
}
// console.log(node.kind + ": " + this.operationPathParts.join(""));
}
leaveOperationPath(node) {
switch (node.kind) {
case graphql_1.Kind.SELECTION_SET: {
// Noop
break;
}
case graphql_1.Kind.FIELD:
case graphql_1.Kind.DIRECTIVE:
case graphql_1.Kind.OPERATION_DEFINITION: {
this.operationPathParts.pop();
break;
}
case graphql_1.Kind.INLINE_FRAGMENT: {
if (node.typeCondition) {
this.operationPathParts.pop();
}
else {
// TODO: what to use for anonymous inline spread without type
// condition? May be useful for directives on fragments.
}
break;
}
case graphql_1.Kind.FRAGMENT_DEFINITION:
case graphql_1.Kind.VARIABLE_DEFINITION:
case graphql_1.Kind.ARGUMENT: {
this.operationPathParts.pop();
break;
}
case graphql_1.Kind.LIST: {
//const listType: unknown = getNullableType(this.getInputType());
break;
}
case graphql_1.Kind.OBJECT_FIELD: {
//const objectType: unknown = getNamedType(this.getInputType());
break;
}
case graphql_1.Kind.ENUM: {
//const enumType: unknown = getNamedType(this.getInputType());
break;
}
}
}
getOperationPath() {
return this.operationPathParts.join("");
}
isIntrospection() {
return this._introspectionDepth > 0;
}
}
exports.TypeAndOperationPathInfo = TypeAndOperationPathInfo;