modern-module-resolution.js 9.67 KB
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
// This is a workaround for https://github.com/eslint/eslint/issues/3458
//
// To correct how ESLint searches for plugin packages, add this line to the top of your project's .eslintrc.js file:
//
//    require("@rushstack/eslint-patch/modern-module-resolution");
//
const path = require('path');
const fs = require('fs');
const isModuleResolutionError = (ex) => typeof ex === 'object' && !!ex && 'code' in ex && ex.code === 'MODULE_NOT_FOUND';
// Module path for eslintrc.cjs
// Example: ".../@eslint/eslintrc/dist/eslintrc.cjs"
let eslintrcBundlePath = undefined;
// Module path for config-array-factory.js
// Example: ".../@eslint/eslintrc/lib/config-array-factory"
let configArrayFactoryPath = undefined;
// Module path for relative-module-resolver.js
// Example: ".../@eslint/eslintrc/lib/shared/relative-module-resolver"
let moduleResolverPath = undefined;
// Folder path where ESLint's package.json can be found
// Example: ".../node_modules/eslint"
let eslintFolder = undefined;
// Probe for the ESLint >=8.0.0 layout:
for (let currentModule = module;;) {
    if (!eslintrcBundlePath) {
        // For ESLint >=8.0.0, all @eslint/eslintrc code is bundled at this path:
        //   .../@eslint/eslintrc/dist/eslintrc.cjs
        try {
            const eslintrcFolder = path.dirname(require.resolve('@eslint/eslintrc/package.json', { paths: [currentModule.path] }));
            // Make sure we actually resolved the module in our call path
            // and not some other spurious dependency.
            if (path.join(eslintrcFolder, 'dist/eslintrc.cjs') === currentModule.filename) {
                eslintrcBundlePath = path.join(eslintrcFolder, 'dist/eslintrc.cjs');
            }
        }
        catch (ex) {
            // Module resolution failures are expected, as we're walking
            // up our require stack to look for eslint. All other errors
            // are rethrown.
            if (!isModuleResolutionError(ex)) {
                throw ex;
            }
        }
    }
    else {
        // Next look for a file in ESLint's folder
        //   .../eslint/lib/cli-engine/cli-engine.js
        try {
            const eslintCandidateFolder = path.dirname(require.resolve('eslint/package.json', {
                paths: [currentModule.path]
            }));
            // Make sure we actually resolved the module in our call path
            // and not some other spurious dependency.
            if (path.join(eslintCandidateFolder, 'lib/cli-engine/cli-engine.js') === currentModule.filename) {
                eslintFolder = eslintCandidateFolder;
                break;
            }
        }
        catch (ex) {
            // Module resolution failures are expected, as we're walking
            // up our require stack to look for eslint. All other errors
            // are rethrown.
            if (!isModuleResolutionError(ex)) {
                throw ex;
            }
        }
    }
    if (!currentModule.parent) {
        break;
    }
    currentModule = currentModule.parent;
}
if (!eslintFolder) {
    // Probe for the ESLint >=7.8.0 layout:
    for (let currentModule = module;;) {
        if (!configArrayFactoryPath) {
            // For ESLint >=7.8.0, config-array-factory.js is at this path:
            //   .../@eslint/eslintrc/lib/config-array-factory.js
            try {
                const eslintrcFolder = path.dirname(require.resolve('@eslint/eslintrc/package.json', {
                    paths: [currentModule.path]
                }));
                if (path.join(eslintrcFolder, '/lib/config-array-factory.js') == currentModule.filename) {
                    configArrayFactoryPath = path.join(eslintrcFolder, 'lib/config-array-factory.js');
                    moduleResolverPath = path.join(eslintrcFolder, 'lib/shared/relative-module-resolver');
                }
            }
            catch (ex) {
                // Module resolution failures are expected, as we're walking
                // up our require stack to look for eslint. All other errors
                // are rethrown.
                if (!isModuleResolutionError(ex)) {
                    throw ex;
                }
            }
        }
        else {
            // Next look for a file in ESLint's folder
            //   .../eslint/lib/cli-engine/cli-engine.js
            try {
                const eslintCandidateFolder = path.dirname(require.resolve('eslint/package.json', {
                    paths: [currentModule.path]
                }));
                if (path.join(eslintCandidateFolder, 'lib/cli-engine/cli-engine.js') == currentModule.filename) {
                    eslintFolder = eslintCandidateFolder;
                    break;
                }
            }
            catch (ex) {
                // Module resolution failures are expected, as we're walking
                // up our require stack to look for eslint. All other errors
                // are rethrown.
                if (!isModuleResolutionError(ex)) {
                    throw ex;
                }
            }
        }
        if (!currentModule.parent) {
            break;
        }
        currentModule = currentModule.parent;
    }
}
if (!eslintFolder) {
    // Probe for the <7.8.0 layout:
    for (let currentModule = module;;) {
        // For ESLint <7.8.0, config-array-factory.js was at this path:
        //   .../eslint/lib/cli-engine/config-array-factory.js
        if (/[\\/]eslint[\\/]lib[\\/]cli-engine[\\/]config-array-factory\.js$/i.test(currentModule.filename)) {
            eslintFolder = path.join(path.dirname(currentModule.filename), '../..');
            configArrayFactoryPath = path.join(eslintFolder, 'lib/cli-engine/config-array-factory');
            moduleResolverPath = path.join(eslintFolder, 'lib/shared/relative-module-resolver');
            break;
        }
        if (!currentModule.parent) {
            // This was tested with ESLint 6.1.0 .. 7.12.1.
            throw new Error('Failed to patch ESLint because the calling module was not recognized.\n' +
                'If you are using a newer ESLint version that may be unsupported, please create a GitHub issue:\n' +
                'https://github.com/microsoft/rushstack/issues');
        }
        currentModule = currentModule.parent;
    }
}
// Detect the ESLint package version
const eslintPackageJson = fs.readFileSync(path.join(eslintFolder, 'package.json')).toString();
const eslintPackageObject = JSON.parse(eslintPackageJson);
const eslintPackageVersion = eslintPackageObject.version;
const versionMatch = /^([0-9]+)\./.exec(eslintPackageVersion); // parse the SemVer MAJOR part
if (!versionMatch) {
    throw new Error('Unable to parse ESLint version: ' + eslintPackageVersion);
}
const eslintMajorVersion = Number(versionMatch[1]);
if (!(eslintMajorVersion >= 6 && eslintMajorVersion <= 8)) {
    throw new Error('The patch-eslint.js script has only been tested with ESLint version 6.x, 7.x, and 8.x.' +
        ` (Your version: ${eslintPackageVersion})\n` +
        'Consider reporting a GitHub issue:\n' +
        'https://github.com/microsoft/rushstack/issues');
}
let ConfigArrayFactory;
if (eslintMajorVersion === 8) {
    ConfigArrayFactory = require(eslintrcBundlePath).Legacy.ConfigArrayFactory;
}
else {
    ConfigArrayFactory = require(configArrayFactoryPath).ConfigArrayFactory;
}
if (!ConfigArrayFactory.__patched) {
    ConfigArrayFactory.__patched = true;
    let ModuleResolver;
    if (eslintMajorVersion === 8) {
        ModuleResolver = require(eslintrcBundlePath).Legacy.ModuleResolver;
    }
    else {
        ModuleResolver = require(moduleResolverPath);
    }
    const originalLoadPlugin = ConfigArrayFactory.prototype._loadPlugin;
    if (eslintMajorVersion === 6) {
        // ESLint 6.x
        ConfigArrayFactory.prototype._loadPlugin = function (name, importerPath, importerName) {
            const originalResolve = ModuleResolver.resolve;
            try {
                ModuleResolver.resolve = function (moduleName, relativeToPath) {
                    try {
                        // resolve using importerPath instead of relativeToPath
                        return originalResolve.call(this, moduleName, importerPath);
                    }
                    catch (e) {
                        if (isModuleResolutionError(e)) {
                            return originalResolve.call(this, moduleName, relativeToPath);
                        }
                        throw e;
                    }
                };
                return originalLoadPlugin.apply(this, arguments);
            }
            finally {
                ModuleResolver.resolve = originalResolve;
            }
        };
    }
    else {
        // ESLint 7.x || 8.x
        ConfigArrayFactory.prototype._loadPlugin = function (name, ctx) {
            const originalResolve = ModuleResolver.resolve;
            try {
                ModuleResolver.resolve = function (moduleName, relativeToPath) {
                    try {
                        // resolve using ctx.filePath instead of relativeToPath
                        return originalResolve.call(this, moduleName, ctx.filePath);
                    }
                    catch (e) {
                        if (isModuleResolutionError(e)) {
                            return originalResolve.call(this, moduleName, relativeToPath);
                        }
                        throw e;
                    }
                };
                return originalLoadPlugin.apply(this, arguments);
            }
            finally {
                ModuleResolver.resolve = originalResolve;
            }
        };
    }
}
//# sourceMappingURL=modern-module-resolution.js.map