Skip to content

Commit f6bc787

Browse files
committed
Update order rule types and utils
1 parent 4b55cfc commit f6bc787

File tree

7 files changed

+285
-113
lines changed

7 files changed

+285
-113
lines changed

src/rules/order.ts

Lines changed: 153 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@ import { AST_NODE_TYPES, ESLintUtils, type TSESTree, type TSESLint } from '@type
22

33
import { compare } from '../utils/compare.js'
44
import { computeGroup, isSideEffectImport } from '../utils/compute-group.js'
5-
import { getCommentBefore } from '../utils/get-comment.js'
5+
import { getCommentsBefore } from '../utils/get-comment.js'
6+
import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines.js'
67
import { getGroupNumber } from '../utils/get-group-number.js'
78
import { getLinesBetween } from '../utils/get-lines-between.js'
9+
import { getNewlineErrors } from '../utils/get-newline-errors.js'
810
import { getNodeRange } from '../utils/get-node-range.js'
11+
import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled.js'
12+
import { makeFixes } from '../utils/make-fixes.js'
13+
import { makeNewlineFixes } from '../utils/make-newline-fixes.js'
914
import { pairwise } from '../utils/pairwise.js'
15+
import { rangeToDiff } from '../utils/range-to-diff.js'
16+
import { sortNodesByGroups } from '../utils/sort-nodes-by-groups.js'
1017
import { sortNodes } from '../utils/sort-nodes.js'
1118
import type { ImportDeclarationNode, Options, SortingNode } from '../utils/types.js'
1219

@@ -22,13 +29,15 @@ export const IMPORT_GROUPS = [
2229
'unknown',
2330
] as const
2431

32+
type MessageId = 'out-of-order' | 'needs-newline' | 'extra-newline'
33+
2534
// eslint-disable-next-line new-cap
2635
const createRule = ESLintUtils.RuleCreator(
2736
(name) =>
2837
`https://github.com/stormwarning/eslint-plugin-import-sorting/blob/main/docs/rules/${name}.md`,
2938
)
3039

