Skip to content

Commit 3e24fd0

Browse files
committed
test(ownts): cover async ES5 function effect in leak controls (CodeRabbit)
The async-cleanup suppression path was only exercised via `async () =>` in EffectLeakControl.tsx. Add a fifth control — `useEffect(async function () { … return function () {} }, [])` — so the ES5 transpiled async shape is regressed too: React still ignores the returned cleanup, so the listener must stay OWN001. Bumps the expected count to 5 in the spike test and the CI step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVeuchch67CtjSbizYm8gi
1 parent f180ea5 commit 3e24fd0

3 files changed

Lines changed: 19 additions & 7 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,16 +1132,17 @@ jobs:
11321132
# cleanup, observer.subscribe/unsubscribe — all released, zero findings.
11331133
[ "$(echo "$rw" | grep -cE '\[(OWN|EFF|DI)[0-9]{3}\]')" -eq 0 ] \
11341134
|| { echo "FAIL: real-world cleanup patterns must not be reported"; exit 1; }
1135-
- name: False-negative controls (wrong controller / args / conditional / async) still leak
1135+
- name: False-negative controls (wrong controller / args / conditional / async arrow+ES5) still leak
11361136
run: |
11371137
python frontend/ownts/ownts.py frontend/ownts/examples/EffectLeakControl.tsx \
11381138
-o "$RUNNER_TEMP/leak.facts.json"
11391139
leak=$(python -m ownlang ownir "$RUNNER_TEMP/leak.facts.json" || true)
11401140
echo "$leak"
11411141
# a release-shaped cleanup that does not release THIS resource (wrong
1142-
# controller / mismatched args / conditional return / async effect) must
1143-
# not be over-suppressed — all four controls stay OWN001
1144-
[ "$(echo "$leak" | grep -c 'OWN001')" -eq 4 ] \
1142+
# controller / mismatched args / conditional return / async arrow effect /
1143+
# async ES5 `function` effect) must not be over-suppressed — all five
1144+
# controls stay OWN001
1145+
[ "$(echo "$leak" | grep -c 'OWN001')" -eq 5 ] \
11451146
|| { echo "FAIL: broadened matchers must not over-suppress real leaks"; exit 1; }
11461147
- name: ES5 `function` callbacks parse; capture-mismatch leak caught (real bug shape)
11471148
run: |

‎frontend/ownts/examples/EffectLeakControl.tsx‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,15 @@ export function LeakControl({ enabled }: { enabled: boolean }) {
3636
return () => window.removeEventListener("message", onMessage);
3737
}, []);
3838

39+
// (5) async ES5 effect: same async suppression, but the transpiled
40+
// `async function () { … }` shape — React still ignores the returned
41+
// `function () {}` cleanup, so the listener leaks.
42+
useEffect(async function () {
43+
window.addEventListener("online", onOnline);
44+
return function () {
45+
window.removeEventListener("online", onOnline);
46+
};
47+
}, []);
48+
3949
return <div>leak control</div>;
4050
}

‎frontend/ownts/test_ownts.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@ def main() -> int:
6565

6666
# False-negative controls: a release-shaped cleanup that does NOT release THIS
6767
# resource (wrong AbortController, mismatched unsubscribe args, a conditionally
68-
# returned cleanup over an unconditional acquire) must STILL report the leak —
69-
# the broadened matchers must not over-suppress.
68+
# returned cleanup over an unconditional acquire, an async arrow effect, and an
69+
# async ES5 `function` effect) must STILL report the leak — the broadened
70+
# matchers and async suppression must not over-suppress.
7071
leaks = codes("EffectLeakControl.tsx")
71-
assert leaks == ["OWN001"] * 4, f"EffectLeakControl -> {leaks}"
72+
assert leaks == ["OWN001"] * 5, f"EffectLeakControl -> {leaks}"
7273

7374
# Transpiled-ES5 shape: `function () { … return function () { … } }`. The parser
7475
# handles `function` callbacks + `return function` cleanups — a matched cleanup is

0 commit comments

Comments
 (0)