Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support arbitrary module namespace names in no-restricted-exports #15478

Merged
merged 2 commits into from Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 16 additions & 8 deletions docs/rules/no-restricted-exports.md
Expand Up @@ -18,7 +18,7 @@ Examples of **incorrect** code for this rule:

```js
/*eslint no-restricted-exports: ["error", {
"restrictedNamedExports": ["foo", "bar", "Baz", "a", "b", "c", "d"]
"restrictedNamedExports": ["foo", "bar", "Baz", "a", "b", "c", "d", "e", "👍"]
}]*/

export const foo = 1;
Expand All @@ -33,16 +33,20 @@ export { a };
function someFunction() {}
export { someFunction as b };

export { c } from 'some_module';
export { c } from "some_module";

export { something as d } from 'some_module';
export { "d" } from "some_module";

export { something as e } from "some_module";

export { "👍" } from "some_module";
```

Examples of **correct** code for this rule:

```js
/*eslint no-restricted-exports: ["error", {
"restrictedNamedExports": ["foo", "bar", "Baz", "a", "b", "c", "d"]
"restrictedNamedExports": ["foo", "bar", "Baz", "a", "b", "c", "d", "e", "👍"]
}]*/

export const quux = 1;
Expand All @@ -57,9 +61,13 @@ export { a as myObject };
function someFunction() {}
export { someFunction };

export { c as someName } from 'some_module';
export { c as someName } from "some_module";

export { "d" as " d " } from "some_module";

export { something } from "some_module";

export { something } from 'some_module';
export { "👍" as thumbsUp } from "some_module";
```

### Default exports
Expand All @@ -79,7 +87,7 @@ export { foo as default };
```js
/*eslint no-restricted-exports: ["error", { "restrictedNamedExports": ["default"] }]*/

export { default } from 'some_module';
export { default } from "some_module";
```

Examples of additional **correct** code for this rule:
Expand All @@ -102,5 +110,5 @@ export function foo() {}
//----- my_module.js -----
/*eslint no-restricted-exports: ["error", { "restrictedNamedExports": ["foo"] }]*/

export * from 'some_module'; // allowed, although this declaration exports "foo" from my_module
export * from "some_module"; // allowed, although this declaration exports "foo" from my_module
```
12 changes: 9 additions & 3 deletions lib/rules/no-restricted-exports.js
Expand Up @@ -5,6 +5,12 @@

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -44,12 +50,12 @@ module.exports = {
const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports);

/**
* Checks and reports given exported identifier.
* @param {ASTNode} node exported `Identifier` node to check.
* Checks and reports given exported name.
* @param {ASTNode} node exported `Identifier` or string `Literal` node to check.
* @returns {void}
*/
function checkExportedName(node) {
const name = node.name;
const name = astUtils.getModuleExportName(node);

if (restrictedNames.has(name)) {
context.report({
Expand Down
22 changes: 21 additions & 1 deletion lib/rules/utils/ast-utils.js
Expand Up @@ -769,6 +769,25 @@ function getSwitchCaseColonToken(node, sourceCode) {
return sourceCode.getFirstToken(node, 1);
}

/**
* Gets ESM module export name represented by the given node.
* @param {ASTNode} node `Identifier` or string `Literal` node in a position
* that represents a module export name:
* - `ImportSpecifier#imported`
* - `ExportSpecifier#local` (if it is a re-export from another module)
* - `ExportSpecifier#exported`
* - `ExportAllDeclaration#exported`
* @returns {string} The module export name.
*/
function getModuleExportName(node) {
if (node.type === "Identifier") {
return node.name;
}

// string literal
return node.value;
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -1898,5 +1917,6 @@ module.exports = {
equalLiteralValue,
isSameReference,
isLogicalAssignmentOperator,
getSwitchCaseColonToken
getSwitchCaseColonToken,
getModuleExportName
};
64 changes: 62 additions & 2 deletions tests/lib/rules/no-restricted-exports.js
Expand Up @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester");
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020, sourceType: "module" } });
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022, sourceType: "module" } });

