Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 60eb443

Browse files
authoredMar 16, 2025
feat: migrate minimatch to picomatch (#240)
1 parent 0449729 commit 60eb443

15 files changed

+159
-121
lines changed
 

‎.changeset/chilly-weeks-sit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": minor
3+
---
4+
5+
feat: migrate `minimatch` to `picomatch`

‎.gitignore

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,26 @@
11
# Logs
2-
logs
32
*.log
43

5-
# Runtime data
6-
pids
7-
*.pid
8-
*.seed
9-
10-
# Directory for instrumented libs generated by jscoverage/JSCover
11-
lib-cov
12-
134
# Coverage directory used by tools like istanbul
145
coverage
156

16-
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
17-
.grunt
18-
19-
# Compiled binary addons (https://nodejs.org/api/addons.html)
20-
build/Release
21-
227
# Dependency directory
23-
# Commenting this out is preferred by some people, see
24-
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
258
node_modules
269
!test/**/node_modules/
2710

28-
# Users Environment Variables
29-
.lock-wscript
30-
3111
# added by GitSavvy
3212
*.sublime-workspace
3313

3414
# generated output
35-
lib/
36-
**/.nyc_output/
15+
lib
3716

3817
# macOS
3918
.DS_Store
19+
20+
# Cache
4021
.*cache
4122

23+
# Yarn Berry
4224
.yarn/*
4325
!.yarn/patches
4426
!.yarn/plugins

‎package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@
5454
"doctrine": "^3.0.0",
5555
"eslint-import-resolver-node": "^0.3.9",
5656
"get-tsconfig": "^4.10.0",
57-
"is-glob": "^4.0.3",
58-
"minimatch": "^10.0.1",
57+
"picomatch": "^4.0.2",
5958
"rspack-resolver": "^1.1.0",
6059
"semver": "^7.7.1",
6160
"stable-hash": "^0.0.5",
@@ -90,19 +89,18 @@
9089
"@types/debug": "^4.1.12",
9190
"@types/eslint": "^9.6.1",
9291
"@types/eslint8.56": "npm:@types/eslint@~8.56.0",
93-
"@types/is-glob": "^4.0.4",
9492
"@types/jest": "^29.5.14",
9593
"@types/json-schema": "^7.0.15",
9694
"@types/klaw-sync": "^6.0.5",
9795
"@types/node": "^20.17.24",
96+
"@types/picomatch": "^3.0.2",
9897
"@types/pnpapi": "^0.0.5",
9998
"@typescript-eslint/eslint-plugin": "^8.26.1",
10099
"@typescript-eslint/parser": "^8.26.1",
101100
"@typescript-eslint/rule-tester": "^8.26.1",
102101
"@unts/patch-package": "^8.1.1",
103102
"clean-pkg-json": "^1.2.1",
104103
"cross-env": "^7.0.3",
105-
"escope": "^4.0.0",
106104
"eslint": "^9.22.0",
107105
"eslint-config-prettier": "^10.1.1",
108106
"eslint-doc-generator": "^2.1.1",

‎src/node-resolver.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,17 @@ export function createNodeResolver({
5353
return { found: true, path: null }
5454
}
5555

56+
/**
57+
* {@link https://github.com/webpack/enhanced-resolve/blob/38e9fd9acb79643a70e7bcd0d85dabc600ea321f/lib/PnpPlugin.js#L81-L83}
58+
*/
5659
if (process.versions.pnp && modulePath === 'pnpapi') {
5760
return {
5861
found: true,
5962
path: module
6063
.findPnpApi(sourceFile)
61-
.resolveToUnqualified(modulePath, null),
64+
.resolveToUnqualified(modulePath, sourceFile, {
65+
considerBuiltins: false,
66+
}),
6267
}
6368
}
6469

‎src/rules/no-extraneous-dependencies.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import fs from 'node:fs'
22
import path from 'node:path'
33

44
import type { TSESTree } from '@typescript-eslint/utils'
5-
import { minimatch } from 'minimatch'
65
import type { PackageJson } from 'type-fest'
76

87
import type { RuleContext } from '../types'
@@ -13,6 +12,7 @@ import {
1312
pkgUp,
1413
importType,
1514
getFilePackageName,
15+
isFileMatch,
1616
} from '../utils'
1717

1818
type PackageDeps = ReturnType<typeof extractDepFields>
@@ -329,16 +329,11 @@ function reportIfMissing(
329329
}
330330

331331
function testConfig(config: string[] | boolean | undefined, filename: string) {
332-
// Simplest configuration first, either a boolean or nothing.
333-
if (typeof config === 'boolean' || config === undefined) {
334-
return config
335-
}
336-
// Array of globs.
337-
return config.some(
338-
c =>
339-
minimatch(filename, c) ||
340-
minimatch(filename, path.resolve(c), { windowsPathsNoEscape: true }),
341-
)
332+
return Array.isArray(config)
333+
? // Array of globs.
334+
isFileMatch(filename, config)
335+
: // Simplest configuration first, either a boolean or nothing.
336+
config
342337
}
343338

344339
type Options = {

‎src/rules/no-import-module-exports.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import path from 'node:path'
22

33
import type { TSESLint, TSESTree } from '@typescript-eslint/utils'
4-
import { minimatch } from 'minimatch'
54

65
import type { RuleContext } from '../types'
7-
import { createRule, pkgUp } from '../utils'
6+
import { createRule, matcher, pkgUp } from '../utils'
87

98
function getEntryPoint(context: RuleContext) {
109
const pkgPath = pkgUp({
@@ -79,6 +78,8 @@ export = createRule<[Options?], MessageId>({
7978

8079
let alreadyReported = false
8180

81+
const isMatch = matcher(options.exceptions)
82+
8283
return {
8384
ImportDeclaration(node) {
8485
importDeclarations.push(node)
@@ -106,15 +107,12 @@ export = createRule<[Options?], MessageId>({
106107
const isImportBinding = variableDefinition?.type === 'ImportBinding'
107108
const hasCJSExportReference =
108109
hasKeywords && (!objectScope || objectScope.type === 'module')
109-
const isException = !!options.exceptions?.some(glob =>
110-
minimatch(filename, glob),
111-
)
112110

113111
if (
114112
isIdentifier &&
115113
hasCJSExportReference &&
116114
!isEntryPoint &&
117-
!isException &&
115+
!isMatch(filename) &&
118116
!isImportBinding
119117
) {
120118
for (const importDeclaration of importDeclarations) {

‎src/rules/no-internal-modules.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import { makeRe } from 'minimatch'
2-
3-
import { importType, createRule, moduleVisitor, resolve } from '../utils'
4-
5-
// minimatch patterns are expected to use / path separators, like import
1+
import {
2+
importType,
3+
createRule,
4+
moduleVisitor,
5+
resolve,
6+
matcher,
7+
} from '../utils'
8+
9+
// picomatch patterns are expected to use / path separators, like import
610
// statements, so normalize paths to use the same
711
function normalizeSep(somePath: string) {
812
return somePath.split('\\').join('/')
@@ -80,21 +84,21 @@ export = createRule<[Options?], MessageId>({
8084
defaultOptions: [],
8185
create(context) {
8286
const options = context.options[0] || {}
83-
const allowRegexps = (options.allow || [])
84-
.map(p => makeRe(p))
87+
const allowMatches = (options.allow || [])
88+
.map(p => matcher(p))
8589
.filter(Boolean)
86-
const forbidRegexps = (options.forbid || [])
87-
.map(p => makeRe(p))
90+
const forbidMatches = (options.forbid || [])
91+
.map(p => matcher(p))
8892
.filter(Boolean)
8993

9094
// test if reaching to this destination is allowed
9195
function reachingAllowed(importPath: string) {
92-
return allowRegexps.some(re => re.test(importPath))
96+
return allowMatches.some(m => m(importPath))
9397
}
9498

9599
// test if reaching to this destination is forbidden
96100
function reachingForbidden(importPath: string) {
97-
return forbidRegexps.some(re => re.test(importPath))
101+
return forbidMatches.some(m => m(importPath))
98102
}
99103

100104
function isAllowViolation(importPath: string) {

‎src/rules/no-namespace.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
*/
44

55
import type { TSESLint, TSESTree } from '@typescript-eslint/utils'
6-
import { minimatch } from 'minimatch'
76

8-
import { createRule } from '../utils'
7+
import { createRule, matcher } from '../utils'
98

109
type MessageId = 'noNamespace'
1110

@@ -45,17 +44,11 @@ export = createRule<[Options?], MessageId>({
4544
const firstOption = context.options[0] || {}
4645
const ignoreGlobs = firstOption.ignore
4746

47+
const isMatch = matcher(ignoreGlobs, { matchBase: true })
48+
4849
return {
4950
ImportNamespaceSpecifier(node) {
50-
if (
51-
ignoreGlobs?.find(glob =>
52-
minimatch(
53-
(node.parent as TSESTree.ImportDeclaration).source.value,
54-
glob,
55-
{ matchBase: true },
56-
),
57-
)
58-
) {
51+
if (isMatch(node.parent.source.value)) {
5952
return
6053
}
6154

‎src/rules/no-restricted-paths.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import path from 'node:path'
22

33
import type { TSESTree } from '@typescript-eslint/utils'
4-
import isGlob from 'is-glob'
5-
import { Minimatch } from 'minimatch'
64

75
import type { Arrayable } from '../types'
8-
import { importType, createRule, moduleVisitor, resolve } from '../utils'
6+
import {
7+
importType,
8+
createRule,
9+
moduleVisitor,
10+
resolve,
11+
isDynamicPattern,
12+
fileMatcher,
13+
isMatch,
14+
} from '../utils'
915

1016
const containsPath = (filepath: string, target: string) => {
1117
const relative = path.relative(target, filepath)
1218
return relative === '' || !relative.startsWith('..')
1319
}
1420

1521
function isMatchingTargetPath(filename: string, targetPath: string) {
16-
if (isGlob(targetPath)) {
17-
const mm = new Minimatch(targetPath, { windowsPathsNoEscape: true })
18-
return mm.match(filename)
22+
if (isDynamicPattern(targetPath)) {
23+
return isMatch(filename, targetPath)
1924
}
2025

2126
return containsPath(filename, targetPath)
@@ -171,17 +176,13 @@ export = createRule<[Options?], MessageId>({
171176
) {
172177
let isPathException: ((absoluteImportPath: string) => boolean) | undefined
173178

174-
const mm = new Minimatch(absoluteFrom, { windowsPathsNoEscape: true })
175-
const isPathRestricted = (absoluteImportPath: string) =>
176-
mm.match(absoluteImportPath)
177-
const hasValidExceptions = zoneExcept.every(it => isGlob(it))
179+
const isPathRestricted = fileMatcher(absoluteFrom)
180+
const hasValidExceptions = zoneExcept.every(it => isDynamicPattern(it))
178181

179182
if (hasValidExceptions) {
180-
const exceptionsMm = zoneExcept.map(
181-
except => new Minimatch(except, { windowsPathsNoEscape: true }),
182-
)
183+
const exceptionsMm = zoneExcept.map(except => fileMatcher(except))
183184
isPathException = (absoluteImportPath: string) =>
184-
exceptionsMm.some(mm => mm.match(absoluteImportPath))
185+
exceptionsMm.some(mm => mm(absoluteImportPath))
185186
}
186187

187188
const reportInvalidException = reportInvalidExceptionGlob
@@ -258,7 +259,7 @@ export = createRule<[Options?], MessageId>({
258259
zoneExcept: string[] = [],
259260
) => {
260261
const allZoneFrom = [zoneFrom].flat()
261-
const areGlobPatterns = allZoneFrom.map(it => isGlob(it))
262+
const areGlobPatterns = allZoneFrom.map(it => isDynamicPattern(it))
262263

263264
if (areBothGlobPatternAndAbsolutePath(areGlobPatterns)) {
264265
return [computeMixedGlobAndAbsolutePathValidator()]

‎src/rules/no-unassigned-import.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import path from 'node:path'
22

3-
import { minimatch } from 'minimatch'
4-
5-
import { isStaticRequire, createRule } from '../utils'
3+
import { isStaticRequire, createRule, isFileMatch } from '../utils'
64

75
function testIsAllow(
86
globs: string[] | undefined,
@@ -15,15 +13,11 @@ function testIsAllow(
1513

1614
const filePath =
1715
// a node module
18-
source[0] !== '.' && source[0] !== path.sep
16+
source[0] !== '.' && source[0] !== '/'
1917
? source
20-
: path.resolve(path.dirname(filename), source) // get source absolute path
18+
: path.resolve(filename, '..', source) // get source absolute path
2119

22-
return globs.some(
23-
glob =>
24-
minimatch(filePath, glob) ||
25-
minimatch(filePath, path.resolve(glob), { windowsPathsNoEscape: true }),
26-
)
20+
return isFileMatch(filePath, globs)
2721
}
2822

2923
type Options = {

‎src/rules/order.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { TSESLint, TSESTree } from '@typescript-eslint/utils'
2-
import { minimatch } from 'minimatch'
3-
import type { MinimatchOptions } from 'minimatch'
2+
import type { PicomatchOptions } from 'picomatch'
43

54
import type {
65
AlphabetizeOptions,
@@ -9,7 +8,7 @@ import type {
98
PathGroup,
109
RuleContext,
1110
} from '../types'
12-
import { importType, isStaticRequire, createRule } from '../utils'
11+
import { importType, isStaticRequire, createRule, isMatch } from '../utils'
1312

1413
type ImportEntryWithRank = {
1514
rank: number
@@ -502,7 +501,7 @@ type Ranks = {
502501
groups: Record<string, number>
503502
pathGroups: Array<{
504503
pattern: string
505-
patternOptions?: MinimatchOptions
504+
patternOptions?: PicomatchOptions
506505
group: string
507506
position?: number
508507
}>
@@ -519,7 +518,7 @@ function computePathRank(
519518
) {
520519
for (let i = 0, l = pathGroups.length; i < l; i++) {
521520
const { pattern, patternOptions, group, position = 1 } = pathGroups[i]
522-
if (minimatch(path, pattern, patternOptions || { nocomment: true })) {
521+
if (isMatch(path, pattern, patternOptions)) {
523522
return ranks[group] + position / maxPosition
524523
}
525524
}

0 commit comments

Comments
 (0)