Skip to content

Commit fb1db6f

Browse files
EskiMojo14markerikson
authored andcommitted
write create callback codemod
1 parent 32a7dcf commit fb1db6f

File tree

5 files changed

+245
-0
lines changed

5 files changed

+245
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# createSliceReducerBuilder
2+
3+
4+
## Usage
5+
6+
```
7+
npx @reduxjs/rtk-codemods createSliceReducerBuilder path/of/files/ or/some**/*glob.js
8+
9+
# or
10+
11+
yarn global add @reduxjs/rtk-codemods
12+
@reduxjs/rtk-codemods createSliceReducerBuilder path/of/files/ or/some**/*glob.js
13+
```
14+
15+
## Local Usage
16+
```
17+
node ./bin/cli.js createSliceReducerBuilder path/of/files/ or/some**/*glob.js
18+
```
19+
20+
## Input / Output
21+
22+
<!--FIXTURES_TOC_START-->
23+
<!--FIXTURES_TOC_END-->
24+
25+
<!--FIXTURES_CONTENT_START-->
26+
<!--FIXTURES_CONTENT_END-->
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const aSlice = createSlice({
2+
name: 'name',
3+
initialState: todoAdapter.getInitialState(),
4+
reducers: {
5+
property: () => {},
6+
method(state, action) {
7+
todoAdapter.setMany(state, action);
8+
},
9+
identifier: todoAdapter.removeOne,
10+
preparedProperty: {
11+
prepare: (todo) => ({ payload: { id: nanoid(), ...todo } }),
12+
reducer: () => {}
13+
},
14+
preparedMethod: {
15+
prepare(todo) {
16+
return { payload: { id: nanoid(), ...todo } }
17+
},
18+
reducer(state, action) {
19+
todoAdapter.setMany(state, action);
20+
}
21+
},
22+
preparedIdentifier: {
23+
prepare: withPayload(),
24+
reducer: todoAdapter.setMany
25+
},
26+
}
27+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const aSlice = createSlice({
2+
name: 'name',
3+
initialState: todoAdapter.getInitialState(),
4+
reducers: (create) => ({
5+
property: create.reducer(() => {}),
6+
method: create.reducer((state, action) => {
7+
todoAdapter.setMany(state, action);
8+
}),
9+
identifier: create.reducer(todoAdapter.removeOne),
10+
preparedProperty: create.preparedReducer((todo) => ({ payload: { id: nanoid(), ...todo } }), () => {}),
11+
preparedMethod: create.preparedReducer((todo) => {
12+
return { payload: { id: nanoid(), ...todo } }
13+
}, (state, action) => {
14+
todoAdapter.setMany(state, action);
15+
}),
16+
preparedIdentifier: create.preparedReducer(withPayload(), todoAdapter.setMany)
17+
})
18+
})
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/* eslint-disable node/no-extraneous-import */
2+
/* eslint-disable node/no-unsupported-features/es-syntax */
3+
import type { ExpressionKind, SpreadElementKind } from 'ast-types/gen/kinds';
4+
import type { JSCodeshift, ObjectExpression, ObjectProperty, Transform } from 'jscodeshift';
5+
6+
function creatorCall(
7+
j: JSCodeshift,
8+
type: 'reducer' | 'preparedReducer',
9+
argumentsParam: Array<ExpressionKind | SpreadElementKind>
10+
) {
11+
return j.callExpression(
12+
j.memberExpression(j.identifier('create'), j.identifier(type)),
13+
argumentsParam
14+
);
15+
}
16+
17+
export function reducerPropsToBuilderExpression(j: JSCodeshift, defNode: ObjectExpression) {
18+
const returnedObject = j.objectExpression([]);
19+
for (let property of defNode.properties) {
20+
let finalProp: ObjectProperty | undefined;
21+
switch (property.type) {
22+
case 'ObjectMethod': {
23+
const { key, params, body } = property;
24+
finalProp = j.objectProperty(
25+
key,
26+
creatorCall(j, 'reducer', [j.arrowFunctionExpression(params, body)])
27+
);
28+
break;
29+
}
30+
case 'ObjectProperty': {
31+
const { key } = property;
32+
33+
switch (property.value.type) {
34+
case 'ObjectExpression': {
35+
let preparedReducerParams: { prepare?: ExpressionKind; reducer?: ExpressionKind } = {};
36+
37+
for (const objProp of property.value.properties) {
38+
switch (objProp.type) {
39+
case 'ObjectMethod': {
40+
const { key, params, body } = objProp;
41+
if (
42+
key.type === 'Identifier' &&
43+
(key.name === 'reducer' || key.name === 'prepare')
44+
) {
45+
preparedReducerParams[key.name] = j.arrowFunctionExpression(params, body);
46+
}
47+
break;
48+
}
49+
case 'ObjectProperty': {
50+
const { key, value } = objProp;
51+
52+
let finalExpression: ExpressionKind | undefined = undefined;
53+
54+
switch (value.type) {
55+
case 'ArrowFunctionExpression':
56+
case 'FunctionExpression':
57+
case 'Identifier':
58+
case 'MemberExpression':
59+
case 'CallExpression': {
60+
finalExpression = value;
61+
}
62+
}
63+
64+
if (
65+
key.type === 'Identifier' &&
66+
(key.name === 'reducer' || key.name === 'prepare') &&
67+
finalExpression
68+
) {
69+
preparedReducerParams[key.name] = finalExpression;
70+
}
71+
break;
72+
}
73+
}
74+
}
75+
76+
if (preparedReducerParams.prepare && preparedReducerParams.reducer) {
77+
finalProp = j.objectProperty(
78+
key,
79+
creatorCall(j, 'preparedReducer', [
80+
preparedReducerParams.prepare,
81+
preparedReducerParams.reducer,
82+
])
83+
);
84+
} else if (preparedReducerParams.reducer) {
85+
finalProp = j.objectProperty(
86+
key,
87+
creatorCall(j, 'reducer', [preparedReducerParams.reducer])
88+
);
89+
}
90+
break;
91+
}
92+
case 'ArrowFunctionExpression':
93+
case 'FunctionExpression':
94+
case 'Identifier':
95+
case 'MemberExpression':
96+
case 'CallExpression': {
97+
const { value } = property;
98+
finalProp = j.objectProperty(key, creatorCall(j, 'reducer', [value]));
99+
break;
100+
}
101+
}
102+
break;
103+
}
104+
}
105+
if (!finalProp) {
106+
continue;
107+
}
108+
returnedObject.properties.push(finalProp);
109+
}
110+
111+
return j.arrowFunctionExpression([j.identifier('create')], returnedObject, true);
112+
}
113+
114+
const transform: Transform = (file, api) => {
115+
const j = api.jscodeshift;
116+
117+
return (
118+
j(file.source)
119+
// @ts-ignore some expression mismatch
120+
.find(j.CallExpression, {
121+
callee: { name: 'createSlice' },
122+
// @ts-ignore some expression mismatch
123+
arguments: { 0: { type: 'ObjectExpression' } },
124+
})
125+
126+
.filter((path) => {
127+
const createSliceArgsObject = path.node.arguments[0] as ObjectExpression;
128+
return createSliceArgsObject.properties.some(
129+
(p) =>
130+
p.type === 'ObjectProperty' &&
131+
p.key.type === 'Identifier' &&
132+
p.key.name === 'reducers' &&
133+
p.value.type === 'ObjectExpression'
134+
);
135+
})
136+
.forEach((path) => {
137+
const createSliceArgsObject = path.node.arguments[0] as ObjectExpression;
138+
j(path).replaceWith(
139+
j.callExpression(j.identifier('createSlice'), [
140+
j.objectExpression(
141+
createSliceArgsObject.properties.map((p) => {
142+
if (
143+
p.type === 'ObjectProperty' &&
144+
p.key.type === 'Identifier' &&
145+
p.key.name === 'reducers' &&
146+
p.value.type === 'ObjectExpression'
147+
) {
148+
const expressionStatement = reducerPropsToBuilderExpression(j, p.value);
149+
return j.objectProperty(p.key, expressionStatement);
150+
}
151+
return p;
152+
})
153+
),
154+
])
155+
);
156+
})
157+
.toSource({
158+
arrowParensAlways: true,
159+
})
160+
);
161+
};
162+
163+
export const parser = 'tsx';
164+
165+
export default transform;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
const { runTransformTest } = require('codemod-cli');
4+
5+
runTransformTest({
6+
name: 'createSliceReducerBuilder',
7+
path: require.resolve('./index.ts'),
8+
fixtureDir: `${__dirname}/__testfixtures__/`,
9+
});

0 commit comments

Comments
 (0)