Skip to content

Commit bacb536

Browse files
committed
add support for arrow functions
1 parent de8d33b commit bacb536

File tree

1 file changed

+135
-135
lines changed

1 file changed

+135
-135
lines changed

source/index.ts

Lines changed: 135 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -10,146 +10,146 @@ export default function () {
1010
return {
1111
visitor: {
1212
FunctionDeclaration(path: any) {
13-
Object.keys(path.scope.bindings).forEach((binding) => {
14-
if (/^\$/.test(binding)) {
15-
const normName = normalizeName(binding)
16-
// add to list of identifiers to compare and replace
17-
// (not using scope replace to avoid shadow variables being replaced)
18-
toMod.push({
19-
raw: binding,
20-
simplified: normName,
21-
})
22-
}
23-
})
24-
25-
path.traverse({
26-
Identifier({node}: {node: t.Identifier}) {
27-
if (isReactiveIdentifier(node.name, toMod)) {
28-
node.name = normalizeName(node.name)
29-
}
30-
},
31-
VariableDeclaration({node}: {node: t.VariableDeclaration}) {
32-
for (let i = 0; i < node.declarations.length; i += 1) {
33-
const declaration = node.declarations[i]
34-
35-
if (
36-
!(
37-
t.isIdentifier(declaration.id) &&
38-
/^\$/.test(declaration.id.name)
39-
)
40-
) {
41-
continue
42-
}
43-
44-
// change to const if it's `let` by any chance
45-
node.kind = 'const'
46-
47-
const normName = normalizeName(declaration.id.name)
48-
const setterName = getSetterName(normName)
49-
50-
// convert to `const [x,setX] = React.useState()`
51-
node.declarations[i] = t.variableDeclarator(
52-
t.arrayPattern([
53-
t.identifier(normName),
54-
t.identifier(setterName),
55-
]),
56-
t.callExpression(
57-
t.memberExpression(
58-
t.identifier('React'),
59-
t.identifier('useState')
60-
),
61-
declaration.init ? [declaration.init] : []
62-
)
63-
)
64-
}
65-
},
66-
ExpressionStatement({node}: {node: t.ExpressionStatement}) {
67-
if (!t.isAssignmentExpression(node.expression)) {
68-
return
69-
}
70-
71-
//HACK: forced to assignment expression for now, will need to switch to a `switch`
72-
// statement when working with both Assignment(=,+=,-=,etc) and Update expressions(++,--,**,etc)
73-
const expression: t.AssignmentExpression = node.expression
74-
75-
if (!t.isIdentifier(expression.left)) {
76-
return
77-
}
78-
79-
if (!isReactiveIdentifier(expression.left.name, toMod)) {
80-
return
81-
}
82-
83-
const normName = normalizeName(expression.left.name)
84-
const setterName = getSetterName(normName)
85-
86-
let callArgs: t.Expression[]
87-
88-
switch (expression.operator) {
89-
case '=': {
90-
callArgs = [{...expression.right}]
91-
break
92-
}
93-
94-
case '+=': {
95-
callArgs = [
96-
t.binaryExpression(
97-
'+',
98-
t.identifier(normName),
99-
expression.right
100-
),
101-
]
102-
break
103-
}
104-
105-
case '-=': {
106-
callArgs = [
107-
t.binaryExpression(
108-
'-',
109-
t.identifier(normName),
110-
expression.right
111-
),
112-
]
113-
break
114-
}
115-
116-
case '/=': {
117-
callArgs = [
118-
t.binaryExpression(
119-
'/',
120-
t.identifier(normName),
121-
expression.right
122-
),
123-
]
124-
break
125-
}
126-
127-
case '*=': {
128-
callArgs = [
129-
t.binaryExpression(
130-
'*',
131-
t.identifier(normName),
132-
expression.right
133-
),
134-
]
135-
break
136-
}
137-
default: {
138-
callArgs = []
139-
}
140-
}
141-
142-
node.expression = t.callExpression(
143-
t.identifier(setterName),
144-
callArgs
145-
)
146-
},
147-
})
13+
transformToStateByScope(path, toMod)
14814
},
15+
ArrowFunctionExpression(path: any) {
16+
transformToStateByScope(path, toMod)
17+
},
18+
},
19+
}
20+
}
21+
22+
function transformToStateByScope(path: any, toMod: ToModifyVariableI[]) {
23+
// TODO: check if the returned values are of the form `React.createElement`
24+
// NOTE: can avoid the above one since custom hooks won't be able to use this
25+
26+
Object.keys(path.scope.bindings).forEach((binding) => {
27+
if (/^\$/.test(binding)) {
28+
// add to list of identifiers to compare and replace
29+
// (not using scope replace to avoid shadow variables being replaced)
30+
const normName = normalizeName(binding)
31+
toMod.push({
32+
raw: binding,
33+
simplified: normName,
34+
})
35+
}
36+
})
37+
38+
// nested traverse to avoid replacing bindings of anything other than what's in this
39+
// function. To prevent creating state hooks outside a function
40+
path.traverse({
41+
Identifier({node}: {node: t.Identifier}) {
42+
if (isReactiveIdentifier(node.name, toMod)) {
43+
node.name = normalizeName(node.name)
44+
}
14945
},
46+
VariableDeclaration({node}: {node: t.VariableDeclaration}) {
47+
transformReactiveDeclarations(node, toMod)
48+
},
49+
ExpressionStatement({node}: {node: t.ExpressionStatement}) {
50+
transformAssignmentExpression(node, toMod)
51+
},
52+
})
53+
}
54+
55+
function transformReactiveDeclarations(
56+
node: t.VariableDeclaration,
57+
toMod: ToModifyVariableI[]
58+
) {
59+
for (let i = 0; i < node.declarations.length; i += 1) {
60+
const declaration = node.declarations[i]
61+
62+
if (
63+
!(
64+
t.isIdentifier(declaration.id) &&
65+
isReactiveIdentifier(declaration.id.name, toMod)
66+
)
67+
) {
68+
continue
69+
}
70+
71+
// change to const if it's `let` by any chance
72+
node.kind = 'const'
73+
74+
const normName = normalizeName(declaration.id.name)
75+
const setterName = getSetterName(normName)
76+
77+
// convert to `const [x,setX] = React.useState()`
78+
node.declarations[i] = t.variableDeclarator(
79+
t.arrayPattern([t.identifier(normName), t.identifier(setterName)]),
80+
t.callExpression(
81+
t.memberExpression(t.identifier('React'), t.identifier('useState')),
82+
declaration.init ? [declaration.init] : []
83+
)
84+
)
15085
}
15186
}
15287

