*/
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
const ignore = require("ignore");
-const arrayOfStrings = {
- type: "array",
- items: { type: "string" },
- uniqueItems: true
-};
-
const arrayOfStringsOrObjects = {
type: "array",
items: {
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`",
- category: "ECMAScript 6",
+ 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
+ // 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
+ // 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
+ // 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}}"
},
type: "object",
properties: {
paths: arrayOfStringsOrObjects,
- patterns: arrayOfStrings
+ patterns: arrayOfStringsOrObjectPatterns
},
additionalProperties: false
}],
(Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
- const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
-
- // if no imports are restricted we don"t need to check
- if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
- return {};
- }
-
const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
if (typeof importSource === "string") {
memo[importSource] = { message: null };
return memo;
}, {});
- const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
+ // 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.
/**
* 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) {
+ function reportPathForPatterns(node, group, importNames) {
const importSource = node.source.value.trim();
- context.report({
- node,
- messageId: "patterns",
- data: {
- importSource
+ 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) {
- return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
+ function isRestrictedPattern(importSource, group) {
+ return group.matcher.ignores(importSource);
}
/**
} else if (specifier.type === "ImportNamespaceSpecifier") {
name = "*";
} else if (specifier.imported) {
- name = specifier.imported.name;
+ name = astUtils.getModuleExportName(specifier.imported);
} else if (specifier.local) {
- name = specifier.local.name;
+ name = astUtils.getModuleExportName(specifier.local);
}
- if (name) {
+ if (typeof name === "string") {
if (importNames.has(name)) {
importNames.get(name).push(specifierData);
} else {
}
checkRestrictedPathAndReport(importSource, importNames, node);
-
- if (isRestrictedPattern(importSource)) {
- reportPathForPatterns(node);
- }
+ restrictedPatternGroups.forEach(group => {
+ if (isRestrictedPattern(importSource, group)) {
+ reportPathForPatterns(node, group, importNames);
+ }
+ });
}
return {