no-restricted-imports.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /**
  2. * @fileoverview Restrict usage of specified node imports.
  3. * @author Guy Ellis
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. const ignore = require("ignore");
  10. const arrayOfStrings = {
  11. type: "array",
  12. items: { type: "string" },
  13. uniqueItems: true
  14. };
  15. const arrayOfStringsOrObjects = {
  16. type: "array",
  17. items: {
  18. anyOf: [
  19. { type: "string" },
  20. {
  21. type: "object",
  22. properties: {
  23. name: { type: "string" },
  24. message: {
  25. type: "string",
  26. minLength: 1
  27. },
  28. importNames: {
  29. type: "array",
  30. items: {
  31. type: "string"
  32. }
  33. }
  34. },
  35. additionalProperties: false,
  36. required: ["name"]
  37. }
  38. ]
  39. },
  40. uniqueItems: true
  41. };
  42. module.exports = {
  43. meta: {
  44. type: "suggestion",
  45. docs: {
  46. description: "disallow specified modules when loaded by `import`",
  47. category: "ECMAScript 6",
  48. recommended: false,
  49. url: "https://eslint.org/docs/rules/no-restricted-imports"
  50. },
  51. messages: {
  52. path: "'{{importSource}}' import is restricted from being used.",
  53. // eslint-disable-next-line eslint-plugin/report-message-format
  54. pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}",
  55. patterns: "'{{importSource}}' import is restricted from being used by a pattern.",
  56. everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
  57. // eslint-disable-next-line eslint-plugin/report-message-format
  58. everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}",
  59. importName: "'{{importName}}' import from '{{importSource}}' is restricted.",
  60. // eslint-disable-next-line eslint-plugin/report-message-format
  61. importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}"
  62. },
  63. schema: {
  64. anyOf: [
  65. arrayOfStringsOrObjects,
  66. {
  67. type: "array",
  68. items: [{
  69. type: "object",
  70. properties: {
  71. paths: arrayOfStringsOrObjects,
  72. patterns: arrayOfStrings
  73. },
  74. additionalProperties: false
  75. }],
  76. additionalItems: false
  77. }
  78. ]
  79. }
  80. },
  81. create(context) {
  82. const sourceCode = context.getSourceCode();
  83. const options = Array.isArray(context.options) ? context.options : [];
  84. const isPathAndPatternsObject =
  85. typeof options[0] === "object" &&
  86. (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
  87. const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
  88. const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
  89. // if no imports are restricted we don"t need to check
  90. if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
  91. return {};
  92. }
  93. const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
  94. if (typeof importSource === "string") {
  95. memo[importSource] = { message: null };
  96. } else {
  97. memo[importSource.name] = {
  98. message: importSource.message,
  99. importNames: importSource.importNames
  100. };
  101. }
  102. return memo;
  103. }, {});
  104. const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
  105. /**
  106. * Report a restricted path.
  107. * @param {string} importSource path of the import
  108. * @param {Map<string,Object[]>} importNames Map of import names that are being imported
  109. * @param {node} node representing the restricted path reference
  110. * @returns {void}
  111. * @private
  112. */
  113. function checkRestrictedPathAndReport(importSource, importNames, node) {
  114. if (!Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) {
  115. return;
  116. }
  117. const customMessage = restrictedPathMessages[importSource].message;
  118. const restrictedImportNames = restrictedPathMessages[importSource].importNames;
  119. if (restrictedImportNames) {
  120. if (importNames.has("*")) {
  121. const specifierData = importNames.get("*")[0];
  122. context.report({
  123. node,
  124. messageId: customMessage ? "everythingWithCustomMessage" : "everything",
  125. loc: specifierData.loc,
  126. data: {
  127. importSource,
  128. importNames: restrictedImportNames,
  129. customMessage
  130. }
  131. });
  132. }
  133. restrictedImportNames.forEach(importName => {
  134. if (importNames.has(importName)) {
  135. const specifiers = importNames.get(importName);
  136. specifiers.forEach(specifier => {
  137. context.report({
  138. node,
  139. messageId: customMessage ? "importNameWithCustomMessage" : "importName",
  140. loc: specifier.loc,
  141. data: {
  142. importSource,
  143. customMessage,
  144. importName
  145. }
  146. });
  147. });
  148. }
  149. });
  150. } else {
  151. context.report({
  152. node,
  153. messageId: customMessage ? "pathWithCustomMessage" : "path",
  154. data: {
  155. importSource,
  156. customMessage
  157. }
  158. });
  159. }
  160. }
  161. /**
  162. * Report a restricted path specifically for patterns.
  163. * @param {node} node representing the restricted path reference
  164. * @returns {void}
  165. * @private
  166. */
  167. function reportPathForPatterns(node) {
  168. const importSource = node.source.value.trim();
  169. context.report({
  170. node,
  171. messageId: "patterns",
  172. data: {
  173. importSource
  174. }
  175. });
  176. }
  177. /**
  178. * Check if the given importSource is restricted by a pattern.
  179. * @param {string} importSource path of the import
  180. * @returns {boolean} whether the variable is a restricted pattern or not
  181. * @private
  182. */
  183. function isRestrictedPattern(importSource) {
  184. return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
  185. }
  186. /**
  187. * Checks a node to see if any problems should be reported.
  188. * @param {ASTNode} node The node to check.
  189. * @returns {void}
  190. * @private
  191. */
  192. function checkNode(node) {
  193. const importSource = node.source.value.trim();
  194. const importNames = new Map();
  195. if (node.type === "ExportAllDeclaration") {
  196. const starToken = sourceCode.getFirstToken(node, 1);
  197. importNames.set("*", [{ loc: starToken.loc }]);
  198. } else if (node.specifiers) {
  199. for (const specifier of node.specifiers) {
  200. let name;
  201. const specifierData = { loc: specifier.loc };
  202. if (specifier.type === "ImportDefaultSpecifier") {
  203. name = "default";
  204. } else if (specifier.type === "ImportNamespaceSpecifier") {
  205. name = "*";
  206. } else if (specifier.imported) {
  207. name = specifier.imported.name;
  208. } else if (specifier.local) {
  209. name = specifier.local.name;
  210. }
  211. if (name) {
  212. if (importNames.has(name)) {
  213. importNames.get(name).push(specifierData);
  214. } else {
  215. importNames.set(name, [specifierData]);
  216. }
  217. }
  218. }
  219. }
  220. checkRestrictedPathAndReport(importSource, importNames, node);
  221. if (isRestrictedPattern(importSource)) {
  222. reportPathForPatterns(node);
  223. }
  224. }
  225. return {
  226. ImportDeclaration: checkNode,
  227. ExportNamedDeclaration(node) {
  228. if (node.source) {
  229. checkNode(node);
  230. }
  231. },
  232. ExportAllDeclaration: checkNode
  233. };
  234. }
  235. };