diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeader-move-into-emptyState.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeader-move-into-emptyState.md
new file mode 100644
index 000000000..95d61eae7
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeader-move-into-emptyState.md
@@ -0,0 +1,20 @@
+### emptyStateHeader-move-into-emptyState [(#9947)](https://github.com/patternfly/patternfly-react/pull/9947)
+
+EmptyStateHeader and EmptyStateIcon are now rendered internally within EmptyState and should only be customized using props. Content passed to the `icon` prop on EmptyState will also be wrapped by EmptyStateIcon automatically.
+
+Additionally, the `titleText` prop is now required on EmptyState.
+
+#### Examples
+
+In:
+
+```jsx
+%inputExample%
+```
+
+Out:
+
+```jsx
+%outputExample%
+```
+
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeader-move-into-emptyState.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeader-move-into-emptyState.test.ts
new file mode 100644
index 000000000..8591677a1
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeader-move-into-emptyState.test.ts
@@ -0,0 +1,276 @@
+const ruleTester = require("../../ruletester");
+import * as rule from "./emptyStateHeader-move-into-emptyState";
+
+ruleTester.run("emptyStateHeader-move-into-emptyState", rule, {
+ valid: [
+ {
+ code: ``,
+ },
+ {
+ code: ``,
+ },
+ {
+ code: `} />`,
+ },
+ {
+ code: `import { EmptyState } from '@patternfly/react-core'; `,
+ },
+ ],
+ invalid: [
+ {
+ // with all EmptyStateHeader props
+ code: `import {
+ EmptyState,
+ EmptyStateHeader,
+ EmptyStateIcon,
+ CubesIcon
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+ }
+ titleClassName="custom-empty-state-title-class"
+ titleText="Empty state"
+ />
+
+ );
+ `,
+ output: `import {
+ EmptyState,
+ EmptyStateHeader,
+ EmptyStateIcon,
+ CubesIcon
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+
+ );
+ `,
+ errors: [
+ {
+ message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.`,
+ type: "JSXElement",
+ },
+ ],
+ },
+ {
+ // without any optional EmptyStateHeader props
+ code: `import {
+ EmptyState,
+ EmptyStateHeader,
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+
+
+ );
+ `,
+ output: `import {
+ EmptyState,
+ EmptyStateHeader,
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+
+ );
+ `,
+ errors: [
+ {
+ message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.`,
+ type: "JSXElement",
+ },
+ ],
+ },
+ {
+ // without any EmptyStateHeader props or children
+ code: `import {
+ EmptyState,
+ EmptyStateHeader,
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+
+
+ );
+ `,
+ output: `import {
+ EmptyState,
+ EmptyStateHeader,
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+
+
+ );
+ `,
+ errors: [
+ {
+ message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState. You must manually supply a titleText prop to EmptyState, then you can rerun this codemod.`,
+ type: "JSXElement",
+ },
+ ],
+ },
+ {
+ // without any EmptyStateHeader props but with children
+ code: `import {
+ EmptyState,
+ EmptyStateHeader,
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+ Foo bar
+
+ );
+ `,
+ output: `import {
+ EmptyState,
+ EmptyStateHeader,
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+
+ );
+ `,
+ errors: [
+ {
+ message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.`,
+ type: "JSXElement",
+ },
+ ],
+ },
+ {
+ // without an EmptyStateHeader or titleText
+ code: `import { EmptyState } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+ Foo bar
+
+ );
+ `,
+ output: `import { EmptyState } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+ Foo bar
+
+ );
+ `,
+ errors: [
+ {
+ message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState. You must manually supply a titleText prop to EmptyState.`,
+ type: "JSXElement",
+ },
+ ],
+ },
+ {
+ // with both titleText and children
+ code: `import {
+ EmptyState,
+ EmptyStateHeader,
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+ Foo
+
+ );
+ `,
+ output: `import {
+ EmptyState,
+ EmptyStateHeader,
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+ Foo
+
+ );
+ `,
+ errors: [
+ {
+ message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState. Because the children for EmptyStateHeader are now inaccessible you must remove either the children or the titleText prop, then you can rerun this codemod.`,
+ type: "JSXElement",
+ },
+ ],
+ },
+ {
+ // with the color prop on the EmptyStateIcon
+ code: `import {
+ EmptyState,
+ EmptyStateHeader,
+ EmptyStateIcon,
+ CubesIcon
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+ } />
+
+ );
+ `,
+ output: `import {
+ EmptyState,
+ EmptyStateHeader,
+ EmptyStateIcon,
+ CubesIcon
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+
+ );
+ `,
+ errors: [
+ {
+ message: `The color prop on EmptyStateIcon has been removed. We suggest using the new status prop on EmptyState to apply colors to the icon.`,
+ type: "JSXElement",
+ },
+ {
+ message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.`,
+ type: "JSXElement",
+ },
+ ],
+ },
+ {
+ // with a string expression for the heading className prop
+ code: `import {
+ EmptyState,
+ EmptyStateHeader,
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+
+
+ );
+ `,
+ output: `import {
+ EmptyState,
+ EmptyStateHeader,
+ } from "@patternfly/react-core";
+
+ export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+
+ );
+ `,
+ errors: [
+ {
+ message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.`,
+ type: "JSXElement",
+ },
+ ],
+ },
+ ],
+});
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeader-move-into-emptyState.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeader-move-into-emptyState.ts
new file mode 100644
index 000000000..3c8e8e699
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeader-move-into-emptyState.ts
@@ -0,0 +1,257 @@
+import { AST, Rule } from "eslint";
+import {
+ JSXElement,
+ ImportDeclaration,
+ ImportSpecifier,
+ JSXAttribute,
+ Node,
+ JSXText,
+ JSXExpressionContainer,
+ JSXSpreadChild,
+ JSXFragment,
+} from "estree-jsx";
+import { getFromPackage, pfPackageMatches } from "../../helpers";
+
+const baseMessage =
+ "EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.";
+
+// https://github.com/patternfly/patternfly-react/pull/9947
+module.exports = {
+ meta: { fixable: "code" },
+ create: function (context: Rule.RuleContext) {
+ const pkg = "@patternfly/react-core";
+ const { imports } = getFromPackage(context, pkg);
+
+ const allOfType = (nodes: Node[], type: string) =>
+ nodes.every((specifier) => specifier.type === type);
+
+ const includesImport = (
+ arr: ImportDeclaration["specifiers"],
+ targetImport: string
+ ) => {
+ if (!allOfType(arr, "ImportSpecifier")) {
+ return false;
+ }
+
+ return arr.some(
+ (specifier) =>
+ (specifier as ImportSpecifier).imported.name === targetImport
+ );
+ };
+
+ if (!includesImport(imports, "EmptyState")) {
+ return {};
+ }
+
+ const getChildElementByName = (name: string, node: JSXElement) =>
+ node.children?.find(
+ (child) =>
+ child.type === "JSXElement" &&
+ child.openingElement.name.type === "JSXIdentifier" &&
+ child.openingElement.name.name === name
+ );
+
+ const isComponentNode = (node: JSXElement, componentName: string) => {
+ if (node.openingElement.name.type === "JSXIdentifier") {
+ return node.openingElement.name.name === componentName;
+ }
+
+ return false;
+ };
+
+ const getAttribute = (
+ node: JSXElement,
+ attributeName: string
+ ): JSXAttribute | undefined => {
+ const attributes = node.openingElement.attributes.filter(
+ (attr) => attr.type === "JSXAttribute"
+ ) as JSXAttribute[];
+ return attributes.find((attr) => attr.name.name === attributeName);
+ };
+
+ const getExpressionValue = (node?: JSXAttribute["value"]) => {
+ if (!node) {
+ return;
+ }
+
+ if (node.type === "JSXExpressionContainer") {
+ return node.expression;
+ }
+ };
+
+ const getAttributeText = (attribute?: JSXAttribute) => {
+ if (!attribute) {
+ return "";
+ }
+
+ return context.getSourceCode().getText(attribute);
+ };
+
+ const getAttributeValueText = (attribute?: JSXAttribute) => {
+ if (!attribute || !attribute.value) {
+ return "";
+ }
+
+ return context.getSourceCode().getText(attribute.value);
+ };
+
+ const getNodesText = (nodes: Node[]) => {
+ return nodes
+ .map((node) => context.getSourceCode().getText(node))
+ .join("");
+ };
+
+ const getElementChildText = (children: JSXElement["children"]) => {
+ if (!children.length) {
+ return "";
+ }
+
+ switch (children[0].type) {
+ case "JSXText":
+ return (children as JSXText[]).map((child) => child.value).join("");
+ case "JSXExpressionContainer":
+ case "JSXSpreadChild":
+ return getNodesText(
+ children.map(
+ (child) =>
+ (child as JSXExpressionContainer | JSXSpreadChild).expression
+ )
+ );
+ case "JSXElement":
+ case "JSXFragment":
+ return getNodesText(children as JSXElement[] | JSXFragment[]);
+ default:
+ return "";
+ }
+ };
+
+ return {
+ JSXElement(node: JSXElement) {
+ if (!isComponentNode(node, "EmptyState")) {
+ return;
+ }
+
+ const header = getChildElementByName("EmptyStateHeader", node);
+ const emptyStateTitleTextAttribute = getAttribute(node, "titleText");
+
+ if (!header && !emptyStateTitleTextAttribute) {
+ context.report({
+ node,
+ message: `${baseMessage} You must manually supply a titleText prop to EmptyState.`,
+ });
+ return;
+ }
+
+ if (!header || header.type !== "JSXElement") {
+ return;
+ }
+
+ const headingClassNameAttribute = getAttribute(header, "className");
+ const headingLevelAttribute = getAttribute(header, "headingLevel");
+ const titleClassNameAttribute = getAttribute(header, "titleClassName");
+ const titleTextAttribute = getAttribute(header, "titleText");
+ const headerIconAttribute = getAttribute(header, "icon");
+
+ const headerChildren = header.children;
+
+ if (!titleTextAttribute && !headerChildren.length) {
+ context.report({
+ node,
+ message: `${baseMessage} You must manually supply a titleText prop to EmptyState, then you can rerun this codemod.`,
+ });
+ return;
+ }
+
+ if (titleTextAttribute && headerChildren.length) {
+ context.report({
+ node,
+ message: `${baseMessage} Because the children for EmptyStateHeader are now inaccessible you must remove either the children or the titleText prop, then you can rerun this codemod.`,
+ });
+ return;
+ }
+
+ const headingClassNameValue = getAttributeValueText(
+ headingClassNameAttribute
+ );
+
+ const headingClassName = headingClassNameValue
+ ? `headerClassName=${headingClassNameValue}`
+ : "";
+ const headingLevel = getAttributeText(headingLevelAttribute);
+ const titleClassName = getAttributeText(titleClassNameAttribute);
+ const titleTextPropValue = getAttributeText(titleTextAttribute);
+
+ const titleText =
+ titleTextPropValue ||
+ `titleText="${getElementChildText(headerChildren)}"`;
+
+ const iconPropValue = getExpressionValue(headerIconAttribute?.value);
+
+ const emptyStateIconComponent =
+ iconPropValue?.type === "JSXElement" ? iconPropValue : undefined;
+
+ const emptyStateIconComponentIconAttribute =
+ emptyStateIconComponent &&
+ getAttribute(emptyStateIconComponent, "icon");
+
+ const emptyStateIconComponentColorAttribute =
+ emptyStateIconComponent &&
+ getAttribute(emptyStateIconComponent, "color");
+ const emptyStateIconComponentColor = getAttributeText(
+ emptyStateIconComponentColorAttribute
+ );
+
+ if (emptyStateIconComponentColor) {
+ context.report({
+ node,
+ message: `The color prop on EmptyStateIcon has been removed. We suggest using the new status prop on EmptyState to apply colors to the icon.`,
+ });
+ }
+
+ const icon = emptyStateIconComponentIconAttribute
+ ? context
+ .getSourceCode()
+ .getText(emptyStateIconComponentIconAttribute)
+ : "";
+
+ context.report({
+ node,
+ message: baseMessage,
+ fix(fixer) {
+ const removeEmptyLineAfter = (
+ node?: JSXElement | ImportSpecifier | AST.Token | null
+ ) => {
+ if (!node) {
+ return [];
+ }
+ const token = context.getSourceCode().getTokenAfter(node);
+
+ return token &&
+ token.type === "JSXText" &&
+ token.value.trim() === ""
+ ? [fixer.remove(token)]
+ : [];
+ };
+
+ const removeElement = (node?: JSXElement) => {
+ if (!node) {
+ return [];
+ }
+
+ return [fixer.remove(node)];
+ };
+
+ return [
+ fixer.insertTextAfter(
+ node.openingElement.name,
+ ` ${headingClassName} ${headingLevel} ${icon} ${titleClassName} ${titleText}`
+ ),
+ ...removeElement(header),
+ ...removeEmptyLineAfter(header),
+ ];
+ },
+ });
+ },
+ };
+ },
+};
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeaderMoveIntoEmptyStateInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeaderMoveIntoEmptyStateInput.tsx
new file mode 100644
index 000000000..e3fe33eca
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeaderMoveIntoEmptyStateInput.tsx
@@ -0,0 +1,16 @@
+import {
+ EmptyState,
+ EmptyStateHeader,
+ EmptyStateIcon,
+ CubesIcon
+} from "@patternfly/react-core";
+
+export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+ }
+ />
+
+);
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeaderMoveIntoEmptyStateOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeaderMoveIntoEmptyStateOutput.tsx
new file mode 100644
index 000000000..2772835bc
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/emptyStateHeaderMoveIntoEmptyState/emptyStateHeaderMoveIntoEmptyStateOutput.tsx
@@ -0,0 +1,11 @@
+import {
+ EmptyState,
+ EmptyStateHeader,
+ EmptyStateIcon,
+ CubesIcon
+} from "@patternfly/react-core";
+
+export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
+
+
+);