UNPKG

@graphql-eslint/eslint-plugin

Version:
324 lines (323 loc) • 11.9 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf, __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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target, mod )), __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod); var alphabetize_exports = {}; __export(alphabetize_exports, { rule: () => rule }); module.exports = __toCommonJS(alphabetize_exports); var import_graphql = require("graphql"), import_lodash = __toESM(require("lodash.lowercase")), import_utils = require("../utils.js"); const RULE_ID = "alphabetize", fieldsEnum = [ import_graphql.Kind.OBJECT_TYPE_DEFINITION, import_graphql.Kind.INTERFACE_TYPE_DEFINITION, import_graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION ], selectionsEnum = [ import_graphql.Kind.OPERATION_DEFINITION, import_graphql.Kind.FRAGMENT_DEFINITION ], argumentsEnum = [ import_graphql.Kind.FIELD_DEFINITION, import_graphql.Kind.FIELD, import_graphql.Kind.DIRECTIVE_DEFINITION, import_graphql.Kind.DIRECTIVE ], schema = { type: "array", minItems: 1, maxItems: 1, items: { type: "object", additionalProperties: !1, minProperties: 1, properties: { fields: { ...import_utils.ARRAY_DEFAULT_OPTIONS, items: { enum: fieldsEnum }, description: "Fields of `type`, `interface`, and `input`." }, values: { type: "boolean", description: "Values of `enum`." }, selections: { ...import_utils.ARRAY_DEFAULT_OPTIONS, items: { enum: selectionsEnum }, description: "Selections of `fragment` and operations `query`, `mutation` and `subscription`." }, variables: { type: "boolean", description: "Variables of operations `query`, `mutation` and `subscription`." }, arguments: { ...import_utils.ARRAY_DEFAULT_OPTIONS, items: { enum: argumentsEnum }, description: "Arguments of fields and directives." }, definitions: { type: "boolean", description: "Definitions \u2013 `type`, `interface`, `enum`, `scalar`, `input`, `union` and `directive`." }, groups: { ...import_utils.ARRAY_DEFAULT_OPTIONS, minItems: 2, description: "Custom order group. Example: `['id', '*', 'createdAt', 'updatedAt']` where `*` says for everything else." } } } }, rule = { meta: { type: "suggestion", fixable: "code", docs: { category: ["Schema", "Operations"], description: "Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.", url: `https://the-guild.dev/graphql/eslint/rules/${RULE_ID}`, examples: [ { title: "Incorrect", usage: [{ fields: [import_graphql.Kind.OBJECT_TYPE_DEFINITION] }], code: ( /* GraphQL */ ` type User { password: String firstName: String! # should be before "password" age: Int # should be before "firstName" lastName: String! } ` ) }, { title: "Correct", usage: [{ fields: [import_graphql.Kind.OBJECT_TYPE_DEFINITION] }], code: ( /* GraphQL */ ` type User { age: Int firstName: String! lastName: String! password: String } ` ) }, { title: "Incorrect", usage: [{ values: !0 }], code: ( /* GraphQL */ ` enum Role { SUPER_ADMIN ADMIN # should be before "SUPER_ADMIN" USER GOD # should be before "USER" } ` ) }, { title: "Correct", usage: [{ values: !0 }], code: ( /* GraphQL */ ` enum Role { ADMIN GOD SUPER_ADMIN USER } ` ) }, { title: "Incorrect", usage: [{ selections: [import_graphql.Kind.OPERATION_DEFINITION] }], code: ( /* GraphQL */ ` query { me { firstName lastName email # should be before "lastName" } } ` ) }, { title: "Correct", usage: [{ selections: [import_graphql.Kind.OPERATION_DEFINITION] }], code: ( /* GraphQL */ ` query { me { email firstName lastName } } ` ) } ], configOptions: { schema: [ { definitions: !0, fields: fieldsEnum, values: !0, arguments: argumentsEnum, groups: ["id", "*", "createdAt", "updatedAt"] } ], operations: [ { definitions: !0, selections: selectionsEnum, variables: !0, arguments: [import_graphql.Kind.FIELD, import_graphql.Kind.DIRECTIVE], groups: ["id", "*", "createdAt", "updatedAt"] } ] } }, messages: { [RULE_ID]: "{{ currNode }} should be before {{ prevNode }}" }, schema }, create(context) { const sourceCode = context.getSourceCode(); function isNodeAndCommentOnSameLine(node, comment) { return node.loc.end.line === comment.loc.start.line; } function getBeforeComments(node) { const commentsBefore = sourceCode.getCommentsBefore(node); if (commentsBefore.length === 0) return []; const tokenBefore = sourceCode.getTokenBefore(node); if (tokenBefore) return commentsBefore.filter((comment) => !isNodeAndCommentOnSameLine(tokenBefore, comment)); const filteredComments = [], nodeLine = node.loc.start.line; for (let i = commentsBefore.length - 1; i >= 0; i -= 1) { const comment = commentsBefore[i]; if (nodeLine - comment.loc.start.line - filteredComments.length > 1) break; filteredComments.unshift(comment); } return filteredComments; } function getRangeWithComments(node) { node.kind === import_graphql.Kind.VARIABLE && (node = node.parent); const [firstBeforeComment] = getBeforeComments(node), [firstAfterComment] = sourceCode.getCommentsAfter(node), from = firstBeforeComment || node, to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment) ? firstAfterComment : node; return [from.range[0], to.range[1]]; } function checkNodes(nodes = []) { for (let i = 1; i < nodes.length; i += 1) { const currNode = nodes[i], currName = "alias" in currNode && currNode.alias?.value || "name" in currNode && currNode.name?.value; if (!currName) continue; const prevNode = nodes[i - 1], prevName = "alias" in prevNode && prevNode.alias?.value || "name" in prevNode && prevNode.name?.value; if (prevName) { const compareResult = prevName.localeCompare(currName), { groups } = opts; let shouldSortByGroup = !1; if (groups?.length) { if (!groups.includes("*")) throw new Error("`groups` option should contain `*` string."); let indexForPrev = groups.indexOf(prevName); indexForPrev === -1 && (indexForPrev = groups.indexOf("*")); let indexForCurr = groups.indexOf(currName); if (indexForCurr === -1 && (indexForCurr = groups.indexOf("*")), shouldSortByGroup = indexForPrev - indexForCurr > 0, indexForPrev < indexForCurr) continue; } if (!shouldSortByGroup && !(compareResult === 1) && (!(compareResult === 0) || !prevNode.kind.endsWith("Extension") || currNode.kind.endsWith("Extension"))) continue; } context.report({ // @ts-expect-error can't be undefined node: "alias" in currNode && currNode.alias || currNode.name, messageId: RULE_ID, data: { currNode: (0, import_utils.displayNodeName)(currNode), prevNode: prevName ? (0, import_utils.displayNodeName)(prevNode) : (0, import_lodash.default)(prevNode.kind) }, *fix(fixer) { const prevRange = getRangeWithComments(prevNode), currRange = getRangeWithComments(currNode); yield fixer.replaceTextRange( prevRange, sourceCode.getText({ range: currRange }) ), yield fixer.replaceTextRange( currRange, sourceCode.getText({ range: prevRange }) ); } }); } } const opts = context.options[0], fields = new Set(opts.fields ?? []), listeners = {}, fieldsSelector = [ fields.has(import_graphql.Kind.OBJECT_TYPE_DEFINITION) && [ import_graphql.Kind.OBJECT_TYPE_DEFINITION, import_graphql.Kind.OBJECT_TYPE_EXTENSION ], fields.has(import_graphql.Kind.INTERFACE_TYPE_DEFINITION) && [ import_graphql.Kind.INTERFACE_TYPE_DEFINITION, import_graphql.Kind.INTERFACE_TYPE_EXTENSION ], fields.has(import_graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION) && [ import_graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION, import_graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION ] ].filter(import_utils.truthy).flat().join(","), selectionsSelector = opts.selections?.join(","), argumentsSelector = opts.arguments?.join(","); if (fieldsSelector && (listeners[fieldsSelector] = (node) => { checkNodes(node.fields); }), opts.values) { const enumValuesSelector = [import_graphql.Kind.ENUM_TYPE_DEFINITION, import_graphql.Kind.ENUM_TYPE_EXTENSION].join(","); listeners[enumValuesSelector] = (node) => { checkNodes(node.values); }; } return selectionsSelector && (listeners[`:matches(${selectionsSelector}) SelectionSet`] = (node) => { checkNodes(node.selections); }), opts.variables && (listeners.OperationDefinition = (node) => { checkNodes(node.variableDefinitions?.map((varDef) => varDef.variable)); }), argumentsSelector && (listeners[argumentsSelector] = (node) => { checkNodes(node.arguments); }), opts.definitions && (listeners.Document = (node) => { checkNodes(node.definitions); }), listeners; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { rule });