diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/enableAnimations/enable-animations.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/enableAnimations/enable-animations.test.ts index 16a8f9d8..43a465ed 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/enableAnimations/enable-animations.test.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/enableAnimations/enable-animations.test.ts @@ -18,6 +18,30 @@ const valid = [ ]; const invalid = [ + // --- Without includeTable (default) --- + { + code: "import { Table } from '@patternfly/react-table'; ", + output: "import { Table } from '@patternfly/react-table';
", + errors: [ + { + message: "Consider adding hasAnimations prop to enable component animations.", + type: "JSXOpeningElement" + } + ] + }, + // --- With includeTable: true --- + { + code: "import { Table } from '@patternfly/react-table';
", + output: "import { Table } from '@patternfly/react-table';
", + 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'; ", output: "import { AlertGroup } from '@patternfly/react-core'; ", @@ -78,27 +102,11 @@ const invalid = [ } ] }, - { - code: "import { Table } from '@patternfly/react-table';
", - output: "import { Table } from '@patternfly/react-table';
", - errors: [ - { - message: "Consider adding hasAnimations prop to enable component animations.", - type: "JSXOpeningElement" - } - ] - }, { code: `import { AlertGroup, TreeView } from '@patternfly/react-core'; - <> - - - `, + <>`, output: `import { AlertGroup, TreeView } from '@patternfly/react-core'; - <> - - - `, + <>`, errors: [ { message: "Consider adding hasAnimations prop to enable component animations.", diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/enableAnimations/enable-animations.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/enableAnimations/enable-animations.ts index a9f75491..b473702a 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/enableAnimations/enable-animations.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/enableAnimations/enable-animations.ts @@ -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, @@ -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) ); @@ -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)) { @@ -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]; @@ -110,7 +114,7 @@ module.exports = { // No existing attributes, insert after component name return fixer.insertTextAfter(node.name, " hasAnimations"); } - }, + } : undefined, }); } } diff --git a/packages/pf-codemods/index.js b/packages/pf-codemods/index.js index f368bb9f..bf3e421d 100755 --- a/packages/pf-codemods/index.js +++ b/packages/pf-codemods/index.js @@ -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([ { @@ -87,8 +93,9 @@ async function getRulesToRemove(options) { ], }, ]); - selectedVersion = answer.version; + } else if (skipVersionPrompt) { + return []; } return pfVersions.flatMap((version) => @@ -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) => { @@ -159,6 +193,9 @@ async function runCodemods(path, otherPaths, options) { {}, ...rulesArr.filter(filterFunc).map((r) => r.rule) ), + settings: enableAnimationsRule ? { + enableAnimationsIncludeTable: enableAnimationsIncludeTable + } : {} }, }); });