88+
function transformAssignmentExpression(
89+
node: t.ExpressionStatement,
90+
toMod: ToModifyVariableI[]
91+
) {
92+
if (!t.isAssignmentExpression(node.expression)) {
93+
return
94+
}
95+
//HACK: forced to assignment expression for now, will need to switch to a `switch`
96+
// statement when working with both Assignment(=,+=,-=,etc) and Update expressions(++,--,**,etc)
97+
const expression: t.AssignmentExpression = node.expression
98+
99+
if (!t.isIdentifier(expression.left)) {
100+
return
101+
}
102+
103+
if (!isReactiveIdentifier(expression.left.name, toMod)) {
104+
return
105+
}
106+
107+
const normName = normalizeName(expression.left.name)
108+
const setterName = getSetterName(normName)
109+
110+
let callArgs: t.Expression[]
111+
112+
switch (expression.operator) {
113+
case '=': {
114+
callArgs = [{...expression.right}]
115+
break
116+
}
117+
118+
case '+=': {
119+
callArgs = [
120+
t.binaryExpression('+', t.identifier(normName), expression.right),
121+
]
122+
break
123+
}
124+
125+
case '-=': {
126+
callArgs = [
127+
t.binaryExpression('-', t.identifier(normName), expression.right),
128+
]
129+
break
130+
}
131+
132+
case '/=': {
133+
callArgs = [
134+
t.binaryExpression('/', t.identifier(normName), expression.right),
135+
]
136+
break
137+
}
138+
139+
case '*=': {
140+
callArgs = [
141+
t.binaryExpression('*', t.identifier(normName), expression.right),
142+
]
143+
break
144+
}
145+
default: {
146+
callArgs = []
147+
}
148+
}
149+
150+
node.expression = t.callExpression(t.identifier(setterName), callArgs)
151+
}
152+
153153
function isReactiveIdentifier(idName: string, modMap: ToModifyVariableI[]) {
154154
return (
155155
modMap.findIndex((x) => x.raw === idName || x.simplified === idName) > -1

0 commit comments

Comments
 (0)