From 89a1a81aba5f90781986acc367cd2b09f52f9a64 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Mon, 20 May 2024 10:12:14 -0400 Subject: [PATCH] fix(EmptyState): components no longer exported --- .../emptyState-nonExported-components.md | 17 +++ .../emptyState-nonExported-components.test.ts | 121 ++++++++++++++++++ .../emptyState-nonExported-components.ts | 117 +++++++++++++++++ .../emptyStateNonExportedComponentsInput.tsx | 7 + .../emptyStateNonExportedComponentsOutput.tsx | 1 + 5 files changed, 263 insertions(+) create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.md create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.test.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyStateNonExportedComponentsInput.tsx create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyStateNonExportedComponentsOutput.tsx diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.md new file mode 100644 index 000000000..b71e17181 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.md @@ -0,0 +1,17 @@ +### emptyState-nonExported-components [(#10364)](https://github.com/patternfly/patternfly-react/pull/10364) + +EmptyStateHeader and EmptyStateIcon are no longer exported by PatternFly. This rule will only apply fixes for exports of these components, as our rule for unused imports will handle applying fixes for imports. + +#### Examples + +In: + +```jsx +%inputExample% +``` + +Out: + +```jsx +%outputExample% +``` diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.test.ts new file mode 100644 index 000000000..bd2aa5084 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.test.ts @@ -0,0 +1,121 @@ +const ruleTester = require("../../ruletester"); +import * as rule from "./emptyState-nonExported-components"; + +ruleTester.run("emptyState-nonExported-components", rule, { + valid: [ + { + code: `import { EmptyStateHeader } from '@some/other/package';`, + }, + { + code: `import { EmptyStateIcon } from '@some/other/package';`, + }, + ], + invalid: [ + { + code: `import { EmptyStateHeader } from '@patternfly/react-core';`, + output: `import { EmptyStateHeader } from '@patternfly/react-core';`, + errors: [ + { + message: `EmptyStateHeader is no longer exported by PatternFly. This rule will not fix any imports, as our cleanup rule handles removal of unused imports.`, + type: "ImportSpecifier", + }, + ], + }, + { + code: `import { EmptyStateHeader as CustomHeader } from '@patternfly/react-core';`, + output: `import { EmptyStateHeader as CustomHeader } from '@patternfly/react-core';`, + errors: [ + { + message: `EmptyStateHeader is no longer exported by PatternFly. This rule will not fix any imports, as our cleanup rule handles removal of unused imports.`, + type: "ImportSpecifier", + }, + ], + }, + { + code: `export { EmptyStateHeader } from '@patternfly/react-core';`, + output: ``, + errors: [ + { + message: `EmptyStateHeader is no longer exported by PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + { + code: `export { EmptyStateHeader as CustomHeader } from '@patternfly/react-core';`, + output: ``, + errors: [ + { + message: `EmptyStateHeader is no longer exported by PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + { + code: `export { EmptyStateHeader, EmptyStateIcon } from '@patternfly/react-core';`, + output: ``, + errors: [ + { + message: `EmptyStateHeader and EmptyStateIcon are no longer exported by PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + { + code: `export { EmptyStateHeader, Button, EmptyStateIcon } from '@patternfly/react-core';`, + output: `export { Button } from '@patternfly/react-core';`, + errors: [ + { + message: `EmptyStateHeader is no longer exported by PatternFly.`, + type: "ExportNamedDeclaration", + }, + { + message: `EmptyStateIcon is no longer exported by PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + { + code: `import { EmptyStateHeader } from '@patternfly/react-core';export { EmptyStateHeader };`, + output: `import { EmptyStateHeader } from '@patternfly/react-core';`, + errors: [ + { + message: `EmptyStateHeader is no longer exported by PatternFly. This rule will not fix any imports, as our cleanup rule handles removal of unused imports.`, + type: "ImportSpecifier", + }, + { + message: `EmptyStateHeader is no longer exported by PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + { + code: `import { EmptyStateHeader as CustomHeader } from '@patternfly/react-core';export { CustomHeader };`, + output: `import { EmptyStateHeader as CustomHeader } from '@patternfly/react-core';`, + errors: [ + { + message: `EmptyStateHeader is no longer exported by PatternFly. This rule will not fix any imports, as our cleanup rule handles removal of unused imports.`, + type: "ImportSpecifier", + }, + { + message: `CustomHeader is no longer exported by PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + { + code: `import { EmptyStateHeader } from '@patternfly/react-core';export { EmptyStateHeader as CustomHeader };`, + output: `import { EmptyStateHeader } from '@patternfly/react-core';`, + errors: [ + { + message: `EmptyStateHeader is no longer exported by PatternFly. This rule will not fix any imports, as our cleanup rule handles removal of unused imports.`, + type: "ImportSpecifier", + }, + { + message: `EmptyStateHeader is no longer exported by PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.ts new file mode 100644 index 000000000..9b4a40bf4 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyState-nonExported-components.ts @@ -0,0 +1,117 @@ +import { Rule } from "eslint"; +import { + ImportSpecifier, + ExportSpecifier, + ExportNamedDeclaration, + ExportDefaultDeclaration, +} from "estree-jsx"; +import { getFromPackage } from "../../helpers"; + +// https://github.com/patternfly/patternfly-react/pull/10364 +module.exports = { + meta: { fixable: "code" }, + create: function (context: Rule.RuleContext) { + const { imports, exports } = getFromPackage( + context, + "@patternfly/react-core" + ); + const emptyStateComponents = ["EmptyStateHeader", "EmptyStateIcon"]; + const emptyStateImports = imports.filter((specifier) => + emptyStateComponents.includes(specifier.imported.name) + ); + const emptyStateExports = exports.filter((specifier) => + emptyStateComponents.includes(specifier.local.name) + ); + + if (!emptyStateImports.length && !emptyStateExports.length) { + return {}; + } + + return { + ImportSpecifier(node: ImportSpecifier) { + if (!emptyStateImports.length) { + return; + } + + emptyStateImports.forEach((esImport) => { + context.report({ + node, + message: `${esImport.imported.name} is no longer exported by PatternFly. This rule will not fix any imports, as our cleanup rule handles removal of unused imports.`, + }); + }); + }, + ExportNamedDeclaration(node: ExportNamedDeclaration) { + const exportsToRemove = node.specifiers.filter( + (spec) => + (emptyStateComponents.includes(spec.local.name) && + node.source?.value === "@patternfly/react-core") || + emptyStateImports.some((imp) => imp.local.name === spec.local.name) + ); + if (!exportsToRemove.length) { + return; + } + + if (exportsToRemove.length === node.specifiers.length) { + const validExportNames = exportsToRemove + .map((exportToRemove) => exportToRemove.local.name) + .join(" and "); + context.report({ + node, + message: `${validExportNames} ${ + exportsToRemove.length > 1 ? "are" : "is" + } no longer exported by PatternFly.`, + fix(fixer) { + return fixer.remove(node); + }, + }); + + return; + } + + exportsToRemove.forEach((exportToRemove) => { + const { range } = exportToRemove; + const tokenBefore = context + .getSourceCode() + .getTokenBefore(exportToRemove); + const isCommaBefore = tokenBefore?.value === ","; + const tokenAfter = context + .getSourceCode() + .getTokenAfter(exportToRemove); + const isCommaAfter = tokenAfter?.value === ","; + const rangeStartValue = isCommaBefore + ? tokenBefore.range[0] + : range?.[0]; + const rangeEndValue = isCommaAfter ? tokenAfter.range[1] : range?.[1]; + context.report({ + node, + message: `${exportToRemove.local.name} is no longer exported by PatternFly.`, + fix(fixer) { + return rangeStartValue && rangeEndValue + ? fixer.removeRange([rangeStartValue, rangeEndValue]) + : []; + }, + }); + }); + }, + ExportDefaultDeclaration(node: ExportDefaultDeclaration) { + const exportName = + node.declaration.type === "Identifier" && node.declaration.name; + const isEmptyStateDefaultExport = emptyStateImports.some( + (imp) => + node.declaration.type === "Identifier" && + imp.local.name === exportName + ); + + if (isEmptyStateDefaultExport) { + context.report({ + node, + message: `${exportName} is no longer exported by PatternFly.`, + fix(fixer) { + return fixer.remove(node); + }, + }); + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyStateNonExportedComponentsInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyStateNonExportedComponentsInput.tsx new file mode 100644 index 000000000..017f72a73 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyStateNonExportedComponentsInput.tsx @@ -0,0 +1,7 @@ +import { EmptyStateHeader, EmptyStateIcon } from "@patternfly/react-core"; + +export { EmptyStateHeader, EmptyStateIcon }; +export { + EmptyStateHeader as CustomESHeader, + EmptyStateIcon as CustomESIcon, +} from "@patternfly/react-core"; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyStateNonExportedComponentsOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyStateNonExportedComponentsOutput.tsx new file mode 100644 index 000000000..44ff04c0a --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateNonExportedComponents/emptyStateNonExportedComponentsOutput.tsx @@ -0,0 +1 @@ +import { EmptyStateHeader, EmptyStateIcon } from "@patternfly/react-core";