Skip to content

Commit d0b6662

Browse files
committed
adding hooks state names by incorporating ast parser
1 parent 4eab364 commit d0b6662

File tree

6 files changed

+119
-41
lines changed

6 files changed

+119
-41
lines changed

dev-reactime/__tests__/astParser.test.js

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@
77
import { configure } from 'enzyme';
88
import Adapter from 'enzyme-adapter-react-16';
99
// import toJson from 'enzyme-to-json';
10-
import astParser from '../astParser';
10+
import { getHooksNames } from '../helpers';
1111

1212
// Newer Enzyme versions require an adapter to a particular version of React
1313
configure({ adapter: new Adapter() });
1414

1515
describe('AST Unit Tests', () => {
16-
describe('astParser', () => {
16+
describe('getHooksNames', () => {
1717
it.skip('Should return object with one getter/setter for a single useState instance', () => {
1818
const useState = 'const singleUseStateTest = () => { const [testCount, setTestCount] = useState(0); return ( <div> <p> You clicked this {testCount} times </p> <button onClick={() => setTestCount(testCount + 1)}>+1</button> <button onClick={() => setTestCount(testCount - 1)}>-1</button> <hr /> </div> )';
1919

2020
const expectedObject = {
2121
_useState: 'testCount',
2222
_useState2: 'setTestCount',
2323
};
24-
expect(astParser(useState)).toEqual(expectedObject);
24+
expect(getHooksNames(useState)).toEqual(expectedObject);
2525
});
2626

2727
it.skip('Should output the right number of properties when taking in multiple function definitions', () => {
@@ -33,26 +33,94 @@ describe('AST Unit Tests', () => {
3333
_useState3: 'age',
3434
_useState4: 'setAge',
3535
};
36-
expect(astParser(useState)).toEqual(expectedObject);
37-
expect(Object.keys(astParser(useState))).toHaveLength(4);
36+
expect(getHooksNames(useState)).toEqual(expectedObject);
37+
expect(Object.keys(getHooksNames(useState))).toHaveLength(4);
3838
});
3939

4040
it.skip('Should ignore any non-hook definitions', () => {
4141
const useState = 'const singleUseStateTest = () => { const [testCount, setTestCount] = useState(0); const age = 20; return ( <div> <p> You clicked this {testCount} times </p> <button onClick={() => setTestCount(testCount + 1)}>+1</button> <button onClick={() => setTestCount(testCount - 1)}>-1</button> <p> You are {age} years old! </p> <button onClick={age => age + 1}>Get Older</button> <hr /> </div>)';
4242

43-
expect(Object.keys(astParser(useState))).toHaveLength(2);
43+
expect(Object.keys(getHooksNames(useState))).toHaveLength(2);
4444
});
4545

4646
it.skip('Should return an empty object if no hooks found', () => {
4747
const useState = 'const singleUseStateTest = () => { const age = 20; return ( <div> <p> You are {age} years old! </p> <button onClick={age => age + 1}>Get Older</button> <hr /> </div>)';
4848

49-
expect(astParser(useState)).toBe({});
49+
expect(getHooksNames(useState)).toBe({});
5050
});
5151

5252
it.skip('Should throw an error if input is invalid javascript', () => {
5353
const useState = 'const singleUseStateTest = () => { age: 20; return ( <div> <p> You are {age} years old! </p> <button onClick={age + 1}>Get Older</button></div>) }';
5454

55-
expect(astParser(useState)).toThrow();
55+
expect(getHooksNames(useState)).toThrow();
5656
});
5757
});
5858
});
59+
60+
61+
62+
/* /*
63+
console.log('getHooksNames: ', getHooksNames(`function LastSnapshot(props) {
64+
var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(''),
65+
_useState2 = _slicedToArray(_useState, 2),
66+
currentSnapshot = _useState2[0],
67+
setCurrentSnapshot = _useState2[1];
68+
69+
var _useState3 = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(25),
70+
_useState4 = _slicedToArray(_useState3, 2),
71+
testState = _useState4[0],
72+
setTestState = _useState4[1];
73+
74+
var _useState5 = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(50),
75+
_useState6 = _slicedToArray(_useState5, 2),
76+
testState2 = _useState6[0],
77+
setTestState2 = _useState6[1];
78+
79+
function replacer(name, val) {
80+
// Ignore the key that is the name of the state variable
81+
if (name === 'currentSnapshot') {
82+
console.log('filtering currentSnapshot from display');
83+
return undefined;
84+
}
85+
86+
return val;
87+
}
88+
89+
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(function () {
90+
window.addEventListener('message', function (_ref) {
91+
var _ref$data = _ref.data,
92+
action = _ref$data.action,
93+
payload = _ref$data.payload;
94+
95+
if (action === 'recordSnap') {
96+
console.log('stringifying payload:', payload);
97+
var payloadContent = JSON.stringify(payload, replacer, 1);
98+
setCurrentSnapshot(payloadContent);
99+
setTestState(function (state) {
100+
return state * 2;
101+
});
102+
setTestState2(function (state) {
103+
return state * 2;
104+
});
105+
console.log('current snapshot', currentSnapshot);
106+
}
107+
});
108+
}, []);
109+
/*
110+
// This method is for testing. Setting state after the activeSandbox is changed modifies the overall behavior of the sandbox environment.
111+
const { activeSandbox } = props;
112+
useEffect(() => {
113+
// Reset the current snapshot when a new sandbox is entered
114+
setCurrentSnapshot('');
115+
}, [activeSandbox]);
116+
*/
117+
/*
118+
return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
119+
id: "lastSnapshot",
120+
className: "ml-5 mt-2",
121+
style: {
122+
whiteSpace: 'pre'
123+
}
124+
}, testState, testState2, currentSnapshot));
125+
};`));
126+
*/

dev-reactime/helpers.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
/* eslint-disable linebreak-style */
22
/* eslint-disable no-inner-declarations, no-loop-func */
33
// eslint-disable-next-line import/newline-after-import
4-
import acorn from 'acorn'; // javascript parser
5-
import jsx from 'acorn-jsx';
4+
const acorn = require('acorn');
5+
const jsx = require('acorn-jsx');
6+
// import { acorn } from 'acorn'; // javascript parser
7+
// import { jsx } from 'acorn-jsx';
8+
9+
const JSXParser = acorn.Parser.extend(jsx());
610

711
// Returns a throttled version of an input function
812
// The returned throttled function only executes at most once every t milliseconds
@@ -35,10 +39,11 @@ export const throttle = (f, t) => {
3539

3640
// Helper function to grab the getters/setters from `elementType`
3741
export const getHooksNames = elementType => {
38-
const JSXParser = acorn.Parser.extend(jsx());
42+
3943
// Initialize empty object to store the setters and getter
4044
let ast = JSXParser.parse(elementType);
4145
const hookState = {};
46+
const hooksNames = {};
4247

4348
while (Object.hasOwnProperty.call(ast, 'body')) {
4449
let tsCount = 0; // Counter for the number of TypeScript hooks seen (to distinguish in masterState)
@@ -51,8 +56,8 @@ export const getHooksNames = elementType => {
5156
* Check within each function declaration if there are hook declarations */
5257
ast.forEach(functionDec => {
5358
let body;
54-
if (functionDec.expression) body = functionDec.expression.body.body;
55-
else body = functionDec.body.body;
59+
if (functionDec.expression && functionDec.expression.body) body = functionDec.expression.body.body;
60+
else body = functionDec.body ? functionDec.body.body : [];
5661
// Traverse through the function's funcDecs and Expression Statements
5762
body.forEach(elem => {
5863
if (elem.type === 'VariableDeclaration') {
@@ -69,7 +74,15 @@ export const getHooksNames = elementType => {
6974
statements.unshift(`_useWildcard${tsCount}`);
7075
tsCount += 1;
7176
});
72-
} else statements.push(hook.id.name);
77+
} else {
78+
if (hook.init.object && hook.init.object.name) {
79+
const varName = hook.init.object.name;
80+
if (!hooksNames[varName] && varName.match(/_use/)) {
81+
hooksNames[varName] = hook.id.name;
82+
}
83+
}
84+
statements.push(hook.id.name);
85+
}
7386
});
7487
}
7588
});
@@ -80,5 +93,5 @@ export const getHooksNames = elementType => {
8093
});
8194
});
8295
}
83-
return hookState;
84-
};
96+
return Object.values(hooksNames);
97+
};

