Skip to content

Commit 6d576f3

Browse files
committed
fix(no-unused-modules): don't error out when running with flat config and an eslintrc isn't present
This change adjusts how we're instantiating the FileEnumerator from eslint's unsupported api, in the case that the user is running with flat config. We have to turn off the `useEslintrc` property on the ConfigArrayFactory that's passed into the FileEnumerator's constructor. Note: This doesn't fix the fact that the FileEnumerator doesn't have knowledge of what the user's config is ignoring, it just prevents the rule from looking for a legacy / rc config and erroring out. FileEnumerator used the rc config to understand which files to ignore.
1 parent f0727a6 commit 6d576f3

File tree

2 files changed

+72
-117
lines changed

2 files changed

+72
-117
lines changed

src/core/fsWalk.js

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/rules/no-unused-modules.js

Lines changed: 72 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ import { getPhysicalFilename } from 'eslint-module-utils/contextCompat';
88
import { getFileExtensions } from 'eslint-module-utils/ignore';
99
import resolve from 'eslint-module-utils/resolve';
1010
import visit from 'eslint-module-utils/visit';
11-
import { dirname, join, resolve as resolvePath } from 'path';
11+
import { dirname, join } from 'path';
1212
import readPkgUp from 'eslint-module-utils/readPkgUp';
1313
import values from 'object.values';
1414
import includes from 'array-includes';
1515
import flatMap from 'array.prototype.flatmap';
1616

17-
import { walkSync } from '../core/fsWalk';
1817
import ExportMapBuilder from '../exportMap/builder';
1918
import recursivePatternCapture from '../exportMap/patternCapture';
2019
import docsUrl from '../docsUrl';
@@ -51,21 +50,81 @@ function requireFileEnumerator() {
5150
}
5251

5352
/**
54-
*
53+
* Given a FileEnumerator class, instantiate and load the list of files.
5554
* @param FileEnumerator the `FileEnumerator` class from `eslint`'s internal api
5655
* @param {string} src path to the src root
5756
* @param {string[]} extensions list of supported extensions
5857
* @returns {{ filename: string, ignored: boolean }[]} list of files to operate on
5958
*/
6059
function listFilesUsingFileEnumerator(FileEnumerator, src, extensions) {
61-
const e = new FileEnumerator({
62-
extensions,
63-
});
60+
// We need to know whether this is being run with flat config in order to
61+
// determine whether we need to customize the FileEnumerator or not.
62+
63+
// This condition is sufficient to test in v8, since the environment variable
64+
// is necessary to turn on flat config
65+
let isUsingFlatConfig = process.env.ESLINT_USE_FLAT_CONFIG
66+
&& process.env.ESLINT_USE_FLAT_CONFIG !== 'false';
67+
68+
// In the case of using v9, we can check the `shouldUseFlatConfig` function
69+
// If this function is present, then we assume it's v9
70+
try {
71+
const { shouldUseFlatConfig } = require('eslint/use-at-your-own-risk');
72+
isUsingFlatConfig = shouldUseFlatConfig();
73+
} catch (_) {
74+
// We don't want to throw here, since we only want to update the
75+
// boolean if the function is available.
76+
}
77+
78+
let enumerator;
79+
if (isUsingFlatConfig) {
80+
// If this run is using flat config, then we need to create an instance of `CascadingConfigArrayFactory`
81+
// with eslintrc turned off, to pass to the `FileEnumerator`.
82+
// This prevents the FileEnumerator from looking for an eslintrc file in parent directories.
83+
// https://github.com/import-js/eslint-plugin-import/issues/3079
84+
try {
85+
const {
86+
Legacy: { CascadingConfigArrayFactory },
87+
} = require('@eslint/eslintrc'); // eslint-disable-line import/no-extraneous-dependencies
88+
89+
const configArrayFactory = new CascadingConfigArrayFactory({
90+
// These are the same as what `FileEnumerator` would normally pass in
91+
cwd: process.cwd(),
92+
// eslint-disable-next-line import/no-extraneous-dependencies
93+
getEslintRecommendedConfig: () => require('@eslint/js').configs.recommended,
94+
// eslint-disable-next-line import/no-extraneous-dependencies
95+
getEslintAllConfig: () => require('@eslint/js').configs.all,
96+
97+
// This is what we're doing differently for flat config use
98+
useEslintrc: false,
99+
});
100+
101+
enumerator = new FileEnumerator({
102+
configArrayFactory,
103+
extensions,
104+
});
105+
} catch (e) {
106+
// Absorb this if we weren't able to require the config array factory
107+
if (e.code !== 'MODULE_NOT_FOUND') {
108+
throw e;
109+
}
110+
// If we can't load the config array factory, we'll just do our best
111+
// and create the enumerator without turning off eslintrc.
112+
// This will ultimately result in an error if the project doesn't have an
113+
// eslintrc file in a parent directory.
114+
enumerator = new FileEnumerator({
115+
extensions,
116+
});
117+
}
118+
} else {
119+
enumerator = new FileEnumerator({
120+
extensions,
121+
});
122+
}
64123

65-
return Array.from(
66-
e.iterateFiles(src),
67-
({ filePath, ignored }) => ({ filename: filePath, ignored }),
68-
);
124+
return Array.from(enumerator.iterateFiles(src), ({ filePath, ignored }) => ({
125+
filename: filePath,
126+
ignored,
127+
}));
69128
}
70129

