|
7 | 7 | import { getFileExtensions } from 'eslint-module-utils/ignore';
|
8 | 8 | import resolve from 'eslint-module-utils/resolve';
|
9 | 9 | import visit from 'eslint-module-utils/visit';
|
10 |
| -import { dirname, join } from 'path'; |
| 10 | +import { dirname, join, resolve as resolvePath } from 'path'; |
11 | 11 | import readPkgUp from 'eslint-module-utils/readPkgUp';
|
12 | 12 | import values from 'object.values';
|
13 | 13 | import includes from 'array-includes';
|
14 | 14 | import flatMap from 'array.prototype.flatmap';
|
15 | 15 |
|
| 16 | +import { walkSync } from '../core/fsWalk'; |
16 | 17 | import ExportMapBuilder from '../exportMap/builder';
|
17 | 18 | import recursivePatternCapture from '../exportMap/patternCapture';
|
18 | 19 | import docsUrl from '../docsUrl';
|
19 | 20 |
|
20 |
| -let FileEnumerator; |
21 |
| -let listFilesToProcess; |
| 21 | +/** |
| 22 | + * Attempt to load the internal `FileEnumerator` class, which has existed in a couple |
| 23 | + * of different places, depending on the version of `eslint`. Try requiring it from both |
| 24 | + * locations. |
| 25 | + * @returns Returns the `FileEnumerator` class if its requirable, otherwise `undefined`. |
| 26 | + */ |
| 27 | +function requireFileEnumerator() { |
| 28 | + let FileEnumerator; |
22 | 29 |
|
23 |
| -try { |
24 |
| - ({ FileEnumerator } = require('eslint/use-at-your-own-risk')); |
25 |
| -} catch (e) { |
| 30 | + // Try getting it from the eslint private / deprecated api |
26 | 31 | try {
|
27 |
| - // has been moved to eslint/lib/cli-engine/file-enumerator in version 6 |
28 |
| - ({ FileEnumerator } = require('eslint/lib/cli-engine/file-enumerator')); |
| 32 | + ({ FileEnumerator } = require('eslint/use-at-your-own-risk')); |
29 | 33 | } catch (e) {
|
| 34 | + // Absorb this if it's MODULE_NOT_FOUND |
| 35 | + if (e.code !== 'MODULE_NOT_FOUND') { |
| 36 | + throw e; |
| 37 | + } |
| 38 | + |
| 39 | + // If not there, then try getting it from eslint/lib/cli-engine/file-enumerator (moved there in v6) |
30 | 40 | try {
|
31 |
| - // eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3 |
32 |
| - const { listFilesToProcess: originalListFilesToProcess } = require('eslint/lib/util/glob-utils'); |
33 |
| - |
34 |
| - // Prevent passing invalid options (extensions array) to old versions of the function. |
35 |
| - // https://github.com/eslint/eslint/blob/v5.16.0/lib/util/glob-utils.js#L178-L280 |
36 |
| - // https://github.com/eslint/eslint/blob/v5.2.0/lib/util/glob-util.js#L174-L269 |
37 |
| - listFilesToProcess = function (src, extensions) { |
38 |
| - return originalListFilesToProcess(src, { |
39 |
| - extensions, |
40 |
| - }); |
41 |
| - }; |
| 41 | + ({ FileEnumerator } = require('eslint/lib/cli-engine/file-enumerator')); |
42 | 42 | } catch (e) {
|
43 |
| - const { listFilesToProcess: originalListFilesToProcess } = require('eslint/lib/util/glob-util'); |
| 43 | + // Absorb this if it's MODULE_NOT_FOUND |
| 44 | + if (e.code !== 'MODULE_NOT_FOUND') { |
| 45 | + throw e; |
| 46 | + } |
| 47 | + } |
| 48 | + } |
| 49 | + return FileEnumerator; |
| 50 | +} |
| 51 | + |
| 52 | +/** |
| 53 | + * |
| 54 | + * @param FileEnumerator the `FileEnumerator` class from `eslint`'s internal api |
| 55 | + * @param {string} src path to the src root |
| 56 | + * @param {string[]} extensions list of supported extensions |
| 57 | + * @returns {{ filename: string, ignored: boolean }[]} list of files to operate on |
| 58 | + */ |
| 59 | +function listFilesUsingFileEnumerator(FileEnumerator, src, extensions) { |
| 60 | + const e = new FileEnumerator({ |
| 61 | + extensions, |
| 62 | + }); |
| 63 | + |
| 64 | + return Array.from( |
| 65 | + e.iterateFiles(src), |
| 66 | + ({ filePath, ignored }) => ({ filename: filePath, ignored }), |
| 67 | + ); |
| 68 | +} |
44 | 69 |
|
45 |
| - listFilesToProcess = function (src, extensions) { |
46 |
| - const patterns = src.concat(flatMap(src, (pattern) => extensions.map((extension) => (/\*\*|\*\./).test(pattern) ? pattern : `${pattern}/**/*${extension}`))); |
| 70 | +/** |
| 71 | + * Attempt to require old versions of the file enumeration capability from v6 `eslint` and earlier, and use |
| 72 | + * those functions to provide the list of files to operate on |
| 73 | + * @param {string} src path to the src root |
| 74 | + * @param {string[]} extensions list of supported extensions |
| 75 | + * @returns {string[]} list of files to operate on |
| 76 | + */ |
| 77 | +function listFilesWithLegacyFunctions(src, extensions) { |
| 78 | + try { |
| 79 | + // eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3 |
| 80 | + const { listFilesToProcess: originalListFilesToProcess } = require('eslint/lib/util/glob-utils'); |
| 81 | + // Prevent passing invalid options (extensions array) to old versions of the function. |
| 82 | + // https://github.com/eslint/eslint/blob/v5.16.0/lib/util/glob-utils.js#L178-L280 |
| 83 | + // https://github.com/eslint/eslint/blob/v5.2.0/lib/util/glob-util.js#L174-L269 |
47 | 84 |
|
48 |
| - return originalListFilesToProcess(patterns); |
49 |
| - }; |
| 85 | + return originalListFilesToProcess(src, { |
| 86 | + extensions, |
| 87 | + }); |
| 88 | + } catch (e) { |
| 89 | + // Absorb this if it's MODULE_NOT_FOUND |
| 90 | + if (e.code !== 'MODULE_NOT_FOUND') { |
| 91 | + throw e; |
50 | 92 | }
|
| 93 | + |
| 94 | + // Last place to try (pre v5.3) |
| 95 | + const { |
| 96 | + listFilesToProcess: originalListFilesToProcess, |
| 97 | + } = require('eslint/lib/util/glob-util'); |
| 98 | + const patterns = src.concat( |
| 99 | + flatMap( |
| 100 | + src, |
| 101 | + (pattern) => extensions.map((extension) => (/\*\*|\*\./).test(pattern) ? pattern : `${pattern}/**/*${extension}`), |
| 102 | + ), |
| 103 | + ); |
| 104 | + |
| 105 | + return originalListFilesToProcess(patterns); |
51 | 106 | }
|
52 | 107 | }
|
53 | 108 |
|
54 |
| -if (FileEnumerator) { |
55 |
| - listFilesToProcess = function (src, extensions) { |
56 |
| - const e = new FileEnumerator({ |
57 |
| - extensions, |
| 109 | +/** |
| 110 | + * Given a source root and list of supported extensions, use fsWalk and the |
| 111 | + * new `eslint` `context.session` api to build the list of files we want to operate on |
| 112 | + * @param {string[]} srcPaths array of source paths (for flat config this should just be a singular root (e.g. cwd)) |
| 113 | + * @param {string[]} extensions list of supported extensions |
| 114 | + * @param {{ isDirectoryIgnored: (path: string) => boolean, isFileIgnored: (path: string) => boolean }} session eslint context session object |
| 115 | + * @returns {string[]} list of files to operate on |
| 116 | + */ |
| 117 | +function listFilesWithModernApi(srcPaths, extensions, session) { |
| 118 | + /** @type {string[]} */ |
| 119 | + const files = []; |
| 120 | + |
| 121 | + for (let i = 0; i < srcPaths.length; i++) { |
| 122 | + const src = srcPaths[i]; |
| 123 | + // Use walkSync along with the new session api to gather the list of files |
| 124 | + const entries = walkSync(src, { |
| 125 | + deepFilter(entry) { |
| 126 | + const fullEntryPath = resolvePath(src, entry.path); |
| 127 | + |
| 128 | + // Include the directory if it's not marked as ignore by eslint |
| 129 | + return !session.isDirectoryIgnored(fullEntryPath); |
| 130 | + }, |
| 131 | + entryFilter(entry) { |
| 132 | + const fullEntryPath = resolvePath(src, entry.path); |
| 133 | + |
| 134 | + // Include the file if it's not marked as ignore by eslint and its extension is included in our list |
| 135 | + return ( |
| 136 | + !session.isFileIgnored(fullEntryPath) |
| 137 | + && extensions.find((extension) => entry.path.endsWith(extension)) |
| 138 | + ); |
| 139 | + }, |
58 | 140 | });
|
59 | 141 |
|
60 |
| - return Array.from(e.iterateFiles(src), ({ filePath, ignored }) => ({ |
61 |
| - ignored, |
62 |
| - filename: filePath, |
63 |
| - })); |
64 |
| - }; |
| 142 | + // Filter out directories and map entries to their paths |
| 143 | + files.push( |
| 144 | + ...entries |
| 145 | + .filter((entry) => !entry.dirent.isDirectory()) |
| 146 | + .map((entry) => entry.path), |
| 147 | + ); |
| 148 | + } |
| 149 | + return files; |
| 150 | +} |
| 151 | + |
| 152 | +/** |
| 153 | + * Given a src pattern and list of supported extensions, return a list of files to process |
| 154 | + * with this rule. |
| 155 | + * @param {string} src - file, directory, or glob pattern of files to act on |
| 156 | + * @param {string[]} extensions - list of supported file extensions |
| 157 | + * @param {import('eslint').Rule.RuleContext} context - the eslint context object |
| 158 | + * @returns {string[] | { filename: string, ignored: boolean }[]} the list of files that this rule will evaluate. |
| 159 | + */ |
| 160 | +function listFilesToProcess(src, extensions, context) { |
| 161 | + // If the context object has the new session functions, then prefer those |
| 162 | + // Otherwise, fallback to using the deprecated `FileEnumerator` for legacy support. |
| 163 | + // https://github.com/eslint/eslint/issues/18087 |
| 164 | + if ( |
| 165 | + context.session |
| 166 | + && context.session.isFileIgnored |
| 167 | + && context.session.isDirectoryIgnored |
| 168 | + ) { |
| 169 | + return listFilesWithModernApi(src, extensions, context.session); |
| 170 | + } |
| 171 | + |
| 172 | + // Fallback to og FileEnumerator |
| 173 | + const FileEnumerator = requireFileEnumerator(); |
| 174 | + |
| 175 | + // If we got the FileEnumerator, then let's go with that |
| 176 | + if (FileEnumerator) { |
| 177 | + return listFilesUsingFileEnumerator(FileEnumerator, src, extensions); |
| 178 | + } |
| 179 | + // If not, then we can try even older versions of this capability (listFilesToProcess) |
| 180 | + return listFilesWithLegacyFunctions(src, extensions); |
65 | 181 | }
|
66 | 182 |
|
67 | 183 | const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration';
|
|
163 | 279 |
|
164 | 280 | const visitorKeyMap = new Map();
|
165 | 281 |
|
| 282 | +/** @type {Set<string>} */ |
166 | 283 | const ignoredFiles = new Set();
|
167 | 284 | const filesOutsideSrc = new Set();
|
168 | 285 |
|
|
172 | 289 | * read all files matching the patterns in src and ignoreExports
|
173 | 290 | *
|
174 | 291 | * return all files matching src pattern, which are not matching the ignoreExports pattern
|
| 292 | + * @type {(src: string, ignoreExports: string, context: import('eslint').Rule.RuleContext) => Set<string>} |
175 | 293 | */
|
176 |
| -const resolveFiles = (src, ignoreExports, context) => { |
| 294 | +function resolveFiles(src, ignoreExports, context) { |
177 | 295 | const extensions = Array.from(getFileExtensions(context.settings));
|
178 | 296 |
|
179 |
| - const srcFileList = listFilesToProcess(src, extensions); |
| 297 | + const srcFileList = listFilesToProcess(src, extensions, context); |
180 | 298 |
|
181 | 299 | // prepare list of ignored files
|
182 |
| - const ignoredFilesList = listFilesToProcess(ignoreExports, extensions); |
183 |
| - ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename)); |
| 300 | + const ignoredFilesList = listFilesToProcess(ignoreExports, extensions, context); |
| 301 | + |
| 302 | + // The modern api will return a list of file paths, rather than an object |
| 303 | + if (ignoredFilesList.length && typeof ignoredFilesList[0] === 'string') { |
| 304 | + ignoredFilesList.forEach((filename) => ignoredFiles.add(filename)); |
| 305 | + } else { |
| 306 | + ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename)); |
| 307 | + } |
184 | 308 |
|
185 | 309 | // prepare list of source files, don't consider files from node_modules
|
| 310 | + const resolvedFiles = srcFileList.length && typeof srcFileList[0] === 'string' |
| 311 | + ? srcFileList.filter((filePath) => !isNodeModule(filePath)) |
| 312 | + : flatMap(srcFileList, ({ filename }) => isNodeModule(filename) ? [] : filename); |
186 | 313 |
|
187 |
| - return new Set( |
188 |
| - flatMap(srcFileList, ({ filename }) => isNodeModule(filename) ? [] : filename), |
189 |
| - ); |
190 |
| -}; |
| 314 | + return new Set(resolvedFiles); |
| 315 | +} |
191 | 316 |
|
192 | 317 | /**
|
193 | 318 | * parse all source files and build up 2 maps containing the existing imports and exports
|
|
226 | 351 | } else {
|
227 | 352 | exports.set(key, { whereUsed: new Set() });
|
228 | 353 | }
|
229 |
| - const reexport = value.getImport(); |
| 354 | + const reexport = value.getImport(); |
230 | 355 | if (!reexport) {
|
231 | 356 | return;
|
232 | 357 | }
|
|
329 | 454 | * prepare the lists of existing imports and exports - should only be executed once at
|
330 | 455 | * the start of a new eslint run
|
331 | 456 | */
|
| 457 | +/** @type {Set<string>} */ |
332 | 458 | let srcFiles;
|
333 | 459 | let lastPrepareKey;
|
334 | 460 | const doPreparation = (src, ignoreExports, context) => {
|
|
0 commit comments