mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 15:40:05 +00:00
fix(extract): skip JSX member tags
With a lowercase receiver, <icons.Close /> and <props.Comp /> resolved by the
property name and bound to unrelated local Close() / Comp() functions. None
of the member tags tried produced a correct edge, so only bare identifiers
are handled now.
(cherry picked from commit aa4be46dac)
This commit is contained in:
1 parent
a3559c2541
commit
1990b02b00
2 files changed
+29
-12
No files matched your search
@@ -6459,18 +6459,16 @@ def _extract_generic(
|
||||
# `<Comp />` / `<Comp>` renders Comp; the tag is the `name` field.
|
||||
# Same rule as the JSX transform: a tag starting with a lowercase
|
||||
# letter (`<div>`) is an intrinsic element, not a symbol in scope,
|
||||
# so it must not bind by name to a same-named function. For a
|
||||
# member tag the last segment decides (`<motion.div>` is not a
|
||||
# local `div`). Fragments and namespaced tags (`<svg:rect>`) are
|
||||
# skipped.
|
||||
# so it must not bind by name to a same-named function.
|
||||
# Only bare identifiers are handled. Member tags (`<icons.Close>`,
|
||||
# `<props.Comp>`, `<Ctx.Provider>`) are skipped: with a lowercase
|
||||
# receiver the member path falls back to the bare property name and
|
||||
# binds to an unrelated local function. Fragments and namespaced
|
||||
# tags (`<svg:rect>`) have no identifier name either.
|
||||
func_node = node.child_by_field_name("name")
|
||||
tag = func_node
|
||||
if tag is not None and tag.type == "member_expression":
|
||||
tag = tag.child_by_field_name("property")
|
||||
if (func_node is None
|
||||
or func_node.type not in ("identifier", "member_expression")
|
||||
or tag is None
|
||||
or _read_text(tag, source)[:1].islower()):
|
||||
or func_node.type != "identifier"
|
||||
or _read_text(func_node, source)[:1].islower()):
|
||||
func_node = None
|
||||
if func_node:
|
||||
if func_node.type == "identifier":
|
||||
|
||||
@@ -5,8 +5,9 @@ A JSX element renders its component, but tree-sitter models it as
|
||||
field, not as a `call_expression`. Those node types were never in `call_types`,
|
||||
so a component that is only ever rendered (the normal case in React) had no
|
||||
incoming edge and looked unused. A lowercase bare tag (`<div>`) is an intrinsic
|
||||
DOM element and must not bind to a same-named function; for a member tag the last
|
||||
segment decides (`<motion.div>`).
|
||||
DOM element and must not bind to a same-named function. Member tags
|
||||
(`<icons.Close>`, `<motion.div>`) are not handled: resolving them by the property
|
||||
name binds to unrelated local functions.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -120,3 +121,21 @@ def test_member_tag_does_not_bind_to_local_function_by_last_segment(tmp_path: Pa
|
||||
),
|
||||
})
|
||||
assert ("App()", "Provider()") not in calls
|
||||
|
||||
|
||||
|
||||
def test_member_tag_with_lowercase_receiver_does_not_bind_to_local_function(tmp_path: Path):
|
||||
# `<icons.Close>` / `<props.Comp>` are members, not the local Close() / Comp().
|
||||
calls, _ = _calls(tmp_path, {
|
||||
"icons.tsx": "export function X() { return null; }\n",
|
||||
"app.tsx": (
|
||||
"import * as icons from './icons';\n"
|
||||
"function Close() { return null; }\n"
|
||||
"function Comp() { return null; }\n"
|
||||
"export function App(props: any) {\n"
|
||||
" return <div><icons.Close /><props.Comp /></div>;\n"
|
||||
"}\n"
|
||||
),
|
||||
})
|
||||
assert ("App()", "Close()") not in calls
|
||||
assert ("App()", "Comp()") not in calls
|
||||
Reference in new issue
Block a user