Skip to content

Commit c71d953

Browse files
feat(extensions): support fix and pathGroupOverrides options
1 parent de7bae3 commit c71d953

File tree

1 file changed

+76
-12
lines changed

1 file changed

+76
-12
lines changed

src/rules/extensions.ts

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
moduleVisitor,
1010
resolve,
1111
} from '../utils/index.js'
12+
import { minimatch } from 'minimatch'
1213

1314
const modifierValues = ['always', 'ignorePackages', 'never'] as const
1415

@@ -32,6 +33,22 @@ const properties = {
3233
checkTypeImports: {
3334
type: 'boolean' as const,
3435
},
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+
},
3552
},
3653
}
3754

@@ -43,11 +60,21 @@ export interface OptionsItemWithPatternProperty {
4360
ignorePackages?: boolean
4461
checkTypeImports?: boolean
4562
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'
4671
}
4772

4873
export interface OptionsItemWithoutPatternProperty {
4974
ignorePackages?: boolean
5075
checkTypeImports?: boolean
76+
fix?: boolean
77+
pathGroupOverrides?: PathGroupOverride[]
5178
}
5279

5380
export type Options =
@@ -63,6 +90,8 @@ export interface NormalizedOptions {
6390
pattern?: Record<string, Modifier>
6491
ignorePackages?: boolean
6592
checkTypeImports?: boolean
93+
fix?: boolean
94+
pathGroupOverrides?: PathGroupOverride[]
6695
}
6796

6897
export type MessageId = 'missing' | 'missingKnown' | 'unexpected'
@@ -73,6 +102,8 @@ function buildProperties(context: RuleContext<MessageId, Options>) {
73102
pattern: {},
74103
ignorePackages: false,
75104
checkTypeImports: false,
105+
fix: false,
106+
pathGroupOverrides: [],
76107
}
77108

78109
for (const obj of context.options) {
@@ -109,6 +140,14 @@ function buildProperties(context: RuleContext<MessageId, Options>) {
109140
if (typeof obj.checkTypeImports === 'boolean') {
110141
result.checkTypeImports = obj.checkTypeImports
111142
}
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+
}
112151
}
113152

114153
if (result.defaultConfig === 'ignorePackages') {
@@ -124,14 +163,15 @@ function isExternalRootModule(file: string) {
124163
return false
125164
}
126165
const slashCount = file.split('/').length - 1
166+
return slashCount === 0 || (isScoped(file) && slashCount <= 1)
167+
}
127168

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+
}
133174
}
134-
return false
135175
}
136176

137177
export default createRule<Options, MessageId>({
@@ -143,6 +183,7 @@ export default createRule<Options, MessageId>({
143183
description:
144184
'Ensure consistent use of file extension within the import path.',
145185
},
186+
fixable: 'code',
146187
schema: {
147188
anyOf: [
148189
{
@@ -220,17 +261,22 @@ export default createRule<Options, MessageId>({
220261
}
221262

222263
const importPathWithQueryString = source.value
264+
const overrideAction = computeOverrideAction(
265+
props.pathGroupOverrides || [],
266+
importPathWithQueryString,
267+
)
268+
if (overrideAction === 'ignore') return
223269

224270
// don't enforce anything on builtins
225-
if (isBuiltIn(importPathWithQueryString, context.settings)) {
271+
if (!overrideAction && isBuiltIn(importPathWithQueryString, context.settings)) {
226272
return
227273
}
228274

229275
const importPath = importPathWithQueryString.replace(/\?(.*)$/, '')
230276

231277
// don't enforce in root external packages as they may have names with `.js`.
232278
// Like `import Decimal from decimal.js`)
233-
if (isExternalRootModule(importPath)) {
279+
if (!overrideAction && isExternalRootModule(importPath)) {
234280
return
235281
}
236282

@@ -261,7 +307,7 @@ export default createRule<Options, MessageId>({
261307
}
262308
const extensionRequired = isUseOfExtensionRequired(
263309
extension,
264-
isPackage,
310+
!overrideAction && isPackage,
265311
)
266312
const extensionForbidden = isUseOfExtensionForbidden(extension)
267313
if (extensionRequired && !extensionForbidden) {
@@ -272,6 +318,16 @@ export default createRule<Options, MessageId>({
272318
extension,
273319
importPath: importPathWithQueryString,
274320
},
321+
...(props.fix && extension
322+
? {
323+
fix(fixer) {
324+
return fixer.replaceText(
325+
source,
326+
JSON.stringify(`${importPathWithQueryString}.${extension}`),
327+
)
328+
},
329+
}
330+
: {}),
275331
})
276332
}
277333
} else if (
@@ -286,10 +342,18 @@ export default createRule<Options, MessageId>({
286342
extension,
287343
importPath: importPathWithQueryString,
288344
},
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+
: {}),
289355
})
290356
}
291-
},
292-
{ commonjs: true },
293-
)
357+
}, { commonjs: true })
294358
},
295359
})

0 commit comments

Comments
 (0)