diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts
index 3d0f26ca..b9b783c9 100644
--- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts
+++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts
@@ -1,7 +1,10 @@
import { Rule, Scope } from "eslint";
import {
- JSXElement,
+ Expression,
JSXAttribute,
+ JSXElement,
+ JSXEmptyExpression,
+ JSXFragment,
JSXOpeningElement,
MemberExpression,
} from "estree-jsx";
@@ -68,7 +71,7 @@ export function getExpression(node?: JSXAttribute["value"]) {
}
if (node.type === "JSXExpressionContainer") {
- return node.expression;
+ return node.expression as Expression | JSXEmptyExpression | JSXFragment;
}
}
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/childrenIsEmpty.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/childrenIsEmpty.ts
new file mode 100644
index 00000000..0523fbf1
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/childrenIsEmpty.ts
@@ -0,0 +1,11 @@
+import { JSXElement } from "estree-jsx";
+
+/** Checks whether children is empty (no children or only whitespaces) */
+export function childrenIsEmpty(children: JSXElement["children"]) {
+ return (
+ !children.length ||
+ (children.length === 1 &&
+ children[0].type === "JSXText" &&
+ children[0].value.trim() === "")
+ );
+}
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getChildJSXElementByName.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getChildJSXElementByName.ts
index 4f00e951..54f750fa 100644
--- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getChildJSXElementByName.ts
+++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getChildJSXElementByName.ts
@@ -25,7 +25,10 @@ function getChildJSXElementCallback(
/** Can be used to run logic if the specific child element exists, or to run logic on the
* specified element.
*/
-export function getChildJSXElementByName(node: JSXElement, name: string) {
+export function getChildJSXElementByName(
+ node: JSXElement | JSXFragment,
+ name: string
+) {
return node.children?.find((child) =>
getChildJSXElementCallback(child, name)
) as JSXElement | undefined;
@@ -34,7 +37,10 @@ export function getChildJSXElementByName(node: JSXElement, name: string) {
/** Can be used to run logic if the specific child elements exist, or to run logic on the
* specified elements.
*/
-export function getAllChildJSXElementsByName(node: JSXElement, name: string) {
+export function getAllChildJSXElementsByName(
+ node: JSXElement | JSXFragment,
+ name: string
+) {
return node.children?.filter((child) =>
getChildJSXElementCallback(child, name)
) as JSXElement[];
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 5ef2822a..e719e884 100644
--- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts
+++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts
@@ -1,3 +1,4 @@
+export * from "./childrenIsEmpty";
export * from "./contextReports";
export * from "./findAncestor";
export * from "./fixers";
@@ -22,6 +23,7 @@ export * from "./includesImport";
export * from "./interfaces";
export * from "./isReactIcon";
export * from "./JSXAttributes";
+export * from "./makeJSXElementSelfClosing";
export * from "./nodeMatches";
export * from "./pfPackageMatches";
export * from "./removeElement";
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/makeJSXElementSelfClosing.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/makeJSXElementSelfClosing.ts
new file mode 100644
index 00000000..44944780
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/makeJSXElementSelfClosing.ts
@@ -0,0 +1,47 @@
+import { Rule } from "eslint";
+import { JSXElement, JSXText } from "estree-jsx";
+import { childrenIsEmpty } from ".";
+
+/** Transforms JSXElement to a self-closing tag.
+ * Works only on elements without children by default, but you can overwrite this behaviour with the removeChildren parameter.
+ */
+export function makeJSXElementSelfClosing(
+ node: JSXElement,
+ context: Rule.RuleContext,
+ fixer: Rule.RuleFixer,
+ removeChildren: boolean = false
+): Rule.Fix[] {
+ if (!node.closingElement) {
+ return [];
+ }
+
+ const fixes = [];
+
+ const emptyChildren = childrenIsEmpty(node.children);
+
+ if (removeChildren || emptyChildren) {
+ const closingSymbol = context
+ .getSourceCode()
+ .getLastToken(node.openingElement)!;
+
+ fixes.push(
+ fixer.replaceText(closingSymbol, " />"),
+ fixer.remove(node.closingElement)
+ );
+
+ if (node.children.length) {
+ if (removeChildren) {
+ fixes.push(
+ fixer.removeRange([
+ node.children[0].range![0],
+ node.children[node.children.length - 1].range![1],
+ ])
+ );
+ } else if (emptyChildren) {
+ fixes.push(fixer.remove(node.children[0] as JSXText));
+ }
+ }
+ }
+
+ return fixes;
+}
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/buttonMoveIconsIconProp/button-moveIcons-icon-prop.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/buttonMoveIconsIconProp/button-moveIcons-icon-prop.test.ts
index 957aeee0..b7aa8e51 100644
--- a/packages/eslint-plugin-pf-codemods/src/rules/v6/buttonMoveIconsIconProp/button-moveIcons-icon-prop.test.ts
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/buttonMoveIconsIconProp/button-moveIcons-icon-prop.test.ts
@@ -17,7 +17,7 @@ ruleTester.run("button-moveIcons-icon-prop", rule, {
invalid: [
{
code: `import { Button } from '@patternfly/react-core'; const icon = Some icon; `,
- output: `import { Button } from '@patternfly/react-core'; const icon = Some icon; `,
+ output: `import { Button } from '@patternfly/react-core'; const icon = Some icon; `,
errors: [
{
message: `Icons must now be passed to the \`icon\` prop of Button instead of as children. If you are passing anything other than an icon as children, ignore this rule when running fixes.`,
@@ -27,7 +27,7 @@ ruleTester.run("button-moveIcons-icon-prop", rule, {
},
{
code: `import { Button } from '@patternfly/react-core'; `,
- output: `import { Button } from '@patternfly/react-core'; `,
+ output: `import { Button } from '@patternfly/react-core';