UNPKG

eslint-plugin-perfectionist

Version:

ESLint plugin for sorting various data such as objects, imports, types, enums, JSX props, etc.

237 lines (236 loc) 7.94 kB
import { buildCommonJsonSchemas } from '../utils/json-schemas/common-json-schemas.js' import { buildCommonGroupsJsonSchemas } from '../utils/json-schemas/common-groups-json-schemas.js' import { UnreachableCaseError } from '../utils/unreachable-case-error.js' import { EXTRA_SPACING_ERROR, GROUP_ORDER_ERROR, MISSED_COMMENT_ABOVE_ERROR, MISSED_SPACING_ERROR, ORDER_ERROR, } from '../utils/report-errors.js' import { partitionByCommentJsonSchema, partitionByNewLineJsonSchema, } from '../utils/json-schemas/common-partition-json-schemas.js' import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration.js' import { defaultComparatorByOptionsComputer } from '../utils/compare/default-comparator-by-options-computer.js' import { buildOptionsByGroupIndexComputer } from '../utils/build-options-by-group-index-computer.js' import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration.js' import { validateGroupsConfiguration } from '../utils/validate-groups-configuration.js' import { generatePredefinedGroups } from '../utils/generate-predefined-groups.js' import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines.js' import { doesCustomGroupMatch } from '../utils/does-custom-group-match.js' import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled.js' import { sortNodesByGroups } from '../utils/sort-nodes-by-groups.js' import { reportAllErrors } from '../utils/report-all-errors.js' import { shouldPartition } from '../utils/should-partition.js' import { computeGroup } from '../utils/compute-group.js' import { rangeToDiff } from '../utils/range-to-diff.js' import { getSettings } from '../utils/get-settings.js' import { complete } from '../utils/complete.js' import { createEslintRule } from '../utils/create-eslint-rule.js' import { isNodeOnSingleLine } from '../utils/is-node-on-single-line.js' import { additionalCustomGroupMatchOptionsJsonSchema, allModifiers, allSelectors, } from './sort-exports/types.js' import { AST_NODE_TYPES } from '@typescript-eslint/utils' /** * Cache computed groups by modifiers and selectors for performance. */ var cachedGroupsByModifiersAndSelectors = /* @__PURE__ */ new Map() var ORDER_ERROR_ID = 'unexpectedExportsOrder' var GROUP_ORDER_ERROR_ID = 'unexpectedExportsGroupOrder' var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenExports' var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenExports' var MISSED_COMMENT_ABOVE_ERROR_ID = 'missedCommentAboveExport' var defaultOptions = { fallbackSort: { type: 'unsorted' }, newlinesInside: 'newlinesBetween', specialCharacters: 'keep', partitionByComment: false, newlinesBetween: 'ignore', partitionByNewLine: false, type: 'alphabetical', customGroups: [], ignoreCase: true, locales: 'en-US', alphabet: '', order: 'asc', groups: [], } var sort_exports_default = createEslintRule({ create: context => { let settings = getSettings(context.settings) let options = complete(context.options.at(0), settings, defaultOptions) validateCustomSortConfiguration(options) validateGroupsConfiguration({ modifiers: allModifiers, selectors: allSelectors, options, }) validateNewlinesAndPartitionConfiguration(options) let { sourceCode, id } = context let eslintDisabledLines = getEslintDisabledLines({ ruleName: id, sourceCode, }) let formattedMembers = [[]] function registerNode(node) { if (!node.source) { return } let selector = 'export' let modifiers = [ computeExportKindModifier(node), computeExportTypeModifier(node), computeLineCountModifier(node), ] let name = node.source.value let group = computeGroup({ customGroupMatcher: customGroup => doesCustomGroupMatch({ selectors: [selector], elementName: name, customGroup, modifiers, }), predefinedGroups: generatePredefinedGroups({ cache: cachedGroupsByModifiersAndSelectors, selectors: [selector], modifiers, }), options, }) let sortingNode = { isEslintDisabled: isNodeEslintDisabled(node, eslintDisabledLines), size: rangeToDiff(node, sourceCode), addSafetySemicolonWhenInline: true, group, name, node, } let lastSortingNode = formattedMembers.at(-1)?.at(-1) if ( shouldPartition({ lastSortingNode, sortingNode, sourceCode, options, }) ) { formattedMembers.push([]) } formattedMembers.at(-1).push({ ...sortingNode, partitionId: formattedMembers.length, }) } return { 'Program:exit': () => { sortExportNodes({ formattedMembers, context, options, }) }, ExportNamedDeclaration: registerNode, ExportAllDeclaration: registerNode, } }, meta: { schema: { items: { properties: { ...buildCommonJsonSchemas(), ...buildCommonGroupsJsonSchemas({ additionalCustomGroupMatchProperties: additionalCustomGroupMatchOptionsJsonSchema, }), partitionByComment: partitionByCommentJsonSchema, partitionByNewLine: partitionByNewLineJsonSchema, }, additionalProperties: false, type: 'object', }, uniqueItems: true, type: 'array', }, messages: { [MISSED_COMMENT_ABOVE_ERROR_ID]: MISSED_COMMENT_ABOVE_ERROR, [MISSED_SPACING_ERROR_ID]: MISSED_SPACING_ERROR, [EXTRA_SPACING_ERROR_ID]: EXTRA_SPACING_ERROR, [GROUP_ORDER_ERROR_ID]: GROUP_ORDER_ERROR, [ORDER_ERROR_ID]: ORDER_ERROR, }, docs: { url: 'https://perfectionist.dev/rules/sort-exports', description: 'Enforce sorted exports.', recommended: true, }, type: 'suggestion', fixable: 'code', }, defaultOptions: [defaultOptions], name: 'sort-exports', }) function sortExportNodes({ formattedMembers, context, options }) { let optionsByGroupIndexComputer = buildOptionsByGroupIndexComputer(options) let nodes = formattedMembers.flat() reportAllErrors({ availableMessageIds: { missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID, extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID, missedCommentAbove: MISSED_COMMENT_ABOVE_ERROR_ID, unexpectedGroupOrder: GROUP_ORDER_ERROR_ID, unexpectedOrder: ORDER_ERROR_ID, }, sortNodesExcludingEslintDisabled, options, context, nodes, }) function sortNodesExcludingEslintDisabled(ignoreEslintDisabledNodes) { return formattedMembers.flatMap(groupedNodes => sortNodesByGroups({ comparatorByOptionsComputer: defaultComparatorByOptionsComputer, optionsByGroupIndexComputer, ignoreEslintDisabledNodes, groups: options.groups, nodes: groupedNodes, }), ) } } function computeExportKindModifier(node) { let exportKind = 'exportKind' in node ? node.exportKind : void 0 switch (exportKind) { case void 0: case 'value': return 'value' case 'type': return 'type' /* v8 ignore next 2 -- @preserve Exhaustive guard. */ default: throw new UnreachableCaseError(exportKind) } } function computeExportTypeModifier(node) { switch (node.type) { case AST_NODE_TYPES.ExportNamedDeclaration: return 'named' case AST_NODE_TYPES.ExportAllDeclaration: return 'wildcard' /* v8 ignore next 2 -- @preserve Exhaustive guard. */ default: throw new UnreachableCaseError(node) } } function computeLineCountModifier(node) { if (isNodeOnSingleLine(node)) { return 'singleline' } return 'multiline' } export { sort_exports_default as default }