Skip to content

Commit de8d33b

Browse files
committed
limit reactive variables to functional scope
1 parent fb5c579 commit de8d33b

File tree

4 files changed

+200
-100
lines changed

4 files changed

+200
-100
lines changed

source/index.ts

Lines changed: 134 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -9,107 +9,142 @@ export default function () {
99
const toMod: ToModifyVariableI[] = []
1010
return {
1111
visitor: {
12-
Identifier({node}: {node: t.Identifier}) {
13-
if (isReactiveIdentifier(node.name, toMod)) {
14-
node.name = normalizeName(node.name)
15-
}
16-
},
17-
VariableDeclaration({node}: {node: t.VariableDeclaration}) {
18-
for (let i = 0; i < node.declarations.length; i += 1) {
19-
const declaration = node.declarations[i]
20-
21-
if (
22-
!(t.isIdentifier(declaration.id) && /^\$/.test(declaration.id.name))
23-
) {
24-
continue
12+
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+
})
2522
}
26-
27-
// change to const if it's `let` by any chance
28-
node.kind = 'const'
29-
30-
const normName = normalizeName(declaration.id.name)
31-
const setterName = getSetterName(normName)
32-
33-
// add to list of identifiers to compare and replace
34-
toMod.push({
35-
raw: declaration.id.name,
36-
simplified: normName,
37-
})
38-
39-
// convert to `const [x,setX] = React.useState()`
40-
node.declarations[i] = t.variableDeclarator(
41-
t.arrayPattern([t.identifier(normName), t.identifier(setterName)]),
42-
t.callExpression(
43-
t.memberExpression(
44-
t.identifier('React'),
45-
t.identifier('useState')
46-
),
47-
declaration.init ? [declaration.init] : []
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
48145
)
49-
)
50-
}
51-
},
52-
ExpressionStatement({node}: {node: t.ExpressionStatement}) {
53-
if (!t.isAssignmentExpression(node.expression)) {
54-
return
55-
}
56-
57-
//HACK: forced to assignment expression for now, will need to switch to a `switch`
58-
// statement when working with both Assignment(=,+=,-=,etc) and Update expressions(++,--,**,etc)
59-
const expression: t.AssignmentExpression = node.expression
60-
61-
if (!t.isIdentifier(expression.left)) {
62-
return
63-
}
64-
65-
if (!isReactiveIdentifier(expression.left.name, toMod)) {
66-
return
67-
}
68-
69-
const normName = normalizeName(expression.left.name)
70-
const setterName = getSetterName(normName)
71-
72-
let callArgs: t.Expression[]
73-
74-
switch (expression.operator) {
75-
case '=': {
76-
callArgs = [{...expression.right}]
77-
break
78-
}
79-
80-
case '+=': {
81-
callArgs = [
82-
t.binaryExpression('+', t.identifier(normName), expression.right),
83-
]
84-
break
85-
}
86-
87-
case '-=': {
88-
callArgs = [
89-
t.binaryExpression('-', t.identifier(normName), expression.right),
90-
]
91-
break
92-
}
93-
94-
case '/=': {
95-
callArgs = [
96-
t.binaryExpression('/', t.identifier(normName), expression.right),
97-
]
98-
break
99-
}
100-
101-
case '*=': {
102-
callArgs = [
103-
t.binaryExpression('*', t.identifier(normName), expression.right),
104-
]
105-
break
106-
}
107-
default: {
108-
callArgs = []
109-
}
110-
}
111-
112-
node.expression = t.callExpression(t.identifier(setterName), callArgs)
146+
},
147+
})
113148
},
114149
},
115150
}

test/snapshots/test.ts.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,41 @@ Generated by [AVA](https://avajs.dev).
2121
onClick: onPress␊
2222
}, "Press"));␊
2323
}`
24+
25+
## Simple Transform
26+
27+
> Snapshot 1
28+
29+
`import * as React from "react";␊
30+
31+
function Component() {␊
32+
const [a, setA] = React.useState(1);␊
33+
34+
const onPress = () => {␊
35+
setA(a + 1);␊
36+
};␊
37+
38+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("p", null, a), /*#__PURE__*/React.createElement("button", {␊
39+
onClick: onPress␊
40+
}, "Press"));␊
41+
}`
42+
43+
## Check Functional Scope
44+
45+
> Snapshot 1
46+
47+
`import * as React from "react";␊
48+
let $b = 2;␊
49+
50+
function Component() {␊
51+
const [a, setA] = React.useState(1);␊
52+
53+
const onPress = () => {␊
54+
setA(a + 1);␊
55+
$b = 3;␊
56+
};␊
57+
58+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("p", null, a), /*#__PURE__*/React.createElement("button", {␊
59+
onClick: onPress␊
60+
}, "Press"));␊
61+
}`

test/snapshots/test.ts.snap

83 Bytes
Binary file not shown.

test/test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const compile = (code: string) =>
88
plugins: [plugin],
99
})
1010

11-
test('works', (t) => {
11+
test('Simple Transform', (t) => {
1212
const code = `
1313
import * as React from "react"
1414
@@ -32,3 +32,30 @@ test('works', (t) => {
3232
}
3333
t.snapshot(result.code)
3434
})
35+
36+
test('Check Functional Scope', (t) => {
37+
const code = `
38+
import * as React from "react"
39+
40+
let $b = 2;
41+
42+
function Component(){
43+
let $a = 1;
44+
45+
const onPress = () => {
46+
$a += 1;
47+
$b = 3;
48+
}
49+
50+
return <div>
51+
<p>{$a}</p>
52+
<button onClick={onPress}>Press</button>
53+
</div>;
54+
}`
55+
56+
const result = compile(code)
57+
if (!result) {
58+
return t.fail()
59+
}
60+
t.snapshot(result.code)
61+
})

0 commit comments

Comments
 (0)