UNPKG

@graphql-eslint/eslint-plugin

Version:
396 lines (395 loc) • 13.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__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: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var alphabetize_exports = {}; __export(alphabetize_exports, { rule: () => rule }); module.exports = __toCommonJS(alphabetize_exports); var import_graphql = require("graphql"); var import_lodash = __toESM(require("lodash.lowercase")); var import_utils = require("../utils.js"); const RULE_ID = "alphabetize"; const fieldsEnum = [ import_graphql.Kind.OBJECT_TYPE_DEFINITION, import_graphql.Kind.INTERFACE_TYPE_DEFINITION, import_graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION ]; const valuesEnum = [import_graphql.Kind.ENUM_TYPE_DEFINITION]; const selectionsEnum = [ import_graphql.Kind.OPERATION_DEFINITION, import_graphql.Kind.FRAGMENT_DEFINITION ]; const variablesEnum = [import_graphql.Kind.OPERATION_DEFINITION]; const argumentsEnum = [ import_graphql.Kind.FIELD_DEFINITION, import_graphql.Kind.FIELD, import_graphql.Kind.DIRECTIVE_DEFINITION, import_graphql.Kind.DIRECTIVE ]; const schema = { type: "array", minItems: 1, maxItems: 1, items: { type: "object", additionalProperties: false, minProperties: 1, properties: { fields: { ...import_utils.ARRAY_DEFAULT_OPTIONS, items: { enum: fieldsEnum }, description: "Fields of `type`, `interface`, and `input`." }, values: { ...import_utils.ARRAY_DEFAULT_OPTIONS, items: { enum: valuesEnum }, description: "Values of `enum`." }, selections: { ...import_utils.ARRAY_DEFAULT_OPTIONS, items: { enum: selectionsEnum }, description: "Selections of `fragment` and operations `query`, `mutation` and `subscription`." }, variables: { ...import_utils.ARRAY_DEFAULT_OPTIONS, items: { enum: variablesEnum }, 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`.", default: false }, groups: { ...import_utils.ARRAY_DEFAULT_OPTIONS, minItems: 2, description: "Custom order group. Example: `['id', '*', 'createdAt', 'updatedAt']` where `*` says for everything else." } } } }; const 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: [import_graphql.Kind.ENUM_TYPE_DEFINITION] }], code: ( /* GraphQL */ ` enum Role { SUPER_ADMIN ADMIN # should be before "SUPER_ADMIN" USER GOD # should be before "USER" } ` ) }, { title: "Correct", usage: [{ values: [import_graphql.Kind.ENUM_TYPE_DEFINITION] }], 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: [ { fields: fieldsEnum, values: valuesEnum, arguments: argumentsEnum // TODO: add in graphql-eslint v4 // definitions: true, // groups: ['id', '*', 'createdAt', 'updatedAt'] } ], operations: [ { selections: selectionsEnum, variables: variablesEnum, arguments: [import_graphql.Kind.FIELD, import_graphql.Kind.DIRECTIVE] } ] } }, messages: { [RULE_ID]: "{{ currNode }} should be before {{ prevNode }}" }, schema }, create(context) { var _a, _b, _c, _d, _e; 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 = []; const 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) { if (node.kind === import_graphql.Kind.VARIABLE) { node = node.parent; } const [firstBeforeComment] = getBeforeComments(node); const [firstAfterComment] = sourceCode.getCommentsAfter(node); const from = firstBeforeComment || node; const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment) ? firstAfterComment : node; return [from.range[0], to.range[1]]; } function checkNodes(nodes = []) { var _a2, _b2, _c2, _d2; for (let i = 1; i < nodes.length; i += 1) { const currNode = nodes[i]; const currName = "alias" in currNode && ((_a2 = currNode.alias) == null ? void 0 : _a2.value) || "name" in currNode && ((_b2 = currNode.name) == null ? void 0 : _b2.value); if (!currName) { continue; } const prevNode = nodes[i - 1]; const prevName = "alias" in prevNode && ((_c2 = prevNode.alias) == null ? void 0 : _c2.value) || "name" in prevNode && ((_d2 = prevNode.name) == null ? void 0 : _d2.value); if (prevName) { const compareResult = prevName.localeCompare(currName); const { groups } = opts; let shouldSortByGroup = false; if (groups == null ? void 0 : groups.length) { if (!groups.includes("*")) { throw new Error("`groups` option should contain `*` string."); } let indexForPrev = groups.indexOf(prevName); if (indexForPrev === -1) indexForPrev = groups.indexOf("*"); let indexForCurr = groups.indexOf(currName); if (indexForCurr === -1) indexForCurr = groups.indexOf("*"); shouldSortByGroup = indexForPrev - indexForCurr > 0; if (indexForPrev < indexForCurr) { continue; } } const shouldSort = compareResult === 1; if (!shouldSortByGroup && !shouldSort) { const isSameName = compareResult === 0; if (!isSameName || !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); const currRange = getRangeWithComments(currNode); yield fixer.replaceTextRange( prevRange, sourceCode.getText({ range: currRange }) ); yield fixer.replaceTextRange( currRange, sourceCode.getText({ range: prevRange }) ); } }); } } const opts = context.options[0]; const fields = new Set((_a = opts.fields) != null ? _a : []); const listeners = {}; const kinds = [ 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(); const fieldsSelector = kinds.join(","); const hasEnumValues = ((_b = opts.values) == null ? void 0 : _b[0]) === import_graphql.Kind.ENUM_TYPE_DEFINITION; const selectionsSelector = (_c = opts.selections) == null ? void 0 : _c.join(","); const hasVariables = ((_d = opts.variables) == null ? void 0 : _d[0]) === import_graphql.Kind.OPERATION_DEFINITION; const argumentsSelector = (_e = opts.arguments) == null ? void 0 : _e.join(","); if (fieldsSelector) { listeners[fieldsSelector] = (node) => { checkNodes(node.fields); }; } if (hasEnumValues) { const enumValuesSelector = [import_graphql.Kind.ENUM_TYPE_DEFINITION, import_graphql.Kind.ENUM_TYPE_EXTENSION].join(","); listeners[enumValuesSelector] = (node) => { checkNodes(node.values); }; } if (selectionsSelector) { listeners[`:matches(${selectionsSelector}) SelectionSet`] = (node) => { checkNodes(node.selections); }; } if (hasVariables) { listeners.OperationDefinition = (node) => { var _a2; checkNodes((_a2 = node.variableDefinitions) == null ? void 0 : _a2.map((varDef) => varDef.variable)); }; } if (argumentsSelector) { listeners[argumentsSelector] = (node) => { checkNodes(node.arguments); }; } if (opts.definitions) { listeners.Document = (node) => { checkNodes(node.definitions); }; } return listeners; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { rule });