Skip to content

Commit 0166302

Browse files
committed
intitial commit
0 parents  commit 0166302

File tree

5 files changed

+221
-0
lines changed

5 files changed

+221
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
dist
3+
.DS_Store

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

.yarnrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--add.no-lockfile true
2+
--install.no-lockfile true

package.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "babel-transform-mutable-react-state",
3+
"version": "0.0.0",
4+
"main": "./dist/index.js",
5+
"module": "./dist/index.mjs",
6+
"types": "./dist/index.d.ts",
7+
"exports": {
8+
"./package.json": "./package.json",
9+
".": {
10+
"require": "./dist/index.js",
11+
"import": "./dist/index.mjs",
12+
"types": "./dist/index.d.ts",
13+
"default": "./dist/index.js"
14+
}
15+
},
16+
"scripts": {
17+
"build": "tsup source/index.ts --format cjs,esm --clean",
18+
"watch": "npm run build -- --watch source",
19+
"prepublishOnly": "npm run build"
20+
},
21+
"dependencies": {
22+
"@babel/core": "^7.16.5",
23+
"@babel/generator": "^7.16.5",
24+
"@babel/parser": "^7.16.6",
25+
"@babel/preset-react": "^7.16.5",
26+
"@babel/traverse": "^7.16.5",
27+
"@babel/types": "^7.16.0",
28+
"acorn": "^8.6.0",
29+
"acorn-walk": "^8.2.0",
30+
"escodegen": "^2.0.0",
31+
"object-path": "^0.11.8"
32+
},
33+
"devDependencies": {
34+
"@types/react": "^17.0.37",
35+
"tsup": "^5.11.4",
36+
"typescript": "^4.5.4"
37+
}
38+
}

source/index.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// TODO: remove when converting to plugin
2+
import { parse } from "@babel/parser";
3+
import traverse from "@babel/traverse";
4+
import * as t from "@babel/types";
5+
import generate from "@babel/generator";
6+
// ----
7+
8+
// TODO: remove when converting to plugin
9+
const codeSnippet = `
10+
import * as React from "react";
11+
12+
function Main(){
13+
let $aBara = 1;
14+
15+
const handlePress = ()=>{
16+
$aBara=2
17+
}
18+
19+
return <>
20+
<p>{$aBara}</p>
21+
<button onPress={handlePress}>Press</button>
22+
</>
23+
}
24+
`;
25+
// ----
26+
27+
// TODO: remove when converting to plugin
28+
const ast = parse(codeSnippet, {
29+
sourceType: "module",
30+
plugins: ["jsx"],
31+
});
32+
// ----
33+
34+
// TODO: move to a single functional scope with check for a react node
35+
const toMod = [];
36+
37+
traverse(ast, {
38+
Identifier({ node }) {
39+
if (isReactiveIdentifier(node.name, toMod)) {
40+
node.name = normalizeName(node.name);
41+
}
42+
},
43+
VariableDeclaration({ node }) {
44+
for (let i = 0; i < node.declarations.length; i += 1) {
45+
const declaration = node.declarations[i];
46+
47+
if (!/^\$/.test(declaration.id.name)) {
48+
continue;
49+
}
50+
51+
const normName = normalizeName(declaration.id.name);
52+
const setterName = getSetterName(normName);
53+
54+
toMod.push({
55+
raw: declaration.id.name,
56+
simplified: normName,
57+
});
58+
59+
node.declarations[i] = {
60+
type: "VariableDeclarator",
61+
kind: "const",
62+
id: {
63+
type: "ArrayPattern",
64+
elements: [t.identifier(normName), t.identifier(setterName)],
65+
},
66+
67+
init: {
68+
type: "CallExpression",
69+
callee: {
70+
type: "MemberExpression",
71+
object: t.identifier("React"),
72+
property: t.identifier("useState"),
73+
},
74+
arguments: [declaration.init],
75+
},
76+
};
77+
}
78+
},
79+
ExpressionStatement({ node }) {
80+
const expression = node.expression;
81+
82+
if (!t.isAssignmentExpression(node.expression)) {
83+
return;
84+
}
85+
86+
if (
87+
expression.left &&
88+
t.isIdentifier(expression.left) &&
89+
!isReactiveIdentifier(expression.left.name, toMod)
90+
) {
91+
return;
92+
}
93+
94+
const normName = normalizeName(expression.left.name);
95+
const setterName = getSetterName(normName);
96+
97+
let callArgs = [];
98+
99+
switch (expression.operator) {
100+
case "=": {
101+
callArgs = [{ ...expression.right }];
102+
break;
103+
}
104+
case "+=": {
105+
callArgs = [
106+
t.binaryExpression("+", t.identifier(normName), expression.right),
107+
];
108+
break;
109+
}
110+
case "-=": {
111+
callArgs = [
112+
t.binaryExpression("-", t.identifier(normName), expression.right),
113+
];
114+
break;
115+
}
116+
case "/=": {
117+
callArgs = [
118+
t.binaryExpression("/", t.identifier(normName), expression.right),
119+
];
120+
break;
121+
}
122+
case "*=": {
123+
callArgs = [
124+
t.binaryExpression("*", t.identifier(normName), expression.right),
125+
];
126+
break;
127+
}
128+
}
129+
130+
node.expression = createCallExpression(setterName, callArgs);
131+
},
132+
});
133+
134+
function isReactiveIdentifier(idName, modMap) {
135+
return (
136+
modMap.findIndex((x) => x.raw === idName || x.simplified === idName) > -1
137+
);
138+
}
139+
140+
function getSetterName(normalizedName) {
141+
return (
142+
"set" + normalizedName.charAt(0).toUpperCase() + normalizedName.slice(1)
143+
);
144+
}
145+
146+
function normalizeName(n) {
147+
return String(n).replace(/\$/, "");
148+
}
149+
150+
function createCallExpression(calleeName, args) {
151+
return {
152+
type: "CallExpression",
153+
callee: t.identifier(calleeName),
154+
arguments: args,
155+
};
156+
}
157+
158+
// TODO: remove when converting to plugin
159+
const output = generate(ast, {}, codeSnippet);
160+
console.log(
161+
`
162+
${codeSnippet}
163+
164+
↓ ↓ ↓
165+
166+
${output.code}
167+
`
168+
);
169+
// ----
170+
171+
// TODO: convert to plugin
172+
export default function ({ types: _t }) {
173+
// plugin contents
174+
return {
175+
visitor: {},
176+
};
177+
}

0 commit comments

Comments
 (0)