Skip to content

Commit 1da4043

Browse files
SukkaWJounQincoderabbitai[bot]Copilot
authored
refactor: make no-unused-modules no-op on ESLint 10 or later (#457)
Signed-off-by: JounQin <admin@1stg.me> Co-authored-by: JounQin <admin@1stg.me> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent d801fd7 commit 1da4043

2 files changed

Lines changed: 58 additions & 5 deletions

File tree

‎.changeset/clever-meals-fly.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": patch
3+
---
4+
5+
Make the `no-unused-modules` rule no-op on ESLint 10 or later for now before we can implement an alternative. A warning message about this behavior is added, and can be suppressed with the new `suppressMissingFileEnumeratorAPIWarning` rule option (`import-x/no-unused-modules: ['error', { suppressMissingFileEnumeratorAPIWarning: true }]`).

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

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,29 @@ import { TSESTree } from '@typescript-eslint/types'
99
import type { TSESLint } from '@typescript-eslint/utils'
1010
// eslint-disable-next-line import-x/default -- incorrect types , commonjs actually
1111
import eslintUnsupportedApi from 'eslint/use-at-your-own-risk'
12+
import type * as ESLint9UnsupportedApi from 'eslint9/use-at-your-own-risk'
13+
14+
function isESLint9UnsupportedApi(
15+
input: unknown,
16+
): input is typeof ESLint9UnsupportedApi {
17+
return (
18+
typeof input === 'object' &&
19+
input !== null &&
20+
'shouldUseFlatConfig' in input &&
21+
'FileEnumerator' in eslintUnsupportedApi
22+
)
23+
}
24+
25+
function ensureESLint9UnsupportedApi(
26+
input: unknown,
27+
): typeof ESLint9UnsupportedApi {
28+
if (!isESLint9UnsupportedApi(input)) {
29+
throw new TypeError(
30+
'ESLint 10 and later versions remove the FileEnumerator API, which is required by the "no-unused-modules" rule.',
31+
)
32+
}
33+
return input
34+
}
1235

1336
import type { FileExtension, RuleContext } from '../types.js'
1437
import {
@@ -22,13 +45,13 @@ import {
2245
getValue,
2346
} from '../utils/index.js'
2447

25-
// eslint-disable-next-line import-x/no-named-as-default-member -- incorrect types , commonjs actually
26-
const { FileEnumerator, shouldUseFlatConfig } = eslintUnsupportedApi
27-
2848
function listFilesUsingFileEnumerator(
2949
src: string[],
3050
extensions: FileExtension[],
3151
) {
52+
const { FileEnumerator, shouldUseFlatConfig } =
53+
ensureESLint9UnsupportedApi(eslintUnsupportedApi)
54+
3255
// We need to know whether this is being run with flat config in order to
3356
// determine how to report errors if FileEnumerator throws due to a lack of eslintrc.
3457

@@ -41,7 +64,8 @@ function listFilesUsingFileEnumerator(
4164
// If this function is present, then we assume it's v9
4265
try {
4366
isUsingFlatConfig =
44-
// @ts-expect-error -- only available in ESLint v9
67+
// This allows us to avoid accessing the removed APIs as long as the function is not invoked.
68+
// @ts-expect-error -- only exists in ESLint 9, and we only try to ensure this function exists
4569
shouldUseFlatConfig && ESLINT_USE_FLAT_CONFIG !== 'false'
4670
} catch {
4771
// We don't want to throw here, since we only want to init the
@@ -50,7 +74,9 @@ function listFilesUsingFileEnumerator(
5074
!!ESLINT_USE_FLAT_CONFIG && ESLINT_USE_FLAT_CONFIG !== 'false'
5175
}
5276

53-
const enumerator = new FileEnumerator({ extensions })
77+
const enumerator = new FileEnumerator({
78+
extensions,
79+
})
5480

5581
try {
5682
return Array.from(
@@ -447,10 +473,13 @@ export interface Options {
447473
missingExports?: true
448474
unusedExports?: boolean
449475
ignoreUnusedTypeExports?: boolean
476+
suppressMissingFileEnumeratorAPIWarning?: boolean
450477
}
451478

452479
type MessageId = 'notFound' | 'unused'
453480

481+
let eslint10WarningEmitted = false
482+
454483
export default createRule<Options[], MessageId>({
455484
name: 'no-unused-modules',
456485
meta: {
@@ -495,6 +524,11 @@ export default createRule<Options[], MessageId>({
495524
description: 'ignore type exports without any usage',
496525
type: 'boolean',
497526
},
527+
suppressMissingFileEnumeratorAPIWarning: {
528+
description:
529+
'ESLint 10 and later versions remove the FileEnumerator API, which is required by the "no-unused-modules" rule. Therefore a warning message will be logged before an alternative is implemented. You can suppress the warning with this option',
530+
type: 'boolean',
531+
},
498532
},
499533
anyOf: [
500534
{
@@ -537,8 +571,22 @@ export default createRule<Options[], MessageId>({
537571
missingExports,
538572
unusedExports,
539573
ignoreUnusedTypeExports,
574+
suppressMissingFileEnumeratorAPIWarning,
540575
} = context.options[0] || {}
541576

577+
// ESLint 10 removes shouldUseFlatConfig and FileEnumerator
578+
if (!isESLint9UnsupportedApi(eslintUnsupportedApi)) {
579+
if (!suppressMissingFileEnumeratorAPIWarning && !eslint10WarningEmitted) {
580+
eslint10WarningEmitted = true
581+
582+
console.warn(`
583+
ESLint removes the FileEnumerator API since ESLint 10, which is required by the "no-unused-modules" rule. Therefore, the "no-unused-modules" rule is no-op for now before we can implement an alternative.
584+
In the meantime, if you want to keep this rule enabled, you can suppress this warning with the "suppressMissingFileEnumeratorAPIWarning" rule option: \`import-x/no-unused-modules: ['error', { suppressMissingFileEnumeratorAPIWarning: true }]\`
585+
`)
586+
}
587+
return {}
588+
}
589+
542590
if (unusedExports) {
543591
doPreparation(src, ignoreExports, context)
544592
}

0 commit comments

Comments
 (0)