@@ -2,11 +2,18 @@ import { AST_NODE_TYPES, ESLintUtils, type TSESTree, type TSESLint } from '@type
2
2
3
3
import { compare } from '../utils/compare.js'
4
4
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'
6
7
import { getGroupNumber } from '../utils/get-group-number.js'
7
8
import { getLinesBetween } from '../utils/get-lines-between.js'
9
+ import { getNewlineErrors } from '../utils/get-newline-errors.js'
8
10
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'
9
14
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'
10
17
import { sortNodes } from '../utils/sort-nodes.js'
11
18
import type { ImportDeclarationNode , Options , SortingNode } from '../utils/types.js'
12
19
@@ -22,13 +29,15 @@ export const IMPORT_GROUPS = [
22
29
'unknown' ,
23
30
] as const
24
31
32
+ type MessageId = 'out-of-order' | 'needs-newline' | 'extra-newline'
33
+
25
34
// eslint-disable-next-line new-cap
26
35
const createRule = ESLintUtils . RuleCreator (
27
36
( name ) =>
28
37
`https://github.com/stormwarning/eslint-plugin-import-sorting/blob/main/docs/rules/${ name } .md` ,
29
38
)
30
39
31
- export default createRule ( {
40
+ export default createRule < unknown [ ] , MessageId > ( {
32
41
name : 'order' ,
33
42
meta : {
34
43
type : 'suggestion' ,
@@ -53,6 +62,10 @@ export default createRule({
53
62
order : 'asc' ,
54
63
type : 'natural' ,
55
64
}
65
+ let eslintDisabledLines = getEslintDisabledLines ( {
66
+ ruleName : context . id ,
67
+ sourceCode,
68
+ } )
56
69
let nodes : SortingNode [ ] = [ ]
57
70
58
71
function registerNode ( node : ImportDeclarationNode ) {
@@ -63,19 +76,20 @@ export default createRule({
63
76
} else if ( node . type === AST_NODE_TYPES . TSImportEqualsDeclaration ) {
64
77
name =
65
78
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 )
69
81
} else {
70
82
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 ( )
73
85
}
74
86
75
87
nodes . push ( {
76
88
group : computeGroup ( node , settings , sourceCode ) ,
77
- node,
89
+ isEslintDisabled : isNodeEslintDisabled ( node , eslintDisabledLines ) ,
78
90
name,
91
+ node,
92
+ size : rangeToDiff ( node , sourceCode ) ,
79
93
} )
80
94
}
81
95
@@ -95,92 +109,161 @@ export default createRule({
95
109
} ,
96
110
// eslint-disable-next-line @typescript-eslint/naming-convention
97
111
'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
106
128
107
- let splittedNodes : SortingNode [ ] [ ] = [ [ ] ]
129
+ let formattedNodes : SortingNode [ ] [ ] = [ [ ] ]
108
130
109
131
for ( let node of nodes ) {
110
- let lastNode = splittedNodes . at ( - 1 ) ?. at ( - 1 )
132
+ let lastNode = formattedNodes . at ( - 1 ) ?. at ( - 1 )
111
133
112
134
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 ] )
114
141
} else {
115
- splittedNodes . at ( - 1 ) ! . push ( node )
142
+ formattedNodes . at ( - 1 ) ! . push ( node )
116
143
}
117
144
}
118
145
119
- for ( let nodeList of splittedNodes ) {
146
+ for ( let nodeList of formattedNodes ) {
147
+ let sortedNodes = sortNodesByGroups ( nodeList , options , { } )
120
148
pairwise ( nodeList , ( left , right ) => {
121
149
let leftNumber = getGroupNumber ( IMPORT_GROUPS , left )
122
150
let rightNumber = getGroupNumber ( IMPORT_GROUPS , right )
151
+ let indexOfLeft = sortedNodes . indexOf ( left )
152
+ let indexOfRight = sortedNodes . indexOf ( right )
123
153
124
- let numberOfEmptyLinesBetween = getLinesBetween ( sourceCode , left , right )
154
+ let messages : MessageId [ ] = [ ]
125
155
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
+ )
144
160
}
145
161
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 ) {
147
177
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
+ ] ,
149
195
data : {
150
- left : left . name ,
196
+ rightGroup : right . group ,
197
+ leftGroup : left . group ,
151
198
right : right . name ,
199
+ left : left . name ,
152
200
} ,
153
201
node : right . node ,
154
- fix : ( fixer ) => fix ( fixer , nodeList , sourceCode , options ) ,
202
+ messageId : message ,
155
203
} )
156
204
}
157
205
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
+ // }
184
267
} )
185
268
}
186
269
} ,
0 commit comments