trail-code 0.0.0-stage → 0.28.0-semantic.3
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.
- package/LICENSE +21 -0
- package/README.md +567 -2
- package/dist/ai/crux.d.ts +88 -0
- package/dist/ai/crux.d.ts.map +1 -0
- package/dist/ai/crux.js +219 -0
- package/dist/ai/crux.js.map +1 -0
- package/dist/ai/failure.d.ts +60 -0
- package/dist/ai/failure.d.ts.map +1 -0
- package/dist/ai/failure.js +86 -0
- package/dist/ai/failure.js.map +1 -0
- package/dist/ai/llm/anthropic.d.ts +37 -0
- package/dist/ai/llm/anthropic.d.ts.map +1 -0
- package/dist/ai/llm/anthropic.js +144 -0
- package/dist/ai/llm/anthropic.js.map +1 -0
- package/dist/ai/llm/factory.d.ts +28 -0
- package/dist/ai/llm/factory.d.ts.map +1 -0
- package/dist/ai/llm/factory.js +36 -0
- package/dist/ai/llm/factory.js.map +1 -0
- package/dist/ai/llm/litellm.d.ts +34 -0
- package/dist/ai/llm/litellm.d.ts.map +1 -0
- package/dist/ai/llm/litellm.js +45 -0
- package/dist/ai/llm/litellm.js.map +1 -0
- package/dist/ai/llm/openai.d.ts +145 -0
- package/dist/ai/llm/openai.d.ts.map +1 -0
- package/dist/ai/llm/openai.js +629 -0
- package/dist/ai/llm/openai.js.map +1 -0
- package/dist/ai/llm/orcarouter.d.ts +34 -0
- package/dist/ai/llm/orcarouter.d.ts.map +1 -0
- package/dist/ai/llm/orcarouter.js +45 -0
- package/dist/ai/llm/orcarouter.js.map +1 -0
- package/dist/ai/llm/recover-tool.d.ts +21 -0
- package/dist/ai/llm/recover-tool.d.ts.map +1 -0
- package/dist/ai/llm/recover-tool.js +109 -0
- package/dist/ai/llm/recover-tool.js.map +1 -0
- package/dist/ai/llm/types.d.ts +122 -0
- package/dist/ai/llm/types.d.ts.map +1 -0
- package/dist/ai/llm/types.js +33 -0
- package/dist/ai/llm/types.js.map +1 -0
- package/dist/ai/providers.d.ts +59 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +54 -0
- package/dist/ai/providers.js.map +1 -0
- package/dist/ai/summarize.d.ts +25 -0
- package/dist/ai/summarize.d.ts.map +1 -0
- package/dist/ai/summarize.js +34 -0
- package/dist/ai/summarize.js.map +1 -0
- package/dist/ai/synthesize.d.ts +40 -0
- package/dist/ai/synthesize.d.ts.map +1 -0
- package/dist/ai/synthesize.js +230 -0
- package/dist/ai/synthesize.js.map +1 -0
- package/dist/ask/ask.d.ts +199 -0
- package/dist/ask/ask.d.ts.map +1 -0
- package/dist/ask/ask.js +1291 -0
- package/dist/ask/ask.js.map +1 -0
- package/dist/ask/file-rank.d.ts +72 -0
- package/dist/ask/file-rank.d.ts.map +1 -0
- package/dist/ask/file-rank.js +160 -0
- package/dist/ask/file-rank.js.map +1 -0
- package/dist/ask/file-selection.d.ts +22 -0
- package/dist/ask/file-selection.d.ts.map +1 -0
- package/dist/ask/file-selection.js +66 -0
- package/dist/ask/file-selection.js.map +1 -0
- package/dist/ask/fuse.d.ts +195 -0
- package/dist/ask/fuse.d.ts.map +1 -0
- package/dist/ask/fuse.js +344 -0
- package/dist/ask/fuse.js.map +1 -0
- package/dist/ask/graphrank.d.ts +61 -0
- package/dist/ask/graphrank.d.ts.map +1 -0
- package/dist/ask/graphrank.js +130 -0
- package/dist/ask/graphrank.js.map +1 -0
- package/dist/ask/index-file.d.ts +44 -0
- package/dist/ask/index-file.d.ts.map +1 -0
- package/dist/ask/index-file.js +128 -0
- package/dist/ask/index-file.js.map +1 -0
- package/dist/ask/semantic.d.ts +42 -0
- package/dist/ask/semantic.d.ts.map +1 -0
- package/dist/ask/semantic.js +293 -0
- package/dist/ask/semantic.js.map +1 -0
- package/dist/bin/graft.d.ts +3 -0
- package/dist/bin/graft.d.ts.map +1 -0
- package/dist/bin/graft.js +14 -0
- package/dist/bin/graft.js.map +1 -0
- package/dist/bin/trail.d.ts +3 -0
- package/dist/bin/trail.d.ts.map +1 -0
- package/dist/bin/trail.js +13 -0
- package/dist/bin/trail.js.map +1 -0
- package/dist/blast/blast-cli.d.ts +35 -0
- package/dist/blast/blast-cli.d.ts.map +1 -0
- package/dist/blast/blast-cli.js +155 -0
- package/dist/blast/blast-cli.js.map +1 -0
- package/dist/blast/blast.d.ts +128 -0
- package/dist/blast/blast.d.ts.map +1 -0
- package/dist/blast/blast.js +383 -0
- package/dist/blast/blast.js.map +1 -0
- package/dist/blast/diff.d.ts +53 -0
- package/dist/blast/diff.d.ts.map +1 -0
- package/dist/blast/diff.js +193 -0
- package/dist/blast/diff.js.map +1 -0
- package/dist/blast/evidence.d.ts +51 -0
- package/dist/blast/evidence.d.ts.map +1 -0
- package/dist/blast/evidence.js +162 -0
- package/dist/blast/evidence.js.map +1 -0
- package/dist/blast/modules.d.ts +33 -0
- package/dist/blast/modules.d.ts.map +1 -0
- package/dist/blast/modules.js +83 -0
- package/dist/blast/modules.js.map +1 -0
- package/dist/blast/name.d.ts +74 -0
- package/dist/blast/name.d.ts.map +1 -0
- package/dist/blast/name.js +269 -0
- package/dist/blast/name.js.map +1 -0
- package/dist/blast/owners.d.ts +85 -0
- package/dist/blast/owners.d.ts.map +1 -0
- package/dist/blast/owners.js +272 -0
- package/dist/blast/owners.js.map +1 -0
- package/dist/blast/render.d.ts +46 -0
- package/dist/blast/render.d.ts.map +1 -0
- package/dist/blast/render.js +422 -0
- package/dist/blast/render.js.map +1 -0
- package/dist/blast/viz.d.ts +37 -0
- package/dist/blast/viz.d.ts.map +1 -0
- package/dist/blast/viz.js +180 -0
- package/dist/blast/viz.js.map +1 -0
- package/dist/brand.d.ts +111 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +282 -0
- package/dist/brand.js.map +1 -0
- package/dist/claude/build-progress.d.ts +30 -0
- package/dist/claude/build-progress.d.ts.map +1 -0
- package/dist/claude/build-progress.js +81 -0
- package/dist/claude/build-progress.js.map +1 -0
- package/dist/claude/hermes-skill-template.d.ts +16 -0
- package/dist/claude/hermes-skill-template.d.ts.map +1 -0
- package/dist/claude/hermes-skill-template.js +154 -0
- package/dist/claude/hermes-skill-template.js.map +1 -0
- package/dist/claude/init.d.ts +65 -0
- package/dist/claude/init.d.ts.map +1 -0
- package/dist/claude/init.js +258 -0
- package/dist/claude/init.js.map +1 -0
- package/dist/claude/paths.d.ts +9 -0
- package/dist/claude/paths.d.ts.map +1 -0
- package/dist/claude/paths.js +22 -0
- package/dist/claude/paths.js.map +1 -0
- package/dist/claude/session-metrics.d.ts +73 -0
- package/dist/claude/session-metrics.d.ts.map +1 -0
- package/dist/claude/session-metrics.js +209 -0
- package/dist/claude/session-metrics.js.map +1 -0
- package/dist/claude/settings-merge.d.ts +76 -0
- package/dist/claude/settings-merge.d.ts.map +1 -0
- package/dist/claude/settings-merge.js +283 -0
- package/dist/claude/settings-merge.js.map +1 -0
- package/dist/claude/skill-template.d.ts +13 -0
- package/dist/claude/skill-template.d.ts.map +1 -0
- package/dist/claude/skill-template.js +290 -0
- package/dist/claude/skill-template.js.map +1 -0
- package/dist/claude/state.d.ts +85 -0
- package/dist/claude/state.d.ts.map +1 -0
- package/dist/claude/state.js +88 -0
- package/dist/claude/state.js.map +1 -0
- package/dist/claude/stats.d.ts +5 -0
- package/dist/claude/stats.d.ts.map +1 -0
- package/dist/claude/stats.js +24 -0
- package/dist/claude/stats.js.map +1 -0
- package/dist/cli-epilogue.d.ts +12 -0
- package/dist/cli-epilogue.d.ts.map +1 -0
- package/dist/cli-epilogue.js +72 -0
- package/dist/cli-epilogue.js.map +1 -0
- package/dist/cli-meta.d.ts +82 -0
- package/dist/cli-meta.d.ts.map +1 -0
- package/dist/cli-meta.js +238 -0
- package/dist/cli-meta.js.map +1 -0
- package/dist/cli-picker.d.ts +139 -0
- package/dist/cli-picker.d.ts.map +1 -0
- package/dist/cli-picker.js +439 -0
- package/dist/cli-picker.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1638 -0
- package/dist/cli.js.map +1 -0
- package/dist/context/batches.d.ts +29 -0
- package/dist/context/batches.d.ts.map +1 -0
- package/dist/context/batches.js +130 -0
- package/dist/context/batches.js.map +1 -0
- package/dist/context/build.d.ts +57 -0
- package/dist/context/build.d.ts.map +1 -0
- package/dist/context/build.js +396 -0
- package/dist/context/build.js.map +1 -0
- package/dist/context/check.d.ts +42 -0
- package/dist/context/check.d.ts.map +1 -0
- package/dist/context/check.js +162 -0
- package/dist/context/check.js.map +1 -0
- package/dist/context/map-dir.d.ts +13 -0
- package/dist/context/map-dir.d.ts.map +1 -0
- package/dist/context/map-dir.js +81 -0
- package/dist/context/map-dir.js.map +1 -0
- package/dist/context/node-file.d.ts +109 -0
- package/dist/context/node-file.d.ts.map +1 -0
- package/dist/context/node-file.js +390 -0
- package/dist/context/node-file.js.map +1 -0
- package/dist/context/price.d.ts +49 -0
- package/dist/context/price.d.ts.map +1 -0
- package/dist/context/price.js +83 -0
- package/dist/context/price.js.map +1 -0
- package/dist/context/savings.d.ts +67 -0
- package/dist/context/savings.d.ts.map +1 -0
- package/dist/context/savings.js +125 -0
- package/dist/context/savings.js.map +1 -0
- package/dist/engine.d.ts +85 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +122 -0
- package/dist/engine.js.map +1 -0
- package/dist/graph/bindings.d.ts +64 -0
- package/dist/graph/bindings.d.ts.map +1 -0
- package/dist/graph/bindings.js +713 -0
- package/dist/graph/bindings.js.map +1 -0
- package/dist/graph/build-run.d.ts +25 -0
- package/dist/graph/build-run.d.ts.map +1 -0
- package/dist/graph/build-run.js +121 -0
- package/dist/graph/build-run.js.map +1 -0
- package/dist/graph/build.d.ts +88 -0
- package/dist/graph/build.d.ts.map +1 -0
- package/dist/graph/build.js +386 -0
- package/dist/graph/build.js.map +1 -0
- package/dist/graph/cards.d.ts +46 -0
- package/dist/graph/cards.d.ts.map +1 -0
- package/dist/graph/cards.js +280 -0
- package/dist/graph/cards.js.map +1 -0
- package/dist/graph/check.d.ts +25 -0
- package/dist/graph/check.d.ts.map +1 -0
- package/dist/graph/check.js +192 -0
- package/dist/graph/check.js.map +1 -0
- package/dist/graph/container.d.ts +63 -0
- package/dist/graph/container.d.ts.map +1 -0
- package/dist/graph/container.js +191 -0
- package/dist/graph/container.js.map +1 -0
- package/dist/graph/depth-parser.d.ts +61 -0
- package/dist/graph/depth-parser.d.ts.map +1 -0
- package/dist/graph/depth-parser.js +153 -0
- package/dist/graph/depth-parser.js.map +1 -0
- package/dist/graph/enrich.d.ts +62 -0
- package/dist/graph/enrich.d.ts.map +1 -0
- package/dist/graph/enrich.js +258 -0
- package/dist/graph/enrich.js.map +1 -0
- package/dist/graph/extract-cache.d.ts +101 -0
- package/dist/graph/extract-cache.d.ts.map +1 -0
- package/dist/graph/extract-cache.js +199 -0
- package/dist/graph/extract-cache.js.map +1 -0
- package/dist/graph/extract.d.ts +85 -0
- package/dist/graph/extract.d.ts.map +1 -0
- package/dist/graph/extract.js +2377 -0
- package/dist/graph/extract.js.map +1 -0
- package/dist/graph/fingerprint.d.ts +76 -0
- package/dist/graph/fingerprint.d.ts.map +1 -0
- package/dist/graph/fingerprint.js +160 -0
- package/dist/graph/fingerprint.js.map +1 -0
- package/dist/graph/generic.d.ts +74 -0
- package/dist/graph/generic.d.ts.map +1 -0
- package/dist/graph/generic.js +773 -0
- package/dist/graph/generic.js.map +1 -0
- package/dist/graph/invariants.d.ts +33 -0
- package/dist/graph/invariants.d.ts.map +1 -0
- package/dist/graph/invariants.js +52 -0
- package/dist/graph/invariants.js.map +1 -0
- package/dist/graph/load.d.ts +29 -0
- package/dist/graph/load.d.ts.map +1 -0
- package/dist/graph/load.js +97 -0
- package/dist/graph/load.js.map +1 -0
- package/dist/graph/lsp/client.d.ts +61 -0
- package/dist/graph/lsp/client.d.ts.map +1 -0
- package/dist/graph/lsp/client.js +217 -0
- package/dist/graph/lsp/client.js.map +1 -0
- package/dist/graph/lsp/enrich.d.ts +11 -0
- package/dist/graph/lsp/enrich.d.ts.map +1 -0
- package/dist/graph/lsp/enrich.js +163 -0
- package/dist/graph/lsp/enrich.js.map +1 -0
- package/dist/graph/lsp/registry.d.ts +22 -0
- package/dist/graph/lsp/registry.d.ts.map +1 -0
- package/dist/graph/lsp/registry.js +71 -0
- package/dist/graph/lsp/registry.js.map +1 -0
- package/dist/graph/map.d.ts +107 -0
- package/dist/graph/map.d.ts.map +1 -0
- package/dist/graph/map.js +239 -0
- package/dist/graph/map.js.map +1 -0
- package/dist/graph/markdown.d.ts +22 -0
- package/dist/graph/markdown.d.ts.map +1 -0
- package/dist/graph/markdown.js +143 -0
- package/dist/graph/markdown.js.map +1 -0
- package/dist/graph/native.d.ts +46 -0
- package/dist/graph/native.d.ts.map +1 -0
- package/dist/graph/native.js +169 -0
- package/dist/graph/native.js.map +1 -0
- package/dist/graph/queries/c.scm +13 -0
- package/dist/graph/queries/c_sharp.scm +19 -0
- package/dist/graph/queries/clojure.scm +58 -0
- package/dist/graph/queries/cpp.scm +23 -0
- package/dist/graph/queries/dart.scm +42 -0
- package/dist/graph/queries/elixir.scm +54 -0
- package/dist/graph/queries/gleam.scm +51 -0
- package/dist/graph/queries/java.scm +20 -0
- package/dist/graph/queries/kotlin.scm +43 -0
- package/dist/graph/queries/lua.scm +36 -0
- package/dist/graph/queries/nix.scm +32 -0
- package/dist/graph/queries/php.scm +40 -0
- package/dist/graph/queries/ruby.scm +65 -0
- package/dist/graph/queries/rust.scm +41 -0
- package/dist/graph/queries/scala.scm +66 -0
- package/dist/graph/queries/solidity.scm +43 -0
- package/dist/graph/queries/swift.scm +51 -0
- package/dist/graph/refresh.d.ts +106 -0
- package/dist/graph/refresh.d.ts.map +1 -0
- package/dist/graph/refresh.js +430 -0
- package/dist/graph/refresh.js.map +1 -0
- package/dist/graph/relations.d.ts +14 -0
- package/dist/graph/relations.d.ts.map +1 -0
- package/dist/graph/relations.js +8 -0
- package/dist/graph/relations.js.map +1 -0
- package/dist/graph/resolve.d.ts +17 -0
- package/dist/graph/resolve.d.ts.map +1 -0
- package/dist/graph/resolve.js +716 -0
- package/dist/graph/resolve.js.map +1 -0
- package/dist/graph/root.d.ts +11 -0
- package/dist/graph/root.d.ts.map +1 -0
- package/dist/graph/root.js +40 -0
- package/dist/graph/root.js.map +1 -0
- package/dist/graph/scopes.d.ts +40 -0
- package/dist/graph/scopes.d.ts.map +1 -0
- package/dist/graph/scopes.js +383 -0
- package/dist/graph/scopes.js.map +1 -0
- package/dist/graph/seed.d.ts +38 -0
- package/dist/graph/seed.d.ts.map +1 -0
- package/dist/graph/seed.js +229 -0
- package/dist/graph/seed.js.map +1 -0
- package/dist/graph/source-files.d.ts +46 -0
- package/dist/graph/source-files.d.ts.map +1 -0
- package/dist/graph/source-files.js +118 -0
- package/dist/graph/source-files.js.map +1 -0
- package/dist/graph/traverse-cli.d.ts +55 -0
- package/dist/graph/traverse-cli.d.ts.map +1 -0
- package/dist/graph/traverse-cli.js +189 -0
- package/dist/graph/traverse-cli.js.map +1 -0
- package/dist/graph/traverse.d.ts +112 -0
- package/dist/graph/traverse.d.ts.map +1 -0
- package/dist/graph/traverse.js +192 -0
- package/dist/graph/traverse.js.map +1 -0
- package/dist/graph/types.d.ts +78 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +14 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/graph/wasm-runtime.d.ts +19 -0
- package/dist/graph/wasm-runtime.d.ts.map +1 -0
- package/dist/graph/wasm-runtime.js +38 -0
- package/dist/graph/wasm-runtime.js.map +1 -0
- package/dist/graph/workspace-cli.d.ts +56 -0
- package/dist/graph/workspace-cli.d.ts.map +1 -0
- package/dist/graph/workspace-cli.js +111 -0
- package/dist/graph/workspace-cli.js.map +1 -0
- package/dist/graph/workspace.d.ts +172 -0
- package/dist/graph/workspace.d.ts.map +1 -0
- package/dist/graph/workspace.js +714 -0
- package/dist/graph/workspace.js.map +1 -0
- package/dist/graph/write.d.ts +13 -0
- package/dist/graph/write.d.ts.map +1 -0
- package/dist/graph/write.js +86 -0
- package/dist/graph/write.js.map +1 -0
- package/dist/hosts/antigravity.d.ts +14 -0
- package/dist/hosts/antigravity.d.ts.map +1 -0
- package/dist/hosts/antigravity.js +59 -0
- package/dist/hosts/antigravity.js.map +1 -0
- package/dist/hosts/auto.d.ts +37 -0
- package/dist/hosts/auto.d.ts.map +1 -0
- package/dist/hosts/auto.js +141 -0
- package/dist/hosts/auto.js.map +1 -0
- package/dist/hosts/claude-global.d.ts +72 -0
- package/dist/hosts/claude-global.d.ts.map +1 -0
- package/dist/hosts/claude-global.js +230 -0
- package/dist/hosts/claude-global.js.map +1 -0
- package/dist/hosts/config-write.d.ts +36 -0
- package/dist/hosts/config-write.d.ts.map +1 -0
- package/dist/hosts/config-write.js +64 -0
- package/dist/hosts/config-write.js.map +1 -0
- package/dist/hosts/init.d.ts +28 -0
- package/dist/hosts/init.d.ts.map +1 -0
- package/dist/hosts/init.js +89 -0
- package/dist/hosts/init.js.map +1 -0
- package/dist/hosts/instructions.d.ts +14 -0
- package/dist/hosts/instructions.d.ts.map +1 -0
- package/dist/hosts/instructions.js +95 -0
- package/dist/hosts/instructions.js.map +1 -0
- package/dist/hosts/mcp-config.d.ts +78 -0
- package/dist/hosts/mcp-config.d.ts.map +1 -0
- package/dist/hosts/mcp-config.js +251 -0
- package/dist/hosts/mcp-config.js.map +1 -0
- package/dist/hosts/plan.d.ts +32 -0
- package/dist/hosts/plan.d.ts.map +1 -0
- package/dist/hosts/plan.js +73 -0
- package/dist/hosts/plan.js.map +1 -0
- package/dist/hosts/registry.d.ts +41 -0
- package/dist/hosts/registry.d.ts.map +1 -0
- package/dist/hosts/registry.js +164 -0
- package/dist/hosts/registry.js.map +1 -0
- package/dist/hosts/retract.d.ts +47 -0
- package/dist/hosts/retract.d.ts.map +1 -0
- package/dist/hosts/retract.js +520 -0
- package/dist/hosts/retract.js.map +1 -0
- package/dist/hosts/sections.d.ts +47 -0
- package/dist/hosts/sections.d.ts.map +1 -0
- package/dist/hosts/sections.js +127 -0
- package/dist/hosts/sections.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/fs.d.ts +79 -0
- package/dist/ingest/fs.d.ts.map +1 -0
- package/dist/ingest/fs.js +306 -0
- package/dist/ingest/fs.js.map +1 -0
- package/dist/init-guard.d.ts +16 -0
- package/dist/init-guard.d.ts.map +1 -0
- package/dist/init-guard.js +27 -0
- package/dist/init-guard.js.map +1 -0
- package/dist/mcp/instructions.d.ts +5 -0
- package/dist/mcp/instructions.d.ts.map +1 -0
- package/dist/mcp/instructions.js +64 -0
- package/dist/mcp/instructions.js.map +1 -0
- package/dist/mcp/kept-listing.d.ts +27 -0
- package/dist/mcp/kept-listing.d.ts.map +1 -0
- package/dist/mcp/kept-listing.js +63 -0
- package/dist/mcp/kept-listing.js.map +1 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +147 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tool-names.d.ts +27 -0
- package/dist/mcp/tool-names.d.ts.map +1 -0
- package/dist/mcp/tool-names.js +47 -0
- package/dist/mcp/tool-names.js.map +1 -0
- package/dist/mcp/tools.d.ts +22 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +440 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/node-version.d.ts +15 -0
- package/dist/node-version.d.ts.map +1 -0
- package/dist/node-version.js +23 -0
- package/dist/node-version.js.map +1 -0
- package/dist/search/grep-cli.d.ts +35 -0
- package/dist/search/grep-cli.d.ts.map +1 -0
- package/dist/search/grep-cli.js +115 -0
- package/dist/search/grep-cli.js.map +1 -0
- package/dist/search/grep.d.ts +60 -0
- package/dist/search/grep.d.ts.map +1 -0
- package/dist/search/grep.js +143 -0
- package/dist/search/grep.js.map +1 -0
- package/dist/search/ignored-code.d.ts +9 -0
- package/dist/search/ignored-code.d.ts.map +1 -0
- package/dist/search/ignored-code.js +73 -0
- package/dist/search/ignored-code.js.map +1 -0
- package/dist/search/user-regexp.d.ts +26 -0
- package/dist/search/user-regexp.d.ts.map +1 -0
- package/dist/search/user-regexp.js +150 -0
- package/dist/search/user-regexp.js.map +1 -0
- package/dist/telemetry/contract.d.ts +117 -0
- package/dist/telemetry/contract.d.ts.map +1 -0
- package/dist/telemetry/contract.js +341 -0
- package/dist/telemetry/contract.js.map +1 -0
- package/dist/telemetry/flush.d.ts +21 -0
- package/dist/telemetry/flush.d.ts.map +1 -0
- package/dist/telemetry/flush.js +85 -0
- package/dist/telemetry/flush.js.map +1 -0
- package/dist/telemetry/gate.d.ts +30 -0
- package/dist/telemetry/gate.d.ts.map +1 -0
- package/dist/telemetry/gate.js +81 -0
- package/dist/telemetry/gate.js.map +1 -0
- package/dist/telemetry/identity.d.ts +40 -0
- package/dist/telemetry/identity.d.ts.map +1 -0
- package/dist/telemetry/identity.js +80 -0
- package/dist/telemetry/identity.js.map +1 -0
- package/dist/telemetry/index.d.ts +18 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +16 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/key.d.ts +21 -0
- package/dist/telemetry/key.d.ts.map +1 -0
- package/dist/telemetry/key.js +49 -0
- package/dist/telemetry/key.js.map +1 -0
- package/dist/telemetry/notice.d.ts +19 -0
- package/dist/telemetry/notice.d.ts.map +1 -0
- package/dist/telemetry/notice.js +93 -0
- package/dist/telemetry/notice.js.map +1 -0
- package/dist/telemetry/queue.d.ts +35 -0
- package/dist/telemetry/queue.d.ts.map +1 -0
- package/dist/telemetry/queue.js +162 -0
- package/dist/telemetry/queue.js.map +1 -0
- package/dist/telemetry/send.d.ts +19 -0
- package/dist/telemetry/send.d.ts.map +1 -0
- package/dist/telemetry/send.js +84 -0
- package/dist/telemetry/send.js.map +1 -0
- package/dist/telemetry/sessions.d.ts +25 -0
- package/dist/telemetry/sessions.d.ts.map +1 -0
- package/dist/telemetry/sessions.js +104 -0
- package/dist/telemetry/sessions.js.map +1 -0
- package/dist/telemetry/track.d.ts +62 -0
- package/dist/telemetry/track.d.ts.map +1 -0
- package/dist/telemetry/track.js +143 -0
- package/dist/telemetry/track.js.map +1 -0
- package/dist/upkeep-run.d.ts +22 -0
- package/dist/upkeep-run.d.ts.map +1 -0
- package/dist/upkeep-run.js +85 -0
- package/dist/upkeep-run.js.map +1 -0
- package/dist/upkeep.d.ts +127 -0
- package/dist/upkeep.d.ts.map +1 -0
- package/dist/upkeep.js +252 -0
- package/dist/upkeep.js.map +1 -0
- package/dist/util/actions-folder.d.ts +2 -0
- package/dist/util/actions-folder.d.ts.map +1 -0
- package/dist/util/actions-folder.js +9 -0
- package/dist/util/actions-folder.js.map +1 -0
- package/dist/util/browser.d.ts +3 -0
- package/dist/util/browser.d.ts.map +1 -0
- package/dist/util/browser.js +14 -0
- package/dist/util/browser.js.map +1 -0
- package/dist/util/child-process.d.ts +15 -0
- package/dist/util/child-process.d.ts.map +1 -0
- package/dist/util/child-process.js +15 -0
- package/dist/util/child-process.js.map +1 -0
- package/dist/util/front-matter.d.ts +46 -0
- package/dist/util/front-matter.d.ts.map +1 -0
- package/dist/util/front-matter.js +113 -0
- package/dist/util/front-matter.js.map +1 -0
- package/dist/util/id.d.ts +7 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +14 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/paths.d.ts +29 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +71 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/util/private-file.d.ts +14 -0
- package/dist/util/private-file.d.ts.map +1 -0
- package/dist/util/private-file.js +82 -0
- package/dist/util/private-file.js.map +1 -0
- package/dist/util/source.d.ts +4 -0
- package/dist/util/source.d.ts.map +1 -0
- package/dist/util/source.js +12 -0
- package/dist/util/source.js.map +1 -0
- package/dist/util/spinner.d.ts +15 -0
- package/dist/util/spinner.d.ts.map +1 -0
- package/dist/util/spinner.js +52 -0
- package/dist/util/spinner.js.map +1 -0
- package/dist/util/state.d.ts +120 -0
- package/dist/util/state.d.ts.map +1 -0
- package/dist/util/state.js +264 -0
- package/dist/util/state.js.map +1 -0
- package/dist/vendor/grammars/tree-sitter-go.LICENSE +21 -0
- package/dist/vendor/grammars/tree-sitter-go.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-java.LICENSE +21 -0
- package/dist/vendor/grammars/tree-sitter-java.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-kotlin.LICENSE +9 -0
- package/dist/vendor/grammars/tree-sitter-kotlin.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-php.LICENSE +22 -0
- package/dist/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-python.LICENSE +21 -0
- package/dist/vendor/grammars/tree-sitter-python.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-r.LICENSE +9 -0
- package/dist/vendor/grammars/tree-sitter-r.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-swift.LICENSE +21 -0
- package/dist/vendor/grammars/tree-sitter-swift.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-typescript.LICENSE +21 -0
- package/dist/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/viewer/app.js +2 -0
- package/dist/viewer/index.html +49 -0
- package/dist/viewer/style.css +154 -0
- package/dist/viz/assemble.d.ts +83 -0
- package/dist/viz/assemble.d.ts.map +1 -0
- package/dist/viz/assemble.js +101 -0
- package/dist/viz/assemble.js.map +1 -0
- package/dist/viz/export.d.ts +39 -0
- package/dist/viz/export.d.ts.map +1 -0
- package/dist/viz/export.js +98 -0
- package/dist/viz/export.js.map +1 -0
- package/dist/viz/serve.d.ts +13 -0
- package/dist/viz/serve.d.ts.map +1 -0
- package/dist/viz/serve.js +130 -0
- package/dist/viz/serve.js.map +1 -0
- package/package.json +115 -4
- package/scripts/build-grammar-wasm.mjs +112 -0
- package/scripts/build-viewer.mjs +44 -0
- package/scripts/graph-quality.mjs +117 -0
- package/scripts/pack-trail.mjs +53 -0
- package/scripts/postinstall.mjs +65 -0
- package/scripts/run-tests.mjs +72 -0
- package/scripts/stamp-telemetry-key.mjs +56 -0
- package/scripts/tally-audit.mjs +140 -0
- package/scripts/vendor-grammars.mjs +53 -0
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic "breadth" extractor tier — one language-agnostic extractor over any
|
|
3
|
+
* tree-sitter grammar + its `tags.scm` (the standard tree-sitter tags convention). This is how
|
|
4
|
+
* graft covers the long tail of languages for ~one registry row each, instead of
|
|
5
|
+
* a hand-written extractor per language (the depth tier in extract.ts).
|
|
6
|
+
*
|
|
7
|
+
* Grammars are WASM (`tree-sitter-wasm` bundle) loaded via `web-tree-sitter`, so
|
|
8
|
+
* a new language needs no native node-gyp build. Loading is async (WASM init), so
|
|
9
|
+
* callers MUST `await warmGenericGrammars([...])` once before the synchronous
|
|
10
|
+
* `extractGeneric()` is used in a build/check loop. If a grammar isn't warmed,
|
|
11
|
+
* `extractGeneric` degrades to a file node only (never throws).
|
|
12
|
+
*
|
|
13
|
+
* What it emits (signature-only): a file node + one node per `@definition.<kind>`
|
|
14
|
+
* capture, and bare-name `calls` raw edges from `@reference.call` attributed to
|
|
15
|
+
* the innermost enclosing definition. resolve.ts then resolves those calls by
|
|
16
|
+
* name for free (same-file → extracted, unique-global → inferred). Member-call
|
|
17
|
+
* receiver typing (recvType) is NOT produced here — that needs a per-language
|
|
18
|
+
* binding pass; the opt-in LSP tier fills that gap for popular languages.
|
|
19
|
+
*/
|
|
20
|
+
import { readFileSync } from "node:fs";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
import { createRequire } from "node:module";
|
|
24
|
+
import { contentHash } from "../util/id.js";
|
|
25
|
+
import { webTreeSitter } from "./wasm-runtime.js";
|
|
26
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
// queries/ ships beside the compiled JS (copied by the build); fall back to src.
|
|
29
|
+
const QUERY_DIRS = [join(HERE, "queries"), join(HERE, "..", "..", "src", "graph", "queries")];
|
|
30
|
+
/** The breadth registry. Add a row + a queries/<name>.scm to support a language.
|
|
31
|
+
* Extensions here must NOT collide with the depth tier's EXTENSIONS (extract.ts). */
|
|
32
|
+
export const GENERIC_LANGS = [
|
|
33
|
+
{ name: "rust", exts: [".rs"], wasm: "rust" },
|
|
34
|
+
{ name: "java", exts: [".java"], wasm: "java" },
|
|
35
|
+
{ name: "c", exts: [".c", ".h"], wasm: "c" },
|
|
36
|
+
{ name: "cpp", exts: [".cpp", ".cc", ".cxx", ".hpp", ".hh"], wasm: "cpp" },
|
|
37
|
+
{ name: "ruby", exts: [".rb"], wasm: "ruby" },
|
|
38
|
+
{ name: "c_sharp", exts: [".cs"], wasm: "c_sharp" },
|
|
39
|
+
// These ship a tags.scm (calls + symbols); ocaml/zig have none and use the
|
|
40
|
+
// node-kind walker fallback (symbols only) — still one row, zero query.
|
|
41
|
+
{ name: "scala", exts: [".scala", ".sc"], wasm: "scala" },
|
|
42
|
+
{ name: "elixir", exts: [".ex", ".exs"], wasm: "elixir" },
|
|
43
|
+
{ name: "solidity", exts: [".sol"], wasm: "solidity" },
|
|
44
|
+
{ name: "ocaml", exts: [".ml", ".mli"], wasm: "ocaml" },
|
|
45
|
+
{ name: "zig", exts: [".zig"], wasm: "zig" },
|
|
46
|
+
{ name: "dart", exts: [".dart"], wasm: "dart" }, // surfaced by PR #38 (@muneebshere)
|
|
47
|
+
{ name: "clojure", exts: [".clj", ".cljs", ".cljc", ".bb"], wasm: "clojure" },
|
|
48
|
+
{ name: "nix", exts: [".nix"], wasm: "nix" },
|
|
49
|
+
{ name: "lua", exts: [".lua"], wasm: "lua" },
|
|
50
|
+
{ name: "gleam", exts: [".gleam"], wasm: "gleam" },
|
|
51
|
+
];
|
|
52
|
+
/**
|
|
53
|
+
* Stand-ins for the depth tier, used only for a language whose own grammar did not
|
|
54
|
+
* load (depth-parser.ts), as after a broken install. They claim no extension, so
|
|
55
|
+
* `genericLangOf` never returns them; the build picks one by asking
|
|
56
|
+
* {@link fallbackLangOf}. Output is the
|
|
57
|
+
* breadth tier's: symbols and bare-name calls, no receiver types or bindings.
|
|
58
|
+
* Kotlin, Swift and PHP use their vendored queries/<name>.scm; the rest use the
|
|
59
|
+
* bundle's own tags.scm (typescript's builds on javascript's, as upstream does).
|
|
60
|
+
*/
|
|
61
|
+
const FALLBACK_LANGS = {
|
|
62
|
+
typescript: { name: "typescript", exts: [], wasm: "typescript", bundledTags: ["javascript", "typescript"] },
|
|
63
|
+
tsx: { name: "tsx", exts: [], wasm: "tsx", bundledTags: ["javascript", "tsx"] },
|
|
64
|
+
python: { name: "python", exts: [], wasm: "python", bundledTags: ["python"] },
|
|
65
|
+
go: { name: "go", exts: [], wasm: "go", bundledTags: ["go"] },
|
|
66
|
+
r: { name: "r", exts: [], wasm: "r", bundledTags: ["r"] },
|
|
67
|
+
kotlin: { name: "kotlin", exts: [], wasm: "kotlin" },
|
|
68
|
+
swift: { name: "swift", exts: [], wasm: "swift" },
|
|
69
|
+
php: { name: "php", exts: [], wasm: "php" },
|
|
70
|
+
};
|
|
71
|
+
/** The breadth-tier grammar that stands in for a depth-tier language whose native
|
|
72
|
+
* grammar can't load. Java already has a breadth row, so it is its own stand-in. */
|
|
73
|
+
export function fallbackLangOf(lang) {
|
|
74
|
+
return lang === "java" ? GENERIC_LANGS.find((l) => l.name === "java") : FALLBACK_LANGS[lang];
|
|
75
|
+
}
|
|
76
|
+
function rowNamed(name) {
|
|
77
|
+
return GENERIC_LANGS.find((l) => l.name === name) ?? Object.values(FALLBACK_LANGS).find((l) => l.name === name);
|
|
78
|
+
}
|
|
79
|
+
const byExt = new Map();
|
|
80
|
+
for (const l of GENERIC_LANGS)
|
|
81
|
+
for (const e of l.exts)
|
|
82
|
+
byExt.set(e, l);
|
|
83
|
+
/** The generic language for a path, or null if no breadth grammar claims it. */
|
|
84
|
+
export function genericLangOf(path) {
|
|
85
|
+
const lower = path.toLowerCase();
|
|
86
|
+
for (const [ext, l] of byExt)
|
|
87
|
+
if (lower.endsWith(ext))
|
|
88
|
+
return l;
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
/** Every file extension a breadth-tier (generic tree-sitter) grammar claims. */
|
|
92
|
+
export function genericExtensions() {
|
|
93
|
+
return GENERIC_LANGS.flatMap((l) => l.exts);
|
|
94
|
+
}
|
|
95
|
+
// tags.scm @definition.<X> → graft Kind (types.ts). Unmapped → "function".
|
|
96
|
+
const KIND = {
|
|
97
|
+
function: "function", method: "method", class: "class", interface: "interface",
|
|
98
|
+
type: "type", struct: "struct", enum: "enum", module: "module",
|
|
99
|
+
constant: "constant", variable: "variable", field: "variable",
|
|
100
|
+
object: "class", property: "variable", // Scala object; Swift/Scala property
|
|
101
|
+
};
|
|
102
|
+
const loaded = new Map();
|
|
103
|
+
let tsMod = null;
|
|
104
|
+
function requireWasm(wasm) {
|
|
105
|
+
// Resolve the grammar wasm from the tree-sitter-wasm bundle (its package.json
|
|
106
|
+
// `exports` maps the bare "<lang>/…" subpath to the actual "out/<lang>/…" file).
|
|
107
|
+
try {
|
|
108
|
+
const p = require.resolve(`tree-sitter-wasm/${wasm}/tree-sitter-${wasm}.wasm`);
|
|
109
|
+
return readFileSync(p);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Sanitize editor-specific query predicates the tree-sitter Query compiler rejects.
|
|
116
|
+
function sanitizeQuery(raw) {
|
|
117
|
+
return raw.replace(/\(#(?:strip!|set!|set-adjacent!|select-adjacent!|make-range!|offset!|gsub!)[^()]*\)/g, "");
|
|
118
|
+
}
|
|
119
|
+
function loadQuery(row) {
|
|
120
|
+
for (const dir of QUERY_DIRS) {
|
|
121
|
+
try {
|
|
122
|
+
return sanitizeQuery(readFileSync(join(dir, `${row.name}.scm`), "utf8"));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
/* try next dir */
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!row.bundledTags)
|
|
129
|
+
return null;
|
|
130
|
+
const parts = [];
|
|
131
|
+
for (const dir of row.bundledTags) {
|
|
132
|
+
try {
|
|
133
|
+
parts.push(readFileSync(require.resolve(`tree-sitter-wasm/${dir}/tags.scm`), "utf8"));
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
/* no tags.scm for this one */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return parts.length > 0 ? sanitizeQuery(parts.join("\n")) : null;
|
|
140
|
+
}
|
|
141
|
+
/** Warm the WASM grammars for the given graft lang names. Idempotent; must be
|
|
142
|
+
* awaited once before extractGeneric is used in a sync loop. Unknown/unavailable
|
|
143
|
+
* grammars are silently skipped (their files then extract as file-only). */
|
|
144
|
+
export async function warmGenericGrammars(langNames) {
|
|
145
|
+
const want = new Set(langNames);
|
|
146
|
+
const need = [...want].filter((n) => !loaded.has(n) && rowNamed(n) !== undefined);
|
|
147
|
+
if (need.length === 0)
|
|
148
|
+
return;
|
|
149
|
+
tsMod = await webTreeSitter();
|
|
150
|
+
const { Language, Query } = tsMod;
|
|
151
|
+
for (const name of need) {
|
|
152
|
+
const row = rowNamed(name);
|
|
153
|
+
const bytes = requireWasm(row.wasm);
|
|
154
|
+
if (!bytes)
|
|
155
|
+
continue;
|
|
156
|
+
try {
|
|
157
|
+
const language = await Language.load(bytes);
|
|
158
|
+
const scm = loadQuery(row);
|
|
159
|
+
let query = null;
|
|
160
|
+
if (scm) {
|
|
161
|
+
try {
|
|
162
|
+
query = new Query(language, scm);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
query = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
loaded.set(name, { language, query });
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
/* grammar failed to instantiate — skip; files extract as file-only */
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** True if a grammar has been warmed for this lang (else extractGeneric is file-only). */
|
|
176
|
+
export function isWarm(langName) {
|
|
177
|
+
return loaded.has(langName);
|
|
178
|
+
}
|
|
179
|
+
/** Test seam: swap in (or, with null, remove) a grammar entry, returning the
|
|
180
|
+
* previous one so the caller can restore it. The parse-failure path in
|
|
181
|
+
* extractGeneric needs a grammar that throws deterministically; the real crash
|
|
182
|
+
* that motivated it (tree-sitter-wasm PHP on heredocs, #139) is heap-state
|
|
183
|
+
* dependent, so tests inject a fake here instead of depending on it. */
|
|
184
|
+
export function swapGrammarForTest(name, entry) {
|
|
185
|
+
const prev = loaded.get(name) ?? null;
|
|
186
|
+
if (entry)
|
|
187
|
+
loaded.set(name, entry);
|
|
188
|
+
else
|
|
189
|
+
loaded.delete(name);
|
|
190
|
+
return prev;
|
|
191
|
+
}
|
|
192
|
+
/** Load one grammar from the tree-sitter-wasms bundle, initialising
|
|
193
|
+
* web-tree-sitter on first call. Null when the wasm is missing or won't
|
|
194
|
+
* instantiate — never throws, so a caller degrades instead of failing the build.
|
|
195
|
+
*
|
|
196
|
+
* Shared with the container tier (container.ts), which needs a grammar to find
|
|
197
|
+
* where an embedded language starts but none of the tags.scm machinery above.
|
|
198
|
+
* The runtime itself is shared with every WASM tier (wasm-runtime.ts). */
|
|
199
|
+
export async function loadWasmLanguage(wasm) {
|
|
200
|
+
tsMod = await webTreeSitter();
|
|
201
|
+
const bytes = requireWasm(wasm);
|
|
202
|
+
if (!bytes)
|
|
203
|
+
return null;
|
|
204
|
+
try {
|
|
205
|
+
return await tsMod.Language.load(bytes);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const PARSE_CHUNK = 16384; // <32KB slices — same tree-sitter limit workaround as extract.ts
|
|
212
|
+
/** Parse with an already-loaded grammar. Returns the root node, or null if the
|
|
213
|
+
* grammar was never warmed or the parse blew up. Companion to
|
|
214
|
+
* `loadWasmLanguage` for callers outside this module. */
|
|
215
|
+
export function parseWasm(language, source) {
|
|
216
|
+
if (!tsMod)
|
|
217
|
+
return null;
|
|
218
|
+
try {
|
|
219
|
+
const parser = new tsMod.Parser();
|
|
220
|
+
parser.setLanguage(language);
|
|
221
|
+
const tree = parser.parse((i) => source.slice(i, i + PARSE_CHUNK));
|
|
222
|
+
return tree?.rootNode ?? null;
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function fileNode(rel, source) {
|
|
229
|
+
return {
|
|
230
|
+
id: rel, name: rel.split("/").pop() ?? rel, kind: "file", path: rel,
|
|
231
|
+
span: `L1-L${Math.max(1, source.split("\n").length)}`, signature: null,
|
|
232
|
+
exported: true, origin: "generic", body_hash: contentHash(source),
|
|
233
|
+
chars: Buffer.byteLength(source), summary_state: "pending", summary: null, crux: null,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/** The innermost definition whose span covers the token at byte offset `at` —
|
|
237
|
+
* the caller a reference found there belongs to. Shared by the tags.scm path and
|
|
238
|
+
* the per-language passes below so both attribute edges the same way. */
|
|
239
|
+
function enclosingDef(defs, at) {
|
|
240
|
+
return defs
|
|
241
|
+
.filter((d) => d.startIndex <= at && at < d.endIndex)
|
|
242
|
+
.sort((a, b) => (a.endIndex - a.startIndex) - (b.endIndex - b.startIndex))[0];
|
|
243
|
+
}
|
|
244
|
+
/** Extract a single generic-tier file. Synchronous; needs the grammar pre-warmed.
|
|
245
|
+
* Uses the grammar's compiled tags.scm when present (symbols + call edges);
|
|
246
|
+
* otherwise falls back to a node-kind tree walker (symbols only) so ANY warmed
|
|
247
|
+
* grammar yields a graph even with no vendored query. */
|
|
248
|
+
export function extractGeneric(rel, source, langName) {
|
|
249
|
+
const nodes = [fileNode(rel, source)];
|
|
250
|
+
const rawEdges = [];
|
|
251
|
+
const entry = loaded.get(langName);
|
|
252
|
+
if (!entry || !tsMod)
|
|
253
|
+
return { nodes, rawEdges };
|
|
254
|
+
let tree;
|
|
255
|
+
try {
|
|
256
|
+
const parser = new tsMod.Parser();
|
|
257
|
+
parser.setLanguage(entry.language);
|
|
258
|
+
tree = parser.parse((i) => source.slice(i, i + PARSE_CHUNK));
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
// A grammar that throws (e.g. a WASM "memory access out of bounds" from an
|
|
262
|
+
// external scanner) is not a per-file syntax problem: swallowing it here left
|
|
263
|
+
// the file with only its file node and the build reporting success. Rethrow so
|
|
264
|
+
// build.ts records it in `errors`, the CLI prints it, and the extract cache
|
|
265
|
+
// remembers the failure instead of caching an empty result as clean.
|
|
266
|
+
throw new Error(`${langName} grammar threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
267
|
+
}
|
|
268
|
+
if (!tree)
|
|
269
|
+
return { nodes, rawEdges };
|
|
270
|
+
const minted = new Set([rel]);
|
|
271
|
+
const lines = source.split("\n");
|
|
272
|
+
const defs = [];
|
|
273
|
+
// One definition per source span: a grammar's tags.scm can capture the same node
|
|
274
|
+
// under two @definition kinds (Swift `func` is method AND function), and the
|
|
275
|
+
// walker can revisit a nested match — both would emit near-duplicate nodes.
|
|
276
|
+
const spanSeen = new Set();
|
|
277
|
+
// Mint one definition node from a whole-definition tree node. Shared by both
|
|
278
|
+
// the tags.scm path and the walker fallback so id/span/signature/body_text are
|
|
279
|
+
// built identically.
|
|
280
|
+
const mkDef = (name, kind, whole) => {
|
|
281
|
+
if (spanSeen.has(whole.startIndex))
|
|
282
|
+
return;
|
|
283
|
+
spanSeen.add(whole.startIndex);
|
|
284
|
+
const idBase = `${rel}#${name}`;
|
|
285
|
+
let id = idBase, n = 2;
|
|
286
|
+
while (minted.has(id))
|
|
287
|
+
id = `${idBase}~${n++}`;
|
|
288
|
+
minted.add(id);
|
|
289
|
+
const startRow = whole.startPosition.row, endRow = whole.endPosition.row;
|
|
290
|
+
const sigLine = (lines[startRow] ?? "").trim().replace(/\s*\{?\s*$/, "");
|
|
291
|
+
nodes.push({
|
|
292
|
+
id, name, kind, path: rel,
|
|
293
|
+
span: `L${startRow + 1}-L${endRow + 1}`,
|
|
294
|
+
signature: sigLine || null, exported: true, origin: "generic",
|
|
295
|
+
body_hash: contentHash(source.slice(whole.startIndex, whole.endIndex)),
|
|
296
|
+
body_text: source.slice(whole.startIndex, whole.endIndex).replace(/\s+/g, " ").slice(0, 5000),
|
|
297
|
+
summary_state: "pending", summary: null, crux: null,
|
|
298
|
+
});
|
|
299
|
+
defs.push({ id, startIndex: whole.startIndex, endIndex: whole.endIndex });
|
|
300
|
+
};
|
|
301
|
+
if (entry.query) {
|
|
302
|
+
tagsExtract(entry.query, tree.rootNode, rel, mkDef, defs, rawEdges, langName);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
walkExtract(tree.rootNode, mkDef); // no tags.scm → symbols only
|
|
306
|
+
}
|
|
307
|
+
// The preprocessor is invisible to tags.scm, but in C/C++ a local `#include "x.h"`
|
|
308
|
+
// IS the dependency graph — capture it as a file→file import. Likewise a Rust
|
|
309
|
+
// `use crate::…` is an in-crate module dependency.
|
|
310
|
+
if (langName === "c" || langName === "cpp")
|
|
311
|
+
extractIncludes(tree.rootNode, rel, rawEdges);
|
|
312
|
+
else if (langName === "rust")
|
|
313
|
+
extractUses(tree.rootNode, rel, rawEdges);
|
|
314
|
+
else if (langName === "php")
|
|
315
|
+
extractPhpUses(tree.rootNode, rel, rawEdges);
|
|
316
|
+
else if (langName === "gleam")
|
|
317
|
+
extractGleamModules(tree.rootNode, rel, defs, rawEdges);
|
|
318
|
+
return { nodes, rawEdges };
|
|
319
|
+
}
|
|
320
|
+
/** PHP `use App\Models\User;` → a file→class-file `imports` raw edge, one per imported
|
|
321
|
+
* name (a `{ … }` group expands to several). `use function`/`use const` are skipped —
|
|
322
|
+
* those name a symbol, not a PSR-4 class file. resolve.ts settles the fully-qualified
|
|
323
|
+
* name to the in-repo file by namespace suffix, and drops it when it can't. */
|
|
324
|
+
function extractPhpUses(root, rel, rawEdges) {
|
|
325
|
+
const visit = (n) => {
|
|
326
|
+
if (n.type === "namespace_use_declaration") {
|
|
327
|
+
for (const fqn of phpUseNames(n.text))
|
|
328
|
+
rawEdges.push({ source: rel, relation: "imports", specifier: fqn, file: rel });
|
|
329
|
+
}
|
|
330
|
+
for (let i = 0; i < (n.namedChildCount ?? 0); i++) {
|
|
331
|
+
const c = n.namedChild?.(i);
|
|
332
|
+
if (c)
|
|
333
|
+
visit(c);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
visit(root);
|
|
337
|
+
}
|
|
338
|
+
/** The fully-qualified class names a PHP `use` declaration imports. Handles a plain
|
|
339
|
+
* `use A\B\C;`, a comma list `use A\B, C\D;`, a group `use A\B\{C, D};`, and `as` aliases;
|
|
340
|
+
* returns [] for `use function`/`use const` (symbol imports, not class files). */
|
|
341
|
+
function phpUseNames(text) {
|
|
342
|
+
let s = text.replace(/^\s*use\s+/, "").replace(/;\s*$/, "").trim();
|
|
343
|
+
if (/^(function|const)\b/.test(s))
|
|
344
|
+
return [];
|
|
345
|
+
const brace = s.indexOf("{");
|
|
346
|
+
if (brace >= 0) {
|
|
347
|
+
const prefix = s.slice(0, brace).replace(/\\\s*$/, "");
|
|
348
|
+
const inner = s.slice(brace + 1, s.lastIndexOf("}"));
|
|
349
|
+
return inner.split(",").map((m) => m.trim().replace(/\s+as\s+\w+$/i, "").trim()).filter(Boolean)
|
|
350
|
+
.map((m) => `${prefix}\\${m}`);
|
|
351
|
+
}
|
|
352
|
+
return s.split(",").map((c) => c.trim().replace(/\s+as\s+\w+$/i, "").trim()).filter(Boolean);
|
|
353
|
+
}
|
|
354
|
+
/** Rust `use crate::a::b::Item` → a file→module `imports` raw edge whose specifier is the
|
|
355
|
+
* crate-relative module path (`a/b`). Only in-crate imports are captured; `std::`,
|
|
356
|
+
* `super::`, `self::`, external crates, and globs are skipped — resolve.ts settles the
|
|
357
|
+
* path against the file's crate root, and drops it when it can't. */
|
|
358
|
+
function extractUses(root, rel, rawEdges) {
|
|
359
|
+
const visit = (n) => {
|
|
360
|
+
if (n.type === "use_declaration") {
|
|
361
|
+
const spec = rustUseModule(n.text);
|
|
362
|
+
if (spec !== null)
|
|
363
|
+
rawEdges.push({ source: rel, relation: "imports", specifier: spec ? `crate/${spec}` : "crate", file: rel });
|
|
364
|
+
}
|
|
365
|
+
for (let i = 0; i < (n.namedChildCount ?? 0); i++) {
|
|
366
|
+
const c = n.namedChild?.(i);
|
|
367
|
+
if (c)
|
|
368
|
+
visit(c);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
visit(root);
|
|
372
|
+
}
|
|
373
|
+
/** The crate-relative path a Rust `use crate::…` names (`::`→`/`), or null when it is not
|
|
374
|
+
* an in-crate import. The FULL path is returned — including any trailing item segment —
|
|
375
|
+
* because a single `crate::lexical` can be either a module OR a crate-root item; the
|
|
376
|
+
* resolver settles that by finding the longest prefix that is a real module file. A
|
|
377
|
+
* `{ … }` group has no single item, so its prefix (before `{`) is the path. */
|
|
378
|
+
function rustUseModule(text) {
|
|
379
|
+
let s = text.replace(/^\s*use\s+/, "").replace(/;\s*$/, "").trim();
|
|
380
|
+
const brace = s.indexOf("{");
|
|
381
|
+
if (brace >= 0)
|
|
382
|
+
s = s.slice(0, brace).replace(/::\s*$/, "");
|
|
383
|
+
else
|
|
384
|
+
s = s.replace(/\s+as\s+\w+$/, "");
|
|
385
|
+
s = s.trim();
|
|
386
|
+
if (s !== "crate" && !s.startsWith("crate::"))
|
|
387
|
+
return null; // only in-crate absolute imports
|
|
388
|
+
if (s.includes("*"))
|
|
389
|
+
return null; // glob — no single module target
|
|
390
|
+
return s.replace(/^crate::?/, "").replace(/\s+/g, "").replace(/::/g, "/"); // "" = crate root
|
|
391
|
+
}
|
|
392
|
+
/** C/C++ `#include "header.h"` → a file→file `imports` raw edge. Only LOCAL includes
|
|
393
|
+
* (quoted) are captured; system includes (`<stdio.h>`) are skipped — high volume, and
|
|
394
|
+
* there is no in-repo target to navigate to. resolve.ts settles the quoted path to an
|
|
395
|
+
* in-repo header (relative to the including file, else a unique path-suffix match), and
|
|
396
|
+
* keeps it as an external string when it cannot — never a guessed edge. */
|
|
397
|
+
function extractIncludes(root, rel, rawEdges) {
|
|
398
|
+
const visit = (n) => {
|
|
399
|
+
if (n.type === "preproc_include") {
|
|
400
|
+
const raw = n.childForFieldName?.("path")?.text ?? "";
|
|
401
|
+
if (raw.startsWith('"')) {
|
|
402
|
+
const spec = raw.replace(/^"|"$/g, "").trim();
|
|
403
|
+
if (spec)
|
|
404
|
+
rawEdges.push({ source: rel, relation: "imports", specifier: spec, file: rel });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
for (let i = 0; i < (n.namedChildCount ?? 0); i++) {
|
|
408
|
+
const c = n.namedChild?.(i);
|
|
409
|
+
if (c)
|
|
410
|
+
visit(c);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
visit(root);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Gleam's module system IS its dependency graph, and it is unusually exact: an
|
|
417
|
+
* `import app/thing` names one module file, every reference to another module
|
|
418
|
+
* goes through a qualifier that import binds (`thing.helper()`), and there is no
|
|
419
|
+
* ambient global scope for a name to arrive from. This pass reads that table and
|
|
420
|
+
* uses it for two things.
|
|
421
|
+
*
|
|
422
|
+
* First, a file→file `imports` raw edge per import, which resolve.ts settles
|
|
423
|
+
* against the repo's `.gleam` files (`app/thing` → `…/src/app/thing.gleam`).
|
|
424
|
+
*
|
|
425
|
+
* Second, precision on calls. tags.scm sees `list.map(…)` as the bare label
|
|
426
|
+
* `map`, and because EVERY stdlib call in Gleam is spelled that way, resolving
|
|
427
|
+
* those by name alone is a false-edge machine — `map`, `filter`, `try`, `new`
|
|
428
|
+
* and `to_string` all collide with in-repo functions. So qualified calls are
|
|
429
|
+
* emitted here instead, stamped with the module the qualifier names: resolve.ts
|
|
430
|
+
* then looks the label up in that one file, or drops the call when the module is
|
|
431
|
+
* out of repo (the stdlib, a hex dependency). Unqualified imports
|
|
432
|
+
* (`import app/thing.{helper}`, then a bare `helper()`) give the same two halves,
|
|
433
|
+
* so the bare edge tags.scm already emitted is upgraded in place.
|
|
434
|
+
*
|
|
435
|
+
* A function is as often piped into or handed over as it is called outright
|
|
436
|
+
* (`xs |> thing.helper`, `list.map(xs, thing.helper)`), and neither is a
|
|
437
|
+
* `function_call` node, so both are read here too: a pipe's right side is a
|
|
438
|
+
* call, a name passed as a value is a `references` edge.
|
|
439
|
+
*/
|
|
440
|
+
function extractGleamModules(root, rel, defs, rawEdges) {
|
|
441
|
+
// `list` in `list.map(…)` → the module path it was bound to. The qualifier is
|
|
442
|
+
// the `as` alias when there is one, else the module path's last segment.
|
|
443
|
+
const byQualifier = new Map();
|
|
444
|
+
// `helper` from `import app/thing.{helper}` (or its `as` alias) → that module.
|
|
445
|
+
const byName = new Map();
|
|
446
|
+
for (let i = 0; i < (root.namedChildCount ?? 0); i++) {
|
|
447
|
+
const imp = root.namedChild?.(i);
|
|
448
|
+
if (imp?.type !== "import")
|
|
449
|
+
continue;
|
|
450
|
+
const mod = imp.childForFieldName?.("module")?.text;
|
|
451
|
+
if (!mod)
|
|
452
|
+
continue;
|
|
453
|
+
rawEdges.push({ source: rel, relation: "imports", specifier: mod, file: rel });
|
|
454
|
+
byQualifier.set(imp.childForFieldName?.("alias")?.text || mod.slice(mod.lastIndexOf("/") + 1), mod);
|
|
455
|
+
const unqualified = imp.childForFieldName?.("imports");
|
|
456
|
+
for (let j = 0; j < (unqualified?.namedChildCount ?? 0); j++) {
|
|
457
|
+
const u = unqualified?.namedChild?.(j);
|
|
458
|
+
if (u?.type !== "unqualified_import")
|
|
459
|
+
continue;
|
|
460
|
+
// `{ other as o }` binds `o`; `{ type Handler }` binds a type, harmless here.
|
|
461
|
+
const local = u.childForFieldName?.("alias")?.text ?? u.childForFieldName?.("name")?.text;
|
|
462
|
+
if (local)
|
|
463
|
+
byName.set(local, mod);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// This file's own functions, and the module path that names this file, so a
|
|
467
|
+
// name that can only be local resolves in this file alone.
|
|
468
|
+
const ownFns = new Set();
|
|
469
|
+
for (let i = 0; i < (root.namedChildCount ?? 0); i++) {
|
|
470
|
+
const fn = root.namedChild?.(i);
|
|
471
|
+
const name = fn?.type === "function" ? fn.childForFieldName?.("name")?.text : undefined;
|
|
472
|
+
if (name)
|
|
473
|
+
ownFns.add(name);
|
|
474
|
+
}
|
|
475
|
+
const ownModule = rel.replace(/\.gleam$/, "");
|
|
476
|
+
const ownNames = new Set(defs.map((d) => d.id.slice(rel.length + 1).replace(/~\d+$/, "")));
|
|
477
|
+
// A bare name in Gleam is this file's own definition, an unqualified import, or
|
|
478
|
+
// a local — there is nowhere else for it to come from. So every bare edge
|
|
479
|
+
// tags.scm emitted is pinned to the one module it can mean, and one that means
|
|
480
|
+
// neither (a parameter being called, a prelude `Ok` or `Int`) is dropped rather
|
|
481
|
+
// than left to match a same-named definition somewhere else in the repo.
|
|
482
|
+
for (let i = rawEdges.length - 1; i >= 0; i--) {
|
|
483
|
+
const e = rawEdges[i];
|
|
484
|
+
if ((e.relation !== "calls" && e.relation !== "references") || e.specifier || !e.name)
|
|
485
|
+
continue;
|
|
486
|
+
const mod = byName.get(e.name) ?? (ownNames.has(e.name) ? ownModule : undefined);
|
|
487
|
+
if (mod)
|
|
488
|
+
e.specifier = mod;
|
|
489
|
+
else
|
|
490
|
+
rawEdges.splice(i, 1);
|
|
491
|
+
}
|
|
492
|
+
// Every name a function binds — parameters, `let`, `use` and `case` patterns,
|
|
493
|
+
// at any depth. There is no scope analysis here, so a name bound ANYWHERE in
|
|
494
|
+
// the function is treated as a local throughout it. Coarser than real scoping,
|
|
495
|
+
// and only ever in the direction of dropping an edge.
|
|
496
|
+
const boundIn = (fn) => {
|
|
497
|
+
const bound = new Set();
|
|
498
|
+
const names = (n) => {
|
|
499
|
+
// `thing.Busy(x)` and `x: thing.State` spell a module, not a binding.
|
|
500
|
+
if (!n || n.type === "remote_constructor_name" || n.type === "remote_type_identifier")
|
|
501
|
+
return;
|
|
502
|
+
if (n.type === "identifier")
|
|
503
|
+
bound.add(n.text);
|
|
504
|
+
// `Busy(since:)` is shorthand for `Busy(since: since)` — the label binds.
|
|
505
|
+
if (n.type === "record_pattern_argument" && n.namedChildCount === 1 && n.namedChild?.(0)?.type === "label")
|
|
506
|
+
bound.add(n.namedChild(0).text);
|
|
507
|
+
for (let i = 0; i < (n.namedChildCount ?? 0); i++)
|
|
508
|
+
names(n.namedChild?.(i));
|
|
509
|
+
};
|
|
510
|
+
const walk = (n) => {
|
|
511
|
+
if (n.type === "function_parameters" || n.type === "use_assignments" || n.type === "case_clause_patterns")
|
|
512
|
+
names(n);
|
|
513
|
+
else if (n.type === "let" || n.type === "let_assert")
|
|
514
|
+
names(n.childForFieldName?.("pattern"));
|
|
515
|
+
for (let i = 0; i < (n.namedChildCount ?? 0); i++) {
|
|
516
|
+
const c = n.namedChild?.(i);
|
|
517
|
+
if (c)
|
|
518
|
+
walk(c);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
walk(fn);
|
|
522
|
+
return bound;
|
|
523
|
+
};
|
|
524
|
+
const same = (a, b) => !!a && a.startIndex === b.startIndex && a.endIndex === b.endIndex;
|
|
525
|
+
// `xs |> thing.helper` — the right of a pipe is applied, with or without `()`.
|
|
526
|
+
const piped = (n, parent) => parent?.type === "binary_expression" &&
|
|
527
|
+
parent.childForFieldName?.("operator")?.type === "|>" &&
|
|
528
|
+
same(parent.childForFieldName?.("right"), n);
|
|
529
|
+
// Where a bare name is a value being handed on rather than a binding or a call.
|
|
530
|
+
const passed = (n, parent) => parent?.type === "argument" || parent?.type === "list" || parent?.type === "tuple" ||
|
|
531
|
+
((parent?.type === "let" || parent?.type === "let_assert") && same(parent.childForFieldName?.("value"), n));
|
|
532
|
+
const NONE = new Set();
|
|
533
|
+
// The ways a Gleam file names something in another module. All carry the
|
|
534
|
+
// qualifier, so all get scoped to one file rather than guessed by name.
|
|
535
|
+
const emit = (qualifier, name, relation, locals = NONE) => {
|
|
536
|
+
// Only a bare identifier can be a module qualifier. `field_access` is also how
|
|
537
|
+
// the grammar spells record access, so `cfg.handler.run()` is a field chain,
|
|
538
|
+
// and a qualifier no import bound is a record field holding a function.
|
|
539
|
+
// Neither names a module, and neither gets a guessed edge.
|
|
540
|
+
const mod = qualifier?.type === "identifier" && name && !locals.has(qualifier.text) ? byQualifier.get(qualifier.text) : undefined;
|
|
541
|
+
if (!mod || !name)
|
|
542
|
+
return;
|
|
543
|
+
const enc = enclosingDef(defs, name.startIndex);
|
|
544
|
+
if (relation === "references" && !enc)
|
|
545
|
+
return; // no sound source to hang it on
|
|
546
|
+
rawEdges.push({ source: enc ? enc.id : rel, relation, file: rel, name: name.text, specifier: mod });
|
|
547
|
+
};
|
|
548
|
+
const visit = (n, parent, bound) => {
|
|
549
|
+
if (n.type === "function")
|
|
550
|
+
bound = boundIn(n);
|
|
551
|
+
if (n.type === "function_call") {
|
|
552
|
+
// `list.map(…)` — a remote function call.
|
|
553
|
+
const fn = n.childForFieldName?.("function");
|
|
554
|
+
if (fn?.type === "field_access")
|
|
555
|
+
emit(fn.childForFieldName?.("record") ?? null, fn.childForFieldName?.("field") ?? null, "calls");
|
|
556
|
+
}
|
|
557
|
+
else if (n.type === "field_access") {
|
|
558
|
+
// `thing.helper` with no call around it: applied when it sits on the right
|
|
559
|
+
// of a pipe, otherwise named as a value — a callback (`list.map(xs,
|
|
560
|
+
// thing.helper)`) or a constant (`thing.limit`). Either way the module's
|
|
561
|
+
// symbol is in use, and without the edge it reads as dead code.
|
|
562
|
+
//
|
|
563
|
+
// A local may share the module's name (`fn show(user: User)` under `import
|
|
564
|
+
// app/user`), and Gleam then reads `user.x` as the record's field first and
|
|
565
|
+
// the module's `x` only if there is no such field. Applied, that is nearly
|
|
566
|
+
// always the module (`user.to_string(user)`), so calls keep their edge; read
|
|
567
|
+
// as a value it is nearly always the field (`user.name`), so that is dropped.
|
|
568
|
+
if (!(parent?.type === "function_call" && same(parent.childForFieldName?.("function"), n))) {
|
|
569
|
+
const record = n.childForFieldName?.("record") ?? null;
|
|
570
|
+
const field = n.childForFieldName?.("field") ?? null;
|
|
571
|
+
if (piped(n, parent))
|
|
572
|
+
emit(record, field, "calls");
|
|
573
|
+
else
|
|
574
|
+
emit(record, field, "references", bound);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else if (n.type === "identifier" && !bound.has(n.text)) {
|
|
578
|
+
// The same two shapes for a bare name, which is only ever this file's own
|
|
579
|
+
// function or an unqualified import — anything a local could be is dropped.
|
|
580
|
+
const enc = enclosingDef(defs, n.startIndex);
|
|
581
|
+
const mod = byName.get(n.text) ?? (ownFns.has(n.text) ? ownModule : undefined);
|
|
582
|
+
if (!mod || !enc || enc.id === `${rel}#${n.text}`) {
|
|
583
|
+
// not a function this file can name, or the function naming itself
|
|
584
|
+
}
|
|
585
|
+
else if (piped(n, parent)) {
|
|
586
|
+
rawEdges.push({ source: enc.id, relation: "calls", file: rel, name: n.text, specifier: mod });
|
|
587
|
+
}
|
|
588
|
+
else if (passed(n, parent)) {
|
|
589
|
+
rawEdges.push({ source: enc.id, relation: "references", file: rel, name: n.text, specifier: mod });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
else if (n.type === "remote_constructor_name") {
|
|
593
|
+
// `thing.Busy(since: 1)`, a record update, or a `case` pattern — all apply
|
|
594
|
+
// or take apart the same callable, the way the local forms do in tags.scm.
|
|
595
|
+
emit(n.childForFieldName?.("module") ?? null, n.childForFieldName?.("name") ?? null, "calls");
|
|
596
|
+
}
|
|
597
|
+
else if (n.type === "remote_type_identifier") {
|
|
598
|
+
// `thing.State` in an annotation — names the type without calling it.
|
|
599
|
+
emit(n.childForFieldName?.("module") ?? null, n.childForFieldName?.("name") ?? null, "references");
|
|
600
|
+
}
|
|
601
|
+
for (let i = 0; i < (n.namedChildCount ?? 0); i++) {
|
|
602
|
+
const c = n.namedChild?.(i);
|
|
603
|
+
if (c)
|
|
604
|
+
visit(c, n, bound);
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
visit(root, null, NONE);
|
|
608
|
+
}
|
|
609
|
+
/** tags.scm path: @definition.<kind> → nodes, @reference.call/@reference.send →
|
|
610
|
+
* bare-name call edges attributed to the innermost enclosing definition. */
|
|
611
|
+
function tagsExtract(query, root, rel, mkDef, defs, rawEdges, langName) {
|
|
612
|
+
const q = query;
|
|
613
|
+
const matches = q.matches(root);
|
|
614
|
+
// Some grammars' tags.scm capture a definition's own NAME token as a
|
|
615
|
+
// @reference.call too (Ruby tags `def foo`'s `foo` as both @name and a call),
|
|
616
|
+
// producing bogus self-loops (foo→foo). Skip any call at a definition's name token.
|
|
617
|
+
const defNameAt = new Set();
|
|
618
|
+
const calls = [];
|
|
619
|
+
const refs = [];
|
|
620
|
+
for (const m of matches) {
|
|
621
|
+
const cap = {};
|
|
622
|
+
for (const c of m.captures)
|
|
623
|
+
cap[c.name] = c.node;
|
|
624
|
+
const defKey = Object.keys(cap).find((k) => k.startsWith("definition."));
|
|
625
|
+
if (defKey && cap.name) {
|
|
626
|
+
defNameAt.add(cap.name.startIndex);
|
|
627
|
+
mkDef(cap.name.text, KIND[defKey.slice("definition.".length)] ?? "function", defScope(cap[defKey], langName));
|
|
628
|
+
}
|
|
629
|
+
if (("reference.call" in cap || "reference.send" in cap) && cap.name)
|
|
630
|
+
calls.push({ name: cap.name.text, at: cap.name.startIndex });
|
|
631
|
+
// Structural references the grammar already marks: a supertype (extends), an
|
|
632
|
+
// implemented interface, an object creation (`new Foo`), a module alias. Grammars
|
|
633
|
+
// label these @reference.class/.interface/.implementation/.module — heterogeneous
|
|
634
|
+
// syntactically but all "names this symbol without calling it". They become
|
|
635
|
+
// `references` edges the same precision-first resolver settles to a type-like def
|
|
636
|
+
// (same-file certain, unique cross-file inferred, ambiguous dropped), so a data
|
|
637
|
+
// class that's only ever extended or instantiated stops being an orphan.
|
|
638
|
+
if (("reference.class" in cap || "reference.interface" in cap ||
|
|
639
|
+
"reference.implementation" in cap || "reference.module" in cap) && cap.name)
|
|
640
|
+
refs.push({ name: cap.name.text, at: cap.name.startIndex });
|
|
641
|
+
}
|
|
642
|
+
for (const c of calls) {
|
|
643
|
+
if (defNameAt.has(c.at))
|
|
644
|
+
continue;
|
|
645
|
+
const enc = enclosingDef(defs, c.at);
|
|
646
|
+
rawEdges.push({ source: enc ? enc.id : rel, relation: "calls", file: rel, name: c.name });
|
|
647
|
+
}
|
|
648
|
+
for (const r of refs) {
|
|
649
|
+
if (defNameAt.has(r.at))
|
|
650
|
+
continue;
|
|
651
|
+
const enc = enclosingDef(defs, r.at);
|
|
652
|
+
if (!enc)
|
|
653
|
+
continue; // a reference with no enclosing definition has no sound source
|
|
654
|
+
rawEdges.push({ source: enc.id, relation: "references", file: rel, name: r.name });
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// Node-type → Kind for the walker fallback. A node is a definition when its type
|
|
658
|
+
// ends with a declaration-ish suffix AND names a construct — matched most-specific
|
|
659
|
+
// first (method before function). Covers the common tree-sitter grammar vocab
|
|
660
|
+
// without any per-language query.
|
|
661
|
+
const DEF_SUFFIX = /(declaration|definition|_item|_specifier|_decl|_def|_binding)$/;
|
|
662
|
+
function classifyKind(type) {
|
|
663
|
+
const t = type.toLowerCase();
|
|
664
|
+
if (!DEF_SUFFIX.test(t) && !/^(class|struct|enum|interface|trait|module|namespace)/.test(t))
|
|
665
|
+
return null;
|
|
666
|
+
if (/method|constructor/.test(t))
|
|
667
|
+
return "method";
|
|
668
|
+
if (/func|function|def\b|subroutine|procedure/.test(t))
|
|
669
|
+
return "function";
|
|
670
|
+
if (/class/.test(t))
|
|
671
|
+
return "class";
|
|
672
|
+
if (/struct|record/.test(t))
|
|
673
|
+
return "struct";
|
|
674
|
+
if (/interface|trait|protocol/.test(t))
|
|
675
|
+
return "interface";
|
|
676
|
+
if (/enum/.test(t))
|
|
677
|
+
return "enum";
|
|
678
|
+
if (/module|namespace|package|mod_/.test(t))
|
|
679
|
+
return "module";
|
|
680
|
+
if (/const/.test(t))
|
|
681
|
+
return "constant";
|
|
682
|
+
if (/typedef|type_alias|type_def|alias/.test(t))
|
|
683
|
+
return "type";
|
|
684
|
+
if (/type/.test(t))
|
|
685
|
+
return "type";
|
|
686
|
+
if (/(^|_)(val|var|let|field|property)/.test(t))
|
|
687
|
+
return "variable";
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
/** The declared name of a node: its `name` field, else the first identifier-ish
|
|
691
|
+
* descendant within a couple of levels. */
|
|
692
|
+
function nodeName(node) {
|
|
693
|
+
const byField = node.childForFieldName?.("name");
|
|
694
|
+
if (byField?.text)
|
|
695
|
+
return byField.text;
|
|
696
|
+
const stack = [{ n: node, d: 0 }];
|
|
697
|
+
while (stack.length) {
|
|
698
|
+
const { n, d } = stack.shift();
|
|
699
|
+
if (d > 0 && /identifier|name/.test(n.type) && n.text && !n.text.includes("\n"))
|
|
700
|
+
return n.text;
|
|
701
|
+
if (d < 3)
|
|
702
|
+
for (let i = 0; i < (n.namedChildCount ?? 0); i++) {
|
|
703
|
+
const c = n.namedChild?.(i);
|
|
704
|
+
if (c)
|
|
705
|
+
stack.push({ n: c, d: d + 1 });
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
/** Walker fallback: DFS every named node, emit a def for each classified one
|
|
711
|
+
* (symbols only — no call resolution without a query). */
|
|
712
|
+
function walkExtract(root, mkDef) {
|
|
713
|
+
const visit = (n) => {
|
|
714
|
+
const kind = classifyKind(n.type);
|
|
715
|
+
if (kind) {
|
|
716
|
+
const name = nodeName(n);
|
|
717
|
+
if (name)
|
|
718
|
+
mkDef(name, kind, n);
|
|
719
|
+
}
|
|
720
|
+
for (let i = 0; i < (n.namedChildCount ?? 0); i++) {
|
|
721
|
+
const c = n.namedChild?.(i);
|
|
722
|
+
if (c)
|
|
723
|
+
visit(c);
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
visit(root);
|
|
727
|
+
}
|
|
728
|
+
// Some grammars' tags.scm put @definition.<X> on a narrow node (C tags the
|
|
729
|
+
// `function_declarator`, not the whole `function_definition` with its body), so
|
|
730
|
+
// the capture's span stops before the body and calls inside it can't be
|
|
731
|
+
// attributed to the function. Expand up to the outermost enclosing
|
|
732
|
+
// declaration/definition node so a def's span covers its body.
|
|
733
|
+
const DEF_CONTAINER = /(definition|declaration|specifier|_item)$/;
|
|
734
|
+
function nextNamedSibling(n) {
|
|
735
|
+
const tagged = n;
|
|
736
|
+
if ("nextNamedSibling" in tagged)
|
|
737
|
+
return tagged.nextNamedSibling ?? null;
|
|
738
|
+
const p = n.parent;
|
|
739
|
+
if (!p?.namedChild)
|
|
740
|
+
return null;
|
|
741
|
+
const count = p.namedChildCount ?? 0;
|
|
742
|
+
for (let i = 0; i < count - 1; i++) {
|
|
743
|
+
const c = p.namedChild(i);
|
|
744
|
+
if (c && c.startIndex === n.startIndex && c.endIndex === n.endIndex)
|
|
745
|
+
return p.namedChild(i + 1);
|
|
746
|
+
}
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
function defScope(node, langName) {
|
|
750
|
+
let n = node;
|
|
751
|
+
while (n.parent && DEF_CONTAINER.test(n.parent.type))
|
|
752
|
+
n = n.parent;
|
|
753
|
+
// Dart's grammar leaves `function_signature` / `method_signature` as a sibling
|
|
754
|
+
// of `function_body` (no wrapping function_definition). Expand so a def's span
|
|
755
|
+
// covers its body and calls inside it attribute to the function, not the file
|
|
756
|
+
// or enclosing class. Gated on Dart so other breadth-tier languages are untouched.
|
|
757
|
+
if (langName === "dart") {
|
|
758
|
+
const body = nextNamedSibling(n);
|
|
759
|
+
if (body?.type === "function_body") {
|
|
760
|
+
return {
|
|
761
|
+
type: n.type,
|
|
762
|
+
text: n.text,
|
|
763
|
+
startIndex: n.startIndex,
|
|
764
|
+
endIndex: body.endIndex,
|
|
765
|
+
startPosition: n.startPosition,
|
|
766
|
+
endPosition: body.endPosition,
|
|
767
|
+
parent: n.parent,
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return n;
|
|
772
|
+
}
|
|
773
|
+
//# sourceMappingURL=generic.js.map
|