UNPKG

eslint-plugin-perfectionist

Version:

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

262 lines (261 loc) 8.78 kB
import { buildCommonJsonSchemas } from '../utils/json-schemas/common-json-schemas.js' import { buildCommonGroupsJsonSchemas } from '../utils/json-schemas/common-groups-json-schemas.js' import { EXTRA_SPACING_ERROR, GROUP_ORDER_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 { 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 { isSortable } from '../utils/is-sortable.js' import { complete } from '../utils/complete.js' import { createEslintRule } from '../utils/create-eslint-rule.js' import { getNodeDecorators } from '../utils/get-node-decorators.js' import { getDecoratorName } from '../utils/get-decorator-name.js' import { AST_NODE_TYPES } from '@typescript-eslint/utils' var ORDER_ERROR_ID = 'unexpectedDecoratorsOrder' var GROUP_ORDER_ERROR_ID = 'unexpectedDecoratorsGroupOrder' var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenDecorators' var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenDecorators' var defaultOptions = { fallbackSort: { type: 'unsorted' }, newlinesInside: 'newlinesBetween', specialCharacters: 'keep', partitionByComment: false, partitionByNewLine: false, newlinesBetween: 'ignore', sortOnProperties: true, sortOnParameters: true, sortOnAccessors: true, type: 'alphabetical', sortOnClasses: true, sortOnMethods: true, ignoreCase: true, customGroups: [], locales: 'en-US', alphabet: '', order: 'asc', groups: [], } var sort_decorators_default = createEslintRule({ meta: { schema: { items: { properties: { ...buildCommonJsonSchemas(), ...buildCommonGroupsJsonSchemas(), sortOnParameters: { description: 'Controls whether sorting should be enabled for method parameter decorators.', type: 'boolean', }, sortOnProperties: { description: 'Controls whether sorting should be enabled for class property decorators.', type: 'boolean', }, sortOnAccessors: { description: 'Controls whether sorting should be enabled for class accessor decorators.', type: 'boolean', }, sortOnMethods: { description: 'Controls whether sorting should be enabled for class method decorators.', type: 'boolean', }, sortOnClasses: { description: 'Controls whether sorting should be enabled for class decorators.', type: 'boolean', }, partitionByComment: partitionByCommentJsonSchema, partitionByNewLine: partitionByNewLineJsonSchema, }, additionalProperties: false, type: 'object', }, uniqueItems: true, type: 'array', }, messages: { [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-decorators', description: 'Enforce sorted decorators.', recommended: true, }, type: 'suggestion', fixable: 'code', }, create: context => { let settings = getSettings(context.settings) let options = complete(context.options.at(0), settings, defaultOptions) validateCustomSortConfiguration(options) validateGroupsConfiguration({ modifiers: [], selectors: [], options, }) validateNewlinesAndPartitionConfiguration(options) return { Decorator: decorator => { if (!options.sortOnParameters) { return } if ( 'decorators' in decorator.parent && decorator.parent.type === AST_NODE_TYPES.Identifier && decorator.parent.parent.type === AST_NODE_TYPES.FunctionExpression ) { let { decorators } = decorator.parent if (decorator !== decorators[0]) { return } sortDecorators(context, options, decorators) } }, PropertyDefinition: propertyDefinition => { if (options.sortOnProperties) { sortDecorators( context, options, getNodeDecorators(propertyDefinition), ) } }, AccessorProperty: accessorDefinition => { if (options.sortOnAccessors) { sortDecorators( context, options, getNodeDecorators(accessorDefinition), ) } }, MethodDefinition: methodDefinition => { if (options.sortOnMethods) { sortDecorators(context, options, getNodeDecorators(methodDefinition)) } }, ClassDeclaration: declaration => { if (options.sortOnClasses) { sortDecorators(context, options, getNodeDecorators(declaration)) } }, } }, defaultOptions: [defaultOptions], name: 'sort-decorators', }) /** * Sorts decorators attached to a class, method, or property. * * Processes the decorators, groups them according to options, and reports any * ordering errors found. Handles partitioning by comments and newlines. * * @param context - The ESLint rule context. * @param options - The sorting options for decorators. * @param decorators - Array of decorator nodes to sort. */ function sortDecorators(context, options, decorators) { if (!isSortable(decorators)) { return } let { sourceCode, id } = context let eslintDisabledLines = getEslintDisabledLines({ ruleName: id, sourceCode, }) let optionsByGroupIndexComputer = buildOptionsByGroupIndexComputer(options) let formattedMembers = decorators.reduce( (accumulator, decorator) => { let name = getDecoratorName({ sourceCode, decorator, }) let group = computeGroup({ customGroupMatcher: customGroup => doesCustomGroupMatch({ elementName: name, selectors: [], modifiers: [], customGroup, }), predefinedGroups: [], options, }) let sortingNode = { isEslintDisabled: isNodeEslintDisabled(decorator, eslintDisabledLines), size: rangeToDiff(decorator, sourceCode), node: decorator, group, name, } let lastSortingNode = accumulator.at(-1)?.at(-1) if ( shouldPartition({ lastSortingNode, sortingNode, sourceCode, options, }) ) { accumulator.push([]) } accumulator.at(-1).push({ ...sortingNode, partitionId: accumulator.length, }) return accumulator }, [[]], ) function sortNodesExcludingEslintDisabled(ignoreEslintDisabledNodes) { return formattedMembers.flatMap(nodes => sortNodesByGroups({ comparatorByOptionsComputer: defaultComparatorByOptionsComputer, optionsByGroupIndexComputer, ignoreEslintDisabledNodes, groups: options.groups, nodes, }), ) } let nodes = formattedMembers.flat() reportAllErrors({ availableMessageIds: { missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID, extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID, unexpectedGroupOrder: GROUP_ORDER_ERROR_ID, unexpectedOrder: ORDER_ERROR_ID, }, ignoreFirstNodeHighestBlockComment: true, sortNodesExcludingEslintDisabled, options, context, nodes, }) } export { sort_decorators_default as default }