From 206d305a682afd665abc68b83d80f9192b45715d Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sat, 14 Jan 2023 14:52:18 +0530 Subject: [PATCH 01/14] feat: add `restrictDefaultExports` option to no-restricted-exports rule --- lib/rules/no-restricted-exports.js | 48 +++++++++++++++++++++- tests/lib/rules/no-restricted-exports.js | 52 +++++++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index d99e8928209..55a26284681 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -35,19 +35,51 @@ module.exports = { type: "string" }, 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 };` format + named: { + type: "boolean" + }, + + // Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` format + defaultFrom: { + type: "boolean" + }, + + // Allow/Disallow `export { foo as default } from "mod";` format + namedFrom: { + type: "boolean" + }, + + // Allow/Disallow `export * as default from "mod"`; format + namespaceFrom: { + type: "boolean" + } + } } }, 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 the 'default' value is restricted." } }, create(context) { const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); + const restrictDefaultExports = context.options[0] && !restrictedNames.has("default") && context.options[0].restrictDefaultExports; /** * Checks and reports given exported name. @@ -63,6 +95,11 @@ module.exports = { messageId: "restrictedNamed", data: { name } }); + } else if (name === "default" && restrictDefaultExports && restrictDefaultExports.named) { + context.report({ + node, + messageId: "restrictedDefault" + }); } } @@ -73,6 +110,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..4fb1c8f272d 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -107,7 +107,16 @@ 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 } }] }, + { code: "export default foo;", options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { direct: true } }] }, + + // restrictDefaultExports.named option + { code: "const foo = 123;\nexport { foo as default };", options: [{ restrictDefaultExports: { named: false } }] } ], invalid: [ @@ -519,6 +528,47 @@ 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 }] + }, + + // restrictedNamedExports should take priority over restrictDefaultExports.named + { + code: "const foo = 123;\nexport { foo as default };", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { named: false } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 2, column: 17 }] + }, + { + code: "const foo = 123;\nexport { foo as default };", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { named: true } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 2, column: 17 }] } ] }); From ddfcaaf805f9dd00846e2ed98c96cab2bcc9622b Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sat, 14 Jan 2023 15:22:20 +0530 Subject: [PATCH 02/14] feat: add restrictDefaultExports.defaultFrom --- lib/rules/no-restricted-exports.js | 26 +++++++++++++++++++----- tests/lib/rules/no-restricted-exports.js | 23 ++++++++++++++++++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index 55a26284681..5be79766bff 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -95,11 +95,27 @@ module.exports = { messageId: "restrictedNamed", data: { name } }); - } else if (name === "default" && restrictDefaultExports && restrictDefaultExports.named) { - context.report({ - node, - messageId: "restrictedDefault" - }); + + return; + } + + if (name === "default") { + const isDefaultFromFormat = node.parent.parent.source; + + if (!isDefaultFromFormat && restrictDefaultExports && restrictDefaultExports.named) { + context.report({ + node, + messageId: "restrictedDefault" + }); + return; + } + + if (isDefaultFromFormat && restrictDefaultExports && restrictDefaultExports.defaultFrom) { + context.report({ + node, + messageId: "restrictedDefault" + }); + } } } diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index 4fb1c8f272d..dbf29903fca 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -116,7 +116,11 @@ ruleTester.run("no-restricted-exports", rule, { { code: "export default foo;", options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { direct: true } }] }, // restrictDefaultExports.named option - { code: "const foo = 123;\nexport { foo as default };", options: [{ restrictDefaultExports: { named: false } }] } + { 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 } from 'mod';", options: [{ restrictDefaultExports: { named: true, defaultFrom: false } }] } ], invalid: [ @@ -569,6 +573,23 @@ ruleTester.run("no-restricted-exports", rule, { code: "const foo = 123;\nexport { foo as default };", options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { named: true } }], errors: [{ messageId: "restrictedNamed", 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 } from 'mod';", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 10 }] + }, + { + code: "export { default } from 'mod';", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: false } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 10 }] } ] }); From 366991278e251439f44ea29e3533afeb16c30829 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sat, 14 Jan 2023 15:37:49 +0530 Subject: [PATCH 03/14] feat: add restrictDefaultExports.defaultFrom --- lib/rules/no-restricted-exports.js | 5 +++-- tests/lib/rules/no-restricted-exports.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index 5be79766bff..4deed6d2c1f 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -95,12 +95,13 @@ module.exports = { messageId: "restrictedNamed", data: { name } }); - return; } if (name === "default") { const isDefaultFromFormat = node.parent.parent.source; + const specifierLocalName = node.parent.local.name; + console.log("🚀 ~ file: no-restricted-exports.js:104 ~ checkExportedName ~ specifierLocalName", specifierLocalName) if (!isDefaultFromFormat && restrictDefaultExports && restrictDefaultExports.named) { context.report({ @@ -110,7 +111,7 @@ module.exports = { return; } - if (isDefaultFromFormat && restrictDefaultExports && restrictDefaultExports.defaultFrom) { + if (isDefaultFromFormat && specifierLocalName === "default" && restrictDefaultExports && restrictDefaultExports.defaultFrom) { context.report({ node, messageId: "restrictedDefault" diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index dbf29903fca..d642f6fca2b 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -120,6 +120,8 @@ ruleTester.run("no-restricted-exports", rule, { // 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 } }] } ], @@ -581,15 +583,30 @@ ruleTester.run("no-restricted-exports", rule, { 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: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: true } }], errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 10 }] }, + { + code: "export { default as default } from 'mod';", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 21 }] + }, { code: "export { default } from 'mod';", options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: false } }], errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 10 }] + }, + { + code: "export { default as default } from 'mod';", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: false } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 21 }] } ] }); From a80c1de4d99bbff34b4740062fa2b07bd34951d0 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sat, 14 Jan 2023 16:10:47 +0530 Subject: [PATCH 04/14] feat: add restrictDefaultExports.namedFrom --- lib/rules/no-restricted-exports.js | 16 ++++++++++------ tests/lib/rules/no-restricted-exports.js | 24 +++++++++++++++++++++++- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index 4deed6d2c1f..8173cb6773f 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -101,7 +101,6 @@ module.exports = { if (name === "default") { const isDefaultFromFormat = node.parent.parent.source; const specifierLocalName = node.parent.local.name; - console.log("🚀 ~ file: no-restricted-exports.js:104 ~ checkExportedName ~ specifierLocalName", specifierLocalName) if (!isDefaultFromFormat && restrictDefaultExports && restrictDefaultExports.named) { context.report({ @@ -111,11 +110,16 @@ module.exports = { return; } - if (isDefaultFromFormat && specifierLocalName === "default" && restrictDefaultExports && restrictDefaultExports.defaultFrom) { - context.report({ - node, - messageId: "restrictedDefault" - }); + if (isDefaultFromFormat) { + if ( + (specifierLocalName === "default" && restrictDefaultExports && restrictDefaultExports.defaultFrom) || + (specifierLocalName !== "default" && restrictDefaultExports && restrictDefaultExports.namedFrom) + ) { + context.report({ + node, + messageId: "restrictedDefault" + }); + } } } } diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index d642f6fca2b..eea816f2d66 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -122,7 +122,12 @@ ruleTester.run("no-restricted-exports", rule, { { 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: { named: true, 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 } }] } ], invalid: [ @@ -607,6 +612,23 @@ ruleTester.run("no-restricted-exports", rule, { code: "export { default as default } from 'mod';", options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: false } }], errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 21 }] + }, + + // restrictDefaultExports.namedFrom option + { + code: "export { foo as default } from 'mod';", + options: [{ restrictDefaultExports: { namedFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 17 }] + }, + { + code: "export { foo as default } from 'mod';", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { namedFrom: true } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 17 }] + }, + { + code: "export { foo as default } from 'mod';", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { namedFrom: false } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 17 }] } ] }); From c27ea9789b2b0d958c2706a9e7fc0140ad20f3b2 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sat, 14 Jan 2023 16:16:28 +0530 Subject: [PATCH 05/14] feat: add restrictDefaultExports.namedFrom --- lib/rules/no-restricted-exports.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index 8173cb6773f..131c11e88fe 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -100,7 +100,7 @@ module.exports = { if (name === "default") { const isDefaultFromFormat = node.parent.parent.source; - const specifierLocalName = node.parent.local.name; + const specifierLocalName = node.parent.local && node.parent.local.name; if (!isDefaultFromFormat && restrictDefaultExports && restrictDefaultExports.named) { context.report({ From a45178ba8a9a88b9b4f2c5e983d24e089558b0e0 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sun, 15 Jan 2023 08:04:09 +0530 Subject: [PATCH 06/14] feat: add restrictDefaultExports.namespaceFrom --- lib/rules/no-restricted-exports.js | 11 ++++++----- tests/lib/rules/no-restricted-exports.js | 24 +++++++++++++++++++++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index 131c11e88fe..353c669b6d8 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -99,10 +99,10 @@ module.exports = { } if (name === "default") { - const isDefaultFromFormat = node.parent.parent.source; + const isSourceSpecified = node.parent.source || node.parent.parent.source; const specifierLocalName = node.parent.local && node.parent.local.name; - if (!isDefaultFromFormat && restrictDefaultExports && restrictDefaultExports.named) { + if (!isSourceSpecified && restrictDefaultExports && restrictDefaultExports.named) { context.report({ node, messageId: "restrictedDefault" @@ -110,10 +110,11 @@ module.exports = { return; } - if (isDefaultFromFormat) { + if (isSourceSpecified && restrictDefaultExports) { if ( - (specifierLocalName === "default" && restrictDefaultExports && restrictDefaultExports.defaultFrom) || - (specifierLocalName !== "default" && restrictDefaultExports && restrictDefaultExports.namedFrom) + (specifierLocalName === "default" && restrictDefaultExports.defaultFrom) || + (specifierLocalName !== "default" && restrictDefaultExports.namedFrom) || + (node.parent.type === "ExportAllDeclaration" && restrictDefaultExports.namespaceFrom) ) { context.report({ node, diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index eea816f2d66..9b1767a7fce 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -127,7 +127,10 @@ ruleTester.run("no-restricted-exports", rule, { // 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 as default } from 'mod';", options: [{ restrictDefaultExports: { namedFrom: false } }] }, + + // restrictDefaultExports.namespaceFrom option + { code: "export * as default from 'mod';", options: [{ restrictDefaultExports: { namespaceFrom: false } }] } ], invalid: [ @@ -569,8 +572,6 @@ ruleTester.run("no-restricted-exports", rule, { options: [{ restrictDefaultExports: { named: true } }], errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 2, column: 17 }] }, - - // restrictedNamedExports should take priority over restrictDefaultExports.named { code: "const foo = 123;\nexport { foo as default };", options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { named: false } }], @@ -629,6 +630,23 @@ ruleTester.run("no-restricted-exports", rule, { code: "export { foo as default } from 'mod';", options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { namedFrom: false } }], errors: [{ messageId: "restrictedNamed", 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 }] + }, + { + code: "export * as default from 'mod';", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { namespaceFrom: false } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 13 }] + }, + { + code: "export * as default from 'mod';", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { namespaceFrom: true } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 13 }] } ] }); From 5b311b0db6db53b6d1fc0e475cc2891adb0d125d Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sun, 15 Jan 2023 08:38:47 +0530 Subject: [PATCH 07/14] docs: add `restrictDefaultExports` option --- docs/src/rules/no-restricted-exports.md | 99 ++++++++++++++++++++++-- lib/rules/no-restricted-exports.js | 8 +- tests/lib/rules/no-restricted-exports.js | 10 +-- 3 files changed, 102 insertions(+), 15 deletions(-) diff --git a/docs/src/rules/no-restricted-exports.md b/docs/src/rules/no-restricted-exports.md index 8a40852e067..8ecf5114418 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 default exports in different formats. 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 353c669b6d8..c8862922326 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -45,22 +45,22 @@ module.exports = { type: "boolean" }, - // Allow/Disallow `export { foo as default };` format + // Allow/Disallow `export { foo as default };` declarations named: { type: "boolean" }, - // Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` format + // Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` declarations defaultFrom: { type: "boolean" }, - // Allow/Disallow `export { foo as default } from "mod";` format + // Allow/Disallow `export { foo as default } from "mod";` declarations namedFrom: { type: "boolean" }, - // Allow/Disallow `export * as default from "mod"`; format + // Allow/Disallow `export * as default from "mod"`; declarations namespaceFrom: { type: "boolean" } diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index 9b1767a7fce..2d0e84ad087 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -599,16 +599,16 @@ ruleTester.run("no-restricted-exports", rule, { options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: true } }], errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 10 }] }, - { - code: "export { default as default } from 'mod';", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: true } }], - errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 21 }] - }, { code: "export { default } from 'mod';", options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: false } }], errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 10 }] }, + { + code: "export { default as default } from 'mod';", + options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 21 }] + }, { code: "export { default as default } from 'mod';", options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: false } }], From ef210d3141b2f10f5f6f8765eccd595317486a6d Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sun, 15 Jan 2023 08:46:55 +0530 Subject: [PATCH 08/14] docs: fix lint isuues --- docs/src/rules/no-restricted-exports.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/rules/no-restricted-exports.md b/docs/src/rules/no-restricted-exports.md index 8ecf5114418..91b51592520 100644 --- a/docs/src/rules/no-restricted-exports.md +++ b/docs/src/rules/no-restricted-exports.md @@ -18,11 +18,11 @@ 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 default exports in different formats. 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. + * `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. ### restrictedNamedExports From a961188c608d2e90f48dea67f3cb97aa45785ac7 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sun, 15 Jan 2023 08:51:40 +0530 Subject: [PATCH 09/14] docs: update sentence --- docs/src/rules/no-restricted-exports.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/rules/no-restricted-exports.md b/docs/src/rules/no-restricted-exports.md index 91b51592520..c43a4d895dd 100644 --- a/docs/src/rules/no-restricted-exports.md +++ b/docs/src/rules/no-restricted-exports.md @@ -17,7 +17,7 @@ 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 default exports in different formats. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. The following properties are allowed: +* `"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. From 010651de00c480d27bb400f80ad20649eb9be333 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Tue, 17 Jan 2023 18:58:28 +0530 Subject: [PATCH 10/14] fix: update rule schema --- lib/rules/no-restricted-exports.js | 86 ++++++++++++++---------- tests/lib/rules/no-restricted-exports.js | 51 -------------- 2 files changed, 52 insertions(+), 85 deletions(-) diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index c8862922326..100a6b612ce 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -28,46 +28,64 @@ module.exports = { schema: [{ type: "object", - properties: { - restrictedNamedExports: { - type: "array", - items: { - type: "string" + anyOf: [ + { + properties: { + restrictedNamedExports: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } }, - uniqueItems: true + additionalProperties: false }, - 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" + restrictedNamedExports: { + type: "array", + items: { + type: "string", + pattern: "^(?!default$)" + }, + uniqueItems: true }, - - // Allow/Disallow `export { foo as default } from "mod";` declarations - namedFrom: { - type: "boolean" - }, - - // Allow/Disallow `export * as default from "mod"`; declarations - namespaceFrom: { - type: "boolean" + 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: { diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index 2d0e84ad087..49827c38443 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -113,7 +113,6 @@ ruleTester.run("no-restricted-exports", rule, { { 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 } }] }, - { code: "export default foo;", options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { direct: true } }] }, // restrictDefaultExports.named option { code: "const foo = 123;\nexport { foo as default };", options: [{ restrictDefaultExports: { named: false } }] }, @@ -572,16 +571,6 @@ ruleTester.run("no-restricted-exports", rule, { options: [{ restrictDefaultExports: { named: true } }], errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 2, column: 17 }] }, - { - code: "const foo = 123;\nexport { foo as default };", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { named: false } }], - errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 2, column: 17 }] - }, - { - code: "const foo = 123;\nexport { foo as default };", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { named: true } }], - errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 2, column: 17 }] - }, // restrictDefaultExports.defaultFrom option { @@ -594,26 +583,6 @@ ruleTester.run("no-restricted-exports", rule, { options: [{ restrictDefaultExports: { defaultFrom: true } }], errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 21 }] }, - { - code: "export { default } from 'mod';", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: true } }], - errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 10 }] - }, - { - code: "export { default } from 'mod';", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: false } }], - errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 10 }] - }, - { - code: "export { default as default } from 'mod';", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: true } }], - errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 21 }] - }, - { - code: "export { default as default } from 'mod';", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { defaultFrom: false } }], - errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 21 }] - }, // restrictDefaultExports.namedFrom option { @@ -621,32 +590,12 @@ ruleTester.run("no-restricted-exports", rule, { options: [{ restrictDefaultExports: { namedFrom: true } }], errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 17 }] }, - { - code: "export { foo as default } from 'mod';", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { namedFrom: true } }], - errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 17 }] - }, - { - code: "export { foo as default } from 'mod';", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { namedFrom: false } }], - errors: [{ messageId: "restrictedNamed", 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 }] - }, - { - code: "export * as default from 'mod';", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { namespaceFrom: false } }], - errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 13 }] - }, - { - code: "export * as default from 'mod';", - options: [{ restrictedNamedExports: ["default"], restrictDefaultExports: { namespaceFrom: true } }], - errors: [{ messageId: "restrictedNamed", type: "Identifier", line: 1, column: 13 }] } ] }); From 037b30c60503c870fd0cdda121a41e5d7e3fa61b Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Tue, 17 Jan 2023 19:18:43 +0530 Subject: [PATCH 11/14] fix: update rule schema --- lib/rules/no-restricted-exports.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index 100a6b612ce..c3b44b68387 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -27,9 +27,9 @@ module.exports = { }, schema: [{ - type: "object", anyOf: [ { + type: "object", properties: { restrictedNamedExports: { type: "array", @@ -42,6 +42,7 @@ module.exports = { additionalProperties: false }, { + type: "object", properties: { restrictedNamedExports: { type: "array", From 4bf73804d94bf8443bcc868febd594f3c1253768 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Fri, 20 Jan 2023 07:29:23 +0530 Subject: [PATCH 12/14] fix: use astUtils.getModuleExportName --- lib/rules/no-restricted-exports.js | 41 +++++++++++++++--------- tests/lib/rules/no-restricted-exports.js | 7 ++++ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index c3b44b68387..d57c5438859 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -118,27 +118,38 @@ module.exports = { } if (name === "default") { - const isSourceSpecified = node.parent.source || node.parent.parent.source; - const specifierLocalName = node.parent.local && node.parent.local.name; + if (node.parent.type === "ExportAllDeclaration") { + const isSourceSpecified = node.parent.source; - if (!isSourceSpecified && restrictDefaultExports && restrictDefaultExports.named) { - context.report({ - node, - messageId: "restrictedDefault" - }); - return; - } + if (isSourceSpecified && 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) { - if ( - (specifierLocalName === "default" && restrictDefaultExports.defaultFrom) || - (specifierLocalName !== "default" && restrictDefaultExports.namedFrom) || - (node.parent.type === "ExportAllDeclaration" && restrictDefaultExports.namespaceFrom) - ) { + 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" + }); + } } } } diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index 49827c38443..18c8db99008 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -122,11 +122,13 @@ ruleTester.run("no-restricted-exports", rule, { { 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 } }] } @@ -583,6 +585,11 @@ ruleTester.run("no-restricted-exports", rule, { 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 { From 70fa35de119fc4808bc14916ad2e73e14b2a35d8 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sat, 21 Jan 2023 06:35:03 +0530 Subject: [PATCH 13/14] refactor: apply suggestions --- lib/rules/no-restricted-exports.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index d57c5438859..d9d72dff9e5 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -98,7 +98,7 @@ module.exports = { create(context) { const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); - const restrictDefaultExports = context.options[0] && !restrictedNames.has("default") && context.options[0].restrictDefaultExports; + const restrictDefaultExports = context.options[0] && context.options[0].restrictDefaultExports; /** * Checks and reports given exported name. @@ -119,9 +119,7 @@ module.exports = { if (name === "default") { if (node.parent.type === "ExportAllDeclaration") { - const isSourceSpecified = node.parent.source; - - if (isSourceSpecified && restrictDefaultExports && restrictDefaultExports.namespaceFrom) { + if (restrictDefaultExports && restrictDefaultExports.namespaceFrom) { context.report({ node, messageId: "restrictedDefault" @@ -129,7 +127,7 @@ module.exports = { } } else { // ExportSpecifier - const isSourceSpecified = node.parent.parent.source; + const isSourceSpecified = !!node.parent.parent.source; const specifierLocalName = astUtils.getModuleExportName(node.parent.local); if (!isSourceSpecified && restrictDefaultExports && restrictDefaultExports.named) { From 5bf2be1cc5073ed218d76fb15b61bac778ca19c1 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Tue, 24 Jan 2023 07:54:02 +0530 Subject: [PATCH 14/14] refactor: update the error message Co-authored-by: Milos Djermanovic --- docs/src/rules/no-restricted-exports.md | 2 +- lib/rules/no-restricted-exports.js | 2 +- tests/lib/rules/no-restricted-exports.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/rules/no-restricted-exports.md b/docs/src/rules/no-restricted-exports.md index c43a4d895dd..2f83643f9cb 100644 --- a/docs/src/rules/no-restricted-exports.md +++ b/docs/src/rules/no-restricted-exports.md @@ -145,7 +145,7 @@ Examples of **incorrect** code for the `"restrictDefaultExports": { "direct": tr export default foo; export default 42; -export default function foo() {}; +export default function foo() {} ``` ::: diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index d9d72dff9e5..d201e3b03a6 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -91,7 +91,7 @@ module.exports = { messages: { restrictedNamed: "'{{name}}' is restricted from being used as an exported name.", - restrictedDefault: "Exporting the 'default' value is restricted." + restrictedDefault: "Exporting 'default' is restricted." } }, diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index 18c8db99008..9505e8ff6c3 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -557,7 +557,7 @@ ruleTester.run("no-restricted-exports", rule, { errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] }, { - code: "export default function foo() {};", + code: "export default function foo() {}", options: [{ restrictDefaultExports: { direct: true } }], errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] },