UNPKG

eslint-plugin-perfectionist

Version:

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

384 lines (383 loc) 12.8 kB
'use strict' Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' }, }) const commonJsonSchemas = require('../utils/common-json-schemas.js') const reportErrors = require('../utils/report-errors.js') const validateNewlinesAndPartitionConfiguration = require('../utils/validate-newlines-and-partition-configuration.js') const types = require('./sort-object-types/types.js') const validateGeneratedGroupsConfiguration = require('../utils/validate-generated-groups-configuration.js') const getCustomGroupsCompareOptions = require('./sort-object-types/get-custom-groups-compare-options.js') const validateCustomSortConfiguration = require('../utils/validate-custom-sort-configuration.js') const getMatchingContextOptions = require('../utils/get-matching-context-options.js') const generatePredefinedGroups = require('../utils/generate-predefined-groups.js') const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js') const isMemberOptional = require('./sort-object-types/is-member-optional.js') const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js') const doesCustomGroupMatch = require('../utils/does-custom-group-match.js') const isNodeFunctionType = require('../utils/is-node-function-type.js') const sortNodesByGroups = require('../utils/sort-nodes-by-groups.js') const createEslintRule = require('../utils/create-eslint-rule.js') const reportAllErrors = require('../utils/report-all-errors.js') const shouldPartition = require('../utils/should-partition.js') const rangeToDiff = require('../utils/range-to-diff.js') const getSettings = require('../utils/get-settings.js') const isSortable = require('../utils/is-sortable.js') const useGroups = require('../utils/use-groups.js') const complete = require('../utils/complete.js') const matches = require('../utils/matches.js') let cachedGroupsByModifiersAndSelectors = /* @__PURE__ */ new Map() let defaultOptions = { fallbackSort: { type: 'unsorted', sortBy: 'name' }, partitionByComment: false, partitionByNewLine: false, newlinesBetween: 'ignore', specialCharacters: 'keep', useConfigurationIf: {}, type: 'alphabetical', groupKind: 'mixed', ignorePattern: [], ignoreCase: true, customGroups: {}, locales: 'en-US', sortBy: 'name', alphabet: '', order: 'asc', groups: [], } let jsonSchema = { items: { properties: { ...commonJsonSchemas.buildCommonJsonSchemas({ additionalFallbackSortProperties: {}, }), customGroups: { oneOf: [ commonJsonSchemas.customGroupsJsonSchema, commonJsonSchemas.buildCustomGroupsArrayJsonSchema({ additionalFallbackSortProperties: { sortBy: types.sortByJsonSchema, }, singleCustomGroupJsonSchema: types.singleCustomGroupJsonSchema, }), ], }, groupKind: { description: '[DEPRECATED] Specifies top-level groups.', enum: ['mixed', 'required-first', 'optional-first'], type: 'string', }, useConfigurationIf: commonJsonSchemas.buildUseConfigurationIfJsonSchema({ additionalProperties: { declarationMatchesPattern: commonJsonSchemas.regexJsonSchema, }, }), partitionByComment: commonJsonSchemas.partitionByCommentJsonSchema, partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema, newlinesBetween: commonJsonSchemas.newlinesBetweenJsonSchema, ignorePattern: commonJsonSchemas.regexJsonSchema, sortBy: types.sortByJsonSchema, groups: commonJsonSchemas.groupsJsonSchema, }, additionalProperties: false, type: 'object', }, uniqueItems: true, type: 'array', } const sortObjectTypes = createEslintRule.createEslintRule({ create: context => ({ TSTypeLiteral: node => sortObjectTypeElements({ availableMessageIds: { missedSpacingBetweenMembers: 'missedSpacingBetweenObjectTypeMembers', extraSpacingBetweenMembers: 'extraSpacingBetweenObjectTypeMembers', unexpectedGroupOrder: 'unexpectedObjectTypesGroupOrder', unexpectedOrder: 'unexpectedObjectTypesOrder', }, parentNodeName: node.parent.type === 'TSTypeAliasDeclaration' ? node.parent.id.name : null, elements: node.members, context, }), }), meta: { messages: { missedSpacingBetweenObjectTypeMembers: reportErrors.MISSED_SPACING_ERROR, extraSpacingBetweenObjectTypeMembers: reportErrors.EXTRA_SPACING_ERROR, unexpectedObjectTypesGroupOrder: reportErrors.GROUP_ORDER_ERROR, unexpectedObjectTypesOrder: reportErrors.ORDER_ERROR, }, docs: { url: 'https://perfectionist.dev/rules/sort-object-types', description: 'Enforce sorted object types.', recommended: true, }, schema: jsonSchema, type: 'suggestion', fixable: 'code', }, defaultOptions: [defaultOptions], name: 'sort-object-types', }) let sortObjectTypeElements = ({ availableMessageIds, parentNodeName, elements, context, }) => { if (!isSortable.isSortable(elements)) { return } let settings = getSettings.getSettings(context.settings) let { sourceCode, id } = context let matchedContextOptions = getMatchingContextOptions .getMatchingContextOptions({ nodeNames: elements.map(node => getNodeName({ typeElement: node, sourceCode }), ), contextOptions: context.options, }) .find(options2 => { var _a if ( !((_a = options2.useConfigurationIf) == null ? void 0 : _a.declarationMatchesPattern) ) { return true } if (!parentNodeName) { return false } return matches.matches( parentNodeName, options2.useConfigurationIf.declarationMatchesPattern, ) }) let options = complete.complete( matchedContextOptions, settings, defaultOptions, ) validateCustomSortConfiguration.validateCustomSortConfiguration(options) validateGeneratedGroupsConfiguration.validateGeneratedGroupsConfiguration({ selectors: types.allSelectors, modifiers: types.allModifiers, options, }) validateNewlinesAndPartitionConfiguration.validateNewlinesAndPartitionConfiguration( options, ) if ( parentNodeName && matches.matches(parentNodeName, options.ignorePattern) ) { return } let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({ ruleName: id, sourceCode, }) let formattedMembers = elements.reduce( (accumulator, typeElement) => { if ( typeElement.type === 'TSCallSignatureDeclaration' || typeElement.type === 'TSConstructSignatureDeclaration' ) { accumulator.push([]) return accumulator } let lastGroup = accumulator.at(-1) let lastSortingNode = lastGroup == null ? void 0 : lastGroup.at(-1) let { setCustomGroups, defineGroup, getGroup } = useGroups.useGroups(options) let selectors = [] let modifiers = [] if (typeElement.type === 'TSIndexSignature') { selectors.push('index-signature') } if (isNodeFunctionType.isNodeFunctionType(typeElement)) { selectors.push('method') } if (typeElement.loc.start.line !== typeElement.loc.end.line) { modifiers.push('multiline') selectors.push('multiline') } if ( !['index-signature', 'method'].some(selector => selectors.includes(selector), ) ) { selectors.push('property') } selectors.push('member') if (isMemberOptional.isMemberOptional(typeElement)) { modifiers.push('optional') } else { modifiers.push('required') } let predefinedGroups = generatePredefinedGroups.generatePredefinedGroups({ cache: cachedGroupsByModifiersAndSelectors, selectors, modifiers, }) for (let predefinedGroup of predefinedGroups) { defineGroup(predefinedGroup) } let name = getNodeName({ typeElement, sourceCode }) let value = null if ( typeElement.type === 'TSPropertySignature' && typeElement.typeAnnotation ) { value = sourceCode.getText(typeElement.typeAnnotation.typeAnnotation) } if (Array.isArray(options.customGroups)) { for (let customGroup of options.customGroups) { if ( doesCustomGroupMatch.doesCustomGroupMatch({ elementValue: value, elementName: name, customGroup, selectors, modifiers, }) ) { defineGroup(customGroup.groupName, true) if (getGroup() === customGroup.groupName) { break } } } } else { setCustomGroups(options.customGroups, name, { override: true, }) } let sortingNode = { isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled( typeElement, eslintDisabledLines, ), groupKind: isMemberOptional.isMemberOptional(typeElement) ? 'optional' : 'required', size: rangeToDiff.rangeToDiff(typeElement, sourceCode), addSafetySemicolonWhenInline: true, group: getGroup(), node: typeElement, value, name, } if ( shouldPartition.shouldPartition({ lastSortingNode, sortingNode, sourceCode, options, }) ) { lastGroup = [] accumulator.push(lastGroup) } lastGroup == null ? void 0 : lastGroup.push(sortingNode) return accumulator }, [[]], ) let groupKindOrder if (options.groupKind === 'required-first') { groupKindOrder = ['required', 'optional'] } else if (options.groupKind === 'optional-first') { groupKindOrder = ['optional', 'required'] } else { groupKindOrder = ['any'] } for (let nodes of formattedMembers) { let filteredGroupKindNodes = groupKindOrder.map(groupKind => nodes.filter( currentNode => groupKind === 'any' || currentNode.groupKind === groupKind, ), ) let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes => filteredGroupKindNodes.flatMap(groupedNodes => sortNodesByGroups.sortNodesByGroups({ getOptionsByGroupNumber: groupNumber => { let { fallbackSortNodeValueGetter, options: overriddenOptions, nodeValueGetter, } = getCustomGroupsCompareOptions.getCustomGroupsCompareOptions( options, groupNumber, ) return { options: { ...options, ...overriddenOptions, }, fallbackSortNodeValueGetter, nodeValueGetter, } }, isNodeIgnoredForGroup: (node, groupOptions) => { if (groupOptions.sortBy === 'value') { return !node.value } return false }, ignoreEslintDisabledNodes, groups: options.groups, nodes: groupedNodes, }), ) reportAllErrors.reportAllErrors({ sortNodesExcludingEslintDisabled, availableMessageIds, sourceCode, options, context, nodes, }) } } let getNodeName = ({ typeElement, sourceCode }) => { var _a, _b let name let formatName = value => value.replace(/[,;]$/u, '') if (typeElement.type === 'TSPropertySignature') { if (typeElement.key.type === 'Identifier') { ;({ name } = typeElement.key) } else if (typeElement.key.type === 'Literal') { name = `${typeElement.key.value}` } else { let end = ((_a = typeElement.typeAnnotation) == null ? void 0 : _a.range.at(0)) ?? typeElement.range.at(1) - (typeElement.optional ? '?'.length : 0) name = sourceCode.text.slice(typeElement.range.at(0), end) } } else if (typeElement.type === 'TSIndexSignature') { let endIndex = ((_b = typeElement.typeAnnotation) == null ? void 0 : _b.range.at(0)) ?? typeElement.range.at(1) name = formatName(sourceCode.text.slice(typeElement.range.at(0), endIndex)) } else if ( typeElement.type === 'TSMethodSignature' && 'name' in typeElement.key ) { ;({ name } = typeElement.key) } else { name = formatName( sourceCode.text.slice(typeElement.range.at(0), typeElement.range.at(1)), ) } return name } exports.default = sortObjectTypes exports.jsonSchema = jsonSchema exports.sortObjectTypeElements = sortObjectTypeElements