no-restricted-imports.js 9.4 KB

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