dev-reactime/linkFiber.js

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,14 @@
99
* that is invoked on
1010
* @param snap --> Current snapshot
1111
* @param mode --> Current mode (jumping i.e. time-traveling, locked, or paused)
12-
* and @returns a function to be invoked on the rootContainer HTMLElement
12+
* and @returns a function to be invoked by index.js to initiate snapshot monitoring
1313
*
1414
* @function updateSnapShotTree
1515
* --> Middleware #1: Updates snap object with latest snapshot
1616
*
1717
* @function sendSnapshot
1818
* --> Middleware #2: Gets a copy of the current snap.tree and posts a message to the window
1919
*
20-
* @function changeSetState
21-
* @param component : stateNode property on a stateful class component's FiberNode object
22-
* --> Binds class component setState method to the component
23-
* --> Injects middleware into class component's setState method
24-
*
25-
* @function changeUseState
26-
* @param component : memoizedState property on a stateful functional component's FiberNode object
27-
* --> Binds functional component dispatch method to the component
28-
* --> Injects middleware into component's dispatch method
29-
* Note: dispatch is hook equivalent to setState()
30-
*
3120
* @function traverseHooks
3221
* @param memoizedState : memoizedState property on a stateful fctnl component's FiberNode object
3322
* --> Helper function to traverse through memoizedState
@@ -46,11 +35,14 @@
4635