31-
export default createRule({
40+
export default createRule<unknown[], MessageId>({
3241
name: 'order',
3342
meta: {
3443
type: 'suggestion',
@@ -53,6 +62,10 @@ export default createRule({
5362
order: 'asc',
5463
type: 'natural',
5564
}
65+
let eslintDisabledLines = getEslintDisabledLines({
66+
ruleName: context.id,
67+
sourceCode,
68+
})
5669
let nodes: SortingNode[] = []
5770

5871
function registerNode(node: ImportDeclarationNode) {
@@ -63,19 +76,20 @@ export default createRule({
6376
} else if (node.type === AST_NODE_TYPES.TSImportEqualsDeclaration) {
6477
name =
6578
node.moduleReference.type === AST_NODE_TYPES.TSExternalModuleReference
66-
? // @ts-expect-error -- `value` is not in the type definition.
67-
`${node.moduleReference.expression.value}`
68-
: sourceCode.text.slice(...node.moduleReference.range)
79+
? node.moduleReference.expression.value
80+
: sourceCode.getText(node.moduleReference)
6981
} else {
7082
let decl = node.declarations[0].init as TSESTree.CallExpression
71-
let declValue = (decl.arguments[0] as TSESTree.Literal).value
72-
name = declValue!.toString()
83+
let { value } = decl.arguments[0] as TSESTree.Literal
84+
name = value!.toString()
7385
}
7486

7587
nodes.push({
7688
group: computeGroup(node, settings, sourceCode),
77-
node,
89+
isEslintDisabled: isNodeEslintDisabled(node, eslintDisabledLines),
7890
name,
91+
node,
92+
size: rangeToDiff(node, sourceCode),
7993
})
8094
}
8195

@@ -95,92 +109,161 @@ export default createRule({
95109
},
96110
// eslint-disable-next-line @typescript-eslint/naming-convention
97111
'Program:exit'() {
98-
let hasContentBetweenNodes = (left: SortingNode, right: SortingNode): boolean =>
99-
sourceCode.getTokensBetween(
100-
left.node,
101-
getCommentBefore(right.node, sourceCode) ?? right.node,
102-
{
103-
includeComments: true,
104-
},
105-
).length > 0
112+
function hasContentBetweenNodes(left: SortingNode, right: SortingNode): boolean {
113+
return (
114+
sourceCode.getTokensBetween(left.node, right.node, {
115+
includeComments: false,
116+
}).length > 0
117+
)
118+
}
119+
//
120+
// let hasContentBetweenNodes = (left: SortingNode, right: SortingNode): boolean =>
121+
// sourceCode.getTokensBetween(
122+
// left.node,
123+
// getCommentBefore(right.node, sourceCode) ?? right.node,
124+
// {
125+
// includeComments: true,
126+
// },
127+
// ).length > 0
106128

107-
let splittedNodes: SortingNode[][] = [[]]
129+
let formattedNodes: SortingNode[][] = [[]]
108130

109131
for (let node of nodes) {
110-
let lastNode = splittedNodes.at(-1)?.at(-1)
132+
let lastNode = formattedNodes.at(-1)?.at(-1)
111133

112134
if (lastNode && hasContentBetweenNodes(lastNode, node)) {
113-
splittedNodes.push([node])
135+
/**
136+
* Including `node` in this empty array allows groups
137+
* of imports separated by other statements to be
138+
* sorted, but may break other aspects.
139+
*/
140+
formattedNodes.push([node])
114141
} else {
115-
splittedNodes.at(-1)!.push(node)
142+
formattedNodes.at(-1)!.push(node)
116143
}
117144
}
118145

119-
for (let nodeList of splittedNodes) {
146+
for (let nodeList of formattedNodes) {
147+
let sortedNodes = sortNodesByGroups(nodeList, options, {})
120148
pairwise(nodeList, (left, right) => {
121149
let leftNumber = getGroupNumber(IMPORT_GROUPS, left)
122150
let rightNumber = getGroupNumber(IMPORT_GROUPS, right)
151+
let indexOfLeft = sortedNodes.indexOf(left)
152+
let indexOfRight = sortedNodes.indexOf(right)
123153

124-
let numberOfEmptyLinesBetween = getLinesBetween(sourceCode, left, right)
154+
let messages: MessageId[] = []
125155

126-
if (
127-
!(
128-
isSideEffectImport(left.node, sourceCode) &&
129-
isSideEffectImport(right.node, sourceCode)
130-
) &&
131-
!hasContentBetweenNodes(left, right) &&
132-
(leftNumber > rightNumber ||
133-
(leftNumber === rightNumber && compare(left, right, options) > 0))
134-
) {
135-
context.report({
136-
messageId: 'out-of-order',
137-
data: {
138-
left: left.name,
139-
right: right.name,
140-
},
141-
node: right.node,
142-
fix: (fixer) => fix(fixer, nodeList, sourceCode, options),
143-
})
156+
if (indexOfLeft > indexOfRight) {
157+
messages.push(
158+
leftNumber === rightNumber ? 'out-of-order' : 'out-of-order',
159+
)
144160
}
145161

146-
if (options.newlinesBetween === 'never' && numberOfEmptyLinesBetween > 0) {
162+
messages = [
163+
...messages,
164+
...getNewlineErrors({
165+
missingLineError: 'needs-newline',
166+
extraLineError: 'extra-newline',
167+
left,
168+
leftNumber,
169+
right,
170+
rightNumber,
171+
sourceCode,
172+
options,
173+
}),
174+
]
175+
176+
for (let message of messages) {
147177
context.report({
148-
messageId: 'extra-newline',
178+
fix: (fixer) => [
179+
...makeFixes({
180+
fixer,
181+
nodes: nodeList,
182+
sortedNodes,
183+
sourceCode,
184+
//
185+
// options,
186+
}),
187+
...makeNewlineFixes({
188+
fixer,
189+
nodes: nodeList,
190+
sortedNodes,
191+
sourceCode,
192+
options,
193+
}),
194+
],
149195
data: {
150-
left: left.name,
196+
rightGroup: right.group,
197+
leftGroup: left.group,
151198
right: right.name,
199+
left: left.name,
152200
},
153201
node: right.node,
154-
fix: (fixer) => fix(fixer, nodeList, sourceCode, options),
202+
messageId: message,
155203
})
156204
}
157205

158-
if (options.newlinesBetween === 'always') {
159-
if (leftNumber < rightNumber && numberOfEmptyLinesBetween === 0) {
160-
context.report({
161-
messageId: 'needs-newline',
162-
data: {
163-
left: left.name,
164-
right: right.name,
165-
},
166-
node: right.node,
167-
fix: (fixer) => fix(fixer, nodeList, sourceCode, options),
168-
})
169-
} else if (
170-
numberOfEmptyLinesBetween > 1 ||
171-
(leftNumber === rightNumber && numberOfEmptyLinesBetween > 0)
172-
) {
173-
context.report({
174-
messageId: 'extra-newline',
175-
data: {
176-
left: left.name,
177-
right: right.name,
178-
},
179-
node: right.node,
180-
fix: (fixer) => fix(fixer, nodeList, sourceCode, options),
181-
})
182-
}
183-
}
206+
//
207+
// let numberOfEmptyLinesBetween = getLinesBetween(sourceCode, left, right)
208+
//
209+
// if (
210+
// !(
211+
// isSideEffectImport(left.node, sourceCode) &&
212+
// isSideEffectImport(right.node, sourceCode)
213+
// ) &&
214+
// !hasContentBetweenNodes(left, right) &&
215+
// (leftNumber > rightNumber ||
216+
// (leftNumber === rightNumber && compare(left, right, options) > 0))
217+
// ) {
218+
// context.report({
219+
// messageId: 'out-of-order',
220+
// data: {
221+
// left: left.name,
222+
// right: right.name,
223+
// },
224+
// node: right.node,
225+
// fix: (fixer) => fix(fixer, nodeList, sourceCode, options),
226+
// })
227+
// }
228+
//
229+
// if (options.newlinesBetween === 'never' && numberOfEmptyLinesBetween > 0) {
230+
// context.report({
231+
// messageId: 'extra-newline',
232+
// data: {
233+
// left: left.name,
234+
// right: right.name,
235+
// },
236+
// node: right.node,
237+
// fix: (fixer) => fix(fixer, nodeList, sourceCode, options),
238+
// })
239+
// }
240+
//
241+
// if (options.newlinesBetween === 'always') {
242+
// if (leftNumber < rightNumber && numberOfEmptyLinesBetween === 0) {
243+
// context.report({
244+
// messageId: 'needs-newline',
245+
// data: {
246+
// left: left.name,
247+
// right: right.name,
248+
// },
249+
// node: right.node,
250+
// fix: (fixer) => fix(fixer, nodeList, sourceCode, options),
251+
// })
252+
// } else if (
253+
// numberOfEmptyLinesBetween > 1 ||
254+
// (leftNumber === rightNumber && numberOfEmptyLinesBetween > 0)
255+
// ) {
256+
// context.report({
257+
// messageId: 'extra-newline',
258+
// data: {
259+
// left: left.name,
260+
// right: right.name,
261+
// },
262+
// node: right.node,
263+
// fix: (fixer) => fix(fixer, nodeList, sourceCode, options),
264+
// })
265+
// }
266+
// }
184267
})
185268
}
186269
},

src/utils/compare.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
import type { SortingNode } from './types.js'
22

3-
interface BaseCompareOptions {
3+
interface BaseCompareOptions<T extends SortingNode> {
44
/**
55
* Custom function to get the value of the node. By default, returns the
66
* node's name.
77
*/
8-
nodeValueGetter?: (node: SortingNode) => string
8+
nodeValueGetter?: (node: T) => string
99
order: 'desc' | 'asc'
1010
}
1111

12-
interface NaturalCompareOptions extends BaseCompareOptions {
12+
interface NaturalCompareOptions<T extends SortingNode> extends BaseCompareOptions<T> {
1313
ignoreCase?: boolean
1414
type: 'natural'
1515
}
1616

17-
export type CompareOptions = NaturalCompareOptions
17+
export type CompareOptions<T extends SortingNode> = NaturalCompareOptions<T>
1818

19-
export function compare(a: SortingNode, b: SortingNode, options: CompareOptions): number {
20-
/** Don't sort unsassigned imports. */
19+
export function compare<T extends SortingNode>(a: T, b: T, options: CompareOptions<T>): number {
20+
/** Don't sort unassigned imports. */
2121
if (a.group === 'unassigned' || b.group === 'unassigned') return 0
2222

2323
if (b.dependencies?.includes(a.name)) return -1
2424
if (a.dependencies?.includes(b.name)) return 1
2525

2626
let orderCoefficient = options.order === 'asc' ? 1 : -1
27-
let sortingFunction: (a: SortingNode, b: SortingNode) => number
27+
let sortingFunction: (a: T, b: T) => number
2828

29-
let nodeValueGetter = options.nodeValueGetter ?? ((node: SortingNode) => node.name)
29+
let nodeValueGetter = options.nodeValueGetter ?? ((node: T) => node.name)
3030

3131
sortingFunction = (aNode, bNode) => {
3232
let aImport = stripProtocol(nodeValueGetter(aNode))

0 commit comments

Comments
 (0)