UNPKG

@graphql-eslint/eslint-plugin

Version:
162 lines (161 loc) • 7.85 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: !0 }); }, __copyProps = (to, from, except, desc) => { if (from && typeof from == "object" || typeof from == "function") for (let key of __getOwnPropNames(from)) !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod); var relay_edge_types_exports = {}; __export(relay_edge_types_exports, { rule: () => rule }); module.exports = __toCommonJS(relay_edge_types_exports); var import_utils = require("@graphql-tools/utils"), import_graphql = require("graphql"), import_utils2 = require("../utils.js"); const RULE_ID = "relay-edge-types", MESSAGE_MUST_BE_OBJECT_TYPE = "MESSAGE_MUST_BE_OBJECT_TYPE", MESSAGE_MISSING_EDGE_SUFFIX = "MESSAGE_MISSING_EDGE_SUFFIX", MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE = "MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE", MESSAGE_SHOULD_IMPLEMENTS_NODE = "MESSAGE_SHOULD_IMPLEMENTS_NODE"; let edgeTypesCache; function getEdgeTypes(schema2) { if (edgeTypesCache) return edgeTypesCache; const edgeTypes = /* @__PURE__ */ new Set(), visitor = { ObjectTypeDefinition(node) { if (!node.name.value.endsWith("Connection")) return; const edges = node.fields?.find((field) => field.name.value === "edges"); if (edges) { const edgesTypeName = (0, import_utils2.getTypeName)(edges), edgesType = schema2.getType(edgesTypeName); (0, import_graphql.isObjectType)(edgesType) && edgeTypes.add(edgesTypeName); } } }, astNode = (0, import_utils.getDocumentNodeFromSchema)(schema2); return (0, import_graphql.visit)(astNode, visitor), edgeTypesCache = edgeTypes, edgeTypesCache; } const schema = { type: "array", maxItems: 1, items: { type: "object", additionalProperties: !1, minProperties: 1, properties: { withEdgeSuffix: { type: "boolean", default: !0, description: 'Edge type name must end in "Edge".' }, shouldImplementNode: { type: "boolean", default: !0, description: "Edge type's field `node` must implement `Node` interface." }, listTypeCanWrapOnlyEdgeType: { type: "boolean", default: !0, description: "A list type should only wrap an edge type." } } } }, rule = { meta: { type: "problem", docs: { category: "Schema", description: [ "Set of rules to follow Relay specification for Edge types.", "", "- A type that is returned in list form by a connection type's `edges` field is considered by this spec to be an Edge type", "- Edge type must be an Object type", "- Edge type must contain a field `node` that return either Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types. Notably, this field cannot return a list", "- Edge type must contain a field `cursor` that return either String, Scalar, or a non-null wrapper around one of those types", '- Edge type name must end in "Edge" _(optional)_', "- Edge type's field `node` must implement `Node` interface _(optional)_", "- A list type should only wrap an edge type _(optional)_" ].join(` `), url: `https://the-guild.dev/graphql/eslint/rules/${RULE_ID}`, isDisabledForAllConfig: !0, requiresSchema: !0, examples: [ { title: "Correct", code: ( /* GraphQL */ ` type UserConnection { edges: [UserEdge] pageInfo: PageInfo! } ` ) } ] }, messages: { [MESSAGE_MUST_BE_OBJECT_TYPE]: "Edge type must be an Object type.", [MESSAGE_MISSING_EDGE_SUFFIX]: 'Edge type must have "Edge" suffix.', [MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE]: "A list type should only wrap an edge type.", [MESSAGE_SHOULD_IMPLEMENTS_NODE]: "Edge type's field `node` must implement `Node` interface." }, schema }, create(context) { const schema2 = (0, import_utils2.requireGraphQLSchemaFromContext)(RULE_ID, context), edgeTypes = getEdgeTypes(schema2), options = { withEdgeSuffix: !0, shouldImplementNode: !0, listTypeCanWrapOnlyEdgeType: !0, ...context.options[0] }, isNamedOrNonNullNamed = (node) => node.kind === import_graphql.Kind.NAMED_TYPE || node.kind === import_graphql.Kind.NON_NULL_TYPE && node.gqlType.kind === import_graphql.Kind.NAMED_TYPE, checkNodeField = (node) => { const nodeField = node.fields?.find((field) => field.name.value === "node"), message = "return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types."; if (!nodeField) context.report({ node: node.name, message: `Edge type must contain a field \`node\` that ${message}` }); else if (!isNamedOrNonNullNamed(nodeField.gqlType)) context.report({ node: nodeField.name, message: `Field \`node\` must ${message}` }); else if (options.shouldImplementNode) { const nodeReturnTypeName = (0, import_utils2.getTypeName)(nodeField.gqlType.rawNode()), type = schema2.getType(nodeReturnTypeName); if (!(0, import_graphql.isObjectType)(type)) return; type.astNode.interfaces?.some((n) => n.name.value === "Node") || context.report({ node: node.name, messageId: MESSAGE_SHOULD_IMPLEMENTS_NODE }); } }, checkCursorField = (node) => { const cursorField = node.fields?.find((field) => field.name.value === "cursor"), message = "return either a String, Scalar, or a non-null wrapper wrapper around one of those types."; if (!cursorField) { context.report({ node: node.name, message: `Edge type must contain a field \`cursor\` that ${message}` }); return; } const typeName = (0, import_utils2.getTypeName)(cursorField.rawNode()); (!isNamedOrNonNullNamed(cursorField.gqlType) || typeName !== "String" && !(0, import_graphql.isScalarType)(schema2.getType(typeName))) && context.report({ node: cursorField.name, message: `Field \`cursor\` must ${message}` }); }, listeners = { ":matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType Name"(node) { const type = schema2.getType(node.value); (0, import_graphql.isObjectType)(type) || context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE }); }, ":matches(ObjectTypeDefinition, ObjectTypeExtension)"(node) { const typeName = node.name.value; edgeTypes.has(typeName) && (checkNodeField(node), checkCursorField(node), options.withEdgeSuffix && !typeName.endsWith("Edge") && context.report({ node: node.name, messageId: MESSAGE_MISSING_EDGE_SUFFIX })); } }; return options.listTypeCanWrapOnlyEdgeType && (listeners["FieldDefinition > .gqlType"] = (node) => { if (node.kind === import_graphql.Kind.LIST_TYPE || node.kind === import_graphql.Kind.NON_NULL_TYPE && node.gqlType.kind === import_graphql.Kind.LIST_TYPE) { const typeName = (0, import_utils2.getTypeName)(node.rawNode()); edgeTypes.has(typeName) || context.report({ node, messageId: MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE }); } }), listeners; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { rule });