9
9
moduleVisitor ,
10
10
resolve ,
11
11
} from '../utils/index.js'
12
+ import { minimatch } from 'minimatch'
12
13
13
14
const modifierValues = [ 'always' , 'ignorePackages' , 'never' ] as const
14
15
@@ -32,6 +33,22 @@ const properties = {
32
33
checkTypeImports : {
33
34
type : 'boolean' as const ,
34
35
} ,
36
+ pathGroupOverrides : {
37
+ type : 'array' as const ,
38
+ items : {
39
+ type : 'object' as const ,
40
+ properties : {
41
+ pattern : { type : 'string' as const } ,
42
+ patternOptions : { type : 'object' as const } ,
43
+ action : {
44
+ type : 'string' as const ,
45
+ enum : [ 'enforce' , 'ignore' ] ,
46
+ } ,
47
+ } ,
48
+ additionalProperties : false ,
49
+ required : [ 'pattern' , 'action' ] ,
50
+ } ,
51
+ } ,
35
52
} ,
36
53
}
37
54
@@ -43,11 +60,21 @@ export interface OptionsItemWithPatternProperty {
43
60
ignorePackages ?: boolean
44
61
checkTypeImports ?: boolean
45
62
pattern : ModifierByFileExtension
63
+ fix ?: boolean
64
+ pathGroupOverrides ?: PathGroupOverride [ ]
65
+ }
66
+
67
+ export interface PathGroupOverride {
68
+ pattern : string
69
+ patternOptions ?: Record < string , any >
70
+ action : 'enforce' | 'ignore'
46
71
}
47
72
48
73
export interface OptionsItemWithoutPatternProperty {
49
74
ignorePackages ?: boolean
50
75
checkTypeImports ?: boolean
76
+ fix ?: boolean
77
+ pathGroupOverrides ?: PathGroupOverride [ ]
51
78
}
52
79
53
80
export type Options =
@@ -63,6 +90,8 @@ export interface NormalizedOptions {
63
90
pattern ?: Record < string , Modifier >
64
91
ignorePackages ?: boolean
65
92
checkTypeImports ?: boolean
93
+ fix ?: boolean
94
+ pathGroupOverrides ?: PathGroupOverride [ ]
66
95
}
67
96
68
97
export type MessageId = 'missing' | 'missingKnown' | 'unexpected'
@@ -73,6 +102,8 @@ function buildProperties(context: RuleContext<MessageId, Options>) {
73
102
pattern : { } ,
74
103
ignorePackages : false ,
75
104
checkTypeImports : false ,
105
+ fix : false ,
106
+ pathGroupOverrides : [ ] ,
76
107
}
77
108
78
109
for ( const obj of context . options ) {
@@ -109,6 +140,14 @@ function buildProperties(context: RuleContext<MessageId, Options>) {
109
140
if ( typeof obj . checkTypeImports === 'boolean' ) {
110
141
result . checkTypeImports = obj . checkTypeImports
111
142
}
143
+
144
+ if ( 'fix' in obj ) {
145
+ result . fix = Boolean ( obj . fix )
146
+ }
147
+
148
+ if ( 'pathGroupOverrides' in obj && Array . isArray ( obj . pathGroupOverrides ) ) {
149
+ result . pathGroupOverrides = obj . pathGroupOverrides
150
+ }
112
151
}
113
152
114
153
if ( result . defaultConfig === 'ignorePackages' ) {
@@ -124,14 +163,15 @@ function isExternalRootModule(file: string) {
124
163
return false
125
164
}
126
165
const slashCount = file . split ( '/' ) . length - 1
166
+ return slashCount === 0 || ( isScoped ( file ) && slashCount <= 1 )
167
+ }
127
168
128
- if ( slashCount === 0 ) {
129
- return true
130
- }
131
- if ( isScoped ( file ) && slashCount <= 1 ) {
132
- return true
169
+ function computeOverrideAction ( overrides : PathGroupOverride [ ] , path : string ) {
170
+ for ( const { pattern , patternOptions , action } of overrides ) {
171
+ if ( minimatch ( path , pattern , patternOptions || { nocomment : true } ) ) {
172
+ return action
173
+ }
133
174
}
134
- return false
135
175
}
136
176
137
177
export default createRule < Options , MessageId > ( {
@@ -143,6 +183,7 @@ export default createRule<Options, MessageId>({
143
183
description :
144
184
'Ensure consistent use of file extension within the import path.' ,
145
185
} ,
186
+ fixable : 'code' ,
146
187
schema : {
147
188
anyOf : [
148
189
{
@@ -220,17 +261,22 @@ export default createRule<Options, MessageId>({
220
261
}
221
262
222
263
const importPathWithQueryString = source . value
264
+ const overrideAction = computeOverrideAction (
265
+ props . pathGroupOverrides || [ ] ,
266
+ importPathWithQueryString ,
267
+ )
268
+ if ( overrideAction === 'ignore' ) return
223
269
224
270
// don't enforce anything on builtins
225
- if ( isBuiltIn ( importPathWithQueryString , context . settings ) ) {
271
+ if ( ! overrideAction && isBuiltIn ( importPathWithQueryString , context . settings ) ) {
226
272
return
227
273
}
228
274
229
275
const importPath = importPathWithQueryString . replace ( / \? ( .* ) $ / , '' )
230
276
231
277
// don't enforce in root external packages as they may have names with `.js`.
232
278
// Like `import Decimal from decimal.js`)
233
- if ( isExternalRootModule ( importPath ) ) {
279
+ if ( ! overrideAction && isExternalRootModule ( importPath ) ) {
234
280
return
235
281
}
236
282
@@ -261,7 +307,7 @@ export default createRule<Options, MessageId>({
261
307
}
262
308
const extensionRequired = isUseOfExtensionRequired (
263
309
extension ,
264
- isPackage ,
310
+ ! overrideAction && isPackage ,
265
311
)
266
312
const extensionForbidden = isUseOfExtensionForbidden ( extension )
267
313
if ( extensionRequired && ! extensionForbidden ) {
@@ -272,6 +318,16 @@ export default createRule<Options, MessageId>({
272
318
extension,
273
319
importPath : importPathWithQueryString ,
274
320
} ,
321
+ ...( props . fix && extension
322
+ ? {
323
+ fix ( fixer ) {
324
+ return fixer . replaceText (
325
+ source ,
326
+ JSON . stringify ( `${ importPathWithQueryString } .${ extension } ` ) ,
327
+ )
328
+ } ,
329
+ }
330
+ : { } ) ,
275
331
} )
276
332
}
277
333
} else if (
@@ -286,10 +342,18 @@ export default createRule<Options, MessageId>({
286
342
extension,
287
343
importPath : importPathWithQueryString ,
288
344
} ,
345
+ ...( props . fix
346
+ ? {
347
+ fix ( fixer ) {
348
+ return fixer . replaceText (
349
+ source ,
350
+ JSON . stringify ( importPath . slice ( 0 , - ( extension . length + 1 ) ) ) ,
351
+ )
352
+ } ,
353
+ }
354
+ : { } ) ,
289
355
} )
290
356
}
291
- } ,
292
- { commonjs : true } ,
293
- )
357
+ } , { commonjs : true } )
294
358
} ,
295
359
} )
0 commit comments