Skip to content

Commit b8be49c

Browse files
committed
make it into a plugin for babel
also converted to ts and added the base set of tests with snapshots for the same
1 parent 0166302 commit b8be49c

File tree

6 files changed

+245
-204
lines changed

6 files changed

+245
-204
lines changed

package.json

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,57 @@
11
{
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-
}
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 --dts --clean",
18+
"test": "ava",
19+
"fix": "npx prettier --write source/* test/*",
20+
"watch": "npm run build -- --watch source",
21+
"prepublishOnly": "npm run build"
22+
},
23+
"dependencies": {
24+
"@babel/types": "^7.16.0"
25+
},
26+
"devDependencies": {
27+
"@babel/core": "^7.16.5",
28+
"@babel/plugin-transform-react-jsx": "^7.16.5",
29+
"@babel/preset-env": "^7.16.5",
30+
"@babel/preset-react": "^7.16.5",
31+
"@types/babel__core": "^7.1.17",
32+
"@types/react": "^17.0.37",
33+
"ava": "^3.15.0",
34+
"prettier": "^2.5.1",
35+
"ts-node": "^10.4.0",
36+
"tsup": "^5.11.4",
37+
"typescript": "^4.5.4"
38+
},
39+
"prettier": {
40+
"bracketSpacing": false,
41+
"semi": false,
42+
"singleQuote": true,
43+
"trailingComma": "es5",
44+
"useTabs": true
45+
},
46+
"ava": {
47+
"extensions": {
48+
"ts": "module"
49+
},
50+
"nonSemVerExperiments": {
51+
"configurableModuleFormat": true
52+
},
53+
"nodeArguments": [
54+
"--loader=ts-node/esm"
55+
]
56+
}
3857
}

source/index.ts

Lines changed: 123 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,132 @@
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-
);
1+
import * as t from '@babel/types'
2+
3+
interface ToModifyVariableI {
4+
raw: string
5+
simplified: string
1386
}
1397

140-
function getSetterName(normalizedName) {
141-
return (
142-
"set" + normalizedName.charAt(0).toUpperCase() + normalizedName.slice(1)
143-
);
8+
export default function () {
9+
const toMod: ToModifyVariableI[] = []
10+
return {
11+
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
25+
}
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] : []
48+
)
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)
113+
},
114+
},
115+
}
144116
}
145117

146-
function normalizeName(n) {
147-
return String(n).replace(/\$/, "");
118+
function isReactiveIdentifier(idName: string, modMap: ToModifyVariableI[]) {
119+
return (
120+
modMap.findIndex((x) => x.raw === idName || x.simplified === idName) > -1
121+
)
148122
}
149123

150-
function createCallExpression(calleeName, args) {
151-
return {
152-
type: "CallExpression",
153-
callee: t.identifier(calleeName),
154-
arguments: args,
155-
};
124+
function getSetterName(normalizedName: string) {
125+
return (
126+
'set' + normalizedName.charAt(0).toUpperCase() + normalizedName.slice(1)
127+
)
156128
}
157129

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-
};
130+
function normalizeName(n: string) {
131+
return n.replace(/\$/, '')
177132
}

0 commit comments

Comments
 (0)