Skip to content

Absolute paths still land in node id + source_file on 0.9.15: out-of-root reference targets and degenerate-symbol id fallback (residual of #1789) #1899

Description

@tparnell

Summary

Follow-up to #1789 (fixed in 0.9.13 for .sln/.csproj file node ids, and in 0.9.14 for virtual solution-folder ids — thanks for both). On 0.9.15 two code paths still emit machine-specific absolute paths into graph.json:

  • Variant A — out-of-root reference targets. A node created for a file outside the scan root (e.g. the target of a <ProjectReference Include="..\Core\Core.csproj" /> when only WebApi/ is scanned) gets an absolute source_file AND an absolute-path-derived id. The relativization added for Node IDs for file / directory / project nodes embed the absolute scan path (leaks local username, makes committed graph.json non-portable / non-idempotent) #1789 appears to rely on relative_to(root), which can't apply to out-of-root paths, so both fields fall back to absolute.
  • Variant B — degenerate symbol nodes from in-root files. Some symbol nodes get a correctly relativized source_file but an absolute-path-derived id. Two triggers observed: a JSON file using the tsconfig "//" comment-key convention (node label //), and a minified vendor bundle (node label $() from moment.min.js). Symbol-id derivation seems to fall back to _make_id(str(absolute_path)) when no usable symbol/module name is available.

Consequences are the same as #1789: the committed graph.json leaks the local username / checkout path, ids differ per machine and per checkout location, so graphify update from another clone re-creates these nodes (diff churn, non-idempotent versioned graphs).

Environment

Minimal repro (4 files, no .NET SDK, no API key)

#!/usr/bin/env bash
set -euo pipefail
R="$(mktemp -d)"
mkdir -p "$R/Core" "$R/WebApi"

cat > "$R/Core/Core.csproj" <<'EOF'
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup>
</Project>
EOF

cat > "$R/WebApi/WebApi.csproj" <<'EOF'
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Core\Core.csproj" />
  </ItemGroup>
</Project>
EOF

cat > "$R/WebApi/Foo.cs" <<'EOF'
namespace WebApi { public class Foo { public int Bar() => 1; } }
EOF

cat > "$R/WebApi/tsconfig.dev.json" <<'EOF'
{
    "//": "dev-only override of the parent tsconfig",
    "extends": "../tsconfig.json",
    "compilerOptions": { "outDir": "../dist" }
}
EOF

GRAPHIFY_OUT="$R/out" graphify "$R/WebApi" >/dev/null 2>&1

python3 - "$R/out/graph.json" <<'EOF'
import json, sys
g = json.load(open(sys.argv[1]))
for n in g["nodes"]:
    sf = str(n.get("source_file") or "")
    if sf.startswith("/") or n["id"].startswith(("tmp_","private_","var_","users_","home_")):
        print(json.dumps({k: n.get(k) for k in ("label","source_file","id")}, indent=2))
EOF

Observed output (verbatim, 0.9.15)

{
  "label": "Core.csproj",
  "source_file": "/private/var/folders/sv/.../T/tmp.eSYW7WLP1S/Core/Core.csproj",
  "id": "private_var_folders_sv_..._t_tmp_esyw7wlp1s_core_core_csproj"
}
{
  "label": "//",
  "source_file": "tsconfig.dev.json",
  "id": "private_var_folders_sv_..._t_tmp_esyw7wlp1s_webapi_tsconfig_dev"
}

(Only elided the middles of the long tmp path with ... — everything else is exactly as emitted. Note Core.csproj shows Variant A — both fields absolute — while the // node shows Variant B — relative source_file, absolute id.)

Expected

  • Node ids derived from scan-root-relative paths in all code paths.
  • For out-of-root targets, something machine-independent: relativize with .. segments against the scan root, key off the reference text as written (..\Core\Core.csproj), or mark the node external and key it by basename — anything that doesn't embed the checkout location in id/source_file.

Real-world impact

Hit this rolling graphify out on a large private monorepo (9 scan scopes, ~300k nodes, graphs committed to git): 8 of 9 scope graphs contained 88 machine-specific strings — every scope with a cross-project .csproj reference (the norm in .NET monorepos) leaked the absolute checkout path via Variant A, and vendored/minified bundles plus tsconfig-style JSON hit Variant B.

Current workaround (in case it helps others): post-build textual normalization — strip the <abs-checkout>/ prefix from source_file values and the derived slug prefix from ids; a plain find/replace also fixes the edge references, and node/edge counts stay intact.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions