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
2 changes: 1 addition & 1 deletion codemods/react/19/replace-default-props/.codemodrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://codemod-utils.s3.us-west-1.amazonaws.com/configuration_schema.json",
"name": "react/19/replace-default-props",
"version": "1.0.3",
"version": "1.0.5",
"engine": "jscodeshift",
"private": false,
"arguments": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ const Button = ({ size, color }) => {
Button.defaultProps = {
size: "16px",
color: "blue",
};
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
const Button = ({ size = "16px", color = "blue" }) => {
return <button style={{ color, fontSize: size }}>Click me</button>;
};
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const Card = ({ user: { name, age } = {
const cardDefaultPropUser = {
name: "Unknown",
age: 0,
} }) => {
};

const Card = ({ user: { name, age } = cardDefaultPropUser }) => {
return (
<div>
<p>{name}</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const C = (props) => {
console.log(props.helloWorld);
return <>{props.text}</>;
};

C.defaultProps = {
text: "test",
text: "Hello",
test: 2,
helloWorld: true,
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
const C = (props) => {
props = {
...props,
text: typeof props.text === "undefined" ? "Hello" : props.text,
test: typeof props.test === "undefined" ? 2 : props.test,
helloWorld: typeof props.helloWorld === "undefined" ? true : props.helloWorld
};

console.log(props.helloWorld);
return <>{props.text}</>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const MyComp = ({foo, ...props}) => {
console.log(props.bar)
}

MyComp.defaultProps = { foo: "hello", bar: "bye", test: 2 };
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const MyComp = ({foo = "hello", ...props}) => {
props = {
...props,
bar: typeof props.bar === "undefined" ? "bye" : props.bar,
test: typeof props.test === "undefined" ? 2 : props.test
};

console.log(props.bar)
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const List = ({ items = [], renderItem = (item) => <li key={item}>{item}</li> }) => {
const listDefaultPropItems = [];
const listDefaultPropRenderItem = (item) => <li key={item}>{item}</li>;
const List = ({ items = listDefaultPropItems, renderItem = listDefaultPropRenderItem }) => {
return <ul>{items.map(renderItem)}</ul>;
};
111 changes: 102 additions & 9 deletions codemods/react/19/replace-default-props/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ import type {
MemberExpression,
ObjectProperty,
Property,
VariableDeclaration,
RestElement,
} from "jscodeshift";

import { getFunctionName } from "@codemod.com/codemod-utils/dist/jscodeshift/function.js";
import { getFunctionComponents } from "@codemod.com/codemod-utils/dist/jscodeshift/react.js";
import {
getFunctionName,
getFunctionComponents,
} from "@codemod.com/codemod-utils";

const getComponentStaticPropValue = (
j: JSCodeshift,
root: Collection<any>,
componentName: string,
name: string,
name: string
): ASTPath<MemberExpression> | null => {
return (
root
Expand All @@ -38,9 +42,8 @@ const getComponentStaticPropValue = (
const buildPropertyWithDefaultValue = (
j: JSCodeshift,
property: ObjectProperty | Property,
defaultValue: any,
defaultValue: any
) => {
// Special handling for nested destructuring patterns
if (property.value.type === "ObjectPattern") {
return j.assignmentPattern(property.value, defaultValue);
}
Expand All @@ -55,7 +58,7 @@ const buildPropertyWithDefaultValue = (

export default function transform(
file: FileInfo,
api: API,
api: API
): string | undefined {
const j = api.jscodeshift;
const root = j(file.source);
Expand All @@ -68,11 +71,21 @@ export default function transform(
return;
}

const componentFunction = j.functionDeclaration(
j.identifier(componentName),
path.value.params,
path.value.body
);

if (componentFunction === null) {
return;
}

const defaultProps = getComponentStaticPropValue(
j,
root,
componentName,
"defaultProps",
"defaultProps"
);

const defaultPropsRight = defaultProps?.parent?.value?.right ?? null;
Expand All @@ -82,17 +95,46 @@ export default function transform(
}

const defaultPropsMap = new Map();
const defaultPropsConstants: VariableDeclaration[] = [];

defaultPropsRight.properties?.forEach((property) => {
if (
(j.Property.check(property) || j.ObjectProperty.check(property)) &&
j.Identifier.check(property.key)
) {
defaultPropsMap.set(property.key.name, property.value);
if (
property.value.type === "ObjectExpression" ||
property.value.type === "ArrayExpression" ||
property.value.type === "ArrowFunctionExpression"
) {
const constName = `${componentName[0]?.toLowerCase()}${componentName.slice(
1
)}DefaultProp${
property.key.name[0]?.toUpperCase() + property.key.name.slice(1)
}`;
const constNamePath = root
.find(j.Identifier, {
name: constName,
})
.paths();
if (constNamePath.length) {
return defaultPropsMap.set(property.key.name, property.value);
}
defaultPropsConstants.push(
j.variableDeclaration("const", [
j.variableDeclarator(j.identifier(constName), property.value),
])
);
defaultPropsMap.set(property.key.name, j.identifier(constName));
} else {
defaultPropsMap.set(property.key.name, property.value);
}
}
});

const propsArg = path.value.params.at(0);
let inlineDefaultProps: { key: string; value: any }[] = [];
let propsArgName: string | undefined;

if (j.ObjectPattern.check(propsArg)) {
propsArg.properties.forEach((property) => {
Expand All @@ -105,11 +147,62 @@ export default function transform(
property.value = buildPropertyWithDefaultValue(
j,
property,
defaultPropsMap.get(property.key.name),
defaultPropsMap.get(property.key.name)
);
defaultPropsMap.delete(property.key.name);
}
} else if (j.RestElement.check(property)) {
const restElement = property as RestElement;
if (j.Identifier.check(restElement.argument)) {
propsArgName = restElement.argument.name;
inlineDefaultProps = Array.from(defaultPropsMap.entries()).map(
([key, value]) => ({ key, value })
);
}
}
});
} else if (j.Identifier.check(propsArg)) {
propsArgName = propsArg.name;
inlineDefaultProps = Array.from(defaultPropsMap.entries()).map(
([key, value]) => ({ key, value })
);
}

if (propsArgName && inlineDefaultProps.length) {
componentFunction.body.body.unshift(
j.expressionStatement(
j.assignmentExpression(
"=",
j.identifier(propsArgName),
j.objectExpression([
j.spreadElement(j.identifier(propsArgName)),
...inlineDefaultProps.map(({ key, value }) =>
j.objectProperty(
j.identifier(key),
j.conditionalExpression(
j.binaryExpression(
"===",
j.unaryExpression(
"typeof",
j.identifier(`${propsArgName}.${key}`)
),
j.literal("undefined")
),
value,
j.identifier(`${propsArgName}.${key}`)
)
)
),
])
)
)
);
}

if (defaultPropsConstants.length && path.parent) {
for (let defaultPropsConstant of defaultPropsConstants) {
path.parent.parent.insertBefore(defaultPropsConstant);
}
}

j(defaultProps).closest(j.ExpressionStatement).remove();
Expand Down
Loading