71130
/**
@@ -107,70 +166,14 @@ function listFilesWithLegacyFunctions(src, extensions) {
107166
}
108167
}
109168

110-
/**
111-
* Given a source root and list of supported extensions, use fsWalk and the
112-
* new `eslint` `context.session` api to build the list of files we want to operate on
113-
* @param {string[]} srcPaths array of source paths (for flat config this should just be a singular root (e.g. cwd))
114-
* @param {string[]} extensions list of supported extensions
115-
* @param {{ isDirectoryIgnored: (path: string) => boolean, isFileIgnored: (path: string) => boolean }} session eslint context session object
116-
* @returns {string[]} list of files to operate on
117-
*/
118-
function listFilesWithModernApi(srcPaths, extensions, session) {
119-
/** @type {string[]} */
120-
const files = [];
121-
122-
for (let i = 0; i < srcPaths.length; i++) {
123-
const src = srcPaths[i];
124-
// Use walkSync along with the new session api to gather the list of files
125-
const entries = walkSync(src, {
126-
deepFilter(entry) {
127-
const fullEntryPath = resolvePath(src, entry.path);
128-
129-
// Include the directory if it's not marked as ignore by eslint
130-
return !session.isDirectoryIgnored(fullEntryPath);
131-
},
132-
entryFilter(entry) {
133-
const fullEntryPath = resolvePath(src, entry.path);
134-
135-
// Include the file if it's not marked as ignore by eslint and its extension is included in our list
136-
return (
137-
!session.isFileIgnored(fullEntryPath)
138-
&& extensions.find((extension) => entry.path.endsWith(extension))
139-
);
140-
},
141-
});
142-
143-
// Filter out directories and map entries to their paths
144-
files.push(
145-
...entries
146-
.filter((entry) => !entry.dirent.isDirectory())
147-
.map((entry) => entry.path),
148-
);
149-
}
150-
return files;
151-
}
152-
153169
/**
154170
* Given a src pattern and list of supported extensions, return a list of files to process
155171
* with this rule.
156172
* @param {string} src - file, directory, or glob pattern of files to act on
157173
* @param {string[]} extensions - list of supported file extensions
158-
* @param {import('eslint').Rule.RuleContext} context - the eslint context object
159174
* @returns {string[] | { filename: string, ignored: boolean }[]} the list of files that this rule will evaluate.
160175
*/
161-
function listFilesToProcess(src, extensions, context) {
162-
// If the context object has the new session functions, then prefer those
163-
// Otherwise, fallback to using the deprecated `FileEnumerator` for legacy support.
164-
// https://github.com/eslint/eslint/issues/18087
165-
if (
166-
context.session
167-
&& context.session.isFileIgnored
168-
&& context.session.isDirectoryIgnored
169-
) {
170-
return listFilesWithModernApi(src, extensions, context.session);
171-
}
172-
173-
// Fallback to og FileEnumerator
176+
function listFilesToProcess(src, extensions) {
174177
const FileEnumerator = requireFileEnumerator();
175178

176179
// If we got the FileEnumerator, then let's go with that
@@ -295,10 +298,10 @@ const isNodeModule = (path) => (/\/(node_modules)\//).test(path);
295298
function resolveFiles(src, ignoreExports, context) {
296299
const extensions = Array.from(getFileExtensions(context.settings));
297300

298-
const srcFileList = listFilesToProcess(src, extensions, context);
301+
const srcFileList = listFilesToProcess(src, extensions);
299302

300303
// prepare list of ignored files
301-
const ignoredFilesList = listFilesToProcess(ignoreExports, extensions, context);
304+
const ignoredFilesList = listFilesToProcess(ignoreExports, extensions);
302305

303306
// The modern api will return a list of file paths, rather than an object
304307
if (ignoredFilesList.length && typeof ignoredFilesList[0] === 'string') {

0 commit comments

Comments
 (0)