diff --git a/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts b/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts index 2f3635f2d..03873479f 100644 --- a/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts +++ b/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts @@ -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", +]; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/no-duplicate-import-specifiers.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/no-duplicate-import-specifiers.md new file mode 100644 index 000000000..9c390ff82 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/no-duplicate-import-specifiers.md @@ -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% +``` diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/no-duplicate-import-specifiers.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/no-duplicate-import-specifiers.test.ts new file mode 100644 index 000000000..0209ffcd9 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/no-duplicate-import-specifiers.test.ts @@ -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"; + <> + + Another one + `, + }, + { + 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"; + `, + output: `import { Button, } from "@patternfly/react-core"; + `, + 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"; + `, + output: `import { Button } from "@patternfly/react-core"; + + `, + 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'; + `, + output: `import { Button } from "@patternfly/react-core"; + + `, + 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"; + <> + Sample button + Text + `, + output: `import { Button as BTN, TextInput, } from "@patternfly/react-core"; + <> + Sample button + Text + `, + errors: [ + { + message: `Duplicate import specifier BTN imported from '@patternfly/react-core'.`, + type: "ImportSpecifier", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/no-duplicate-import-specifiers.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/no-duplicate-import-specifiers.ts new file mode 100644 index 000000000..1a864e503 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/no-duplicate-import-specifiers.ts @@ -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 + ); + }, + }); + } + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/noDuplicateImportSpecifiersInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/noDuplicateImportSpecifiersInput.tsx new file mode 100644 index 000000000..5423c9b6b --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/noDuplicateImportSpecifiersInput.tsx @@ -0,0 +1,5 @@ +import { Button, Button } from "@patternfly/react-core"; + +export const NoDuplicateImportSpecifiersInput = () => ( + +); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/noDuplicateImportSpecifiersOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/noDuplicateImportSpecifiersOutput.tsx new file mode 100644 index 000000000..faada7b26 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/noDuplicateImportSpecifiers/noDuplicateImportSpecifiersOutput.tsx @@ -0,0 +1,5 @@ +import { Button } from "@patternfly/react-core"; + +export const NoDuplicateImportSpecifiersInput = () => ( + +);