UNPKG

@graphql-eslint/eslint-plugin

Version:
199 lines (195 loc) • 7.64 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 require_selections_exports = {}; __export(require_selections_exports, { rule: () => rule }); module.exports = __toCommonJS(require_selections_exports); var import_utils = require("@graphql-tools/utils"), import_graphql = require("graphql"), import_estree_converter = require("../estree-converter/index.js"), import_utils2 = require("../utils.js"); const RULE_ID = "require-selections", DEFAULT_ID_FIELD_NAME = "id", schema = { definitions: { asString: { type: "string" }, asArray: import_utils2.ARRAY_DEFAULT_OPTIONS }, type: "array", maxItems: 1, items: { type: "object", additionalProperties: !1, properties: { fieldName: { oneOf: [{ $ref: "#/definitions/asString" }, { $ref: "#/definitions/asArray" }], default: DEFAULT_ID_FIELD_NAME } } } }, rule = { meta: { type: "suggestion", hasSuggestions: !0, docs: { category: "Operations", description: "Enforce selecting specific fields when they are available on the GraphQL type.", url: `https://the-guild.dev/graphql/eslint/rules/${RULE_ID}`, requiresSchema: !0, requiresSiblings: !0, examples: [ { title: "Incorrect", code: ( /* GraphQL */ ` # In your schema type User { id: ID! name: String! } # Query query { user { name } } ` ) }, { title: "Correct", code: ( /* GraphQL */ ` # In your schema type User { id: ID! name: String! } # Query query { user { id name } } # Selecting \`id\` with an alias is also valid query { user { id: name } } ` ) } ], recommended: !0 }, messages: { [RULE_ID]: `Field{{ pluralSuffix }} {{ fieldName }} must be selected when it's available on a type. Include it in your selection set{{ addition }}.` }, schema }, create(context) { const schema2 = (0, import_utils2.requireGraphQLSchemaFromContext)(RULE_ID, context), siblings = (0, import_utils2.requireSiblingsOperations)(RULE_ID, context), { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {}, idNames = (0, import_utils.asArray)(fieldName), selector = "OperationDefinition SelectionSet[parent.kind!=/(^OperationDefinition|InlineFragment)$/]", typeInfo = new import_graphql.TypeInfo(schema2); function checkFragments(node) { for (const selection of node.selections) { if (selection.kind !== import_graphql.Kind.FRAGMENT_SPREAD) continue; const [foundSpread] = siblings.getFragment(selection.name.value); if (!foundSpread) continue; const checkedFragmentSpreads = /* @__PURE__ */ new Set(), visitor = (0, import_graphql.visitWithTypeInfo)(typeInfo, { SelectionSet(node2, key, _parent) { const parent = _parent; parent.kind === import_graphql.Kind.FRAGMENT_DEFINITION ? checkedFragmentSpreads.add(parent.name.value) : parent.kind !== import_graphql.Kind.INLINE_FRAGMENT && checkSelections( node2, typeInfo.getType(), selection.loc.start, parent, checkedFragmentSpreads ); } }); (0, import_graphql.visit)(foundSpread.document, visitor); } } function checkSelections(node, type, loc, parent, checkedFragmentSpreads = /* @__PURE__ */ new Set()) { const rawType = (0, import_estree_converter.getBaseType)(type); if (rawType instanceof import_graphql.GraphQLObjectType || rawType instanceof import_graphql.GraphQLInterfaceType) checkFields(rawType); else if (rawType instanceof import_graphql.GraphQLUnionType) { for (const selection of node.selections) if (selection.kind === import_graphql.Kind.INLINE_FRAGMENT) { const t = rawType.getTypes().find((t2) => t2.name === selection.typeCondition.name.value); t && checkFields(t); } } function checkFields(rawType2) { const fields = rawType2.getFields(); if (!idNames.some((name) => fields[name])) return; function hasIdField({ selections }) { return selections.some((selection) => { if (selection.kind === import_graphql.Kind.FIELD) return selection.alias && idNames.includes(selection.alias.value) ? !0 : idNames.includes(selection.name.value); if (selection.kind === import_graphql.Kind.INLINE_FRAGMENT) return hasIdField(selection.selectionSet); if (selection.kind === import_graphql.Kind.FRAGMENT_SPREAD) { const [foundSpread] = siblings.getFragment(selection.name.value); if (foundSpread) { const fragmentSpread = foundSpread.document; return checkedFragmentSpreads.add(fragmentSpread.name.value), hasIdField(fragmentSpread.selectionSet); } } return !1; }); } const hasId = hasIdField(node); if (checkFragments(node), hasId) return; const pluralSuffix = idNames.length > 1 ? "s" : "", fieldName2 = (0, import_utils2.englishJoinWords)( idNames.map((name) => `\`${(parent.alias || parent.name).value}.${name}\``) ), addition = checkedFragmentSpreads.size === 0 ? "" : ` or add to used fragment${checkedFragmentSpreads.size > 1 ? "s" : ""} ${(0, import_utils2.englishJoinWords)([...checkedFragmentSpreads].map((name) => `\`${name}\``))}`, problem = { loc, messageId: RULE_ID, data: { pluralSuffix, fieldName: fieldName2, addition } }; "type" in node && (problem.suggest = idNames.map((idName) => ({ desc: `Add \`${idName}\` selection`, fix: (fixer) => { let insertNode = node.selections[0]; return insertNode = insertNode.kind === import_graphql.Kind.INLINE_FRAGMENT ? insertNode.selectionSet.selections[0] : insertNode, fixer.insertTextBefore(insertNode, `${idName} `); } }))), context.report(problem); } } return { [selector](node) { const typeInfo2 = node.typeInfo(); typeInfo2.gqlType && checkSelections(node, typeInfo2.gqlType, node.loc.start, node.parent); } }; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { rule });