@@ -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+
153153function isReactiveIdentifier ( idName : string , modMap : ToModifyVariableI [ ] ) {
154154 return (
155155 modMap . findIndex ( ( x ) => x . raw === idName || x . simplified === idName ) > - 1
0 commit comments