Skip to content

Commit fed60da

Browse files
authored
feat(853): favorite button codemod (#854)
Signed-off-by: gitdallas <[email protected]>
1 parent 54c7121 commit fed60da

File tree

7 files changed

+343
-0
lines changed

7 files changed

+343
-0
lines changed

packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const warningRules = [
1313
"aboutModalBoxHero-remove-subcomponent",
1414
"accordionItem-warn-update-markup",
1515
"applicationLauncher-warn-input",
16+
"button-support-favorite-variant",
1617
"card-deprecate-props",
1718
"card-warn-component",
1819
"charts-warn-tooltip",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
### button-support-favorite-variant [(#11853)](https://github.com/patternfly/patternfly-react/pull/11853)
2+
3+
This rule detects when you're using `StarIcon` with Button components and suggests using the new `isFavorite` and `isFavorited` props instead. This supports the new favorite variant that includes animation styling.
4+
5+
#### Examples
6+
7+
In:
8+
9+
```jsx
10+
%inputExample%
11+
```
12+
13+
Out:
14+
15+
```jsx
16+
%outputExample%
17+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
const ruleTester = require("../../ruletester");
2+
import * as rule from "./button-support-favorite-variant";
3+
4+
ruleTester.run("button-support-favorite-variant", rule, {
5+
valid: [
6+
{
7+
code: `<Button isFavorite={true} />`,
8+
},
9+
{
10+
code: `<Button isFavorited={true} />`,
11+
},
12+
{
13+
code: `<Button isFavorite={true} isFavorited={false} />`,
14+
},
15+
{
16+
code: `import { Button } from '@patternfly/react-core'; <Button>Some text</Button>`,
17+
},
18+
{
19+
code: `import { Button } from '@patternfly/react-core'; import { SomeIcon } from '@patternfly/react-icons'; <Button><SomeIcon /></Button>`,
20+
},
21+
{
22+
code: `import { Button } from '@patternfly/react-core'; <Button icon={<SomeIcon />} />`,
23+
},
24+
// Button from PF, StarIcon not from PF - should not trigger
25+
{
26+
code: `import { Button } from '@patternfly/react-core'; import { StarIcon } from '@some-other-package'; <Button><StarIcon /></Button>`,
27+
},
28+
// Button not from PF, StarIcon from PF - should not trigger
29+
{
30+
code: `import { Button } from '@some-other-package'; import { StarIcon } from '@patternfly/react-icons'; <Button><StarIcon /></Button>`,
31+
},
32+
// Both Button and StarIcon not from PF - should not trigger
33+
{
34+
code: `import { Button } from '@some-other-package'; import { StarIcon } from '@another-package'; <Button><StarIcon /></Button>`,
35+
},
36+
// Button from PF, StarIcon not from PF (in icon prop) - should not trigger
37+
{
38+
code: `import { Button } from '@patternfly/react-core'; import { StarIcon } from '@some-other-package'; <Button icon={<StarIcon />} />`,
39+
},
40+
// Button not from PF, StarIcon from PF (in icon prop) - should not trigger
41+
{
42+
code: `import { Button } from '@some-other-package'; import { StarIcon } from '@patternfly/react-icons'; <Button icon={<StarIcon />} />`,
43+
},
44+
],
45+
invalid: [
46+
// Simple case - StarIcon as child
47+
{
48+
code: `import { Button } from '@patternfly/react-core'; import { StarIcon } from '@patternfly/react-icons'; <Button><StarIcon /></Button>`,
49+
errors: [
50+
{
51+
message: "It looks like you are trying to create a custom favorites button. Use `isFavorite` and `isFavorited` button properties instead to apply the correct styling.",
52+
type: "JSXElement",
53+
},
54+
],
55+
},
56+
// StarIcon in icon prop
57+
{
58+
code: `import { Button } from '@patternfly/react-core'; import { StarIcon } from '@patternfly/react-icons'; <Button icon={<StarIcon />} />`,
59+
errors: [
60+
{
61+
message: "It looks like you are trying to create a custom favorites button. Use `isFavorite` and `isFavorited` button properties instead to apply the correct styling.",
62+
type: "JSXElement",
63+
},
64+
],
65+
},
66+
// StarIcon with aliased import
67+
{
68+
code: `import { Button } from '@patternfly/react-core'; import { StarIcon as Star } from '@patternfly/react-icons'; <Button><Star /></Button>`,
69+
errors: [
70+
{
71+
message: "It looks like you are trying to create a custom favorites button. Use `isFavorite` and `isFavorited` button properties instead to apply the correct styling.",
72+
type: "JSXElement",
73+
},
74+
],
75+
},
76+
// Button with aliased import
77+
{
78+
code: `import { Button as PFButton } from '@patternfly/react-core'; import { StarIcon } from '@patternfly/react-icons'; <PFButton><StarIcon /></PFButton>`,
79+
errors: [
80+
{
81+
message: "It looks like you are trying to create a custom favorites button. Use `isFavorite` and `isFavorited` button properties instead to apply the correct styling.",
82+
type: "JSXElement",
83+
},
84+
],
85+
},
86+
// StarIcon with text
87+
{
88+
code: `import { Button } from '@patternfly/react-core'; import { StarIcon } from '@patternfly/react-icons'; <Button>Favorite <StarIcon /></Button>`,
89+
errors: [
90+
{
91+
message: "It looks like you are trying to create a custom favorites button. Use `isFavorite` and `isFavorited` button properties instead to apply the correct styling.",
92+
type: "JSXElement",
93+
},
94+
],
95+
},
96+
// StarIcon with other icon
97+
{
98+
code: `import { Button } from '@patternfly/react-core'; import { StarIcon, SomeIcon } from '@patternfly/react-icons'; <Button><SomeIcon /><StarIcon /></Button>`,
99+
errors: [
100+
{
101+
message: "It looks like you are trying to create a custom favorites button. Use `isFavorite` and `isFavorited` button properties instead to apply the correct styling.",
102+
type: "JSXElement",
103+
},
104+
],
105+
},
106+
// StarIcon in fragment with text
107+
{
108+
code: `import { Button } from '@patternfly/react-core'; import { StarIcon } from '@patternfly/react-icons'; <Button><>Favorite <StarIcon /></></Button>`,
109+
errors: [
110+
{
111+
message: "It looks like you are trying to create a custom favorites button. Use `isFavorite` and `isFavorited` button properties instead to apply the correct styling.",
112+
type: "JSXElement",
113+
},
114+
],
115+
},
116+
// StarIcon with existing props
117+
{
118+
code: `import { Button } from '@patternfly/react-core'; import { StarIcon } from '@patternfly/react-icons'; <Button variant="primary"><StarIcon /></Button>`,
119+
errors: [
120+
{
121+
message: "It looks like you are trying to create a custom favorites button. Use `isFavorite` and `isFavorited` button properties instead to apply the correct styling.",
122+
type: "JSXElement",
123+
},
124+
],
125+
},
126+
],
127+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Rule } from "eslint";
2+
import { JSXElement, JSXIdentifier, JSXFragment } from "estree-jsx";
3+
import {
4+
getFromPackage,
5+
getAttribute,
6+
getAttributeValue,
7+
isReactIcon,
8+
} from "../../helpers";
9+
10+
// https://www.patternfly.org/components/button/#favorite
11+
module.exports = {
12+
meta: {},
13+
create: function (context: Rule.RuleContext) {
14+
const { imports: buttonImports } = getFromPackage(context, "@patternfly/react-core");
15+
const { imports: iconImports } = getFromPackage(context, "@patternfly/react-icons");
16+
17+
const buttonImport = buttonImports.find(
18+
(specifier) => specifier.imported.name === "Button"
19+
);
20+
21+
if (!buttonImport) {
22+
return {};
23+
}
24+
25+
// Helper function to check if a JSX element is a StarIcon
26+
const isStarIcon = (element: JSXElement) => {
27+
return isReactIcon(context, element) &&
28+
iconImports.some(
29+
(specifier) => specifier.imported.name === "StarIcon" &&
30+
specifier.local.name === (element.openingElement.name as JSXIdentifier).name
31+
);
32+
};
33+
34+
// Helper function to recursively check for StarIcon in children
35+
const hasStarIconInChildren = (children: any[]): boolean => {
36+
return children.some((child) => {
37+
if (child.type === "JSXElement") {
38+
return isStarIcon(child);
39+
}
40+
if (child.type === "JSXFragment") {
41+
return hasStarIconInChildren(child.children);
42+
}
43+
return false;
44+
});
45+
};
46+
47+
return {
48+
JSXElement(node: JSXElement) {
49+
if (
50+
node.openingElement.name.type === "JSXIdentifier" &&
51+
buttonImport.local.name === node.openingElement.name.name
52+
) {
53+
// Check if Button already has isFavorite or isFavorited props
54+
const isFavoriteProp = getAttribute(node.openingElement, "isFavorite");
55+
const isFavoritedProp = getAttribute(node.openingElement, "isFavorited");
56+
57+
if (isFavoriteProp || isFavoritedProp) {
58+
return; // Already using the new props
59+
}
60+
61+
// Check for StarIcon in the icon prop
62+
const iconProp = getAttribute(node.openingElement, "icon");
63+
if (iconProp && iconProp.value?.type === "JSXExpressionContainer") {
64+
const iconExpression = iconProp.value.expression;
65+
if (iconExpression.type === "JSXElement" && isStarIcon(iconExpression)) {
66+
context.report({
67+
node,
68+
message: `It looks like you are trying to create a custom favorites button. Use \`isFavorite\` and \`isFavorited\` button properties instead to apply the correct styling.`,
69+
});
70+
return;
71+
}
72+
}
73+
74+
// Check for StarIcon in children (including fragments)
75+
const hasStarIconChild = hasStarIconInChildren(node.children);
76+
77+
if (!hasStarIconChild) {
78+
return; // No StarIcon found
79+
}
80+
81+
// Report warning for all cases
82+
context.report({
83+
node,
84+
message: `It looks like you are trying to create a custom favorites button. Use \`isFavorite\` and \`isFavorited\` button properties instead to apply the correct styling.`,
85+
});
86+
}
87+
},
88+
};
89+
},
90+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Button } from "@patternfly/react-core";
2+
import { StarIcon, SomeIcon } from "@patternfly/react-icons";
3+
4+
export const ButtonSupportFavoriteVariantInput = () => (
5+
<>
6+
{/* Simple case - can be auto-fixed */}
7+
<Button>
8+
<StarIcon />
9+
</Button>
10+
11+
{/* StarIcon in icon prop - can be auto-fixed */}
12+
<Button icon={<StarIcon />} />
13+
14+
{/* StarIcon with existing props - can be auto-fixed */}
15+
<Button variant="primary">
16+
<StarIcon />
17+
</Button>
18+
19+
{/* StarIcon with text - warning only */}
20+
<Button>
21+
Favorite <StarIcon />
22+
</Button>
23+
24+
{/* StarIcon with other icon - warning only */}
25+
<Button>
26+
<SomeIcon />
27+
<StarIcon />
28+
</Button>
29+
30+
{/* StarIcon in fragment - warning only */}
31+
<Button>
32+
<>Favorite <StarIcon /></>
33+
</Button>
34+
35+
{/* Already using new props - should be ignored */}
36+
<Button isFavorite={true} />
37+
<Button isFavorited={true} />
38+
</>
39+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Button } from "@patternfly/react-core";
2+
import { StarIcon, SomeIcon } from "@patternfly/react-icons";
3+
4+
export const ButtonSupportFavoriteVariantInput = () => (
5+
<>
6+
{/* Simple case - warning only */}
7+
<Button>
8+
<StarIcon />
9+
</Button>
10+
11+
{/* StarIcon in icon prop - warning only */}
12+
<Button icon={<StarIcon />} />
13+
14+
{/* StarIcon with existing props - warning only */}
15+
<Button variant="primary">
16+
<StarIcon />
17+
</Button>
18+
19+
{/* StarIcon with text - warning only */}
20+
<Button>
21+
Favorite <StarIcon />
22+
</Button>
23+
24+
{/* StarIcon with other icon - warning only */}
25+
<Button>
26+
<SomeIcon />
27+
<StarIcon />
28+
</Button>
29+
30+
{/* StarIcon in fragment - warning only */}
31+
<Button>
32+
<>Favorite <StarIcon /></>
33+
</Button>
34+
35+
{/* Already using new props - no warning */}
36+
<Button isFavorite={true} />
37+
<Button isFavorited={true} />
38+
</>
39+
);

