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.
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.
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 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
iconprop toButtoncomponent instead of separate Icon wrapper - Input:
<Button><BellIcon />Click me</Button>
- Output:
<Button icon={<BellIcon />}>Click me</Button>
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/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
- v5:
Usage:
npx @patternfly/class-name-updater --v6 src/styles/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
- v5:
Usage:
npx @patternfly/css-vars-updater src/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
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)
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 referenceddedupe-import-specifiers: Merge duplicate imports
Beta Rules (opt-in only):
- Require explicit
--onlyflag - 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)
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
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);
}
});
}
}
}
};
}
};Located in packages/eslint-plugin-pf-codemods/src/rules/helpers/:
Import tracking:
getFromPackage(): Get all imports from a packagegetDefaultImportsFromPackage(): Get default importsgetSpecifiersFromImport(): Extract named imports
JSX manipulation:
getAttribute(): Get JSX attribute by namegetAttributeValue(): Extract attribute value (string/expression)getChildJSXElementByName(): Find child componentgetExpression(): Extract expression from JSXExpressionContainermakeJSXElementSelfClosing(): Convert<Foo></Foo>β<Foo />
Component detection:
isReactIcon(): Check if import is from react-iconscheckMatchingJSXOpeningClosingElement(): Verify balanced tagsgetNodeForAttribute(): Find attribute node for fixing
Common transformations:
renameProps(): Batch rename multiple propsremoveComponentProps(): Delete specific propsrenameComponent(): Change component name
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
# 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 packagesruleTester.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"
}]
}
]
});With 289 rules across 3 versions, manual coding would be unmaintainable. The generator creates boilerplate, ensuring consistency.
yarn generateInteractive Prompts:
- Select component (e.g., "Button")
- Choose template type:
- Generic: Standard prop transformation
- Add event parameter: Add
eventto callback - Swap parameter: Reorder callback params
- 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)
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) => ...} />| 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 |
- Import verification: Only transforms components actually imported from PatternFly
- Format preservation: Minimal whitespace/formatting changes
- Dry-run default: See changes before applying (
--fixrequired to modify)
- Parses code into Abstract Syntax Tree (not regex)
- Understands code structure (JSX, expressions, etc.)
- Prevents false positives (won't change
<Button>in comments)
- Setup: Handle big structural changes first
- Main: Apply most transformations
- Cleanup: Remove artifacts (unused imports)
--only: Run specific rules (allowlist)--exclude: Skip specific rules (blocklist)- Beta rules: Opt-in only (not in default set)
- No flags? Prompts for version selection
- Special cases (e.g., animations) trigger follow-up questions
- Targets JSX syntax only
- Doesn't handle
React.createElement()calls - Reason: PatternFly users primarily use JSX
# 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# Only migrate Button and Alert components
npx @patternfly/pf-codemods --v6 --only button-moveIcons-icon-prop,alert-remove-props --fix src/# Enable animations (opt-in feature in v6)
npx @patternfly/pf-codemods --v6 --only enable-animations --fix src/# 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- Go to
packages/eslint-plugin-pf-codemods/src/rules/v6/<rule-name>/ - Read
<rule-name>.mdfor documentation - Check
<rule-name>Input.tsxand<rule-name>Output.tsxfor before/after examples - Review
<rule-name>.tsfor implementation details
- Run
yarn generate - Follow the interactive prompts
- Edit generated files in
src/rules/v6/<new-rule>/ - Run
yarn test:v6to verify
- Add your test case to
test/test.tsx - Run
yarn test:v6:single - Check console output for errors
- Add
console.log()in rule implementation if needed
- Start at
packages/pf-codemods/index.js(CLI entry point) - Follow to
packages/eslint-plugin-pf-codemods/src/ruleCuration.ts(rule loading) - Individual rules in
packages/eslint-plugin-pf-codemods/src/rules/v6/*/
// Old prop β New prop
renameProps(node.openingElement.attributes, { isExpanded: "isOpen" });// Remove deprecated prop
removeComponentProps(node, ["noBoxShadow"]);// Wizard β WizardNext
renameComponent(context, node, "WizardNext");// <Button><Icon /></Button> β <Button icon={<Icon />} />
const iconChild = getChildJSXElementByName(node, "Icon");
setAttribute(node, "icon", `{${iconChild}}`);// Add tracking attribute for later processing
addAttribute(node, "data-codemods", "true");- Always dry-run first: Review changes before applying
- Commit before running: Use version control to review diffs
- Run tests after: Codemods handle 90%, but manual fixes may be needed
- Read warnings: Warning-level rules need manual verification
- Use
--onlyfor gradual migration: Don't need to migrate everything at once
- Use the generator: Don't hand-code from scratch
- Test with real code: Add Input/Output fixtures from actual codebases
- Follow helper patterns: Reuse existing helpers instead of reinventing
- Document edge cases: Add comments for tricky transformations
- Consider warning vs error: If manual verification needed, use warning severity
- 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.