From 4c32f472cedc548a406e0fdc19423ec28f67f78c Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Mon, 20 May 2024 10:12:14 -0400 Subject: [PATCH 1/2] 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"; From 8f72511b8b3b0642684b472b241f886d8c2a1036 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Thu, 23 May 2024 08:22:25 -0400 Subject: [PATCH 2/2] PR feedback --- .../emptyState-nonExported-components.test.ts | 28 +++++++++++++++++++ .../emptyState-nonExported-components.ts | 5 +--- 2 files changed, 29 insertions(+), 4 deletions(-) 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 index bd2aa5084..745e49bca 100644 --- 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 @@ -117,5 +117,33 @@ ruleTester.run("emptyState-nonExported-components", rule, { }, ], }, + { + code: `import { EmptyStateHeader } from '@patternfly/react-core';export default 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: "ExportDefaultDeclaration", + }, + ], + }, + { + code: `import { EmptyStateHeader as CustomHeader } from '@patternfly/react-core';export default 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: "ExportDefaultDeclaration", + }, + ], + }, ], }); 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 index 9b4a40bf4..8a197ac84 100644 --- 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 @@ -1,7 +1,6 @@ import { Rule } from "eslint"; import { ImportSpecifier, - ExportSpecifier, ExportNamedDeclaration, ExportDefaultDeclaration, } from "estree-jsx"; @@ -97,9 +96,7 @@ module.exports = { const exportName = node.declaration.type === "Identifier" && node.declaration.name; const isEmptyStateDefaultExport = emptyStateImports.some( - (imp) => - node.declaration.type === "Identifier" && - imp.local.name === exportName + (imp) => imp.local.name === exportName ); if (isEmptyStateDefaultExport) {