UNPKG

@graphql-eslint/eslint-plugin

Version:
148 lines (141 loc) • 7.83 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _graphql = require('graphql'); var _utils = require('@graphql-tools/utils'); var _utilsjs = 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 (process.env.NODE_ENV !== "test" && edgeTypesCache) return edgeTypesCache; const edgeTypes = /* @__PURE__ */ new Set(), visitor = { ObjectTypeDefinition(node) { if (!node.name.value.endsWith("Connection")) return; const edges = _optionalChain([node, 'access', _ => _.fields, 'optionalAccess', _2 => _2.find, 'call', _3 => _3((field) => field.name.value === "edges")]); if (edges) { const edgesTypeName = _utilsjs.getTypeName.call(void 0, edges), edgesType = schema2.getType(edgesTypeName); _graphql.isObjectType.call(void 0, edgesType) && edgeTypes.add(edgesTypeName); } } }, astNode = _utils.getDocumentNodeFromSchema.call(void 0, schema2); return _graphql.visit.call(void 0, 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 = exports.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 = _utilsjs.requireGraphQLSchema.call(void 0, RULE_ID, context), edgeTypes = getEdgeTypes(schema2), options = { withEdgeSuffix: !0, shouldImplementNode: !0, listTypeCanWrapOnlyEdgeType: !0, ...context.options[0] }, isNamedOrNonNullNamed = (node) => node.kind === _graphql.Kind.NAMED_TYPE || node.kind === _graphql.Kind.NON_NULL_TYPE && node.gqlType.kind === _graphql.Kind.NAMED_TYPE, checkNodeField = (node) => { const nodeField = _optionalChain([node, 'access', _4 => _4.fields, 'optionalAccess', _5 => _5.find, 'call', _6 => _6((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 = _utilsjs.getTypeName.call(void 0, nodeField.gqlType.rawNode()), type = schema2.getType(nodeReturnTypeName); if (!_graphql.isObjectType.call(void 0, type)) return; _optionalChain([type, 'access', _7 => _7.astNode, 'access', _8 => _8.interfaces, 'optionalAccess', _9 => _9.some, 'call', _10 => _10((n) => n.name.value === "Node")]) || context.report({ node: node.name, messageId: MESSAGE_SHOULD_IMPLEMENTS_NODE }); } }, checkCursorField = (node) => { const cursorField = _optionalChain([node, 'access', _11 => _11.fields, 'optionalAccess', _12 => _12.find, 'call', _13 => _13((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 = _utilsjs.getTypeName.call(void 0, cursorField.rawNode()); (!isNamedOrNonNullNamed(cursorField.gqlType) || typeName !== "String" && !_graphql.isScalarType.call(void 0, 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); _graphql.isObjectType.call(void 0, 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 === _graphql.Kind.LIST_TYPE || node.kind === _graphql.Kind.NON_NULL_TYPE && node.gqlType.kind === _graphql.Kind.LIST_TYPE) { const typeName = _utilsjs.getTypeName.call(void 0, node.rawNode()); edgeTypes.has(typeName) || context.report({ node, messageId: MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE }); } }), listeners; } }; exports.rule = rule;