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,240 @@
|
|
|
1
|
+
"""Json_config extractor. Moved verbatim from graphify/extract.py."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from graphify.extractors.base import _file_stem, _make_id, _read_text
|
|
7
|
+
from graphify.ids import normalize_id
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_CONFIG_JSON_NAMES = frozenset({
|
|
11
|
+
"package.json", "tsconfig.json", "jsconfig.json", "composer.json",
|
|
12
|
+
"deno.json", "deno.jsonc", "bower.json", "manifest.json",
|
|
13
|
+
"app.json", "now.json", "vercel.json", "angular.json", "nest-cli.json",
|
|
14
|
+
"biome.json", "biome.jsonc", "renovate.json", ".babelrc", ".babelrc.json",
|
|
15
|
+
".eslintrc.json", ".prettierrc.json", ".prettierrc", "babel.config.json",
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
_CONFIG_JSON_KEYS = frozenset({
|
|
19
|
+
"dependencies", "devDependencies", "peerDependencies",
|
|
20
|
+
"optionalDependencies", "bundleDependencies", "bundledDependencies",
|
|
21
|
+
"extends", "$ref", "$schema", "compilerOptions",
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
def _is_config_json(path: Path, obj_node, source: bytes) -> bool:
|
|
25
|
+
"""True if a .json file is a recognized config/manifest worth AST-extracting.
|
|
26
|
+
|
|
27
|
+
Matches by filename first (cheap), then falls back to a top-level key probe
|
|
28
|
+
so arbitrarily-named config files (e.g. ``api.tsconfig.json``,
|
|
29
|
+
``foo.eslintrc.json``) are still picked up. Returns False for data JSON so it
|
|
30
|
+
is skipped by the structural pass (#1224)."""
|
|
31
|
+
name = path.name.casefold()
|
|
32
|
+
if name in _CONFIG_JSON_NAMES:
|
|
33
|
+
return True
|
|
34
|
+
# Common compound config names: *.eslintrc.json, *.prettierrc.json, etc.
|
|
35
|
+
if name.endswith((".eslintrc.json", ".prettierrc.json", ".babelrc.json",
|
|
36
|
+
"tsconfig.json", "jsconfig.json")):
|
|
37
|
+
return True
|
|
38
|
+
# Top-level key probe: scan the root object's immediate keys (no deep walk).
|
|
39
|
+
for top_key in obj_node.children:
|
|
40
|
+
if top_key.type != "pair":
|
|
41
|
+
continue
|
|
42
|
+
key_node = top_key.child_by_field_name("key")
|
|
43
|
+
if key_node is None:
|
|
44
|
+
continue
|
|
45
|
+
kc = key_node.child_by_field_name("string_content")
|
|
46
|
+
text = _read_text(kc, source) if kc else _read_text(key_node, source).strip('"\'')
|
|
47
|
+
if text in _CONFIG_JSON_KEYS:
|
|
48
|
+
return True
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
def extract_json(path: Path) -> dict:
|
|
52
|
+
"""Extract structure and dependency edges from a *config/manifest* .json file.
|
|
53
|
+
|
|
54
|
+
Data-shaped JSON (eval fixtures, datasets, GeoJSON, API response dumps) is
|
|
55
|
+
deliberately skipped — AST-walking it produced hundreds of orphan key-nodes
|
|
56
|
+
and duplicate communities that swamped real structure (#1224). Recognition
|
|
57
|
+
is by filename (package.json, tsconfig.json, …) or a top-level key probe
|
|
58
|
+
(dependencies / extends / $ref / $schema / compilerOptions)."""
|
|
59
|
+
_JSON_MAX_BYTES = 1_048_576 # 1 MiB — skip large fixture dumps / GeoJSON blobs
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
import tree_sitter_json as tsjson
|
|
63
|
+
from tree_sitter import Language, Parser
|
|
64
|
+
except ImportError:
|
|
65
|
+
return {"nodes": [], "edges": [], "error": "tree-sitter-json not installed"}
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
# Bounded read instead of stat()+read() to eliminate TOCTOU (J-1):
|
|
69
|
+
# read one byte beyond the limit so we can detect oversized files even
|
|
70
|
+
# if the file grows between stat and read.
|
|
71
|
+
with path.open("rb") as _f:
|
|
72
|
+
source = _f.read(_JSON_MAX_BYTES + 1)
|
|
73
|
+
if len(source) > _JSON_MAX_BYTES:
|
|
74
|
+
return {"nodes": [], "edges": [], "error": "json file too large to index"}
|
|
75
|
+
language = Language(tsjson.language())
|
|
76
|
+
parser = Parser(language)
|
|
77
|
+
tree = parser.parse(source)
|
|
78
|
+
root = tree.root_node
|
|
79
|
+
except Exception as e:
|
|
80
|
+
return {"nodes": [], "edges": [], "error": str(e)}
|
|
81
|
+
|
|
82
|
+
stem = _file_stem(path)
|
|
83
|
+
str_path = str(path)
|
|
84
|
+
nodes: list[dict] = []
|
|
85
|
+
edges: list[dict] = []
|
|
86
|
+
seen_ids: set[str] = set()
|
|
87
|
+
|
|
88
|
+
# Keys whose string values become imports (package.json dep blocks)
|
|
89
|
+
_DEP_KEYS = frozenset({
|
|
90
|
+
"dependencies", "devDependencies", "peerDependencies",
|
|
91
|
+
"optionalDependencies", "bundleDependencies", "bundledDependencies",
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
def add_node(nid: str, label: str, line: int, file_type: str = "code") -> None:
|
|
95
|
+
if nid and nid not in seen_ids:
|
|
96
|
+
seen_ids.add(nid)
|
|
97
|
+
nodes.append({"id": nid, "label": label, "file_type": file_type,
|
|
98
|
+
"source_file": str_path, "source_location": f"L{line}"})
|
|
99
|
+
|
|
100
|
+
def add_edge(src: str, tgt: str, relation: str, line: int,
|
|
101
|
+
context: str | None = None) -> None:
|
|
102
|
+
if not src or not tgt or src == tgt:
|
|
103
|
+
return
|
|
104
|
+
edge = {"source": src, "target": tgt, "relation": relation,
|
|
105
|
+
"confidence": "EXTRACTED", "source_file": str_path,
|
|
106
|
+
"source_location": f"L{line}", "weight": 1.0}
|
|
107
|
+
if context:
|
|
108
|
+
edge["context"] = context
|
|
109
|
+
edges.append(edge)
|
|
110
|
+
|
|
111
|
+
file_nid = _make_id(str(path))
|
|
112
|
+
add_node(file_nid, path.name, 1)
|
|
113
|
+
|
|
114
|
+
def _key_text(pair_node) -> str | None:
|
|
115
|
+
"""Extract the string content of a pair's key."""
|
|
116
|
+
key_node = pair_node.child_by_field_name("key")
|
|
117
|
+
if key_node is None:
|
|
118
|
+
return None
|
|
119
|
+
if key_node.type == "string":
|
|
120
|
+
content = key_node.child_by_field_name("string_content")
|
|
121
|
+
if content:
|
|
122
|
+
return _read_text(content, source)
|
|
123
|
+
# fallback: strip surrounding quotes
|
|
124
|
+
raw = _read_text(key_node, source)
|
|
125
|
+
return raw.strip('"\'')
|
|
126
|
+
return _read_text(key_node, source)
|
|
127
|
+
|
|
128
|
+
def _val_node(pair_node):
|
|
129
|
+
return pair_node.child_by_field_name("value")
|
|
130
|
+
|
|
131
|
+
def walk_object(obj_node, parent_nid: str, parent_key: str | None,
|
|
132
|
+
depth: int, pair_count: list) -> None:
|
|
133
|
+
if depth > 6:
|
|
134
|
+
return
|
|
135
|
+
for child in obj_node.children:
|
|
136
|
+
if child.type != "pair":
|
|
137
|
+
continue
|
|
138
|
+
if pair_count[0] >= 500: # check per-pair so the cap is honoured exactly (J-3)
|
|
139
|
+
return
|
|
140
|
+
pair_count[0] += 1
|
|
141
|
+
key = _key_text(child)
|
|
142
|
+
if not key:
|
|
143
|
+
continue
|
|
144
|
+
# A key that normalizes to nothing (a JSONC `"//"` comment key, say)
|
|
145
|
+
# would collapse `_make_id(stem, key)` down to the bare file-stem id,
|
|
146
|
+
# which is absolute-path-derived and leaks the scan path (#1899). Such
|
|
147
|
+
# keys carry no graph signal, so drop them.
|
|
148
|
+
if not normalize_id(key):
|
|
149
|
+
continue
|
|
150
|
+
key_nid = _make_id(stem, *(([parent_key] if parent_key else []) + [key]))
|
|
151
|
+
if not key_nid:
|
|
152
|
+
continue
|
|
153
|
+
line = child.start_point[0] + 1
|
|
154
|
+
add_node(key_nid, key, line)
|
|
155
|
+
add_edge(parent_nid, key_nid, "contains", line)
|
|
156
|
+
|
|
157
|
+
val = _val_node(child)
|
|
158
|
+
if val is None:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
if val.type == "object":
|
|
162
|
+
walk_object(val, key_nid, key, depth + 1, pair_count)
|
|
163
|
+
|
|
164
|
+
elif val.type == "array" and key == "extends":
|
|
165
|
+
# Only a genuine `extends` array (tsconfig 5.0+, eslint) is
|
|
166
|
+
# inheritance. This branch used to fire for *every* array under
|
|
167
|
+
# a tracked key, so `compilerOptions.lib: ["dom","esnext"]` and
|
|
168
|
+
# `exclude: ["node_modules",".next"]` each emitted an `extends`
|
|
169
|
+
# edge — array membership relabelled as inheritance. The string
|
|
170
|
+
# branch below already gates on `key == "extends"`; this matches
|
|
171
|
+
# it. Non-`extends` arrays keep their `contains` edge only.
|
|
172
|
+
# Prefix with "ref_" so external refs don't collide with real
|
|
173
|
+
# code/file node IDs that share the same collapsed _make_id (J-4).
|
|
174
|
+
for item in val.children:
|
|
175
|
+
if item.type == "string":
|
|
176
|
+
content = item.child_by_field_name("string_content")
|
|
177
|
+
ref = _read_text(content, source) if content else _read_text(item, source).strip('"\'')
|
|
178
|
+
if ref:
|
|
179
|
+
ref_nid = _make_id("ref", ref)
|
|
180
|
+
if ref_nid:
|
|
181
|
+
add_node(ref_nid, ref, line, file_type="concept")
|
|
182
|
+
add_edge(key_nid, ref_nid, "extends", line, context="import")
|
|
183
|
+
|
|
184
|
+
elif val.type == "string":
|
|
185
|
+
content = val.child_by_field_name("string_content")
|
|
186
|
+
val_text = _read_text(content, source) if content else _read_text(val, source).strip('"\'')
|
|
187
|
+
|
|
188
|
+
if key == "extends" and val_text:
|
|
189
|
+
# Namespace external refs to avoid ID collision with file nodes (J-4)
|
|
190
|
+
ref_nid = _make_id("ref", val_text)
|
|
191
|
+
if ref_nid:
|
|
192
|
+
add_node(ref_nid, val_text, line, file_type="concept")
|
|
193
|
+
add_edge(file_nid, ref_nid, "extends", line, context="import")
|
|
194
|
+
|
|
195
|
+
elif key == "$ref" and val_text:
|
|
196
|
+
# Namespace $ref values to prevent edge hijacking into code nodes (J-4)
|
|
197
|
+
ref_nid = _make_id("ref", val_text)
|
|
198
|
+
if ref_nid:
|
|
199
|
+
add_edge(parent_nid, ref_nid, "references", line)
|
|
200
|
+
|
|
201
|
+
elif parent_key in _DEP_KEYS and val_text:
|
|
202
|
+
# Two defects fixed here, both from the #1764 shape:
|
|
203
|
+
#
|
|
204
|
+
# 1. The edge ran `key_nid -> dep_nid`, and both nodes carry
|
|
205
|
+
# the *same label* (the dependency name), so every
|
|
206
|
+
# dependency produced a visually self-referential
|
|
207
|
+
# `react --imports--> react` (22 of them on a mid-size
|
|
208
|
+
# Next.js repo). The self-loop guard in
|
|
209
|
+
# test_extract_json_import_and_extends_targets_are_real_nodes
|
|
210
|
+
# compares node ids, and these two ids differ, so the
|
|
211
|
+
# duplicate slipped past it. Sourcing the edge at the
|
|
212
|
+
# manifest yields `package.json --imports--> react`.
|
|
213
|
+
#
|
|
214
|
+
# 2. `_make_id(key)` minted a *bare* id, skipping the "ref"
|
|
215
|
+
# namespacing every other external reference in this file
|
|
216
|
+
# applies (J-4). A dependency named `utils`, `colors` or
|
|
217
|
+
# `types` could then be collapsed onto a same-named local
|
|
218
|
+
# module by build.py's alias index -- the #1638 failure
|
|
219
|
+
# mode reached through a different door.
|
|
220
|
+
dep_nid = _make_id("ref", key)
|
|
221
|
+
if dep_nid:
|
|
222
|
+
add_node(dep_nid, key, line, file_type="concept")
|
|
223
|
+
add_edge(file_nid, dep_nid, "imports", line, context="import")
|
|
224
|
+
|
|
225
|
+
# Entry: find root document → object
|
|
226
|
+
doc = root
|
|
227
|
+
if doc.type == "document" and doc.child_count > 0:
|
|
228
|
+
doc = doc.children[0]
|
|
229
|
+
if doc.type == "object":
|
|
230
|
+
# Only AST-extract recognized config/manifest JSON. Data JSON (fixtures,
|
|
231
|
+
# datasets, GeoJSON, API dumps) is skipped so it doesn't explode into
|
|
232
|
+
# orphan key-nodes (#1224); it's left to the LLM semantic pass.
|
|
233
|
+
if not _is_config_json(path, doc, source):
|
|
234
|
+
return {"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
|
|
235
|
+
walk_object(doc, file_nid, None, 0, [0])
|
|
236
|
+
else:
|
|
237
|
+
# Top-level array or scalar => data JSON, never a config/manifest.
|
|
238
|
+
return {"nodes": [], "edges": [], "skipped": "data json (non-object root)"}
|
|
239
|
+
|
|
240
|
+
return {"nodes": nodes, "edges": edges}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""julia — moved verbatim from graphify/extract.py."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from graphify.extractors.base import _file_stem, _make_id, _read_text
|
|
5
|
+
from graphify.extractors.engine import _semantic_reference_edge
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def extract_julia(path: Path) -> dict:
|
|
10
|
+
"""Extract modules, structs, functions, imports, and calls from a .jl file."""
|
|
11
|
+
try:
|
|
12
|
+
import tree_sitter_julia as tsjulia
|
|
13
|
+
from tree_sitter import Language, Parser
|
|
14
|
+
except ImportError:
|
|
15
|
+
return {"nodes": [], "edges": [], "error": "tree-sitter-julia not installed"}
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
language = Language(tsjulia.language())
|
|
19
|
+
parser = Parser(language)
|
|
20
|
+
source = path.read_bytes()
|
|
21
|
+
tree = parser.parse(source)
|
|
22
|
+
root = tree.root_node
|
|
23
|
+
except Exception as e:
|
|
24
|
+
return {"nodes": [], "edges": [], "error": str(e)}
|
|
25
|
+
|
|
26
|
+
stem = _file_stem(path)
|
|
27
|
+
str_path = str(path)
|
|
28
|
+
nodes: list[dict] = []
|
|
29
|
+
edges: list[dict] = []
|
|
30
|
+
seen_ids: set[str] = set()
|
|
31
|
+
function_bodies: list[tuple[str, object]] = []
|
|
32
|
+
|
|
33
|
+
def add_node(nid: str, label: str, line: int) -> None:
|
|
34
|
+
if nid not in seen_ids:
|
|
35
|
+
seen_ids.add(nid)
|
|
36
|
+
nodes.append({
|
|
37
|
+
"id": nid,
|
|
38
|
+
"label": label,
|
|
39
|
+
"file_type": "code",
|
|
40
|
+
"source_file": str_path,
|
|
41
|
+
"source_location": f"L{line}",
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
def add_edge(src: str, tgt: str, relation: str, line: int,
|
|
45
|
+
confidence: str = "EXTRACTED", weight: float = 1.0,
|
|
46
|
+
context: str | None = None) -> None:
|
|
47
|
+
edge = {
|
|
48
|
+
"source": src,
|
|
49
|
+
"target": tgt,
|
|
50
|
+
"relation": relation,
|
|
51
|
+
"confidence": confidence,
|
|
52
|
+
"source_file": str_path,
|
|
53
|
+
"source_location": f"L{line}",
|
|
54
|
+
"weight": weight,
|
|
55
|
+
}
|
|
56
|
+
if context:
|
|
57
|
+
edge["context"] = context
|
|
58
|
+
edges.append(edge)
|
|
59
|
+
|
|
60
|
+
file_nid = _make_id(str(path))
|
|
61
|
+
add_node(file_nid, path.name, 1)
|
|
62
|
+
|
|
63
|
+
def ensure_named_node(name: str, line: int) -> str:
|
|
64
|
+
nid = _make_id(stem, name)
|
|
65
|
+
if nid in seen_ids:
|
|
66
|
+
return nid
|
|
67
|
+
nid = _make_id(name)
|
|
68
|
+
if nid not in seen_ids:
|
|
69
|
+
# The name isn't defined in this file, so this is a cross-file reference
|
|
70
|
+
# (e.g. a `Thing` type annotation imported from another module). Emit a
|
|
71
|
+
# SOURCELESS stub — like the inheritance-base path below — so the
|
|
72
|
+
# corpus-level rewire can collapse it onto the real definition. A sourced
|
|
73
|
+
# stub here makes _disambiguate_colliding_node_ids bake the referencing
|
|
74
|
+
# file's path (with extension) into the id and blocks the rewire, which is
|
|
75
|
+
# the phantom-duplicate-node bug (#1402).
|
|
76
|
+
seen_ids.add(nid)
|
|
77
|
+
nodes.append({
|
|
78
|
+
"id": nid,
|
|
79
|
+
"label": name,
|
|
80
|
+
"file_type": "code",
|
|
81
|
+
"source_file": "",
|
|
82
|
+
"source_location": "",
|
|
83
|
+
"origin_file": str_path,
|
|
84
|
+
})
|
|
85
|
+
return nid
|
|
86
|
+
|
|
87
|
+
def _type_head_names(type_head) -> tuple[str | None, str | None]:
|
|
88
|
+
"""Return (type_name, supertype_name) from a Julia `type_head`.
|
|
89
|
+
|
|
90
|
+
A bare declaration (`Foo`) exposes an `identifier`; a subtyping
|
|
91
|
+
declaration (`Foo <: Bar`) wraps both names in a `binary_expression`.
|
|
92
|
+
Both the struct and abstract-type paths need this, so parse it once.
|
|
93
|
+
"""
|
|
94
|
+
bin_expr = next(
|
|
95
|
+
(c for c in type_head.children if c.type == "binary_expression"), None
|
|
96
|
+
)
|
|
97
|
+
if bin_expr:
|
|
98
|
+
identifiers = [c for c in bin_expr.children if c.type == "identifier"]
|
|
99
|
+
if identifiers:
|
|
100
|
+
name = _read_text(identifiers[0], source)
|
|
101
|
+
super_name = _read_text(identifiers[-1], source) if len(identifiers) >= 2 else None
|
|
102
|
+
return name, super_name
|
|
103
|
+
return None, None
|
|
104
|
+
name_node = next((c for c in type_head.children if c.type == "identifier"), None)
|
|
105
|
+
return (_read_text(name_node, source) if name_node else None), None
|
|
106
|
+
|
|
107
|
+
def _func_name_from_signature(sig_node) -> str | None:
|
|
108
|
+
"""Extract function name from a Julia signature node (call_expression > identifier)."""
|
|
109
|
+
for child in sig_node.children:
|
|
110
|
+
if child.type == "call_expression":
|
|
111
|
+
callee = child.children[0] if child.children else None
|
|
112
|
+
if callee and callee.type == "identifier":
|
|
113
|
+
return _read_text(callee, source)
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
def walk_calls(body_node, func_nid: str) -> None:
|
|
117
|
+
if body_node is None:
|
|
118
|
+
return
|
|
119
|
+
t = body_node.type
|
|
120
|
+
if t in ("function_definition", "short_function_definition"):
|
|
121
|
+
return
|
|
122
|
+
if t == "call_expression" and body_node.children:
|
|
123
|
+
callee = body_node.children[0]
|
|
124
|
+
# Direct call: foo(...)
|
|
125
|
+
if callee.type == "identifier":
|
|
126
|
+
callee_name = _read_text(callee, source)
|
|
127
|
+
target_nid = _make_id(stem, callee_name)
|
|
128
|
+
add_edge(func_nid, target_nid, "calls", body_node.start_point[0] + 1,
|
|
129
|
+
confidence="EXTRACTED", context="call")
|
|
130
|
+
# Method call: obj.method(...)
|
|
131
|
+
elif callee.type == "field_expression" and len(callee.children) >= 3:
|
|
132
|
+
method_node = callee.children[-1]
|
|
133
|
+
method_name = _read_text(method_node, source)
|
|
134
|
+
target_nid = _make_id(stem, method_name)
|
|
135
|
+
add_edge(func_nid, target_nid, "calls", body_node.start_point[0] + 1,
|
|
136
|
+
confidence="EXTRACTED", context="call")
|
|
137
|
+
for child in body_node.children:
|
|
138
|
+
walk_calls(child, func_nid)
|
|
139
|
+
|
|
140
|
+
def walk(node, scope_nid: str) -> None:
|
|
141
|
+
t = node.type
|
|
142
|
+
|
|
143
|
+
# Module
|
|
144
|
+
if t == "module_definition":
|
|
145
|
+
name_node = next((c for c in node.children if c.type == "identifier"), None)
|
|
146
|
+
if name_node:
|
|
147
|
+
mod_name = _read_text(name_node, source)
|
|
148
|
+
mod_nid = _make_id(stem, mod_name)
|
|
149
|
+
line = node.start_point[0] + 1
|
|
150
|
+
add_node(mod_nid, mod_name, line)
|
|
151
|
+
add_edge(file_nid, mod_nid, "defines", line)
|
|
152
|
+
for child in node.children:
|
|
153
|
+
walk(child, mod_nid)
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
# Struct (struct / mutable struct — both map to struct_definition in tree-sitter-julia)
|
|
157
|
+
if t == "struct_definition":
|
|
158
|
+
# type_head may contain: identifier (simple) or binary_expression (Foo <: Bar)
|
|
159
|
+
type_head = next((c for c in node.children if c.type == "type_head"), None)
|
|
160
|
+
if not type_head:
|
|
161
|
+
return
|
|
162
|
+
struct_name, super_name = _type_head_names(type_head)
|
|
163
|
+
if not struct_name:
|
|
164
|
+
return
|
|
165
|
+
struct_nid = _make_id(stem, struct_name)
|
|
166
|
+
line = node.start_point[0] + 1
|
|
167
|
+
add_node(struct_nid, struct_name, line)
|
|
168
|
+
add_edge(scope_nid, struct_nid, "defines", line)
|
|
169
|
+
if super_name:
|
|
170
|
+
add_edge(struct_nid, ensure_named_node(super_name, line),
|
|
171
|
+
"inherits", line, confidence="EXTRACTED")
|
|
172
|
+
# Field types: each `name::Type` lowers to a typed_expression child of struct_definition
|
|
173
|
+
for child in node.children:
|
|
174
|
+
if child.type == "typed_expression":
|
|
175
|
+
type_ids = [c for c in child.children if c.type == "identifier"]
|
|
176
|
+
if len(type_ids) >= 2:
|
|
177
|
+
field_line = child.start_point[0] + 1
|
|
178
|
+
type_name = _read_text(type_ids[-1], source)
|
|
179
|
+
type_nid = ensure_named_node(type_name, field_line)
|
|
180
|
+
edges.append(_semantic_reference_edge(
|
|
181
|
+
struct_nid, type_nid, "field", str_path, field_line))
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# Abstract type
|
|
185
|
+
if t == "abstract_definition":
|
|
186
|
+
# type_head is a bare `identifier` (`abstract type Foo end`) or a
|
|
187
|
+
# `binary_expression` for the subtyping form (`abstract type Foo <: Bar end`).
|
|
188
|
+
# The latter was dropped entirely — abstract types are the backbone of
|
|
189
|
+
# Julia's dispatch hierarchies, so an intermediate `Foo <: Bar` vanishing
|
|
190
|
+
# broke the inheritance chain and lost the type node itself.
|
|
191
|
+
type_head = next((c for c in node.children if c.type == "type_head"), None)
|
|
192
|
+
if type_head:
|
|
193
|
+
abs_name, super_name = _type_head_names(type_head)
|
|
194
|
+
if abs_name:
|
|
195
|
+
abs_nid = _make_id(stem, abs_name)
|
|
196
|
+
line = node.start_point[0] + 1
|
|
197
|
+
add_node(abs_nid, abs_name, line)
|
|
198
|
+
add_edge(scope_nid, abs_nid, "defines", line)
|
|
199
|
+
if super_name:
|
|
200
|
+
add_edge(abs_nid, ensure_named_node(super_name, line),
|
|
201
|
+
"inherits", line, confidence="EXTRACTED")
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
# Function: function foo(...) ... end
|
|
205
|
+
if t == "function_definition":
|
|
206
|
+
sig_node = next((c for c in node.children if c.type == "signature"), None)
|
|
207
|
+
if sig_node:
|
|
208
|
+
func_name = _func_name_from_signature(sig_node)
|
|
209
|
+
if func_name:
|
|
210
|
+
func_nid = _make_id(stem, func_name)
|
|
211
|
+
line = node.start_point[0] + 1
|
|
212
|
+
add_node(func_nid, f"{func_name}()", line)
|
|
213
|
+
add_edge(scope_nid, func_nid, "defines", line)
|
|
214
|
+
function_bodies.append((func_nid, node))
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
# Short function: foo(x) = expr
|
|
218
|
+
if t == "assignment":
|
|
219
|
+
lhs = node.children[0] if node.children else None
|
|
220
|
+
if lhs and lhs.type == "call_expression" and lhs.children:
|
|
221
|
+
callee = lhs.children[0]
|
|
222
|
+
if callee.type == "identifier":
|
|
223
|
+
func_name = _read_text(callee, source)
|
|
224
|
+
func_nid = _make_id(stem, func_name)
|
|
225
|
+
line = node.start_point[0] + 1
|
|
226
|
+
add_node(func_nid, f"{func_name}()", line)
|
|
227
|
+
add_edge(scope_nid, func_nid, "defines", line)
|
|
228
|
+
# Only walk the RHS (index 2 after lhs and operator) to avoid self-loops
|
|
229
|
+
rhs = node.children[-1] if len(node.children) >= 3 else None
|
|
230
|
+
if rhs:
|
|
231
|
+
function_bodies.append((func_nid, rhs))
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
# Using / Import
|
|
235
|
+
if t in ("using_statement", "import_statement"):
|
|
236
|
+
line = node.start_point[0] + 1
|
|
237
|
+
|
|
238
|
+
def _julia_mod_name(n):
|
|
239
|
+
# identifier (`Foo`), scoped_identifier (`Base.Threads`), or
|
|
240
|
+
# import_path (relative `..Sibling`) -> the module name. Only bare
|
|
241
|
+
# identifiers were handled, so qualified/relative imports — and the
|
|
242
|
+
# scoped package of a `selected_import` — were silently dropped.
|
|
243
|
+
if n.type == "import_path":
|
|
244
|
+
ids = [c for c in n.children if c.type == "identifier"]
|
|
245
|
+
return _read_text(ids[-1], source) if ids else None
|
|
246
|
+
if n.type in ("identifier", "scoped_identifier"):
|
|
247
|
+
return _read_text(n, source)
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
def _emit_import(name):
|
|
251
|
+
if not name:
|
|
252
|
+
return
|
|
253
|
+
imp_nid = _make_id(name)
|
|
254
|
+
add_node(imp_nid, name, line)
|
|
255
|
+
add_edge(scope_nid, imp_nid, "imports", line, context="import")
|
|
256
|
+
|
|
257
|
+
for child in node.children:
|
|
258
|
+
if child.type in ("identifier", "scoped_identifier", "import_path"):
|
|
259
|
+
_emit_import(_julia_mod_name(child))
|
|
260
|
+
elif child.type == "selected_import":
|
|
261
|
+
# `import Base.Threads: nthreads` — the package (first named
|
|
262
|
+
# child) may itself be a scoped_identifier/import_path.
|
|
263
|
+
pkg = next(
|
|
264
|
+
(c for c in child.children
|
|
265
|
+
if c.type in ("identifier", "scoped_identifier", "import_path")),
|
|
266
|
+
None,
|
|
267
|
+
)
|
|
268
|
+
if pkg is not None:
|
|
269
|
+
_emit_import(_julia_mod_name(pkg))
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
for child in node.children:
|
|
273
|
+
walk(child, scope_nid)
|
|
274
|
+
|
|
275
|
+
walk(root, file_nid)
|
|
276
|
+
|
|
277
|
+
for func_nid, body_node in function_bodies:
|
|
278
|
+
# For function_definition nodes, walk children directly to avoid
|
|
279
|
+
# the boundary check returning early on the top-level node itself.
|
|
280
|
+
# Skip the "signature" child — it contains the function's own call_expression
|
|
281
|
+
# which would create a self-loop.
|
|
282
|
+
if body_node.type == "function_definition":
|
|
283
|
+
for child in body_node.children:
|
|
284
|
+
if child.type != "signature":
|
|
285
|
+
walk_calls(child, func_nid)
|
|
286
|
+
else:
|
|
287
|
+
walk_calls(body_node, func_nid)
|
|
288
|
+
|
|
289
|
+
return {"nodes": nodes, "edges": edges}
|