Skip to content

Commit

Permalink
feat: add group-kind option on sort-named-imports and sort-named-exports
Browse files Browse the repository at this point in the history
  • Loading branch information
renato-bohler authored Mar 14, 2024
1 parent ec3d11c commit eb78461
Show file tree
Hide file tree
Showing 7 changed files with 826 additions and 20 deletions.
11 changes: 11 additions & 0 deletions docs/rules/sort-named-exports.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ This rule accepts an options object with the following properties:
interface Options {
type?: 'alphabetical' | 'natural' | 'line-length'
order?: 'asc' | 'desc'
'group-kind'?: 'mixed' | 'values-first' | 'types-first'
'ignore-case'?: boolean
}
```
Expand All @@ -88,6 +89,16 @@ interface Options {
- `asc` - enforce properties to be in ascending order.
- `desc` - enforce properties to be in descending order.

### group-kind

<sub>(default: `'mixed'`)</sub>

Allows to group named exports by their kind, with value exports coming either before or after type exports.

- `mixed` - does not group named exports by their kind
- `values-first` - groups all value exports before type exports
- `types-first` - groups all type exports before value exports

### ignore-case

<sub>(default: `false`)</sub>
Expand Down
11 changes: 11 additions & 0 deletions docs/rules/sort-named-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ This rule accepts an options object with the following properties:
interface Options {
type?: 'alphabetical' | 'natural' | 'line-length'
order?: 'asc' | 'desc'
'group-kind'?: 'mixed' | 'values-first' | 'types-first'
'ignore-case'?: boolean
'ignore-alias'?: boolean
}
Expand All @@ -105,6 +106,16 @@ interface Options {
- `asc` - enforce properties to be in ascending order.
- `desc` - enforce properties to be in descending order.

### group-kind

<sub>(default: `'mixed'`)</sub>

Allows to group named imports by their kind, with value imports coming either before or after type imports.

- `mixed` - does not group named imports by their kind
- `values-first` - groups all value imports before type imports
- `types-first` - groups all type imports before value imports

### ignore-case

<sub>(default: `false`)</sub>
Expand Down
44 changes: 36 additions & 8 deletions rules/sort-named-exports.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { SortingNode } from '../typings'

import { createEslintRule } from '../utils/create-eslint-rule'
import { SortOrder, GroupKind, SortType } from '../typings'
import { getGroupNumber } from '../utils/get-group-number'
import { rangeToDiff } from '../utils/range-to-diff'
import { isPositive } from '../utils/is-positive'
import { SortOrder, SortType } from '../typings'
import { sortNodes } from '../utils/sort-nodes'
import { makeFixes } from '../utils/make-fixes'
import { complete } from '../utils/complete'
Expand All @@ -14,6 +15,7 @@ type MESSAGE_ID = 'unexpectedNamedExportsOrder'

type Options = [
Partial<{
'group-kind': GroupKind
'ignore-case': boolean
order: SortOrder
type: SortType
Expand Down Expand Up @@ -52,6 +54,15 @@ export default createEslintRule<Options, MESSAGE_ID>({
type: 'boolean',
default: false,
},
'group-kind': {
enum: [
GroupKind.mixed,
GroupKind['values-first'],
GroupKind['types-first'],
],
default: GroupKind.mixed,
type: 'string',
},
},
additionalProperties: false,
},
Expand All @@ -74,16 +85,38 @@ export default createEslintRule<Options, MESSAGE_ID>({
type: SortType.alphabetical,
'ignore-case': false,
order: SortOrder.asc,
'group-kind': GroupKind.mixed,
})

let nodes: SortingNode[] = node.specifiers.map(specifier => ({
size: rangeToDiff(specifier.range),
name: specifier.local.name,
node: specifier,
group: specifier.exportKind,
}))

let shouldGroupByKind = options['group-kind'] !== GroupKind.mixed
let groupKindOrder =
options['group-kind'] === GroupKind['values-first']
? ['value', 'type']
: ['type', 'value']

pairwise(nodes, (left, right) => {
if (isPositive(compare(left, right, options))) {
let leftNum = getGroupNumber(groupKindOrder, left)
let rightNum = getGroupNumber(groupKindOrder, right)

if (
(shouldGroupByKind && leftNum > rightNum) ||
((!shouldGroupByKind || leftNum === rightNum) &&
isPositive(compare(left, right, options)))
) {
let sortedNodes = shouldGroupByKind
? groupKindOrder
.map(group => nodes.filter(n => n.group === group))
.map(groupedNodes => sortNodes(groupedNodes, options))
.flat()
: sortNodes(nodes, options)

context.report({
messageId: 'unexpectedNamedExportsOrder',
data: {
Expand All @@ -92,12 +125,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
},
node: right.node,
fix: fixer =>
makeFixes(
fixer,
nodes,
sortNodes(nodes, options),
context.sourceCode,
),
makeFixes(fixer, nodes, sortedNodes, context.sourceCode),
})
}
})
Expand Down
52 changes: 42 additions & 10 deletions rules/sort-named-imports.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { SortingNode } from '../typings'

import { createEslintRule } from '../utils/create-eslint-rule'
import { SortOrder, GroupKind, SortType } from '../typings'
import { getGroupNumber } from '../utils/get-group-number'
import { rangeToDiff } from '../utils/range-to-diff'
import { isPositive } from '../utils/is-positive'
import { SortOrder, SortType } from '../typings'
import { sortNodes } from '../utils/sort-nodes'
import { makeFixes } from '../utils/make-fixes'
import { complete } from '../utils/complete'
Expand All @@ -15,6 +16,7 @@ type MESSAGE_ID = 'unexpectedNamedImportsOrder'
type Options = [
Partial<{
'ignore-alias': boolean
'group-kind': GroupKind
'ignore-case': boolean
order: SortOrder
type: SortType
Expand Down Expand Up @@ -57,6 +59,15 @@ export default createEslintRule<Options, MESSAGE_ID>({
type: 'boolean',
default: false,
},
'group-kind': {
enum: [
GroupKind.mixed,
GroupKind['values-first'],
GroupKind['types-first'],
],
default: GroupKind.mixed,
type: 'string',
},
},
additionalProperties: false,
},
Expand Down Expand Up @@ -84,24 +95,50 @@ export default createEslintRule<Options, MESSAGE_ID>({
'ignore-alias': true,
'ignore-case': false,
order: SortOrder.asc,
'group-kind': GroupKind.mixed,
})

let nodes: SortingNode[] = specifiers.map(specifier => {
let group
let { name } = specifier.local

if (options['ignore-alias'] && specifier.type === 'ImportSpecifier') {
;({ name } = specifier.imported)
if (specifier.type === 'ImportSpecifier') {
if (options['ignore-alias']) {
;({ name } = specifier.imported)
}
group = specifier.importKind
}

return {
size: rangeToDiff(specifier.range),
node: specifier,
name,
group,
}
})

let shouldGroupByKind = options['group-kind'] !== GroupKind.mixed
let groupKindOrder =
options['group-kind'] === GroupKind['values-first']
? ['value', 'type']
: ['type', 'value']

pairwise(nodes, (left, right) => {
if (isPositive(compare(left, right, options))) {
let leftNum = getGroupNumber(groupKindOrder, left)
let rightNum = getGroupNumber(groupKindOrder, right)

if (
(shouldGroupByKind && leftNum > rightNum) ||
((!shouldGroupByKind || leftNum === rightNum) &&
isPositive(compare(left, right, options)))
) {
let sortedNodes = shouldGroupByKind
? groupKindOrder
.map(group => nodes.filter(n => n.group === group))
.map(groupedNodes => sortNodes(groupedNodes, options))
.flat()
: sortNodes(nodes, options)

context.report({
messageId: 'unexpectedNamedImportsOrder',
data: {
Expand All @@ -110,12 +147,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
},
node: right.node,
fix: fixer =>
makeFixes(
fixer,
nodes,
sortNodes(nodes, options),
context.sourceCode,
),
makeFixes(fixer, nodes, sortedNodes, context.sourceCode),
})
}
})
Expand Down
Loading

0 comments on commit eb78461

Please sign in to comment.