Skip to content

Commit

Permalink
Fix: #16 False positive for "Better to just use Proxy state" (#17)
Browse files Browse the repository at this point in the history
* add conditional check for reading of snapshot

* additional tests for allowing read on snapshots
  • Loading branch information
barelyhuman authored Feb 23, 2022
1 parent 85b2b93 commit b313738
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 11 deletions.
6 changes: 3 additions & 3 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"index.js": {
"bundled": 12750,
"minified": 7384,
"gzipped": 2144
"bundled": 13543,
"minified": 7698,
"gzipped": 2234
}
}
28 changes: 20 additions & 8 deletions src/StateSnapshot.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { isInSomething } from './lib/utils'
import {
callExpressions,
functionTypes,
isInSomething,
isReadOnly,
} from './lib/utils'

const functionTypes = ['ArrowFunctionExpression', 'FunctionExpression']
const callExpressions = ['JSXExpressionContainer', 'CallExpression']
export const PROXY_RENDER_PHASE_MESSAGE =
'Using proxies in the render phase would cause unexpected problems.'
export const SNAPSHOT_CALLBACK_MESSAGE = 'Better to just use proxy state.'
Expand Down Expand Up @@ -84,11 +87,20 @@ export default {
message: PROXY_RENDER_PHASE_MESSAGE,
})
}
if (kind === 'snapshot' && isInCallback(node)) {
return context.report({
node,
message: SNAPSHOT_CALLBACK_MESSAGE,
})

if (kind === 'snapshot') {
// ignore the error if the snapshot
// is just being read
if (isReadOnly(node)) {
return
}

if (isInCallback(node)) {
return context.report({
node,
message: SNAPSHOT_CALLBACK_MESSAGE,
})
}
}
},
}
Expand Down
57 changes: 57 additions & 0 deletions src/lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export const callExpressions = ['JSXExpressionContainer', 'CallExpression']
export const functionTypes = ['ArrowFunctionExpression', 'FunctionExpression']
export const writingOpExpressionTypes = ['UpdateExpression']

/**
* @param {any} node ASTNode to check
* @param {string} thing Check if the node is inside a particular type of ASTNode
Expand Down Expand Up @@ -87,3 +91,56 @@ export function getParentOfNodeType(node, nodeType) {
}
return null
}

/**
* @description check if the node is only being read
* and not being used to manipulate state, recursively checks in the node is
* in a member expression as well
* @param {*} node
*/
export function isReadOnly(node) {
if (writingOpExpressionTypes.indexOf(node.parent.type) > -1) {
return false
}

if (node.parent.type === 'AssignmentExpression' && isLeftOfAssignment(node)) {
return false
}

if (node.parent.type === 'MemberExpression') {
return isReadOnly(node.parent)
}

return true
}

/**
*
* @param {*} node
* @returns {boolean} true if the node is on the left
* of an assignment expression aka being modified
*/
export function isLeftOfAssignment(node) {
if (Object.is(node.parent.left, node)) {
return true
}

return false
}

/**
* @description get the closes call expression or function defintion
* @param {*} node
*/
export function returnFirstCallback(node) {
if (!node.parent || !node.parent.type) return false

if (
callExpressions.includes(node.parent.type) &&
functionTypes.includes(node.type)
) {
return node
} else {
return returnFirstCallback(node.parent)
}
}
44 changes: 44 additions & 0 deletions tests/StateSnapshot.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ ruleTester.run('state-snapshot-rule', rule, {
return <div>{snap.foo}</div>
}
`,
`
function useExample2(s) {
const {b: {c} } = useSnapshot(s.a1);
useEffect(() => {
if (c === 'a1c') {
state.a1.b.c = 'example';
}
}, [c]);
}`,

`
function useProxyStateExample(a) {
const s = useSnapshot(a);
useEffect(() => {
a.a = 'example';
}, [s.a]);
}`,
],
invalid: [
{
Expand Down Expand Up @@ -178,5 +197,30 @@ ruleTester.run('state-snapshot-rule', rule, {
`,
errors: [COMPUTED_DECLARATION_ORDER],
},
{
code: `function Counter() {
const snapshot = useSnapshot(state)
useEffect(() => {
snapshot.count = randomValue + 1;
})
return (
<></>
)
}`,
errors: [SNAPSHOT_CALLBACK_MESSAGE],
},
{
code: `
function useProxyStateExample(a) {
const s = useSnapshot(a);
useEffect(() => {
if(s.a==="1"){
s.a = 'example';
}
}, [s.a]);
}`,
errors: [SNAPSHOT_CALLBACK_MESSAGE],
},
],
})

0 comments on commit b313738

Please sign in to comment.