no-restricted-exports.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /**
  2. * @fileoverview Rule to disallow specified names in exports
  3. * @author Milos Djermanovic
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Rule Definition
  12. //------------------------------------------------------------------------------
  13. /** @type {import('../types').Rule.RuleModule} */
  14. module.exports = {
  15. meta: {
  16. type: "suggestion",
  17. docs: {
  18. description: "Disallow specified names in exports",
  19. recommended: false,
  20. url: "https://eslint.org/docs/latest/rules/no-restricted-exports",
  21. },
  22. schema: [
  23. {
  24. anyOf: [
  25. {
  26. type: "object",
  27. properties: {
  28. restrictedNamedExports: {
  29. type: "array",
  30. items: {
  31. type: "string",
  32. },
  33. uniqueItems: true,
  34. },
  35. restrictedNamedExportsPattern: { type: "string" },
  36. },
  37. additionalProperties: false,
  38. },
  39. {
  40. type: "object",
  41. properties: {
  42. restrictedNamedExports: {
  43. type: "array",
  44. items: {
  45. type: "string",
  46. pattern: "^(?!default$)",
  47. },
  48. uniqueItems: true,
  49. },
  50. restrictedNamedExportsPattern: { type: "string" },
  51. restrictDefaultExports: {
  52. type: "object",
  53. properties: {
  54. // Allow/Disallow `export default foo; export default 42; export default function foo() {}` format
  55. direct: {
  56. type: "boolean",
  57. },
  58. // Allow/Disallow `export { foo as default };` declarations
  59. named: {
  60. type: "boolean",
  61. },
  62. // Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` declarations
  63. defaultFrom: {
  64. type: "boolean",
  65. },
  66. // Allow/Disallow `export { foo as default } from "mod";` declarations
  67. namedFrom: {
  68. type: "boolean",
  69. },
  70. // Allow/Disallow `export * as default from "mod"`; declarations
  71. namespaceFrom: {
  72. type: "boolean",
  73. },
  74. },
  75. additionalProperties: false,
  76. },
  77. },
  78. additionalProperties: false,
  79. },
  80. ],
  81. },
  82. ],
  83. messages: {
  84. restrictedNamed:
  85. "'{{name}}' is restricted from being used as an exported name.",
  86. restrictedDefault: "Exporting 'default' is restricted.",
  87. },
  88. },
  89. create(context) {
  90. const restrictedNames = new Set(
  91. context.options[0] && context.options[0].restrictedNamedExports,
  92. );
  93. const restrictedNamePattern =
  94. context.options[0] &&
  95. context.options[0].restrictedNamedExportsPattern;
  96. const restrictDefaultExports =
  97. context.options[0] && context.options[0].restrictDefaultExports;
  98. const sourceCode = context.sourceCode;
  99. /**
  100. * Checks and reports given exported name.
  101. * @param {ASTNode} node exported `Identifier` or string `Literal` node to check.
  102. * @returns {void}
  103. */
  104. function checkExportedName(node) {
  105. const name = astUtils.getModuleExportName(node);
  106. let matchesRestrictedNamePattern = false;
  107. if (restrictedNamePattern && name !== "default") {
  108. const patternRegex = new RegExp(restrictedNamePattern, "u");
  109. matchesRestrictedNamePattern = patternRegex.test(name);
  110. }
  111. if (matchesRestrictedNamePattern || restrictedNames.has(name)) {
  112. context.report({
  113. node,
  114. messageId: "restrictedNamed",
  115. data: { name },
  116. });
  117. return;
  118. }
  119. if (name === "default") {
  120. if (node.parent.type === "ExportAllDeclaration") {
  121. if (
  122. restrictDefaultExports &&
  123. restrictDefaultExports.namespaceFrom
  124. ) {
  125. context.report({
  126. node,
  127. messageId: "restrictedDefault",
  128. });
  129. }
  130. } else {
  131. // ExportSpecifier
  132. const isSourceSpecified = !!node.parent.parent.source;
  133. const specifierLocalName = astUtils.getModuleExportName(
  134. node.parent.local,
  135. );
  136. if (
  137. !isSourceSpecified &&
  138. restrictDefaultExports &&
  139. restrictDefaultExports.named
  140. ) {
  141. context.report({
  142. node,
  143. messageId: "restrictedDefault",
  144. });
  145. return;
  146. }
  147. if (isSourceSpecified && restrictDefaultExports) {
  148. if (
  149. (specifierLocalName === "default" &&
  150. restrictDefaultExports.defaultFrom) ||
  151. (specifierLocalName !== "default" &&
  152. restrictDefaultExports.namedFrom)
  153. ) {
  154. context.report({
  155. node,
  156. messageId: "restrictedDefault",
  157. });
  158. }
  159. }
  160. }
  161. }
  162. }
  163. return {
  164. ExportAllDeclaration(node) {
  165. if (node.exported) {
  166. checkExportedName(node.exported);
  167. }
  168. },
  169. ExportDefaultDeclaration(node) {
  170. if (restrictDefaultExports && restrictDefaultExports.direct) {
  171. context.report({
  172. node,
  173. messageId: "restrictedDefault",
  174. });
  175. }
  176. },
  177. ExportNamedDeclaration(node) {
  178. const declaration = node.declaration;
  179. if (declaration) {
  180. if (
  181. declaration.type === "FunctionDeclaration" ||
  182. declaration.type === "ClassDeclaration"
  183. ) {
  184. checkExportedName(declaration.id);
  185. } else if (declaration.type === "VariableDeclaration") {
  186. sourceCode
  187. .getDeclaredVariables(declaration)
  188. .map(v =>
  189. v.defs.find(d => d.parent === declaration),
  190. )
  191. .map(d => d.name) // Identifier nodes
  192. .forEach(checkExportedName);
  193. }
  194. } else {
  195. node.specifiers
  196. .map(s => s.exported)
  197. .forEach(checkExportedName);
  198. }
  199. },
  200. };
  201. },
  202. };