| 1 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|---|
| 2 | // See LICENSE in the project root for license information.
|
|---|
| 3 | // This is a workaround for https://github.com/eslint/eslint/issues/3458
|
|---|
| 4 | //
|
|---|
| 5 | // To correct how ESLint searches for plugin packages, add this line to the top of your project's .eslintrc.js file:
|
|---|
| 6 | //
|
|---|
| 7 | // require("@rushstack/eslint-patch/modern-module-resolution");
|
|---|
| 8 | //
|
|---|
| 9 | import path from 'node:path';
|
|---|
| 10 | const isModuleResolutionError = (ex) => typeof ex === 'object' && !!ex && 'code' in ex && ex.code === 'MODULE_NOT_FOUND';
|
|---|
| 11 | const FLAT_CONFIG_REGEX = /eslint\.config\.(cjs|mjs|js)$/i;
|
|---|
| 12 | // Ex:
|
|---|
| 13 | // at async ESLint.lintFiles (C:\\path\\to\\\\eslint\\lib\\eslint\\eslint.js:720:21)
|
|---|
| 14 | const NODE_STACK_REGEX = /^\s*at (?:((?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(.*?)(?::(\d+)| (\d+))(?::(\d+))?\)?\s*$/i;
|
|---|
| 15 | function parseNodeStack(stack) {
|
|---|
| 16 | const stackTraceMatch = NODE_STACK_REGEX.exec(stack);
|
|---|
| 17 | if (!stackTraceMatch) {
|
|---|
| 18 | return undefined;
|
|---|
| 19 | }
|
|---|
| 20 | return {
|
|---|
| 21 | file: stackTraceMatch[2],
|
|---|
| 22 | method: stackTraceMatch[1],
|
|---|
| 23 | lineNumber: parseInt(stackTraceMatch[3], 10),
|
|---|
| 24 | column: stackTraceMatch[4] ? parseInt(stackTraceMatch[4], 10) : undefined
|
|---|
| 25 | };
|
|---|
| 26 | }
|
|---|
| 27 | function getStackTrace() {
|
|---|
| 28 | const stackObj = {};
|
|---|
| 29 | const originalStackTraceLimit = Error.stackTraceLimit;
|
|---|
| 30 | Error.stackTraceLimit = Infinity;
|
|---|
| 31 | Error.captureStackTrace(stackObj, getStackTrace);
|
|---|
| 32 | Error.stackTraceLimit = originalStackTraceLimit;
|
|---|
| 33 | if (!stackObj.stack) {
|
|---|
| 34 | throw new Error('Unable to capture stack trace');
|
|---|
| 35 | }
|
|---|
| 36 | const { stack } = stackObj;
|
|---|
| 37 | const stackLines = stack.split('\n');
|
|---|
| 38 | const frames = [];
|
|---|
| 39 | for (const line of stackLines) {
|
|---|
| 40 | const frame = parseNodeStack(line);
|
|---|
| 41 | if (frame) {
|
|---|
| 42 | frames.push(frame);
|
|---|
| 43 | }
|
|---|
| 44 | }
|
|---|
| 45 | return frames;
|
|---|
| 46 | }
|
|---|
| 47 | // Module path for eslintrc.cjs
|
|---|
| 48 | // Example: ".../@eslint/eslintrc/dist/eslintrc.cjs"
|
|---|
| 49 | let eslintrcBundlePath = undefined;
|
|---|
| 50 | // Module path for config-array-factory.js
|
|---|
| 51 | // Example: ".../@eslint/eslintrc/lib/config-array-factory"
|
|---|
| 52 | let configArrayFactoryPath = undefined;
|
|---|
| 53 | // Module path for relative-module-resolver.js
|
|---|
| 54 | // Example: ".../@eslint/eslintrc/lib/shared/relative-module-resolver"
|
|---|
| 55 | let moduleResolverPath = undefined;
|
|---|
| 56 | // Module path for naming.js
|
|---|
| 57 | // Example: ".../@eslint/eslintrc/lib/shared/naming"
|
|---|
| 58 | let namingPath = undefined;
|
|---|
| 59 | // Folder path where ESLint's package.json can be found
|
|---|
| 60 | // Example: ".../node_modules/eslint"
|
|---|
| 61 | let eslintFolder = undefined;
|
|---|
| 62 | // Probe for the ESLint >=9.0.0 flat config layout:
|
|---|
| 63 | for (let currentModule = module;;) {
|
|---|
| 64 | if (FLAT_CONFIG_REGEX.test(currentModule.filename)) {
|
|---|
| 65 | // Obtain the stack trace of the current module, since the
|
|---|
| 66 | // parent module of a flat config is undefined. From the
|
|---|
| 67 | // stack trace, we can find the ESLint folder.
|
|---|
| 68 | const stackTrace = getStackTrace();
|
|---|
| 69 | const targetFrame = stackTrace.find((frame) => frame.file && frame.file.endsWith('eslint.js'));
|
|---|
| 70 | if (targetFrame) {
|
|---|
| 71 | // Walk up the path and continuously attempt to resolve the ESLint folder
|
|---|
| 72 | let currentPath = targetFrame.file;
|
|---|
| 73 | while (currentPath) {
|
|---|
| 74 | const potentialPath = path.dirname(currentPath);
|
|---|
| 75 | if (potentialPath === currentPath) {
|
|---|
| 76 | break;
|
|---|
| 77 | }
|
|---|
| 78 | currentPath = potentialPath;
|
|---|
| 79 | try {
|
|---|
| 80 | eslintFolder = path.dirname(require.resolve('eslint/package.json', { paths: [currentPath] }));
|
|---|
| 81 | break;
|
|---|
| 82 | }
|
|---|
| 83 | catch (ex) {
|
|---|
| 84 | if (!isModuleResolutionError(ex)) {
|
|---|
| 85 | throw ex;
|
|---|
| 86 | }
|
|---|
| 87 | }
|
|---|
| 88 | }
|
|---|
| 89 | }
|
|---|
| 90 | if (eslintFolder) {
|
|---|
| 91 | const eslintrcFolderPath = path.dirname(require.resolve('@eslint/eslintrc/package.json', { paths: [eslintFolder] }));
|
|---|
| 92 | eslintrcBundlePath = path.join(eslintrcFolderPath, 'dist/eslintrc.cjs');
|
|---|
| 93 | }
|
|---|
| 94 | break;
|
|---|
| 95 | }
|
|---|
| 96 | if (!currentModule.parent) {
|
|---|
| 97 | break;
|
|---|
| 98 | }
|
|---|
| 99 | currentModule = currentModule.parent;
|
|---|
| 100 | }
|
|---|
| 101 | if (!eslintFolder) {
|
|---|
| 102 | // Probe for the ESLint >=8.0.0 layout:
|
|---|
| 103 | for (let currentModule = module;;) {
|
|---|
| 104 | if (!eslintrcBundlePath) {
|
|---|
| 105 | if (currentModule.filename.endsWith('eslintrc.cjs')) {
|
|---|
| 106 | // For ESLint >=8.0.0, all @eslint/eslintrc code is bundled at this path:
|
|---|
| 107 | // .../@eslint/eslintrc/dist/eslintrc.cjs
|
|---|
| 108 | try {
|
|---|
| 109 | const eslintrcFolderPath = path.dirname(require.resolve('@eslint/eslintrc/package.json', { paths: [currentModule.path] }));
|
|---|
| 110 | // Make sure we actually resolved the module in our call path
|
|---|
| 111 | // and not some other spurious dependency.
|
|---|
| 112 | const resolvedEslintrcBundlePath = path.join(eslintrcFolderPath, 'dist/eslintrc.cjs');
|
|---|
| 113 | if (resolvedEslintrcBundlePath === currentModule.filename) {
|
|---|
| 114 | eslintrcBundlePath = resolvedEslintrcBundlePath;
|
|---|
| 115 | }
|
|---|
| 116 | }
|
|---|
| 117 | catch (ex) {
|
|---|
| 118 | // Module resolution failures are expected, as we're walking
|
|---|
| 119 | // up our require stack to look for eslint. All other errors
|
|---|
| 120 | // are re-thrown.
|
|---|
| 121 | if (!isModuleResolutionError(ex)) {
|
|---|
| 122 | throw ex;
|
|---|
| 123 | }
|
|---|
| 124 | }
|
|---|
| 125 | }
|
|---|
| 126 | }
|
|---|
| 127 | else {
|
|---|
| 128 | // Next look for a file in ESLint's folder
|
|---|
| 129 | // .../eslint/lib/cli-engine/cli-engine.js
|
|---|
| 130 | try {
|
|---|
| 131 | const eslintCandidateFolder = path.dirname(require.resolve('eslint/package.json', {
|
|---|
| 132 | paths: [currentModule.path]
|
|---|
| 133 | }));
|
|---|
| 134 | // Make sure we actually resolved the module in our call path
|
|---|
| 135 | // and not some other spurious dependency.
|
|---|
| 136 | if (currentModule.filename.startsWith(eslintCandidateFolder + path.sep)) {
|
|---|
| 137 | eslintFolder = eslintCandidateFolder;
|
|---|
| 138 | break;
|
|---|
| 139 | }
|
|---|
| 140 | }
|
|---|
| 141 | catch (ex) {
|
|---|
| 142 | // Module resolution failures are expected, as we're walking
|
|---|
| 143 | // up our require stack to look for eslint. All other errors
|
|---|
| 144 | // are re-thrown.
|
|---|
| 145 | if (!isModuleResolutionError(ex)) {
|
|---|
| 146 | throw ex;
|
|---|
| 147 | }
|
|---|
| 148 | }
|
|---|
| 149 | }
|
|---|
| 150 | if (!currentModule.parent) {
|
|---|
| 151 | break;
|
|---|
| 152 | }
|
|---|
| 153 | currentModule = currentModule.parent;
|
|---|
| 154 | }
|
|---|
| 155 | }
|
|---|
| 156 | if (!eslintFolder) {
|
|---|
| 157 | // Probe for the ESLint >=7.12.0 layout:
|
|---|
| 158 | for (let currentModule = module;;) {
|
|---|
| 159 | if (!configArrayFactoryPath) {
|
|---|
| 160 | // For ESLint >=7.12.0, config-array-factory.js is at this path:
|
|---|
| 161 | // .../@eslint/eslintrc/lib/config-array-factory.js
|
|---|
| 162 | try {
|
|---|
| 163 | const eslintrcFolder = path.dirname(require.resolve('@eslint/eslintrc/package.json', {
|
|---|
| 164 | paths: [currentModule.path]
|
|---|
| 165 | }));
|
|---|
| 166 | const resolvedConfigArrayFactoryPath = path.join(eslintrcFolder, '/lib/config-array-factory.js');
|
|---|
| 167 | if (resolvedConfigArrayFactoryPath === currentModule.filename) {
|
|---|
| 168 | configArrayFactoryPath = resolvedConfigArrayFactoryPath;
|
|---|
| 169 | moduleResolverPath = `${eslintrcFolder}/lib/shared/relative-module-resolver`;
|
|---|
| 170 | namingPath = `${eslintrcFolder}/lib/shared/naming`;
|
|---|
| 171 | }
|
|---|
| 172 | }
|
|---|
| 173 | catch (ex) {
|
|---|
| 174 | // Module resolution failures are expected, as we're walking
|
|---|
| 175 | // up our require stack to look for eslint. All other errors
|
|---|
| 176 | // are re-thrown.
|
|---|
| 177 | if (!isModuleResolutionError(ex)) {
|
|---|
| 178 | throw ex;
|
|---|
| 179 | }
|
|---|
| 180 | }
|
|---|
| 181 | }
|
|---|
| 182 | else if (currentModule.filename.endsWith('cli-engine.js')) {
|
|---|
| 183 | // Next look for a file in ESLint's folder
|
|---|
| 184 | // .../eslint/lib/cli-engine/cli-engine.js
|
|---|
| 185 | try {
|
|---|
| 186 | const eslintCandidateFolder = path.dirname(require.resolve('eslint/package.json', {
|
|---|
| 187 | paths: [currentModule.path]
|
|---|
| 188 | }));
|
|---|
| 189 | if (path.join(eslintCandidateFolder, 'lib/cli-engine/cli-engine.js') === currentModule.filename) {
|
|---|
| 190 | eslintFolder = eslintCandidateFolder;
|
|---|
| 191 | break;
|
|---|
| 192 | }
|
|---|
| 193 | }
|
|---|
| 194 | catch (ex) {
|
|---|
| 195 | // Module resolution failures are expected, as we're walking
|
|---|
| 196 | // up our require stack to look for eslint. All other errors
|
|---|
| 197 | // are rethrown.
|
|---|
| 198 | if (!isModuleResolutionError(ex)) {
|
|---|
| 199 | throw ex;
|
|---|
| 200 | }
|
|---|
| 201 | }
|
|---|
| 202 | }
|
|---|
| 203 | if (!currentModule.parent) {
|
|---|
| 204 | break;
|
|---|
| 205 | }
|
|---|
| 206 | currentModule = currentModule.parent;
|
|---|
| 207 | }
|
|---|
| 208 | }
|
|---|
| 209 | if (!eslintFolder) {
|
|---|
| 210 | // Probe for the <7.12.0 layout:
|
|---|
| 211 | for (let currentModule = module;;) {
|
|---|
| 212 | // For ESLint <7.12.0, config-array-factory.js was at this path:
|
|---|
| 213 | // .../eslint/lib/cli-engine/config-array-factory.js
|
|---|
| 214 | if (/[\\/]eslint[\\/]lib[\\/]cli-engine[\\/]config-array-factory\.js$/i.test(currentModule.filename)) {
|
|---|
| 215 | eslintFolder = path.join(path.dirname(currentModule.filename), '../..');
|
|---|
| 216 | configArrayFactoryPath = `${eslintFolder}/lib/cli-engine/config-array-factory`;
|
|---|
| 217 | moduleResolverPath = `${eslintFolder}/lib/shared/relative-module-resolver`;
|
|---|
| 218 | // The naming module was moved to @eslint/eslintrc in ESLint 7.8.0, which is also when the @eslint/eslintrc
|
|---|
| 219 | // package was created and added to ESLint, so we need to probe for whether it's in the old or new location.
|
|---|
| 220 | let eslintrcFolder;
|
|---|
| 221 | try {
|
|---|
| 222 | eslintrcFolder = path.dirname(require.resolve('@eslint/eslintrc/package.json', {
|
|---|
| 223 | paths: [currentModule.path]
|
|---|
| 224 | }));
|
|---|
| 225 | }
|
|---|
| 226 | catch (ex) {
|
|---|
| 227 | if (!isModuleResolutionError(ex)) {
|
|---|
| 228 | throw ex;
|
|---|
| 229 | }
|
|---|
| 230 | }
|
|---|
| 231 | namingPath = `${eslintrcFolder !== null && eslintrcFolder !== void 0 ? eslintrcFolder : eslintFolder}/lib/shared/naming`;
|
|---|
| 232 | break;
|
|---|
| 233 | }
|
|---|
| 234 | if (!currentModule.parent) {
|
|---|
| 235 | // This was tested with ESLint 6.1.0 .. 7.12.1.
|
|---|
| 236 | throw new Error('Failed to patch ESLint because the calling module was not recognized.\n' +
|
|---|
| 237 | 'If you are using a newer ESLint version that may be unsupported, please create a GitHub issue:\n' +
|
|---|
| 238 | 'https://github.com/microsoft/rushstack/issues');
|
|---|
| 239 | }
|
|---|
| 240 | currentModule = currentModule.parent;
|
|---|
| 241 | }
|
|---|
| 242 | }
|
|---|
| 243 | // Detect the ESLint package version
|
|---|
| 244 | const eslintPackageJsonPath = `${eslintFolder}/package.json`;
|
|---|
| 245 | const eslintPackageObject = require(eslintPackageJsonPath);
|
|---|
| 246 | export const eslintPackageVersion = eslintPackageObject.version;
|
|---|
| 247 | const ESLINT_MAJOR_VERSION = parseInt(eslintPackageVersion, 10);
|
|---|
| 248 | if (isNaN(ESLINT_MAJOR_VERSION)) {
|
|---|
| 249 | throw new Error(`Unable to parse ESLint version "${eslintPackageVersion}" in file "${eslintPackageJsonPath}"`);
|
|---|
| 250 | }
|
|---|
| 251 | if (!(ESLINT_MAJOR_VERSION >= 6 && ESLINT_MAJOR_VERSION <= 9)) {
|
|---|
| 252 | throw new Error('The ESLint patch script has only been tested with ESLint version 6.x, 7.x, 8.x, and 9.x.' +
|
|---|
| 253 | ` (Your version: ${eslintPackageVersion})\n` +
|
|---|
| 254 | 'Consider reporting a GitHub issue:\n' +
|
|---|
| 255 | 'https://github.com/microsoft/rushstack/issues');
|
|---|
| 256 | }
|
|---|
| 257 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|---|
| 258 | let configArrayFactory;
|
|---|
| 259 | if (ESLINT_MAJOR_VERSION >= 8 && eslintrcBundlePath) {
|
|---|
| 260 | configArrayFactory = require(eslintrcBundlePath).Legacy.ConfigArrayFactory;
|
|---|
| 261 | }
|
|---|
| 262 | else if (configArrayFactoryPath) {
|
|---|
| 263 | configArrayFactory = require(configArrayFactoryPath).ConfigArrayFactory;
|
|---|
| 264 | }
|
|---|
| 265 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|---|
| 266 | let ModuleResolver;
|
|---|
| 267 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|---|
| 268 | let Naming;
|
|---|
| 269 | if (ESLINT_MAJOR_VERSION >= 8 && eslintrcBundlePath) {
|
|---|
| 270 | ModuleResolver = require(eslintrcBundlePath).Legacy.ModuleResolver;
|
|---|
| 271 | Naming = require(eslintrcBundlePath).Legacy.naming;
|
|---|
| 272 | }
|
|---|
| 273 | else if (moduleResolverPath && namingPath) {
|
|---|
| 274 | ModuleResolver = require(moduleResolverPath);
|
|---|
| 275 | Naming = require(namingPath);
|
|---|
| 276 | }
|
|---|
| 277 | export { eslintFolder, configArrayFactory, ModuleResolver, Naming, ESLINT_MAJOR_VERSION, isModuleResolutionError };
|
|---|
| 278 | //# sourceMappingURL=_patch-base.js.map |
|---|