UNPKG

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
"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;