Skip to content

Instantly share code, notes, and snippets.

@jwmatthews
Created January 19, 2026 18:07
Show Gist options
  • Select an option

  • Save jwmatthews/502562803c3d9cd39f35f09241b3207a to your computer and use it in GitHub Desktop.

Select an option

Save jwmatthews/502562803c3d9cd39f35f09241b3207a to your computer and use it in GitHub Desktop.

PatternFly Codemods - Complete Understanding Guide

πŸ“‹ Executive Summary

pf-codemods is an automated code transformation system (codemod suite) that helps developers migrate their applications between major versions of PatternFly's React component library (v3β†’v4, v4β†’v5, v5β†’v6).

Think of it as: An intelligent "find and replace on steroids" that understands React/JSX code structure and safely updates component props, imports, class names, and CSS variables when PatternFly releases breaking changes.


🎯 What Problem Does This Solve?

When PatternFly releases a major version (e.g., v5 β†’ v6), components often have:

  • Renamed or removed props
  • Changed component APIs
  • Updated CSS class names
  • New CSS variable naming conventions
  • Deprecated components replaced with new ones

Manually updating hundreds/thousands of component instances across a large codebase would be:

  • Time-consuming (weeks of work)
  • Error-prone (easy to miss instances)
  • Tedious (repetitive find-and-replace)

pf-codemods automates 90%+ of this work by parsing your code, understanding its structure, and applying intelligent transformations.


πŸ—οΈ Architecture Overview

Monorepo Structure

pf-codemods/ (root)
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ eslint-plugin-pf-codemods/    [Core rule engine]
β”‚   β”œβ”€β”€ pf-codemods/                  [CLI wrapper]
β”‚   β”œβ”€β”€ class-name-updater/           [CSS class updater]
β”‚   β”œβ”€β”€ css-vars-updater/             [CSS variable updater]
β”‚   └── shared-codemod-helpers/       [Shared utilities]
β”œβ”€β”€ generators/                       [Rule generation templates]
└── getPackages.js                   [Test helper]

