Skip to content

Commit 0a111de

Browse files
committed
proper handling of false positives
1 parent 312be68 commit 0a111de

File tree

2 files changed

+50
-15
lines changed

2 files changed

+50
-15
lines changed

src/reactpy/core/events.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
from __future__ import annotations
22

33
import asyncio
4-
import contextlib
5-
import inspect
4+
import dis
65
from collections.abc import Sequence
76
from typing import Any, Callable, Literal, cast, overload
87

@@ -116,23 +115,37 @@ def __init__(
116115
target: str | None = None,
117116
) -> None:
118117
self.function = to_event_handler_function(function, positional_args=False)
119-
120-
if not (stop_propagation and prevent_default):
121-
with contextlib.suppress(Exception):
122-
func_to_inspect = cast(Any, function)
123-
while hasattr(func_to_inspect, "__wrapped__"):
124-
func_to_inspect = func_to_inspect.__wrapped__
125-
126-
source = inspect.getsource(func_to_inspect)
127-
if not stop_propagation and ".stopPropagation()" in source:
128-
stop_propagation = True
129-
if not prevent_default and ".preventDefault()" in source:
130-
prevent_default = True
131-
132118
self.prevent_default = prevent_default
133119
self.stop_propagation = stop_propagation
134120
self.target = target
135121

122+
# Check if our `preventDefault` or `stopPropagation` methods were called
123+
# by inspecting the function's bytecode
124+
func_to_inspect = cast(Any, function)
125+
while hasattr(func_to_inspect, "__wrapped__"):
126+
func_to_inspect = func_to_inspect.__wrapped__
127+
128+
code = func_to_inspect.__code__
129+
if code.co_argcount > 0:
130+
event_arg_name = code.co_varnames[0]
131+
last_was_event = False
132+
133+
for instr in dis.get_instructions(func_to_inspect):
134+
if instr.opname == "LOAD_FAST" and instr.argval == event_arg_name:
135+
last_was_event = True
136+
continue
137+
138+
if last_was_event and instr.opname in (
139+
"LOAD_METHOD",
140+
"LOAD_ATTR",
141+
):
142+
if instr.argval == "preventDefault":
143+
self.prevent_default = True
144+
elif instr.argval == "stopPropagation":
145+
self.stop_propagation = True
146+
147+
last_was_event = False
148+
136149
__hash__ = None # type: ignore
137150

138151
def __eq__(self, other: object) -> bool:

tests/test_core/test_events.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,25 @@ def test_event_export():
384384
from reactpy import Event
385385

386386
assert Event is not None
387+
388+
389+
def test_detect_false_positive():
390+
def handler(event: Event):
391+
# This should not trigger detection
392+
other = Event()
393+
other.preventDefault()
394+
other.stopPropagation()
395+
396+
eh = EventHandler(handler)
397+
assert eh.prevent_default is False
398+
assert eh.stop_propagation is False
399+
400+
401+
def test_detect_renamed_argument():
402+
def handler(e: Event):
403+
e.preventDefault()
404+
e.stopPropagation()
405+
406+
eh = EventHandler(handler)
407+
assert eh.prevent_default is True
408+
assert eh.stop_propagation is True

0 commit comments

Comments
 (0)