| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 | /** * @fileoverview Restrict usage of specified node imports. * @author Guy Ellis */"use strict";//------------------------------------------------------------------------------// Requirements//------------------------------------------------------------------------------const astUtils = require("./utils/ast-utils");//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------const ignore = require("ignore");const arrayOfStringsOrObjects = {    type: "array",    items: {        anyOf: [            { type: "string" },            {                type: "object",                properties: {                    name: { type: "string" },                    message: {                        type: "string",                        minLength: 1                    },                    importNames: {                        type: "array",                        items: {                            type: "string"                        }                    }                },                additionalProperties: false,                required: ["name"]            }        ]    },    uniqueItems: true};const arrayOfStringsOrObjectPatterns = {    anyOf: [        {            type: "array",            items: {                type: "string"            },            uniqueItems: true        },        {            type: "array",            items: {                type: "object",                properties: {                    importNames: {                        type: "array",                        items: {                            type: "string"                        },                        minItems: 1,                        uniqueItems: true                    },                    group: {                        type: "array",                        items: {                            type: "string"                        },                        minItems: 1,                        uniqueItems: true                    },                    message: {                        type: "string",                        minLength: 1                    },                    caseSensitive: {                        type: "boolean"                    }                },                additionalProperties: false,                required: ["group"]            },            uniqueItems: true        }    ]};/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",        docs: {            description: "Disallow specified modules when loaded by `import`",            recommended: false,            url: "https://eslint.org/docs/rules/no-restricted-imports"        },        messages: {            path: "'{{importSource}}' import is restricted from being used.",            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period            pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}",            patterns: "'{{importSource}}' import is restricted from being used by a pattern.",            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period            patternWithCustomMessage: "'{{importSource}}' import is restricted from being used by a pattern. {{customMessage}}",            patternAndImportName: "'{{importName}}' import from '{{importSource}}' is restricted from being used by a pattern.",            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period            patternAndImportNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}",            patternAndEverything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern.",            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period            patternAndEverythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}",            everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period            everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}",            importName: "'{{importName}}' import from '{{importSource}}' is restricted.",            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period            importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}"        },        schema: {            anyOf: [                arrayOfStringsOrObjects,                {                    type: "array",                    items: [{                        type: "object",                        properties: {                            paths: arrayOfStringsOrObjects,                            patterns: arrayOfStringsOrObjectPatterns                        },                        additionalProperties: false                    }],                    additionalItems: false                }            ]        }    },    create(context) {        const sourceCode = context.getSourceCode();        const options = Array.isArray(context.options) ? context.options : [];        const isPathAndPatternsObject =            typeof options[0] === "object" &&            (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));        const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];        const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {            if (typeof importSource === "string") {                memo[importSource] = { message: null };            } else {                memo[importSource.name] = {                    message: importSource.message,                    importNames: importSource.importNames                };            }            return memo;        }, {});        // Handle patterns too, either as strings or groups        let restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];        // standardize to array of objects if we have an array of strings        if (restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string") {            restrictedPatterns = [{ group: restrictedPatterns }];        }        // relative paths are supported for this rule        const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive, importNames }) => ({            matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group),            customMessage: message,            importNames        }));        // if no imports are restricted we don't need to check        if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {            return {};        }        /**         * Report a restricted path.         * @param {string} importSource path of the import         * @param {Map<string,Object[]>} importNames Map of import names that are being imported         * @param {node} node representing the restricted path reference         * @returns {void}         * @private         */        function checkRestrictedPathAndReport(importSource, importNames, node) {            if (!Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) {                return;            }            const customMessage = restrictedPathMessages[importSource].message;            const restrictedImportNames = restrictedPathMessages[importSource].importNames;            if (restrictedImportNames) {                if (importNames.has("*")) {                    const specifierData = importNames.get("*")[0];                    context.report({                        node,                        messageId: customMessage ? "everythingWithCustomMessage" : "everything",                        loc: specifierData.loc,                        data: {                            importSource,                            importNames: restrictedImportNames,                            customMessage                        }                    });                }                restrictedImportNames.forEach(importName => {                    if (importNames.has(importName)) {                        const specifiers = importNames.get(importName);                        specifiers.forEach(specifier => {                            context.report({                                node,                                messageId: customMessage ? "importNameWithCustomMessage" : "importName",                                loc: specifier.loc,                                data: {                                    importSource,                                    customMessage,                                    importName                                }                            });                        });                    }                });            } else {                context.report({                    node,                    messageId: customMessage ? "pathWithCustomMessage" : "path",                    data: {                        importSource,                        customMessage                    }                });            }        }        /**         * Report a restricted path specifically for patterns.         * @param {node} node representing the restricted path reference         * @param {Object} group contains an Ignore instance for paths, the customMessage to show on failure,         * and any restricted import names that have been specified in the config         * @param {Map<string,Object[]>} importNames Map of import names that are being imported         * @returns {void}         * @private         */        function reportPathForPatterns(node, group, importNames) {            const importSource = node.source.value.trim();            const customMessage = group.customMessage;            const restrictedImportNames = group.importNames;            /*             * If we are not restricting to any specific import names and just the pattern itself,             * report the error and move on             */            if (!restrictedImportNames) {                context.report({                    node,                    messageId: customMessage ? "patternWithCustomMessage" : "patterns",                    data: {                        importSource,                        customMessage                    }                });                return;            }            if (importNames.has("*")) {                const specifierData = importNames.get("*")[0];                context.report({                    node,                    messageId: customMessage ? "patternAndEverythingWithCustomMessage" : "patternAndEverything",                    loc: specifierData.loc,                    data: {                        importSource,                        importNames: restrictedImportNames,                        customMessage                    }                });            }            restrictedImportNames.forEach(importName => {                if (!importNames.has(importName)) {                    return;                }                const specifiers = importNames.get(importName);                specifiers.forEach(specifier => {                    context.report({                        node,                        messageId: customMessage ? "patternAndImportNameWithCustomMessage" : "patternAndImportName",                        loc: specifier.loc,                        data: {                            importSource,                            customMessage,                            importName                        }                    });                });            });        }        /**         * Check if the given importSource is restricted by a pattern.         * @param {string} importSource path of the import         * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails         * @returns {boolean} whether the variable is a restricted pattern or not         * @private         */        function isRestrictedPattern(importSource, group) {            return group.matcher.ignores(importSource);        }        /**         * Checks a node to see if any problems should be reported.         * @param {ASTNode} node The node to check.         * @returns {void}         * @private         */        function checkNode(node) {            const importSource = node.source.value.trim();            const importNames = new Map();            if (node.type === "ExportAllDeclaration") {                const starToken = sourceCode.getFirstToken(node, 1);                importNames.set("*", [{ loc: starToken.loc }]);            } else if (node.specifiers) {                for (const specifier of node.specifiers) {                    let name;                    const specifierData = { loc: specifier.loc };                    if (specifier.type === "ImportDefaultSpecifier") {                        name = "default";                    } else if (specifier.type === "ImportNamespaceSpecifier") {                        name = "*";                    } else if (specifier.imported) {                        name = astUtils.getModuleExportName(specifier.imported);                    } else if (specifier.local) {                        name = astUtils.getModuleExportName(specifier.local);                    }                    if (typeof name === "string") {                        if (importNames.has(name)) {                            importNames.get(name).push(specifierData);                        } else {                            importNames.set(name, [specifierData]);                        }                    }                }            }            checkRestrictedPathAndReport(importSource, importNames, node);            restrictedPatternGroups.forEach(group => {                if (isRestrictedPattern(importSource, group)) {                    reportPathForPatterns(node, group, importNames);                }            });        }        return {            ImportDeclaration: checkNode,            ExportNamedDeclaration(node) {                if (node.source) {                    checkNode(node);                }            },            ExportAllDeclaration: checkNode        };    }};
 |