4736
// const Tree = require('./tree').default;
4837
// const componentActionsRecord = require('./masterState');
38+
import acorn from 'acorn'; // javascript parser
39+
import jsx from 'acorn-jsx';
4940
import Tree from './tree';
5041
import componentActionsRecord from './masterState';
51-
import { throttle } from './helpers';
5242

53-
const DEBUG_MODE = true;
43+
import { throttle, getHooksNames } from './helpers';
44+
45+
const DEBUG_MODE = false;
5446

5547
const alwaysLog = console.log;
5648

@@ -93,7 +85,7 @@ export default (snap, mode) => {
9385
// a hooks component changes state
9486
function traverseHooks(memoizedState) {
9587
const hooksStates = [];
96-
if (memoizedState && memoizedState.queue) {
88+
while (memoizedState && memoizedState.queue) {
9789
// Carlos: these two are legacy comments, we should look into them later
9890
// prevents useEffect from crashing on load
9991
// if (memoizedState.next.queue === null) { // prevents double pushing snapshot updates
@@ -156,14 +148,16 @@ export default (snap, mode) => {
156148
// We then store them along with the corresponding memoizedState.queue,
157149
// which includes the dispatch() function we use to change their state.
158150
const hooksStates = traverseHooks(memoizedState);
159-
hooksStates.forEach(state => {
151+
const hooksNames = getHooksNames(elementType.toString());
152+
console.log('hooks names:', hooksNames);
153+
hooksStates.forEach((state, i) => {
160154
hooksIndex = componentActionsRecord.saveNew(state.state, state.component);
161155
if (newState && newState.hooksState) {
162-
newState.hooksState.push([state.state, hooksIndex]);
156+
newState.hooksState.push([{ [hooksNames[i]]: state.state }, hooksIndex]);
163157
} else if (newState) {
164-
newState.hooksState = [[state.state, hooksIndex]];
158+
newState.hooksState = [{ [hooksNames[i]]: state.state }, hooksIndex];
165159
} else {
166-
newState = { hooksState: [[state.state, hooksIndex]] };
160+
newState = { hooksState: [{ [hooksNames[i]]: state.state }, hooksIndex] };
167161
}
168162
componentFound = true;
169163
console.log('currentFiber of hooks state:', currentFiber);

package-lock.json

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@
4949
"@babel/plugin-proposal-decorators": "^7.4.4",
5050
"@babel/preset-env": "^7.10.3",
5151
"@babel/preset-react": "^7.0.0",
52-
"acorn": "^7.3.1",
53-
"acorn-jsx": "^5.2.0",
5452
"babel-loader": "^8.0.6",
5553
"core-js": "^3.6.5",
5654
"css-loader": "^3.2.0",
@@ -79,6 +77,8 @@
7977
"webpack-cli": "^3.3.6"
8078
},
8179
"dependencies": {
80+
"acorn": "^7.3.1",
81+
"acorn-jsx": "^5.2.0",
8282
"bower": "^1.8.8",
8383
"d3": "^5.16.0",
8484
"d3-zoom": "^1.8.3",

sandboxes/automated-tests/hooks-redux-router/Frontend/src/scenes/lastSnapshot.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import './styles.sass';
1010
const LastSnapshot = props => {
1111
const [currentSnapshot, setCurrentSnapshot] = useState('');
1212

13+
const [testState, setTestState] = useState(25);
14+
const [testState2, setTestState2] = useState(50);
15+
1316
function replacer(name, val) {
1417
// Ignore the key that is the name of the state variable
1518
if (name === 'currentSnapshot') {
@@ -26,6 +29,8 @@ const LastSnapshot = props => {
2629
console.log('stringifying payload:', payload);
2730
const payloadContent = JSON.stringify(payload, replacer, 1);
2831
setCurrentSnapshot(payloadContent);
32+
setTestState((state) => state*2);
33+
setTestState2((state) => state*2);
2934
console.log('current snapshot', currentSnapshot);
3035
}
3136
});

0 commit comments

Comments
 (0)