Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,30 @@ const valid = [
];

const invalid = [
// --- Without includeTable (default) ---
{
code: "import { Table } from '@patternfly/react-table'; <Table />",
output: "import { Table } from '@patternfly/react-table'; <Table />",
errors: [
{
message: "Consider adding hasAnimations prop to enable component animations.",
type: "JSXOpeningElement"
}
]
},
// --- With includeTable: true ---
{
code: "import { Table } from '@patternfly/react-table'; <Table />",
output: "import { Table } from '@patternfly/react-table'; <Table hasAnimations />",
errors: [
{
message: "Consider adding hasAnimations prop to enable component animations.",
type: "JSXOpeningElement"
}
],
options: [{ includeTable: true }]
},
// --- Core components (unchanged) ---
{
code: "import { AlertGroup } from '@patternfly/react-core'; <AlertGroup />",
output: "import { AlertGroup } from '@patternfly/react-core'; <AlertGroup hasAnimations />",
Expand Down Expand Up @@ -78,27 +102,11 @@ const invalid = [
}
]
},
{
code: "import { Table } from '@patternfly/react-table'; <Table />",
output: "import { Table } from '@patternfly/react-table'; <Table hasAnimations />",
errors: [
{
message: "Consider adding hasAnimations prop to enable component animations.",
type: "JSXOpeningElement"
}
]
},
{
code: `import { AlertGroup, TreeView } from '@patternfly/react-core';
<>
<AlertGroup />
<TreeView />
</>`,
<><AlertGroup /><TreeView /></>`,
output: `import { AlertGroup, TreeView } from '@patternfly/react-core';
<>
<AlertGroup hasAnimations />
<TreeView hasAnimations />
</>`,
<><AlertGroup hasAnimations /><TreeView hasAnimations /></>`,
errors: [
{
message: "Consider adding hasAnimations prop to enable component animations.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Rule } from "eslint";
import { JSXOpeningElement, JSXAttribute } from "estree-jsx";
import { getFromPackage, checkMatchingJSXOpeningElement } from "../../helpers";
import { getFromPackage, checkMatchingJSXOpeningElement, getNodeName } from "../../helpers";
import { getAttribute, getAttributeValue } from "../../helpers/JSXAttributes";

// Rule to add hasAnimations prop to components that support animations
module.exports = {
meta: { fixable: "code" },
create: function (context: Rule.RuleContext) {
// Get options from context (set by CLI)
const includeTable =
(context.settings && context.settings.enableAnimationsIncludeTable) ||
(context.options && context.options[0] && context.options[0].includeTable) ||
false;

// Get imports from both react-core and react-table packages
const { imports: coreImports } = getFromPackage(
context,
Expand Down Expand Up @@ -34,6 +40,7 @@ module.exports = {
targetComponents.includes(specifier.imported.name)
);

// Always include Table imports for detection, but only add hasAnimations if includeTable is true
const targetTableImports = tableImports.filter((specifier) =>
tableComponents.includes(specifier.imported.name)
);
Expand Down Expand Up @@ -68,20 +75,14 @@ module.exports = {
return true;
}

// Helper function to get component name from node
function getComponentName(node: JSXOpeningElement): string | null {
if (node.name.type === "JSXIdentifier") {
return node.name.name;
}
return null;
}


return allTargetImports.length === 0
? {}
: {
JSXOpeningElement(node: JSXOpeningElement) {
if (checkMatchingJSXOpeningElement(node, allTargetImports)) {
const componentName = getComponentName(node);
const componentName = getNodeName(node);

// Special handling for DualListSelector - only add hasAnimations if isTree is true
if (componentName === "DualListSelector" && !hasValidIsTreeProp(node)) {
Expand All @@ -98,10 +99,13 @@ module.exports = {

// Only add prop if it doesn't already exist
if (!hasAnimationsAttribute) {
// For Table, only apply the fix if includeTable is true
const shouldApplyFix = componentName !== "Table" || includeTable;

context.report({
node,
message,
fix(fixer) {
fix: shouldApplyFix ? (fixer) => {
// Insert hasAnimations at the end of existing attributes
if (node.attributes.length > 0) {
const lastAttribute = node.attributes[node.attributes.length - 1];
Expand All @@ -110,7 +114,7 @@ module.exports = {
// No existing attributes, insert after component name
return fixer.insertTextAfter(node.name, " hasAnimations");
}
},
} : undefined,
});
}
}
Expand Down
41 changes: 39 additions & 2 deletions packages/pf-codemods/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,13 @@ async function getRulesToRemove(options) {
const pfVersions = ["v6", "v5", "v4"];
let selectedVersion = pfVersions.find((version) => options[version]);

if (!selectedVersion) {
// If only enable-animations is being run, skip the PatternFly version prompt and default to v6 logic
let skipVersionPrompt = false;
if (options.only && options.only.split(",").length === 1 && options.only.includes("enable-animations")) {
skipVersionPrompt = true;
}

if (!selectedVersion && !skipVersionPrompt) {
const inquirer = await import("inquirer");
const answer = await inquirer.default.prompt([
{
Expand All @@ -87,8 +93,9 @@ async function getRulesToRemove(options) {
],
},
]);

selectedVersion = answer.version;
} else if (skipVersionPrompt) {
return [];
}

return pfVersions.flatMap((version) =>
Expand All @@ -99,6 +106,33 @@ async function getRulesToRemove(options) {
async function runCodemods(path, otherPaths, options) {
const prefix = "@patternfly/pf-codemods/";

// Determine if enable-animations is being run
let enableAnimationsRule = false;
if (options.only) {
enableAnimationsRule = options.only.split(",").includes("enable-animations");
} else {
enableAnimationsRule = Object.keys(configs.recommended.rules).includes(prefix + "enable-animations");
}

let enableAnimationsIncludeTable = false;
if (enableAnimationsRule) {
const inquirer = await import("inquirer");
const answer = await inquirer.default.prompt([
{
type: "list",
name: "includeTable",
message:
"This will update several React Core components. Would you like to include Table? (Note: Opting into table animations may require structural updates in your codebase. See our release highlights for more information.)",
choices: [
{ name: "Just React Core components", value: false },
{ name: "React Core and Table components", value: true },
],
default: 0,
},
]);
enableAnimationsIncludeTable = answer.includeTable;
}

if (options.only) {
// Set rules to error like eslint likes
configs.recommended.rules = options.only.split(",").reduce((acc, rule) => {
Expand Down Expand Up @@ -159,6 +193,9 @@ async function runCodemods(path, otherPaths, options) {
{},
...rulesArr.filter(filterFunc).map((r) => r.rule)
),
settings: enableAnimationsRule ? {
enableAnimationsIncludeTable: enableAnimationsIncludeTable
} : {}
},
});
});
Expand Down
Loading