[MIL-1547] Recognize subscription.remove() as effect cleanup#471
[MIL-1547] Recognize subscription.remove() as effect cleanup#471rayhanadev wants to merge 10 commits into
subscription.remove() as effect cleanup#471Conversation
|
✅ No new issues Reviewed by reactreview for commit a7c338d. Configure here. |
There was a problem hiding this comment.
Pull request overview
This PR updates the effect-needs-cleanup detection heuristics to treat React Native EventSubscription cleanup (sub.remove()) as a valid effect teardown, and adds a regression test to prevent reintroducing the false positive.
Changes:
- Add
"remove"to the recognized unsubscription/cleanup method names. - Add a regression test covering
AppState.addEventListener(...); return () => sub.remove();.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/react-doctor/tests/regressions/state-rules/effect-needs-cleanup.test.ts | Adds a regression test ensuring RN subscription .remove() cleanup is not flagged. |
| packages/oxlint-plugin-react-doctor/src/plugin/constants/react.ts | Expands the unsubscription method allowlist to include remove for RN-style subscriptions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
aidenybai
left a comment
There was a problem hiding this comment.
ran the suite locally (effect-needs-cleanup 20/20, prefer-use-sync-external-store 8/8, tsc clean) and probed a few edge cases. direction is right. a handful of inline notes below, mostly small.
subscription.remove() as effect cleanup
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
packages/oxlint-plugin-react-doctor/src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts:67
isCleanupReturncurrently returnstruefor any returned subscribe-like call expression. This will incorrectly credit returns likereturn window.addEventListener(...)as valid cleanup (and similarly impacts expression-body effects) even though these APIs don't return a cleanup function. Suggest restricting this case to subscribe-like methods known to return an unsubscribe function, or otherwise requiring the returned value to be a function-shaped cleanup.
if (isNodeOfType(returnedValue, "Identifier")) {
return knownBoundReleaseNames.has(returnedValue.name);
}
if (isSubscribeLikeCallExpression(returnedValue)) return true;
if (
bc530c7 to
c89150a
Compare
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
c89150a to
b78bad7
Compare
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Why?
React Native APIs such as
AppState.addEventListener,Keyboard.addListener,Appearance.addChangeListener, andDimensions.addEventListenerreturn subscription objects that are cleaned up by calling.remove()on the returned value. The cleanup detector previously recognized global teardown calls likeremoveEventListener,off,unsubscribe, andabort, but it did not understand this React Native subscription-object shape.That meant effects which correctly returned
sub.remove()could still be flagged as missing cleanup. At the same time, treating every.remove()call as cleanup would be too broad becauseremoveis common on unrelated objects. This change recognizes.remove()only when the receiver is a binding created by a subscribe-like call in the same effect.What changed?
node.remove()do not hide real missing cleanups..remove()cleanup, unrelated.remove()false negatives, conditional bound cleanup, and theprefer-use-sync-external-storeinteraction.Before:
Could be reported as missing cleanup.
After:
Is accepted because
subis known to come from a subscribe-like call.Before:
Could become a false negative if
.remove()were treated globally.After:
Still reports missing listener cleanup because
nodeis not the subscription returned by the effect.Test plan
Users can verify correctness by running:
pnpm --filter react-doctor test -- tests/regressions/state-rules/effect-needs-cleanup.test.ts tests/regressions/state-rules/prefer-use-sync-external-store.test.ts pnpm --filter oxlint-plugin-react-doctor typecheck