diff --git a/docs/src/rules/no-restricted-exports.md b/docs/src/rules/no-restricted-exports.md index 8a40852e067..2f83643f9cb 100644 --- a/docs/src/rules/no-restricted-exports.md +++ b/docs/src/rules/no-restricted-exports.md @@ -17,8 +17,16 @@ By default, this rule doesn't disallow any names. Only the names you specify in This rule has an object option: * `"restrictedNamedExports"` is an array of strings, where each string is a name to be restricted. +* `"restrictDefaultExports"` is an object option with boolean properties to restrict certain default export declarations. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. The following properties are allowed: + * `direct`: restricts `export default` declarations. + * `named`: restricts `export { foo as default };` declarations. + * `defaultFrom`: restricts `export { default } from 'foo';` declarations. + * `namedFrom`: restricts `export { foo as default } from 'foo';` declarations. + * `namespaceFrom`: restricts `export * as default from 'foo';` declarations. -Examples of **incorrect** code for this rule: +### restrictedNamedExports + +Examples of **incorrect** code for the `"restrictedNamedExports"` option: ::: incorrect @@ -50,7 +58,7 @@ export { "👍" } from "some_module"; ::: -Examples of **correct** code for this rule: +Examples of **correct** code for the `"restrictedNamedExports"` option: ::: correct @@ -82,11 +90,11 @@ export { "👍" as thumbsUp } from "some_module"; ::: -### Default exports +#### Default exports -By design, this rule doesn't disallow `export default` declarations. If you configure `"default"` as a restricted name, that restriction will apply only to named export declarations. +By design, the `"restrictedNamedExports"` option doesn't disallow `export default` declarations. If you configure `"default"` as a restricted name, that restriction will apply only to named export declarations. -Examples of additional **incorrect** code for this rule: +Examples of additional **incorrect** code for the `"restrictedNamedExports": ["default"]` option: ::: incorrect @@ -110,7 +118,7 @@ export { default } from "some_module"; ::: -Examples of additional **correct** code for this rule: +Examples of additional **correct** code for the `"restrictedNamedExports": ["default"]` option: ::: correct @@ -122,6 +130,85 @@ export default function foo() {} ::: +### restrictDefaultExports + +This option allows you to restrict certain `default` declarations. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. This option accepts the following properties: + +#### direct + +Examples of **incorrect** code for the `"restrictDefaultExports": { "direct": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "direct": true } }]*/ + +export default foo; +export default 42; +export default function foo() {} +``` + +::: + +#### named + +Examples of **incorrect** code for the `"restrictDefaultExports": { "named": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "named": true } }]*/ + +const foo = 123; + +export { foo as default }; +``` + +::: + +#### defaultFrom + +Examples of **incorrect** code for the `"restrictDefaultExports": { "defaultFrom": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "defaultFrom": true } }]*/ + +export { default } from 'foo'; +export { default as default } from 'foo'; +``` + +::: + +#### namedFrom + +Examples of **incorrect** code for the `"restrictDefaultExports": { "namedFrom": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "namedFrom": true } }]*/ + +export { foo as default } from 'foo'; +``` + +::: + +#### namespaceFrom + +Examples of **incorrect** code for the `"restrictDefaultExports": { "namespaceFrom": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "namespaceFrom": true } }]*/ + +export * as default from 'foo'; +``` + +::: + ## Known Limitations This rule doesn't inspect the content of source modules in re-export declarations. In particular, if you are re-exporting everything from another module's export, that export may include a restricted name. This rule cannot detect such cases. diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index d99e8928209..d201e3b03a6 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -27,27 +27,78 @@ module.exports = { }, schema: [{ - type: "object", - properties: { - restrictedNamedExports: { - type: "array", - items: { - type: "string" + anyOf: [ + { + type: "object", + properties: { + restrictedNamedExports: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } }, - 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 } - }, - additionalProperties: false + ] }], messages: { - restrictedNamed: "'{{name}}' is restricted from being used as an exported name." + 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; /** * Checks and reports given exported name. @@ -63,6 +114,42 @@ module.exports = { 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" + }); + } + } + } } } @@ -73,6 +160,15 @@ module.exports = { } }, + ExportDefaultDeclaration(node) { + if (restrictDefaultExports && restrictDefaultExports.direct) { + context.report({ + node, + messageId: "restrictedDefault" + }); + } + }, + ExportNamedDeclaration(node) { const declaration = node.declaration; diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index 631fd6f02fa..9505e8ff6c3 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -107,7 +107,31 @@ ruleTester.run("no-restricted-exports", rule, { { code: "export default 1;", options: [{ restrictedNamedExports: ["default"] }] }, // "default" does not disallow re-exporting a renamed default export from another module - { code: "export { default as a } from 'foo';", options: [{ restrictedNamedExports: ["default"] }] } + { code: "export { default as a } from 'foo';", options: [{ restrictedNamedExports: ["default"] }] }, + + // restrictDefaultExports.direct option + { code: "export default foo;", options: [{ restrictDefaultExports: { direct: false } }] }, + { code: "export default 42;", options: [{ restrictDefaultExports: { direct: false } }] }, + { code: "export default function foo() {}", options: [{ restrictDefaultExports: { direct: false } }] }, + + // restrictDefaultExports.named option + { code: "const foo = 123;\nexport { foo as default };", options: [{ restrictDefaultExports: { named: false } }] }, + + // restrictDefaultExports.defaultFrom option + { code: "export { default } from 'mod';", options: [{ restrictDefaultExports: { defaultFrom: false } }] }, + { code: "export { default as default } from 'mod';", options: [{ restrictDefaultExports: { defaultFrom: false } }] }, + { code: "export { foo as default } from 'mod';", options: [{ restrictDefaultExports: { defaultFrom: true } }] }, + { code: "export { default } from 'mod';", options: [{ restrictDefaultExports: { named: true, defaultFrom: false } }] }, + { code: "export { 'default' } from 'mod'; ", options: [{ restrictDefaultExports: { defaultFrom: false } }] }, + + // restrictDefaultExports.namedFrom option + { code: "export { foo as default } from 'mod';", options: [{ restrictDefaultExports: { namedFrom: false } }] }, + { code: "export { default as default } from 'mod';", options: [{ restrictDefaultExports: { namedFrom: true } }] }, + { code: "export { default as default } from 'mod';", options: [{ restrictDefaultExports: { namedFrom: false } }] }, + { code: "export { 'default' } from 'mod'; ", options: [{ restrictDefaultExports: { defaultFrom: false, namedFrom: true } }] }, + + // restrictDefaultExports.namespaceFrom option + { code: "export * as default from 'mod';", options: [{ restrictDefaultExports: { namespaceFrom: false } }] } ], invalid: [ @@ -519,6 +543,66 @@ ruleTester.run("no-restricted-exports", rule, { code: "export { default } from 'foo';", options: [{ restrictedNamedExports: ["default"] }], errors: [{ messageId: "restrictedNamed", data: { name: "default" }, type: "Identifier", column: 10 }] + }, + + // restrictDefaultExports.direct option + { + code: "export default foo;", + options: [{ restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + { + code: "export default 42;", + options: [{ restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + { + code: "export default function foo() {}", + options: [{ restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + { + code: "export default foo;", + options: [{ restrictedNamedExports: ["bar"], restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + + // restrictDefaultExports.named option + { + code: "const foo = 123;\nexport { foo as default };", + options: [{ restrictDefaultExports: { named: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 2, column: 17 }] + }, + + // restrictDefaultExports.defaultFrom option + { + code: "export { default } from 'mod';", + options: [{ restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 10 }] + }, + { + code: "export { default as default } from 'mod';", + options: [{ restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 21 }] + }, + { + code: "export { 'default' } from 'mod';", + options: [{ restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Literal", line: 1, column: 10 }] + }, + + // restrictDefaultExports.namedFrom option + { + code: "export { foo as default } from 'mod';", + options: [{ restrictDefaultExports: { namedFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 17 }] + }, + + // restrictDefaultExports.namespaceFrom option + { + code: "export * as default from 'mod';", + options: [{ restrictDefaultExports: { namespaceFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 13 }] } ] });