diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getImportedName.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getImportedName.ts
new file mode 100644
index 000000000..7ebadddc4
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getImportedName.ts
@@ -0,0 +1,17 @@
+import { ImportSpecifier, JSXOpeningElement } from "estree-jsx";
+
+/** Resolves the imported name of a node, even if that node has an aliased local name */
+export function getImportedName(
+ namedImports: ImportSpecifier[],
+ node: JSXOpeningElement
+) {
+ if (node.name.type !== "JSXIdentifier") {
+ return;
+ }
+
+ const nodeName = node.name.name;
+
+ const nodeImport = namedImports.find((imp) => imp.local.name === nodeName);
+
+ return nodeImport?.imported.name;
+}
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getLocalComponentName.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getLocalComponentName.ts
new file mode 100644
index 000000000..7fd25eac9
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getLocalComponentName.ts
@@ -0,0 +1,20 @@
+import { ImportSpecifier } from "estree-jsx";
+
+/** Resolves the local name of an import */
+export function getLocalComponentName(
+ namedImports: ImportSpecifier[],
+ importedName: string
+) {
+ const componentImport = namedImports.find(
+ (name) => name.imported.name === importedName
+ );
+
+ const isAlias =
+ componentImport?.imported.name !== componentImport?.local.name;
+
+ if (componentImport && isAlias) {
+ return componentImport.local.name;
+ }
+
+ return importedName;
+}
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 b232d90d7..5e5d6407b 100644
--- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts
+++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts
@@ -7,6 +7,8 @@ export * from "./getComponentImportName";
export * from "./getDefaultDeclarationString";
export * from "./getEndRange";
export * from "./getFromPackage";
+export * from "./getImportedName";
+export * from "./getLocalComponentName";
export * from "./getNodeName";
export * from "./getText";
export * from "./hasCodemodDataTag";
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 3cf2caedf..4f25975f3 100644
--- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/interfaces.ts
+++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/interfaces.ts
@@ -20,6 +20,10 @@ export interface ImportDefaultSpecifierWithParent
parent?: ImportDeclaration;
}
-export interface JSXOpeningElementWithParent extends JSXOpeningElement {
+export interface JSXElementWithParent extends JSXElement {
parent?: JSXElement;
}
+
+export interface JSXOpeningElementWithParent extends JSXOpeningElement {
+ parent?: JSXElementWithParent;
+}
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.md
new file mode 100644
index 000000000..82f14b8dc
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.md
@@ -0,0 +1,18 @@
+### masthead-structure-changes [(#10809)](https://github.com/patternfly/patternfly-react/pull/10809)
+
+The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain.
+
+#### Examples
+
+In:
+
+```jsx
+%inputExample%
+```
+
+Out:
+
+```jsx
+%outputExample%
+```
+
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.test.ts
new file mode 100644
index 000000000..57dbbeae6
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.test.ts
@@ -0,0 +1,175 @@
+const ruleTester = require("../../ruletester");
+import * as rule from "./masthead-structure-changes";
+
+ruleTester.run("masthead-structure-changes", rule, {
+ valid: [
+ // no pf import and has NOT had MastheadBrand renamed to MastheadLogo by the masthead-name-changes codemod
+ {
+ code: `FooBar`,
+ },
+ // no pf import and has had MastheadBrand renamed to MastheadLogo by the masthead-name-changes codemod
+ {
+ code: `FooBar`,
+ },
+ //toggle already wrapped in MastheadMain and brand double wrapped with data-codemods on the top level, has NOT had MastheadBrand renamed to MastheadLogo by the masthead-name-changes codemod
+ {
+ code: `import { Masthead, MastheadBrand, MastheadMain, MastheadToggle } from '@patternfly/react-core'; FooBar`,
+ },
+ // toggle already wrapped in MastheadMain and logo wrapped in a brand with data-codemods, has had MastheadBrand renamed to MastheadLogo by the masthead-name-changes codemod
+ {
+ code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core'; FooBar`,
+ },
+ ],
+ invalid: [
+ // stage one of a file that has NOT had MastheadBrand renamed to MastheadLogo by the masthead-name-changes codemod
+ {
+ code: `import { Masthead, MastheadBrand, MastheadMain, MastheadToggle } from '@patternfly/react-core'; FooBar`,
+ output: `import { Masthead, MastheadBrand, MastheadMain, MastheadToggle } from '@patternfly/react-core'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, MastheadToggle should now be wrapped in MastheadMain.`,
+ type: "JSXOpeningElement",
+ },
+ {
+ message: `The structure of Masthead has been updated, the PF5 MastheadBrand has been renamed to MastheadLogo (this renaming is handled by our masthead-name-changes codemod) and should now be wrapped in a new MastheadBrand.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ // stage two of a file that has NOT had MastheadBrand renamed to MastheadLogo by the masthead-name-changes codemod
+ {
+ code: `import { Masthead, MastheadBrand, MastheadMain, MastheadToggle } from '@patternfly/react-core'; FooBar`,
+ output: `import { Masthead, MastheadBrand, MastheadMain, MastheadToggle } from '@patternfly/react-core'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, the PF5 MastheadBrand has been renamed to MastheadLogo (this renaming is handled by our masthead-name-changes codemod) and should now be wrapped in a new MastheadBrand.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ // stage one of a file that has had MastheadBrand renamed to MastheadLogo by the masthead-name-changes codemod
+ {
+ code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle } from '@patternfly/react-core'; FooBar`,
+ output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, MastheadToggle should now be wrapped in MastheadMain.`,
+ type: "JSXOpeningElement",
+ },
+ {
+ message: `The structure of Masthead has been updated, MastheadLogo should now be wrapped in MastheadBrand.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ // stage two of a file that has had MastheadBrand renamed to MastheadLogo by the masthead-name-changes codemod
+ {
+ code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core'; FooBar`,
+ output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, MastheadToggle should now be wrapped in MastheadMain.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ // with aliases
+ {
+ code: `import { Masthead as MH, MastheadBrand as MB, MastheadMain as MM, MastheadToggle as MT } from '@patternfly/react-core'; FooBar`,
+ output: `import { Masthead as MH, MastheadBrand as MB, MastheadMain as MM, MastheadToggle as MT } from '@patternfly/react-core'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, MastheadToggle should now be wrapped in MastheadMain.`,
+ type: "JSXOpeningElement",
+ },
+ {
+ message: `The structure of Masthead has been updated, the PF5 MastheadBrand has been renamed to MastheadLogo (this renaming is handled by our masthead-name-changes codemod) and should now be wrapped in a new MastheadBrand.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ {
+ code: `import { Masthead as MH, MastheadBrand as MB, MastheadMain as MM, MastheadToggle as MT } from '@patternfly/react-core'; FooBar`,
+ output: `import { Masthead as MH, MastheadBrand as MB, MastheadMain as MM, MastheadToggle as MT } from '@patternfly/react-core'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, the PF5 MastheadBrand has been renamed to MastheadLogo (this renaming is handled by our masthead-name-changes codemod) and should now be wrapped in a new MastheadBrand.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ // with dist imports
+ {
+ code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle } from '@patternfly/react-core/dist/esm/components/Masthead'; FooBar`,
+ output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/esm/components/Masthead'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, MastheadToggle should now be wrapped in MastheadMain.`,
+ type: "JSXOpeningElement",
+ },
+ {
+ message: `The structure of Masthead has been updated, MastheadLogo should now be wrapped in MastheadBrand.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ {
+ code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/esm/components/Masthead'; FooBar`,
+ output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/esm/components/Masthead'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, MastheadToggle should now be wrapped in MastheadMain.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ {
+ code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle } from '@patternfly/react-core/dist/js/components/Masthead'; FooBar`,
+ output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/js/components/Masthead'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, MastheadToggle should now be wrapped in MastheadMain.`,
+ type: "JSXOpeningElement",
+ },
+ {
+ message: `The structure of Masthead has been updated, MastheadLogo should now be wrapped in MastheadBrand.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ {
+ code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/js/components/Masthead'; FooBar`,
+ output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/js/components/Masthead'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, MastheadToggle should now be wrapped in MastheadMain.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ {
+ code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle } from '@patternfly/react-core/dist/dynamic/components/Masthead'; FooBar`,
+ output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/dynamic/components/Masthead'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, MastheadToggle should now be wrapped in MastheadMain.`,
+ type: "JSXOpeningElement",
+ },
+ {
+ message: `The structure of Masthead has been updated, MastheadLogo should now be wrapped in MastheadBrand.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ {
+ code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/dynamic/components/Masthead'; FooBar`,
+ output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/dynamic/components/Masthead'; FooBar`,
+ errors: [
+ {
+ message: `The structure of Masthead has been updated, MastheadToggle should now be wrapped in MastheadMain.`,
+ type: "JSXOpeningElement",
+ },
+ ],
+ },
+ ],
+});
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.ts
new file mode 100644
index 000000000..95f47fe5f
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.ts
@@ -0,0 +1,174 @@
+import { Rule } from "eslint";
+import { ImportSpecifier } from "estree-jsx";
+import { JSXOpeningElementWithParent } from "../../helpers";
+import {
+ getAllImportsFromPackage,
+ getChildElementByName,
+ getImportedName,
+ getLocalComponentName,
+ hasCodeModDataTag,
+} from "../../helpers";
+// https://github.com/patternfly/patternfly-react/pull/10809
+
+function moveNodeIntoMastheadMain(
+ context: Rule.RuleContext,
+ fixer: Rule.RuleFixer,
+ node: JSXOpeningElementWithParent,
+ namedImports: ImportSpecifier[]
+) {
+ if (!node.parent || !node.parent.parent) {
+ return [];
+ }
+
+ const localMastheadMain = getLocalComponentName(namedImports, "MastheadMain");
+ const mastheadMain = getChildElementByName(
+ node.parent.parent,
+ localMastheadMain
+ );
+
+ if (!mastheadMain) {
+ return [];
+ }
+
+ const fixes = [fixer.remove(node.parent)];
+
+ const nodeString = context.getSourceCode().getText(node.parent);
+
+ fixes.push(fixer.insertTextAfter(mastheadMain.openingElement, nodeString));
+
+ return fixes;
+}
+
+function wrapNodeInMastheadBrand(
+ fixer: Rule.RuleFixer,
+ node: JSXOpeningElementWithParent,
+ namedImports: ImportSpecifier[]
+) {
+ if (!node.parent) {
+ return [];
+ }
+
+ const fixes = [];
+
+ const closingNode = node.parent?.closingElement
+ ? node.parent.closingElement
+ : node;
+
+ const importCount = namedImports.length - 1;
+ const lastImport = namedImports[importCount];
+
+ const localMastheadBrand = getLocalComponentName(
+ namedImports,
+ "MastheadBrand"
+ );
+
+ fixes.push(
+ fixer.insertTextBefore(node, `<${localMastheadBrand} data-codemods>`)
+ );
+ fixes.push(fixer.insertTextAfter(closingNode, `${localMastheadBrand}>`));
+
+ if (!namedImports.some((imp) => imp.imported.name === "MastheadBrand")) {
+ fixes.push(fixer.insertTextAfter(lastImport, ", MastheadBrand"));
+ }
+
+ return fixes;
+}
+
+function formatMessage(
+ component: "MastheadToggle" | "MastheadBrand" | "MastheadLogo"
+) {
+ const baseMessage = "The structure of Masthead has been updated, ";
+
+ const restOfMessage = {
+ MastheadToggle: "MastheadToggle should now be wrapped in MastheadMain.",
+ MastheadBrand:
+ "the PF5 MastheadBrand has been renamed to MastheadLogo (this renaming is handled by our masthead-name-changes codemod) and should now be wrapped in a new MastheadBrand.",
+ MastheadLogo: "MastheadLogo should now be wrapped in MastheadBrand.",
+ };
+
+ return baseMessage + restOfMessage[component];
+}
+
+module.exports = {
+ meta: { fixable: "code" },
+ create: function (context: Rule.RuleContext) {
+ const targetComponents = [
+ "MastheadBrand",
+ "MastheadToggle",
+ "MastheadLogo",
+ "Masthead",
+ "MastheadMain",
+ ];
+ const componentImports = getAllImportsFromPackage(
+ context,
+ "@patternfly/react-core",
+ targetComponents
+ );
+ const namedImports = componentImports.filter(
+ (imp) => imp.type === "ImportSpecifier"
+ ) as ImportSpecifier[];
+
+ return !namedImports.length
+ ? {}
+ : {
+ JSXOpeningElement(node: JSXOpeningElementWithParent) {
+ const nodeImportedName = getImportedName(namedImports, node);
+
+ if (node.name.type !== "JSXIdentifier" || !nodeImportedName) {
+ return;
+ }
+ const parentOpeningElement = node.parent?.parent?.openingElement;
+
+ if (!parentOpeningElement) {
+ return;
+ }
+
+ const parentImportedName = getImportedName(
+ namedImports,
+ parentOpeningElement
+ );
+
+ if (
+ nodeImportedName === "MastheadToggle" &&
+ parentImportedName !== "MastheadMain"
+ ) {
+ context.report({
+ node,
+ message: formatMessage("MastheadToggle"),
+ fix: (fixer) =>
+ moveNodeIntoMastheadMain(context, fixer, node, namedImports),
+ });
+ return;
+ }
+
+ const isPreRenameMastheadBrand =
+ nodeImportedName === "MastheadBrand" &&
+ parentImportedName === "MastheadMain" &&
+ !hasCodeModDataTag(node);
+
+ const isPostRenameMastheadBrand =
+ nodeImportedName === "MastheadLogo" &&
+ parentImportedName !== "MastheadBrand";
+
+ if (isPreRenameMastheadBrand) {
+ context.report({
+ node,
+ message: formatMessage("MastheadBrand"),
+ fix: (fixer) =>
+ wrapNodeInMastheadBrand(fixer, node, namedImports),
+ });
+ return;
+ }
+
+ if (isPostRenameMastheadBrand) {
+ context.report({
+ node,
+ message: formatMessage("MastheadLogo"),
+ fix: (fixer) =>
+ wrapNodeInMastheadBrand(fixer, node, namedImports),
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/mastheadStructureChangesInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/mastheadStructureChangesInput.tsx
new file mode 100644
index 000000000..90f082599
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/mastheadStructureChangesInput.tsx
@@ -0,0 +1,25 @@
+import {
+ Masthead,
+ MastheadBrand,
+ MastheadMain,
+ MastheadToggle,
+ MastheadLogo
+} from "@patternfly/react-core";
+
+export const MastheadStructureChangesInputPreNameChange = () => (
+
+ Foo
+
+ Bar
+
+
+);
+
+export const MastheadStructureChangesInputPostNameChange = () => (
+
+ Foo
+
+ Bar
+
+
+);
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/mastheadStructureChangesOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/mastheadStructureChangesOutput.tsx
new file mode 100644
index 000000000..d7ab84e35
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/mastheadStructureChangesOutput.tsx
@@ -0,0 +1,29 @@
+import {
+ Masthead,
+ MastheadBrand,
+ MastheadMain,
+ MastheadToggle,
+ MastheadLogo,
+} from "@patternfly/react-core";
+
+export const MastheadStructureChangesInputPreNameChange = () => (
+
+
+ Foo
+
+ Bar
+
+
+
+);
+
+export const MastheadStructureChangesInputPostNameChange = () => (
+
+
+ Foo
+
+ Bar
+
+
+
+);