Skip to content

Commit

Permalink
feat: Support arbitrary module namespace names in no-restricted-expor…
Browse files Browse the repository at this point in the history
…ts (#15478)

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

Refs #15465

* align quotes
  • Loading branch information
mdjermanovic authored Jan 5, 2022
1 parent 6278281 commit fd3683f
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 14 deletions.
24 changes: 16 additions & 8 deletions docs/rules/no-restricted-exports.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit fd3683f

Please sign in to comment.