UNPKG

@graphql-eslint/eslint-plugin

Version:
195 lines (193 loc) • 7.41 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.rule = void 0; const utils_1 = require("@graphql-tools/utils"); const graphql_1 = require("graphql"); const utils_js_1 = require("../utils.js"); const RULE_ID = 'require-description'; const ALLOWED_KINDS = [ ...utils_js_1.TYPES_KINDS, graphql_1.Kind.DIRECTIVE_DEFINITION, graphql_1.Kind.FIELD_DEFINITION, graphql_1.Kind.INPUT_VALUE_DEFINITION, graphql_1.Kind.ENUM_VALUE_DEFINITION, graphql_1.Kind.OPERATION_DEFINITION, ]; function getNodeName(node) { const DisplayNodeNameMap = { [graphql_1.Kind.OBJECT_TYPE_DEFINITION]: 'type', [graphql_1.Kind.INTERFACE_TYPE_DEFINITION]: 'interface', [graphql_1.Kind.ENUM_TYPE_DEFINITION]: 'enum', [graphql_1.Kind.SCALAR_TYPE_DEFINITION]: 'scalar', [graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'input', [graphql_1.Kind.UNION_TYPE_DEFINITION]: 'union', [graphql_1.Kind.DIRECTIVE_DEFINITION]: 'directive', }; switch (node.kind) { case graphql_1.Kind.OBJECT_TYPE_DEFINITION: case graphql_1.Kind.INTERFACE_TYPE_DEFINITION: case graphql_1.Kind.ENUM_TYPE_DEFINITION: case graphql_1.Kind.SCALAR_TYPE_DEFINITION: case graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION: case graphql_1.Kind.UNION_TYPE_DEFINITION: return `${DisplayNodeNameMap[node.kind]} ${node.name.value}`; case graphql_1.Kind.DIRECTIVE_DEFINITION: return `${DisplayNodeNameMap[node.kind]} @${node.name.value}`; case graphql_1.Kind.FIELD_DEFINITION: case graphql_1.Kind.INPUT_VALUE_DEFINITION: case graphql_1.Kind.ENUM_VALUE_DEFINITION: return `${node.parent.name.value}.${node.name.value}`; case graphql_1.Kind.OPERATION_DEFINITION: return node.name ? `${node.operation} ${node.name.value}` : node.operation; } } const schema = { type: 'array', minItems: 1, maxItems: 1, items: { type: 'object', additionalProperties: false, minProperties: 1, properties: { types: { type: 'boolean', description: `Includes:\n${utils_js_1.TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`, }, rootField: { type: 'boolean', description: 'Definitions within `Query`, `Mutation`, and `Subscription` root types.', }, ...Object.fromEntries([...ALLOWED_KINDS].sort().map(kind => { let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`; if (kind === graphql_1.Kind.OPERATION_DEFINITION) { description += '\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.'; } return [kind, { type: 'boolean', description }]; })), }, }, }; exports.rule = { meta: { docs: { category: 'Schema', description: 'Enforce descriptions in type definitions and operations.', url: `https://the-guild.dev/graphql/eslint/rules/${RULE_ID}`, examples: [ { title: 'Incorrect', usage: [{ types: true, FieldDefinition: true }], code: /* GraphQL */ ` type someTypeName { name: String } `, }, { title: 'Correct', usage: [{ types: true, FieldDefinition: true }], code: /* GraphQL */ ` """ Some type description """ type someTypeName { """ Name description """ name: String } `, }, { title: 'Correct', usage: [{ OperationDefinition: true }], code: /* GraphQL */ ` # Create a new user mutation createUser { # ... } `, }, { title: 'Correct', usage: [{ rootField: true }], code: /* GraphQL */ ` type Mutation { "Create a new user" createUser: User } type User { name: String } `, }, ], configOptions: [ { types: true, [graphql_1.Kind.DIRECTIVE_DEFINITION]: true, // rootField: true TODO enable in graphql-eslint v4 }, ], recommended: true, }, type: 'suggestion', messages: { [RULE_ID]: 'Description is required for `{{ nodeName }}`.', }, schema, }, create(context) { const { types, rootField, ...restOptions } = context.options[0] || {}; const kinds = new Set(types ? utils_js_1.TYPES_KINDS : []); for (const [kind, isEnabled] of Object.entries(restOptions)) { if (isEnabled) { kinds.add(kind); } else { kinds.delete(kind); } } if (rootField) { const schema = (0, utils_js_1.requireGraphQLSchemaFromContext)(RULE_ID, context); const rootTypeNames = (0, utils_1.getRootTypeNames)(schema); kinds.add(`:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/^(${[ ...rootTypeNames, ].join(',')})$/] > FieldDefinition`); } const selector = [...kinds].join(','); return { [selector](node) { var _a; let description = ''; const isOperation = node.kind === graphql_1.Kind.OPERATION_DEFINITION; if (isOperation) { const rawNode = node.rawNode(); const { prev, line } = rawNode.loc.startToken; if ((prev === null || prev === void 0 ? void 0 : prev.kind) === graphql_1.TokenKind.COMMENT) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- TODO: remove `!` when drop support of graphql@15 const value = prev.value.trim(); const linesBefore = line - prev.line; if (!value.startsWith('eslint') && linesBefore === 1) { description = value; } } } else { description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value.trim()) || ''; } if (description.length === 0) { context.report({ loc: isOperation ? (0, utils_js_1.getLocation)(node.loc.start, node.operation) : node.name.loc, messageId: RULE_ID, data: { nodeName: getNodeName(node), }, }); } }, }; }, };