mirror of
https://github.com/safishamsi/graphify.git
synced 2026-08-27 16:56:36 +00:00
fix(extract): canonicalize source_file to POSIX separators (#2627)
This commit is contained in:
committed by
safishamsi
parent
33283f5356
commit
ce942e144f
+26
-1
@@ -10,7 +10,7 @@ import sys
|
||||
import textwrap
|
||||
from collections import Counter
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from pathlib import Path, PurePath
|
||||
from typing import Any, Callable
|
||||
|
||||
from .cache import load_cached, save_cached
|
||||
@@ -6574,6 +6574,31 @@ def extract(
|
||||
for e in all_edges:
|
||||
e["_origin"] = "ast"
|
||||
|
||||
# Canonicalize source_file to POSIX on every node AND edge (#2625).
|
||||
#
|
||||
# Extractors build source_file from the Path they were handed, so a run
|
||||
# given RELATIVE inputs keeps the native separator on Windows. Only the
|
||||
# relativizing branch of _sf_entry above ever calls as_posix(), so a single
|
||||
# extraction could emit `src\lib\content.ts` and `src/pages/index.astro`
|
||||
# side by side. source_file is compared as a STRING downstream
|
||||
# (build._norm_source_file keying, _derive_prune_root, dedup, and
|
||||
# analyze.find_import_cycles, which matches an edge's source_file against a
|
||||
# node's by equality), so two spellings are two different files — the
|
||||
# fragmentation of #683, and the reason the CLI path (which passes an
|
||||
# explicit root) looked correct while the library entry point did not.
|
||||
#
|
||||
# Safe as a final pass: ids are minted through make_id, which collapses
|
||||
# every non-word character — `\` and `/` alike — to `_`, so canonicalizing
|
||||
# the separator here cannot desync an id from its source_file.
|
||||
#
|
||||
# PurePath is the NATIVE flavour on purpose: on POSIX a backslash is a legal
|
||||
# filename character and must be left alone, so this only rewrites paths on
|
||||
# the platform where `\` is actually a separator.
|
||||
for _item in (*all_nodes, *all_edges):
|
||||
_sf = _item.get("source_file")
|
||||
if _sf and "\\" in str(_sf):
|
||||
_item["source_file"] = PurePath(_sf).as_posix()
|
||||
|
||||
return {
|
||||
"nodes": all_nodes,
|
||||
"edges": all_edges,
|
||||
|
||||
@@ -3328,3 +3328,54 @@ def test_rewire_does_not_bind_supertype_stub_to_function():
|
||||
"source_file": "store.py", "weight": 1.0}]
|
||||
_rewire_unique_stub_nodes(nodes, edges)
|
||||
assert edges[0]["target"] == "BookStore" # inherits stub not bound to function
|
||||
|
||||
|
||||
def test_extract_emits_posix_source_file_for_relative_inputs(tmp_path):
|
||||
r"""source_file must be canonical POSIX on every node AND edge, whatever
|
||||
separator the caller's input paths used.
|
||||
|
||||
Extractors build source_file from the Path handed to them, and only the
|
||||
relativizing branch of extract()'s remap calls as_posix(), so a run given
|
||||
relative inputs used to keep the native separator on Windows — mixing
|
||||
`src\lib\content.ts` and `src/pages/index.astro` in one extraction.
|
||||
source_file is compared as a string downstream (build keying, prune-root
|
||||
derivation, dedup, analyze.find_import_cycles), so two spellings are two
|
||||
different files (#683 / #2625).
|
||||
|
||||
Uses the relative-input form deliberately: passing an explicit ``root``
|
||||
takes the branch that already normalized, and would make this vacuous.
|
||||
"""
|
||||
(tmp_path / "src" / "lib").mkdir(parents=True)
|
||||
(tmp_path / "src" / "pages").mkdir(parents=True)
|
||||
(tmp_path / "src" / "lib" / "content.ts").write_text(
|
||||
"export function getPosts() { return []; }\n", encoding="utf-8"
|
||||
)
|
||||
(tmp_path / "src" / "pages" / "index.astro").write_text(
|
||||
"---\nimport { getPosts } from '../lib/content';\n"
|
||||
"const posts = getPosts();\n---\n<h1>{posts.length}</h1>\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
cwd = os.getcwd()
|
||||
os.chdir(tmp_path)
|
||||
try:
|
||||
result = extract([Path("src/lib/content.ts"), Path("src/pages/index.astro")])
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
|
||||
carriers = [
|
||||
(kind, item.get("source_file"))
|
||||
for kind, items in (("node", result["nodes"]), ("edge", result["edges"]))
|
||||
for item in items
|
||||
if item.get("source_file")
|
||||
]
|
||||
assert carriers, "fixture produced nothing with a source_file; test would be vacuous"
|
||||
|
||||
offenders = [(kind, sf) for kind, sf in carriers if "\\" in sf]
|
||||
assert not offenders, f"native separator survived into source_file: {offenders}"
|
||||
|
||||
# ...and both files are present under one spelling each, so the graph sees
|
||||
# two files rather than four.
|
||||
assert {sf for _, sf in carriers} == {
|
||||
"src/lib/content.ts", "src/pages/index.astro",
|
||||
}
|
||||
|
||||
@@ -1407,8 +1407,11 @@ def test_alias_import_does_not_remap_an_owned_symbol_id(tmp_path, monkeypatch):
|
||||
}
|
||||
target_symbol = _make_id(_file_stem(target), "formatDate")
|
||||
mirror_symbol = _make_id(_file_stem(mirror), "formatDate")
|
||||
assert symbols[str(target)] == target_symbol
|
||||
assert symbols[str(mirror)] == mirror_symbol
|
||||
# as_posix, not str: source_file is canonical POSIX in extract() output
|
||||
# (#2625), while str(Path(...)) is the native spelling and so only matched
|
||||
# on POSIX hosts.
|
||||
assert symbols[target.as_posix()] == target_symbol
|
||||
assert symbols[mirror.as_posix()] == mirror_symbol
|
||||
|
||||
imports = [
|
||||
edge
|
||||
@@ -1418,8 +1421,8 @@ def test_alias_import_does_not_remap_an_owned_symbol_id(tmp_path, monkeypatch):
|
||||
by_source: dict[str, list[str]] = {}
|
||||
for edge in imports:
|
||||
by_source.setdefault(edge["source_file"], []).append(edge["target"])
|
||||
assert by_source[str(button)] == [target_symbol]
|
||||
assert by_source[str(mirror_user)] == [mirror_symbol]
|
||||
assert by_source[button.as_posix()] == [target_symbol]
|
||||
assert by_source[mirror_user.as_posix()] == [mirror_symbol]
|
||||
assert all(edge["source"] in node_ids and edge["target"] in node_ids for edge in imports)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user