/** * @fileoverview Rule to disallow specified names in exports * @author Milos Djermanovic */ "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { type: "suggestion", docs: { description: "Disallow specified names in exports", recommended: false, url: "https://eslint.org/docs/latest/rules/no-restricted-exports" }, schema: [{ anyOf: [ { type: "object", properties: { restrictedNamedExports: { type: "array", items: { type: "string" }, uniqueItems: true } }, additionalProperties: false }, { type: "object", properties: { restrictedNamedExports: { type: "array", items: { type: "string", pattern: "^(?!default$)" }, uniqueItems: true }, restrictDefaultExports: { type: "object", properties: { // Allow/Disallow `export default foo; export default 42; export default function foo() {}` format direct: { type: "boolean" }, // Allow/Disallow `export { foo as default };` declarations named: { type: "boolean" }, // Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` declarations defaultFrom: { type: "boolean" }, // Allow/Disallow `export { foo as default } from "mod";` declarations namedFrom: { type: "boolean" }, // Allow/Disallow `export * as default from "mod"`; declarations namespaceFrom: { type: "boolean" } }, additionalProperties: false } }, additionalProperties: false } ] }], messages: { restrictedNamed: "'{{name}}' is restricted from being used as an exported name.", restrictedDefault: "Exporting 'default' is restricted." } }, create(context) { const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); const restrictDefaultExports = context.options[0] && context.options[0].restrictDefaultExports; const sourceCode = context.sourceCode; /** * Checks and reports given exported name. * @param {ASTNode} node exported `Identifier` or string `Literal` node to check. * @returns {void} */ function checkExportedName(node) { const name = astUtils.getModuleExportName(node); if (restrictedNames.has(name)) { context.report({ node, messageId: "restrictedNamed", data: { name } }); return; } if (name === "default") { if (node.parent.type === "ExportAllDeclaration") { if (restrictDefaultExports && restrictDefaultExports.namespaceFrom) { context.report({ node, messageId: "restrictedDefault" }); } } else { // ExportSpecifier const isSourceSpecified = !!node.parent.parent.source; const specifierLocalName = astUtils.getModuleExportName(node.parent.local); if (!isSourceSpecified && restrictDefaultExports && restrictDefaultExports.named) { context.report({ node, messageId: "restrictedDefault" }); return; } if (isSourceSpecified && restrictDefaultExports) { if ( (specifierLocalName === "default" && restrictDefaultExports.defaultFrom) || (specifierLocalName !== "default" && restrictDefaultExports.namedFrom) ) { context.report({ node, messageId: "restrictedDefault" }); } } } } } return { ExportAllDeclaration(node) { if (node.exported) { checkExportedName(node.exported); } }, ExportDefaultDeclaration(node) { if (restrictDefaultExports && restrictDefaultExports.direct) { context.report({ node, messageId: "restrictedDefault" }); } }, ExportNamedDeclaration(node) { const declaration = node.declaration; if (declaration) { if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") { checkExportedName(declaration.id); } else if (declaration.type === "VariableDeclaration") { sourceCode.getDeclaredVariables(declaration) .map(v => v.defs.find(d => d.parent === declaration)) .map(d => d.name) // Identifier nodes .forEach(checkExportedName); } } else { node.specifiers .map(s => s.exported) .forEach(checkExportedName); } } }; } };