Skip to content

Commit 498bef9

Browse files
authored
feat(sort-variable-declarations): support conditional config by ast selector and name pattern
1 parent 745c79c commit 498bef9

6 files changed

Lines changed: 711 additions & 213 deletions

File tree

docs/content/rules/sort-variable-declarations.mdx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,82 @@ Specifies the sorting locales. Refer To [String.prototype.localeCompare() - loca
169169
- `string` — A BCP 47 language tag (e.g. `'en'`, `'en-US'`, `'zh-CN'`).
170170
- `string[]` — An array of BCP 47 language tags.
171171

172+
### useConfigurationIf
173+
174+
<sub>
175+
type:
176+
```ts
177+
{
178+
allNamesMatchPattern?:
179+
| string
180+
| string[]
181+
| { pattern: string; flags: string }
182+
| { pattern: string; flags: string }[]
183+
matchesAstSelector?: string
184+
}
185+
```
186+
</sub>
187+
<sub>default: `{}`</sub>
188+
189+
Specifies filters to match a particular options configuration for a given variable declaration.
190+
191+
The first matching options configuration will be used. If no configuration matches, the default options configuration will be used.
192+
193+
- `allNamesMatchPattern` — A regexp pattern that all variable declarator names must match.
194+
195+
Example configuration:
196+
```ts
197+
{
198+
'perfectionist/sort-variable-declarations': [
199+
'error',
200+
{
201+
groups: ['r', 'g', 'b'], // Sort colors by RGB
202+
customGroups: [
203+
{
204+
elementNamePattern: '^r$',
205+
groupName: 'r',
206+
},
207+
{
208+
elementNamePattern: '^g$',
209+
groupName: 'g',
210+
},
211+
{
212+
elementNamePattern: '^b$',
213+
groupName: 'b',
214+
},
215+
],
216+
useConfigurationIf: {
217+
allNamesMatchPattern: '^[rgb]$',
218+
},
219+
},
220+
{
221+
type: 'alphabetical' // Fallback configuration
222+
}
223+
],
224+
}
225+
```
226+
227+
- `matchesAstSelector` — An [AST selector](https://eslint.org/docs/latest/extend/selectors) matching a `VariableDeclaration` node.
228+
To avoid unexpected behavior, do not use `:exit` or `:enter` pseudo-selectors.
229+
230+
Example configuration: don't sort `let` variable declarations.
231+
```ts
232+
{
233+
'perfectionist/sort-variable-declarations': [
234+
'error',
235+
{
236+
useConfigurationIf: {
237+
matchesAstSelector: 'VariableDeclaration[kind="let"]',
238+
},
239+
type: 'unsorted'
240+
},
241+
{
242+
type: 'alphabetical' // Fallback configuration
243+
}
244+
],
245+
}
246+
```
247+
172248
### partitionByComment
173249

174250
<sub>default: `false`</sub>
Lines changed: 37 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
import type {
2-
SortVariableDeclarationsSortingNode,
3-
Selector,
4-
Options,
5-
} from './sort-variable-declarations/types'
1+
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
2+
3+
import type { MessageId, Options } from './sort-variable-declarations/types'
64

5+
import {
6+
additionalCustomGroupMatchOptionsJsonSchema,
7+
DEPENDENCY_ORDER_ERROR_ID,
8+
MISSED_SPACING_ERROR_ID,
9+
EXTRA_SPACING_ERROR_ID,
10+
GROUP_ORDER_ERROR_ID,
11+
ORDER_ERROR_ID,
12+
} from './sort-variable-declarations/types'
13+
import {
14+
useExperimentalDependencyDetectionJsonSchema,
15+
buildUseConfigurationIfJsonSchema,
16+
matchesAstSelectorJsonSchema,
17+
buildCommonJsonSchemas,
18+
} from '../utils/json-schemas/common-json-schemas'
719
import {
820
DEPENDENCY_ORDER_ERROR,
921
MISSED_SPACING_ERROR,
@@ -16,223 +28,28 @@ import {
1628
partitionByNewLineJsonSchema,
1729
} from '../utils/json-schemas/common-partition-json-schemas'
1830
import {
19-
useExperimentalDependencyDetectionJsonSchema,
20-
buildCommonJsonSchemas,
21-
} from '../utils/json-schemas/common-json-schemas'
22-
import { computeDependenciesOutsideFunctionsBySortingNode } from '../utils/compute-dependencies-outside-functions-by-sorting-node'
23-
import {
24-
additionalCustomGroupMatchOptionsJsonSchema,
25-
allSelectors,
26-
} from './sort-variable-declarations/types'
27-
import { populateSortingNodeGroupsWithDependencies } from '../utils/populate-sorting-node-groups-with-dependencies'
28-
import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration'
29-
import { defaultComparatorByOptionsComputer } from '../utils/compare/default-comparator-by-options-computer'
30-
import { buildOptionsByGroupIndexComputer } from '../utils/build-options-by-group-index-computer'
31+
sortVariableDeclaration,
32+
defaultOptions,
33+
} from './sort-variable-declarations/sort-variable-declaration'
3134
import { buildCommonGroupsJsonSchemas } from '../utils/json-schemas/common-groups-json-schemas'
32-
import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
33-
import { computeDependencies } from './sort-variable-declarations/compute-dependencies'
34-
import { validateGroupsConfiguration } from '../utils/validate-groups-configuration'
35-
import { computeNodeName } from './sort-variable-declarations/compute-node-name'
36-
import { generatePredefinedGroups } from '../utils/generate-predefined-groups'
37-
import { sortNodesByDependencies } from '../utils/sort-nodes-by-dependencies'
38-
import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
39-
import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
40-
import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
41-
import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
35+
import { buildAstListeners } from '../utils/build-ast-listeners'
4236
import { createEslintRule } from '../utils/create-eslint-rule'
43-
import { reportAllErrors } from '../utils/report-all-errors'
44-
import { shouldPartition } from '../utils/should-partition'
45-
import { computeGroup } from '../utils/compute-group'
46-
import { rangeToDiff } from '../utils/range-to-diff'
47-
import { getSettings } from '../utils/get-settings'
48-
import { isSortable } from '../utils/is-sortable'
49-
import { complete } from '../utils/complete'
50-
51-
/**
52-
* Cache computed groups by modifiers and selectors for performance.
53-
*/
54-
let cachedGroupsByModifiersAndSelectors = new Map<string, string[]>()
55-
56-
const ORDER_ERROR_ID = 'unexpectedVariableDeclarationsOrder'
57-
const GROUP_ORDER_ERROR_ID = 'unexpectedVariableDeclarationsGroupOrder'
58-
const EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenVariableDeclarationsMembers'
59-
const MISSED_SPACING_ERROR_ID =
60-
'missedSpacingBetweenVariableDeclarationsMembers'
61-
const DEPENDENCY_ORDER_ERROR_ID =
62-
'unexpectedVariableDeclarationsDependencyOrder'
63-
64-
type MessageId =
65-
| typeof DEPENDENCY_ORDER_ERROR_ID
66-
| typeof MISSED_SPACING_ERROR_ID
67-
| typeof EXTRA_SPACING_ERROR_ID
68-
| typeof GROUP_ORDER_ERROR_ID
69-
| typeof ORDER_ERROR_ID
70-
71-
let defaultOptions: Required<Options[number]> = {
72-
useExperimentalDependencyDetection: true,
73-
fallbackSort: { type: 'unsorted' },
74-
newlinesInside: 'newlinesBetween',
75-
specialCharacters: 'keep',
76-
partitionByNewLine: false,
77-
partitionByComment: false,
78-
newlinesBetween: 'ignore',
79-
type: 'alphabetical',
80-
customGroups: [],
81-
ignoreCase: true,
82-
locales: 'en-US',
83-
alphabet: '',
84-
order: 'asc',
85-
groups: [],
86-
}
8737

8838
export default createEslintRule<Options, MessageId>({
89-
create: context => ({
90-
VariableDeclaration: variableDeclaration => {
91-
if (!isSortable(variableDeclaration.declarations)) {
92-
return
93-
}
94-
95-
let settings = getSettings(context.settings)
96-
let options = complete(context.options.at(0), settings, defaultOptions)
97-
98-
validateCustomSortConfiguration(options)
99-
validateNewlinesAndPartitionConfiguration(options)
100-
validateGroupsConfiguration({
101-
selectors: allSelectors,
102-
modifiers: [],
103-
options,
104-
})
105-
106-
let { sourceCode, id } = context
107-
let eslintDisabledLines = getEslintDisabledLines({
108-
ruleName: id,
109-
sourceCode,
110-
})
111-
let optionsByGroupIndexComputer =
112-
buildOptionsByGroupIndexComputer(options)
113-
114-
let sortingNodeGroups = variableDeclaration.declarations.reduce(
115-
(accumulator: SortVariableDeclarationsSortingNode[][], declaration) => {
116-
let name = computeNodeName({
117-
node: declaration,
118-
sourceCode,
119-
})
120-
121-
let selector: Selector =
122-
declaration.init ? 'initialized' : 'uninitialized'
123-
124-
let predefinedGroups = generatePredefinedGroups({
125-
cache: cachedGroupsByModifiersAndSelectors,
126-
selectors: [selector],
127-
modifiers: [],
128-
})
129-
130-
let lastSortingNode = accumulator.at(-1)?.at(-1)
131-
let sortingNode: Omit<
132-
SortVariableDeclarationsSortingNode,
133-
'partitionId'
134-
> = {
135-
group: computeGroup({
136-
customGroupMatcher: customGroup =>
137-
doesCustomGroupMatch({
138-
selectors: [selector],
139-
elementName: name,
140-
modifiers: [],
141-
customGroup,
142-
}),
143-
predefinedGroups,
144-
options,
145-
}),
146-
dependencies:
147-
options.useExperimentalDependencyDetection ?
148-
[]
149-
: computeDependencies(declaration),
150-
isEslintDisabled: isNodeEslintDisabled(
151-
declaration,
152-
eslintDisabledLines,
153-
),
154-
size: rangeToDiff(declaration, sourceCode),
155-
dependencyNames: [name],
156-
node: declaration,
157-
name,
158-
}
159-
160-
if (
161-
shouldPartition({
162-
lastSortingNode,
163-
sortingNode,
164-
sourceCode,
165-
options,
166-
})
167-
) {
168-
accumulator.push([])
169-
}
170-
171-
accumulator.at(-1)?.push({
172-
...sortingNode,
173-
partitionId: accumulator.length,
174-
})
175-
176-
return accumulator
177-
},
178-
[[]],
179-
)
180-
181-
if (options.useExperimentalDependencyDetection) {
182-
let dependenciesBySortingNode =
183-
computeDependenciesOutsideFunctionsBySortingNode({
184-
sortingNodes: sortingNodeGroups.flat(),
185-
sourceCode,
186-
})
187-
sortingNodeGroups = populateSortingNodeGroupsWithDependencies({
188-
dependenciesBySortingNode,
189-
sortingNodeGroups,
190-
})
191-
}
192-
let sortingNodes = sortingNodeGroups.flat()
193-
194-
reportAllErrors<MessageId>({
195-
availableMessageIds: {
196-
missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID,
197-
unexpectedDependencyOrder: DEPENDENCY_ORDER_ERROR_ID,
198-
extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID,
199-
unexpectedGroupOrder: GROUP_ORDER_ERROR_ID,
200-
unexpectedOrder: ORDER_ERROR_ID,
201-
},
202-
sortNodesExcludingEslintDisabled,
203-
nodes: sortingNodes,
204-
options,
205-
context,
206-
})
207-
208-
function sortNodesExcludingEslintDisabled(
209-
ignoreEslintDisabledNodes: boolean,
210-
): SortVariableDeclarationsSortingNode[] {
211-
let nodesSortedByGroups = sortingNodeGroups.flatMap(sortingNodeGroup =>
212-
sortNodesByGroups({
213-
comparatorByOptionsComputer: defaultComparatorByOptionsComputer,
214-
optionsByGroupIndexComputer,
215-
ignoreEslintDisabledNodes,
216-
nodes: sortingNodeGroup,
217-
groups: options.groups,
218-
}),
219-
)
220-
221-
return sortNodesByDependencies(nodesSortedByGroups, {
222-
ignoreEslintDisabledNodes,
223-
})
224-
}
225-
},
226-
}),
22739
meta: {
228-
schema: [
229-
{
40+
schema: {
41+
items: {
23042
properties: {
23143
...buildCommonJsonSchemas(),
23244
...buildCommonGroupsJsonSchemas({
23345
additionalCustomGroupMatchProperties:
23446
additionalCustomGroupMatchOptionsJsonSchema,
23547
}),
48+
useConfigurationIf: buildUseConfigurationIfJsonSchema({
49+
additionalProperties: {
50+
matchesAstSelector: matchesAstSelectorJsonSchema,
51+
},
52+
}),
23653
useExperimentalDependencyDetection:
23754
useExperimentalDependencyDetectionJsonSchema,
23855
partitionByComment: partitionByCommentJsonSchema,
@@ -241,7 +58,9 @@ export default createEslintRule<Options, MessageId>({
24158
additionalProperties: false,
24259
type: 'object',
24360
},
244-
],
61+
uniqueItems: true,
62+
type: 'array',
63+
},
24564
messages: {
24665
[DEPENDENCY_ORDER_ERROR_ID]: DEPENDENCY_ORDER_ERROR,
24766
[MISSED_SPACING_ERROR_ID]: MISSED_SPACING_ERROR,
@@ -257,6 +76,12 @@ export default createEslintRule<Options, MessageId>({
25776
type: 'suggestion',
25877
fixable: 'code',
25978
},
79+
create: context =>
80+
buildAstListeners({
81+
nodeTypes: [AST_NODE_TYPES.VariableDeclaration],
82+
sorter: sortVariableDeclaration,
83+
context,
84+
}),
26085
name: 'sort-variable-declarations',
26186
defaultOptions: [defaultOptions],
26287
})

0 commit comments

Comments
 (0)