diff --git a/packages/eslint-plugin-pf-codemods/index.js b/packages/eslint-plugin-pf-codemods/index.js index be6d68e80..5230d3ccd 100644 --- a/packages/eslint-plugin-pf-codemods/index.js +++ b/packages/eslint-plugin-pf-codemods/index.js @@ -1,7 +1,11 @@ const createListOfRules = (version) => { const rules = {}; require("glob") - .sync(require.resolve('@patternfly/eslint-plugin-pf-codemods').replace('index.js', `lib/rules/v${version}/*.js`)) + .sync( + require + .resolve("@patternfly/eslint-plugin-pf-codemods") + .replace("index.js", `lib/rules/v${version}/*.js`) + ) .forEach(function (file) { const ruleName = /.*\/([^.]+)/.exec(file)[1]; rules[ruleName] = require(`./lib/rules/v${version}/${ruleName}`); @@ -19,7 +23,6 @@ const warningRules = [ "charts-tooltip-warning", "datePicker-warn-appendTo-default-value-changed", "horizontalSubnav-ariaLabel", - "onToggle-warn-event", "nav-warn-flyouts-now-inline", "popover-appendTo-default", "react-dropzone-warn-upgrade", diff --git a/packages/eslint-plugin-pf-codemods/lib/helpers.js b/packages/eslint-plugin-pf-codemods/lib/helpers.js index 787c01d85..eca3b7494 100644 --- a/packages/eslint-plugin-pf-codemods/lib/helpers.js +++ b/packages/eslint-plugin-pf-codemods/lib/helpers.js @@ -178,6 +178,90 @@ function ensureImports(context, node, package, imports) { } } +function addCallbackParam(componentsArray, propMap) { + return function (context) { + const imports = getPackageImports(context, "@patternfly/react-core").filter( + (specifier) => componentsArray.includes(specifier.imported.name) + ); + + return !imports.length + ? {} + : { + JSXOpeningElement(node) { + if (imports.map((imp) => imp.local.name).includes(node.name.name)) { + const namedAttributes = node.attributes.filter((attr) => + Object.keys(propMap).includes(attr.name?.name) + ); + + namedAttributes.forEach((attribute) => { + const newParam = propMap[attribute.name.name]; + + const propProperties = { + type: attribute.value?.expression?.type, + name: attribute.value?.expression?.name, + }; + + if (propProperties.type === "ArrowFunctionExpression") { + propProperties.params = attribute.value?.expression?.params; + } else if (propProperties.type === "Identifier") { + const currentScope = context.getScope(); + const matchingVariable = currentScope.variables.find( + (variable) => variable.name === propProperties.name + ); + const matchingDefinition = matchingVariable.defs.find( + (def) => def.name.name === propProperties.name + ); + + propProperties.params = + matchingDefinition.type === "FunctionName" + ? matchingDefinition.node.params + : matchingDefinition.node.init.params; + } + const { type, params } = propProperties; + + if ( + (params?.length === 1 && + ["ArrowFunctionExpression", "Identifier"].includes(type)) || + type === "MemberExpression" + ) { + context.report({ + node, + message: `The "${attribute.name.name}" prop for ${node.name.name} has been updated to include the "${newParam}" parameter as its first parameter. "${attribute.name.name}" handlers may require an update.`, + fix(fixer) { + const fixes = []; + const createReplacerFix = (functionParam) => { + const hasParenthesis = + context.getTokenAfter(functionParam).value === ")"; + const replacementParams = `${newParam}, ${functionParam.name}`; + + return fixer.replaceText( + functionParam, + hasParenthesis + ? replacementParams + : `(${replacementParams})` + ); + }; + + if ( + ["ArrowFunctionExpression", "Identifier"].includes( + type + ) && + params.length === 1 + ) { + fixes.push(createReplacerFix(params[0])); + } + + return fixes; + }, + }); + } + }); + } + }, + }; + }; +} + module.exports = { ensureImports, getPackageImports, @@ -185,4 +269,5 @@ module.exports = { renameProps0, renameProps, renameComponents, + addCallbackParam } diff --git a/packages/eslint-plugin-pf-codemods/lib/rules/v5/dataList-updated-callback.js b/packages/eslint-plugin-pf-codemods/lib/rules/v5/dataList-updated-callback.js new file mode 100644 index 000000000..4be374fdb --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/lib/rules/v5/dataList-updated-callback.js @@ -0,0 +1,7 @@ +const { addCallbackParam } = require("../../helpers"); + +// https://github.com/patternfly/patternfly-react/pull/8723 +module.exports = { + meta: { fixable: "code" }, + create: addCallbackParam(["DataList"], { onSelectDataListItem: "_event" }), +}; diff --git a/packages/eslint-plugin-pf-codemods/lib/rules/v5/onToggle-updated-paramaters.js b/packages/eslint-plugin-pf-codemods/lib/rules/v5/onToggle-updated-paramaters.js new file mode 100644 index 000000000..942ad680b --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/lib/rules/v5/onToggle-updated-paramaters.js @@ -0,0 +1,15 @@ +const { addCallbackParam } = require("../../helpers"); +const onToggleAPIUpdateList = [ + "ApplicationLauncher", + "BadgeToggle", + "DropdownToggle", + "KebabToggle", + "Toggle", + "Select", + "SelectToggle", +]; +// https://github.com/patternfly/patternfly-react/pull/8667 +module.exports = { + meta: { fixable: "code" }, + create: addCallbackParam(onToggleAPIUpdateList, { onToggle: "_event" }), +}; diff --git a/packages/eslint-plugin-pf-codemods/lib/rules/v5/onToggle-warn-event.js b/packages/eslint-plugin-pf-codemods/lib/rules/v5/onToggle-warn-event.js deleted file mode 100644 index 8b73f0b73..000000000 --- a/packages/eslint-plugin-pf-codemods/lib/rules/v5/onToggle-warn-event.js +++ /dev/null @@ -1,29 +0,0 @@ -const { getPackageImports } = require("../../helpers"); - -// https://github.com/patternfly/patternfly-react/pull/8667 -module.exports = { - create: function (context) { - const onToggleAPIUpdateList = ['ApplicationLauncher', 'BadgeToggle', 'DropdownToggle', 'KebabToggle', 'Toggle', 'Select', 'SelectToggle']; - const imports = getPackageImports( - context, - "@patternfly/react-core" - ).filter((specifier) => onToggleAPIUpdateList.includes(specifier.imported.name)); - - return imports.length === 0 - ? {} - : { - JSXOpeningElement(node) { - if ( - imports - .map((imp) => imp.local.name) - .includes(node.name.name) - ) { - context.report({ - node, - message: `${node.name.name} onToggle prop has been updated to include the event parameter as its first parameter. onToggle handlers may require an update.`, - }); - } - }, - }; - }, -}; diff --git a/packages/eslint-plugin-pf-codemods/test/rules/v5/dataList-updated-callback.js b/packages/eslint-plugin-pf-codemods/test/rules/v5/dataList-updated-callback.js new file mode 100644 index 000000000..3498e05f7 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/test/rules/v5/dataList-updated-callback.js @@ -0,0 +1,85 @@ +const ruleTester = require("../../ruletester"); +const rule = require("../../../lib/rules/v5/dataList-updated-callback"); + +ruleTester.run("dataList-updated-callback", rule, { + valid: [ + { + code: `import { DataList } from '@patternfly/react-core'; ;`, + }, + { + code: `import { DataList } from '@patternfly/react-core'; onSelect()} />;`, + }, + { + code: `import { DataList } from '@patternfly/react-core'; const onSelect = () => {}; ;`, + }, + { + code: `import { DataList } from '@patternfly/react-core'; function onSelect() {}; ;`, + }, + { + // No @patternfly/react-core import + code: `;`, + }, + ], + invalid: [ + { + code: `import { DataList } from '@patternfly/react-core'; onSelect(id)} />;`, + output: `import { DataList } from '@patternfly/react-core'; onSelect(id)} />;`, + errors: [ + { + message: `The "onSelectDataListItem" prop for DataList has been updated to include the "_event" parameter as its first parameter. "onSelectDataListItem" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DataList } from '@patternfly/react-core'; onSelect(id)} />;`, + output: `import { DataList } from '@patternfly/react-core'; onSelect(id)} />;`, + errors: [ + { + message: `The "onSelectDataListItem" prop for DataList has been updated to include the "_event" parameter as its first parameter. "onSelectDataListItem" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DataList } from '@patternfly/react-core'; const onSelect = (id) => {}; ;`, + output: `import { DataList } from '@patternfly/react-core'; const onSelect = (_event, id) => {}; ;`, + errors: [ + { + message: `The "onSelectDataListItem" prop for DataList has been updated to include the "_event" parameter as its first parameter. "onSelectDataListItem" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DataList } from '@patternfly/react-core'; function onSelect(id) {}; ;`, + output: `import { DataList } from '@patternfly/react-core'; function onSelect(_event, id) {}; ;`, + errors: [ + { + message: `The "onSelectDataListItem" prop for DataList has been updated to include the "_event" parameter as its first parameter. "onSelectDataListItem" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DataList } from '@patternfly/react-core'; ;`, + output: `import { DataList } from '@patternfly/react-core'; ;`, + errors: [ + { + message: `The "onSelectDataListItem" prop for DataList has been updated to include the "_event" parameter as its first parameter. "onSelectDataListItem" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DataList as PFDataList } from '@patternfly/react-core'; onSelect(id)} />;`, + output: `import { DataList as PFDataList } from '@patternfly/react-core'; onSelect(id)} />;`, + errors: [ + { + message: `The "onSelectDataListItem" prop for PFDataList has been updated to include the "_event" parameter as its first parameter. "onSelectDataListItem" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-pf-codemods/test/rules/v5/onToggle-updated-paramaters.js b/packages/eslint-plugin-pf-codemods/test/rules/v5/onToggle-updated-paramaters.js new file mode 100644 index 000000000..9b829056b --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/test/rules/v5/onToggle-updated-paramaters.js @@ -0,0 +1,84 @@ +const ruleTester = require("../../ruletester"); +const rule = require("../../../lib/rules/v5/onToggle-updated-paramaters"); +const onToggleAPIUpdateList = [ + "ApplicationLauncher", + "BadgeToggle", + "DropdownToggle", + "KebabToggle", + "Toggle", + "Select", + "SelectToggle", +]; + +ruleTester.run("onToggle-updated-paramaters", rule, { + valid: [ + ...onToggleAPIUpdateList.map((component) => ({ + code: `import { ${component} } from '@patternfly/react-core'; <${component} />;`, + })), + ...onToggleAPIUpdateList.map((component) => ({ + code: `import { ${component} } from '@patternfly/react-core'; <${component} onToggle={() => onToggle} />;`, + })), + ...onToggleAPIUpdateList.map((component) => ({ + code: `import { ${component} } from '@patternfly/react-core'; const onToggle = () => {}; <${component} onToggle={onToggle} />;`, + })), + ...onToggleAPIUpdateList.map((component) => ({ + code: `import { ${component} } from '@patternfly/react-core'; function onToggle() {}; <${component} onToggle={onToggle} />;`, + })), + // No @patternfly/react-core import + ...onToggleAPIUpdateList.map((component) => ({ + code: `<${component} onToggle />;`, + })), + ], + invalid: [ + ...onToggleAPIUpdateList.map((component) => ({ + code: `import { ${component} } from '@patternfly/react-core'; <${component} onToggle={(isOpen) => onToggle(isOpen)} />;`, + output: `import { ${component} } from '@patternfly/react-core'; <${component} onToggle={(_event, isOpen) => onToggle(isOpen)} />;`, + errors: [ + { + message: `The "onToggle" prop for ${component} has been updated to include the "_event" parameter as its first parameter. "onToggle" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + })), + ...onToggleAPIUpdateList.map((component) => ({ + code: `import { ${component} } from '@patternfly/react-core'; <${component} onToggle={isOpen => onToggle(isOpen)} />;`, + output: `import { ${component} } from '@patternfly/react-core'; <${component} onToggle={(_event, isOpen) => onToggle(isOpen)} />;`, + errors: [ + { + message: `The "onToggle" prop for ${component} has been updated to include the "_event" parameter as its first parameter. "onToggle" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + })), + ...onToggleAPIUpdateList.map((component) => ({ + code: `import { ${component} } from '@patternfly/react-core'; const onToggle = (isOpen) => {}; <${component} onToggle={onToggle} />;`, + output: `import { ${component} } from '@patternfly/react-core'; const onToggle = (_event, isOpen) => {}; <${component} onToggle={onToggle} />;`, + errors: [ + { + message: `The "onToggle" prop for ${component} has been updated to include the "_event" parameter as its first parameter. "onToggle" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + })), + ...onToggleAPIUpdateList.map((component) => ({ + code: `import { ${component} } from '@patternfly/react-core'; function onToggle(isOpen) {}; <${component} onToggle={onToggle} />;`, + output: `import { ${component} } from '@patternfly/react-core'; function onToggle(_event, isOpen) {}; <${component} onToggle={onToggle} />;`, + errors: [ + { + message: `The "onToggle" prop for ${component} has been updated to include the "_event" parameter as its first parameter. "onToggle" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + })), + ...onToggleAPIUpdateList.map((component) => ({ + code: `import { ${component} } from '@patternfly/react-core'; <${component} onToggle={this.onToggle} />;`, + output: `import { ${component} } from '@patternfly/react-core'; <${component} onToggle={this.onToggle} />;`, + errors: [ + { + message: `The "onToggle" prop for ${component} has been updated to include the "_event" parameter as its first parameter. "onToggle" handlers may require an update.`, + type: "JSXOpeningElement", + }, + ], + })), + ], +}); diff --git a/packages/eslint-plugin-pf-codemods/test/rules/v5/onToggle-warn-event.js b/packages/eslint-plugin-pf-codemods/test/rules/v5/onToggle-warn-event.js deleted file mode 100644 index afcb1ac74..000000000 --- a/packages/eslint-plugin-pf-codemods/test/rules/v5/onToggle-warn-event.js +++ /dev/null @@ -1,107 +0,0 @@ -const ruleTester = require('../../ruletester'); -const rule = require('../../../lib/rules/v5/onToggle-warn-event'); - -ruleTester.run("onToggle-warn-event", rule, { - valid: [ - { - code: ``, - }, - { - code: ``, - }, - { - code: ``, - }, - { - code: ``, - }, - { - code: ``, - }, - { - code: ``, - }, - { - code: `import { SelectToggle } from '@foo/bar'; `, - } - ], - invalid: [ - { - code: `import { ApplicationLauncher } from '@patternfly/react-core'; ;`, - output: `import { ApplicationLauncher } from '@patternfly/react-core'; ;`, - errors: [{ - message: `ApplicationLauncher onToggle prop has been updated to include the event parameter as its first parameter. onToggle handlers may require an update.`, - type: "JSXOpeningElement", - }] - }, - { - code: `import { BadgeToggle } from '@patternfly/react-core'; ;`, - output: `import { BadgeToggle } from '@patternfly/react-core'; ;`, - errors: [{ - message: `BadgeToggle onToggle prop has been updated to include the event parameter as its first parameter. onToggle handlers may require an update.`, - type: "JSXOpeningElement", - }] - }, - { - code: `import { DropdownToggle } from '@patternfly/react-core'; ;`, - output: `import { DropdownToggle } from '@patternfly/react-core'; ;`, - errors: [{ - message: `DropdownToggle onToggle prop has been updated to include the event parameter as its first parameter. onToggle handlers may require an update.`, - type: "JSXOpeningElement", - }] - }, - { - code: `import { KebabToggle } from '@patternfly/react-core'; ;`, - output: `import { KebabToggle } from '@patternfly/react-core'; ;`, - errors: [{ - message: `KebabToggle onToggle prop has been updated to include the event parameter as its first parameter. onToggle handlers may require an update.`, - type: "JSXOpeningElement", - }] - }, - { - code: `import { Toggle } from '@patternfly/react-core'; ;`, - output: `import { Toggle } from '@patternfly/react-core'; ;`, - errors: [{ - message: `Toggle onToggle prop has been updated to include the event parameter as its first parameter. onToggle handlers may require an update.`, - type: "JSXOpeningElement", - }] - }, - { - code: `import { Select } from '@patternfly/react-core'; ;`, - errors: [{ - message: `Select onToggle prop has been updated to include the event parameter as its first parameter. onToggle handlers may require an update.`, - type: "JSXOpeningElement", - }] - }, - { - code: `import { SelectToggle } from '@patternfly/react-core'; ;`, - output: `import { SelectToggle } from '@patternfly/react-core'; ;`, - errors: [{ - message: `SelectToggle onToggle prop has been updated to include the event parameter as its first parameter. onToggle handlers may require an update.`, - type: "JSXOpeningElement", - }] - }, - ] -}); diff --git a/packages/pf-codemods/README.md b/packages/pf-codemods/README.md index ed64f9c4a..49181c800 100644 --- a/packages/pf-codemods/README.md +++ b/packages/pf-codemods/README.md @@ -329,6 +329,36 @@ Out: ``` +### dataList-updated-callback [(#8723)](https://github.com/patternfly/patternfly-react/pull/8723) + +We've updated the `onSelectDataListItem` prop for DataList to include the `event` as its first parameter. Handlers may require an update. + +#### Examples + +In: + +```jsx + onSelect(id)} /> + +const toggle1 = (id) => {}; + + +function toggle2(id) {}; + +``` + +Out: + +```jsx + onSelect(id)} /> + +const toggle1 = (_event, id) => {}; + + +function toggle2(_event, id) {}; + +``` + ### datePicker-warn-appendTo-default-value-changed [(#8636)](https://github.com/patternfly/patternfly-react/pull/8636) The default value of the `appendTo` prop on DatePicker has been updated, which may cause markup changes that require updating selectors in tests. This rule will raise a warning, but will not make any changes. @@ -539,10 +569,36 @@ Out: ``` -### onToggle-warn-event [(#8667)](https://github.com/patternfly/patternfly-react/pull/8667) +### onToggle-updated-parameters [(#8667)](https://github.com/patternfly/patternfly-react/pull/8667) We've updated the `onToggle` function to include the `event` as its first parameter for the following components: `ApplicationLauncher`, `BadgeToggle`, `DropdownToggle`, `KebabToggle`, `Toggle`, `Select`, and `SelectToggle`. Handlers for these components may require an update. +#### Examples + +In: + +```jsx + onToggle(isOpen)} /> + +const toggleBadge = (isOpen) => {}; + + +function toggleDropdown(isOpen) {}; + +``` + +Out: + +```jsx + onToggle(isOpen)} /> + +const toggleBadge = (_event, isOpen) => {}; + + +function toggleDropdown(_event, isOpen) {}; + +``` + ### pageheader-update-logoComponent [(#8655)](https://github.com/patternfly/patternfly-react/pull/8655) We've updated `PageHeader`'s logo to only be an anchor if an `href` is specified, otherwise it will be a `span`. Explicitly declared `logoComponent` properties will remain unchanged, but if it is not specified a default will be added. diff --git a/test/test.tsx b/test/test.tsx index 69e80b573..e20d07f1e 100644 --- a/test/test.tsx +++ b/test/test.tsx @@ -11,12 +11,15 @@ import { AccordionExpandableContent, Alert, ApplicationLauncher, + BadgeToggle, Button, Card, + DataList, DatePicker, DropdownItem, DropdownToggle, FileUpload, + KebabToggle, KEY_CODES, MenuItem, MenuItemAction, @@ -26,6 +29,8 @@ import { NumberInput, Pagination, Popover, + Select, + SelectToggle, Spinner, Tabs, Toggle, @@ -45,12 +50,16 @@ const newTheme = getCustomTheme("1", "2", "3"); <> + +