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: add no-re-export #27

Merged
merged 5 commits into from
Nov 21, 2023
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
1 change: 1 addition & 0 deletions .README/README.md
Expand Up @@ -93,6 +93,7 @@ See [ESLint documentation](https://eslint.org/docs/user-guide/configuring/config
{"gitdown": "include", "file": "./rules/no-barrel-import.md"}
{"gitdown": "include", "file": "./rules/no-export-all.md"}
{"gitdown": "include", "file": "./rules/no-import-namespace-destructure.md"}
{"gitdown": "include", "file": "./rules/no-re-export.md"}
{"gitdown": "include", "file": "./rules/no-reassign-imports.md"}
{"gitdown": "include", "file": "./rules/no-restricted-imports.md"}
{"gitdown": "include", "file": "./rules/no-restricted-strings.md"}
Expand Down
8 changes: 8 additions & 0 deletions .README/rules/no-re-export.md
@@ -0,0 +1,8 @@
### `no-re-export`

Disallows re-exports of imports.

<!-- assertions noReExport -->

> [!NOTE]
> This rule was originally developed by @christianvuerings as part of https://github.com/christianvuerings/eslint-plugin-no-re-export
74 changes: 74 additions & 0 deletions README.md
Expand Up @@ -1071,6 +1071,80 @@ import * as bar from 'bar'
</details>


<a name="user-content-eslint-plugin-canonical-rules-no-re-export"></a>
<a name="eslint-plugin-canonical-rules-no-re-export"></a>
### <code>no-re-export</code>

Disallows re-exports of imports.

<details><summary>📖 Examples</summary>
The following patterns are considered problems:

```js

import Button1 from 'app/CustomButton';
export const CustomButton = Button1;

// Message: undefined


import { Button as CustomButton2 } from 'app/CustomButton';
export const CustomButton = CustomButton2;

// Message: undefined


import * as Button3 from "app/Button";
export const CustomButton = Button3;

// Message: undefined


import Button4 from 'app/CustomButton';
export default Button4;

// Message: undefined


export { default as Button5 } from 'app/CustomButton';

// Message: undefined


import Button6 from 'app/CustomButton';
export {
Button6
};

// Message: undefined


import Button7 from 'app/CustomButton';
export const Buttons = {
Button: Button7
};

// Message: undefined


import Button8 from 'app/CustomButton';
export default Button8;
export { Button8 }

// Message: undefined
// Message: undefined


export * from 'app/CustomButton';

// Message: undefined
```

</details>


> [!NOTE]
> This rule was originally developed by @christianvuerings as part of https://github.com/christianvuerings/eslint-plugin-no-re-export
<a name="user-content-eslint-plugin-canonical-rules-no-reassign-imports"></a>
<a name="eslint-plugin-canonical-rules-no-reassign-imports"></a>
### <code>no-reassign-imports</code>
Expand Down
146 changes: 146 additions & 0 deletions src/rules/noReExport.ts
@@ -0,0 +1,146 @@
/**
* The code is adapted from https://github.com/christianvuerings/eslint-plugin-no-re-export
*/
import { type TSESTree } from '@typescript-eslint/types';
import { AST_NODE_TYPES } from '@typescript-eslint/types';
import { createRule } from '../utilities';

type Options = [];

type MessageIds = 'noReExport';

type ReportInfo = {
location: TSESTree.SourceLocation;
type:
| 'DefaultReExport'
| 'ExportDefault'
| 'ExportObject'
| 'ObjectProperty'
| 'Variable';
};

export default createRule<Options, MessageIds>({
create(context) {
const imports = new Map();
const exports: Record<string, ReportInfo[]> = {};

const appendToExports = (name: string, reportInfo: ReportInfo) => {
exports[name] = [...(exports[name] ? exports[name] : []), reportInfo];
};

return {
// export * from 'app/CustomCustomButton'
ExportAllDeclaration(node) {
context.report({
data: {
name: node.source.value,
},
loc: node.loc,
messageId: 'noReExport',
});
},

ExportDefaultDeclaration(node) {
if (node.declaration.type === AST_NODE_TYPES.Identifier) {
// export default Button
appendToExports(node.declaration.name, {
location: node.declaration.loc,
type: 'ExportDefault',
});
}
},

ExportNamedDeclaration(node) {
if (
node.declaration &&
node.declaration?.type === AST_NODE_TYPES.VariableDeclaration &&
node.declaration.declarations
) {
for (const declaration of node.declaration.declarations) {
if (
declaration.init?.type === AST_NODE_TYPES.Identifier &&
declaration.init?.name
) {
// export const CustomButtom = Button;
appendToExports(declaration.init.name, {
location: declaration.init.loc,
type: 'Variable',
});
} else if (
declaration.init?.type === AST_NODE_TYPES.ObjectExpression &&
declaration.init.properties
) {
for (const property of declaration.init.properties) {
if (
property?.type === AST_NODE_TYPES.Property &&
property.value.type === AST_NODE_TYPES.Identifier &&
property.value?.name
) {
// export const CustomButton = { Button }
appendToExports(property.value.name, {
location: property.loc,
type: 'ObjectProperty',
});
}
}
}
}
} else if (node.specifiers) {
for (const specifier of node.specifiers) {
if (node.source && specifier.local.name === 'default') {
// export { default as Button } from 'app/CustomButtom';
appendToExports(specifier.exported.name, {
location: specifier.exported.loc,
type: 'DefaultReExport',
});
} else {
// export { Button }
appendToExports(specifier.local.name, {
location: specifier.local.loc,
type: 'ExportObject',
});
}
}
}
},

ImportDefaultSpecifier(node) {
imports.set(node.local.name, { node });
},
ImportNamespaceSpecifier(node) {
imports.set(node.local.name, { node });
},
ImportSpecifier(node) {
imports.set(node.local.name, { node });
},
'Program:exit': () => {
for (const exportName of Object.keys(exports)) {
const reportInfoList = exports[exportName];
for (const { location, type } of reportInfoList) {
if (imports.has(exportName) || type === 'DefaultReExport') {
context.report({
data: {
name: exportName,
},
loc: location,
messageId: 'noReExport',
});
}
}
}
},
};
},
defaultOptions: [],
meta: {
docs: {
description: 'Disallows re-exports of imports.',
},
messages: {
noReExport: "Do not re-export '{{name}}'",
},
schema: [],
type: 'layout',
},
name: 'no-re-export',
});
121 changes: 121 additions & 0 deletions tests/rules/noReExport.ts
@@ -0,0 +1,121 @@
/**
* The code is adapted from https://github.com/christianvuerings/eslint-plugin-no-re-export
*/
import rule from '../../src/rules/noReExport';
import { createRuleTester } from '../RuleTester';

export default createRuleTester(
'no-re-export',
rule,
{ parser: '@typescript-eslint/parser' },
{
invalid: [
{
code: `
import Button1 from 'app/CustomButton';
export const CustomButton = Button1;
`,
errors: [
{
messageId: 'noReExport',
},
],
},
{
code: `
import { Button as CustomButton2 } from 'app/CustomButton';
export const CustomButton = CustomButton2;
`,
errors: [
{
messageId: 'noReExport',
},
],
},
{
code: `
import * as Button3 from "app/Button";
export const CustomButton = Button3;
`,
errors: [
{
messageId: 'noReExport',
},
],
},
{
code: `
import Button4 from 'app/CustomButton';
export default Button4;
`,
errors: [
{
messageId: 'noReExport',
},
],
},
{
code: `
export { default as Button5 } from 'app/CustomButton';
`,
errors: [
{
messageId: 'noReExport',
},
],
},
{
code: `
import Button6 from 'app/CustomButton';
export {
Button6
};
`,
errors: [
{
messageId: 'noReExport',
},
],
},
{
code: `
import Button7 from 'app/CustomButton';
export const Buttons = {
Button: Button7
};
`,
errors: [
{
messageId: 'noReExport',
},
],
},
{
code: `
import Button8 from 'app/CustomButton';
export default Button8;
export { Button8 }
`,
errors: [
{
messageId: 'noReExport',
},
{
messageId: 'noReExport',
},
],
},
{
code: `
export * from 'app/CustomButton';
`,
errors: [
{
messageId: 'noReExport',
},
],
},
],
valid: [],
},
);