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 b9b783c9..9ae2c1c9 100644
--- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts
+++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts
@@ -49,18 +49,21 @@ export function getAttributeValue(
return node.value;
}
- const isExpressionContainer = valueType === "JSXExpressionContainer";
- if (isExpressionContainer && node.expression.type === "Identifier") {
+ if (valueType !== "JSXExpressionContainer") {
+ return;
+ }
+
+ if (node.expression.type === "Identifier") {
const variableScope = context.getSourceCode().getScope(node);
- return getVariableValue(node.expression.name, variableScope);
+ return getVariableValue(node.expression.name, variableScope, context);
}
- if (isExpressionContainer && node.expression.type === "MemberExpression") {
+ if (node.expression.type === "MemberExpression") {
return getMemberExpression(node.expression);
}
- if (isExpressionContainer && node.expression.type === "Literal") {
+ if (node.expression.type === "Literal") {
return node.expression.value;
}
- if (isExpressionContainer && node.expression.type === "ObjectExpression") {
+ if (node.expression.type === "ObjectExpression") {
return node.expression.properties;
}
}
@@ -100,7 +103,11 @@ export function getVariableDeclaration(
return undefined;
}
-export function getVariableValue(name: string, scope: Scope.Scope | null) {
+export function getVariableValue(
+ name: string,
+ scope: Scope.Scope | null,
+ context: Rule.RuleContext
+) {
const variableDeclaration = getVariableDeclaration(name, scope);
if (!variableDeclaration) {
return;
@@ -113,6 +120,13 @@ export function getVariableValue(name: string, scope: Scope.Scope | null) {
if (!variableInit) {
return;
}
+ if (variableInit.type === "Identifier") {
+ return getVariableValue(
+ variableInit.name,
+ context.getSourceCode().getScope(variableInit),
+ context
+ );
+ }
if (variableInit.type === "Literal") {
return variableInit.value;
}
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggle-remove-splitButtonOptions.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggle-remove-splitButtonOptions.md
new file mode 100644
index 00000000..ddcebb0c
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggle-remove-splitButtonOptions.md
@@ -0,0 +1,18 @@
+### menuToggle-remove-splitButtonOptions [(#11096)](https://github.com/patternfly/patternfly-react/pull/11096)
+
+We have replaced `splitButtonOptions` prop on MenuToggle with `splitButtonItems`. SplitButtonOptions interface has been deleted, because its `variant` prop no longer supports the "action" option. The `items` prop of SplitButtonOptions will be passed directly to MenuToggle's new `splitButtonItems` prop.
+
+#### Examples
+
+In:
+
+```jsx
+%inputExample%
+```
+
+Out:
+
+```jsx
+%outputExample%
+```
+
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggle-remove-splitButtonOptions.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggle-remove-splitButtonOptions.test.ts
new file mode 100644
index 00000000..1bbcd79e
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggle-remove-splitButtonOptions.test.ts
@@ -0,0 +1,112 @@
+const ruleTester = require("../../ruletester");
+import * as rule from "./menuToggle-remove-splitButtonOptions";
+
+const message =
+ "We have replaced `splitButtonOptions` prop on MenuToggle with `splitButtonItems`. SplitButtonOptions interface has been removed, because its `variant` prop no longer supports the 'action' option. The `items` prop of SplitButtonOptions will be passed directly to MenuToggle's new `splitButtonItems` prop.";
+const interfaceRemovedMessage = `The SplitButtonOptions interface has been removed.`;
+
+const generalError = {
+ message,
+ type: "JSXOpeningElement",
+};
+
+ruleTester.run("menuToggle-remove-splitButtonOptions", rule, {
+ valid: [
+ {
+ code: ``,
+ },
+ {
+ code: `import { MenuToggle } from '@patternfly/react-core'; `,
+ },
+ ],
+ invalid: [
+ {
+ // object expression with "items" property - direct value
+ code: `import { MenuToggle } from '@patternfly/react-core';
+ `,
+ output: `import { MenuToggle } from '@patternfly/react-core';
+ `,
+ errors: [generalError],
+ },
+ {
+ // object expression with "items" property - in a variable
+ code: `import { MenuToggle } from '@patternfly/react-core';
+ const sbItems = ["Item 1", "Item 2"];
+ `,
+ output: `import { MenuToggle } from '@patternfly/react-core';
+ const sbItems = ["Item 1", "Item 2"];
+ `,
+ errors: [generalError],
+ },
+ {
+ // identifier
+ code: `import { MenuToggle } from '@patternfly/react-core'; import { optionsObject } from 'somewhere';
+ `,
+ output: `import { MenuToggle } from '@patternfly/react-core'; import { optionsObject } from 'somewhere';
+ `,
+ errors: [generalError],
+ },
+ {
+ // object expression with a spreaded object
+ code: `import { MenuToggle } from '@patternfly/react-core'; import { optionsObject } from 'somewhere';
+ `,
+ output: `import { MenuToggle } from '@patternfly/react-core'; import { optionsObject } from 'somewhere';
+ `,
+ errors: [generalError],
+ },
+ {
+ // identifier + SplitButtonOptions type
+ code: `import { MenuToggle, SplitButtonOptions } from '@patternfly/react-core';
+ const sbOptions: SplitButtonOptions = { items: sbItems, variant: "action" };
+ `,
+ output: `import { MenuToggle, } from '@patternfly/react-core';
+ const sbOptions = { items: sbItems, variant: "action" };
+ `,
+ errors: [
+ {
+ message: interfaceRemovedMessage,
+ type: "ImportSpecifier",
+ },
+ {
+ message: interfaceRemovedMessage,
+ type: "Identifier",
+ },
+ generalError,
+ ],
+ },
+ {
+ // SplitButtonOptions named export
+ code: `import { SplitButtonOptions } from '@patternfly/react-core';
+ export { SplitButtonOptions as SBO };`,
+ output: `
+ `,
+ errors: [
+ {
+ message: interfaceRemovedMessage,
+ type: "ImportSpecifier",
+ },
+ {
+ message: interfaceRemovedMessage,
+ type: "ExportNamedDeclaration",
+ },
+ ],
+ },
+ {
+ // SplitButtonOptions default export
+ code: `import { SplitButtonOptions } from '@patternfly/react-core';
+ export default SplitButtonOptions;`,
+ output: `
+ `,
+ errors: [
+ {
+ message: interfaceRemovedMessage,
+ type: "ImportSpecifier",
+ },
+ {
+ message: interfaceRemovedMessage,
+ type: "ExportDefaultDeclaration",
+ },
+ ],
+ },
+ ],
+});
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggle-remove-splitButtonOptions.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggle-remove-splitButtonOptions.ts
new file mode 100644
index 00000000..1b3ca0bf
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggle-remove-splitButtonOptions.ts
@@ -0,0 +1,171 @@
+import { Rule } from "eslint";
+import {
+ ExportDefaultDeclaration,
+ ExportNamedDeclaration,
+ Identifier,
+ ImportSpecifier,
+ JSXOpeningElement,
+ Property,
+ SpreadElement,
+} from "estree-jsx";
+import {
+ checkMatchingJSXOpeningElement,
+ getAttribute,
+ getFromPackage,
+ getObjectProperty,
+ ImportSpecifierWithParent,
+ removeSpecifierFromDeclaration,
+} from "../../helpers";
+
+// https://github.com/patternfly/patternfly-react/pull/11096
+module.exports = {
+ meta: { fixable: "code" },
+ create: function (context: Rule.RuleContext) {
+ const message =
+ "We have replaced `splitButtonOptions` prop on MenuToggle with `splitButtonItems`. SplitButtonOptions interface has been removed, because its `variant` prop no longer supports the 'action' option. The `items` prop of SplitButtonOptions will be passed directly to MenuToggle's new `splitButtonItems` prop.";
+ const interfaceRemovedMessage = `The SplitButtonOptions interface has been removed.`;
+
+ const basePackage = "@patternfly/react-core";
+ const { imports: menuToggleImports } = getFromPackage(
+ context,
+ basePackage,
+ ["MenuToggle"]
+ );
+ const { imports: splitButtonOptionsImports } = getFromPackage(
+ context,
+ basePackage,
+ ["SplitButtonOptions"]
+ );
+ const splitButtonOptionsLocalNames = splitButtonOptionsImports.map(
+ (specifier) => specifier.local.name
+ );
+
+ if (!menuToggleImports && !splitButtonOptionsImports) {
+ return;
+ }
+
+ return {
+ JSXOpeningElement(node: JSXOpeningElement) {
+ if (!checkMatchingJSXOpeningElement(node, menuToggleImports)) {
+ return;
+ }
+
+ const splitButtonOptionsProp = getAttribute(node, "splitButtonOptions");
+
+ if (
+ !splitButtonOptionsProp ||
+ splitButtonOptionsProp.value?.type !== "JSXExpressionContainer"
+ ) {
+ return;
+ }
+
+ const reportAndFix = (splitButtonItemsValue: string) => {
+ context.report({
+ node,
+ message,
+ fix(fixer) {
+ return fixer.replaceText(
+ splitButtonOptionsProp,
+ `splitButtonItems={${splitButtonItemsValue}}`
+ );
+ },
+ });
+ };
+
+ const reportAndFixIdentifier = (identifier: Identifier) => {
+ reportAndFix(`${identifier.name}.items`);
+ };
+
+ const propValue = splitButtonOptionsProp.value.expression;
+ if (propValue.type === "Identifier") {
+ reportAndFixIdentifier(propValue);
+ }
+
+ if (propValue.type === "ObjectExpression") {
+ const properties = propValue.properties.filter(
+ (prop) => prop.type === "Property"
+ ) as Property[];
+ const itemsProperty = getObjectProperty(context, properties, "items");
+
+ if (itemsProperty) {
+ const itemsPropertyValueString = context
+ .getSourceCode()
+ .getText(itemsProperty.value);
+
+ reportAndFix(itemsPropertyValueString);
+ } else {
+ const spreadElement = propValue.properties.find(
+ (prop) => prop.type === "SpreadElement"
+ ) as SpreadElement | undefined;
+ if (spreadElement && spreadElement.argument.type === "Identifier") {
+ reportAndFixIdentifier(spreadElement.argument);
+ }
+ }
+ }
+ },
+ Identifier(node: Identifier) {
+ const typeName = (node as any).typeAnnotation?.typeAnnotation?.typeName
+ ?.name;
+
+ if (splitButtonOptionsLocalNames.includes(typeName)) {
+ context.report({
+ node,
+ message: interfaceRemovedMessage,
+ fix(fixer) {
+ return fixer.remove((node as any).typeAnnotation);
+ },
+ });
+ }
+ },
+ ImportSpecifier(node: ImportSpecifier) {
+ if (splitButtonOptionsImports.includes(node)) {
+ context.report({
+ node,
+ message: interfaceRemovedMessage,
+ fix(fixer) {
+ return removeSpecifierFromDeclaration(
+ fixer,
+ context,
+ (node as ImportSpecifierWithParent).parent!,
+ node
+ );
+ },
+ });
+ }
+ },
+ ExportNamedDeclaration(node: ExportNamedDeclaration) {
+ const specifierToRemove = node.specifiers.find((specifier) =>
+ splitButtonOptionsLocalNames.includes(specifier.local.name)
+ );
+ if (specifierToRemove) {
+ context.report({
+ node,
+ message: interfaceRemovedMessage,
+ fix(fixer) {
+ return removeSpecifierFromDeclaration(
+ fixer,
+ context,
+ node,
+ specifierToRemove
+ );
+ },
+ });
+ }
+ },
+ ExportDefaultDeclaration(node: ExportDefaultDeclaration) {
+ if (
+ node.declaration.type === "Identifier" &&
+ splitButtonOptionsLocalNames.includes(node.declaration.name)
+ ) {
+ context.report({
+ node,
+ message: interfaceRemovedMessage,
+ fix(fixer) {
+ return fixer.remove(node);
+ },
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggleRemoveSplitButtonOptionsInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggleRemoveSplitButtonOptionsInput.tsx
new file mode 100644
index 00000000..cd783f7a
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggleRemoveSplitButtonOptionsInput.tsx
@@ -0,0 +1,18 @@
+import { MenuToggle, SplitButtonOptions } from "@patternfly/react-core";
+
+const sbOptions: SplitButtonOptions = {
+ items: ["Item 1", "Item 2"],
+ variant: "action",
+};
+
+export const MenuToggleRemoveSplitButtonOptionsInput = () => (
+ <>
+
+
+ >
+);
diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggleRemoveSplitButtonOptionsOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggleRemoveSplitButtonOptionsOutput.tsx
new file mode 100644
index 00000000..6e10b13d
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/menuToggleRemoveSplitButtonOptions/menuToggleRemoveSplitButtonOptionsOutput.tsx
@@ -0,0 +1,15 @@
+import { MenuToggle, } from "@patternfly/react-core";
+
+const sbOptions = {
+ items: ["Item 1", "Item 2"],
+ variant: "action",
+};
+
+export const MenuToggleRemoveSplitButtonOptionsInput = () => (
+ <>
+
+
+ >
+);