diff --git a/community/react/remove-default-props/README.md b/community/react/remove-default-props/README.md
index 8112fbfa4..1147237f2 100644
--- a/community/react/remove-default-props/README.md
+++ b/community/react/remove-default-props/README.md
@@ -6,13 +6,26 @@ _Credit_: [https://github.com/reactjs/react-codemod](https://github.com/reactjs/
```jsx
/* INPUT */
-import React from 'react'
+import React from 'react';
-export const Greet = ({ name }) => Hi {name}
-Greet.defaultProps = { name: 'Stranger' }
+export const Greet = ({ name }) => Hi {name};
+Greet.defaultProps = { text: 'Stranger' };
/* OUTPUT */
-import React from 'react'
+import React from 'react';
-export const Greet = ({ name }) => Hi {name}
+export const Greet = ({ name, text = 'Stranger' }) => Hi {name};
```
+
+```jsx
+/* INPUT */
+import React from 'react';
+
+export const Greet = (props) => Hi {name};
+Greet.defaultProps = { text: 'Stranger' };
+
+/* OUTPUT */
+import React from 'react';
+
+export const Greet = ({ ...props, text = 'Stranger' }) => Hi {name};
+```
\ No newline at end of file
diff --git a/community/react/remove-default-props/motions/getNewParams.ts b/community/react/remove-default-props/motions/getNewParams.ts
new file mode 100644
index 000000000..033deb332
--- /dev/null
+++ b/community/react/remove-default-props/motions/getNewParams.ts
@@ -0,0 +1,73 @@
+import {
+ JSCodeshift,
+ FunctionDeclaration,
+ ASTPath,
+ ArrowFunctionExpression,
+} from 'jscodeshift';
+
+export function getNewParams(
+ j: JSCodeshift,
+ component: ASTPath,
+ defaultParams: any,
+) {
+ const params = component.node.params;
+
+ const existingSingleProp = params.find(param => param.type === 'Identifier');
+
+ const destructuredProps = params.find(
+ param => param.type === 'ObjectPattern',
+ );
+
+ const noExistingProps = params.length === 0;
+
+ let newParams: FunctionDeclaration['params'] = [];
+
+ if (noExistingProps) {
+ newParams = [j.objectPattern(defaultParams)];
+ } else if (existingSingleProp) {
+ newParams = [
+ j.objectPattern([
+ // @ts-ignore
+ j.spreadProperty(existingSingleProp),
+ ...defaultParams,
+ ]),
+ ];
+ } else {
+ newParams = getNewDestructuredParams(destructuredProps, j, defaultParams);
+ }
+ return newParams;
+}
+function getNewDestructuredParams(
+ existingPropsParam: FunctionDeclaration['params'][number] | undefined,
+ j: JSCodeshift,
+ defaultParams: any,
+) {
+ if (existingPropsParam && 'properties' in existingPropsParam) {
+ const restProp = existingPropsParam.properties.find(
+ // @ts-expect-error for some reason it does not exist
+ prop => prop.type === 'RestElement',
+ );
+
+ const existingPropsDestructuredProps = existingPropsParam.properties
+ .filter(prop => prop.type !== restProp?.type)
+ .map(prop => j.property('init', (prop as any).key, (prop as any).value))
+ .filter(Boolean);
+
+ const restPropArg =
+ restProp && 'argument' in restProp
+ ? restProp.argument
+ : j.identifier('rest');
+
+ const newParams = [
+ j.objectPattern([
+ ...existingPropsDestructuredProps,
+ ...defaultParams,
+ // @ts-expect-error RestElement is not assignable as above
+ ...(restProp ? [j.restProperty(restPropArg)] : []),
+ ]),
+ ];
+ return newParams;
+ }
+
+ return [];
+}
diff --git a/community/react/remove-default-props/motions/moveDefaultPropsToArrowFunctionExpression.ts b/community/react/remove-default-props/motions/moveDefaultPropsToArrowFunctionExpression.ts
new file mode 100644
index 000000000..791288ac1
--- /dev/null
+++ b/community/react/remove-default-props/motions/moveDefaultPropsToArrowFunctionExpression.ts
@@ -0,0 +1,43 @@
+import { Collection, JSCodeshift } from 'jscodeshift';
+import { getNewParams } from './getNewParams';
+
+export function moveDefaultPropsToArrowFunctionExpression(
+ j: JSCodeshift,
+ source: Collection,
+) {
+ source.find(j.VariableDeclarator).forEach(component => {
+ const defaultProps = source.find(j.AssignmentExpression, {
+ left: {
+ // @ts-ignore
+ object: { name: component.node.id?.name },
+ property: { name: 'defaultProps' },
+ },
+ });
+
+ const defaultValues = defaultProps.get('right').value.properties;
+
+ if (defaultProps.length === 0) return;
+
+ // Generate a new function parameter for each default prop
+ const defaultParams = defaultValues.map((prop: any) => {
+ const key = prop.key.name;
+ const value = prop.value.value;
+ return j.objectProperty(
+ j.identifier(key),
+ j.assignmentPattern(j.identifier(key), j.literal(value)),
+ );
+ });
+
+ const arrowFunction = component.get('init');
+
+ const newParams = getNewParams(j, arrowFunction, defaultParams);
+
+ j(component).replaceWith(
+ j.variableDeclarator(
+ // @ts-ignore
+ j.identifier(component.node.id.name),
+ j.arrowFunctionExpression(newParams!, j.blockStatement([])),
+ ),
+ );
+ });
+}
diff --git a/community/react/remove-default-props/motions/moveDefaultPropsToFunctionDeclaration.ts b/community/react/remove-default-props/motions/moveDefaultPropsToFunctionDeclaration.ts
new file mode 100644
index 000000000..a6d4bdcb5
--- /dev/null
+++ b/community/react/remove-default-props/motions/moveDefaultPropsToFunctionDeclaration.ts
@@ -0,0 +1,53 @@
+import { JSCodeshift, Collection } from 'jscodeshift';
+import { getNewParams } from './getNewParams';
+
+export function moveDefaultPropsToFunctionDeclaration(
+ j: JSCodeshift,
+ source: Collection,
+) {
+ source
+ .find(j.FunctionDeclaration)
+ .forEach(component => {
+ const defaultProps = source.find(j.AssignmentExpression, {
+ left: {
+ object: { name: component.node.id?.name },
+ property: { name: 'defaultProps' },
+ },
+ });
+
+ if (defaultProps.length === 0) return;
+
+ // Extract the default props object
+ const defaultValues = defaultProps.get('right').value.properties;
+
+ // Generate a new function parameter for each default prop
+ const defaultParams = defaultValues.map((prop: any) => {
+ const key = prop.key.name;
+ const value = prop.value.value;
+ // return j.objectPattern(`${key}=${JSON.stringify(value)}`);
+ return j.objectProperty(
+ j.identifier(key),
+ j.assignmentPattern(j.identifier(key), j.literal(value)),
+ );
+ });
+ // Find the defaultProps assignment expression
+ const newParams = getNewParams(j, component, defaultParams);
+ // Replace the original function declaration with a new one
+ j(component).replaceWith(nodePath =>
+ j.functionDeclaration(
+ nodePath.node.id,
+ newParams!,
+ nodePath.node.body,
+ nodePath.node.generator,
+ nodePath.node.async,
+ ),
+ );
+ })
+ .find(j.AssignmentExpression, {
+ left: {
+ object: { type: 'Identifier' },
+ property: { name: 'defaultProps' },
+ },
+ })
+ .toSource();
+}
diff --git a/community/react/remove-default-props/motions/removeDefaultPropsAssignment.ts b/community/react/remove-default-props/motions/removeDefaultPropsAssignment.ts
new file mode 100644
index 000000000..b95a8f5bb
--- /dev/null
+++ b/community/react/remove-default-props/motions/removeDefaultPropsAssignment.ts
@@ -0,0 +1,18 @@
+import { JSCodeshift } from 'jscodeshift';
+import { Collection } from 'jscodeshift/src/Collection';
+
+export function removeDefaultPropsAssignment(
+ j: JSCodeshift,
+ source: Collection,
+) {
+ const removePath = (path: any) => j(path).remove();
+ const isAssigningDefaultProps = (e: any) =>
+ e.node.left &&
+ e.node.left.property &&
+ e.node.left.property.name === 'defaultProps';
+
+ return source
+ .find(j.AssignmentExpression)
+ .filter(isAssigningDefaultProps)
+ .forEach(removePath);
+}
diff --git a/community/react/remove-default-props/transform.spec.ts b/community/react/remove-default-props/transform.spec.ts
index ef098f3cd..675e8d224 100644
--- a/community/react/remove-default-props/transform.spec.ts
+++ b/community/react/remove-default-props/transform.spec.ts
@@ -2,21 +2,236 @@ import { applyTransform } from '@codeshift/test-utils';
import * as transformer from './transform';
describe('react#remove-default-props transform', () => {
- it('should remove default props', async () => {
- const result = await applyTransform(
- transformer,
- `
-import React from 'react';
-
-export const Greet = ({ name }) => Hi {name};
-Greet.defaultProps = { name: 'Stranger' };
- `,
- { parser: 'tsx' },
- );
-
- expect(result).toMatchInlineSnapshot(`
- import React from 'react'; export const Greet = ({ name }) =>
- Hi {name};
- `);
+ describe('components with function declaration', () => {
+ it('should move default props when no existing props', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export function Greet(){ Hi {name}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ import React from 'react'; export function Greet( { name: name = "Stranger"
+ } ) {
+ Hi {name}; }
+ `);
+ });
+
+ it('when there are other props', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export function Greet({text}){ Hi {name} {text}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ import React from 'react'; export function Greet( { text: text, name:
+ name = "Stranger" } ) {
+ Hi {name} {text}; }
+ `);
+ });
+
+ it('when there are other destructured renamed props', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export function Greet({text:myText}){ Hi {name} {text}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ import React from 'react'; export function Greet( { text: myText, name:
+ name = "Stranger" } ) {
+ Hi {name} {text}; }
+ `);
+ });
+
+ it('preserves default values for destructured components', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export function Greet({text:myText, props='amazingText'}){ Hi {name} {text}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ import React from 'react'; export function Greet( { text: myText, props:
+ props='amazingText', name: name = "Stranger" } ) {
+ Hi {name} {text}; }
+ `);
+ });
+
+ it('when there are rest parameters', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export function Greet({prop1,...someRest}){ Hi {name} {text}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ import React from 'react'; export function Greet( { prop1: prop1, name:
+ name = "Stranger", ...someRest } ) {
+ Hi {name} {text}; }
+ `);
+ });
+
+ it('works with any props parameter passed', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export function Greet(props){ Hi {name} {text}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ import React from 'react'; export function Greet( { ...props, name: name
+ = "Stranger" } ) {
+ Hi {name} {text}; }
+ `);
+ });
+ });
+
+ describe('components with as arrow functions', () => {
+ it('should move default props when no existing props', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export const Greet = () => { Hi {name}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ "import React from 'react';
+ export const Greet = (
+ {
+ name: name = \\"Stranger\\"
+ }
+ ) => {}"
+ `);
+ });
+
+ it('when there are other props', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export const Greet = ({text}) => { Hi {name} {text}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ "import React from 'react';
+ export const Greet = (
+ {
+ text: text,
+ name: name = \\"Stranger\\"
+ }
+ ) => {}"
+ `);
+ });
+
+ it('when there are other destructured renamed props', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export const Greet = ({text:myText}) => { Hi {name} {text}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ "import React from 'react';
+ export const Greet = (
+ {
+ text: myText,
+ name: name = \\"Stranger\\"
+ }
+ ) => {}"
+ `);
+ });
+
+ it('preserves default values for destructured components', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export const Greet = ({text:myText, props='amazingText'}) => { Hi {name} {text}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ "import React from 'react';
+ export const Greet = (
+ {
+ text: myText,
+ props: props='amazingText',
+ name: name = \\"Stranger\\"
+ }
+ ) => {}"
+ `);
+ });
+
+ it('when there are rest parameters', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export const Greet = ({prop1,...someRest}) =>{ Hi {name} {text}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ "import React from 'react';
+ export const Greet = (
+ {
+ prop1: prop1,
+ name: name = \\"Stranger\\",
+ ...someRest
+ }
+ ) => {}"
+ `);
+ });
+
+ it('works with any props parameter passed', async () => {
+ const result = await applyTransform(
+ transformer,
+ `
+ import React from 'react';
+ export const Greet = (props) => { Hi {name} {text}; }
+ Greet.defaultProps = { name: 'Stranger' };
+ `,
+ { parser: 'tsx' },
+ );
+ expect(result).toMatchInlineSnapshot(`
+ "import React from 'react';
+ export const Greet = (
+ {
+ ...props,
+ name: name = \\"Stranger\\"
+ }
+ ) => {}"
+ `);
+ });
});
});
diff --git a/community/react/remove-default-props/transform.ts b/community/react/remove-default-props/transform.ts
index eea4b99d3..7db75d6ca 100644
--- a/community/react/remove-default-props/transform.ts
+++ b/community/react/remove-default-props/transform.ts
@@ -1,19 +1,19 @@
-import { API, FileInfo, Options } from 'jscodeshift';
+import { FileInfo, API } from 'jscodeshift';
+import { applyMotions } from '../../../packages/utils/src';
-export default function transformer(
- file: FileInfo,
- { jscodeshift: j }: API,
- options: Options,
-) {
- const removePath = (path: any) => j(path).remove();
- const isAssigningDefaultProps = (e: any) =>
- e.node.left &&
- e.node.left.property &&
- e.node.left.property.name === 'defaultProps';
+import { moveDefaultPropsToArrowFunctionExpression } from './motions/moveDefaultPropsToArrowFunctionExpression';
+import { moveDefaultPropsToFunctionDeclaration } from './motions/moveDefaultPropsToFunctionDeclaration';
+import { removeDefaultPropsAssignment } from './motions/removeDefaultPropsAssignment';
- return j(file.source)
- .find(j.AssignmentExpression)
- .filter(isAssigningDefaultProps)
- .forEach(removePath)
- .toSource(options.printOptions);
+export default function transformer(file: FileInfo, api: API) {
+ const j = api.jscodeshift;
+ const source = j(file.source);
+
+ applyMotions(j, source, [
+ moveDefaultPropsToFunctionDeclaration,
+ moveDefaultPropsToArrowFunctionExpression,
+ removeDefaultPropsAssignment,
+ ]);
+
+ return source.toSource();
}