no-restricted-modules.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /**
  2. * @fileoverview Restrict usage of specified node modules.
  3. * @author Christian Schulz
  4. * @deprecated in ESLint v7.0.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const astUtils = require("./utils/ast-utils");
  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. },
  34. additionalProperties: false,
  35. required: ["name"],
  36. },
  37. ],
  38. },
  39. uniqueItems: true,
  40. };
  41. /** @type {import('../types').Rule.RuleModule} */
  42. module.exports = {
  43. meta: {
  44. deprecated: {
  45. message: "Node.js rules were moved out of ESLint core.",
  46. url: "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules",
  47. deprecatedSince: "7.0.0",
  48. availableUntil: "11.0.0",
  49. replacedBy: [
  50. {
  51. message:
  52. "eslint-plugin-n now maintains deprecated Node.js-related rules.",
  53. plugin: {
  54. name: "eslint-plugin-n",
  55. url: "https://github.com/eslint-community/eslint-plugin-n",
  56. },
  57. rule: {
  58. name: "no-restricted-require",
  59. url: "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-restricted-require.md",
  60. },
  61. },
  62. ],
  63. },
  64. type: "suggestion",
  65. docs: {
  66. description: "Disallow specified modules when loaded by `require`",
  67. recommended: false,
  68. url: "https://eslint.org/docs/latest/rules/no-restricted-modules",
  69. },
  70. schema: {
  71. anyOf: [
  72. arrayOfStringsOrObjects,
  73. {
  74. type: "array",
  75. items: {
  76. type: "object",
  77. properties: {
  78. paths: arrayOfStringsOrObjects,
  79. patterns: arrayOfStrings,
  80. },
  81. additionalProperties: false,
  82. },
  83. additionalItems: false,
  84. },
  85. ],
  86. },
  87. messages: {
  88. defaultMessage: "'{{name}}' module is restricted from being used.",
  89. customMessage:
  90. // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
  91. "'{{name}}' module is restricted from being used. {{customMessage}}",
  92. patternMessage:
  93. "'{{name}}' module is restricted from being used by a pattern.",
  94. },
  95. },
  96. create(context) {
  97. const options = Array.isArray(context.options) ? context.options : [];
  98. const isPathAndPatternsObject =
  99. typeof options[0] === "object" &&
  100. (Object.hasOwn(options[0], "paths") ||
  101. Object.hasOwn(options[0], "patterns"));
  102. const restrictedPaths =
  103. (isPathAndPatternsObject ? options[0].paths : context.options) ||
  104. [];
  105. const restrictedPatterns =
  106. (isPathAndPatternsObject ? options[0].patterns : []) || [];
  107. const restrictedPathMessages = restrictedPaths.reduce(
  108. (memo, importName) => {
  109. if (typeof importName === "string") {
  110. memo[importName] = null;
  111. } else {
  112. memo[importName.name] = importName.message;
  113. }
  114. return memo;
  115. },
  116. {},
  117. );
  118. // if no imports are restricted we don't need to check
  119. if (
  120. Object.keys(restrictedPaths).length === 0 &&
  121. restrictedPatterns.length === 0
  122. ) {
  123. return {};
  124. }
  125. // relative paths are supported for this rule
  126. const ig = ignore({ allowRelativePaths: true }).add(restrictedPatterns);
  127. /**
  128. * Function to check if a node is a string literal.
  129. * @param {ASTNode} node The node to check.
  130. * @returns {boolean} If the node is a string literal.
  131. */
  132. function isStringLiteral(node) {
  133. return (
  134. node &&
  135. node.type === "Literal" &&
  136. typeof node.value === "string"
  137. );
  138. }
  139. /**
  140. * Function to check if a node is a require call.
  141. * @param {ASTNode} node The node to check.
  142. * @returns {boolean} If the node is a require call.
  143. */
  144. function isRequireCall(node) {
  145. return (
  146. node.callee.type === "Identifier" &&
  147. node.callee.name === "require"
  148. );
  149. }
  150. /**
  151. * Extract string from Literal or TemplateLiteral node
  152. * @param {ASTNode} node The node to extract from
  153. * @returns {string|null} Extracted string or null if node doesn't represent a string
  154. */
  155. function getFirstArgumentString(node) {
  156. if (isStringLiteral(node)) {
  157. return node.value.trim();
  158. }
  159. if (astUtils.isStaticTemplateLiteral(node)) {
  160. return node.quasis[0].value.cooked.trim();
  161. }
  162. return null;
  163. }
  164. /**
  165. * Report a restricted path.
  166. * @param {node} node representing the restricted path reference
  167. * @param {string} name restricted path
  168. * @returns {void}
  169. * @private
  170. */
  171. function reportPath(node, name) {
  172. const customMessage = restrictedPathMessages[name];
  173. const messageId = customMessage
  174. ? "customMessage"
  175. : "defaultMessage";
  176. context.report({
  177. node,
  178. messageId,
  179. data: {
  180. name,
  181. customMessage,
  182. },
  183. });
  184. }
  185. /**
  186. * Check if the given name is a restricted path name
  187. * @param {string} name name of a variable
  188. * @returns {boolean} whether the variable is a restricted path or not
  189. * @private
  190. */
  191. function isRestrictedPath(name) {
  192. return Object.hasOwn(restrictedPathMessages, name);
  193. }
  194. return {
  195. CallExpression(node) {
  196. if (isRequireCall(node)) {
  197. // node has arguments
  198. if (node.arguments.length) {
  199. const name = getFirstArgumentString(node.arguments[0]);
  200. // if first argument is a string literal or a static string template literal
  201. if (name) {
  202. // check if argument value is in restricted modules array
  203. if (isRestrictedPath(name)) {
  204. reportPath(node, name);
  205. }
  206. if (
  207. restrictedPatterns.length > 0 &&
  208. ig.ignores(name)
  209. ) {
  210. context.report({
  211. node,
  212. messageId: "patternMessage",
  213. data: { name },
  214. });
  215. }
  216. }
  217. }
  218. }
  219. },
  220. };
  221. },
  222. };