ruleTester.run("no-restricted-exports", rule, {
valid: [
Expand Down Expand Up @@ -57,8 +57,12 @@ ruleTester.run("no-restricted-exports", rule, {
{ code: "var b; export { b as a };", options: [{ restrictedNamedExports: ["x"] }] },
{ code: "export { a } from 'foo';", options: [{ restrictedNamedExports: ["x"] }] },
{ code: "export { b as a } from 'foo';", options: [{ restrictedNamedExports: ["x"] }] },
{ code: "export { '' } from 'foo';", options: [{ restrictedNamedExports: ["undefined"] }] },
{ code: "export { '' } from 'foo';", options: [{ restrictedNamedExports: [" "] }] },
{ code: "export { ' ' } from 'foo';", options: [{ restrictedNamedExports: [""] }] },
{ code: "export { ' a', 'a ' } from 'foo';", options: [{ restrictedNamedExports: ["a"] }] },

// does not mistakenly disallow non-exported identifiers that appear in named export declarations
// does not mistakenly disallow non-exported names that appear in named export declarations
{ code: "export var b = a;", options: [{ restrictedNamedExports: ["a"] }] },
{ code: "export let [b = a] = [];", options: [{ restrictedNamedExports: ["a"] }] },
{ code: "export const [b] = [a];", options: [{ restrictedNamedExports: ["a"] }] },
Expand All @@ -69,7 +73,10 @@ ruleTester.run("no-restricted-exports", rule, {
{ code: "export class A { a(){} }", options: [{ restrictedNamedExports: ["a"] }] },
{ code: "export class A extends B {}", options: [{ restrictedNamedExports: ["B"] }] },
{ code: "var a; export { a as b };", options: [{ restrictedNamedExports: ["a"] }] },
{ code: "var a; export { a as 'a ' };", options: [{ restrictedNamedExports: ["a"] }] },
{ code: "export { a as b } from 'foo';", options: [{ restrictedNamedExports: ["a"] }] },
{ code: "export { a as 'a ' } from 'foo';", options: [{ restrictedNamedExports: ["a"] }] },
{ code: "export { 'a' as 'a ' } from 'foo';", options: [{ restrictedNamedExports: ["a"] }] },

// does not check source in re-export declarations
{ code: "export { b } from 'a';", options: [{ restrictedNamedExports: ["a"] }] },
Expand Down Expand Up @@ -188,6 +195,59 @@ ruleTester.run("no-restricted-exports", rule, {
errors: [{ messageId: "restrictedNamed", data: { name: "a" }, type: "Identifier" }]
},

// string literals
{
code: "let a; export { a as 'a' };",
options: [{ restrictedNamedExports: ["a"] }],
errors: [{ messageId: "restrictedNamed", data: { name: "a" }, type: "Literal", column: 22 }]
},
{
code: "let a; export { a as 'b' };",
options: [{ restrictedNamedExports: ["b"] }],
errors: [{ messageId: "restrictedNamed", data: { name: "b" }, type: "Literal", column: 22 }]
},
{
code: "let a; export { a as ' b ' };",
options: [{ restrictedNamedExports: [" b "] }],
errors: [{ messageId: "restrictedNamed", data: { name: " b " }, type: "Literal", column: 22 }]
},
{
code: "let a; export { a as '👍' };",
options: [{ restrictedNamedExports: ["👍"] }],
errors: [{ messageId: "restrictedNamed", data: { name: "👍" }, type: "Literal", column: 22 }]
},
{
code: "export { 'a' } from 'foo';",
options: [{ restrictedNamedExports: ["a"] }],
errors: [{ messageId: "restrictedNamed", data: { name: "a" }, type: "Literal" }]
},
{
code: "export { '' } from 'foo';",
options: [{ restrictedNamedExports: [""] }],
errors: [{ messageId: "restrictedNamed", data: { name: "" }, type: "Literal" }]
},
{
code: "export { ' ' } from 'foo';",
options: [{ restrictedNamedExports: [" "] }],
errors: [{ messageId: "restrictedNamed", data: { name: " " }, type: "Literal" }]
},
{
code: "export { b as 'a' } from 'foo';",
options: [{ restrictedNamedExports: ["a"] }],
errors: [{ messageId: "restrictedNamed", data: { name: "a" }, type: "Literal" }]
},
{
code: "export { b as '\\u0061' } from 'foo';",
options: [{ restrictedNamedExports: ["a"] }],
errors: [{ messageId: "restrictedNamed", data: { name: "a" }, type: "Literal" }]
},
{
code: "export * as 'a' from 'foo';",
options: [{ restrictedNamedExports: ["a"] }],
errors: [{ messageId: "restrictedNamed", data: { name: "a" }, type: "Literal" }]
},


// destructuring
{
code: "export var [a] = [];",
Expand Down