graphitect 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graphify/__init__.py +30 -0
- graphify/__main__.py +757 -0
- graphify/_minhash.py +107 -0
- graphify/affected.py +318 -0
- graphify/always_on/agents-md.md +12 -0
- graphify/always_on/antigravity-rules.md +14 -0
- graphify/always_on/claude-md.md +9 -0
- graphify/always_on/gemini-md.md +9 -0
- graphify/always_on/kiro-steering.md +5 -0
- graphify/always_on/vscode-instructions.md +17 -0
- graphify/analyze.py +769 -0
- graphify/benchmark.py +152 -0
- graphify/build.py +2300 -0
- graphify/cache.py +1746 -0
- graphify/callflow_html.py +2051 -0
- graphify/cargo_introspect.py +109 -0
- graphify/cli.py +4745 -0
- graphify/cluster.py +409 -0
- graphify/command-kilo.md +15 -0
- graphify/cross_repo_calls.py +216 -0
- graphify/cross_repo_types.py +75 -0
- graphify/csharp_dispatch.py +154 -0
- graphify/dedup.py +1213 -0
- graphify/detect.py +2566 -0
- graphify/diagnostics.py +406 -0
- graphify/export.py +1349 -0
- graphify/exporters/__init__.py +1 -0
- graphify/exporters/base.py +14 -0
- graphify/exporters/graphdb.py +173 -0
- graphify/exporters/html.py +637 -0
- graphify/extract.py +7856 -0
- graphify/extractors/MIGRATION.md +107 -0
- graphify/extractors/__init__.py +66 -0
- graphify/extractors/apex.py +215 -0
- graphify/extractors/base.py +85 -0
- graphify/extractors/bash.py +579 -0
- graphify/extractors/blade.py +53 -0
- graphify/extractors/commonlisp.py +540 -0
- graphify/extractors/csharp.py +448 -0
- graphify/extractors/dart.py +564 -0
- graphify/extractors/dm.py +494 -0
- graphify/extractors/elixir.py +241 -0
- graphify/extractors/engine.py +6509 -0
- graphify/extractors/fortran.py +311 -0
- graphify/extractors/go.py +527 -0
- graphify/extractors/json_config.py +240 -0
- graphify/extractors/julia.py +289 -0
- graphify/extractors/markdown.py +408 -0
- graphify/extractors/models.py +131 -0
- graphify/extractors/objc.py +566 -0
- graphify/extractors/ocaml.py +289 -0
- graphify/extractors/pascal.py +688 -0
- graphify/extractors/pascal_forms.py +196 -0
- graphify/extractors/powershell.py +522 -0
- graphify/extractors/razor.py +192 -0
- graphify/extractors/resolution.py +3584 -0
- graphify/extractors/robot.py +296 -0
- graphify/extractors/rust.py +470 -0
- graphify/extractors/sln.py +92 -0
- graphify/extractors/sql.py +720 -0
- graphify/extractors/terraform.py +181 -0
- graphify/extractors/verilog.py +329 -0
- graphify/extractors/zig.py +181 -0
- graphify/file_slice.py +246 -0
- graphify/global_graph.py +194 -0
- graphify/google_workspace.py +237 -0
- graphify/hooks.py +933 -0
- graphify/ids.py +93 -0
- graphify/ingest.py +358 -0
- graphify/install.py +2366 -0
- graphify/llm.py +3544 -0
- graphify/manifest.py +4 -0
- graphify/manifest_ingest.py +311 -0
- graphify/mcp_ingest.py +386 -0
- graphify/multigraph_compat.py +212 -0
- graphify/pascal_resolution.py +129 -0
- graphify/paths.py +436 -0
- graphify/pg_introspect.py +165 -0
- graphify/prs.py +770 -0
- graphify/querylog.py +80 -0
- graphify/reflect.py +882 -0
- graphify/report.py +346 -0
- graphify/resolver_registry.py +85 -0
- graphify/ruby_resolution.py +242 -0
- graphify/scip_ingest.py +363 -0
- graphify/security.py +460 -0
- graphify/semantic_cleanup.py +336 -0
- graphify/serve.py +2608 -0
- graphify/skill-agents.md +710 -0
- graphify/skill-aider.md +1283 -0
- graphify/skill-amp.md +710 -0
- graphify/skill-claw.md +713 -0
- graphify/skill-codex.md +710 -0
- graphify/skill-copilot.md +713 -0
- graphify/skill-devin.md +1410 -0
- graphify/skill-droid.md +710 -0
- graphify/skill-kilo.md +722 -0
- graphify/skill-kiro.md +713 -0
- graphify/skill-opencode.md +705 -0
- graphify/skill-pi.md +713 -0
- graphify/skill-trae.md +711 -0
- graphify/skill-vscode.md +709 -0
- graphify/skill-windows.md +755 -0
- graphify/skill.md +713 -0
- graphify/skills/agents/references/add-watch.md +56 -0
- graphify/skills/agents/references/exports.md +87 -0
- graphify/skills/agents/references/extraction-spec.md +70 -0
- graphify/skills/agents/references/github-and-merge.md +46 -0
- graphify/skills/agents/references/hooks.md +33 -0
- graphify/skills/agents/references/query.md +311 -0
- graphify/skills/agents/references/transcribe.md +52 -0
- graphify/skills/agents/references/update.md +210 -0
- graphify/skills/amp/references/add-watch.md +56 -0
- graphify/skills/amp/references/exports.md +87 -0
- graphify/skills/amp/references/extraction-spec.md +70 -0
- graphify/skills/amp/references/github-and-merge.md +46 -0
- graphify/skills/amp/references/hooks.md +33 -0
- graphify/skills/amp/references/query.md +311 -0
- graphify/skills/amp/references/transcribe.md +52 -0
- graphify/skills/amp/references/update.md +210 -0
- graphify/skills/claude/references/add-watch.md +56 -0
- graphify/skills/claude/references/exports.md +87 -0
- graphify/skills/claude/references/extraction-spec.md +70 -0
- graphify/skills/claude/references/github-and-merge.md +46 -0
- graphify/skills/claude/references/hooks.md +33 -0
- graphify/skills/claude/references/query.md +311 -0
- graphify/skills/claude/references/transcribe.md +52 -0
- graphify/skills/claude/references/update.md +210 -0
- graphify/skills/claw/references/add-watch.md +56 -0
- graphify/skills/claw/references/exports.md +87 -0
- graphify/skills/claw/references/extraction-spec.md +31 -0
- graphify/skills/claw/references/github-and-merge.md +46 -0
- graphify/skills/claw/references/hooks.md +33 -0
- graphify/skills/claw/references/query.md +311 -0
- graphify/skills/claw/references/transcribe.md +52 -0
- graphify/skills/claw/references/update.md +210 -0
- graphify/skills/codex/references/add-watch.md +56 -0
- graphify/skills/codex/references/exports.md +87 -0
- graphify/skills/codex/references/extraction-spec.md +31 -0
- graphify/skills/codex/references/github-and-merge.md +46 -0
- graphify/skills/codex/references/hooks.md +33 -0
- graphify/skills/codex/references/query.md +311 -0
- graphify/skills/codex/references/transcribe.md +52 -0
- graphify/skills/codex/references/update.md +210 -0
- graphify/skills/copilot/references/add-watch.md +56 -0
- graphify/skills/copilot/references/exports.md +87 -0
- graphify/skills/copilot/references/extraction-spec.md +70 -0
- graphify/skills/copilot/references/github-and-merge.md +46 -0
- graphify/skills/copilot/references/hooks.md +33 -0
- graphify/skills/copilot/references/query.md +311 -0
- graphify/skills/copilot/references/transcribe.md +52 -0
- graphify/skills/copilot/references/update.md +210 -0
- graphify/skills/droid/references/add-watch.md +56 -0
- graphify/skills/droid/references/exports.md +87 -0
- graphify/skills/droid/references/extraction-spec.md +70 -0
- graphify/skills/droid/references/github-and-merge.md +46 -0
- graphify/skills/droid/references/hooks.md +33 -0
- graphify/skills/droid/references/query.md +311 -0
- graphify/skills/droid/references/transcribe.md +52 -0
- graphify/skills/droid/references/update.md +210 -0
- graphify/skills/kilo/references/add-watch.md +56 -0
- graphify/skills/kilo/references/exports.md +87 -0
- graphify/skills/kilo/references/extraction-spec.md +70 -0
- graphify/skills/kilo/references/github-and-merge.md +46 -0
- graphify/skills/kilo/references/hooks.md +33 -0
- graphify/skills/kilo/references/query.md +311 -0
- graphify/skills/kilo/references/transcribe.md +52 -0
- graphify/skills/kilo/references/update.md +210 -0
- graphify/skills/kiro/references/add-watch.md +56 -0
- graphify/skills/kiro/references/exports.md +87 -0
- graphify/skills/kiro/references/extraction-spec.md +31 -0
- graphify/skills/kiro/references/github-and-merge.md +46 -0
- graphify/skills/kiro/references/hooks.md +33 -0
- graphify/skills/kiro/references/query.md +311 -0
- graphify/skills/kiro/references/transcribe.md +52 -0
- graphify/skills/kiro/references/update.md +210 -0
- graphify/skills/opencode/references/add-watch.md +56 -0
- graphify/skills/opencode/references/exports.md +87 -0
- graphify/skills/opencode/references/extraction-spec.md +70 -0
- graphify/skills/opencode/references/github-and-merge.md +46 -0
- graphify/skills/opencode/references/hooks.md +33 -0
- graphify/skills/opencode/references/query.md +311 -0
- graphify/skills/opencode/references/transcribe.md +52 -0
- graphify/skills/opencode/references/update.md +210 -0
- graphify/skills/pi/references/add-watch.md +56 -0
- graphify/skills/pi/references/exports.md +87 -0
- graphify/skills/pi/references/extraction-spec.md +31 -0
- graphify/skills/pi/references/github-and-merge.md +46 -0
- graphify/skills/pi/references/hooks.md +33 -0
- graphify/skills/pi/references/query.md +311 -0
- graphify/skills/pi/references/transcribe.md +52 -0
- graphify/skills/pi/references/update.md +210 -0
- graphify/skills/trae/references/add-watch.md +56 -0
- graphify/skills/trae/references/exports.md +87 -0
- graphify/skills/trae/references/extraction-spec.md +70 -0
- graphify/skills/trae/references/github-and-merge.md +46 -0
- graphify/skills/trae/references/hooks.md +35 -0
- graphify/skills/trae/references/query.md +311 -0
- graphify/skills/trae/references/transcribe.md +52 -0
- graphify/skills/trae/references/update.md +210 -0
- graphify/skills/vscode/references/add-watch.md +56 -0
- graphify/skills/vscode/references/exports.md +87 -0
- graphify/skills/vscode/references/extraction-spec.md +70 -0
- graphify/skills/vscode/references/github-and-merge.md +46 -0
- graphify/skills/vscode/references/hooks.md +33 -0
- graphify/skills/vscode/references/query.md +311 -0
- graphify/skills/vscode/references/transcribe.md +52 -0
- graphify/skills/vscode/references/update.md +210 -0
- graphify/skills/windows/references/add-watch.md +56 -0
- graphify/skills/windows/references/exports.md +87 -0
- graphify/skills/windows/references/extraction-spec.md +70 -0
- graphify/skills/windows/references/github-and-merge.md +46 -0
- graphify/skills/windows/references/hooks.md +33 -0
- graphify/skills/windows/references/query.md +311 -0
- graphify/skills/windows/references/transcribe.md +52 -0
- graphify/skills/windows/references/update.md +210 -0
- graphify/symbol_resolution.py +556 -0
- graphify/transcribe.py +186 -0
- graphify/tree_html.py +603 -0
- graphify/validate.py +95 -0
- graphify/watch.py +2280 -0
- graphify/wiki.py +405 -0
- graphitect/__init__.py +28 -0
- graphitect/__main__.py +4 -0
- graphitect/_vendor/__init__.py +2 -0
- graphitect/_vendor/archify/LICENSE +22 -0
- graphitect/_vendor/archify/SKILL.md +137 -0
- graphitect/_vendor/archify/THIRD_PARTY_NOTICES.md +69 -0
- graphitect/_vendor/archify/assets/JetBrainsMono-OFL.txt +93 -0
- graphitect/_vendor/archify/assets/template.html +14935 -0
- graphitect/_vendor/archify/bin/archify.mjs +2091 -0
- graphitect/_vendor/archify/bin/open-artifact.mjs +86 -0
- graphitect/_vendor/archify/bin/preview.mjs +653 -0
- graphitect/_vendor/archify/bin/visual-check.mjs +829 -0
- graphitect/_vendor/archify/brand-marks/README.md +31 -0
- graphitect/_vendor/archify/brand-marks/catalog.json +131 -0
- graphitect/_vendor/archify/delta/architecture-delta.mjs +1221 -0
- graphitect/_vendor/archify/examples/agent-run.lifecycle.json +60 -0
- graphitect/_vendor/archify/examples/agent-tool-call.workflow.json +94 -0
- graphitect/_vendor/archify/examples/async-job-roundtrip.sequence.json +61 -0
- graphitect/_vendor/archify/examples/brand-aware-delivery.architecture.json +47 -0
- graphitect/_vendor/archify/examples/cache-miss-request.sequence.json +82 -0
- graphitect/_vendor/archify/examples/checkout-platform.base.architecture.json +31 -0
- graphitect/_vendor/archify/examples/checkout-platform.head.architecture.json +31 -0
- graphitect/_vendor/archify/examples/dataflow-product-analytics.html +15045 -0
- graphitect/_vendor/archify/examples/deployment-release.lifecycle.json +49 -0
- graphitect/_vendor/archify/examples/event-stream.dataflow.json +57 -0
- graphitect/_vendor/archify/examples/incident-response.workflow.json +64 -0
- graphitect/_vendor/archify/examples/lifecycle-agent-run.html +14980 -0
- graphitect/_vendor/archify/examples/product-analytics.dataflow.json +76 -0
- graphitect/_vendor/archify/examples/production-deployment.architecture.json +71 -0
- graphitect/_vendor/archify/examples/release-delivery.workflow.json +62 -0
- graphitect/_vendor/archify/examples/sequence-cache-miss-request.html +15060 -0
- graphitect/_vendor/archify/examples/web-app-rendered.html +15009 -0
- graphitect/_vendor/archify/examples/web-app.architecture.json +46 -0
- graphitect/_vendor/archify/examples/workflow-agent-tool-call-rendered.html +15051 -0
- graphitect/_vendor/archify/migrations/workflow-v2.mjs +279 -0
- graphitect/_vendor/archify/package-lock.json +149 -0
- graphitect/_vendor/archify/package.json +39 -0
- graphitect/_vendor/archify/recipes/scenarios.mjs +391 -0
- graphitect/_vendor/archify/references/authoring-contract.md +243 -0
- graphitect/_vendor/archify/references/brand-marks.md +65 -0
- graphitect/_vendor/archify/references/delivery-contract.md +120 -0
- graphitect/_vendor/archify/references/viewer-runtime.md +45 -0
- graphitect/_vendor/archify/renderers/architecture/grid.mjs +62 -0
- graphitect/_vendor/archify/renderers/architecture/render-architecture.mjs +1078 -0
- graphitect/_vendor/archify/renderers/dataflow/README.md +104 -0
- graphitect/_vendor/archify/renderers/dataflow/render-dataflow.mjs +483 -0
- graphitect/_vendor/archify/renderers/lifecycle/README.md +115 -0
- graphitect/_vendor/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
- graphitect/_vendor/archify/renderers/sequence/README.md +114 -0
- graphitect/_vendor/archify/renderers/sequence/render-sequence.mjs +464 -0
- graphitect/_vendor/archify/renderers/shared/brand-marks.mjs +563 -0
- graphitect/_vendor/archify/renderers/shared/cli.mjs +218 -0
- graphitect/_vendor/archify/renderers/shared/desktop-readability.mjs +26 -0
- graphitect/_vendor/archify/renderers/shared/diagnostics.mjs +127 -0
- graphitect/_vendor/archify/renderers/shared/engineering-profiles.mjs +157 -0
- graphitect/_vendor/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
- graphitect/_vendor/archify/renderers/shared/generated-validators.mjs +13 -0
- graphitect/_vendor/archify/renderers/shared/geometry.mjs +1423 -0
- graphitect/_vendor/archify/renderers/shared/i18n.mjs +595 -0
- graphitect/_vendor/archify/renderers/shared/layout-report.mjs +40 -0
- graphitect/_vendor/archify/renderers/shared/legend.mjs +217 -0
- graphitect/_vendor/archify/renderers/shared/output-path.mjs +340 -0
- graphitect/_vendor/archify/renderers/shared/repository-evidence.mjs +238 -0
- graphitect/_vendor/archify/renderers/shared/repository-location.mjs +58 -0
- graphitect/_vendor/archify/renderers/shared/text-fit.mjs +49 -0
- graphitect/_vendor/archify/renderers/shared/utils.mjs +232 -0
- graphitect/_vendor/archify/renderers/shared/validator.mjs +86 -0
- graphitect/_vendor/archify/renderers/workflow/README.md +223 -0
- graphitect/_vendor/archify/renderers/workflow/render-workflow.mjs +35 -0
- graphitect/_vendor/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- graphitect/_vendor/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- graphitect/_vendor/archify/schemas/README.md +211 -0
- graphitect/_vendor/archify/schemas/architecture.schema.json +178 -0
- graphitect/_vendor/archify/schemas/common.schema.json +115 -0
- graphitect/_vendor/archify/schemas/dataflow.schema.json +243 -0
- graphitect/_vendor/archify/schemas/lifecycle.schema.json +266 -0
- graphitect/_vendor/archify/schemas/sequence.schema.json +223 -0
- graphitect/_vendor/archify/schemas/workflow.schema.json +428 -0
- graphitect/_vendor/archify/scripts/check-render-output.mjs +836 -0
- graphitect/_vendor/archify/scripts/check-update.mjs +1667 -0
- graphitect/_vendor/archify/scripts/generate-brand-marks.mjs +141 -0
- graphitect/_vendor/archify/scripts/generate-validators.mjs +66 -0
- graphitect/_vendor/archify/scripts/render-examples.mjs +26 -0
- graphitect/_vendor/archify/scripts/update-contract.mjs +182 -0
- graphitect/_vendor/archify/skill-release.json +10 -0
- graphitect/cli.py +981 -0
- graphitect/deliver/__init__.py +5 -0
- graphitect/deliver/archify_adapter.py +1877 -0
- graphitect/deliver/archify_ir.py +160 -0
- graphitect/deliver/archify_repair.py +135 -0
- graphitect/deliver/doc_compiler.py +916 -0
- graphitect/ground/__init__.py +5 -0
- graphitect/ground/describe_source.py +27 -0
- graphitect/ground/fullread_source.py +56 -0
- graphitect/ground/graphify_source.py +107 -0
- graphitect/models.py +118 -0
- graphitect/skill/SKILL.md +80 -0
- graphitect/skill/agents/openai.yaml +4 -0
- graphitect/synthesize/__init__.py +5 -0
- graphitect/synthesize/engine.py +281 -0
- graphitect/synthesize/llm_backend.py +331 -0
- graphitect/synthesize/questions.py +139 -0
- graphitect/synthesize/rubric.py +104 -0
- graphitect-0.2.0.dist-info/METADATA +284 -0
- graphitect-0.2.0.dist-info/RECORD +336 -0
- graphitect-0.2.0.dist-info/WHEEL +5 -0
- graphitect-0.2.0.dist-info/entry_points.txt +2 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE +21 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-ARCHIFY-MIT +22 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-APACHE-2.0 +202 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-MIT +21 -0
- graphitect-0.2.0.dist-info/licenses/NOTICE-ARCHIFY-THIRD-PARTY.md +69 -0
- graphitect-0.2.0.dist-info/licenses/NOTICE-GRAPHIFY +8 -0
- graphitect-0.2.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"""C# cross-file resolution.
|
|
2
|
+
|
|
3
|
+
The config-driven C# *extractor* (``extract_csharp`` → ``_extract_generic``)
|
|
4
|
+
still lives in ``graphify/extract.py``; per ``extractors/MIGRATION.md`` the
|
|
5
|
+
config-driven languages cannot be ported one-by-one until the shared
|
|
6
|
+
``_extract_generic`` core moves as its own coordinated batch. This module is
|
|
7
|
+
the C# home for the parts that *are* cleanly separable — today, the cross-file
|
|
8
|
+
type-reference resolver below — and is where ``extract_csharp`` will land when
|
|
9
|
+
the core migration happens.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import html
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from graphify.extractors.base import _make_id
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _build_csharp_type_def_index(all_nodes: list[dict]) -> dict[tuple[str, str], str]:
|
|
20
|
+
"""Return deterministic ``(namespace, name) -> node_id`` C# type definitions."""
|
|
21
|
+
candidates: dict[tuple[str, str], list[dict]] = {}
|
|
22
|
+
for node in all_nodes:
|
|
23
|
+
if node.get("type") == "namespace":
|
|
24
|
+
continue
|
|
25
|
+
metadata = node.get("metadata") or {}
|
|
26
|
+
if not isinstance(metadata, dict):
|
|
27
|
+
metadata = {}
|
|
28
|
+
if metadata.get("is_nested_type"):
|
|
29
|
+
continue
|
|
30
|
+
nid = node.get("id")
|
|
31
|
+
label = node.get("label")
|
|
32
|
+
if not (isinstance(nid, str) and nid and isinstance(label, str) and label):
|
|
33
|
+
continue
|
|
34
|
+
source_file = node.get("source_file")
|
|
35
|
+
if (
|
|
36
|
+
not isinstance(source_file, str)
|
|
37
|
+
or not source_file.endswith(".cs")
|
|
38
|
+
or node.get("file_type") != "code"
|
|
39
|
+
):
|
|
40
|
+
continue
|
|
41
|
+
if label.endswith(")") or label.startswith(".") or "." in label:
|
|
42
|
+
continue
|
|
43
|
+
namespace = metadata.get("namespace", "")
|
|
44
|
+
if not isinstance(namespace, str):
|
|
45
|
+
namespace = ""
|
|
46
|
+
candidates.setdefault((namespace, label), []).append(node)
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
key: sorted(
|
|
50
|
+
nodes,
|
|
51
|
+
key=lambda node: (
|
|
52
|
+
str(node.get("source_file") or ""),
|
|
53
|
+
str(node.get("source_location") or ""),
|
|
54
|
+
str(node.get("id") or ""),
|
|
55
|
+
),
|
|
56
|
+
)[0]["id"]
|
|
57
|
+
for key, nodes in candidates.items()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _strip_trailing_csharp_generic_args(target_fqn: str) -> str:
|
|
62
|
+
target_fqn = target_fqn.strip()
|
|
63
|
+
if not target_fqn.endswith(">"):
|
|
64
|
+
return target_fqn
|
|
65
|
+
depth = 0
|
|
66
|
+
for index in range(len(target_fqn) - 1, -1, -1):
|
|
67
|
+
char = target_fqn[index]
|
|
68
|
+
if char == ">":
|
|
69
|
+
depth += 1
|
|
70
|
+
elif char == "<":
|
|
71
|
+
depth -= 1
|
|
72
|
+
if depth == 0:
|
|
73
|
+
return target_fqn[:index].strip()
|
|
74
|
+
return target_fqn
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _resolve_cross_file_csharp_imports(
|
|
78
|
+
per_file: list[dict],
|
|
79
|
+
paths: list[Path],
|
|
80
|
+
all_nodes: list[dict],
|
|
81
|
+
all_edges: list[dict],
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Re-point resolvable C# ``using`` import edges to canonical internal nodes.
|
|
84
|
+
|
|
85
|
+
Namespace imports resolve only to canonical C# namespace nodes. Alias imports
|
|
86
|
+
resolve only when the alias target's prefix is a known canonical namespace and
|
|
87
|
+
the simple type name exists in the shared C# type-definition index. ``using
|
|
88
|
+
static`` and nested type aliases remain deliberate gaps because they need
|
|
89
|
+
member/nested-type modeling beyond this import pass.
|
|
90
|
+
"""
|
|
91
|
+
_ = (per_file, paths)
|
|
92
|
+
namespace_id_by_label: dict[str, str] = {}
|
|
93
|
+
for node in sorted(
|
|
94
|
+
all_nodes,
|
|
95
|
+
key=lambda node: (
|
|
96
|
+
str(node.get("source_file") or ""),
|
|
97
|
+
str(node.get("source_location") or ""),
|
|
98
|
+
str(node.get("id") or ""),
|
|
99
|
+
),
|
|
100
|
+
):
|
|
101
|
+
if node.get("type") != "namespace":
|
|
102
|
+
continue
|
|
103
|
+
label = node.get("label")
|
|
104
|
+
nid = node.get("id")
|
|
105
|
+
if isinstance(label, str) and label and isinstance(nid, str) and nid:
|
|
106
|
+
namespace_id_by_label.setdefault(label, nid)
|
|
107
|
+
|
|
108
|
+
type_def_index = _build_csharp_type_def_index(all_nodes)
|
|
109
|
+
if not namespace_id_by_label and not type_def_index:
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
repointed_from: set[str] = set()
|
|
113
|
+
for edge in all_edges:
|
|
114
|
+
if edge.get("relation") != "imports":
|
|
115
|
+
continue
|
|
116
|
+
metadata = edge.get("metadata") or {}
|
|
117
|
+
if not isinstance(metadata, dict):
|
|
118
|
+
continue
|
|
119
|
+
using_kind = metadata.get("using_kind")
|
|
120
|
+
target_fqn = metadata.get("target_fqn")
|
|
121
|
+
if not using_kind or not isinstance(target_fqn, str) or not target_fqn:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
resolved = None
|
|
125
|
+
if using_kind == "namespace":
|
|
126
|
+
resolved = namespace_id_by_label.get(target_fqn)
|
|
127
|
+
elif using_kind == "alias":
|
|
128
|
+
base_fqn = _strip_trailing_csharp_generic_args(html.unescape(target_fqn))
|
|
129
|
+
prefix, sep, name = base_fqn.rpartition(".")
|
|
130
|
+
if sep and prefix in namespace_id_by_label:
|
|
131
|
+
resolved = type_def_index.get((prefix, name))
|
|
132
|
+
|
|
133
|
+
old_target = edge.get("target")
|
|
134
|
+
if resolved and resolved != old_target:
|
|
135
|
+
edge["target"] = resolved
|
|
136
|
+
if isinstance(old_target, str) and old_target:
|
|
137
|
+
repointed_from.add(old_target)
|
|
138
|
+
|
|
139
|
+
if not repointed_from:
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
still_referenced: set[str] = set()
|
|
143
|
+
for edge in all_edges:
|
|
144
|
+
still_referenced.add(edge.get("source"))
|
|
145
|
+
still_referenced.add(edge.get("target"))
|
|
146
|
+
all_nodes[:] = [
|
|
147
|
+
node for node in all_nodes
|
|
148
|
+
if node.get("id") not in repointed_from or node.get("id") in still_referenced
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
_DOTNET_SOURCE_EXTS = (".cs", ".razor", ".cshtml")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _is_cs_file(value: object) -> bool:
|
|
156
|
+
return isinstance(value, str) and value.endswith(".cs")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _is_dotnet_source_file(value: object) -> bool:
|
|
160
|
+
return isinstance(value, str) and value.endswith(_DOTNET_SOURCE_EXTS)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _metadata(value: object) -> dict:
|
|
164
|
+
return value if isinstance(value, dict) else {}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class CsharpNameResolver:
|
|
168
|
+
"""Namespace/using/alias-aware C# simple-name resolution.
|
|
169
|
+
|
|
170
|
+
Factored out of ``_resolve_csharp_type_references`` so the member-call pass
|
|
171
|
+
(``_resolve_csharp_member_calls`` in ``graphify/extract.py``) can type a
|
|
172
|
+
receiver with the same scoping rules the type-reference side already applies
|
|
173
|
+
(the core of #1620), instead of bailing on any corpus-wide bare-name clash.
|
|
174
|
+
|
|
175
|
+
Built from graph-stamped facts only: the ``(namespace, name)`` type-definition
|
|
176
|
+
index plus the per-file ``using``/alias import edges (each carrying its lexical
|
|
177
|
+
scope — ``("file", None)`` applies file-wide; ``("namespace", scope_id)``
|
|
178
|
+
applies only where ``scope_id`` is in the referencing node's ``scope_chain``).
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(self, all_nodes: list[dict], all_edges: list[dict]) -> None:
|
|
182
|
+
self.node_by_id: dict[str, dict] = {
|
|
183
|
+
node["id"]: node
|
|
184
|
+
for node in all_nodes
|
|
185
|
+
if isinstance(node.get("id"), str) and node.get("id")
|
|
186
|
+
}
|
|
187
|
+
self.type_def_index = _build_csharp_type_def_index(all_nodes)
|
|
188
|
+
self.known_namespaces = {
|
|
189
|
+
node.get("label")
|
|
190
|
+
for node in all_nodes
|
|
191
|
+
if node.get("type") == "namespace" and isinstance(node.get("label"), str)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
self.namespace_usings_by_file: dict[str, list[tuple[str, str, str | None]]] = {}
|
|
195
|
+
self.aliases_by_file: dict[str, dict[str, list[tuple[str, str, str | None]]]] = {}
|
|
196
|
+
|
|
197
|
+
for edge in all_edges:
|
|
198
|
+
if edge.get("relation") != "imports":
|
|
199
|
+
continue
|
|
200
|
+
source_node = self.node_by_id.get(edge.get("source"))
|
|
201
|
+
if not (
|
|
202
|
+
source_node
|
|
203
|
+
and isinstance(source_node.get("label"), str)
|
|
204
|
+
and source_node.get("label", "").endswith(_DOTNET_SOURCE_EXTS)
|
|
205
|
+
):
|
|
206
|
+
continue
|
|
207
|
+
source_file = source_node.get("source_file")
|
|
208
|
+
if not _is_dotnet_source_file(source_file):
|
|
209
|
+
continue
|
|
210
|
+
metadata = _metadata(edge.get("metadata"))
|
|
211
|
+
target_fqn = metadata.get("target_fqn")
|
|
212
|
+
if not isinstance(target_fqn, str) or not target_fqn:
|
|
213
|
+
continue
|
|
214
|
+
scope_kind = metadata.get("scope_kind") or "file"
|
|
215
|
+
scope_id = metadata.get("scope_id")
|
|
216
|
+
using_kind = metadata.get("using_kind")
|
|
217
|
+
if using_kind == "namespace":
|
|
218
|
+
entry = (target_fqn, scope_kind, scope_id)
|
|
219
|
+
bucket = self.namespace_usings_by_file.setdefault(source_file, [])
|
|
220
|
+
if entry not in bucket:
|
|
221
|
+
bucket.append(entry)
|
|
222
|
+
elif using_kind == "alias":
|
|
223
|
+
alias = metadata.get("alias")
|
|
224
|
+
if isinstance(alias, str) and alias:
|
|
225
|
+
entry = (target_fqn, scope_kind, scope_id)
|
|
226
|
+
bucket = self.aliases_by_file.setdefault(source_file, {}).setdefault(alias, [])
|
|
227
|
+
if entry not in bucket:
|
|
228
|
+
bucket.append(entry)
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def _namespace(node: dict | None) -> str:
|
|
232
|
+
metadata = _metadata((node or {}).get("metadata"))
|
|
233
|
+
namespace = metadata.get("namespace", "")
|
|
234
|
+
return namespace if isinstance(namespace, str) else ""
|
|
235
|
+
|
|
236
|
+
@staticmethod
|
|
237
|
+
def _scope_chain(node: dict) -> list[str]:
|
|
238
|
+
chain = _metadata(node.get("metadata")).get("scope_chain")
|
|
239
|
+
return chain if isinstance(chain, list) else []
|
|
240
|
+
|
|
241
|
+
def _using_in_scope(self, scope_kind: str, scope_id: str | None, source_node: dict) -> bool:
|
|
242
|
+
if scope_kind == "file":
|
|
243
|
+
return True
|
|
244
|
+
return scope_id is not None and scope_id in self._scope_chain(source_node)
|
|
245
|
+
|
|
246
|
+
def _scopes_for(self, source_node: dict, source_file: str) -> list[str]:
|
|
247
|
+
def _append_unique(items: list[str], value: str) -> None:
|
|
248
|
+
if value not in items:
|
|
249
|
+
items.append(value)
|
|
250
|
+
|
|
251
|
+
scopes: list[str] = []
|
|
252
|
+
_append_unique(scopes, self._namespace(source_node))
|
|
253
|
+
_append_unique(scopes, "")
|
|
254
|
+
for namespace, scope_kind, scope_id in self.namespace_usings_by_file.get(source_file, []):
|
|
255
|
+
if self._using_in_scope(scope_kind, scope_id, source_node):
|
|
256
|
+
_append_unique(scopes, namespace)
|
|
257
|
+
return scopes
|
|
258
|
+
|
|
259
|
+
def _resolve_alias(self, label: str, source_node: dict, source_file: str) -> str | None:
|
|
260
|
+
hits = set()
|
|
261
|
+
for target_fqn, scope_kind, scope_id in self.aliases_by_file.get(source_file, {}).get(label, []):
|
|
262
|
+
if not self._using_in_scope(scope_kind, scope_id, source_node):
|
|
263
|
+
continue
|
|
264
|
+
base_fqn = _strip_trailing_csharp_generic_args(html.unescape(target_fqn))
|
|
265
|
+
namespace, sep, simple_name = base_fqn.rpartition(".")
|
|
266
|
+
if not sep:
|
|
267
|
+
simple_name = namespace
|
|
268
|
+
namespace = ""
|
|
269
|
+
if not simple_name:
|
|
270
|
+
continue
|
|
271
|
+
hit = self.type_def_index.get((namespace, simple_name))
|
|
272
|
+
if hit:
|
|
273
|
+
hits.add(hit)
|
|
274
|
+
return next(iter(hits)) if len(hits) == 1 else None
|
|
275
|
+
|
|
276
|
+
def resolve_type_name(
|
|
277
|
+
self, label: str, source_node: dict, source_file: str
|
|
278
|
+
) -> tuple[str | None, bool]:
|
|
279
|
+
"""Resolve a simple type name to a definition node id, with a verdict.
|
|
280
|
+
|
|
281
|
+
Returns ``(node_id, decisive)``:
|
|
282
|
+
* ``(nid, True)`` — exactly one in-scope definition (or alias target).
|
|
283
|
+
* ``(None, True)`` — the name is claimed by this file's scoping (an
|
|
284
|
+
alias, or >1 in-scope candidates): genuinely ambiguous/unresolvable,
|
|
285
|
+
the caller must NOT fall back to a looser corpus-wide match.
|
|
286
|
+
* ``(None, False)`` — scoping knows nothing about the name; a caller
|
|
287
|
+
may fall back (e.g. to the corpus-wide unique bare-name match).
|
|
288
|
+
"""
|
|
289
|
+
if label in self.aliases_by_file.get(source_file, {}):
|
|
290
|
+
return self._resolve_alias(label, source_node, source_file), True
|
|
291
|
+
candidates: list[str] = []
|
|
292
|
+
for namespace in self._scopes_for(source_node, source_file):
|
|
293
|
+
hit = self.type_def_index.get((namespace, label))
|
|
294
|
+
if hit and hit not in candidates:
|
|
295
|
+
candidates.append(hit)
|
|
296
|
+
if len(candidates) == 1:
|
|
297
|
+
return candidates[0], True
|
|
298
|
+
return None, bool(candidates)
|
|
299
|
+
|
|
300
|
+
def resolve_label(self, label: str, source_node: dict, source_file: str) -> str | None:
|
|
301
|
+
resolved, _decisive = self.resolve_type_name(label, source_node, source_file)
|
|
302
|
+
return resolved
|
|
303
|
+
|
|
304
|
+
def resolve_qualified(
|
|
305
|
+
self, label: str, qualifier: object, source_node: dict, source_file: str
|
|
306
|
+
) -> str | None:
|
|
307
|
+
# Sound qualified resolution: an in-scope alias for Q shadows the namespace Q. For a qualified
|
|
308
|
+
# ref Q.label, look up (alias_target_namespace, label). If no in-scope alias, fall through to an
|
|
309
|
+
# exact known namespace. Dangle on ambiguity / no hit / unknown qualifier.
|
|
310
|
+
if not isinstance(qualifier, str) or not qualifier:
|
|
311
|
+
return None
|
|
312
|
+
in_scope = [
|
|
313
|
+
entry for entry in self.aliases_by_file.get(source_file, {}).get(qualifier, [])
|
|
314
|
+
if self._using_in_scope(entry[1], entry[2], source_node)
|
|
315
|
+
]
|
|
316
|
+
if in_scope:
|
|
317
|
+
hits = set()
|
|
318
|
+
for target_fqn, _scope_kind, _scope_id in in_scope:
|
|
319
|
+
alias_ns = _strip_trailing_csharp_generic_args(html.unescape(target_fqn))
|
|
320
|
+
hit = self.type_def_index.get((alias_ns, label))
|
|
321
|
+
if hit:
|
|
322
|
+
hits.add(hit)
|
|
323
|
+
return next(iter(hits)) if len(hits) == 1 else None
|
|
324
|
+
if qualifier in self.known_namespaces:
|
|
325
|
+
return self.type_def_index.get((qualifier, label))
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _resolve_csharp_type_references(
|
|
330
|
+
per_file: list[dict],
|
|
331
|
+
paths: list[Path],
|
|
332
|
+
all_nodes: list[dict],
|
|
333
|
+
all_edges: list[dict],
|
|
334
|
+
) -> None:
|
|
335
|
+
"""Arbitrate all C# ``inherits``/``implements``/``references`` targets.
|
|
336
|
+
|
|
337
|
+
The extractor emits provisional same-file bindings and sourceless stubs. This
|
|
338
|
+
pass is the single soundness gate: it uses only graph-stamped namespace/import
|
|
339
|
+
facts, keeps a binding only when the referenced simple name resolves to one
|
|
340
|
+
in-scope real type definition, and otherwise leaves the edge on a dangling stub.
|
|
341
|
+
"""
|
|
342
|
+
_ = (per_file, paths)
|
|
343
|
+
|
|
344
|
+
resolver = CsharpNameResolver(all_nodes, all_edges)
|
|
345
|
+
node_by_id = resolver.node_by_id
|
|
346
|
+
aliases_by_file = resolver.aliases_by_file
|
|
347
|
+
|
|
348
|
+
def _resolve_label(label: str, source_node: dict, source_file: str) -> str | None:
|
|
349
|
+
return resolver.resolve_label(label, source_node, source_file)
|
|
350
|
+
|
|
351
|
+
def _resolve_qualified(label: str, qualifier: object, source_node: dict, source_file: str) -> str | None:
|
|
352
|
+
return resolver.resolve_qualified(label, qualifier, source_node, source_file)
|
|
353
|
+
|
|
354
|
+
def _is_placeholder(node: dict | None) -> bool:
|
|
355
|
+
return bool(node) and not node.get("source_file")
|
|
356
|
+
|
|
357
|
+
def _is_csharp_relevant_target(node: dict) -> bool:
|
|
358
|
+
if node.get("type") == "namespace":
|
|
359
|
+
return True
|
|
360
|
+
source_file = node.get("source_file")
|
|
361
|
+
return not source_file or _is_cs_file(source_file)
|
|
362
|
+
|
|
363
|
+
def _label_for_type_ref_target(target_node: dict, source_file: str) -> str | None:
|
|
364
|
+
label = target_node.get("label")
|
|
365
|
+
if not isinstance(label, str) or not label:
|
|
366
|
+
return None
|
|
367
|
+
if not label.endswith(".cs"):
|
|
368
|
+
return label
|
|
369
|
+
|
|
370
|
+
stem = label[:-3]
|
|
371
|
+
for alias in aliases_by_file.get(source_file, {}):
|
|
372
|
+
if alias.lower() == stem.lower() or _make_id(alias) == _make_id(stem):
|
|
373
|
+
return alias
|
|
374
|
+
return stem or None
|
|
375
|
+
|
|
376
|
+
def _dangling_stub_id(label: str, current_target: object) -> str:
|
|
377
|
+
current = node_by_id.get(current_target)
|
|
378
|
+
if _is_placeholder(current) and current.get("label") == label:
|
|
379
|
+
return str(current_target)
|
|
380
|
+
|
|
381
|
+
for node in all_nodes:
|
|
382
|
+
nid = node.get("id")
|
|
383
|
+
if (
|
|
384
|
+
isinstance(nid, str)
|
|
385
|
+
and node.get("label") == label
|
|
386
|
+
and _is_placeholder(node)
|
|
387
|
+
):
|
|
388
|
+
return nid
|
|
389
|
+
|
|
390
|
+
stem = _make_id(label)
|
|
391
|
+
stub_id = stem
|
|
392
|
+
if stub_id in node_by_id:
|
|
393
|
+
stub_id = _make_id("csharp_type_ref", label)
|
|
394
|
+
suffix = 2
|
|
395
|
+
while stub_id in node_by_id:
|
|
396
|
+
stub_id = _make_id("csharp_type_ref", label, str(suffix))
|
|
397
|
+
suffix += 1
|
|
398
|
+
node = {
|
|
399
|
+
"id": stub_id,
|
|
400
|
+
"label": label,
|
|
401
|
+
"file_type": "code",
|
|
402
|
+
"source_file": "",
|
|
403
|
+
"source_location": "",
|
|
404
|
+
}
|
|
405
|
+
all_nodes.append(node)
|
|
406
|
+
node_by_id[stub_id] = node
|
|
407
|
+
return stub_id
|
|
408
|
+
|
|
409
|
+
REPOINT_RELATIONS = {"implements", "inherits", "references"}
|
|
410
|
+
repointed_from: set[str] = set()
|
|
411
|
+
for edge in all_edges:
|
|
412
|
+
if edge.get("relation") not in REPOINT_RELATIONS:
|
|
413
|
+
continue
|
|
414
|
+
source_file = edge.get("source_file")
|
|
415
|
+
if not _is_dotnet_source_file(source_file):
|
|
416
|
+
continue
|
|
417
|
+
source_node = node_by_id.get(edge.get("source"))
|
|
418
|
+
target_node = node_by_id.get(edge.get("target"))
|
|
419
|
+
if not source_node or not target_node:
|
|
420
|
+
continue
|
|
421
|
+
if not _is_csharp_relevant_target(target_node):
|
|
422
|
+
continue
|
|
423
|
+
metadata = _metadata(edge.get("metadata"))
|
|
424
|
+
label = metadata.get("ref_token") or _label_for_type_ref_target(target_node, source_file)
|
|
425
|
+
if not label:
|
|
426
|
+
continue
|
|
427
|
+
if metadata.get("qualified"):
|
|
428
|
+
resolved = _resolve_qualified(label, metadata.get("ref_qualifier"), source_node, source_file)
|
|
429
|
+
else:
|
|
430
|
+
resolved = _resolve_label(label, source_node, source_file)
|
|
431
|
+
target = edge.get("target")
|
|
432
|
+
desired = resolved or _dangling_stub_id(label, target)
|
|
433
|
+
if desired != target:
|
|
434
|
+
edge["target"] = desired
|
|
435
|
+
if isinstance(target, str) and _is_placeholder(target_node):
|
|
436
|
+
repointed_from.add(target)
|
|
437
|
+
|
|
438
|
+
if not repointed_from:
|
|
439
|
+
return
|
|
440
|
+
|
|
441
|
+
still_referenced: set[str] = set()
|
|
442
|
+
for edge in all_edges:
|
|
443
|
+
still_referenced.add(edge.get("source"))
|
|
444
|
+
still_referenced.add(edge.get("target"))
|
|
445
|
+
all_nodes[:] = [
|
|
446
|
+
node for node in all_nodes
|
|
447
|
+
if node.get("id") not in repointed_from or node.get("id") in still_referenced
|
|
448
|
+
]
|