packages/pf-codemods/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2235,6 +2235,36 @@ export const PageToggleButtonReplaceBarsIconWithIsHamburgerButtonOutput = () =>
22352235
);
22362236
```
22372237

2238+
### button-support-favorite-variant [(#11853)](https://github.com/patternfly/patternfly-react/pull/11853)
2239+
2240+
This rule detects when you're using `StarIcon` with Button components and suggests using the new `isFavorite` and `isFavorited` props instead. This supports the new favorite variant that includes animation styling.
2241+
2242+
#### Examples
2243+
2244+
In:
2245+
2246+
```jsx
2247+
import { Button } from "@patternfly/react-core";
2248+
import { StarIcon } from "@patternfly/react-icons";
2249+
2250+
export const ButtonSupportFavoriteVariantInput = () => (
2251+
<Button>
2252+
<StarIcon />
2253+
</Button>
2254+
);
2255+
```
2256+
2257+
Out:
2258+
2259+
```jsx
2260+
import { Button } from "@patternfly/react-core";
2261+
import { StarIcon } from "@patternfly/react-icons";
2262+
2263+
export const ButtonSupportFavoriteVariantOutput = () => (
2264+
<Button isFavorite={true} />
2265+
);
2266+
```
2267+
22382268
### pagination-warn-markup-changed [(#10662)](https://github.com/patternfly/patternfly-react/pull/10662)
22392269

22402270
The markup for Pagination has changed. There is now a wrapper element rendered around the PaginationOptionsMenu toggle. This rule does not provide a fixer, but will throw a warning.

0 commit comments

Comments
 (0)