eslint-plugin-perfectionist
Version:
ESLint plugin for sorting various data such as objects, imports, types, enums, JSX props, etc.
267 lines (266 loc) • 9.7 kB
JavaScript
'use strict'
const types$1 = require('@typescript-eslint/types')
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 getCustomGroupsCompareOptions = require('../utils/get-custom-groups-compare-options.js')
const validateGeneratedGroupsConfiguration = require('../utils/validate-generated-groups-configuration.js')
const validateCustomSortConfiguration = require('../utils/validate-custom-sort-configuration.js')
const getMatchingContextOptions = require('../utils/get-matching-context-options.js')
const doesCustomGroupMatch = require('./sort-jsx-props/does-custom-group-match.js')
const generatePredefinedGroups = require('../utils/generate-predefined-groups.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const types = require('./sort-jsx-props/types.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' },
specialCharacters: 'keep',
newlinesBetween: 'ignore',
partitionByNewLine: false,
useConfigurationIf: {},
type: 'alphabetical',
ignorePattern: [],
ignoreCase: true,
customGroups: {},
locales: 'en-US',
alphabet: '',
order: 'asc',
groups: [],
}
const sortJsxProps = createEslintRule.createEslintRule({
create: context => ({
JSXElement: node => {
if (!isSortable.isSortable(node.openingElement.attributes)) {
return
}
let settings = getSettings.getSettings(context.settings)
let { sourceCode, id } = context
let matchedContextOptions = getMatchingContextOptions
.getMatchingContextOptions({
nodeNames: node.openingElement.attributes
.filter(
attribute =>
attribute.type !==
types$1.TSESTree.AST_NODE_TYPES.JSXSpreadAttribute,
)
.map(attribute => getNodeName({ attribute })),
contextOptions: context.options,
})
.find(options2 => {
var _a
if (
!((_a = options2.useConfigurationIf) == null
? void 0
: _a.tagMatchesPattern)
) {
return true
}
return matches.matches(
sourceCode.getText(node.openingElement.name),
options2.useConfigurationIf.tagMatchesPattern,
)
})
let options = complete.complete(
matchedContextOptions,
settings,
defaultOptions,
)
validateCustomSortConfiguration.validateCustomSortConfiguration(options)
validateGeneratedGroupsConfiguration.validateGeneratedGroupsConfiguration(
{
selectors: types.allSelectors,
modifiers: types.allModifiers,
options,
},
)
validateNewlinesAndPartitionConfiguration.validateNewlinesAndPartitionConfiguration(
options,
)
let shouldIgnore = matches.matches(
sourceCode.getText(node.openingElement.name),
options.ignorePattern,
)
if (
shouldIgnore ||
!isSortable.isSortable(node.openingElement.attributes)
) {
return
}
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: id,
sourceCode,
})
let formattedMembers = node.openingElement.attributes.reduce(
(accumulator, attribute) => {
var _a
if (
attribute.type ===
types$1.TSESTree.AST_NODE_TYPES.JSXSpreadAttribute
) {
accumulator.push([])
return accumulator
}
let name = getNodeName({ attribute })
let { setCustomGroups, defineGroup, getGroup } =
useGroups.useGroups(options)
let selectors = []
let modifiers = []
if (attribute.value === null) {
selectors.push('shorthand')
modifiers.push('shorthand')
}
if (attribute.loc.start.line !== attribute.loc.end.line) {
selectors.push('multiline')
modifiers.push('multiline')
}
selectors.push('prop')
let predefinedGroups =
generatePredefinedGroups.generatePredefinedGroups({
cache: cachedGroupsByModifiersAndSelectors,
selectors,
modifiers,
})
for (let predefinedGroup of predefinedGroups) {
defineGroup(predefinedGroup)
}
if (Array.isArray(options.customGroups)) {
for (let customGroup of options.customGroups) {
if (
doesCustomGroupMatch.doesCustomGroupMatch({
elementValue: attribute.value
? sourceCode.getText(attribute.value)
: null,
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(
attribute,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(attribute, sourceCode),
group: getGroup(),
node: attribute,
name,
}
let lastSortingNode =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
if (
shouldPartition.shouldPartition({
lastSortingNode,
sortingNode,
sourceCode,
options,
})
) {
accumulator.push([])
}
accumulator.at(-1).push(sortingNode)
return accumulator
},
[[]],
)
for (let nodes of formattedMembers) {
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
sortNodesByGroups.sortNodesByGroups({
getOptionsByGroupNumber:
getCustomGroupsCompareOptions.buildGetCustomGroupOverriddenOptionsFunction(
options,
),
ignoreEslintDisabledNodes,
groups: options.groups,
nodes,
})
reportAllErrors.reportAllErrors({
availableMessageIds: {
missedSpacingBetweenMembers: 'missedSpacingBetweenJSXPropsMembers',
extraSpacingBetweenMembers: 'extraSpacingBetweenJSXPropsMembers',
unexpectedGroupOrder: 'unexpectedJSXPropsGroupOrder',
unexpectedOrder: 'unexpectedJSXPropsOrder',
},
sortNodesExcludingEslintDisabled,
sourceCode,
options,
context,
nodes,
})
}
},
}),
meta: {
schema: {
items: {
properties: {
...commonJsonSchemas.commonJsonSchemas,
customGroups: {
oneOf: [
commonJsonSchemas.customGroupsJsonSchema,
commonJsonSchemas.buildCustomGroupsArrayJsonSchema({
singleCustomGroupJsonSchema: types.singleCustomGroupJsonSchema,
}),
],
},
useConfigurationIf:
commonJsonSchemas.buildUseConfigurationIfJsonSchema({
additionalProperties: {
tagMatchesPattern: commonJsonSchemas.regexJsonSchema,
},
}),
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
newlinesBetween: commonJsonSchemas.newlinesBetweenJsonSchema,
ignorePattern: commonJsonSchemas.regexJsonSchema,
groups: commonJsonSchemas.groupsJsonSchema,
},
additionalProperties: false,
type: 'object',
},
uniqueItems: true,
type: 'array',
},
messages: {
missedSpacingBetweenJSXPropsMembers: reportErrors.MISSED_SPACING_ERROR,
extraSpacingBetweenJSXPropsMembers: reportErrors.EXTRA_SPACING_ERROR,
unexpectedJSXPropsGroupOrder: reportErrors.GROUP_ORDER_ERROR,
unexpectedJSXPropsOrder: reportErrors.ORDER_ERROR,
},
docs: {
url: 'https://perfectionist.dev/rules/sort-jsx-props',
description: 'Enforce sorted JSX props.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [defaultOptions],
name: 'sort-jsx-props',
})
let getNodeName = ({ attribute }) =>
attribute.name.type === types$1.TSESTree.AST_NODE_TYPES.JSXNamespacedName
? `${attribute.name.namespace.name}:${attribute.name.name.name}`
: attribute.name.name
module.exports = sortJsxProps