Conversation
The visitor scaffolding shared by no-prop-callback-in-effect and
no-derived-useState pushes an empty Set when entering a non-component
FunctionDeclaration / ArrowFunctionExpression. The intent (per the
existing comment) is to act as a barrier so identifiers inside the
helper don't resolve against an outer component's props.
The implementation walked the entire stack regardless, so a helper
nested inside a component would inherit the component's prop set and
produce false positives:
function Outer({ value, onChange }) { // outer props
function inner() {
const [draft, setDraft] = useState(value);
useEffect(() => {
onChange(isOn);
}, [isOn, onChange]);
}
inner();
}
The closed-over `value` and `onChange` are NOT props of `inner`;
they're lexically captured from `Outer`. Both rules incorrectly
flagged this shape.
Fix: stop the lookup walk at the first empty frame so the barrier
the visitor pushes actually behaves as one.
Two regression cases per rule (real prop → still flags; nested helper
→ no longer flags) live in tests/regressions/prop-stack-barrier.test.ts.
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
no-prop-callback-in-effect / no-derived-useState prop-stack lookupsno-prop-callback-in-effect / no-derived-useState prop-stack lookups
cursor Bot
pushed a commit
that referenced
this pull request
May 8, 2026
Rebased onto main after PR #153 (no-effect-event-handler / no-derived-state-effect) and PR #163 (prop-stack-barrier) merged. Reconstructed the test file by appending the new describe block to main's version (the textual conflict was purely interleaved test sections — both rules' tests now coexist cleanly). Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
cursor Bot
pushed a commit
that referenced
this pull request
May 8, 2026
…ndler defer Rebased onto main after #153 / #163 merged. Test file reconstructed by appending PR #155's describe block to main's version. The noEffectEventHandler rule now combines main's widened test predicate (uses getRootIdentifierName so MemberExpression guards like `if (product.isInCart)` match) with PR #155's deference logic (skip when noEventTriggerState would fire — state-typed dep + recognized side-effect callee). All 552 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
aidenybai
added a commit
that referenced
this pull request
May 8, 2026
Rebased onto main after #153 / #163 merged. Test file reconstructed by appending PR's describe block to main's version + the reactMajorVersion helper-arg modification. All 555 tests pass. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Surfaced during a self-review of the in-flight useEffect rule PRs (#153, #154, #155, #156, #157, #162). The bug is on
mainand predates that work, but the same shape needed fixing on the new branches too — fixing here keepsmainhonest and avoids forking the convention.The bug
The visitor scaffolding shared by
no-prop-callback-in-effectandno-derived-useStatepushes an emptySetwhen entering a non-componentFunctionDeclaration/ArrowFunctionExpression. The intent (per the existing comment) is to act as a barrier: identifiers inside a helper shouldn't resolve against an outer component's props because a closed-overvalueis not a prop of the helper.The implementation walked the entire stack anyway:
Top frame is empty → keep walking → next frame matches → return
true. Barrier intent broken.False positives this produced
Both rules incorrectly fired on this shape:
valueandonChangeare lexically captured fromOuter, not props ofinner.Fix
Single-line change in each rule's
isPropName: stop the walk at the first empty frame.The empty frame the visitor pushes when entering a non-component function now acts as the barrier the comment claims it does.
Tests
New file
tests/regressions/prop-stack-barrier.test.tswith 4 cases:no-derived-useStatereal prop → still flags ✅no-derived-useStatenested helper closing over outer prop → no longer flags ❌no-prop-callback-in-effectreal prop → still flags ✅no-prop-callback-in-effectnested helper closing over outer prop → no longer flags ❌484/484 tests passing. Lint, typecheck, format clean. No changeset.
Related
The same fix has been applied to the equivalent helpers introduced by the in-flight rule PRs:
no-mirror-prop-effect(commit 6954c64 oncursor/comprehensive-useeffect-analyzer-7144)prefer-use-effect-eventrule #162prefer-use-effect-event(commit 77be9a8 oncursor/prefer-use-effect-event-7144)Both of those branches additionally switched from
walkAstto a directfor (statement of componentBody.body)iteration so they only visit useEffects that are direct top-level statements — that's a separate fix for a related FP path that only affected the new rules.