From 36e7225b203e6f27acfcec45c41635cdfd639deb Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Mon, 29 Jul 2024 17:04:04 -0400 Subject: [PATCH 1/8] feat(Masthead): Update subcomponent names --- .../src/rules/helpers/getAttributeName.ts | 11 ++ .../rules/helpers/getComponentImportName.ts | 17 +++ .../src/rules/helpers/getDeclarationString.ts | 8 ++ .../src/rules/helpers/getFromPackage.ts | 3 +- .../src/rules/helpers/getNodeName.ts | 23 ++++ .../src/rules/helpers/hasCodemodDataTag.ts | 13 ++ .../src/rules/helpers/index.ts | 5 + .../src/rules/helpers/interfaces.ts | 6 + .../src/rules/helpers/renameComponent.ts | 113 +++++++++++++++++ .../masthead-name-changes.md | 18 +++ .../masthead-name-changes.test.ts | 119 ++++++++++++++++++ .../masthead-name-changes.ts | 13 ++ .../mastheadNameChangesInput.tsx | 7 ++ .../mastheadNameChangesOutput.tsx | 7 ++ 14 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/helpers/getAttributeName.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/helpers/getComponentImportName.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/helpers/getDeclarationString.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeName.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/helpers/hasCodemodDataTag.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesInput.tsx create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesOutput.tsx diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getAttributeName.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getAttributeName.ts new file mode 100644 index 000000000..de4cd587a --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getAttributeName.ts @@ -0,0 +1,11 @@ +import { JSXAttribute } from "estree-jsx"; + +/** Gets the name value of a JSX attribute */ +export function getAttributeName(attr: JSXAttribute) { + switch (attr.name.type) { + case "JSXIdentifier": + return attr.name.name; + case "JSXNamespacedName": + return attr.name.name.name; + } +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getComponentImportName.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getComponentImportName.ts new file mode 100644 index 000000000..a064e3582 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getComponentImportName.ts @@ -0,0 +1,17 @@ +import { ImportSpecifier } from "estree-jsx"; +import { ImportDefaultSpecifierWithParent } from "./interfaces"; +import { getDeclarationString } from "./getDeclarationString"; + +/** Gets the name of an import based on the specifier and an array of names that should be looked for in default import paths */ +export function getComponentImportName( + importSpecifier: ImportSpecifier | ImportDefaultSpecifierWithParent, + potentialNames: string[] +) { + if (importSpecifier.type === "ImportSpecifier") { + return importSpecifier.imported.name; + } + + return potentialNames.find((name) => + getDeclarationString(importSpecifier)?.includes(name) + ); +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getDeclarationString.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getDeclarationString.ts new file mode 100644 index 000000000..4fac719db --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getDeclarationString.ts @@ -0,0 +1,8 @@ +import { ImportDefaultSpecifierWithParent } from "./interfaces"; + +/** Gets the import path string for a default import */ +export function getDeclarationString( + defaultImportSpecifier: ImportDefaultSpecifierWithParent +) { + return defaultImportSpecifier?.parent?.source.value?.toString(); +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getFromPackage.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getFromPackage.ts index 0c2ce8e39..8a9be8f22 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getFromPackage.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getFromPackage.ts @@ -10,6 +10,7 @@ import { ExportSpecifier, } from "estree-jsx"; import { pfPackageMatches } from "./pfPackageMatches"; +import { ImportDefaultSpecifierWithParent } from "./interfaces"; type Declarations = ImportDeclaration | ExportNamedDeclaration; type Specifiers = ImportSpecifier | ExportSpecifier; @@ -126,5 +127,5 @@ export function getAllImportsFromPackage( componentNames.includes(imp.imported.name) ); - return [filteredImports, defaultImports].flat(); + return [filteredImports, defaultImports as ImportDefaultSpecifierWithParent[]].flat(); } diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeName.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeName.ts new file mode 100644 index 000000000..5c6f49b18 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeName.ts @@ -0,0 +1,23 @@ +import { JSXOpeningElement, JSXMemberExpression } from "estree-jsx"; + +/** Gets the name of an opening element or member expression */ +export function getNodeName(node: JSXOpeningElement | JSXMemberExpression) { + if (node.type === "JSXMemberExpression") { + switch (node.object.type) { + case "JSXMemberExpression": + return getNodeName(node.object); + case "JSXIdentifier": + return node.object.name; + } + } + + switch (node.name.type) { + case "JSXMemberExpression": + return getNodeName(node.name); + case "JSXIdentifier": + case "JSXNamespacedName": + return typeof node.name.name === "string" + ? node.name.name + : node.name.name.name; + } +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/hasCodemodDataTag.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/hasCodemodDataTag.ts new file mode 100644 index 000000000..79427d891 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/hasCodemodDataTag.ts @@ -0,0 +1,13 @@ +import { JSXAttribute, JSXOpeningElement } from "estree-jsx"; +import { getAttributeName } from "./getAttributeName"; + +/** Returns true if the passed opening element has a data-codemods attribute */ +export function hasCodeModDataTag(openingElement: JSXOpeningElement) { + const nonSpreadAttributes = openingElement.attributes.filter( + (attr) => attr.type === "JSXAttribute" + ); + const attributeNames = nonSpreadAttributes.map((attr) => + getAttributeName(attr as JSXAttribute) + ); + return attributeNames.includes("data-codemods"); +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts index a0a6707f2..74e4dc314 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts @@ -1,9 +1,14 @@ export * from "./contextReports"; export * from "./findAncestor"; export * from "./fixers"; +export * from "./getAttributeName"; +export * from "./getComponentImportName"; +export * from "./getDeclarationString"; export * from "./getEndRange"; export * from "./getFromPackage"; +export * from "./getNodeName"; export * from "./getText"; +export * from "./hasCodemodDataTag"; export * from "./helpers"; export * from "./importAndExport"; export * from "./includesImport"; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/interfaces.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/interfaces.ts index cf854aa13..9ceff9418 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/interfaces.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/interfaces.ts @@ -3,6 +3,8 @@ import { Identifier, ImportDefaultSpecifier, ImportDeclaration, + JSXOpeningElement, + JSXElement, } from "estree-jsx"; export interface IdentifierWithParent extends Identifier { @@ -13,3 +15,7 @@ export interface ImportDefaultSpecifierWithParent extends ImportDefaultSpecifier { parent?: ImportDeclaration; } + +export interface JSXOpeningElementWithParent extends JSXOpeningElement { + parent?: JSXElement; +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts new file mode 100644 index 000000000..8a5466cda --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts @@ -0,0 +1,113 @@ +import { Rule } from "eslint"; +import { ImportSpecifier } from "estree-jsx"; +import { getAllImportsFromPackage } from "./getFromPackage"; +import { + ImportDefaultSpecifierWithParent, + JSXOpeningElementWithParent, +} from "./interfaces"; +import { + getDeclarationString, + getComponentImportName, + getNodeName, + hasCodeModDataTag, +} from "./index"; + +interface ComponentRenames { + [currentName: string]: string; +} + +function formatDefaultMessage(oldName: string, newName: string) { + return `${oldName} has been renamed to ${newName}.`; +} + +function getFixes( + fixer: Rule.RuleFixer, + nodeImport: ImportSpecifier | ImportDefaultSpecifierWithParent, + node: JSXOpeningElementWithParent, + oldName: string, + newName: string +) { + const fixes = []; + + const isNamedImport = nodeImport.type === "ImportSpecifier"; + if (isNamedImport) { + fixes.push(fixer.replaceText(nodeImport.imported, newName)); + } else { + const importDeclaration = nodeImport.parent; + const newImportDeclaration = importDeclaration?.source.raw?.replace( + oldName, + newName + ); + if (importDeclaration && newImportDeclaration) { + fixes.push( + fixer.replaceText(importDeclaration.source, newImportDeclaration) + ); + } + } + + const shouldRenameNode = + isNamedImport && nodeImport.imported.name === nodeImport.local.name; + + if (shouldRenameNode) { + fixes.push(fixer.replaceText(node.name, newName)); + fixes.push(fixer.insertTextAfter(node.name, " data-codemods")); + } + + const closingElement = node?.parent?.closingElement; + if (shouldRenameNode && closingElement) { + fixes.push(fixer.replaceText(closingElement.name, newName)); + } + + return fixes; +} + +export function renameComponent( + renames: ComponentRenames, + packageName = "@patternfly/react-core" +) { + return function (context: Rule.RuleContext) { + const oldNames = Object.keys(renames); + const imports = getAllImportsFromPackage(context, packageName, oldNames); + + if (imports.length === 0) { + return {}; + } + + return { + JSXOpeningElement(node: JSXOpeningElementWithParent) { + if (hasCodeModDataTag(node)) { + return; + } + + const nodeName = getNodeName(node); + const nodeImport = imports.find((imp) => { + if (imp.type === "ImportSpecifier") { + return [imp.imported.name, imp.local.name].includes(nodeName); + } + + return oldNames.some((name) => + getDeclarationString(imp)?.includes(name) + ); + }); + + if (!nodeImport) { + return; + } + + const oldName = getComponentImportName(nodeImport, oldNames); + + if (!oldName) { + return; + } + + const newName = renames[oldName]; + + context.report({ + node, + message: formatDefaultMessage(oldName, newName), + fix: (fixer) => getFixes(fixer, nodeImport, node, oldName, newName), + }); + }, + }; + }; +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md new file mode 100644 index 000000000..610408d9a --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md @@ -0,0 +1,18 @@ +### masthead-name-changes [(#10809)](https://github.com/patternfly/patternfly-react/pull/10809) + +The old `MastheadBrand` component has been renamed to `MastheadLogo`, and the old `MastheadMain` has been renamed to `MastheadBrand`. + +#### Examples + +In: + +```jsx +%inputExample% +``` + +Out: + +```jsx +%outputExample% +``` + diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts new file mode 100644 index 000000000..26da6579d --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts @@ -0,0 +1,119 @@ +const ruleTester = require("../../ruletester"); +import * as rule from "./masthead-name-changes"; + +ruleTester.run("masthead-name-changes", rule, { + valid: [ + { + code: ``, + }, + { + code: ``, + }, + { + code: `import { MastheadBrand } from '@patternfly/react-core'; `, + }, + { + code: `import { MastheadMain } from '@patternfly/react-core'; `, + }, + ], + invalid: [ + { + code: `import { MastheadBrand } from '@patternfly/react-core'; `, + output: `import { MastheadLogo } from '@patternfly/react-core'; `, + errors: [ + { + message: `MastheadBrand has been renamed to MastheadLogo.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { MastheadMain } from '@patternfly/react-core'; `, + output: `import { MastheadBrand } from '@patternfly/react-core'; `, + errors: [ + { + message: `MastheadMain has been renamed to MastheadBrand.`, + type: "JSXOpeningElement", + }, + ], + }, + // with other props + { + code: `import { MastheadBrand } from '@patternfly/react-core'; `, + output: `import { MastheadLogo } from '@patternfly/react-core'; `, + errors: [ + { + message: `MastheadBrand has been renamed to MastheadLogo.`, + type: "JSXOpeningElement", + }, + ], + }, + // because of how the unit tests run I have to handle having both MastheadBrand and MastheadMain together in stages + { + code: `import { MastheadBrand, MastheadMain } from '@patternfly/react-core'; Logo`, + output: `import { MastheadLogo, MastheadMain } from '@patternfly/react-core'; Logo`, + errors: [ + { + message: `MastheadMain has been renamed to MastheadBrand.`, + type: "JSXOpeningElement", + }, + { + message: `MastheadBrand has been renamed to MastheadLogo.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { MastheadLogo, MastheadMain } from '@patternfly/react-core'; Logo`, + output: `import { MastheadLogo, MastheadBrand } from '@patternfly/react-core'; Logo`, + errors: [ + { + message: `MastheadMain has been renamed to MastheadBrand.`, + type: "JSXOpeningElement", + }, + ], + }, + // with alias + { + code: `import { MastheadBrand as MB } from '@patternfly/react-core'; `, + output: `import { MastheadLogo as MB } from '@patternfly/react-core'; `, + errors: [ + { + message: `MastheadBrand has been renamed to MastheadLogo.`, + type: "JSXOpeningElement", + }, + ], + }, + // dist imports + { + code: `import { MastheadBrand } from '@patternfly/react-core/dist/esm/components/Masthead/MastheadBrand'; `, + output: `import { MastheadLogo } from '@patternfly/react-core/dist/esm/components/Masthead/MastheadBrand'; `, + errors: [ + { + message: `MastheadBrand has been renamed to MastheadLogo.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { MastheadBrand } from '@patternfly/react-core/dist/js/components/Masthead/MastheadBrand'; `, + output: `import { MastheadLogo } from '@patternfly/react-core/dist/js/components/Masthead/MastheadBrand'; `, + errors: [ + { + message: `MastheadBrand has been renamed to MastheadLogo.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { MastheadBrand } from '@patternfly/react-core/dist/dynamic/components/Masthead/MastheadBrand'; `, + output: `import { MastheadLogo } from '@patternfly/react-core/dist/dynamic/components/Masthead/MastheadBrand'; `, + errors: [ + { + message: `MastheadBrand has been renamed to MastheadLogo.`, + type: "JSXOpeningElement", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.ts new file mode 100644 index 000000000..d194a089a --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.ts @@ -0,0 +1,13 @@ +import { renameComponent } from "../../helpers/renameComponent"; + +// https://github.com/patternfly/patternfly-react/pull/10809 + +const renames = { + MastheadBrand: "MastheadLogo", + MastheadMain: "MastheadBrand", +}; + +module.exports = { + meta: { fixable: "code" }, + create: renameComponent(renames), +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesInput.tsx new file mode 100644 index 000000000..d04155805 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesInput.tsx @@ -0,0 +1,7 @@ +import { MastheadBrand, MastheadMain } from "@patternfly/react-core"; + +export const MastheadNameChanges = () => ( + + Logo + +); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesOutput.tsx new file mode 100644 index 000000000..6395b2663 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesOutput.tsx @@ -0,0 +1,7 @@ +import { MastheadLogo, MastheadBrand } from "@patternfly/react-core"; + +export const MastheadNameChanges = () => ( + + Logo + +); From b3d1e39f3bb086d2caaad259a2ecf0924febf107 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Fri, 2 Aug 2024 15:31:40 -0400 Subject: [PATCH 2/8] chore(helpers): Get rid of recursion in getNodeName --- .../src/rules/helpers/getNodeName.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeName.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeName.ts index 5c6f49b18..9cb7c99bf 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeName.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeName.ts @@ -1,19 +1,19 @@ import { JSXOpeningElement, JSXMemberExpression } from "estree-jsx"; -/** Gets the name of an opening element or member expression */ -export function getNodeName(node: JSXOpeningElement | JSXMemberExpression) { - if (node.type === "JSXMemberExpression") { - switch (node.object.type) { - case "JSXMemberExpression": - return getNodeName(node.object); - case "JSXIdentifier": - return node.object.name; - } +function getJSXMemberExpressionName(node: JSXMemberExpression) { + switch (node.object.type) { + case "JSXMemberExpression": + return getJSXMemberExpressionName(node.object); + case "JSXIdentifier": + return node.object.name; } +} +/** Gets the name of an opening element */ +export function getNodeName(node: JSXOpeningElement) { switch (node.name.type) { case "JSXMemberExpression": - return getNodeName(node.name); + return getJSXMemberExpressionName(node.name); case "JSXIdentifier": case "JSXNamespacedName": return typeof node.name.name === "string" From c1d2b7117591ccd1b6b71f4b196c26294c4f9844 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Fri, 2 Aug 2024 15:35:07 -0400 Subject: [PATCH 3/8] chore(masthead): Refactor to only rename MastheadBrand --- .../masthead-name-changes.md | 2 +- .../masthead-name-changes.test.ts | 41 ------------------- .../masthead-name-changes.ts | 1 - 3 files changed, 1 insertion(+), 43 deletions(-) diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md index 610408d9a..eca9ec4be 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md @@ -1,6 +1,6 @@ ### masthead-name-changes [(#10809)](https://github.com/patternfly/patternfly-react/pull/10809) -The old `MastheadBrand` component has been renamed to `MastheadLogo`, and the old `MastheadMain` has been renamed to `MastheadBrand`. +The old `MastheadBrand` component has been renamed to `MastheadLogo`. #### Examples diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts index 26da6579d..0bd468eb9 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts @@ -6,15 +6,9 @@ ruleTester.run("masthead-name-changes", rule, { { code: ``, }, - { - code: ``, - }, { code: `import { MastheadBrand } from '@patternfly/react-core'; `, }, - { - code: `import { MastheadMain } from '@patternfly/react-core'; `, - }, ], invalid: [ { @@ -27,16 +21,6 @@ ruleTester.run("masthead-name-changes", rule, { }, ], }, - { - code: `import { MastheadMain } from '@patternfly/react-core'; `, - output: `import { MastheadBrand } from '@patternfly/react-core'; `, - errors: [ - { - message: `MastheadMain has been renamed to MastheadBrand.`, - type: "JSXOpeningElement", - }, - ], - }, // with other props { code: `import { MastheadBrand } from '@patternfly/react-core'; `, @@ -48,31 +32,6 @@ ruleTester.run("masthead-name-changes", rule, { }, ], }, - // because of how the unit tests run I have to handle having both MastheadBrand and MastheadMain together in stages - { - code: `import { MastheadBrand, MastheadMain } from '@patternfly/react-core'; Logo`, - output: `import { MastheadLogo, MastheadMain } from '@patternfly/react-core'; Logo`, - errors: [ - { - message: `MastheadMain has been renamed to MastheadBrand.`, - type: "JSXOpeningElement", - }, - { - message: `MastheadBrand has been renamed to MastheadLogo.`, - type: "JSXOpeningElement", - }, - ], - }, - { - code: `import { MastheadLogo, MastheadMain } from '@patternfly/react-core'; Logo`, - output: `import { MastheadLogo, MastheadBrand } from '@patternfly/react-core'; Logo`, - errors: [ - { - message: `MastheadMain has been renamed to MastheadBrand.`, - type: "JSXOpeningElement", - }, - ], - }, // with alias { code: `import { MastheadBrand as MB } from '@patternfly/react-core'; `, diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.ts index d194a089a..e952a95bd 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.ts @@ -4,7 +4,6 @@ import { renameComponent } from "../../helpers/renameComponent"; const renames = { MastheadBrand: "MastheadLogo", - MastheadMain: "MastheadBrand", }; module.exports = { From 1928c195545ae295e31d52657598c57dbfdce699 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Fri, 2 Aug 2024 15:45:08 -0400 Subject: [PATCH 4/8] chore(helpers): Add import source updating to renameComponent helper --- .../src/rules/helpers/interfaces.ts | 4 +++ .../src/rules/helpers/renameComponent.ts | 28 +++++++++++-------- .../masthead-name-changes.test.ts | 6 ++-- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/interfaces.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/interfaces.ts index 9ceff9418..3cf2caedf 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/interfaces.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/interfaces.ts @@ -1,6 +1,7 @@ import { Node, Identifier, + ImportSpecifier, ImportDefaultSpecifier, ImportDeclaration, JSXOpeningElement, @@ -11,6 +12,9 @@ export interface IdentifierWithParent extends Identifier { parent?: Node; } +export interface ImportSpecifierWithParent extends ImportSpecifier { + parent?: ImportDeclaration; +} export interface ImportDefaultSpecifierWithParent extends ImportDefaultSpecifier { parent?: ImportDeclaration; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts index 8a5466cda..67c411c98 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts @@ -1,7 +1,7 @@ import { Rule } from "eslint"; -import { ImportSpecifier } from "estree-jsx"; import { getAllImportsFromPackage } from "./getFromPackage"; import { + ImportSpecifierWithParent, ImportDefaultSpecifierWithParent, JSXOpeningElementWithParent, } from "./interfaces"; @@ -22,7 +22,7 @@ function formatDefaultMessage(oldName: string, newName: string) { function getFixes( fixer: Rule.RuleFixer, - nodeImport: ImportSpecifier | ImportDefaultSpecifierWithParent, + nodeImport: ImportSpecifierWithParent | ImportDefaultSpecifierWithParent, node: JSXOpeningElementWithParent, oldName: string, newName: string @@ -30,19 +30,23 @@ function getFixes( const fixes = []; const isNamedImport = nodeImport.type === "ImportSpecifier"; + const importDeclaration = nodeImport.parent; + const importSource = importDeclaration?.source.raw; + const importSourceHasComponentName = importSource?.includes(oldName); + const newImportDeclaration = importSource?.replace(oldName, newName); + if (isNamedImport) { fixes.push(fixer.replaceText(nodeImport.imported, newName)); - } else { - const importDeclaration = nodeImport.parent; - const newImportDeclaration = importDeclaration?.source.raw?.replace( - oldName, - newName + } + + if ( + importDeclaration && + newImportDeclaration && + importSourceHasComponentName + ) { + fixes.push( + fixer.replaceText(importDeclaration.source, newImportDeclaration) ); - if (importDeclaration && newImportDeclaration) { - fixes.push( - fixer.replaceText(importDeclaration.source, newImportDeclaration) - ); - } } const shouldRenameNode = diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts index 0bd468eb9..7a82e31af 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.test.ts @@ -46,7 +46,7 @@ ruleTester.run("masthead-name-changes", rule, { // dist imports { code: `import { MastheadBrand } from '@patternfly/react-core/dist/esm/components/Masthead/MastheadBrand'; `, - output: `import { MastheadLogo } from '@patternfly/react-core/dist/esm/components/Masthead/MastheadBrand'; `, + output: `import { MastheadLogo } from '@patternfly/react-core/dist/esm/components/Masthead/MastheadLogo'; `, errors: [ { message: `MastheadBrand has been renamed to MastheadLogo.`, @@ -56,7 +56,7 @@ ruleTester.run("masthead-name-changes", rule, { }, { code: `import { MastheadBrand } from '@patternfly/react-core/dist/js/components/Masthead/MastheadBrand'; `, - output: `import { MastheadLogo } from '@patternfly/react-core/dist/js/components/Masthead/MastheadBrand'; `, + output: `import { MastheadLogo } from '@patternfly/react-core/dist/js/components/Masthead/MastheadLogo'; `, errors: [ { message: `MastheadBrand has been renamed to MastheadLogo.`, @@ -66,7 +66,7 @@ ruleTester.run("masthead-name-changes", rule, { }, { code: `import { MastheadBrand } from '@patternfly/react-core/dist/dynamic/components/Masthead/MastheadBrand'; `, - output: `import { MastheadLogo } from '@patternfly/react-core/dist/dynamic/components/Masthead/MastheadBrand'; `, + output: `import { MastheadLogo } from '@patternfly/react-core/dist/dynamic/components/Masthead/MastheadLogo'; `, errors: [ { message: `MastheadBrand has been renamed to MastheadLogo.`, From b4483979c43eb1bb0cbea12b5ef34665a2e05ed7 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Fri, 2 Aug 2024 15:47:39 -0400 Subject: [PATCH 5/8] chore(helpers): Rename getDeclarationString helper --- .../src/rules/helpers/getComponentImportName.ts | 4 ++-- ...getDeclarationString.ts => getDefaultDeclarationString.ts} | 2 +- packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts | 2 +- .../src/rules/helpers/renameComponent.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename packages/eslint-plugin-pf-codemods/src/rules/helpers/{getDeclarationString.ts => getDefaultDeclarationString.ts} (84%) diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getComponentImportName.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getComponentImportName.ts index a064e3582..2d7c83229 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getComponentImportName.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getComponentImportName.ts @@ -1,6 +1,6 @@ import { ImportSpecifier } from "estree-jsx"; import { ImportDefaultSpecifierWithParent } from "./interfaces"; -import { getDeclarationString } from "./getDeclarationString"; +import { getDefaultDeclarationString } from "./getDefaultDeclarationString"; /** Gets the name of an import based on the specifier and an array of names that should be looked for in default import paths */ export function getComponentImportName( @@ -12,6 +12,6 @@ export function getComponentImportName( } return potentialNames.find((name) => - getDeclarationString(importSpecifier)?.includes(name) + getDefaultDeclarationString(importSpecifier)?.includes(name) ); } diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getDeclarationString.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getDefaultDeclarationString.ts similarity index 84% rename from packages/eslint-plugin-pf-codemods/src/rules/helpers/getDeclarationString.ts rename to packages/eslint-plugin-pf-codemods/src/rules/helpers/getDefaultDeclarationString.ts index 4fac719db..68258614e 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getDeclarationString.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getDefaultDeclarationString.ts @@ -1,7 +1,7 @@ import { ImportDefaultSpecifierWithParent } from "./interfaces"; /** Gets the import path string for a default import */ -export function getDeclarationString( +export function getDefaultDeclarationString( defaultImportSpecifier: ImportDefaultSpecifierWithParent ) { return defaultImportSpecifier?.parent?.source.value?.toString(); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts index 74e4dc314..f603ac801 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts @@ -3,7 +3,7 @@ export * from "./findAncestor"; export * from "./fixers"; export * from "./getAttributeName"; export * from "./getComponentImportName"; -export * from "./getDeclarationString"; +export * from "./getDefaultDeclarationString"; export * from "./getEndRange"; export * from "./getFromPackage"; export * from "./getNodeName"; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts index 67c411c98..5c78f3de6 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/renameComponent.ts @@ -6,7 +6,7 @@ import { JSXOpeningElementWithParent, } from "./interfaces"; import { - getDeclarationString, + getDefaultDeclarationString, getComponentImportName, getNodeName, hasCodeModDataTag, @@ -90,7 +90,7 @@ export function renameComponent( } return oldNames.some((name) => - getDeclarationString(imp)?.includes(name) + getDefaultDeclarationString(imp)?.includes(name) ); }); From b600a0029b5286dbbc8d1e49c803cac4f8abcdfb Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Fri, 2 Aug 2024 15:55:58 -0400 Subject: [PATCH 6/8] feat(helpers): Add getCodeModDataTag helper --- .../src/rules/helpers/getCodeModDataTag.ts | 13 +++++++++++++ .../src/rules/helpers/hasCodemodDataTag.ts | 12 +++--------- .../src/rules/helpers/index.ts | 1 + 3 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/helpers/getCodeModDataTag.ts diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getCodeModDataTag.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getCodeModDataTag.ts new file mode 100644 index 000000000..e91133339 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getCodeModDataTag.ts @@ -0,0 +1,13 @@ +import { JSXAttribute, JSXOpeningElement } from "estree-jsx"; +import { getAttributeName } from "./getAttributeName"; + +/** Returns the data-codemods attribute of an element, if it exists */ +export function getCodeModDataTag(openingElement: JSXOpeningElement) { + const nonSpreadAttributes = openingElement.attributes.filter( + (attr) => attr.type === "JSXAttribute" + ); + + return nonSpreadAttributes.find( + (attr) => getAttributeName(attr as JSXAttribute) === "data-codemods" + ); +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/hasCodemodDataTag.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/hasCodemodDataTag.ts index 79427d891..c818efa27 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/hasCodemodDataTag.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/hasCodemodDataTag.ts @@ -1,13 +1,7 @@ -import { JSXAttribute, JSXOpeningElement } from "estree-jsx"; -import { getAttributeName } from "./getAttributeName"; +import { JSXOpeningElement } from "estree-jsx"; +import { getCodeModDataTag } from "./getCodeModDataTag"; /** Returns true if the passed opening element has a data-codemods attribute */ export function hasCodeModDataTag(openingElement: JSXOpeningElement) { - const nonSpreadAttributes = openingElement.attributes.filter( - (attr) => attr.type === "JSXAttribute" - ); - const attributeNames = nonSpreadAttributes.map((attr) => - getAttributeName(attr as JSXAttribute) - ); - return attributeNames.includes("data-codemods"); + return !!getCodeModDataTag(openingElement) } diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts index f603ac801..bf16a945a 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts @@ -2,6 +2,7 @@ export * from "./contextReports"; export * from "./findAncestor"; export * from "./fixers"; export * from "./getAttributeName"; +export * from "./getCodeModDataTag"; export * from "./getComponentImportName"; export * from "./getDefaultDeclarationString"; export * from "./getEndRange"; From fb3ca1955b154eb5f0ab23663a21800cfd8113e6 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Fri, 2 Aug 2024 15:58:23 -0400 Subject: [PATCH 7/8] chore(readme): Update rule description --- .../src/rules/v6/mastheadNameChanges/masthead-name-changes.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md index eca9ec4be..3980cbed7 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/masthead-name-changes.md @@ -1,6 +1,8 @@ ### masthead-name-changes [(#10809)](https://github.com/patternfly/patternfly-react/pull/10809) -The old `MastheadBrand` component has been renamed to `MastheadLogo`. +Our old implementation of `MastheadBrand` has been renamed to `MastheadLogo`, which must be wrapped by our new implementation of `MastheadBrand`." + +This rule will handle the renaming, required restructuring will be handled under a separate rule. #### Examples From 9fc4352b87bfeacc6a170461f7561cfc3e75f9fd Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 13 Aug 2024 14:10:40 -0400 Subject: [PATCH 8/8] chore(masthead): update example input/output --- .../v6/mastheadNameChanges/mastheadNameChangesInput.tsx | 8 ++------ .../v6/mastheadNameChanges/mastheadNameChangesOutput.tsx | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesInput.tsx index d04155805..5d7d84be9 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesInput.tsx +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesInput.tsx @@ -1,7 +1,3 @@ -import { MastheadBrand, MastheadMain } from "@patternfly/react-core"; +import { MastheadBrand } from "@patternfly/react-core"; -export const MastheadNameChanges = () => ( - - Logo - -); +export const MastheadNameChanges = () => Logo; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesOutput.tsx index 6395b2663..331586e8b 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesOutput.tsx +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadNameChanges/mastheadNameChangesOutput.tsx @@ -1,7 +1,3 @@ -import { MastheadLogo, MastheadBrand } from "@patternfly/react-core"; +import { MastheadLogo } from "@patternfly/react-core"; -export const MastheadNameChanges = () => ( - - Logo - -); +export const MastheadNameChanges = () => Logo;