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,75 @@
|
|
|
1
|
+
"""Join the same declared type across repositories in a merged graph (#3007).
|
|
2
|
+
|
|
3
|
+
`merge-graphs` prefixes every node id with its repo tag, so a contract type that
|
|
4
|
+
two services both declare arrives as two unconnected nodes. In a message-bus
|
|
5
|
+
codebase that is exactly where the interesting hop lives: the producer references
|
|
6
|
+
`SyncProductUpsertToSearchEvent` in one repo, the consumer implements
|
|
7
|
+
`IConsumer<SyncProductUpsertToSearchEvent>` in the other, and the merged graph has
|
|
8
|
+
no way to get from one to the other even though both sides name the same type.
|
|
9
|
+
|
|
10
|
+
This pass adds a `same_type_as` edge between type declarations that share both a
|
|
11
|
+
namespace and a name and come from different repos. Requiring the namespace keeps
|
|
12
|
+
it to types that were declared identically rather than two classes that merely
|
|
13
|
+
picked the same short name: on a pair of .NET services with 1440 and 262 declared
|
|
14
|
+
types, the namespace-plus-name match produced 7 pairs, all of them the shared
|
|
15
|
+
`EventManager.Models.*Event` contracts, and nothing else.
|
|
16
|
+
|
|
17
|
+
Edges only, no node merging. Two repos can hold copies of a contract that have
|
|
18
|
+
drifted, and collapsing them would hide that; a link lets a traversal cross while
|
|
19
|
+
each side keeps its own members, file and provenance.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from collections import defaultdict
|
|
24
|
+
from typing import TYPE_CHECKING
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
27
|
+
import networkx as nx
|
|
28
|
+
|
|
29
|
+
SHARED_TYPE_RELATION = "same_type_as"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def link_shared_type_declarations(merged: "nx.Graph") -> int:
|
|
33
|
+
"""Link identically declared types across repos. Returns the edge count added.
|
|
34
|
+
|
|
35
|
+
A candidate node is a sourced type declaration carrying a namespace and a
|
|
36
|
+
repo tag. A group qualifies when its members span at least two repos, and
|
|
37
|
+
every pair inside a qualifying group gets one edge. Groups are the handful of
|
|
38
|
+
types two repos genuinely share, so the pairwise walk stays small.
|
|
39
|
+
"""
|
|
40
|
+
by_declaration: dict[tuple[str, str], list[str]] = defaultdict(list)
|
|
41
|
+
for node, data in merged.nodes(data=True):
|
|
42
|
+
if not data.get("_callable_class") or not data.get("source_file"):
|
|
43
|
+
continue
|
|
44
|
+
namespace = str((data.get("metadata") or {}).get("namespace") or "")
|
|
45
|
+
label = str(data.get("label") or "")
|
|
46
|
+
if not namespace or not label or not data.get("repo"):
|
|
47
|
+
continue
|
|
48
|
+
by_declaration[(namespace, label)].append(node)
|
|
49
|
+
|
|
50
|
+
added = 0
|
|
51
|
+
for (namespace, label), nodes in by_declaration.items():
|
|
52
|
+
if len(nodes) < 2:
|
|
53
|
+
continue
|
|
54
|
+
if len({merged.nodes[n].get("repo") for n in nodes}) < 2:
|
|
55
|
+
continue
|
|
56
|
+
for index, left in enumerate(nodes):
|
|
57
|
+
for right in nodes[index + 1:]:
|
|
58
|
+
if merged.nodes[left].get("repo") == merged.nodes[right].get("repo"):
|
|
59
|
+
continue
|
|
60
|
+
if merged.has_edge(left, right):
|
|
61
|
+
continue
|
|
62
|
+
merged.add_edge(
|
|
63
|
+
left,
|
|
64
|
+
right,
|
|
65
|
+
relation=SHARED_TYPE_RELATION,
|
|
66
|
+
context="cross_repo",
|
|
67
|
+
confidence="INFERRED",
|
|
68
|
+
confidence_score=0.9,
|
|
69
|
+
source_file=str(merged.nodes[left].get("source_file") or ""),
|
|
70
|
+
weight=1.0,
|
|
71
|
+
_src=left,
|
|
72
|
+
_tgt=right,
|
|
73
|
+
)
|
|
74
|
+
added += 1
|
|
75
|
+
return added
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Member-level interface dispatch for C# (#3003).
|
|
2
|
+
|
|
3
|
+
A C# call through a constructor-injected dependency lands on the interface's
|
|
4
|
+
method node, because that is what the call site names: `_report.Build()` where
|
|
5
|
+
`_report` is an `IReport` resolves to `IReport.Build()`. The implementing
|
|
6
|
+
`Report.Build()` is a separate node, and nothing joins the two, so a directed
|
|
7
|
+
walk stops at the interface and every chain through an injected dependency is
|
|
8
|
+
cut at that point. On a Scrutor-scanned .NET service where every dependency is
|
|
9
|
+
an interface, that is most chains.
|
|
10
|
+
|
|
11
|
+
This resolver runs after all files are extracted, with the merged corpus
|
|
12
|
+
available, and links the interface's method to the implementing method:
|
|
13
|
+
|
|
14
|
+
ireport_ireport_build --dispatches_to--> report_report_build
|
|
15
|
+
|
|
16
|
+
It only fires when the interface has exactly one implementer and that
|
|
17
|
+
implementer owns exactly one method of the same name, the single-owner guard
|
|
18
|
+
`resolve_pascal_inherited_calls` and `resolve_ruby_member_calls` already use.
|
|
19
|
+
Walking `implements` to the one type that can serve the call mirrors what the
|
|
20
|
+
runtime does; guessing among several implementers would not, so anything
|
|
21
|
+
ambiguous is left alone.
|
|
22
|
+
|
|
23
|
+
The join is per method rather than per call site, so one edge reconnects every
|
|
24
|
+
call that reaches the interface method. Confidence is `INFERRED`: the target is
|
|
25
|
+
forced once there is a single implementer, but the source text never names it.
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
_CSHARP_SUFFIXES = (".cs",)
|
|
30
|
+
|
|
31
|
+
DISPATCH_RELATION = "dispatches_to"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _is_csharp(node: dict | None) -> bool:
|
|
35
|
+
"""True when the node is a declaration that lives in a C# file.
|
|
36
|
+
|
|
37
|
+
Every end of a dispatch pair has to pass this. `implements` is resolved by
|
|
38
|
+
name, so in a mixed corpus a Java class declaring `implements IReport` binds
|
|
39
|
+
to a C# `IReport` when that is the only node with the name, and linking a C#
|
|
40
|
+
interface member to a Java method would be a wrong edge rather than a missing
|
|
41
|
+
one. A non-C# implementation of a C# interface, from VB or a Razor component,
|
|
42
|
+
is a real thing, but claiming it needs its own extractor evidence.
|
|
43
|
+
"""
|
|
44
|
+
if not node:
|
|
45
|
+
return False
|
|
46
|
+
source_file = node.get("source_file")
|
|
47
|
+
return bool(source_file) and str(source_file).endswith(_CSHARP_SUFFIXES)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _method_label(node: dict) -> str:
|
|
51
|
+
"""Return a method node's bare name, for matching.
|
|
52
|
+
|
|
53
|
+
Case is kept: C# is case sensitive, and an implementing member must spell the
|
|
54
|
+
interface member exactly, so folding case could only pair a declaration with
|
|
55
|
+
a member that does not implement it.
|
|
56
|
+
|
|
57
|
+
The name is cut at the first parenthesis rather than by stripping a trailing
|
|
58
|
+
`()`. Today the C# extractor labels every method `.Name()`, so the two are the
|
|
59
|
+
same, but the pair match is keyed on this string and a label that ever carried
|
|
60
|
+
a signature would silently stop matching instead of failing visibly.
|
|
61
|
+
"""
|
|
62
|
+
label = str(node.get("label", "")).strip().removeprefix(".")
|
|
63
|
+
return label.split("(", 1)[0]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def resolve_csharp_interface_dispatch(
|
|
67
|
+
per_file: list[dict],
|
|
68
|
+
all_nodes: list[dict],
|
|
69
|
+
all_edges: list[dict],
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Link each single-implementer interface method to its implementation.
|
|
72
|
+
|
|
73
|
+
Purely additive: the existing call edge to the interface method is left in
|
|
74
|
+
place, since the call site really does name the interface.
|
|
75
|
+
"""
|
|
76
|
+
if not any(
|
|
77
|
+
str(result.get("source_file", "")).endswith(_CSHARP_SUFFIXES)
|
|
78
|
+
for result in per_file
|
|
79
|
+
if isinstance(result, dict)
|
|
80
|
+
) and not any(
|
|
81
|
+
str(n.get("source_file", "")).endswith(_CSHARP_SUFFIXES) for n in all_nodes
|
|
82
|
+
):
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
node_by_id = {n.get("id"): n for n in all_nodes}
|
|
86
|
+
|
|
87
|
+
implementers: dict[str, set[str]] = {}
|
|
88
|
+
methods_of: dict[str, dict[str, set[str]]] = {}
|
|
89
|
+
for e in all_edges:
|
|
90
|
+
rel = e.get("relation")
|
|
91
|
+
if rel == "implements":
|
|
92
|
+
implementers.setdefault(e.get("target"), set()).add(e.get("source"))
|
|
93
|
+
elif rel == "method":
|
|
94
|
+
owner, method_nid = e.get("source"), e.get("target")
|
|
95
|
+
mnode = node_by_id.get(method_nid)
|
|
96
|
+
if mnode is None or not _is_csharp(mnode):
|
|
97
|
+
continue
|
|
98
|
+
name = _method_label(mnode)
|
|
99
|
+
if name:
|
|
100
|
+
# A set, so the same method arriving on two `method` edges cannot
|
|
101
|
+
# look like two same-named methods and trip the guard below.
|
|
102
|
+
methods_of.setdefault(owner, {}).setdefault(name, set()).add(method_nid)
|
|
103
|
+
|
|
104
|
+
if not implementers or not methods_of:
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
# Scoped to this relation: another edge between the two members, whatever it
|
|
108
|
+
# is, says nothing about whether the dispatch link is already there.
|
|
109
|
+
existing_pairs = {
|
|
110
|
+
(e.get("source"), e.get("target"))
|
|
111
|
+
for e in all_edges
|
|
112
|
+
if e.get("relation") == DISPATCH_RELATION
|
|
113
|
+
}
|
|
114
|
+
new_edges: list[dict] = []
|
|
115
|
+
|
|
116
|
+
for interface_nid, impls in implementers.items():
|
|
117
|
+
if len(impls) != 1:
|
|
118
|
+
continue
|
|
119
|
+
impl_nid = next(iter(impls))
|
|
120
|
+
interface_node = node_by_id.get(interface_nid)
|
|
121
|
+
impl_node = node_by_id.get(impl_nid)
|
|
122
|
+
if interface_node is None or impl_node is None:
|
|
123
|
+
continue
|
|
124
|
+
# Both ends must be C# declarations. A sourceless stub minted for a
|
|
125
|
+
# dangling reference carries no members worth dispatching to, and a
|
|
126
|
+
# cross-language pair is a name collision rather than an implementation.
|
|
127
|
+
if not _is_csharp(interface_node) or not _is_csharp(impl_node):
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
impl_methods = methods_of.get(impl_nid, {})
|
|
131
|
+
for name, declared in methods_of.get(interface_nid, {}).items():
|
|
132
|
+
if len(declared) != 1:
|
|
133
|
+
continue
|
|
134
|
+
candidates = impl_methods.get(name, set())
|
|
135
|
+
if len(candidates) != 1:
|
|
136
|
+
continue
|
|
137
|
+
source = next(iter(declared))
|
|
138
|
+
target = next(iter(candidates))
|
|
139
|
+
if source == target or (source, target) in existing_pairs:
|
|
140
|
+
continue
|
|
141
|
+
existing_pairs.add((source, target))
|
|
142
|
+
new_edges.append({
|
|
143
|
+
"source": source,
|
|
144
|
+
"target": target,
|
|
145
|
+
"relation": DISPATCH_RELATION,
|
|
146
|
+
"context": "call",
|
|
147
|
+
"confidence": "INFERRED",
|
|
148
|
+
"confidence_score": 0.9,
|
|
149
|
+
"source_file": str(impl_node.get("source_file", "")),
|
|
150
|
+
"source_location": impl_node.get("source_location"),
|
|
151
|
+
"weight": 1.0,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
all_edges.extend(new_edges)
|