Technology Stack:

  • ESLint: Core transformation engine (uses ESLint's rule system)
  • TypeScript: Source code language
  • Lerna: Monorepo management
  • Yarn Workspaces: Package linking
  • Mocha: Test runner
  • Plop: Code generation

πŸ“¦ The Five Published Packages

1. @patternfly/eslint-plugin-pf-codemods

The Brain 🧠

  • Purpose: Contains all transformation rules (289 total rules)
  • Version Coverage: v4 (45 rules), v5 (138 rules), v6 (106 rules)
  • Technology: ESLint plugin with custom rules
  • How it works:
    • Parses JSX code into AST (Abstract Syntax Tree)
    • Applies pattern-matching rules to find PatternFly components
    • Transforms code using ESLint's fixer API
    • Outputs modified code

Example Rule: button-moveIcons-icon-prop

  • What it does: Moves icon prop to Button component instead of separate Icon wrapper
  • Input:
    <Button><BellIcon />Click me</Button>
  • Output:
    <Button icon={<BellIcon />}>Click me</Button>

2. @patternfly/pf-codemods

The Interface πŸ–₯️

  • Purpose: CLI that users interact with
  • Executable: npx @patternfly/pf-codemods [options] <path>
  • Key Features:
    • Interactive version selection (v4/v5/v6)
    • Rule filtering (--only, --exclude)
    • Dry-run mode (see changes without applying)
    • Apply fixes automatically (--fix)

Usage Example:

# Migrate to v6 and apply fixes
npx @patternfly/pf-codemods --v6 --fix src/

# Dry run to see what would change
npx @patternfly/pf-codemods --v6 src/

# Run only specific rules
npx @patternfly/pf-codemods --v6 --only button-moveIcons-icon-prop,alert-remove-props src/

3. @patternfly/class-name-updater

The Stylist πŸ’…

  • Purpose: Updates CSS class names with version prefixes
  • Why needed: PatternFly v6 changed class naming convention
  • Example:
    • v5: .pf-c-button
    • v6: .pf-v6-c-button

Usage:

npx @patternfly/class-name-updater --v6 src/styles/

4. @patternfly/css-vars-updater

The Designer 🎨

  • Purpose: Updates CSS variable references for v5β†’v6 migration
  • Why needed: v6 redesigned the design token system
  • Example:
    • v5: --pf-global-color-100
    • v6: --pf-t--global--color--100

Usage:

npx @patternfly/css-vars-updater src/

5. @patternfly/shared-codemod-helpers

The Toolbox 🧰

  • Purpose: Shared utilities used by all other packages
  • Contains:
    • Token mapping lists (old β†’ new names)
    • Diff printing utilities
    • File operation helpers
    • Common validation functions

πŸ”„ How the Codemod System Works

Execution Flow

1. User runs CLI
   ↓
2. Version selection (--v4, --v5, or --v6)
   ↓
3. Load appropriate rule set
   ↓
4. Three-phase execution:

   Phase 1: SETUP (16 rules)
   - Handle deprecated components
   - Update import paths
   - Pre-process special cases

   Phase 2: MAIN (majority of rules)
   - Transform component props
   - Rename components
   - Update JSX structure

   Phase 3: CLEANUP (3 rules)
   - Remove unused imports
   - Deduplicate import specifiers
   - Final cleanup
   ↓
5. Merge results from all phases
   ↓
6. Output report (or apply fixes with --fix)

Rule Categories

Setup Rules (run first):

  • accordion-deprecated, alert-deprecated, button-deprecated
  • Handle components being replaced entirely
  • Example: Wizard (deprecated) β†’ WizardNext

Main Rules (core transformations):

  • Prop renames: isExpanded β†’ isOpen
  • Prop removals: Remove noBoxShadow (no longer supported)
  • Component renames: ToolbarGroup β†’ ToolbarContent
  • Structural changes: Move children to specific props

Cleanup Rules (run last):

  • remove-unused-imports: Delete imports no longer referenced
  • dedupe-import-specifiers: Merge duplicate imports

Beta Rules (opt-in only):

  • Require explicit --only flag
  • Example: enable-animations (opt-in feature migration)

Warning Rules (60+ rules):

  • Default to "warn" severity instead of "error"
  • Require manual verification
  • Example: accordion-warn-update-markup (structural changes that need review)

πŸ› οΈ Rule Implementation Deep Dive

Anatomy of a V6 Rule

Every v6 rule lives in its own directory with 5 files:

button-moveIcons-icon-prop/
β”œβ”€β”€ button-moveIcons-icon-prop.ts        # Rule logic (TypeScript)
β”œβ”€β”€ button-moveIcons-icon-prop.test.ts   # Unit tests (Mocha)
β”œβ”€β”€ button-moveIcons-icon-prop.md        # Documentation
β”œβ”€β”€ buttonMoveIconsIconPropInput.tsx     # Test fixture: before
└── buttonMoveIconsIconPropOutput.tsx    # Test fixture: after

Rule Structure (TypeScript)

module.exports = {
  meta: {
    fixable: "code",  // This rule can auto-fix
    messages: {
      moveIcons: "Icon components should be moved to the icon prop..."
    }
  },
  create: function (context: Rule.RuleContext) {
    // Get imports to verify this is a PatternFly component
    const { imports } = getFromPackage(context, "@patternfly/react-core");

    return {
      // Visitor pattern: when we encounter a JSX element...
      JSXElement(node: JSXElement) {
        // Check if this is a Button component
        if (imports.Button && node.openingElement.name === "Button") {
          // Check for icon children
          const iconChild = getChildJSXElementByName(node, "Icon");

          if (iconChild) {
            // Report the issue
            context.report({
              node,
              messageId: "moveIcons",
              fix(fixer) {
                // Generate the fix
                return fixer.replaceText(node, transformedCode);
              }
            });
          }
        }
      }
    };
  }
};

40+ Helper Functions Available

Located in packages/eslint-plugin-pf-codemods/src/rules/helpers/:

Import tracking:

  • getFromPackage(): Get all imports from a package
  • getDefaultImportsFromPackage(): Get default imports
  • getSpecifiersFromImport(): Extract named imports

JSX manipulation:

  • getAttribute(): Get JSX attribute by name
  • getAttributeValue(): Extract attribute value (string/expression)
  • getChildJSXElementByName(): Find child component
  • getExpression(): Extract expression from JSXExpressionContainer
  • makeJSXElementSelfClosing(): Convert <Foo></Foo> β†’ <Foo />

Component detection:

  • isReactIcon(): Check if import is from react-icons
  • checkMatchingJSXOpeningClosingElement(): Verify balanced tags
  • getNodeForAttribute(): Find attribute node for fixing

Common transformations:

  • renameProps(): Batch rename multiple props
  • removeComponentProps(): Delete specific props
  • renameComponent(): Change component name

πŸ§ͺ Testing System

Test Structure by Version

V4 & V5: Flat test files

test/rules/v4/accordion-remove-noBoxShadow.js
test/rules/v5/button-rename-isActive.js

V6: Organized directories with fixtures

src/rules/v6/buttonMoveIconsIconProp/
β”œβ”€β”€ button-moveIcons-icon-prop.test.ts
β”œβ”€β”€ buttonMoveIconsIconPropInput.tsx    # Real-world example
└── buttonMoveIconsIconPropOutput.tsx   # Expected result

Test Commands

# Run all tests
yarn test

# Test specific versions
yarn test:v4      # 45 v4 rules
yarn test:v5      # 138 v5 rules
yarn test:v6      # 106 v6 rules (requires build)

# Single file testing (development)
yarn test:v5:single         # Test on test/test.tsx
yarn test:v6:single         # Test on v6 Input fixtures

# Real codebase testing
yarn test:koku              # Run on koku-ui project
yarn test:console           # Run on console frontend
yarn test:packages          # Run on downloaded packages

Test Pattern (ESLint RuleTester)

ruleTester.run("button-moveIcons-icon-prop", rule, {
  valid: [
    {
      // Code that should NOT trigger the rule
      code: `<Button icon={<BellIcon />}>Click</Button>`
    }
  ],
  invalid: [
    {
      // Code that SHOULD trigger the rule
      code: `<Button><BellIcon />Click</Button>`,
      output: `<Button icon={<BellIcon />}>Click</Button>`,
      errors: [{
        message: "Icon components should be moved to the icon prop",
        type: "JSXElement"
      }]
    }
  ]
});

βš™οΈ Rule Generation System

Why Generate Rules?

With 289 rules across 3 versions, manual coding would be unmaintainable. The generator creates boilerplate, ensuring consistency.

Plop-Based Generation

yarn generate

Interactive Prompts:

  1. Select component (e.g., "Button")
  2. Choose template type:
    • Generic: Standard prop transformation
    • Add event parameter: Add event to callback
    • Swap parameter: Reorder callback params
  3. Provide rule details (prop names, descriptions)

Generated Output (5 files):

  • TypeScript rule file
  • Mocha test file
  • Markdown documentation
  • Input test fixture (TSX)
  • Output test fixture (TSX)

Template Types

1. Generic Template

  • Prop rename: oldName β†’ newName
  • Prop removal: Delete deprecatedProp
  • Component rename: OldComp β†’ NewComp

2. Add Event Parameter Template

// Before
<Button onClick={() => console.log('clicked')} />

// After (add event parameter)
<Button onClick={(event) => console.log('clicked')} />

3. Swap Parameter Template

// Before
<Select onChange={(value, event) => ...} />

// After (swap order)
<Select onChange={(event, value) => ...} />

πŸ“Š Statistics & Scale

Metric Count
Total Rules 289
V4 Rules 45
V5 Rules 138
V6 Rules 106
Helper Functions 40+
Beta Rules 3
Warning Rules 60+
Setup Rules 16
Cleanup Rules 3
Published Packages 5

πŸŽ“ Key Concepts & Design Principles

1. Safety First

  • Import verification: Only transforms components actually imported from PatternFly
  • Format preservation: Minimal whitespace/formatting changes
  • Dry-run default: See changes before applying (--fix required to modify)

2. AST-Based Transformation

  • Parses code into Abstract Syntax Tree (not regex)
  • Understands code structure (JSX, expressions, etc.)
  • Prevents false positives (won't change <Button> in comments)

3. Multi-Phase Execution

  • Setup: Handle big structural changes first
  • Main: Apply most transformations
  • Cleanup: Remove artifacts (unused imports)

4. Flexible Control

  • --only: Run specific rules (allowlist)
  • --exclude: Skip specific rules (blocklist)
  • Beta rules: Opt-in only (not in default set)

5. Interactive CLI

  • No flags? Prompts for version selection
  • Special cases (e.g., animations) trigger follow-up questions

6. JSX-Focused

  • Targets JSX syntax only
  • Doesn't handle React.createElement() calls
  • Reason: PatternFly users primarily use JSX

πŸš€ Real-World Usage Examples

Scenario 1: Full Codebase Migration (v5 β†’ v6)

# Step 1: See what would change (dry run)
npx @patternfly/pf-codemods --v6 src/

# Step 2: Review the output, then apply
npx @patternfly/pf-codemods --v6 --fix src/

# Step 3: Update CSS class names
npx @patternfly/class-name-updater --v6 src/

# Step 4: Update CSS variables
npx @patternfly/css-vars-updater src/

# Step 5: Run your tests, fix manual issues
npm test

Scenario 2: Targeted Migration (Specific Components)

# Only migrate Button and Alert components
npx @patternfly/pf-codemods --v6 --only button-moveIcons-icon-prop,alert-remove-props --fix src/

Scenario 3: Beta Feature Opt-In

# Enable animations (opt-in feature in v6)
npx @patternfly/pf-codemods --v6 --only enable-animations --fix src/

Scenario 4: CI/CD Integration

# In CI pipeline, fail build if codemods find issues
npx @patternfly/pf-codemods --v6 --format json src/ > codemod-report.json

# Check exit code (non-zero if issues found)
if [ $? -ne 0 ]; then
  echo "PatternFly v6 migration incomplete"
  exit 1
fi

πŸ” How to Navigate the Codebase

Want to understand a specific rule?

  1. Go to packages/eslint-plugin-pf-codemods/src/rules/v6/<rule-name>/
  2. Read <rule-name>.md for documentation
  3. Check <rule-name>Input.tsx and <rule-name>Output.tsx for before/after examples
  4. Review <rule-name>.ts for implementation details

Want to add a new rule?

  1. Run yarn generate
  2. Follow the interactive prompts
  3. Edit generated files in src/rules/v6/<new-rule>/
  4. Run yarn test:v6 to verify

Want to debug a rule?

  1. Add your test case to test/test.tsx
  2. Run yarn test:v6:single
  3. Check console output for errors
  4. Add console.log() in rule implementation if needed

Want to see rule execution flow?

  1. Start at packages/pf-codemods/index.js (CLI entry point)
  2. Follow to packages/eslint-plugin-pf-codemods/src/ruleCuration.ts (rule loading)
  3. Individual rules in packages/eslint-plugin-pf-codemods/src/rules/v6/*/

🎯 Common Patterns in Rules

Pattern 1: Prop Rename

// Old prop β†’ New prop
renameProps(node.openingElement.attributes, { isExpanded: "isOpen" });

Pattern 2: Prop Removal

// Remove deprecated prop
removeComponentProps(node, ["noBoxShadow"]);

Pattern 3: Component Rename

// Wizard β†’ WizardNext
renameComponent(context, node, "WizardNext");

Pattern 4: Move Child to Prop

// <Button><Icon /></Button> β†’ <Button icon={<Icon />} />
const iconChild = getChildJSXElementByName(node, "Icon");
setAttribute(node, "icon", `{${iconChild}}`);

Pattern 5: Add Data Attribute

// Add tracking attribute for later processing
addAttribute(node, "data-codemods", "true");

πŸ’‘ Best Practices & Tips

For Users of pf-codemods:

  1. Always dry-run first: Review changes before applying
  2. Commit before running: Use version control to review diffs
  3. Run tests after: Codemods handle 90%, but manual fixes may be needed
  4. Read warnings: Warning-level rules need manual verification
  5. Use --only for gradual migration: Don't need to migrate everything at once

For Contributors Adding Rules:

  1. Use the generator: Don't hand-code from scratch
  2. Test with real code: Add Input/Output fixtures from actual codebases
  3. Follow helper patterns: Reuse existing helpers instead of reinventing
  4. Document edge cases: Add comments for tricky transformations
  5. Consider warning vs error: If manual verification needed, use warning severity

πŸ”— Quick Reference Links

  • Main CLI: packages/pf-codemods/index.js
  • Rule Engine: packages/eslint-plugin-pf-codemods/src/index.ts
  • Rule Curation: packages/eslint-plugin-pf-codemods/src/ruleCuration.ts
  • Helpers: packages/eslint-plugin-pf-codemods/src/rules/helpers/
  • V6 Rules: packages/eslint-plugin-pf-codemods/src/rules/v6/*/
  • Generators: generators/src/
  • Shared Utils: packages/shared-codemod-helpers/src/

This codebase represents a sophisticated, battle-tested system for managing breaking changes across major versions of a component library, used by Red Hat and the broader PatternFly community to migrate thousands of files across dozens of projects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment