mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-16 00:49:37 +00:00
Emit one node per exported destructured name
export const { auth, handlers, signIn, signOut } = NextAuth(config)
is NextAuth version five's own documented boilerplate, and
createNavigation from next intl follows the exact same shape. The
extractor collapsed the whole pattern into one combined node, id
joining every destructured name and label the literal pattern
syntax, so a single name import elsewhere could never find a node
named just auth and the edge, and any call resolved through it,
dangled. Each shorthand and renamed property in the pattern now
becomes its own node sharing the statement's line, exactly what the
export would have produced had it been written as N separate
statements. A renamed property exports under its key, since that is
what an importer binds by, never its local alias. A rest element or
a default valued entry does not correspond to one discrete exported
name and is skipped rather than guessed at.
The split only applies to an actually exported statement. An
unexported destructure of the identical shape, const doWork equals
require of lib being the common one, is a local import binding, not
a module export, and giving it a bare named node in the importing
file would collide with the real definition it only imports, turning
a clean resolution into a false ambiguous name. Caught this against
the existing suite before it ever reached a commit. Fixes #2604's
class 2.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit b1614da58a)
This commit is contained in:
1 parent
13fef586d3
commit
fd9b1cdb40
1 file changed
+51
-1
@@ -2580,7 +2580,57 @@ def _js_extra_walk(node, source: bytes, file_nid: str, stem: str, str_path: str,
|
||||
):
|
||||
# Simple exported identifiers are part of the module API
|
||||
# regardless of initializer shape. Keep other scalar noise suppressed.
|
||||
if name_node:
|
||||
if name_node is not None and name_node.type == "object_pattern" and is_exported:
|
||||
# #2604 Class 2: `export const { auth, handlers } =
|
||||
# NextAuth(config)` (NextAuth v5's own documented
|
||||
# boilerplate; next-intl's createNavigation() is the
|
||||
# same shape) used to fall through to the generic
|
||||
# branch below, which reads the WHOLE pattern as one
|
||||
# combined node -- id `..._auth_handlers`, label
|
||||
# "{ auth, handlers }". A single-name import
|
||||
# (`import { auth } from './auth'`) looks for a node
|
||||
# named `auth` alone, which never exists, so the
|
||||
# import edge dangles. Emit one node per exported
|
||||
# name instead, all sharing this statement's line, so
|
||||
# each import resolves to its own node the way it
|
||||
# would if the export had been written as N separate
|
||||
# `export const auth = ...` statements.
|
||||
#
|
||||
# Gated on `is_exported`: an UN-exported destructure
|
||||
# of the same shape (`const { doWork } = require(...)`)
|
||||
# is a LOCAL import binding, not a module export. The
|
||||
# old combined-node label ("{ doWork }") never matched
|
||||
# the bare-name index cross-file resolution builds, so
|
||||
# it was silently inert; giving it a bare-name node
|
||||
# here (id `caller_dowork`, label "doWork") would make
|
||||
# it collide with the real `doWork` definition it is
|
||||
# ONLY importing, turning a clean single-candidate
|
||||
# resolution into a false "ambiguous name" and losing
|
||||
# the real calls edge (caught by
|
||||
# test_cross_file_call_promoted_to_extracted_with_import_evidence).
|
||||
line = child.start_point[0] + 1
|
||||
for prop in name_node.named_children:
|
||||
if prop.type == "shorthand_property_identifier_pattern":
|
||||
export_name = _read_text(prop, source)
|
||||
elif prop.type == "pair_pattern":
|
||||
# `handlers: h` exports as `handlers` (the
|
||||
# key) -- `import { handlers }` binds by the
|
||||
# property name, never by the local alias.
|
||||
key_node = prop.child_by_field_name("key")
|
||||
export_name = _read_text(key_node, source) if key_node else None
|
||||
else:
|
||||
# `...rest` and default-valued patterns
|
||||
# (`{ auth = fallback }`) don't correspond to
|
||||
# one discrete exported name; skip rather
|
||||
# than guess.
|
||||
export_name = None
|
||||
if not export_name or not normalize_id(export_name):
|
||||
continue
|
||||
prop_nid = _make_id(stem, export_name)
|
||||
add_node_fn(prop_nid, export_name, line)
|
||||
add_edge_fn(file_nid, prop_nid, "contains", line)
|
||||
const_found = True
|
||||
elif name_node:
|
||||
const_name = _read_text(name_node, source)
|
||||
line = child.start_point[0] + 1
|
||||
const_nid = _make_id(stem, const_name)
|
||||
|
||||
Reference in new issue
Block a user