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,522 @@
|
|
|
1
|
+
"""Powershell extractor. Moved verbatim from graphify/extract.py."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
from graphify.extractors.base import _file_stem, _make_id, _read_text
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def extract_powershell(path: Path) -> dict:
|
|
12
|
+
"""Extract functions, classes, methods, and using statements from a .ps1 file."""
|
|
13
|
+
try:
|
|
14
|
+
import tree_sitter_powershell as tsps
|
|
15
|
+
from tree_sitter import Language, Parser
|
|
16
|
+
except ImportError:
|
|
17
|
+
return {"nodes": [], "edges": [], "error": "tree_sitter_powershell not installed"}
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
language = Language(tsps.language())
|
|
21
|
+
parser = Parser(language)
|
|
22
|
+
source = path.read_bytes()
|
|
23
|
+
tree = parser.parse(source)
|
|
24
|
+
root = tree.root_node
|
|
25
|
+
except Exception as e:
|
|
26
|
+
return {"nodes": [], "edges": [], "error": str(e)}
|
|
27
|
+
|
|
28
|
+
stem = _file_stem(path)
|
|
29
|
+
str_path = str(path)
|
|
30
|
+
nodes: list[dict] = []
|
|
31
|
+
edges: list[dict] = []
|
|
32
|
+
seen_ids: set[str] = set()
|
|
33
|
+
function_bodies: list[tuple[str, Any]] = []
|
|
34
|
+
|
|
35
|
+
def add_node(nid: str, label: str, line: int) -> None:
|
|
36
|
+
if nid not in seen_ids:
|
|
37
|
+
seen_ids.add(nid)
|
|
38
|
+
nodes.append({"id": nid, "label": label, "file_type": "code",
|
|
39
|
+
"source_file": str_path, "source_location": f"L{line}"})
|
|
40
|
+
|
|
41
|
+
def add_edge(src: str, tgt: str, relation: str, line: int,
|
|
42
|
+
confidence: str = "EXTRACTED", weight: float = 1.0,
|
|
43
|
+
context: str | None = None) -> None:
|
|
44
|
+
edge = {"source": src, "target": tgt, "relation": relation,
|
|
45
|
+
"confidence": confidence, "source_file": str_path,
|
|
46
|
+
"source_location": f"L{line}", "weight": weight}
|
|
47
|
+
if context:
|
|
48
|
+
edge["context"] = context
|
|
49
|
+
edges.append(edge)
|
|
50
|
+
|
|
51
|
+
file_nid = _make_id(str(path))
|
|
52
|
+
add_node(file_nid, path.name, 1)
|
|
53
|
+
|
|
54
|
+
_PS_SKIP = frozenset({
|
|
55
|
+
"using", "return", "if", "else", "elseif", "foreach", "for",
|
|
56
|
+
"while", "do", "switch", "try", "catch", "finally", "throw",
|
|
57
|
+
"break", "continue", "exit", "param", "begin", "process", "end",
|
|
58
|
+
# Import commands — handled as import edges, not function calls
|
|
59
|
+
"import-module",
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
def _find_script_block_body(node):
|
|
63
|
+
for child in node.children:
|
|
64
|
+
if child.type == "script_block":
|
|
65
|
+
for sc in child.children:
|
|
66
|
+
if sc.type == "script_block_body":
|
|
67
|
+
return sc
|
|
68
|
+
return child
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def ensure_named_node(name: str, line: int) -> str:
|
|
72
|
+
nid = _make_id(stem, name)
|
|
73
|
+
if nid in seen_ids:
|
|
74
|
+
return nid
|
|
75
|
+
nid = _make_id(name)
|
|
76
|
+
if nid not in seen_ids:
|
|
77
|
+
# The name isn't defined in this file, so this is a cross-file reference
|
|
78
|
+
# (e.g. a `Thing` type annotation imported from another module). Emit a
|
|
79
|
+
# SOURCELESS stub — like the inheritance-base path below — so the
|
|
80
|
+
# corpus-level rewire can collapse it onto the real definition. A sourced
|
|
81
|
+
# stub here makes _disambiguate_colliding_node_ids bake the referencing
|
|
82
|
+
# file's path (with extension) into the id and blocks the rewire, which is
|
|
83
|
+
# the phantom-duplicate-node bug (#1402).
|
|
84
|
+
seen_ids.add(nid)
|
|
85
|
+
nodes.append({
|
|
86
|
+
"id": nid,
|
|
87
|
+
"label": name,
|
|
88
|
+
"file_type": "code",
|
|
89
|
+
"source_file": "",
|
|
90
|
+
"source_location": "",
|
|
91
|
+
"origin_file": str_path,
|
|
92
|
+
})
|
|
93
|
+
return nid
|
|
94
|
+
|
|
95
|
+
def _ps_type_name(type_literal_node) -> str | None:
|
|
96
|
+
"""Drill into a type_literal node and return the inner type_identifier text."""
|
|
97
|
+
if type_literal_node is None:
|
|
98
|
+
return None
|
|
99
|
+
for spec in type_literal_node.children:
|
|
100
|
+
if spec.type != "type_spec":
|
|
101
|
+
continue
|
|
102
|
+
for tname in spec.children:
|
|
103
|
+
if tname.type != "type_name":
|
|
104
|
+
continue
|
|
105
|
+
for tid in tname.children:
|
|
106
|
+
if tid.type == "type_identifier":
|
|
107
|
+
return _read_text(tid, source)
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
def walk(node, parent_class_nid: str | None = None) -> None:
|
|
111
|
+
t = node.type
|
|
112
|
+
|
|
113
|
+
if t == "function_statement":
|
|
114
|
+
name_node = next((c for c in node.children if c.type == "function_name"), None)
|
|
115
|
+
if name_node:
|
|
116
|
+
func_name = _read_text(name_node, source)
|
|
117
|
+
line = node.start_point[0] + 1
|
|
118
|
+
func_nid = _make_id(stem, func_name)
|
|
119
|
+
add_node(func_nid, f"{func_name}()", line)
|
|
120
|
+
add_edge(file_nid, func_nid, "contains", line)
|
|
121
|
+
body = _find_script_block_body(node)
|
|
122
|
+
if body:
|
|
123
|
+
function_bodies.append((func_nid, body))
|
|
124
|
+
# Also walk the body during the main pass so that
|
|
125
|
+
# Import-Module / dot-source inside functions emit
|
|
126
|
+
# file-level imports_from edges (#1331).
|
|
127
|
+
walk(body, parent_class_nid)
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
if t == "enum_statement":
|
|
131
|
+
# PowerShell enums are first-class type definitions (referenced via
|
|
132
|
+
# `[Color]` type literals) but were not extracted at all, so those
|
|
133
|
+
# references resolved to a sourceless phantom stub instead of the real
|
|
134
|
+
# in-file definition — and the enum's members were lost entirely.
|
|
135
|
+
name_node = next((c for c in node.children if c.type == "simple_name"), None)
|
|
136
|
+
if name_node:
|
|
137
|
+
enum_name = _read_text(name_node, source)
|
|
138
|
+
line = node.start_point[0] + 1
|
|
139
|
+
enum_nid = _make_id(stem, enum_name)
|
|
140
|
+
add_node(enum_nid, enum_name, line)
|
|
141
|
+
add_edge(file_nid, enum_nid, "contains", line)
|
|
142
|
+
for member in node.children:
|
|
143
|
+
if member.type != "enum_member":
|
|
144
|
+
continue
|
|
145
|
+
mname_node = next(
|
|
146
|
+
(c for c in member.children if c.type == "simple_name"), None)
|
|
147
|
+
if mname_node is None:
|
|
148
|
+
continue
|
|
149
|
+
member_name = _read_text(mname_node, source)
|
|
150
|
+
m_line = member.start_point[0] + 1
|
|
151
|
+
member_nid = _make_id(enum_nid, member_name)
|
|
152
|
+
add_node(member_nid, member_name, m_line)
|
|
153
|
+
add_edge(enum_nid, member_nid, "contains", m_line)
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
if t == "class_statement":
|
|
157
|
+
name_node = next((c for c in node.children if c.type == "simple_name"), None)
|
|
158
|
+
if name_node:
|
|
159
|
+
class_name = _read_text(name_node, source)
|
|
160
|
+
line = node.start_point[0] + 1
|
|
161
|
+
class_nid = _make_id(stem, class_name)
|
|
162
|
+
add_node(class_nid, class_name, line)
|
|
163
|
+
add_edge(file_nid, class_nid, "contains", line)
|
|
164
|
+
# Base type(s) after ':'. PowerShell has no syntactic base vs
|
|
165
|
+
# interface split, so (matching the C# convention) treat the
|
|
166
|
+
# first base as the superclass (inherits) and the rest as
|
|
167
|
+
# interfaces (implements). Bases are the simple_name children
|
|
168
|
+
# after the ':' token.
|
|
169
|
+
colon_seen = False
|
|
170
|
+
base_index = 0
|
|
171
|
+
for child in node.children:
|
|
172
|
+
if child.type == ":":
|
|
173
|
+
colon_seen = True
|
|
174
|
+
elif colon_seen and child.type == "simple_name":
|
|
175
|
+
base_nid = ensure_named_node(_read_text(child, source), line)
|
|
176
|
+
if base_nid != class_nid:
|
|
177
|
+
rel = "inherits" if base_index == 0 else "implements"
|
|
178
|
+
add_edge(class_nid, base_nid, rel, line)
|
|
179
|
+
base_index += 1
|
|
180
|
+
for child in node.children:
|
|
181
|
+
walk(child, parent_class_nid=class_nid)
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
if t == "class_property_definition" and parent_class_nid:
|
|
185
|
+
type_literal = next((c for c in node.children if c.type == "type_literal"), None)
|
|
186
|
+
type_name = _ps_type_name(type_literal)
|
|
187
|
+
if type_name:
|
|
188
|
+
line = node.start_point[0] + 1
|
|
189
|
+
target_nid = ensure_named_node(type_name, line)
|
|
190
|
+
if target_nid != parent_class_nid:
|
|
191
|
+
add_edge(parent_class_nid, target_nid, "references",
|
|
192
|
+
line, context="field")
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
if t == "class_method_definition":
|
|
196
|
+
name_node = next((c for c in node.children if c.type == "simple_name"), None)
|
|
197
|
+
if name_node:
|
|
198
|
+
method_name = _read_text(name_node, source)
|
|
199
|
+
line = node.start_point[0] + 1
|
|
200
|
+
if parent_class_nid:
|
|
201
|
+
method_nid = _make_id(parent_class_nid, method_name)
|
|
202
|
+
add_node(method_nid, f".{method_name}()", line)
|
|
203
|
+
add_edge(parent_class_nid, method_nid, "method", line)
|
|
204
|
+
else:
|
|
205
|
+
method_nid = _make_id(stem, method_name)
|
|
206
|
+
add_node(method_nid, f"{method_name}()", line)
|
|
207
|
+
add_edge(file_nid, method_nid, "contains", line)
|
|
208
|
+
# Return type: type_literal sibling of simple_name
|
|
209
|
+
return_type_literal = next(
|
|
210
|
+
(c for c in node.children if c.type == "type_literal"), None)
|
|
211
|
+
return_type_name = _ps_type_name(return_type_literal)
|
|
212
|
+
if return_type_name:
|
|
213
|
+
target_nid = ensure_named_node(return_type_name, line)
|
|
214
|
+
if target_nid != method_nid:
|
|
215
|
+
add_edge(method_nid, target_nid, "references",
|
|
216
|
+
line, context="return_type")
|
|
217
|
+
# Parameter types: class_method_parameter_list
|
|
218
|
+
param_list = next(
|
|
219
|
+
(c for c in node.children if c.type == "class_method_parameter_list"), None)
|
|
220
|
+
if param_list is not None:
|
|
221
|
+
for p in param_list.children:
|
|
222
|
+
if p.type != "class_method_parameter":
|
|
223
|
+
continue
|
|
224
|
+
ptype_literal = next(
|
|
225
|
+
(c for c in p.children if c.type == "type_literal"), None)
|
|
226
|
+
ptype_name = _ps_type_name(ptype_literal)
|
|
227
|
+
if not ptype_name:
|
|
228
|
+
continue
|
|
229
|
+
p_line = p.start_point[0] + 1
|
|
230
|
+
target_nid = ensure_named_node(ptype_name, p_line)
|
|
231
|
+
if target_nid != method_nid:
|
|
232
|
+
add_edge(method_nid, target_nid, "references",
|
|
233
|
+
p_line, context="parameter_type")
|
|
234
|
+
body = _find_script_block_body(node)
|
|
235
|
+
if body:
|
|
236
|
+
function_bodies.append((method_nid, body))
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
if t == "command":
|
|
240
|
+
# Dot-sourcing: `. ./Shared.psm1`
|
|
241
|
+
# Uses command_invokation_operator '.' + command_name_expr (not command_name)
|
|
242
|
+
invoke_op = next(
|
|
243
|
+
(c for c in node.children if c.type == "command_invokation_operator"), None
|
|
244
|
+
)
|
|
245
|
+
if invoke_op is not None and _read_text(invoke_op, source).strip() == ".":
|
|
246
|
+
name_expr = next(
|
|
247
|
+
(c for c in node.children if c.type == "command_name_expr"), None
|
|
248
|
+
)
|
|
249
|
+
if name_expr is not None:
|
|
250
|
+
name_node = next(
|
|
251
|
+
(c for c in name_expr.children if c.type == "command_name"), None
|
|
252
|
+
)
|
|
253
|
+
if name_node:
|
|
254
|
+
raw_path = _read_text(name_node, source)
|
|
255
|
+
# Strip relative path prefix (./ or .\ or just the dot)
|
|
256
|
+
module_stem = re.sub(r'^[./\\]+', '', raw_path)
|
|
257
|
+
# Drop extension to get bare module name
|
|
258
|
+
module_stem = re.sub(r'\.[^.]+$', '', module_stem).replace('\\', '/')
|
|
259
|
+
module_name = module_stem.split('/')[-1]
|
|
260
|
+
if module_name:
|
|
261
|
+
add_edge(file_nid, _make_id(module_name), "imports_from",
|
|
262
|
+
node.start_point[0] + 1)
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
cmd_name_node = next((c for c in node.children if c.type == "command_name"), None)
|
|
266
|
+
if cmd_name_node:
|
|
267
|
+
cmd_text = _read_text(cmd_name_node, source).lower()
|
|
268
|
+
if cmd_text == "using":
|
|
269
|
+
tokens = []
|
|
270
|
+
for child in node.children:
|
|
271
|
+
if child.type == "command_elements":
|
|
272
|
+
for el in child.children:
|
|
273
|
+
if el.type == "generic_token":
|
|
274
|
+
tokens.append(_read_text(el, source))
|
|
275
|
+
module_tokens = [t for t in tokens
|
|
276
|
+
if t.lower() not in ("namespace", "module", "assembly")]
|
|
277
|
+
if module_tokens:
|
|
278
|
+
module_name = module_tokens[-1].split(".")[-1]
|
|
279
|
+
add_edge(file_nid, _make_id(module_name), "imports_from",
|
|
280
|
+
node.start_point[0] + 1)
|
|
281
|
+
elif cmd_text == "import-module":
|
|
282
|
+
# Collect generic_token args; skip command_parameter flags like -Name
|
|
283
|
+
# The module name is the first generic_token (or the one after -Name)
|
|
284
|
+
module_name: str | None = None
|
|
285
|
+
expect_name = False
|
|
286
|
+
for child in node.children:
|
|
287
|
+
if child.type != "command_elements":
|
|
288
|
+
continue
|
|
289
|
+
for el in child.children:
|
|
290
|
+
if el.type == "command_parameter":
|
|
291
|
+
param_text = _read_text(el, source).lstrip("-").lower()
|
|
292
|
+
expect_name = param_text in ("name", "n")
|
|
293
|
+
elif el.type == "generic_token":
|
|
294
|
+
token = _read_text(el, source)
|
|
295
|
+
if module_name is None or expect_name:
|
|
296
|
+
module_name = token
|
|
297
|
+
expect_name = False
|
|
298
|
+
if module_name:
|
|
299
|
+
# Strip extension; keep only the stem for the node ID
|
|
300
|
+
bare = re.sub(r'\.[^.]+$', '', module_name).split('/')[-1].split('\\')[-1]
|
|
301
|
+
if bare:
|
|
302
|
+
add_edge(file_nid, _make_id(bare), "imports_from",
|
|
303
|
+
node.start_point[0] + 1)
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
for child in node.children:
|
|
307
|
+
walk(child, parent_class_nid)
|
|
308
|
+
|
|
309
|
+
walk(root)
|
|
310
|
+
|
|
311
|
+
label_to_nid = {n["label"].strip("()").lstrip(".").lower(): n["id"] for n in nodes}
|
|
312
|
+
seen_call_pairs: set[tuple[str, str]] = set()
|
|
313
|
+
raw_calls: list[dict] = []
|
|
314
|
+
|
|
315
|
+
def walk_calls(node, caller_nid: str) -> None:
|
|
316
|
+
if node.type in ("function_statement", "class_statement"):
|
|
317
|
+
return
|
|
318
|
+
if node.type == "command":
|
|
319
|
+
cmd_name_node = next((c for c in node.children if c.type == "command_name"), None)
|
|
320
|
+
if cmd_name_node:
|
|
321
|
+
cmd_text = _read_text(cmd_name_node, source)
|
|
322
|
+
if cmd_text.lower() not in _PS_SKIP:
|
|
323
|
+
tgt_nid = label_to_nid.get(cmd_text.lower())
|
|
324
|
+
if tgt_nid and tgt_nid != caller_nid:
|
|
325
|
+
pair = (caller_nid, tgt_nid)
|
|
326
|
+
if pair not in seen_call_pairs:
|
|
327
|
+
seen_call_pairs.add(pair)
|
|
328
|
+
add_edge(caller_nid, tgt_nid, "calls",
|
|
329
|
+
node.start_point[0] + 1,
|
|
330
|
+
confidence="EXTRACTED", weight=1.0)
|
|
331
|
+
elif cmd_text:
|
|
332
|
+
raw_calls.append({
|
|
333
|
+
"caller_nid": caller_nid,
|
|
334
|
+
"callee": cmd_text,
|
|
335
|
+
"is_member_call": False,
|
|
336
|
+
"source_file": str_path,
|
|
337
|
+
"source_location": f"L{node.start_point[0] + 1}",
|
|
338
|
+
})
|
|
339
|
+
for child in node.children:
|
|
340
|
+
walk_calls(child, caller_nid)
|
|
341
|
+
|
|
342
|
+
for caller_nid, body_node in function_bodies:
|
|
343
|
+
walk_calls(body_node, caller_nid)
|
|
344
|
+
|
|
345
|
+
clean_edges = [e for e in edges if e["source"] in seen_ids and
|
|
346
|
+
(e["target"] in seen_ids or e["relation"] in ("imports_from", "imports"))]
|
|
347
|
+
return {"nodes": nodes, "edges": clean_edges, "raw_calls": raw_calls}
|
|
348
|
+
|
|
349
|
+
_PSD1_IMPORT_KEYS = frozenset({"RootModule", "NestedModules", "RequiredModules"})
|
|
350
|
+
|
|
351
|
+
def _psd1_collect_string_literals(node, source: bytes) -> list[str]:
|
|
352
|
+
"""Recursively collect all string_literal text values under *node*."""
|
|
353
|
+
results: list[str] = []
|
|
354
|
+
|
|
355
|
+
def _walk(n) -> None:
|
|
356
|
+
if n.type == "string_literal":
|
|
357
|
+
raw = source[n.start_byte:n.end_byte].decode(errors="replace")
|
|
358
|
+
# Strip surrounding quote chars (' or ")
|
|
359
|
+
results.append(raw.strip("'\""))
|
|
360
|
+
return
|
|
361
|
+
for child in n.children:
|
|
362
|
+
_walk(child)
|
|
363
|
+
|
|
364
|
+
_walk(node)
|
|
365
|
+
return results
|
|
366
|
+
|
|
367
|
+
def _psd1_module_name(raw: str) -> str:
|
|
368
|
+
"""Derive a bare module name from a raw string value.
|
|
369
|
+
|
|
370
|
+
e.g. 'MyModule.psm1' → 'MyModule', './sub/Util.psm1' → 'Util', 'PSReadLine' → 'PSReadLine'
|
|
371
|
+
"""
|
|
372
|
+
# Strip path prefix and extension
|
|
373
|
+
name = raw.replace("\\", "/").split("/")[-1]
|
|
374
|
+
name = re.sub(r"\.[^.]+$", "", name) # remove last extension
|
|
375
|
+
return name.strip()
|
|
376
|
+
|
|
377
|
+
def extract_powershell_manifest(path: Path) -> dict:
|
|
378
|
+
"""Extract module dependency edges from a PowerShell .psd1 manifest file.
|
|
379
|
+
|
|
380
|
+
.psd1 files are PowerShell data hashtables, not scripts. tree-sitter-powershell
|
|
381
|
+
parses them correctly (they are syntactically valid PS). We walk the AST looking
|
|
382
|
+
for RootModule, NestedModules, and RequiredModules keys and emit imports_from
|
|
383
|
+
edges for every referenced module.
|
|
384
|
+
|
|
385
|
+
RequiredModules supports two forms:
|
|
386
|
+
- Simple string: 'PSReadLine'
|
|
387
|
+
- Module specification: @{ ModuleName = 'Pester'; ModuleVersion = '5.0' }
|
|
388
|
+
For the hashtable form we only follow the ModuleName key.
|
|
389
|
+
"""
|
|
390
|
+
try:
|
|
391
|
+
import tree_sitter_powershell as tsps
|
|
392
|
+
from tree_sitter import Language, Parser
|
|
393
|
+
except ImportError:
|
|
394
|
+
return {"nodes": [], "edges": [], "error": "tree_sitter_powershell not installed"}
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
language = Language(tsps.language())
|
|
398
|
+
parser = Parser(language)
|
|
399
|
+
source = path.read_bytes()
|
|
400
|
+
tree = parser.parse(source)
|
|
401
|
+
root = tree.root_node
|
|
402
|
+
except Exception as e:
|
|
403
|
+
return {"nodes": [], "edges": [], "error": str(e)}
|
|
404
|
+
|
|
405
|
+
str_path = str(path)
|
|
406
|
+
nodes: list[dict] = []
|
|
407
|
+
edges: list[dict] = []
|
|
408
|
+
seen_ids: set[str] = set()
|
|
409
|
+
|
|
410
|
+
def add_node(nid: str, label: str, line: int) -> None:
|
|
411
|
+
if nid not in seen_ids:
|
|
412
|
+
seen_ids.add(nid)
|
|
413
|
+
nodes.append({"id": nid, "label": label, "file_type": "code",
|
|
414
|
+
"source_file": str_path, "source_location": f"L{line}"})
|
|
415
|
+
|
|
416
|
+
def add_import_edge(src: str, module_raw: str, line: int) -> None:
|
|
417
|
+
name = _psd1_module_name(module_raw)
|
|
418
|
+
if not name:
|
|
419
|
+
return
|
|
420
|
+
tgt_nid = _make_id(name)
|
|
421
|
+
edges.append({
|
|
422
|
+
"source": src,
|
|
423
|
+
"target": tgt_nid,
|
|
424
|
+
"relation": "imports_from",
|
|
425
|
+
"confidence": "EXTRACTED",
|
|
426
|
+
"source_file": str_path,
|
|
427
|
+
"source_location": f"L{line}",
|
|
428
|
+
"weight": 1.0,
|
|
429
|
+
"context": "import",
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
file_nid = _make_id(str(path))
|
|
433
|
+
add_node(file_nid, path.name, 1)
|
|
434
|
+
|
|
435
|
+
def walk_manifest(node) -> None:
|
|
436
|
+
"""Walk the AST and emit edges for import-relevant hash_entry nodes."""
|
|
437
|
+
if node.type != "hash_entry":
|
|
438
|
+
for child in node.children:
|
|
439
|
+
walk_manifest(child)
|
|
440
|
+
return
|
|
441
|
+
|
|
442
|
+
# Identify the key
|
|
443
|
+
key_node = next((c for c in node.children if c.type == "key_expression"), None)
|
|
444
|
+
if key_node is None:
|
|
445
|
+
return
|
|
446
|
+
key_text = source[key_node.start_byte:key_node.end_byte].decode(errors="replace").strip()
|
|
447
|
+
|
|
448
|
+
if key_text not in _PSD1_IMPORT_KEYS:
|
|
449
|
+
# Still recurse in case there are nested hashes (e.g. ModuleVersion entries
|
|
450
|
+
# contain sub-hashes, but we only care about top-level keys for imports)
|
|
451
|
+
return
|
|
452
|
+
|
|
453
|
+
line = node.start_point[0] + 1
|
|
454
|
+
value_node = next((c for c in node.children if c.type == "pipeline"), None)
|
|
455
|
+
if value_node is None:
|
|
456
|
+
return
|
|
457
|
+
|
|
458
|
+
if key_text == "RootModule":
|
|
459
|
+
# Value is a single string
|
|
460
|
+
strings = _psd1_collect_string_literals(value_node, source)
|
|
461
|
+
for s in strings:
|
|
462
|
+
add_import_edge(file_nid, s, line)
|
|
463
|
+
|
|
464
|
+
elif key_text == "NestedModules":
|
|
465
|
+
# Value is a string or @('a', 'b', ...) array — collect all string literals
|
|
466
|
+
strings = _psd1_collect_string_literals(value_node, source)
|
|
467
|
+
for s in strings:
|
|
468
|
+
add_import_edge(file_nid, s, line)
|
|
469
|
+
|
|
470
|
+
elif key_text == "RequiredModules":
|
|
471
|
+
# Two forms:
|
|
472
|
+
# 1) 'SimpleModule' — direct string literals in the array
|
|
473
|
+
# 2) @{ ModuleName = 'Foo'; ModuleVersion = '2.0' } — use ModuleName only
|
|
474
|
+
#
|
|
475
|
+
# Strategy: walk the value for hash_entry nodes whose key is 'ModuleName';
|
|
476
|
+
# collect their string values. For the remaining string_literal nodes that
|
|
477
|
+
# are NOT inside a hash_entry subtree, treat them as simple module names.
|
|
478
|
+
module_name_strings: list[str] = []
|
|
479
|
+
inside_hash_entries: set[int] = set() # byte offsets of handled strings
|
|
480
|
+
|
|
481
|
+
def find_modulename_entries(n) -> None:
|
|
482
|
+
if n.type == "hash_entry":
|
|
483
|
+
sub_key = next((c for c in n.children if c.type == "key_expression"), None)
|
|
484
|
+
if sub_key is not None:
|
|
485
|
+
sk_text = source[sub_key.start_byte:sub_key.end_byte].decode(errors="replace").strip()
|
|
486
|
+
# Collect strings inside *all* sub-keys so we can exclude them
|
|
487
|
+
for c in n.children:
|
|
488
|
+
if c.type == "pipeline":
|
|
489
|
+
for s_node in _collect_string_nodes(c):
|
|
490
|
+
inside_hash_entries.add(s_node.start_byte)
|
|
491
|
+
if sk_text == "ModuleName":
|
|
492
|
+
for c in n.children:
|
|
493
|
+
if c.type == "pipeline":
|
|
494
|
+
for s in _psd1_collect_string_literals(c, source):
|
|
495
|
+
module_name_strings.append(s)
|
|
496
|
+
return # don't recurse further into this hash_entry
|
|
497
|
+
for child in n.children:
|
|
498
|
+
find_modulename_entries(child)
|
|
499
|
+
|
|
500
|
+
def _collect_string_nodes(n):
|
|
501
|
+
"""Return all string_literal nodes in subtree."""
|
|
502
|
+
if n.type == "string_literal":
|
|
503
|
+
yield n
|
|
504
|
+
return
|
|
505
|
+
for child in n.children:
|
|
506
|
+
yield from _collect_string_nodes(child)
|
|
507
|
+
|
|
508
|
+
find_modulename_entries(value_node)
|
|
509
|
+
|
|
510
|
+
# Now gather direct string literals not inside hash entries
|
|
511
|
+
direct_strings: list[str] = []
|
|
512
|
+
for s_node in _collect_string_nodes(value_node):
|
|
513
|
+
if s_node.start_byte not in inside_hash_entries:
|
|
514
|
+
raw = source[s_node.start_byte:s_node.end_byte].decode(errors="replace")
|
|
515
|
+
direct_strings.append(raw.strip("'\""))
|
|
516
|
+
|
|
517
|
+
for s in direct_strings + module_name_strings:
|
|
518
|
+
add_import_edge(file_nid, s, line)
|
|
519
|
+
|
|
520
|
+
walk_manifest(root)
|
|
521
|
+
|
|
522
|
+
return {"nodes": nodes, "edges": edges, "raw_calls": []}
|