Skip to content

Commit

Permalink
feat: No duplicate import specifiers rule (#713)
Browse files Browse the repository at this point in the history
* feat: no-duplicate-import-specifiers cleanup rule

* refactor: findDuplicates function

* fix: don't update example with unrelated rule

* test(noDuplicateImportSpecifiers): add tests of different packages
  • Loading branch information
adamviktora authored Jul 31, 2024
1 parent f71a9bb commit 8a09099
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 1 deletion.
6 changes: 5 additions & 1 deletion packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,8 @@ export const setupRules = [
];

// rules that will run after other rules (cleanup imports?)
export const cleanupRules = ["no-unused-imports-v5", "no-unused-imports-v6"];
export const cleanupRules = [
"no-unused-imports-v5",
"no-unused-imports-v6",
"no-duplicate-import-specifiers",
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### no-duplicate-import-specifiers

Duplicate import specifiers should be removed. This is a cleanup rule which runs after other rules.

#### Examples

In:

```jsx
%inputExample%
```

Out:

```jsx
%outputExample%
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
const ruleTester = require("../../ruletester");
import * as rule from "./no-duplicate-import-specifiers";

ruleTester.run("no-duplicate-import-specifiers", rule, {
valid: [
{
// we care only about imports from "@patternfly/react-core"
code: `import { Button, Button } from "somewhere"`,
},
{
code: `import { Button, Button as AnotherButton } from "@patternfly/react-core";
<>
<Button>Sample button</Button>
<AnotherButton>Another one</AnotherButton>
</>`,
},
{
code: `import { Select } from '@patternfly/react-core/deprecated';
import { Select } from '@patternfly/react-core';`
},
{
code: `import { Select } from '@patternfly/react-core/deprecated';
import { Select } from '@patternfly/react-core/dist/dynamic/components/Select';`
},
{
code: `import { Select } from '@patternfly/react-core/next';
import { Select } from '@patternfly/react-core';`
},
{
code: `import { Select } from '@patternfly/react-core/next';
import { Select } from '@patternfly/react-core/dist/dynamic/components/Select';`
}
],
invalid: [
{
code: `import { Button, Button } from "@patternfly/react-core";
<Button>Sample button</Button>`,
output: `import { Button, } from "@patternfly/react-core";
<Button>Sample button</Button>`,
errors: [
{
message: `Duplicate import specifier Button imported from '@patternfly/react-core'.`,
type: "ImportSpecifier",
},
],
},
{
code: `import { Button } from "@patternfly/react-core";
import { Button } from "@patternfly/react-core";
<Button>Sample button</Button>`,
output: `import { Button } from "@patternfly/react-core";
<Button>Sample button</Button>`,
errors: [
{
message: `Duplicate import specifier Button imported from '@patternfly/react-core'.`,
type: "ImportSpecifier",
},
],
},
{
code: `import { Button } from "@patternfly/react-core";
import { Button } from '@patternfly/react-core/dist/dynamic/components/Button';
<Button>Sample button</Button>`,
output: `import { Button } from "@patternfly/react-core";
<Button>Sample button</Button>`,
errors: [
{
message: `Duplicate import specifier Button imported from '@patternfly/react-core/dist/dynamic/components/Button'.`,
type: "ImportSpecifier",
},
],
},
{
code: `import { Button as BTN, TextInput, Button as BTN } from "@patternfly/react-core";
<>
<BTN>Sample button</BTN>
<TextInput>Text</TextInput>
</>`,
output: `import { Button as BTN, TextInput, } from "@patternfly/react-core";
<>
<BTN>Sample button</BTN>
<TextInput>Text</TextInput>
</>`,
errors: [
{
message: `Duplicate import specifier BTN imported from '@patternfly/react-core'.`,
type: "ImportSpecifier",
},
],
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Rule } from "eslint";
import { ImportDeclaration, ImportSpecifier } from "estree-jsx";
import { getFromPackage, removeSpecifierFromDeclaration } from "../../helpers";

// Cleanup from other rules
module.exports = {
meta: { fixable: "code" },
create: function (context: Rule.RuleContext) {
const { imports } = getFromPackage(context, "@patternfly/react-core");

const findDuplicates = (specifiers: ImportSpecifier[]) => {
const localNames = specifiers.map((spec) => spec.local.name);

return specifiers.filter(
(specifier, index) => localNames.indexOf(specifier.local.name) !== index
);
};

const duplicatesToRemove = findDuplicates(imports);

return !duplicatesToRemove.length
? {}
: {
ImportSpecifier(node: ImportSpecifier) {
if (duplicatesToRemove.includes(node)) {
const importDeclaration = context
.getAncestors()
.find(
(ancestor) => ancestor.type === "ImportDeclaration"
) as ImportDeclaration;

if (importDeclaration) {
context.report({
node,
message: `Duplicate import specifier ${node.local.name} imported from '${importDeclaration.source.value}'.`,
fix(fixer) {
return removeSpecifierFromDeclaration(
fixer,
context,
importDeclaration,
node
);
},
});
}
}
},
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Button, Button } from "@patternfly/react-core";

export const NoDuplicateImportSpecifiersInput = () => (
<Button>Sample button</Button>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Button } from "@patternfly/react-core";

export const NoDuplicateImportSpecifiersInput = () => (
<Button>Sample button</Button>
);

0 comments on commit 8a09099

Please sign in to comment.