gitnexus 1.6.11-rc.8 → 1.6.11
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/README.md +95 -26
- package/dist/_shared/graph/types.d.ts +81 -2
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/integrations/circuit-breaker.d.ts.map +1 -1
- package/dist/_shared/integrations/circuit-breaker.js.map +1 -1
- package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
- package/dist/_shared/integrations/resilient-fetch.js.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +2 -0
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/languages.d.ts +2 -1
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +1 -0
- package/dist/_shared/languages.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +2 -2
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +4 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/evidence-weights.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.js +1 -0
- package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.d.ts +3 -0
- package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.js +4 -0
- package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +10 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +15 -0
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +4 -0
- package/dist/cli/ai-context.js +90 -14
- package/dist/cli/analyze-config.js +14 -0
- package/dist/cli/analyze-options.d.ts +14 -0
- package/dist/cli/analyze-watch.d.ts +20 -0
- package/dist/cli/analyze-watch.js +434 -0
- package/dist/cli/analyze.js +27 -9
- package/dist/cli/auto-sync.d.ts +1 -0
- package/dist/cli/auto-sync.js +107 -0
- package/dist/cli/command-banner.d.ts +15 -0
- package/dist/cli/command-banner.js +42 -0
- package/dist/cli/detect-changes-format.js +21 -0
- package/dist/cli/doctor.d.ts +7 -0
- package/dist/cli/doctor.js +14 -0
- package/dist/cli/group.js +7 -1
- package/dist/cli/help-i18n.js +4 -0
- package/dist/cli/i18n/en.d.ts +18 -3
- package/dist/cli/i18n/en.js +18 -3
- package/dist/cli/i18n/resources.d.ts +33 -3
- package/dist/cli/i18n/zh-CN.d.ts +15 -0
- package/dist/cli/i18n/zh-CN.js +18 -3
- package/dist/cli/index.js +34 -7
- package/dist/cli/mcp.d.ts +15 -0
- package/dist/cli/mcp.js +44 -0
- package/dist/cli/optional-grammars.d.ts +3 -2
- package/dist/cli/optional-grammars.js +19 -3
- package/dist/cli/setup.js +41 -15
- package/dist/cli/update-notice.d.ts +34 -0
- package/dist/cli/update-notice.js +91 -0
- package/dist/cli/update.d.ts +21 -0
- package/dist/cli/update.js +93 -0
- package/dist/cli/watch.d.ts +1 -20
- package/dist/cli/watch.js +5 -371
- package/dist/core/analysis-feature-registry.d.ts +2 -0
- package/dist/core/analysis-feature-registry.js +15 -0
- package/dist/core/analyzer-identity.js +21 -3
- package/dist/core/auto-sync/analysis-worker-launch.d.ts +30 -0
- package/dist/core/auto-sync/analysis-worker-launch.js +166 -0
- package/dist/core/auto-sync/config.d.ts +33 -0
- package/dist/core/auto-sync/config.js +320 -0
- package/dist/core/auto-sync/index.d.ts +6 -0
- package/dist/core/auto-sync/index.js +6 -0
- package/dist/core/auto-sync/path-security.d.ts +9 -0
- package/dist/core/auto-sync/path-security.js +251 -0
- package/dist/core/auto-sync/repo.d.ts +1 -0
- package/dist/core/auto-sync/repo.js +6 -0
- package/dist/core/auto-sync/runner.d.ts +44 -0
- package/dist/core/auto-sync/runner.js +428 -0
- package/dist/core/auto-sync/starter.d.ts +50 -0
- package/dist/core/auto-sync/starter.js +497 -0
- package/dist/core/auto-sync/state.d.ts +37 -0
- package/dist/core/auto-sync/state.js +119 -0
- package/dist/core/group/config-parser.js +8 -1
- package/dist/core/group/cross-impact.d.ts +3 -0
- package/dist/core/group/cross-impact.js +36 -4
- package/dist/core/group/extractors/http-patterns/java.js +22 -8
- package/dist/core/group/extractors/http-patterns/kotlin.js +86 -31
- package/dist/core/group/extractors/http-patterns/node.js +145 -8
- package/dist/core/group/extractors/http-patterns/python.js +11 -7
- package/dist/core/group/extractors/http-route-extractor.js +63 -17
- package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
- package/dist/core/group/group-lock.js +5 -6
- package/dist/core/group/normalization.d.ts +38 -1
- package/dist/core/group/normalization.js +59 -3
- package/dist/core/group/service.d.ts +11 -0
- package/dist/core/group/service.js +13 -1
- package/dist/core/group/storage.js +2 -2
- package/dist/core/group/sync.d.ts +24 -1
- package/dist/core/group/sync.js +46 -8
- package/dist/core/group/types.d.ts +13 -0
- package/dist/core/incremental/derived-writeback.d.ts +36 -0
- package/dist/core/incremental/derived-writeback.js +68 -0
- package/dist/core/incremental/spring-config-drift.d.ts +12 -0
- package/dist/core/incremental/spring-config-drift.js +46 -0
- package/dist/core/incremental/subgraph-extract.d.ts +7 -4
- package/dist/core/incremental/subgraph-extract.js +30 -5
- package/dist/core/ingestion/asyncapi/document.d.ts +226 -0
- package/dist/core/ingestion/asyncapi/document.js +758 -0
- package/dist/core/ingestion/asyncapi/protocol.d.ts +79 -0
- package/dist/core/ingestion/asyncapi/protocol.js +203 -0
- package/dist/core/ingestion/call-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/zig.js +4 -0
- package/dist/core/ingestion/call-extractors/zig-static-gating.d.ts +100 -0
- package/dist/core/ingestion/call-extractors/zig-static-gating.js +319 -0
- package/dist/core/ingestion/call-processor.js +3 -1
- package/dist/core/ingestion/class-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/zig.js +32 -0
- package/dist/core/ingestion/class-extractors/generic.js +4 -1
- package/dist/core/ingestion/class-types.d.ts +6 -2
- package/dist/core/ingestion/destination-key.d.ts +59 -0
- package/dist/core/ingestion/destination-key.js +61 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +6 -2
- package/dist/core/ingestion/entry-point-scoring.js +8 -44
- package/dist/core/ingestion/export-detection.d.ts +37 -0
- package/dist/core/ingestion/export-detection.js +60 -0
- package/dist/core/ingestion/field-extractors/configs/zig.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/zig.js +49 -0
- package/dist/core/ingestion/field-extractors/generic.d.ts +1 -1
- package/dist/core/ingestion/field-extractors/generic.js +12 -1
- package/dist/core/ingestion/frameworks/spring/actuator-runtime.d.ts +26 -0
- package/dist/core/ingestion/frameworks/spring/actuator-runtime.js +843 -0
- package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +5 -0
- package/dist/core/ingestion/frameworks/spring/analysis-features.js +9 -0
- package/dist/core/ingestion/frameworks/spring/annotation-arguments.js +8 -1
- package/dist/core/ingestion/frameworks/spring/argument-facts.d.ts +80 -0
- package/dist/core/ingestion/frameworks/spring/argument-facts.js +85 -0
- package/dist/core/ingestion/frameworks/spring/config-bindings.d.ts +1 -0
- package/dist/core/ingestion/frameworks/spring/config-bindings.js +2 -1
- package/dist/core/ingestion/frameworks/spring/destinations.d.ts +539 -0
- package/dist/core/ingestion/frameworks/spring/destinations.js +840 -0
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
- package/dist/core/ingestion/frameworks/spring/message-producers.d.ts +78 -0
- package/dist/core/ingestion/frameworks/spring/message-producers.js +90 -0
- package/dist/core/ingestion/frameworks/spring/non-http-handlers.d.ts +29 -0
- package/dist/core/ingestion/frameworks/spring/vendor-prefixes.d.ts +7 -0
- package/dist/core/ingestion/frameworks/spring/vendor-prefixes.js +22 -0
- package/dist/core/ingestion/import-resolvers/configs/zig.d.ts +13 -0
- package/dist/core/ingestion/import-resolvers/configs/zig.js +26 -0
- package/dist/core/ingestion/import-resolvers/zig.d.ts +34 -0
- package/dist/core/ingestion/import-resolvers/zig.js +193 -0
- package/dist/core/ingestion/language-config.d.ts +177 -0
- package/dist/core/ingestion/language-config.js +719 -0
- package/dist/core/ingestion/language-provider.d.ts +178 -1
- package/dist/core/ingestion/language-provider.js +22 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -0
- package/dist/core/ingestion/languages/index.d.ts +1 -0
- package/dist/core/ingestion/languages/index.js +2 -0
- package/dist/core/ingestion/languages/java/analysis-features.d.ts +1 -1
- package/dist/core/ingestion/languages/java/analysis-features.js +14 -6
- package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +8 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.js +34 -0
- package/dist/core/ingestion/languages/java/captures.js +22 -1
- package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
- package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
- package/dist/core/ingestion/languages/java/spring-actuator.d.ts +2 -0
- package/dist/core/ingestion/languages/java/spring-actuator.js +62 -0
- package/dist/core/ingestion/languages/java/spring-di.d.ts +17 -1
- package/dist/core/ingestion/languages/java/spring-di.js +44 -2
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
- package/dist/core/ingestion/languages/java/spring-message-producers.d.ts +20 -0
- package/dist/core/ingestion/languages/java/spring-message-producers.js +83 -0
- package/dist/core/ingestion/languages/java/spring-non-http-handlers.d.ts +11 -1
- package/dist/core/ingestion/languages/java/spring-non-http-handlers.js +13 -3
- package/dist/core/ingestion/languages/java.js +18 -8
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
- package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
- package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +15 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +50 -0
- package/dist/core/ingestion/languages/kotlin/captures.js +24 -1
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
- package/dist/core/ingestion/languages/kotlin/scope-resolver.js +4 -0
- package/dist/core/ingestion/languages/kotlin/spring-actuator.d.ts +4 -0
- package/dist/core/ingestion/languages/kotlin/spring-actuator.js +172 -0
- package/dist/core/ingestion/languages/kotlin/spring-config-bindings.d.ts +18 -0
- package/dist/core/ingestion/languages/kotlin/spring-config-bindings.js +411 -0
- package/dist/core/ingestion/languages/kotlin/spring-di.d.ts +51 -1
- package/dist/core/ingestion/languages/kotlin/spring-di.js +86 -6
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
- package/dist/core/ingestion/languages/kotlin/spring-message-producers.d.ts +17 -0
- package/dist/core/ingestion/languages/kotlin/spring-message-producers.js +119 -0
- package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.d.ts +16 -0
- package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.js +77 -2
- package/dist/core/ingestion/languages/kotlin.js +24 -0
- package/dist/core/ingestion/languages/python.js +2 -0
- package/dist/core/ingestion/languages/rust/query.js +8 -8
- package/dist/core/ingestion/languages/zig/captures.d.ts +260 -0
- package/dist/core/ingestion/languages/zig/captures.js +2070 -0
- package/dist/core/ingestion/languages/zig/index.d.ts +4 -0
- package/dist/core/ingestion/languages/zig/index.js +4 -0
- package/dist/core/ingestion/languages/zig/interpret.d.ts +36 -0
- package/dist/core/ingestion/languages/zig/interpret.js +176 -0
- package/dist/core/ingestion/languages/zig/query.d.ts +3 -0
- package/dist/core/ingestion/languages/zig/query.js +426 -0
- package/dist/core/ingestion/languages/zig/range-binding.d.ts +37 -0
- package/dist/core/ingestion/languages/zig/range-binding.js +373 -0
- package/dist/core/ingestion/languages/zig/scope-resolver.d.ts +12 -0
- package/dist/core/ingestion/languages/zig/scope-resolver.js +58 -0
- package/dist/core/ingestion/languages/zig/simple-hooks.d.ts +25 -0
- package/dist/core/ingestion/languages/zig/simple-hooks.js +143 -0
- package/dist/core/ingestion/languages/zig.d.ts +32 -0
- package/dist/core/ingestion/languages/zig.js +130 -0
- package/dist/core/ingestion/method-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/zig.js +120 -0
- package/dist/core/ingestion/method-extractors/generic.js +16 -4
- package/dist/core/ingestion/method-types.d.ts +10 -4
- package/dist/core/ingestion/model/field-registry.d.ts +3 -3
- package/dist/core/ingestion/model/field-registry.js +11 -7
- package/dist/core/ingestion/model/method-registry.d.ts +3 -3
- package/dist/core/ingestion/model/method-registry.js +13 -8
- package/dist/core/ingestion/model/registration-table.js +3 -0
- package/dist/core/ingestion/model/type-registry.js +8 -4
- package/dist/core/ingestion/pipeline-phases/di.js +47 -14
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +7 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +79 -91
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +2 -1
- package/dist/core/ingestion/pipeline-phases/processes.js +39 -4
- package/dist/core/ingestion/pipeline-phases/routes.js +27 -18
- package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
- package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
- package/dist/core/ingestion/pipeline-phases/scan.js +82 -1
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +8 -0
- package/dist/core/ingestion/pipeline-phases/spring-destinations.d.ts +123 -0
- package/dist/core/ingestion/pipeline-phases/spring-destinations.js +688 -0
- package/dist/core/ingestion/pipeline.d.ts +30 -1
- package/dist/core/ingestion/pipeline.js +52 -14
- package/dist/core/ingestion/resolve-references.js +5 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +16 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.js +10 -0
- package/dist/core/ingestion/route-extractors/java-const-resolver.d.ts +27 -1
- package/dist/core/ingestion/route-extractors/java-const-resolver.js +161 -13
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.d.ts +12 -18
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.js +214 -33
- package/dist/core/ingestion/route-extractors/kotlin-spring.d.ts +13 -0
- package/dist/core/ingestion/route-extractors/kotlin-spring.js +259 -0
- package/dist/core/ingestion/route-extractors/python-decorator-handler.d.ts +6 -0
- package/dist/core/ingestion/route-extractors/python-decorator-handler.js +16 -0
- package/dist/core/ingestion/route-extractors/spring-shared.d.ts +30 -1
- package/dist/core/ingestion/route-extractors/spring-shared.js +77 -9
- package/dist/core/ingestion/route-extractors/spring.js +9 -5
- package/dist/core/ingestion/scope-extractor.js +8 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +62 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +4 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +2 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +28 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +11 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +23 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +24 -3
- package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +11 -1
- package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +26 -6
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +11 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +29 -3
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +8 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +35 -9
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -4
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +277 -9
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +7 -3
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +43 -9
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +71 -10
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -0
- package/dist/core/ingestion/tree-sitter-queries.js +161 -0
- package/dist/core/ingestion/type-extractors/zig.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/zig.js +17 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +40 -1
- package/dist/core/ingestion/utils/ast-helpers.js +124 -6
- package/dist/core/ingestion/utils/callable-flow-captures.d.ts +9 -0
- package/dist/core/ingestion/utils/callable-flow-captures.js +75 -14
- package/dist/core/ingestion/utils/test-file-path.d.ts +28 -0
- package/dist/core/ingestion/utils/test-file-path.js +87 -0
- package/dist/core/ingestion/variable-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/variable-extractors/configs/zig.js +71 -0
- package/dist/core/ingestion/workers/parse-worker.js +85 -14
- package/dist/core/install-context.d.ts +4 -0
- package/dist/core/install-context.js +152 -0
- package/dist/core/lbug/csv-generator.d.ts +10 -1
- package/dist/core/lbug/csv-generator.js +92 -22
- package/dist/core/lbug/graph-emit-sink.d.ts +3 -2
- package/dist/core/lbug/graph-emit-sink.js +4 -2
- package/dist/core/lbug/lbug-adapter.d.ts +100 -5
- package/dist/core/lbug/lbug-adapter.js +236 -24
- package/dist/core/lbug/schema.d.ts +6 -5
- package/dist/core/lbug/schema.js +84 -22
- package/dist/core/net/url-guard.d.ts +6 -0
- package/dist/core/net/url-guard.js +147 -0
- package/dist/core/package-version.d.ts +5 -0
- package/dist/core/package-version.js +10 -0
- package/dist/core/run-analyze.d.ts +21 -4
- package/dist/core/run-analyze.js +426 -117
- package/dist/core/search/fts-indexes.d.ts +19 -1
- package/dist/core/search/fts-indexes.js +28 -1
- package/dist/core/tree-sitter/parser-loader.js +16 -0
- package/dist/core/update-cache.d.ts +39 -0
- package/dist/core/update-cache.js +141 -0
- package/dist/core/update-check.d.ts +31 -0
- package/dist/core/update-check.js +274 -0
- package/dist/mcp/local/local-backend.d.ts +27 -13
- package/dist/mcp/local/local-backend.js +148 -60
- package/dist/mcp/repository-policy.d.ts +5 -1
- package/dist/mcp/repository-policy.js +48 -4
- package/dist/mcp/resources.js +10 -1
- package/dist/mcp/server.js +8 -9
- package/dist/mcp/tools.js +32 -22
- package/dist/server/analyze-launch.d.ts +2 -0
- package/dist/server/analyze-launch.js +2 -0
- package/dist/server/analyze-worker-protocol.d.ts +6 -1
- package/dist/server/analyze-worker.d.ts +1 -0
- package/dist/server/analyze-worker.js +38 -20
- package/dist/server/api.d.ts +5 -1
- package/dist/server/api.js +105 -88
- package/dist/server/git-clone.d.ts +40 -16
- package/dist/server/git-clone.js +253 -199
- package/dist/server/grep-params.d.ts +18 -0
- package/dist/server/grep-params.js +83 -0
- package/dist/server/grep-scan.d.ts +23 -0
- package/dist/server/grep-scan.js +107 -0
- package/dist/server/grep-worker.d.ts +1 -0
- package/dist/server/grep-worker.js +12 -0
- package/dist/server/mcp-http.d.ts +8 -0
- package/dist/server/mcp-http.js +16 -1
- package/dist/server/update-controller.d.ts +29 -0
- package/dist/server/update-controller.js +82 -0
- package/dist/storage/file-lock.d.ts +15 -0
- package/dist/storage/file-lock.js +155 -0
- package/dist/storage/fs-atomic.d.ts +24 -0
- package/dist/storage/fs-atomic.js +86 -2
- package/dist/storage/global-dir.d.ts +2 -0
- package/dist/storage/global-dir.js +6 -0
- package/dist/storage/parse-cache.d.ts +45 -7
- package/dist/storage/parse-cache.js +189 -42
- package/dist/storage/parsedfile-store.d.ts +41 -60
- package/dist/storage/parsedfile-store.js +247 -171
- package/dist/storage/repo-manager.d.ts +10 -4
- package/dist/storage/repo-manager.js +35 -25
- package/dist/storage/repo-meta.d.ts +34 -0
- package/dist/storage/v8-sidecar.d.ts +48 -0
- package/dist/storage/v8-sidecar.js +347 -0
- package/dist/types/pipeline.d.ts +14 -0
- package/dist/utils/process-identity.d.ts +2 -0
- package/dist/utils/process-identity.js +35 -0
- package/package.json +10 -4
- package/scripts/assert-web-assets.mjs +35 -0
- package/scripts/build-web.js +72 -0
- package/scripts/build.js +31 -28
- package/scripts/cross-platform-tests.ts +5 -0
- package/skills/gitnexus-cli.md +5 -1
- package/web/assets/ProcessFlowModal-DAeX2BHW.js +24 -0
- package/web/assets/{abnfDiagram-QNN2D4NM-Cq2djWWe.js → abnfDiagram-PAR5IXJI-J3OdeHLl.js} +1 -1
- package/web/assets/{agent-BWAXPjC5.js → agent-BMRqXorA.js} +6 -4
- package/web/assets/architecture-7GRP2DOG-M7G3SWCO-G3sWyX6M.js +1 -0
- package/web/assets/architectureDiagram-2PNWDENW-ysKcRlGT.js +36 -0
- package/web/assets/blockDiagram-PUWGUBOA-oLXTa1lY.js +129 -0
- package/web/assets/c4Diagram-3NLNHZP5-D3iu4eC0.js +38 -0
- package/web/assets/{chunk-2SREHG4O-Dip42xOq.js → chunk-2NMSUPJH-B24w-wng.js} +1 -1
- package/web/assets/{chunk-VJFVEZTB-CX66F2ep.js → chunk-32MCUW2P-ByUyt0Sa.js} +3 -3
- package/web/assets/{chunk-4RFN2BYJ-DHHWuC-n.js → chunk-3NPNU5GP-DocZ07Sg.js} +1 -1
- package/web/assets/{chunk-XNMVGMAZ-Cg_LnPtj.js → chunk-3SMGFRGC-CAu4JRLv.js} +1 -1
- package/web/assets/{chunk-W44A43WB-BN8EQc_u.js → chunk-4CAJECRU-DRYMwG9z.js} +1 -1
- package/web/assets/chunk-4OADUZ5Q-CxrSvOwZ.js +10 -0
- package/web/assets/{chunk-DMV4VAQV-Bn6WXDSX.js → chunk-5IQCK5RY-CCMhl4so.js} +2 -2
- package/web/assets/{chunk-RRFMTAIC-CMN0wcYA.js → chunk-5MVFYWFU-Db9KvrYO.js} +1 -1
- package/web/assets/chunk-CLS4B6BI-Uvx5QNsu.js +168 -0
- package/web/assets/chunk-DZP67EKU-B3Mt380j.js +70 -0
- package/web/assets/{chunk-6L755F7B-CMhLBxi2.js → chunk-EICP7K5X-CqwE9hui.js} +1 -1
- package/web/assets/{chunk-BI6VK774-D0oaWk8V.js → chunk-GDY2UAB4-CtQQR0a8.js} +1 -1
- package/web/assets/chunk-H7VHZCWX-CE1nAmt6.js +1 -0
- package/web/assets/chunk-IJBDOHL6-BUKtY6oF.js +1 -0
- package/web/assets/{chunk-OIYT25JQ-CQ6KOZGX.js → chunk-JFFVMJIA-0fJenqia.js} +1 -1
- package/web/assets/{chunk-HNC4WDU7-DoU3KnA7.js → chunk-JZGDI5JK-BeTMSSkJ.js} +1 -1
- package/web/assets/{chunk-EQFTRU2I-1ix7TzW9.js → chunk-KD6SPIPN-Qp1Pflah.js} +1 -1
- package/web/assets/chunk-NF366YI6-CpSpya3n.js +231 -0
- package/web/assets/chunk-NUDPKBEF-CU1-BQIW.js +34 -0
- package/web/assets/{chunk-SATU7PGQ-B1zhgOvc.js → chunk-OJINMHTS-zOCQe4eI.js} +1 -1
- package/web/assets/{chunk-E5QJAATJ-dh-5CtIV.js → chunk-Q6NGPIAH-yyBomb63.js} +1 -1
- package/web/assets/chunk-QJSWEUOL-D_rf9gPD.js +128 -0
- package/web/assets/chunk-R7JDMUKV-CRoqKpD6.js +1 -0
- package/web/assets/{chunk-VE5CLXGZ-C1vB7wpY.js → chunk-RCXAM2GK-CUqrWQDV.js} +1 -1
- package/web/assets/{chunk-66DQ2XMT-CM0xjFAN.js → chunk-RT4MAWR3-zXy9-Lvt.js} +1 -1
- package/web/assets/{chunk-TZUXEDM2-B7p9AcTw.js → chunk-SFMINX4W-J2UrvMpD.js} +1 -1
- package/web/assets/{chunk-LORZLZWG-B57uGw2N.js → chunk-SLDQ463B-CHY6e_bQ.js} +2 -2
- package/web/assets/{chunk-FWYVLQTC-BgDItRzJ.js → chunk-TCEHX7R3-CXPDEVbm.js} +1 -1
- package/web/assets/{chunk-224SPVON-B8gxtXpy.js → chunk-VQC2EELJ-Dc8fIy4R.js} +1 -1
- package/web/assets/chunk-VRZKL3EH-DB3KixTQ.js +1 -0
- package/web/assets/{chunk-A7VWPJGB-B4O3oQdQ.js → chunk-VTHDQ7BV-b7FsPQZO.js} +1 -1
- package/web/assets/chunk-VY2OSTJR-Dkgc3Brg.js +2 -0
- package/web/assets/chunk-WUBWJARI-7FwILJ3k.js +2 -0
- package/web/assets/{chunk-D6VWDJW2-BxcBdZ9c.js → chunk-YBMQ5JGJ-BSnyWZmd.js} +1 -1
- package/web/assets/chunk-YDMLL4PJ-IHhMngTz.js +2 -0
- package/web/assets/chunk-Z6V44ARX-BjCEsF0Q.js +62 -0
- package/web/assets/chunk-ZJJ7VQJP-Dz089b-K.js +321 -0
- package/web/assets/chunk-ZNLK7HRR-B0iFpmW4.js +1 -0
- package/web/assets/chunk-ZQN7UHIL-Cy7KAjQt.js +1 -0
- package/web/assets/{chunk-L5GCOVLC-CE2w8Nm_.js → chunk-ZS6VBONO-DBjJO0w9.js} +2 -2
- package/web/assets/{chunk-NV3KIAZN-5yvcpMiu.js → chunk-ZUNWM646-DNRJyNPe.js} +1 -1
- package/web/assets/classDiagram-FGAMPOII-C5Ibyode.js +1 -0
- package/web/assets/classDiagram-v2-AI5RJEK4-C5Ibyode.js +1 -0
- package/web/assets/{cose-bilkent-RZA4XIPR-BHpK4Crp.js → cose-bilkent-Q72C2OJ3-DMeFXnK8.js} +1 -1
- package/web/assets/cynefin-OW5HDTMX-6OCTWOGG-vlqakYcO.js +1 -0
- package/web/assets/{cynefinDiagram-TR7LZUCG-C3Iar9lZ.js → cynefinDiagram-2OOWUY4M-BY6DZkTt.js} +1 -1
- package/web/assets/dagre-MPVFI544-DWO70fy_.js +4 -0
- package/web/assets/diagram-AE4NWXFW-f7JRVRUL.js +24 -0
- package/web/assets/{diagram-L7DVE3PN-GIRyVUtt.js → diagram-BUJID5UG-BXZv1rCS.js} +2 -2
- package/web/assets/{diagram-JCD7PQ7C-DC5-9FRr.js → diagram-C44CZ5NI-CRNfvjCq.js} +1 -1
- package/web/assets/diagram-EYOLEXXS-DZOjKQVE.js +30 -0
- package/web/assets/diagram-P7B6PWOJ-D3SQmw3_.js +3 -0
- package/web/assets/{ebnfDiagram-DRHRQJWO-D8_CI4ZF.js → ebnfDiagram-FTPZ3PV6-jKWpIDN2.js} +1 -1
- package/web/assets/erDiagram-H3ERR26V-BqJntEuJ.js +99 -0
- package/web/assets/eventmodeling-NTZA5JFV-WKSWQHFS-Bq4FqtBL.js +1 -0
- package/web/assets/flowDiagram-YHGXBVSY-Dnlk2bEu.js +1 -0
- package/web/assets/ganttDiagram-RWFTB7ED-Bl6Y1K5T.js +292 -0
- package/web/assets/gitGraph-4MIJSDKK-JH2HKYUO-B3yc4GkH.js +1 -0
- package/web/assets/gitGraphDiagram-MAR6GKOP-CkNvsrdw.js +106 -0
- package/web/assets/{index-CJDpyH-n.css → index-C2_aej58.css} +1 -1
- package/web/assets/{index-DDRie6_8.js → index-Dj3vQOHK.js} +51 -51
- package/web/assets/info-A6RAGUB7-3TX3OFWW-BM3N5_Dv.js +1 -0
- package/web/assets/infoDiagram-LV547AFH-COkFutAf.js +2 -0
- package/web/assets/{ishikawaDiagram-4JDSWWDM-DmqWtjkl.js → ishikawaDiagram-PUJQGNDS-Bb8FylSR.js} +2 -2
- package/web/assets/{journeyDiagram-QUS3FAKT-XAzVzrQW.js → journeyDiagram-EW5QJT4W-BzoHUBhM.js} +4 -4
- package/web/assets/{kanban-definition-D4L7SQKK-YurRnmYn.js → kanban-definition-JSQERAZB-Uy2Pi0yQ.js} +4 -4
- package/web/assets/katex-CZ4GXH2S-C0Fcou7U.js +257 -0
- package/web/assets/{mindmap-definition-66QP5TD6-ChrWWnil.js → mindmap-definition-VBJCLQLM-B-CnOnt_.js} +3 -3
- package/web/assets/packet-AYTQ26CC-MKII6P3Y-Bd52ZE3V.js +1 -0
- package/web/assets/{pegDiagram-YKIO5C6U-QGeQLEq2.js → pegDiagram-7WILOQTJ-D9sneKXg.js} +1 -1
- package/web/assets/pie-WAS4IAKB-6B4OPMJ5-DW5Xy69Z.js +1 -0
- package/web/assets/pieDiagram-EFA7CBSB-Dx2NQofL.js +39 -0
- package/web/assets/{quadrantDiagram-VO5QLCXA-B7098wSc.js → quadrantDiagram-WARVYA7P-DPKL9_0A.js} +3 -3
- package/web/assets/radar-RG4KPBEZ-UPTR3XUL-CkzibT2h.js +1 -0
- package/web/assets/railroad-74A4TZTK-MEFUPFMS-D9zF-mDN.js +1 -0
- package/web/assets/railroad-abnf-HS5TGJTU-R5YG2S5D-ruacjUbX.js +1 -0
- package/web/assets/railroad-ebnf-LZEXJU2U-G5QEX2L6-S1zc5OiO.js +1 -0
- package/web/assets/railroad-peg-WCYAUIDC-H5I2ZRY2-Cbcr0XzY.js +1 -0
- package/web/assets/{railroadDiagram-M77LTMSB-AXu4vxk_.js → railroadDiagram-5NIQVNLQ-DsweTo14.js} +1 -1
- package/web/assets/{requirementDiagram-RASO5HTO-B-l8InpK.js → requirementDiagram-SXREGOKZ-CXwdNSVR.js} +4 -4
- package/web/assets/{sankeyDiagram-F32LOJ6L-Kd7zjJgX.js → sankeyDiagram-VV74M2VP-CA6jtP9P.js} +3 -3
- package/web/assets/{sequenceDiagram-URATNSBD-BlcdY9DS.js → sequenceDiagram-52ZFIFDL-k_5rEbzm.js} +15 -15
- package/web/assets/src-Df5C1Nz4.js +1 -0
- package/web/assets/stateDiagram-3QAKPIVI-BejHTf31.js +1 -0
- package/web/assets/stateDiagram-v2-HN2P4RDZ-BrfoRuOc.js +1 -0
- package/web/assets/swimlanes-QLVP42QD-CQSVO5D6.js +1 -0
- package/web/assets/swimlanesDiagram-JKPDBFCN-DrWB9kcD.js +8 -0
- package/web/assets/{timeline-definition-WJXWRH4I-DUfp1R3E.js → timeline-definition-GS45XAXW-C0Ks8rPP.js} +4 -4
- package/web/assets/treeView-Q6P3EWNA-ZRJGANNQ-DmKOCo6L.js +1 -0
- package/web/assets/treemap-WGGIJYW6-K64VN5AS-DO3xY92U.js +1 -0
- package/web/assets/{vennDiagram-K3VCMQLI-Bj-FN-XS.js → vennDiagram-QIQU2ZRJ-rgPiS837.js} +3 -3
- package/web/assets/wardley-WFR3VGLG-D67OWCCH-DiDPBNft.js +1 -0
- package/web/assets/wardleyDiagram-U6YNM66N-DRVr7SPi.js +78 -0
- package/web/assets/{xychartDiagram-5VRUVLCW-CK_rbSWE.js → xychartDiagram-XJAZEDNM-COtlZzYb.js} +3 -3
- package/web/index.html +19 -17
- package/web/assets/ProcessFlowModal-Badh2Pbg.js +0 -24
- package/web/assets/architecture-TIHT7OUA-V6N4J3SY-fTePvLoe.js +0 -1
- package/web/assets/architectureDiagram-OT7VKSJ7-mKEXxkeF.js +0 -36
- package/web/assets/blockDiagram-54WLDVWM-Dg_hrRWE.js +0 -132
- package/web/assets/c4Diagram-X2LASM7V-CnhoT4aV.js +0 -10
- package/web/assets/chunk-2AEHWXPW-CV4r6-1I.js +0 -127
- package/web/assets/chunk-5VCL7Z4A-CQ-2dYWj.js +0 -1
- package/web/assets/chunk-6BELYETK-ChsYtfHW.js +0 -1
- package/web/assets/chunk-7CWYLC5S-DRgGkkpF.js +0 -2
- package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +0 -32
- package/web/assets/chunk-KRXBNO2N-C-UNuKKf.js +0 -1
- package/web/assets/chunk-LAHSG3N2-Bz4hcn0N.js +0 -321
- package/web/assets/chunk-LIEV3EAG-pyKGssV3.js +0 -1
- package/web/assets/chunk-NLANEA3F-XpH_NPA5.js +0 -62
- package/web/assets/chunk-PE7DX7ZZ-CCvRVj43.js +0 -10
- package/web/assets/chunk-Q67WD55A-BWCH2SVz.js +0 -156
- package/web/assets/chunk-SZD42YQK-ijyKKVr1.js +0 -70
- package/web/assets/chunk-TFPGFQPA-Dj6a-0pC.js +0 -231
- package/web/assets/chunk-TGVD4F4B-BMc31Gj2.js +0 -2
- package/web/assets/chunk-UAT7B5JY-DW-A_fpI.js +0 -1
- package/web/assets/chunk-VY5UBI4V-yxPQAWxw.js +0 -1
- package/web/assets/chunk-WJBAP47W-BckNkuEN.js +0 -1
- package/web/assets/classDiagram-VPOSVPXH-BjOiXA_C.js +0 -1
- package/web/assets/classDiagram-v2-5VFNUXQA-BjOiXA_C.js +0 -1
- package/web/assets/cynefin-VYW2F7L2-2VAHYKR6-DAsF6GZv.js +0 -1
- package/web/assets/dagre-K64A6Z3X-CUwi79mA.js +0 -4
- package/web/assets/diagram-2TLGJV2N-C2F5Nfym.js +0 -30
- package/web/assets/diagram-KFBZAVDW-Dfmf_4ti.js +0 -3
- package/web/assets/diagram-YP3FGXHI-CyNFYtkd.js +0 -24
- package/web/assets/erDiagram-SZLJLQMB-CohQjznC.js +0 -85
- package/web/assets/eventmodeling-45OFAUF4-AOUJWSLX-BvTc6iQi.js +0 -1
- package/web/assets/flowDiagram-BWE6NHOH-B6RnEvD9.js +0 -1
- package/web/assets/ganttDiagram-2QTCJSNI-Cks_mEcF.js +0 -292
- package/web/assets/gitGraph-TEB2WS4Q-U5VWYIBC-Dy7CCSZ5.js +0 -1
- package/web/assets/gitGraphDiagram-VVZAVV5G-CG41cIVc.js +0 -106
- package/web/assets/info-DKCQHKI2-DX6KST54-BLJg1ikX.js +0 -1
- package/web/assets/infoDiagram-FY4PZKRE-BJ3vPVwW.js +0 -2
- package/web/assets/katex-C5OPUE3Q-Bnbavtmu.js +0 -257
- package/web/assets/packet-7NZHBO7P-5WG45QPS-KUxcuvaA.js +0 -1
- package/web/assets/pie-RZYD4A2V-2OCZ2VTD-DPs08kJY.js +0 -1
- package/web/assets/pieDiagram-YPY3TKG5-BuT3MYzm.js +0 -39
- package/web/assets/radar-I7S5WNFK-EPLSA35B-w5F0K3a7.js +0 -1
- package/web/assets/railroad-3IZDKUUU-PWFQV6Y6-Bu668yqY.js +0 -1
- package/web/assets/railroad-abnf-AHOZXSZD-JYYJX3BS-B6ghpETS.js +0 -1
- package/web/assets/railroad-ebnf-EBAXGLYW-RBZ4ONAP-D-XmHh3M.js +0 -1
- package/web/assets/railroad-peg-LSFZ7HO6-KHNPXGPG-mhkrhzEn.js +0 -1
- package/web/assets/src-C5sx_gMp.js +0 -1
- package/web/assets/stateDiagram-DODFXNG5-DTdHc-fF.js +0 -1
- package/web/assets/stateDiagram-v2-JNMM5JRA-DioNLDDo.js +0 -1
- package/web/assets/swimlanes-OSWOYLSK-CA0ofdwY.js +0 -2
- package/web/assets/swimlanesDiagram-RHGX2RDE-PkwUdckC.js +0 -8
- package/web/assets/treeView-QDETBFTQ-EHYTSQNO-C5lpBgPm.js +0 -1
- package/web/assets/treemap-6X3UGDF4-CCWCF7EK-BB0ZyO-B.js +0 -1
- package/web/assets/wardley-OPB4EBWU-6OWERDVT-CEA3Nq0j.js +0 -1
- package/web/assets/wardleyDiagram-XK6FUAWV-CVV0dHFr.js +0 -78
- /package/web/assets/{sizeCapture-5GORAGP4-BEqQzsxs.js → sizeCapture-FGA5AGYP-BEqQzsxs.js} +0 -0
|
@@ -0,0 +1,2070 @@
|
|
|
1
|
+
import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
|
2
|
+
import { getZigParser, getZigScopeQuery } from './query.js';
|
|
3
|
+
import { getTreeSitterBufferSize } from '../../constants.js';
|
|
4
|
+
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
|
5
|
+
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
|
|
6
|
+
import { hasZigPubKeyword } from '../../export-detection.js';
|
|
7
|
+
import { normalizeZigTypeName } from './interpret.js';
|
|
8
|
+
import { buildZigBoolConstMap, collectZigStaticGatedRanges, isPositionStaticGated, } from '../../call-extractors/zig-static-gating.js';
|
|
9
|
+
/**
|
|
10
|
+
* A call capture inside an `if (CONST_FALSE)` body (or the `else` of an
|
|
11
|
+
* `if (CONST_TRUE)`) gets this extra key; `scope-extractor` turns it into
|
|
12
|
+
* `ReferenceSite.staticGated`
|
|
13
|
+
* and the emitters copy it onto the CALLS edge. Same idiom as Go's
|
|
14
|
+
* `@reference.callee-position`: a zero-range marker, present or absent, so
|
|
15
|
+
* every ungated site's capture set stays byte-identical.
|
|
16
|
+
*/
|
|
17
|
+
const ZERO_RANGE = Object.freeze({ startLine: 0, startCol: 0, endLine: 0, endCol: 0 });
|
|
18
|
+
const STATIC_GATED_MARKER = Object.freeze({
|
|
19
|
+
name: '@reference.static-gated',
|
|
20
|
+
range: ZERO_RANGE,
|
|
21
|
+
text: '',
|
|
22
|
+
});
|
|
23
|
+
const NO_IMPORT_ALIASES = new Map();
|
|
24
|
+
/** Stamp `@reference.static-gated` onto every call capture whose anchor sits
|
|
25
|
+
* in a statically dead range. File-local constants only for now (v1): the
|
|
26
|
+
* cross-file alias walk in `zig-static-gating.ts` needs the repo file list,
|
|
27
|
+
* which the capture layer does not see. */
|
|
28
|
+
function stampZigStaticGating(out, root) {
|
|
29
|
+
// No early return on an empty constant table: `collectZigStaticGatedRanges`
|
|
30
|
+
// also folds bare literals (`if (false) { ... }`), which need no constants.
|
|
31
|
+
const bools = buildZigBoolConstMap(root);
|
|
32
|
+
const ranges = collectZigStaticGatedRanges(root, bools, NO_IMPORT_ALIASES, () => undefined);
|
|
33
|
+
if (ranges.length === 0)
|
|
34
|
+
return out;
|
|
35
|
+
return out.map((m) => {
|
|
36
|
+
const key = Object.keys(m).find((k) => k.startsWith('@reference.call.'));
|
|
37
|
+
if (key === undefined)
|
|
38
|
+
return m;
|
|
39
|
+
const anchor = m[key];
|
|
40
|
+
if (anchor === undefined)
|
|
41
|
+
return m;
|
|
42
|
+
return isPositionStaticGated(anchor.range.startLine, anchor.range.startCol, ranges)
|
|
43
|
+
? { ...m, '@reference.static-gated': STATIC_GATED_MARKER }
|
|
44
|
+
: m;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** Zig container node types: `struct`, `enum`, `union` and the fieldless
|
|
48
|
+
* `opaque` all bind through `const T = <container> {…}` and may own methods.
|
|
49
|
+
* Single source for the class/field/method extractor configs. */
|
|
50
|
+
export const ZIG_CONTAINER_TYPES = new Set([
|
|
51
|
+
'struct_declaration',
|
|
52
|
+
'enum_declaration',
|
|
53
|
+
'union_declaration',
|
|
54
|
+
'opaque_declaration',
|
|
55
|
+
]);
|
|
56
|
+
/** Is `node` the `@import(…)` builtin call? */
|
|
57
|
+
function isZigImportBuiltin(node) {
|
|
58
|
+
if (node?.type !== 'builtin_function')
|
|
59
|
+
return false;
|
|
60
|
+
const builtin = node.namedChild(0);
|
|
61
|
+
return builtin?.type === 'builtin_identifier' && builtin.text === '@import';
|
|
62
|
+
}
|
|
63
|
+
/** The `@import(…)` call at the ROOT of a value expression: the value itself
|
|
64
|
+
* (`@import("x.zig")`) or the leftmost object of a member chain
|
|
65
|
+
* (`@import("x.zig").Foo`, `@import("std").mem.Allocator`). Null otherwise. */
|
|
66
|
+
export function zigImportRootOf(value) {
|
|
67
|
+
let cur = value;
|
|
68
|
+
while (cur?.type === 'field_expression')
|
|
69
|
+
cur = cur.childForFieldName('object');
|
|
70
|
+
return isZigImportBuiltin(cur) ? cur : null;
|
|
71
|
+
}
|
|
72
|
+
/** Is this `@import(…)` builtin the receiver of a member call —
|
|
73
|
+
* `@import("dump.zig").root(...)` — or of a qualified construction —
|
|
74
|
+
* `@import("a.zig").Thing{…}` — i.e. the `object` of a `field_expression`
|
|
75
|
+
* that is the `function` of a `call_expression` or the type of a
|
|
76
|
+
* `struct_initializer`? A deeper chain (`@import("x.zig").Foo.init()`) is
|
|
77
|
+
* not: its receiver is `….Foo`, and the builtin is only the module the chain
|
|
78
|
+
* starts from (the namespace chain walk resolves it from there). */
|
|
79
|
+
export function isZigInlineImportReceiver(importNode) {
|
|
80
|
+
const field = importNode.parent;
|
|
81
|
+
if (field?.type !== 'field_expression')
|
|
82
|
+
return false;
|
|
83
|
+
if (field.childForFieldName('object')?.id !== importNode.id)
|
|
84
|
+
return false;
|
|
85
|
+
const use = field.parent;
|
|
86
|
+
if (use === null || use === undefined)
|
|
87
|
+
return false;
|
|
88
|
+
if (use.type === 'call_expression')
|
|
89
|
+
return use.childForFieldName('function')?.id === field.id;
|
|
90
|
+
// `@import("a.zig").Thing{…}` — the module is the receiver of a qualified
|
|
91
|
+
// CONSTRUCTION (the `@reference.call.constructor` rule with a receiver, see
|
|
92
|
+
// the query), exactly as it is of a member call. Without the namespace
|
|
93
|
+
// binding the site had a receiver nothing was bound to, so the aggregate
|
|
94
|
+
// event had no target (PR #1432 review, 8.11).
|
|
95
|
+
return use.type === 'struct_initializer' && use.namedChild(0)?.id === field.id;
|
|
96
|
+
}
|
|
97
|
+
/** Is this variable_declaration a container binding (`const T = struct {…}`)
|
|
98
|
+
* or an import binding (`const x = @import("…")`, `const X = @import("…").X`)?
|
|
99
|
+
* Those groups are emitted by their dedicated query rules; the plain
|
|
100
|
+
* @declaration.variable / @definition.const match for the same node must be
|
|
101
|
+
* dropped so the name binds exactly once (and no Const node shadows the
|
|
102
|
+
* Struct / import). Shared by the scope walker, the variable extractor and
|
|
103
|
+
* the provider's `shouldSkipDefinitionCapture` so the structure-phase records
|
|
104
|
+
* and the scope-side bindings agree on what counts as a plain variable. */
|
|
105
|
+
/** A binding whose name the type system already owns: a container / import
|
|
106
|
+
* binding, or the top-level `@This()` self-alias of a file-struct. None of
|
|
107
|
+
* these may mint a Const beside the Struct / import they stand for. */
|
|
108
|
+
export function isZigTypeShadowingBinding(declNode) {
|
|
109
|
+
if (isZigContainerOrImportBinding(declNode))
|
|
110
|
+
return true;
|
|
111
|
+
return isZigFileThisAlias(declNode) && isZigFileStruct(declNode.parent);
|
|
112
|
+
}
|
|
113
|
+
export function isZigContainerOrImportBinding(declNode) {
|
|
114
|
+
const typeNode = declNode.childForFieldName('type');
|
|
115
|
+
for (let i = 0; i < declNode.namedChildCount; i++) {
|
|
116
|
+
const child = declNode.namedChild(i);
|
|
117
|
+
if (child === null)
|
|
118
|
+
continue;
|
|
119
|
+
if (ZIG_CONTAINER_TYPES.has(child.type))
|
|
120
|
+
return true;
|
|
121
|
+
// Only the VALUE binds an import: `var x: @import("foo.zig").T =
|
|
122
|
+
// undefined;` types a plain variable by an imported type, it imports
|
|
123
|
+
// nothing under `x` (see `isZigTypePositionImport`).
|
|
124
|
+
if (child.id === typeNode?.id)
|
|
125
|
+
continue;
|
|
126
|
+
if (zigImportRootOf(child) !== null)
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
/** Does the `@import(…)` a binding rule matched sit in the TYPE annotation of
|
|
132
|
+
* its declaration (`var x: @import("foo.zig").T = undefined;`) rather than
|
|
133
|
+
* in its value? `variable_declaration` is fieldless except for `type:`, so
|
|
134
|
+
* the query cannot tell the two positions apart; such a match must not bind
|
|
135
|
+
* `x` as an import — `x` is a variable, and the `@import` is only a file
|
|
136
|
+
* dependency (the `@import.inline` rule records it as a side-effect import
|
|
137
|
+
* once this match releases the source node). */
|
|
138
|
+
export function isZigTypePositionImport(stmt, source) {
|
|
139
|
+
const typeNode = stmt.childForFieldName('type');
|
|
140
|
+
return (typeNode !== null &&
|
|
141
|
+
typeNode.startIndex <= source.startIndex &&
|
|
142
|
+
source.endIndex <= typeNode.endIndex);
|
|
143
|
+
}
|
|
144
|
+
/** Does this `variable_declaration` carry a `const` / `var` keyword child?
|
|
145
|
+
* tree-sitter-zig 1.1.2 parses statement-position ASSIGNMENTS (`x = 5;`,
|
|
146
|
+
* `x += 1;`, `_ = expr;`) as `variable_declaration` too — the only thing
|
|
147
|
+
* that separates a real binding from a re-assignment is the keyword. Query
|
|
148
|
+
* rules match the keyword literally; this is the JS-side twin for the
|
|
149
|
+
* extractors and the phantom-local guard in `emitZigScopeCaptures`. */
|
|
150
|
+
export function isZigKeywordDeclaration(declNode) {
|
|
151
|
+
for (let i = 0; i < declNode.childCount; i++) {
|
|
152
|
+
const t = declNode.child(i)?.type;
|
|
153
|
+
if (t === 'const' || t === 'var')
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
/** Is `root` a FILE-STRUCT — a `.zig` file that IS a type? In Zig every file
|
|
159
|
+
* is a struct; the ones that matter as types are named after the file stem
|
|
160
|
+
* (`Page.zig` declares `Page`, `@typeName(Page)` is `"Page"`) and their
|
|
161
|
+
* top-level `fn`s taking a receiver are its methods. Two signals, either one
|
|
162
|
+
* suffices:
|
|
163
|
+
* - the top level declares at least one container field (an instantiable
|
|
164
|
+
* type with state); the MISSING-identifier placeholder tree-sitter-zig
|
|
165
|
+
* recovers for an empty body is not a field (see
|
|
166
|
+
* `zigFieldConfig.extractName`);
|
|
167
|
+
* - a top-level `fn` takes the file's OWN type as its first parameter —
|
|
168
|
+
* `self: *@This()`, or `self: *Self` with `const Self = @This();` at
|
|
169
|
+
* file level. A zero-sized file type (`Empty.zig`: no field, a `Self`
|
|
170
|
+
* alias and `pub fn ping(self: *Self)`) is still constructed (`Empty{}`)
|
|
171
|
+
* and dispatched on by importers; keyed on fields alone it lost its
|
|
172
|
+
* `Struct`, its `HAS_METHOD`s and every `e.ping()` edge (PR #1432
|
|
173
|
+
* review, 8.12).
|
|
174
|
+
* A file with neither is a namespace and stays a Module: its fns keep their
|
|
175
|
+
* `Function` ids, and a `const js = @This();` there stays a Const. Only the
|
|
176
|
+
* receiver TYPE decides, never the parameter name: `fn f(self: Foo)` in a
|
|
177
|
+
* utility file is a free function that happens to call its argument `self`. */
|
|
178
|
+
export function isZigFileStruct(root) {
|
|
179
|
+
if (root?.type !== 'source_file')
|
|
180
|
+
return false;
|
|
181
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
182
|
+
const child = root.namedChild(i);
|
|
183
|
+
if (child?.type !== 'container_field')
|
|
184
|
+
continue;
|
|
185
|
+
const name = child.childForFieldName('name');
|
|
186
|
+
if (name !== null && name.text.length > 0)
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
let aliases;
|
|
190
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
191
|
+
const fn = root.namedChild(i);
|
|
192
|
+
if (fn?.type !== 'function_declaration')
|
|
193
|
+
continue;
|
|
194
|
+
const first = fn.namedChildren
|
|
195
|
+
.find((c) => c?.type === 'parameters')
|
|
196
|
+
?.namedChild(0);
|
|
197
|
+
const typeNode = first?.type === 'parameter' ? first.childForFieldName('type') : null;
|
|
198
|
+
if (typeNode === null || typeNode === undefined)
|
|
199
|
+
continue;
|
|
200
|
+
const nominal = zigParameterNominalType(typeNode.text);
|
|
201
|
+
if (nominal === '@This()')
|
|
202
|
+
return true;
|
|
203
|
+
aliases ??= zigThisAliasNamesIn(root);
|
|
204
|
+
if (aliases.has(nominal))
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
/** The type name a file-struct declares: the file stem (`src/browser/Page.zig`
|
|
210
|
+
* → `Page`). This is what importers write (`const Page = @import("Page.zig")`)
|
|
211
|
+
* and what `@typeName` reports; the in-file `const Page = @This();` alias is
|
|
212
|
+
* a second spelling of the same type (see `zigThisAliasTargets`). */
|
|
213
|
+
export function zigFileStructName(filePath) {
|
|
214
|
+
const base = filePath.replace(/\\/g, '/').split('/').pop() ?? filePath;
|
|
215
|
+
return base.endsWith('.zig') ? base.slice(0, -'.zig'.length) : base;
|
|
216
|
+
}
|
|
217
|
+
/** Is `declNode` a top-level `const X = @This();` — the idiomatic self-alias of
|
|
218
|
+
* a file-struct? Such a binding names the FILE's own type, so a file-struct
|
|
219
|
+
* must not mint a `Const` (graph node or scope binding) beside the `Struct`
|
|
220
|
+
* it aliases — the Const would shadow the type for every `x: *Page`. */
|
|
221
|
+
export function isZigFileThisAlias(declNode) {
|
|
222
|
+
if (declNode.type !== 'variable_declaration' || declNode.parent?.type !== 'source_file') {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if (!isZigKeywordDeclaration(declNode))
|
|
226
|
+
return false;
|
|
227
|
+
const named = declNode.namedChildren.filter((c) => c !== null);
|
|
228
|
+
if (named.length !== 2 || named[0].type !== 'identifier')
|
|
229
|
+
return false;
|
|
230
|
+
const value = named[1];
|
|
231
|
+
return (value.type === 'builtin_function' &&
|
|
232
|
+
value.namedChild(0)?.type === 'builtin_identifier' &&
|
|
233
|
+
value.namedChild(0)?.text === '@This');
|
|
234
|
+
}
|
|
235
|
+
/** Does this module-level import binding REPUBLISH the name? `pub const Arena
|
|
236
|
+
* = @import("Arena.zig");` at file scope makes `Arena` part of this file's
|
|
237
|
+
* public surface: a third file reaches it as `lp.Arena` (Lightpanda's
|
|
238
|
+
* `lightpanda.zig` is one long list of these). Same fact as a Python
|
|
239
|
+
* `__init__.py` `from .impl import X` — the shared contract's
|
|
240
|
+
* `reexportsName` flag. Only `pub` at container level publishes; a `const`
|
|
241
|
+
* inside a fn body binds locally. */
|
|
242
|
+
export function isZigPublishingImport(declNode) {
|
|
243
|
+
return declNode.parent?.type === 'source_file' && hasZigPubKeyword(declNode);
|
|
244
|
+
}
|
|
245
|
+
/** The BINDING name of a Zig container node — the spelling code uses to
|
|
246
|
+
* refer to it — or undefined for a truly anonymous one. Three shapes carry
|
|
247
|
+
* one:
|
|
248
|
+
* - a file-struct: the file stem (`Page.zig` → `Page`);
|
|
249
|
+
* - `const Point = struct {…}` — the first identifier of the wrapping
|
|
250
|
+
* `variable_declaration`;
|
|
251
|
+
* - `pub fn List(comptime T: type) type { return struct {…}; }` — the
|
|
252
|
+
* generic type constructor. Zig has no other spelling for a generic
|
|
253
|
+
* type, and every reader calls the returned container `List`, so the
|
|
254
|
+
* enclosing function's name IS the type name (`ArrayList(u8)`).
|
|
255
|
+
* This is what scope-side NAME BINDINGS use (`R.get()`, `self: *R`, a
|
|
256
|
+
* `const Self = @This();` rewrite). The graph IDENTITY of a container is
|
|
257
|
+
* `zigContainerName`, which equals the binding name except for the
|
|
258
|
+
* function-local and anonymous shapes (F8). */
|
|
259
|
+
export function zigContainerBindingName(containerNode, filePath) {
|
|
260
|
+
if (containerNode.type === 'source_file') {
|
|
261
|
+
return filePath !== undefined && isZigFileStruct(containerNode)
|
|
262
|
+
? zigFileStructName(filePath)
|
|
263
|
+
: undefined;
|
|
264
|
+
}
|
|
265
|
+
const parent = containerNode.parent;
|
|
266
|
+
if (parent === null || parent === undefined)
|
|
267
|
+
return undefined;
|
|
268
|
+
if (parent.type === 'variable_declaration') {
|
|
269
|
+
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
270
|
+
const child = parent.namedChild(i);
|
|
271
|
+
if (child?.type === 'identifier')
|
|
272
|
+
return child.text;
|
|
273
|
+
}
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
return zigTypeConstructorOf(containerNode)?.childForFieldName('name')?.text;
|
|
277
|
+
}
|
|
278
|
+
/** The nominal type spelled at a parameter with its sigils stripped:
|
|
279
|
+
* `*const Self` → `Self`, `?*T` → `T`, `Pool(Node)` → `Pool`; `@This()` is
|
|
280
|
+
* kept whole (a builtin, not a constructor name). */
|
|
281
|
+
function zigParameterNominalType(typeText) {
|
|
282
|
+
let t = typeText.trim();
|
|
283
|
+
let previous;
|
|
284
|
+
do {
|
|
285
|
+
previous = t;
|
|
286
|
+
t = t.replace(/^(?:\*|\?|const\s+|\s+)/, '');
|
|
287
|
+
} while (t !== previous);
|
|
288
|
+
if (!t.startsWith('@')) {
|
|
289
|
+
const paren = t.indexOf('(');
|
|
290
|
+
if (paren > 0)
|
|
291
|
+
t = t.slice(0, paren).trim();
|
|
292
|
+
}
|
|
293
|
+
return t;
|
|
294
|
+
}
|
|
295
|
+
/** The `const X = @This();` alias names declared DIRECTLY in `container` (a
|
|
296
|
+
* container node, or the root of a file-struct). */
|
|
297
|
+
function zigThisAliasNamesIn(container) {
|
|
298
|
+
const out = new Set();
|
|
299
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
300
|
+
const decl = container.namedChild(i);
|
|
301
|
+
if (decl === null || decl.type !== 'variable_declaration')
|
|
302
|
+
continue;
|
|
303
|
+
const named = decl.namedChildren.filter((c) => c !== null);
|
|
304
|
+
const value = named[named.length - 1];
|
|
305
|
+
if (named.length === 2 &&
|
|
306
|
+
named[0].type === 'identifier' &&
|
|
307
|
+
value?.type === 'builtin_function' &&
|
|
308
|
+
value.namedChild(0)?.text === '@This' &&
|
|
309
|
+
isZigKeywordDeclaration(decl)) {
|
|
310
|
+
out.add(named[0].text);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
315
|
+
/** The RECEIVER parameter of a container method, or null for a static fn.
|
|
316
|
+
*
|
|
317
|
+
* Zig has no receiver keyword: `self` is a convention, not a rule, and real
|
|
318
|
+
* code names the receiver after its type as often as not — tigerbeetle
|
|
319
|
+
* (`replica: *Replica`, 777 of 1127 methods), mach (`pool: *@This()`, 764 of
|
|
320
|
+
* 833). Treating only `self` as the receiver labelled those methods static,
|
|
321
|
+
* counted the receiver in their arity (`Counter.incr#1`) and left the
|
|
322
|
+
* scope-side binding sourced as a plain parameter. The rule, applied to the
|
|
323
|
+
* FIRST parameter only:
|
|
324
|
+
* - named `self`, whatever its type; or
|
|
325
|
+
* - typed as the enclosing container: `@This()`, the container's binding
|
|
326
|
+
* name (`Counter`, the generic constructor `Pool` for `Pool(Node)`, the
|
|
327
|
+
* file stem for a file-struct when `filePath` is known), or a
|
|
328
|
+
* `const X = @This();` alias declared in that container (`Self`, `PRNG`
|
|
329
|
+
* in `prng.zig`) — pointers, `const` and optionals stripped.
|
|
330
|
+
* `fn` must be a direct child of a container (or the file root); a fn nested
|
|
331
|
+
* in a body is never a method. Single source for the method extractor
|
|
332
|
+
* (parameters / receiver type / `isStatic`) and `emitZigScopeCaptures`
|
|
333
|
+
* (`@type-binding.receiver`), so the structure and scope phases agree. */
|
|
334
|
+
export function zigReceiverParameter(fn, filePath) {
|
|
335
|
+
const container = fn.parent;
|
|
336
|
+
if (container === null || container === undefined)
|
|
337
|
+
return null;
|
|
338
|
+
const inContainer = ZIG_CONTAINER_TYPES.has(container.type);
|
|
339
|
+
const inFileStruct = container.type === 'source_file' && isZigFileStruct(container);
|
|
340
|
+
if (!inContainer && !inFileStruct)
|
|
341
|
+
return null;
|
|
342
|
+
const paramList = fn.namedChildren.find((child) => child?.type === 'parameters');
|
|
343
|
+
const first = paramList?.namedChild(0);
|
|
344
|
+
if (first === null || first === undefined || first.type !== 'parameter')
|
|
345
|
+
return null;
|
|
346
|
+
if (first.childForFieldName('name')?.text === 'self')
|
|
347
|
+
return first;
|
|
348
|
+
const typeNode = first.childForFieldName('type');
|
|
349
|
+
if (typeNode === null)
|
|
350
|
+
return null;
|
|
351
|
+
const nominal = zigParameterNominalType(typeNode.text);
|
|
352
|
+
if (nominal.length === 0)
|
|
353
|
+
return null;
|
|
354
|
+
if (nominal === '@This()')
|
|
355
|
+
return first;
|
|
356
|
+
if (zigThisAliasNamesIn(container).has(nominal))
|
|
357
|
+
return first;
|
|
358
|
+
const binding = zigContainerBindingName(container, filePath);
|
|
359
|
+
return binding !== undefined && nominal === binding ? first : null;
|
|
360
|
+
}
|
|
361
|
+
/** The graph IDENTITY of a Zig container node — the name its class-like
|
|
362
|
+
* node, its members' owner segment (`Method:<file>:<name>.<fn>`) and the
|
|
363
|
+
* scope-side def all carry. Single source for the class/field/method
|
|
364
|
+
* extractor configs, the provider's `resolveContainerTypeOwner` hook (the
|
|
365
|
+
* structure phase's owner walk) and `emitZigScopeCaptures`, so the two
|
|
366
|
+
* phases agree by construction.
|
|
367
|
+
*
|
|
368
|
+
* - file-struct / `const T = struct {…}` at file level / generic type
|
|
369
|
+
* constructor: the binding name (`zigContainerBindingName`).
|
|
370
|
+
* - CONTAINER-HOSTED named container — `pub const Item = struct {…}`
|
|
371
|
+
* inside `const A = struct {…}`: owner-qualified, `A.Item` (recursively:
|
|
372
|
+
* `A.B.Item`). By binding name alone `A.Item` and `B.Item` — two
|
|
373
|
+
* distinct types, each with its own `run` — shared ONE `Struct:<file>:
|
|
374
|
+
* Item` node and one `Item.run` (PR #1432 review, 8.5). The scope side
|
|
375
|
+
* still binds the lexical spelling `Item` inside `A` (the
|
|
376
|
+
* `@declaration.binding-name` split in `emitZigScopeCaptures`), and the
|
|
377
|
+
* shared `populateClassOwnedMembers` leaves a dotted qualified name
|
|
378
|
+
* alone, so the def and the graph node agree on `A.Item` and its members
|
|
379
|
+
* qualify as `A.Item.run` — the same shape Java's `Outer.Inner` takes.
|
|
380
|
+
* - FUNCTION-LOCAL named container (F8) — `const R = struct {…}` inside a
|
|
381
|
+
* `fn` or `test` body: `<enclosing callable>$<name>`, e.g. `string$R`,
|
|
382
|
+
* `Reflect.string$R`. Zig code declares such helper containers per
|
|
383
|
+
* builder function (Lightpanda's `reflection.zig` has ~20 `const R =
|
|
384
|
+
* struct { fn get… fn set… }`), and by binding name alone they all
|
|
385
|
+
* collapsed onto ONE `Struct:<file>:R` with one `R.get`. Java's local
|
|
386
|
+
* classes are keyed the same way (`Outer$1Local`); the `$` is what tells
|
|
387
|
+
* the shared `populateClassOwnedMembers` the name is already complete.
|
|
388
|
+
* Two same-named locals in sibling blocks of ONE callable still share
|
|
389
|
+
* an identity (Zig code does not do that; javac's ordinal would).
|
|
390
|
+
* - ANONYMOUS container (F8) — `struct { fn lessThan(…) … }.lessThan`
|
|
391
|
+
* passed to `std.sort.pdq`, `const cmp = struct {…}.lt;`, a field or
|
|
392
|
+
* parameter typed `struct { min: u32 }`: `<host>$<ordinal>` where the
|
|
393
|
+
* host is the enclosing callable (`build$1`), else the enclosing
|
|
394
|
+
* container (`Outer$1`), else the file stem (`util$1`), and the ordinal
|
|
395
|
+
* counts the anonymous containers of that host in source order —
|
|
396
|
+
* javac's `Outer$1` rule (`synthesizeJavaTypeIdentity`). Without an
|
|
397
|
+
* identity their fns were OWNERLESS Methods (`Method:<file>:lessThan`),
|
|
398
|
+
* and same-named ones in one file collided on a single node.
|
|
399
|
+
* Deterministic in the tree alone, so any two walks over one source agree. */
|
|
400
|
+
export function zigContainerName(containerNode, filePath) {
|
|
401
|
+
const binding = zigContainerBindingName(containerNode, filePath);
|
|
402
|
+
if (containerNode.type === 'source_file')
|
|
403
|
+
return binding;
|
|
404
|
+
if (!ZIG_CONTAINER_TYPES.has(containerNode.type))
|
|
405
|
+
return undefined;
|
|
406
|
+
const host = zigIdentityHost(containerNode);
|
|
407
|
+
if (binding !== undefined) {
|
|
408
|
+
if (containerNode.parent?.type !== 'variable_declaration' || host === null)
|
|
409
|
+
return binding;
|
|
410
|
+
if (isZigCallableNode(host))
|
|
411
|
+
return `${zigCallableQualifiedName(host, filePath)}$${binding}`;
|
|
412
|
+
if (ZIG_CONTAINER_TYPES.has(host.type)) {
|
|
413
|
+
const hostName = zigContainerName(host, filePath);
|
|
414
|
+
return hostName === undefined ? binding : `${hostName}.${binding}`;
|
|
415
|
+
}
|
|
416
|
+
return binding; // file level: the binding name is the identity
|
|
417
|
+
}
|
|
418
|
+
const ordinal = zigAnonymousContainerOrdinal(containerNode);
|
|
419
|
+
const prefix = host === null ? undefined : zigAnonymousHostPrefix(host, filePath);
|
|
420
|
+
return `${prefix ?? ''}$${ordinal}`;
|
|
421
|
+
}
|
|
422
|
+
/** The label the graph gives a container node (`opaque {}` is a fieldless
|
|
423
|
+
* container that may own methods — Struct, see ZIG_QUERIES). */
|
|
424
|
+
export function zigContainerLabel(containerNode) {
|
|
425
|
+
switch (containerNode.type) {
|
|
426
|
+
case 'struct_declaration':
|
|
427
|
+
case 'opaque_declaration':
|
|
428
|
+
return 'Struct';
|
|
429
|
+
case 'enum_declaration':
|
|
430
|
+
return 'Enum';
|
|
431
|
+
case 'union_declaration':
|
|
432
|
+
return 'Union';
|
|
433
|
+
default:
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/** Which ZIG_QUERIES rule mints a container's graph node (F8):
|
|
438
|
+
* - 'wrapper': `const T = struct {…}` at FILE level — the
|
|
439
|
+
* `variable_declaration … @definition.struct` rule (name from `@name`);
|
|
440
|
+
* - 'constructor': the container a generic type constructor returns —
|
|
441
|
+
* the `fn … type { return struct {…}; }` rule;
|
|
442
|
+
* - 'container': everything the bare `(struct_declaration)
|
|
443
|
+
* @definition.struct` rule owns — FUNCTION-LOCAL and CONTAINER-HOSTED
|
|
444
|
+
* named containers (their identities `string$R` / `A.Item` are not
|
|
445
|
+
* captures, so the class extractor names them via `zigContainerName`)
|
|
446
|
+
* and ANONYMOUS containers.
|
|
447
|
+
* The provider's `shouldSkipClassCapture` drops the other rules' matches for
|
|
448
|
+
* the same node so each container is minted exactly once. */
|
|
449
|
+
export function zigContainerAnchor(containerNode) {
|
|
450
|
+
if (containerNode.type === 'source_file')
|
|
451
|
+
return 'file';
|
|
452
|
+
if (zigTypeConstructorOf(containerNode) !== null)
|
|
453
|
+
return 'constructor';
|
|
454
|
+
if (containerNode.parent?.type === 'variable_declaration' &&
|
|
455
|
+
zigContainerBindingName(containerNode) !== undefined) {
|
|
456
|
+
const host = zigIdentityHost(containerNode);
|
|
457
|
+
return host === null || host.type === 'source_file' ? 'wrapper' : 'container';
|
|
458
|
+
}
|
|
459
|
+
return 'container';
|
|
460
|
+
}
|
|
461
|
+
/** Is this `@definition.<container>` match of ZIG_QUERIES REDUNDANT — i.e.
|
|
462
|
+
* minted by another rule for the same container (F8)? `definitionNode` is
|
|
463
|
+
* the match anchor (the wrapper `variable_declaration`, or the container
|
|
464
|
+
* node for the type-constructor and bare-container rules), `nameNode` its
|
|
465
|
+
* `@name` capture (the type-constructor rule captures the fn's name, the
|
|
466
|
+
* bare rule captures none). Wired through the provider's
|
|
467
|
+
* `shouldSkipDefinitionCapture`, which the definition phase consults for
|
|
468
|
+
* EVERY label — `shouldSkipClassCapture` would not see `Union`. */
|
|
469
|
+
export function isZigRedundantContainerCapture(definitionNode, nameNode) {
|
|
470
|
+
let container;
|
|
471
|
+
if (ZIG_CONTAINER_TYPES.has(definitionNode.type)) {
|
|
472
|
+
container = definitionNode;
|
|
473
|
+
}
|
|
474
|
+
else if (definitionNode.type === 'variable_declaration') {
|
|
475
|
+
for (let i = 0; i < definitionNode.namedChildCount; i++) {
|
|
476
|
+
const child = definitionNode.namedChild(i);
|
|
477
|
+
if (child !== null && ZIG_CONTAINER_TYPES.has(child.type)) {
|
|
478
|
+
container = child;
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (container === undefined)
|
|
484
|
+
return false;
|
|
485
|
+
const anchor = zigContainerAnchor(container);
|
|
486
|
+
if (definitionNode.type === 'variable_declaration')
|
|
487
|
+
return anchor !== 'wrapper';
|
|
488
|
+
return nameNode !== undefined ? anchor !== 'constructor' : anchor !== 'container';
|
|
489
|
+
}
|
|
490
|
+
/** A callable body in Zig: a `fn` or a `test` block. */
|
|
491
|
+
function isZigCallableNode(node) {
|
|
492
|
+
return node.type === 'function_declaration' || node.type === 'test_declaration';
|
|
493
|
+
}
|
|
494
|
+
/** The nearest ancestor that hosts identities — a callable, a container or
|
|
495
|
+
* the file — starting from `node`'s parent. */
|
|
496
|
+
function zigIdentityHost(node) {
|
|
497
|
+
let cur = node.parent;
|
|
498
|
+
while (cur !== null && cur !== undefined) {
|
|
499
|
+
if (isZigCallableNode(cur) || ZIG_CONTAINER_TYPES.has(cur.type) || cur.type === 'source_file') {
|
|
500
|
+
return cur;
|
|
501
|
+
}
|
|
502
|
+
cur = cur.parent;
|
|
503
|
+
}
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
/** A callable's qualified name as the structure phase spells it: its owner
|
|
507
|
+
* container's identity, a dot, its own name (`Reflect.string`, `Page.init`,
|
|
508
|
+
* `helper` in a namespace file). A `test` block is keyed by its LINE
|
|
509
|
+
* (`test@L<line>`, 1-based; a test is a top-level declaration, so the line
|
|
510
|
+
* identifies it), not by its string: the class extractor's
|
|
511
|
+
* `buildQualifiedName` normalizes a graph node's `qualifiedName`
|
|
512
|
+
* (whitespace stripped, `::` → `.`), so an identity carrying `"Function:
|
|
513
|
+
* requested termination …"` would key the node under one spelling and the
|
|
514
|
+
* scope def under another, and `State{}` in that test would resolve to
|
|
515
|
+
* nothing. No `:` either (`row:col` would do), so an id keeps a single
|
|
516
|
+
* `label:file:name` colon structure for anything that splits on it. */
|
|
517
|
+
export function zigCallableQualifiedName(fnNode, filePath) {
|
|
518
|
+
const own = fnNode.type === 'test_declaration'
|
|
519
|
+
? `test@L${fnNode.startPosition.row + 1}`
|
|
520
|
+
: (fnNode.childForFieldName('name')?.text ?? `fn@L${fnNode.startPosition.row + 1}`);
|
|
521
|
+
const host = zigIdentityHost(fnNode);
|
|
522
|
+
if (host === null)
|
|
523
|
+
return own;
|
|
524
|
+
const hostName = ZIG_CONTAINER_TYPES.has(host.type)
|
|
525
|
+
? zigContainerName(host, filePath)
|
|
526
|
+
: host.type === 'source_file'
|
|
527
|
+
? zigContainerBindingName(host, filePath) // file-struct stem; nothing for a namespace file
|
|
528
|
+
: zigCallableQualifiedName(host, filePath);
|
|
529
|
+
return hostName === undefined ? own : `${hostName}.${own}`;
|
|
530
|
+
}
|
|
531
|
+
/** The `<host>` part of an anonymous container's identity. */
|
|
532
|
+
function zigAnonymousHostPrefix(host, filePath) {
|
|
533
|
+
if (isZigCallableNode(host))
|
|
534
|
+
return zigCallableQualifiedName(host, filePath);
|
|
535
|
+
if (ZIG_CONTAINER_TYPES.has(host.type))
|
|
536
|
+
return zigContainerName(host, filePath);
|
|
537
|
+
// File level: the file stem — a Zig file is itself a struct, so this is
|
|
538
|
+
// javac's `Outer$1` with the file as `Outer` (`Page$1`, `util$1`).
|
|
539
|
+
return filePath === undefined ? undefined : zigFileStructName(filePath);
|
|
540
|
+
}
|
|
541
|
+
/** 1-based ordinal of an anonymous container among the anonymous containers
|
|
542
|
+
* sharing its identity host, in source order. Computed once per tree and
|
|
543
|
+
* memoized on the tree object (a node without `.tree` recomputes from its
|
|
544
|
+
* root); position-derived, so two parses of one source agree. */
|
|
545
|
+
const zigAnonymousOrdinalMemo = new WeakMap();
|
|
546
|
+
function zigAnonymousContainerOrdinal(containerNode) {
|
|
547
|
+
const tree = containerNode.tree;
|
|
548
|
+
let table = tree === undefined ? undefined : zigAnonymousOrdinalMemo.get(tree);
|
|
549
|
+
if (table === undefined) {
|
|
550
|
+
let root = containerNode;
|
|
551
|
+
while (root.parent !== null && root.parent !== undefined)
|
|
552
|
+
root = root.parent;
|
|
553
|
+
const built = new Map();
|
|
554
|
+
const perHost = new Map();
|
|
555
|
+
const visit = (node) => {
|
|
556
|
+
if (ZIG_CONTAINER_TYPES.has(node.type) && zigContainerBindingName(node) === undefined) {
|
|
557
|
+
const host = zigIdentityHost(node);
|
|
558
|
+
const key = host === null ? -1 : host.id;
|
|
559
|
+
const next = (perHost.get(key) ?? 0) + 1;
|
|
560
|
+
perHost.set(key, next);
|
|
561
|
+
built.set(node.id, next);
|
|
562
|
+
}
|
|
563
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
564
|
+
const child = node.namedChild(i);
|
|
565
|
+
if (child !== null)
|
|
566
|
+
visit(child);
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
visit(root);
|
|
570
|
+
if (tree !== undefined)
|
|
571
|
+
zigAnonymousOrdinalMemo.set(tree, built);
|
|
572
|
+
table = built;
|
|
573
|
+
}
|
|
574
|
+
return table.get(containerNode.id) ?? 0;
|
|
575
|
+
}
|
|
576
|
+
/** For a container that is the direct `return` value of a function whose
|
|
577
|
+
* return type is `type` — `fn List(comptime T: type) type { return struct
|
|
578
|
+
* {…}; }` — the function_declaration; null for any other placement. Only the
|
|
579
|
+
* literal `return_expression → expression_statement → block → fn` chain
|
|
580
|
+
* counts: a container nested deeper (`return struct {…}.field`, a container
|
|
581
|
+
* inside an `if`) is not the type the function constructs. */
|
|
582
|
+
export function zigTypeConstructorOf(containerNode) {
|
|
583
|
+
if (!ZIG_CONTAINER_TYPES.has(containerNode.type))
|
|
584
|
+
return null;
|
|
585
|
+
const ret = containerNode.parent;
|
|
586
|
+
if (ret?.type !== 'return_expression')
|
|
587
|
+
return null;
|
|
588
|
+
const stmt = ret.parent;
|
|
589
|
+
if (stmt?.type !== 'expression_statement')
|
|
590
|
+
return null;
|
|
591
|
+
const block = stmt.parent;
|
|
592
|
+
if (block?.type !== 'block')
|
|
593
|
+
return null;
|
|
594
|
+
const fn = block.parent;
|
|
595
|
+
if (fn?.type !== 'function_declaration' || fn.childForFieldName('body')?.id !== block.id) {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
return fn.childForFieldName('type')?.text === 'type' ? fn : null;
|
|
599
|
+
}
|
|
600
|
+
/** F7 — is `call` an instantiation of a generic type constructor —
|
|
601
|
+
* `List(u8)`, `util.List(u8)`, `js.Bridge(T)`, `std.AutoHashMapUnmanaged(K, V)`?
|
|
602
|
+
* Zig spells a generic type as a CALL, and the grammar cannot separate it
|
|
603
|
+
* from a value call; the one signal is the naming convention (types and
|
|
604
|
+
* type constructors are TitleCase, functions camelCase), so the callee's
|
|
605
|
+
* last identifier decides. Heuristic by nature — say so wherever it is used. */
|
|
606
|
+
export function isZigTypeConstructorCall(call) {
|
|
607
|
+
if (call.type !== 'call_expression')
|
|
608
|
+
return false;
|
|
609
|
+
let callee = call.childForFieldName('function');
|
|
610
|
+
if (callee?.type === 'field_expression')
|
|
611
|
+
callee = callee.childForFieldName('member');
|
|
612
|
+
if (callee?.type !== 'identifier')
|
|
613
|
+
return false;
|
|
614
|
+
return /^[A-Z]/.test(callee.text);
|
|
615
|
+
}
|
|
616
|
+
/** A `fn` nested in a struct/enum/union/opaque container is a method. Single
|
|
617
|
+
* predicate shared between the provider's `labelOverride` (worker
|
|
618
|
+
* structure phase) and the scope-capture relabel below, so the graph
|
|
619
|
+
* node label and the scope-side def label cannot drift apart. The loose
|
|
620
|
+
* parameter shape matches what `labelOverride` receives.
|
|
621
|
+
*
|
|
622
|
+
* Only a `function_declaration` can be a method: a named `test "…" {}`
|
|
623
|
+
* inside a container is also a Function definition, but the method
|
|
624
|
+
* extractor does not know test blocks (no parameters, no `self`), so
|
|
625
|
+
* relabelling it would mint a Method id the definition phase never
|
|
626
|
+
* builds. Tests stay Functions wherever they sit. */
|
|
627
|
+
export function isZigContainerMethod(captureNode) {
|
|
628
|
+
if (captureNode?.type !== undefined && captureNode.type !== 'function_declaration')
|
|
629
|
+
return false;
|
|
630
|
+
let ancestor = captureNode?.parent;
|
|
631
|
+
while (ancestor) {
|
|
632
|
+
if (ZIG_CONTAINER_TYPES.has(ancestor.type))
|
|
633
|
+
return true;
|
|
634
|
+
// A top-level fn of a FILE-STRUCT is a method of the file's type.
|
|
635
|
+
if (ancestor.type === 'source_file')
|
|
636
|
+
return isZigFileStruct(ancestor);
|
|
637
|
+
ancestor = ancestor.parent;
|
|
638
|
+
}
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
/** `{ '@import.reexports': 'true' }` when `stmt` is a publishing import
|
|
642
|
+
* (see `isZigPublishingImport`), else nothing — spread into an import group. */
|
|
643
|
+
function republishMarker(stmt) {
|
|
644
|
+
return isZigPublishingImport(stmt)
|
|
645
|
+
? { '@import.reexports': syntheticCapture('@import.reexports', stmt, 'true') }
|
|
646
|
+
: {};
|
|
647
|
+
}
|
|
648
|
+
/** Every `const X = @This();` in the tree, keyed by the alias node id's owner:
|
|
649
|
+
* returns a map from the DECLARING container node id to `[aliasName,
|
|
650
|
+
* containerName]`. The file-level alias maps to the file-struct name (only
|
|
651
|
+
* when the file is a file-struct — a namespace-only file's `const js =
|
|
652
|
+
* @This();` stays a Const and is not a type to rewrite to). The container
|
|
653
|
+
* side is the name the SCOPE BINDS: the binding name (`R` for a function-
|
|
654
|
+
* local `const R = struct {…}`, whose identity `string$R` is not a binding),
|
|
655
|
+
* or the synthetic identity of an anonymous container, which is bound under
|
|
656
|
+
* that very name (F8). */
|
|
657
|
+
function collectZigThisAliases(root, fileStructName, filePath) {
|
|
658
|
+
const out = new Map();
|
|
659
|
+
const visit = (node) => {
|
|
660
|
+
if (node.type === 'variable_declaration') {
|
|
661
|
+
const named = node.namedChildren.filter((c) => c !== null);
|
|
662
|
+
const value = named[named.length - 1];
|
|
663
|
+
if (named.length === 2 &&
|
|
664
|
+
named[0].type === 'identifier' &&
|
|
665
|
+
value?.type === 'builtin_function' &&
|
|
666
|
+
value.namedChild(0)?.text === '@This' &&
|
|
667
|
+
isZigKeywordDeclaration(node)) {
|
|
668
|
+
const owner = node.parent;
|
|
669
|
+
if (owner?.type === 'source_file') {
|
|
670
|
+
if (fileStructName !== undefined) {
|
|
671
|
+
out.set(owner.id, { alias: named[0].text, container: fileStructName });
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
else if (owner !== null && ZIG_CONTAINER_TYPES.has(owner.type)) {
|
|
675
|
+
const containerName = zigContainerBindingName(owner) ?? zigContainerName(owner, filePath);
|
|
676
|
+
if (containerName !== undefined) {
|
|
677
|
+
out.set(owner.id, { alias: named[0].text, container: containerName });
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
683
|
+
const child = node.namedChild(i);
|
|
684
|
+
if (child !== null)
|
|
685
|
+
visit(child);
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
visit(root);
|
|
689
|
+
return out;
|
|
690
|
+
}
|
|
691
|
+
/** If the nominal part of the type written at `typeNode` is a `@This()` alias
|
|
692
|
+
* of an enclosing container, the same text with the alias replaced by the
|
|
693
|
+
* container's name; undefined when nothing applies. Innermost container wins
|
|
694
|
+
* (a nested `Self` shadows the file's). */
|
|
695
|
+
function rewriteZigThisAlias(typeNode, aliases,
|
|
696
|
+
// The text to rewrite: the node's own, unless an earlier step (F6's value
|
|
697
|
+
// inference) already replaced the capture text.
|
|
698
|
+
text = typeNode.text) {
|
|
699
|
+
const nominal = /[A-Za-z_@][\w.]*(?:\([^)]*\))?\s*$/.exec(text)?.[0]?.trim();
|
|
700
|
+
if (nominal === undefined || nominal.length === 0)
|
|
701
|
+
return undefined;
|
|
702
|
+
const bare = nominal.replace(/\(.*\)$/, '');
|
|
703
|
+
let ancestor = typeNode.parent;
|
|
704
|
+
while (ancestor !== null) {
|
|
705
|
+
const entry = aliases.get(ancestor.id);
|
|
706
|
+
if (entry !== undefined && entry.alias === bare) {
|
|
707
|
+
if (entry.container === bare)
|
|
708
|
+
return undefined;
|
|
709
|
+
const idx = text.lastIndexOf(bare);
|
|
710
|
+
return text.slice(0, idx) + entry.container + text.slice(idx + bare.length);
|
|
711
|
+
}
|
|
712
|
+
ancestor = ancestor.parent;
|
|
713
|
+
}
|
|
714
|
+
return undefined;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Callable-value-flow vocabulary for tree-sitter-zig 1.1.2 (verified by AST
|
|
718
|
+
* dump). Two grammar quirks drive the callbacks:
|
|
719
|
+
*
|
|
720
|
+
* - `variable_declaration` is FIELDLESS apart from `type:` — `const f = target;`
|
|
721
|
+
* is `(variable_declaration (identifier) (identifier))`, and a bare
|
|
722
|
+
* re-assignment statement `f = target;` parses to the SAME node shape. The
|
|
723
|
+
* shared `left`/`name`/`value` fallback decomposes nothing, so
|
|
724
|
+
* `extractAssignment` pairs first-identifier → last-child positionally.
|
|
725
|
+
* `assignment_expression` (`self.f = target`) carries real `left`/`right`
|
|
726
|
+
* fields and is left to the shared path.
|
|
727
|
+
* - `call_expression` has NO argument-list wrapper: `invoke(second)` is
|
|
728
|
+
* `(call_expression function: (identifier) (identifier))`. Arguments are
|
|
729
|
+
* every named child other than the `function` field, hence
|
|
730
|
+
* `extractCallArguments`.
|
|
731
|
+
*
|
|
732
|
+
* - Zig's receiver is an EXPLICIT first parameter named `self` (the same
|
|
733
|
+
* convention `interpretZigTypeBinding` keys receiver typing on), and a
|
|
734
|
+
* method has TWO call spellings: the implicit `r.run(target)` and the
|
|
735
|
+
* explicit `Runner.run(&r, target)`. The shared harness joins actuals to
|
|
736
|
+
* formals by index, and formals are numbered once per function — so the
|
|
737
|
+
* receiver must be re-added on the CALL side, never dropped on the formal
|
|
738
|
+
* side (slicing `self` off the formals fixed the implicit spelling and lost
|
|
739
|
+
* the explicit one — PR #1432 review). `extractCallArguments` therefore
|
|
740
|
+
* prepends the receiver as actual 0 when the callee is a member call on a
|
|
741
|
+
* VALUE receiver (`r.run(target)`, `self.slot.release()`, `node.child()`),
|
|
742
|
+
* so both spellings yield `target@1` and join formal `cb@1`. A namespace
|
|
743
|
+
* or type receiver (`Runner.init(cb)`, `std.sort.pdq(…)`, `List(u8).init`,
|
|
744
|
+
* `@import("x.zig").f(cb)`) passes nothing implicitly and gets no prepend.
|
|
745
|
+
* Value-vs-type is F6's rule: the chain head is a fn-local name (param /
|
|
746
|
+
* local / payload) that is not TitleCase, or a MODULE-level value — a
|
|
747
|
+
* file- or container-level `var` / `const` whose declaration shape says
|
|
748
|
+
* "value" (`zigHostValueNames`: annotated, a struct literal, a non-type
|
|
749
|
+
* call, a literal), so `global_runner.run(target)` prepends `global_runner`
|
|
750
|
+
* exactly like `r.run(target)` does and `target` joins formal `cb@1`
|
|
751
|
+
* instead of `self@0` (PR #1432 review, 8.3).
|
|
752
|
+
*
|
|
753
|
+
* `builtin_function` (`@import`, `@sizeOf`, …) is deliberately not a call node:
|
|
754
|
+
* builtins never take user callables as flow arguments.
|
|
755
|
+
*/
|
|
756
|
+
/** Per file: the callable-flow options close over the file's fn-local and
|
|
757
|
+
* host-value name caches (shared with F6's `zigCallReturnTypeOf`). */
|
|
758
|
+
function zigCallableCaptureOptions(fnLocalNames, hostValueNames) {
|
|
759
|
+
return {
|
|
760
|
+
functionNodeTypes: new Set(['function_declaration']),
|
|
761
|
+
callNodeTypes: new Set(['call_expression']),
|
|
762
|
+
parameterListNodeTypes: new Set(['parameters']),
|
|
763
|
+
parameterNodeTypes: new Set(['parameter']),
|
|
764
|
+
bindingNodeTypes: new Set(['variable_declaration']),
|
|
765
|
+
assignmentNodeTypes: new Set(['assignment_expression']),
|
|
766
|
+
identifierNodeTypes: new Set(['identifier']),
|
|
767
|
+
extractAssignment: (node) => {
|
|
768
|
+
if (node.type !== 'variable_declaration')
|
|
769
|
+
return undefined;
|
|
770
|
+
const named = node.namedChildren.filter((child) => child !== null);
|
|
771
|
+
if (named.length < 2 || named[0].type !== 'identifier')
|
|
772
|
+
return undefined;
|
|
773
|
+
const source = named[named.length - 1];
|
|
774
|
+
// `const x: T;` (extern) — the trailing child is the type, not a value.
|
|
775
|
+
if (source.id === node.childForFieldName('type')?.id)
|
|
776
|
+
return undefined;
|
|
777
|
+
return { destination: named[0], source };
|
|
778
|
+
},
|
|
779
|
+
extractFunctionParameters: (fn) => {
|
|
780
|
+
// `parameters` is a fieldless child of `function_declaration`. The
|
|
781
|
+
// leading `self` is kept: it is formal 0, matched by the receiver that
|
|
782
|
+
// `extractCallArguments` prepends (implicit spelling) or the explicit
|
|
783
|
+
// first actual (`Runner.run(&r, target)`).
|
|
784
|
+
const list = fn.namedChildren.find((child) => child?.type === 'parameters');
|
|
785
|
+
if (list === undefined || list === null)
|
|
786
|
+
return undefined;
|
|
787
|
+
return list.namedChildren.filter((child) => child !== null && child.type === 'parameter');
|
|
788
|
+
},
|
|
789
|
+
extractCallArguments: (call) => {
|
|
790
|
+
const callee = call.childForFieldName('function');
|
|
791
|
+
const explicit = call.namedChildren.filter((child) => child !== null && child.id !== callee?.id && child.type !== 'comment');
|
|
792
|
+
const receiver = zigImplicitReceiver(call, fnLocalNames, hostValueNames);
|
|
793
|
+
return receiver === undefined ? explicit : [receiver, ...explicit];
|
|
794
|
+
},
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
/** The value receiver `x.f(…)` passes implicitly as `self`, or undefined for
|
|
798
|
+
* a free call, a decl literal (`.init(…)`), or a namespace / type receiver
|
|
799
|
+
* (`Runner.init(…)`, `std.mem.eql(…)`, `List(u8).init(…)`, `@import(…).f(…)`).
|
|
800
|
+
* Same value-vs-type rule as `zigCallReturnTypeOf` (F6): the chain head is a
|
|
801
|
+
* name declared in the enclosing fn and is not TitleCase (a fn-local
|
|
802
|
+
* TitleCase name is a type alias, F7), or a module-level value
|
|
803
|
+
* (`zigIsHostValueName`). */
|
|
804
|
+
function zigImplicitReceiver(call, fnLocalNames, hostValueNames) {
|
|
805
|
+
const callee = call.childForFieldName('function');
|
|
806
|
+
if (callee === null || callee.type !== 'field_expression')
|
|
807
|
+
return undefined;
|
|
808
|
+
const object = callee.childForFieldName('object');
|
|
809
|
+
if (object === null)
|
|
810
|
+
return undefined; // `.init(…)` decl literal
|
|
811
|
+
const head = zigChainHead(object);
|
|
812
|
+
if (head === null || isZigTitleCase(head.text))
|
|
813
|
+
return undefined;
|
|
814
|
+
return zigIsValueName(call, head.text, fnLocalNames, hostValueNames) ? object : undefined;
|
|
815
|
+
}
|
|
816
|
+
/** Is `name`, read at `at`, a VALUE — a fn-local (param / local / payload) of
|
|
817
|
+
* the enclosing fn, or a module-level value declared by the file or by a
|
|
818
|
+
* container enclosing `at`? Zig forbids shadowing, so the first declaration
|
|
819
|
+
* found walking outwards is the only one. Namespaces (`std`, an `@import`
|
|
820
|
+
* handle, `const mem = std.mem`) and types are not values. */
|
|
821
|
+
function zigIsValueName(at, name, fnLocalNames, hostValueNames) {
|
|
822
|
+
const fn = zigEnclosingFunction(at);
|
|
823
|
+
if (fn !== null && zigFunctionLocalNames(fn, fnLocalNames).has(name))
|
|
824
|
+
return true;
|
|
825
|
+
let host = zigIdentityHost(at);
|
|
826
|
+
while (host !== null) {
|
|
827
|
+
if (!isZigCallableNode(host) && zigHostValueNames(host, hostValueNames).has(name))
|
|
828
|
+
return true;
|
|
829
|
+
host = zigIdentityHost(host);
|
|
830
|
+
}
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
/** The names a HOST (the file root or a container node) declares DIRECTLY as
|
|
834
|
+
* values — module-level state such as `var global_runner = Runner{};`,
|
|
835
|
+
* `var pool: Pool = undefined;`, `const default_config = Config.load();`,
|
|
836
|
+
* `const max = 16;`. Decided from the declaration's shape, the only evidence
|
|
837
|
+
* available before finalization:
|
|
838
|
+
* - annotated (`var x: T = …`, `const x: T;`) or initialized with a struct
|
|
839
|
+
* literal, a literal, or a call that is not a generic type instantiation
|
|
840
|
+
* (`isZigTypeConstructorCall`) → value;
|
|
841
|
+
* - container / `@import` bindings, TitleCase names (types, F7), and
|
|
842
|
+
* values that merely ALIAS another name (`const mem = std.mem;`,
|
|
843
|
+
* `var cur = orig;`) → not a value here: an alias is whatever it aliases,
|
|
844
|
+
* and a namespace alias prepended as a receiver would shift every
|
|
845
|
+
* callback index the other way.
|
|
846
|
+
* Lazily computed once per host node. */
|
|
847
|
+
function zigHostValueNames(host, cache) {
|
|
848
|
+
const cached = cache.get(host.id);
|
|
849
|
+
if (cached !== undefined)
|
|
850
|
+
return cached;
|
|
851
|
+
const names = new Set();
|
|
852
|
+
for (let i = 0; i < host.namedChildCount; i++) {
|
|
853
|
+
const decl = host.namedChild(i);
|
|
854
|
+
if (decl === null || decl.type !== 'variable_declaration' || !isZigKeywordDeclaration(decl)) {
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
const named = decl.namedChildren.filter((c) => c !== null);
|
|
858
|
+
const name = named[0];
|
|
859
|
+
if (name === undefined || name.type !== 'identifier' || isZigTitleCase(name.text))
|
|
860
|
+
continue;
|
|
861
|
+
if (named.length < 2 || isZigContainerOrImportBinding(decl))
|
|
862
|
+
continue;
|
|
863
|
+
const last = named[named.length - 1];
|
|
864
|
+
if (last.id !== decl.childForFieldName('type')?.id) {
|
|
865
|
+
const value = zigUnwrapValue(last);
|
|
866
|
+
if (value.type === 'identifier' ||
|
|
867
|
+
value.type === 'field_expression' ||
|
|
868
|
+
value.type === 'builtin_function' ||
|
|
869
|
+
(value.type === 'call_expression' && isZigTypeConstructorCall(value))) {
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
names.add(name.text);
|
|
874
|
+
}
|
|
875
|
+
cache.set(host.id, names);
|
|
876
|
+
return names;
|
|
877
|
+
}
|
|
878
|
+
// ─── F6: value-inferred and return-type bindings ─────────────────────────────
|
|
879
|
+
/** Strip the value wrappers Zig puts around a call — `try f()`, `f() catch
|
|
880
|
+
* …`, `f() orelse …`, `(f())` — down to the wrapped expression. Rust's
|
|
881
|
+
* `.await` unwrapping, one level richer. */
|
|
882
|
+
export function zigUnwrapValue(value) {
|
|
883
|
+
let cur = value;
|
|
884
|
+
for (;;) {
|
|
885
|
+
if (cur.type === 'try_expression' || cur.type === 'parenthesized_expression') {
|
|
886
|
+
const inner = cur.namedChildren.find((c) => c !== null && c.type !== 'comment');
|
|
887
|
+
if (inner === undefined)
|
|
888
|
+
return cur;
|
|
889
|
+
cur = inner;
|
|
890
|
+
}
|
|
891
|
+
else if (cur.type === 'catch_expression') {
|
|
892
|
+
// `<operand> catch [|err|] <handler>` — the operand is the first child.
|
|
893
|
+
const inner = cur.namedChild(0);
|
|
894
|
+
if (inner === null)
|
|
895
|
+
return cur;
|
|
896
|
+
cur = inner;
|
|
897
|
+
}
|
|
898
|
+
else if (cur.type === 'binary_expression') {
|
|
899
|
+
if (cur.childForFieldName('operator')?.type !== 'orelse')
|
|
900
|
+
return cur;
|
|
901
|
+
const left = cur.childForFieldName('left');
|
|
902
|
+
if (left === null)
|
|
903
|
+
return cur;
|
|
904
|
+
cur = left;
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
return cur;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
/** Zig style: types are TitleCase, functions camelCase, namespaces
|
|
912
|
+
* snake_case. A TitleCase callee (`List(u8)`, `std.ArrayList(T)`) is a type
|
|
913
|
+
* constructor — its call yields a TYPE, not a value, and is a type alias
|
|
914
|
+
* (F7), not a call-return binding. */
|
|
915
|
+
function isZigTitleCase(name) {
|
|
916
|
+
return /^[A-Z]/.test(name);
|
|
917
|
+
}
|
|
918
|
+
/** The leftmost identifier of a `field_expression` chain (`self.parser` →
|
|
919
|
+
* `self`), the identifier itself, or null when the chain roots in anything
|
|
920
|
+
* else (a call, an index, a builtin). */
|
|
921
|
+
function zigChainHead(node) {
|
|
922
|
+
let cur = node;
|
|
923
|
+
while (cur !== null && cur.type === 'field_expression')
|
|
924
|
+
cur = cur.childForFieldName('object');
|
|
925
|
+
return cur !== null && cur.type === 'identifier' ? cur : null;
|
|
926
|
+
}
|
|
927
|
+
/** Names bound INSIDE `fn` (parameters, `const`/`var` locals, payload
|
|
928
|
+
* captures), lazily computed once per function node. Zig forbids shadowing,
|
|
929
|
+
* so a name declared anywhere in the function is a value local everywhere in
|
|
930
|
+
* it, and a name NOT declared in it is a module-level (or imported) name.
|
|
931
|
+
* That is the whole distinction `zigCallReturnTypeOf` needs: a call on a
|
|
932
|
+
* local receiver (`node.asElement()`) yields the METHOD's return type, a
|
|
933
|
+
* call on a module-level receiver (`Counter.init()`, `mod.Counter.init()`)
|
|
934
|
+
* names the receiver's type. */
|
|
935
|
+
function zigFunctionLocalNames(fn, cache) {
|
|
936
|
+
const cached = cache.get(fn.id);
|
|
937
|
+
if (cached !== undefined)
|
|
938
|
+
return cached;
|
|
939
|
+
const names = new Set();
|
|
940
|
+
const visit = (node) => {
|
|
941
|
+
if (node.type === 'parameter') {
|
|
942
|
+
const name = node.childForFieldName('name');
|
|
943
|
+
if (name !== null)
|
|
944
|
+
names.add(name.text);
|
|
945
|
+
}
|
|
946
|
+
else if (node.type === 'variable_declaration' && isZigKeywordDeclaration(node)) {
|
|
947
|
+
const first = node.namedChild(0);
|
|
948
|
+
if (first?.type === 'identifier')
|
|
949
|
+
names.add(first.text);
|
|
950
|
+
}
|
|
951
|
+
else if (node.type === 'payload') {
|
|
952
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
953
|
+
const c = node.namedChild(i);
|
|
954
|
+
if (c?.type === 'identifier')
|
|
955
|
+
names.add(c.text);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
959
|
+
const c = node.namedChild(i);
|
|
960
|
+
if (c !== null)
|
|
961
|
+
visit(c);
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
visit(fn);
|
|
965
|
+
cache.set(fn.id, names);
|
|
966
|
+
return names;
|
|
967
|
+
}
|
|
968
|
+
/** The nearest enclosing `function_declaration` / `test_declaration`, or
|
|
969
|
+
* null at module (container) level. */
|
|
970
|
+
function zigEnclosingFunction(node) {
|
|
971
|
+
let cur = node.parent;
|
|
972
|
+
while (cur !== null && cur !== undefined) {
|
|
973
|
+
if (cur.type === 'function_declaration' || cur.type === 'test_declaration')
|
|
974
|
+
return cur;
|
|
975
|
+
cur = cur.parent;
|
|
976
|
+
}
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
/** For `const t = <value>;`, the type source the value names, or undefined
|
|
980
|
+
* when the value types nothing on its own:
|
|
981
|
+
* - `Counter.init()` / `mod.Counter.init()` / `List(u8).init()` (receiver
|
|
982
|
+
* is a module-level name) → `{ type: 'Counter' }` — the receiver names
|
|
983
|
+
* the type (Rust `Foo::new()`); a value receiver simply finds no
|
|
984
|
+
* container later and declines.
|
|
985
|
+
* - `node.asElement()` / `self.parser.next()` (receiver head is a fn-local
|
|
986
|
+
* param / local / payload) → `{ type: 'node.asElement()', memberCall:
|
|
987
|
+
* true }` — the compound shape the shared resolver walks through the
|
|
988
|
+
* receiver's class scope to the method's `@type-binding.return`.
|
|
989
|
+
* - `makeThing()` (free call) → `{ type: 'makeThing' }` — chains to the
|
|
990
|
+
* fn's return binding.
|
|
991
|
+
* - a `struct_initializer` under a wrapper (`try Thing{…}`) → its type,
|
|
992
|
+
* `structLiteral: true` (the direct shape has its own query rules).
|
|
993
|
+
* - `.init()` decl literals, TitleCase callees (type constructors —
|
|
994
|
+
* aliases, F7), identifiers, field chains, literals → undefined.
|
|
995
|
+
* Wrappers (`try`, `catch`, `orelse`, parens) are unwrapped first. */
|
|
996
|
+
export function zigCallReturnTypeOf(value, localNamesCache, hostValueNames = new Map()) {
|
|
997
|
+
const inner = zigUnwrapValue(value);
|
|
998
|
+
if (inner.type === 'struct_initializer') {
|
|
999
|
+
if (inner.id === value.id)
|
|
1000
|
+
return undefined; // direct: the constructor rules own it
|
|
1001
|
+
const typeNode = inner.namedChild(0);
|
|
1002
|
+
if (typeNode === null ||
|
|
1003
|
+
!(typeNode.type === 'identifier' ||
|
|
1004
|
+
typeNode.type === 'field_expression' ||
|
|
1005
|
+
typeNode.type === 'call_expression')) {
|
|
1006
|
+
return undefined;
|
|
1007
|
+
}
|
|
1008
|
+
return { type: typeNode.text, structLiteral: true };
|
|
1009
|
+
}
|
|
1010
|
+
if (inner.type !== 'call_expression')
|
|
1011
|
+
return undefined;
|
|
1012
|
+
const callee = inner.childForFieldName('function');
|
|
1013
|
+
if (callee === null)
|
|
1014
|
+
return undefined;
|
|
1015
|
+
if (callee.type === 'identifier') {
|
|
1016
|
+
return isZigTitleCase(callee.text) ? undefined : { type: callee.text };
|
|
1017
|
+
}
|
|
1018
|
+
if (callee.type !== 'field_expression')
|
|
1019
|
+
return undefined;
|
|
1020
|
+
const object = callee.childForFieldName('object');
|
|
1021
|
+
const member = callee.childForFieldName('member');
|
|
1022
|
+
if (object === null || member === null)
|
|
1023
|
+
return undefined; // `.init()` decl literal
|
|
1024
|
+
if (isZigTitleCase(member.text))
|
|
1025
|
+
return undefined;
|
|
1026
|
+
const head = zigChainHead(object);
|
|
1027
|
+
if (head !== null) {
|
|
1028
|
+
// A fn-local or module-level value name is a VALUE receiver — unless it
|
|
1029
|
+
// is TitleCase: `const R = generic.List(u8); var l = R.init();` binds a
|
|
1030
|
+
// type alias inside the fn (F7), and `R.init()` names the type `R`
|
|
1031
|
+
// exactly like `Counter.init()` does at module level. Zig's naming
|
|
1032
|
+
// convention (types TitleCase, values snake_case) is the same signal
|
|
1033
|
+
// `isZigTypeConstructorCall` relies on.
|
|
1034
|
+
if (!isZigTitleCase(head.text) &&
|
|
1035
|
+
zigIsValueName(value, head.text, localNamesCache, hostValueNames)) {
|
|
1036
|
+
return { type: `${object.text}.${member.text}()`, memberCall: true };
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return { type: object.text };
|
|
1040
|
+
}
|
|
1041
|
+
/** Return-type node → the nominal type node under its sigils (`!*Thing` →
|
|
1042
|
+
* `Thing`), following error-union `ok`, pointer / nullable / slice / array
|
|
1043
|
+
* wrappers. Undefined when it bottoms out in nothing nominal. */
|
|
1044
|
+
function zigNominalTypeNode(typeNode) {
|
|
1045
|
+
let cur = typeNode;
|
|
1046
|
+
for (let guard = 0; cur !== null && cur !== undefined && guard < 16; guard++) {
|
|
1047
|
+
switch (cur.type) {
|
|
1048
|
+
case 'error_union_type':
|
|
1049
|
+
cur = cur.childForFieldName('ok');
|
|
1050
|
+
continue;
|
|
1051
|
+
case 'pointer_type':
|
|
1052
|
+
case 'nullable_type':
|
|
1053
|
+
case 'slice_type':
|
|
1054
|
+
case 'array_type':
|
|
1055
|
+
// The element type is the LAST named child (arrays carry the length
|
|
1056
|
+
// first, pointers/slices may carry a sentinel or `const`).
|
|
1057
|
+
cur = cur.namedChildren
|
|
1058
|
+
.filter((c) => c !== null && c.type !== 'comment')
|
|
1059
|
+
.pop();
|
|
1060
|
+
continue;
|
|
1061
|
+
default:
|
|
1062
|
+
return cur;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return undefined;
|
|
1066
|
+
}
|
|
1067
|
+
/** Should `fn name() <type>` bind `name ↦ <type>`? Only when the return
|
|
1068
|
+
* type is nominal: a container name, a qualified name, a generic
|
|
1069
|
+
* instantiation, or `@This()`. Builtins (`void`, `u8`, `bool`, `anyerror`),
|
|
1070
|
+
* `type` (a generic type constructor — its container binding is the type),
|
|
1071
|
+
* and `@TypeOf(…)` bind nothing: `List ↦ type` would hijack the
|
|
1072
|
+
* `List(u8){}` constructor chain, and `f ↦ void` is noise. */
|
|
1073
|
+
export function zigReturnTypeIsNominal(typeNode) {
|
|
1074
|
+
const nominal = zigNominalTypeNode(typeNode);
|
|
1075
|
+
if (nominal === undefined)
|
|
1076
|
+
return false;
|
|
1077
|
+
switch (nominal.type) {
|
|
1078
|
+
case 'identifier':
|
|
1079
|
+
// `fn is(self: *Node, comptime T: type) ?*T` — `T` is the CALLER's
|
|
1080
|
+
// choice, not a type; binding `is ↦ T` would type every `node.is(X)`
|
|
1081
|
+
// as whatever class happens to be named `T`.
|
|
1082
|
+
return !zigIsComptimeTypeParameter(typeNode, nominal.text);
|
|
1083
|
+
case 'field_expression':
|
|
1084
|
+
case 'call_expression':
|
|
1085
|
+
return true;
|
|
1086
|
+
case 'builtin_function':
|
|
1087
|
+
return nominal.namedChild(0)?.text === '@This';
|
|
1088
|
+
default:
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
/** Is `name` a `comptime <name>: type` parameter of the function whose
|
|
1093
|
+
* return type `returnTypeNode` is, or of any function enclosing it (the
|
|
1094
|
+
* `T` of a generic type constructor is visible to every method of the
|
|
1095
|
+
* returned container: `fn get(self) T`)? */
|
|
1096
|
+
function zigIsComptimeTypeParameter(returnTypeNode, name) {
|
|
1097
|
+
let fn = returnTypeNode.parent;
|
|
1098
|
+
while (fn !== null && fn !== undefined) {
|
|
1099
|
+
if (fn.type === 'function_declaration') {
|
|
1100
|
+
const params = fn.namedChildren.find((c) => c?.type === 'parameters');
|
|
1101
|
+
for (let i = 0; params !== undefined && params !== null && i < params.namedChildCount; i++) {
|
|
1102
|
+
const p = params.namedChild(i);
|
|
1103
|
+
if (p?.type !== 'parameter')
|
|
1104
|
+
continue;
|
|
1105
|
+
if (p.childForFieldName('name')?.text === name &&
|
|
1106
|
+
p.childForFieldName('type')?.text === 'type') {
|
|
1107
|
+
return true;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
fn = fn.parent;
|
|
1112
|
+
}
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
/** The container a `@This()` in `typeNode`'s position names: the innermost
|
|
1116
|
+
* enclosing container's binding name, or the file-struct's name at file
|
|
1117
|
+
* level. Undefined outside any named container. */
|
|
1118
|
+
function zigThisTargetFor(typeNode, fileStructName) {
|
|
1119
|
+
let cur = typeNode.parent;
|
|
1120
|
+
while (cur !== null && cur !== undefined) {
|
|
1121
|
+
if (ZIG_CONTAINER_TYPES.has(cur.type))
|
|
1122
|
+
return zigContainerName(cur);
|
|
1123
|
+
if (cur.type === 'source_file')
|
|
1124
|
+
return fileStructName;
|
|
1125
|
+
cur = cur.parent;
|
|
1126
|
+
}
|
|
1127
|
+
return undefined;
|
|
1128
|
+
}
|
|
1129
|
+
export function emitZigScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
1130
|
+
let tree = cachedTree;
|
|
1131
|
+
if (tree === undefined) {
|
|
1132
|
+
tree = parseSourceSafe(getZigParser(), sourceText, undefined, {
|
|
1133
|
+
bufferSize: getTreeSitterBufferSize(sourceText),
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
const rawMatches = getZigScopeQuery().matches(tree.rootNode);
|
|
1137
|
+
const out = [];
|
|
1138
|
+
// Pre-pass: this file's `@import` bindings (`const counter =
|
|
1139
|
+
// @import("counter.zig")` → counter ↦ the string node), so a member alias
|
|
1140
|
+
// `const Counter = counter.Counter;` can be promoted to a named import of
|
|
1141
|
+
// `Counter` from `counter.zig` below. Aliases are collected in the same
|
|
1142
|
+
// pass so their plain-variable group is dropped (a local Const binding
|
|
1143
|
+
// would outrank the import binding it stands for).
|
|
1144
|
+
const importSources = new Map();
|
|
1145
|
+
const aliasDeclIds = new Set();
|
|
1146
|
+
// The `@import(…)` string nodes a BINDING rule (or the keyword-less
|
|
1147
|
+
// side-effect rule) matched, by node id, plus their texts. The catch-all
|
|
1148
|
+
// `@import.inline` rule matches those same builtins again; the id set
|
|
1149
|
+
// keeps a bound import from being doubled, the text set keeps a second
|
|
1150
|
+
// spelling of an already-imported file from adding a redundant edge.
|
|
1151
|
+
const claimedImportSourceIds = new Set();
|
|
1152
|
+
const importedSourceTexts = new Set();
|
|
1153
|
+
for (const m of rawMatches) {
|
|
1154
|
+
const byName = new Map(m.captures.map((c) => [c.name, c.node]));
|
|
1155
|
+
const importName = byName.get('import.name');
|
|
1156
|
+
const importSource = byName.get('import.source');
|
|
1157
|
+
const importStmt = byName.get('import.statement');
|
|
1158
|
+
// A binding-rule match whose `@import` is the declaration's TYPE
|
|
1159
|
+
// annotation binds nothing: leave the source unclaimed so the
|
|
1160
|
+
// `@import.inline` rule keeps the file edge.
|
|
1161
|
+
if (importSource !== undefined &&
|
|
1162
|
+
importStmt !== undefined &&
|
|
1163
|
+
isZigTypePositionImport(importStmt, importSource)) {
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
if (importSource !== undefined &&
|
|
1167
|
+
(importStmt !== undefined ||
|
|
1168
|
+
byName.get('import.side-effect') !== undefined ||
|
|
1169
|
+
byName.get('import.wildcard') !== undefined)) {
|
|
1170
|
+
claimedImportSourceIds.add(importSource.id);
|
|
1171
|
+
importedSourceTexts.add(importSource.text);
|
|
1172
|
+
}
|
|
1173
|
+
if (importName !== undefined &&
|
|
1174
|
+
importSource !== undefined &&
|
|
1175
|
+
byName.get('import.imported') === undefined &&
|
|
1176
|
+
importStmt !== undefined &&
|
|
1177
|
+
importStmt.parent?.type === 'source_file' &&
|
|
1178
|
+
isZigKeywordDeclaration(importStmt)) {
|
|
1179
|
+
importSources.set(importName.text, importSource);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
for (const m of rawMatches) {
|
|
1183
|
+
const byName = new Map(m.captures.map((c) => [c.name, c.node]));
|
|
1184
|
+
const stmt = byName.get('alias.statement');
|
|
1185
|
+
const ns = byName.get('alias.namespace');
|
|
1186
|
+
// Only a ONE-level member (`const Counter = counter.Counter;`) is the
|
|
1187
|
+
// named-import fact. A deeper chain (`const chosen = lib.B.work;`) names a
|
|
1188
|
+
// member OF a member: promoting it to a named import of `work` from
|
|
1189
|
+
// `lib.zig` discarded the written owner `B`, and `findExportedDef` then
|
|
1190
|
+
// answered with the first `work` in the file — `A.work` (PR #1432 review,
|
|
1191
|
+
// 8.4). Those stay Consts and are rewritten at their use sites instead
|
|
1192
|
+
// (`collectZigDeepAliases`).
|
|
1193
|
+
if (stmt !== undefined &&
|
|
1194
|
+
ns !== undefined &&
|
|
1195
|
+
importSources.has(ns.text) &&
|
|
1196
|
+
zigMemberChainOf(stmt)?.members.length === 1) {
|
|
1197
|
+
aliasDeclIds.add(stmt.id);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
// Function-local `@import` bindings, keyed per enclosing callable (PR #1432
|
|
1201
|
+
// review, 8.9). Finalization flattens every import of a file onto its
|
|
1202
|
+
// Module scope, so two sibling fns each binding `const m = @import(…)` to a
|
|
1203
|
+
// different file became ONE `m → [a.zig, b.zig]` namespace bucket and both
|
|
1204
|
+
// `m.Thing{}` sites took the first target. The binding and every use of the
|
|
1205
|
+
// name inside that fn are rewritten to `m$<fn>` — a spelling no Zig
|
|
1206
|
+
// identifier can take — so each fn's handle is its own bucket and resolves
|
|
1207
|
+
// through its own lexical import (`rewriteZigFunctionLocalImportNames`).
|
|
1208
|
+
const fnLocalImports = new Map();
|
|
1209
|
+
for (const m of rawMatches) {
|
|
1210
|
+
const byName = new Map(m.captures.map((c) => [c.name, c.node]));
|
|
1211
|
+
const importName = byName.get('import.name');
|
|
1212
|
+
const importStmt = byName.get('import.statement');
|
|
1213
|
+
const importSource = byName.get('import.source');
|
|
1214
|
+
if (importName === undefined || importStmt === undefined || importSource === undefined) {
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
if (!isZigKeywordDeclaration(importStmt) || isZigTypePositionImport(importStmt, importSource)) {
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
const fn = zigEnclosingFunction(importStmt);
|
|
1221
|
+
if (fn === null)
|
|
1222
|
+
continue;
|
|
1223
|
+
let entry = fnLocalImports.get(fn.id);
|
|
1224
|
+
if (entry === undefined) {
|
|
1225
|
+
entry = { fn, names: new Map() };
|
|
1226
|
+
fnLocalImports.set(fn.id, entry);
|
|
1227
|
+
}
|
|
1228
|
+
if (!entry.names.has(importName.text)) {
|
|
1229
|
+
entry.names.set(importName.text, zigFunctionLocalImportKey(importName.text, fn, _filePath));
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
// Deep member aliases — `const chosen = @import("lib.zig").B.work;`,
|
|
1233
|
+
// `const chosen2 = lib.B.work;`, `const Inner = nested.Outer.Inner;` (PR
|
|
1234
|
+
// #1432 review, 8.4): the owner path is kept and the alias's use sites are
|
|
1235
|
+
// rewritten to qualified references (`chosen()` → `lib.B` . `work`), which
|
|
1236
|
+
// the namespace chain walk resolves segment by segment.
|
|
1237
|
+
const deepAliases = collectZigDeepAliases(tree.rootNode, importSources, fnLocalImports);
|
|
1238
|
+
// File-struct (top-level fields): the file IS a type named after the file.
|
|
1239
|
+
// Emit a Class scope over the whole file (nested under the Module scope —
|
|
1240
|
+
// the equal-range Module/Class pair is the C# `namespace`-only-file shape
|
|
1241
|
+
// `canParentScope` admits) and a Struct def anchored on the same node, so
|
|
1242
|
+
// top-level fns/fields become its owned members (`populateClassOwnedMembers`)
|
|
1243
|
+
// and `self: *Page` / `page: *Page` resolve to a class-like def. Name
|
|
1244
|
+
// bindings of those members are hoisted back to the Module scope by
|
|
1245
|
+
// `zigBindingScopeFor` so `Page.init()` (namespace member) keeps working.
|
|
1246
|
+
const root = tree.rootNode;
|
|
1247
|
+
const fileStruct = isZigFileStruct(root);
|
|
1248
|
+
const fileStructName = fileStruct ? zigFileStructName(_filePath) : undefined;
|
|
1249
|
+
if (fileStruct && fileStructName !== undefined) {
|
|
1250
|
+
out.push({ '@scope.class': nodeToCapture('@scope.class', root) });
|
|
1251
|
+
out.push({
|
|
1252
|
+
'@declaration.struct': nodeToCapture('@declaration.struct', root),
|
|
1253
|
+
'@declaration.name': syntheticCapture('@declaration.name', root, fileStructName),
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
// `@This()` aliases: alias name ↦ the container it names (file stem for the
|
|
1257
|
+
// file-struct, binding name for `const Self = @This();` inside a container).
|
|
1258
|
+
const thisAliases = collectZigThisAliases(root, fileStructName, _filePath);
|
|
1259
|
+
// F6: fn-local names per function node, for `zigCallReturnTypeOf`; module-
|
|
1260
|
+
// level value names per host node (file / container), for the value-vs-
|
|
1261
|
+
// namespace receiver rule (`zigHostValueNames`).
|
|
1262
|
+
const fnLocalNames = new Map();
|
|
1263
|
+
const hostValueNames = new Map();
|
|
1264
|
+
for (const m of rawMatches) {
|
|
1265
|
+
const grouped = {};
|
|
1266
|
+
const nodeMap = {};
|
|
1267
|
+
for (const c of m.captures) {
|
|
1268
|
+
const tag = '@' + c.name;
|
|
1269
|
+
if (tag.startsWith('@_'))
|
|
1270
|
+
continue; // skip anonymous predicate captures
|
|
1271
|
+
grouped[tag] = nodeToCapture(tag, c.node);
|
|
1272
|
+
nodeMap[tag] = c.node;
|
|
1273
|
+
}
|
|
1274
|
+
if (Object.keys(grouped).length === 0)
|
|
1275
|
+
continue;
|
|
1276
|
+
// `_ = @import("x.zig");` and any other keyword-less `<ident> =
|
|
1277
|
+
// @import(…)`: a statement (tree-sitter-zig reuses variable_declaration
|
|
1278
|
+
// for assignments), not a declaration. It references the file without
|
|
1279
|
+
// binding a name → side-effect import (file edge only). The query rule
|
|
1280
|
+
// cannot exclude the keyword-bearing shapes, so drop those here — they
|
|
1281
|
+
// are the binding rules' matches. A keyword-less shape never enters
|
|
1282
|
+
// `importSources`, so it cannot promote aliases.
|
|
1283
|
+
const sideEffectStmt = nodeMap['@import.side-effect'];
|
|
1284
|
+
if (sideEffectStmt !== undefined) {
|
|
1285
|
+
if (isZigKeywordDeclaration(sideEffectStmt))
|
|
1286
|
+
continue;
|
|
1287
|
+
out.push({
|
|
1288
|
+
'@import.side-effect': grouped['@import.side-effect'],
|
|
1289
|
+
'@import.source': grouped['@import.source'],
|
|
1290
|
+
});
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
// `@import("…")` in expression position — a tuple element, a call
|
|
1294
|
+
// argument, a comparison operand, a member-call receiver, a chain deeper
|
|
1295
|
+
// than the binding rules follow. Skip the builtins a binding rule (or the
|
|
1296
|
+
// keyword-less side-effect rule) already owns; the rest are file
|
|
1297
|
+
// dependencies without a name. The receiver of a member call
|
|
1298
|
+
// (`try @import("dump.zig").root(...)`) is more: it is a namespace used
|
|
1299
|
+
// in place. Bind it as a namespace import whose local name IS the
|
|
1300
|
+
// builtin's own text — `@reference.receiver` on that call carries the
|
|
1301
|
+
// same text, so the shared Case-1 namespace-receiver lookup resolves
|
|
1302
|
+
// `root` in dump.zig exactly as it would for `const dump =
|
|
1303
|
+
// @import("dump.zig"); dump.root(...)`. Anything else is a side-effect
|
|
1304
|
+
// import (file edge only), emitted once per distinct source per file.
|
|
1305
|
+
const inlineImport = nodeMap['@import.inline'];
|
|
1306
|
+
if (inlineImport !== undefined) {
|
|
1307
|
+
const source = nodeMap['@import.source'];
|
|
1308
|
+
if (claimedImportSourceIds.has(source.id))
|
|
1309
|
+
continue;
|
|
1310
|
+
const sourceCapture = grouped['@import.source'];
|
|
1311
|
+
if (isZigInlineImportReceiver(inlineImport) || deepAliases.inlineRoots.has(inlineImport.id)) {
|
|
1312
|
+
const key = `receiver:${source.text}`;
|
|
1313
|
+
if (importedSourceTexts.has(key))
|
|
1314
|
+
continue;
|
|
1315
|
+
importedSourceTexts.add(key);
|
|
1316
|
+
out.push({
|
|
1317
|
+
'@import.statement': nodeToCapture('@import.statement', inlineImport),
|
|
1318
|
+
'@import.name': nodeToCapture('@import.name', inlineImport),
|
|
1319
|
+
'@import.source': sourceCapture,
|
|
1320
|
+
});
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
if (importedSourceTexts.has(source.text))
|
|
1324
|
+
continue;
|
|
1325
|
+
importedSourceTexts.add(source.text);
|
|
1326
|
+
out.push({
|
|
1327
|
+
'@import.side-effect': nodeToCapture('@import.side-effect', inlineImport),
|
|
1328
|
+
'@import.source': sourceCapture,
|
|
1329
|
+
});
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
// `const chosen = @import("lib.zig").B.work;` — the two-member import
|
|
1333
|
+
// rule matched a DEEP chain. Binding `chosen` as a named import of the
|
|
1334
|
+
// innermost member `work` lost the owner `B` (8.4); bind the module under
|
|
1335
|
+
// the builtin's own text instead — the same namespace binding a member-
|
|
1336
|
+
// call receiver gets — and let the rewritten use sites walk `B.work`.
|
|
1337
|
+
const deepImportStmt = nodeMap['@import.statement'];
|
|
1338
|
+
if (deepImportStmt !== undefined &&
|
|
1339
|
+
nodeMap['@import.imported'] !== undefined &&
|
|
1340
|
+
deepAliases.declIds.has(deepImportStmt.id)) {
|
|
1341
|
+
const importRoot = zigImportRootOf(deepImportStmt.namedChildren.filter((c) => c !== null).pop() ?? null);
|
|
1342
|
+
const source = nodeMap['@import.source'];
|
|
1343
|
+
if (importRoot !== null && source !== undefined) {
|
|
1344
|
+
const key = `receiver:${source.text}`;
|
|
1345
|
+
if (!importedSourceTexts.has(key)) {
|
|
1346
|
+
importedSourceTexts.add(key);
|
|
1347
|
+
out.push({
|
|
1348
|
+
'@import.statement': nodeToCapture('@import.statement', importRoot),
|
|
1349
|
+
'@import.name': nodeToCapture('@import.name', importRoot),
|
|
1350
|
+
'@import.source': grouped['@import.source'],
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
continue;
|
|
1355
|
+
}
|
|
1356
|
+
// Member aliases: promote to a named import when the object is one of
|
|
1357
|
+
// this file's @import bindings; otherwise the group is inert (the same
|
|
1358
|
+
// node is also matched by the plain-variable rule).
|
|
1359
|
+
const aliasStmt = nodeMap['@alias.statement'];
|
|
1360
|
+
if (aliasStmt !== undefined) {
|
|
1361
|
+
if (!aliasDeclIds.has(aliasStmt.id))
|
|
1362
|
+
continue;
|
|
1363
|
+
const source = importSources.get(nodeMap['@alias.namespace'].text);
|
|
1364
|
+
out.push({
|
|
1365
|
+
'@import.statement': nodeToCapture('@import.statement', aliasStmt),
|
|
1366
|
+
'@import.name': nodeToCapture('@import.name', nodeMap['@alias.name']),
|
|
1367
|
+
'@import.imported': nodeToCapture('@import.imported', nodeMap['@alias.member']),
|
|
1368
|
+
'@import.source': nodeToCapture('@import.source', source),
|
|
1369
|
+
...republishMarker(aliasStmt),
|
|
1370
|
+
});
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
// F7 — type aliases (`@type-binding.alias`, see the query): keep the group
|
|
1374
|
+
// only for a value that is a TYPE expression. An identifier / member chain
|
|
1375
|
+
// is kept as written (a value alias such as `const log = lp.log` binds a
|
|
1376
|
+
// type name that resolves to nothing, exactly like Rust's `let x = y`); a
|
|
1377
|
+
// call is kept only when its callee's last identifier is TitleCase —
|
|
1378
|
+
// Zig's naming convention for types, hence for type constructors
|
|
1379
|
+
// (`util.List(u8)`, `js.Bridge(T)`, `GenericIterator(K, V)`). That is a
|
|
1380
|
+
// heuristic and the only one here: the grammar cannot tell a type
|
|
1381
|
+
// constructor call from a value call, and a value call (`const t =
|
|
1382
|
+
// util.makeThing()`) is typed by the call-return rules — a second binding
|
|
1383
|
+
// for the same name would race on match order. A promoted namespace-
|
|
1384
|
+
// member alias (`const Counter = counter.Counter;`) is skipped: it is a
|
|
1385
|
+
// named import already, and the import binding carries the type.
|
|
1386
|
+
const aliasAnchor = nodeMap['@type-binding.alias'];
|
|
1387
|
+
if (aliasAnchor !== undefined) {
|
|
1388
|
+
// An @import binding (`const Stack = @import("counter.zig").Stack;`) is
|
|
1389
|
+
// an import, and a promoted member alias a named import: both already
|
|
1390
|
+
// carry the type through the import binding.
|
|
1391
|
+
// A deep alias off an inline import (`const Inner =
|
|
1392
|
+
// @import("n.zig").Outer.Inner;`) keeps its type binding: the written
|
|
1393
|
+
// path is the type (8.4).
|
|
1394
|
+
if (aliasDeclIds.has(aliasAnchor.id) ||
|
|
1395
|
+
(isZigContainerOrImportBinding(aliasAnchor) && !deepAliases.declIds.has(aliasAnchor.id))) {
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
const value = nodeMap['@type-binding.type'];
|
|
1399
|
+
if (value === undefined)
|
|
1400
|
+
continue;
|
|
1401
|
+
// `var b: Counter = undefined;` — `undefined` / `null` are anonymous
|
|
1402
|
+
// nodes, so the last NAMED child the query anchored on is the `type:`
|
|
1403
|
+
// annotation, not a value. The annotation rule owns that binding.
|
|
1404
|
+
if (value.id === aliasAnchor.childForFieldName('type')?.id)
|
|
1405
|
+
continue;
|
|
1406
|
+
if (value.type === 'call_expression' && !isZigTypeConstructorCall(value))
|
|
1407
|
+
continue;
|
|
1408
|
+
// `.foo` / `.init` (enum / decl literal) parses as a field_expression
|
|
1409
|
+
// without an object — a value, never a type name.
|
|
1410
|
+
if (value.type === 'field_expression' && value.childForFieldName('object') === null)
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
// …and the converse: a call-return binding for `const B = util.List(u8)`
|
|
1414
|
+
// would type `B` as `util` (the call's receiver) and, at equal strength,
|
|
1415
|
+
// race the alias binding above on match order. A type-constructor
|
|
1416
|
+
// instantiation is the alias rule's; drop the call-return group for it.
|
|
1417
|
+
const callReturnAnchor = nodeMap['@type-binding.call-return'];
|
|
1418
|
+
if (callReturnAnchor !== undefined) {
|
|
1419
|
+
const named = callReturnAnchor.namedChildren.filter((c) => c !== null);
|
|
1420
|
+
const value = named[named.length - 1];
|
|
1421
|
+
if (value?.type === 'call_expression' && isZigTypeConstructorCall(value))
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
// `var x: @import("foo.zig").T = undefined;` — the binding rules match
|
|
1425
|
+
// the type-position `@import` too; that group binds nothing (the
|
|
1426
|
+
// variable group for `x` and the inline file edge cover it).
|
|
1427
|
+
if (nodeMap['@import.statement'] !== undefined &&
|
|
1428
|
+
nodeMap['@import.source'] !== undefined &&
|
|
1429
|
+
isZigTypePositionImport(nodeMap['@import.statement'], nodeMap['@import.source'])) {
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
// Drop the plain-variable group for container/import bindings — their
|
|
1433
|
+
// dedicated rules already bind the name (as Struct/Enum/Union or import).
|
|
1434
|
+
// The query already requires a `const`/`var` keyword, so statement
|
|
1435
|
+
// assignments (`x = 5;`, `_ = expr;` — same node type, no keyword) never
|
|
1436
|
+
// mint phantom locals; `isZigKeywordDeclaration` is the belt to that
|
|
1437
|
+
// brace should the rule ever be loosened.
|
|
1438
|
+
const variableAnchor = nodeMap['@declaration.variable'];
|
|
1439
|
+
if (variableAnchor !== undefined &&
|
|
1440
|
+
(isZigContainerOrImportBinding(variableAnchor) ||
|
|
1441
|
+
aliasDeclIds.has(variableAnchor.id) ||
|
|
1442
|
+
!isZigKeywordDeclaration(variableAnchor) ||
|
|
1443
|
+
// `const Page = @This();` in a file-struct names the file's own type,
|
|
1444
|
+
// which the synthetic `@declaration.struct` below already binds.
|
|
1445
|
+
(fileStruct && isZigFileThisAlias(variableAnchor)))) {
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
// F5 — `const page = self.page;` value aliases (`@type-binding.alias`)
|
|
1449
|
+
// share the query shape with `const Counter = counter.Counter;`, which is
|
|
1450
|
+
// a NAMED IMPORT of `Counter` (promoted above) — a type binding beside it
|
|
1451
|
+
// would make `Counter` look like an instance of the class it names. Drop
|
|
1452
|
+
// the alias binding for those declarations.
|
|
1453
|
+
const aliasBinding = nodeMap['@type-binding.alias'];
|
|
1454
|
+
if (aliasBinding !== undefined && aliasDeclIds.has(aliasBinding.id))
|
|
1455
|
+
continue;
|
|
1456
|
+
// F6 — value-inferred bindings: `const t = [try] <call>` (see
|
|
1457
|
+
// `zigCallReturnTypeOf`). The query pins every keyword declaration; here
|
|
1458
|
+
// the value decides whether it types anything, and how. The value capture
|
|
1459
|
+
// is scaffolding and never leaves this function.
|
|
1460
|
+
const valueNode = nodeMap['@type-binding.value'];
|
|
1461
|
+
if (valueNode !== undefined && nodeMap['@type-binding.call-return'] !== undefined) {
|
|
1462
|
+
delete grouped['@type-binding.value'];
|
|
1463
|
+
// `const x: T;` (extern) — the trailing child is the type, not a value.
|
|
1464
|
+
if (valueNode.id === nodeMap['@type-binding.call-return'].childForFieldName('type')?.id) {
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
const inferred = zigCallReturnTypeOf(valueNode, fnLocalNames, hostValueNames);
|
|
1468
|
+
if (inferred === undefined)
|
|
1469
|
+
continue;
|
|
1470
|
+
grouped['@type-binding.type'] = syntheticCapture('@type-binding.type', valueNode, inferred.type);
|
|
1471
|
+
nodeMap['@type-binding.type'] = valueNode;
|
|
1472
|
+
if (inferred.structLiteral === true) {
|
|
1473
|
+
const anchor = grouped['@type-binding.call-return'];
|
|
1474
|
+
delete grouped['@type-binding.call-return'];
|
|
1475
|
+
grouped['@type-binding.constructor'] = { ...anchor, name: '@type-binding.constructor' };
|
|
1476
|
+
}
|
|
1477
|
+
else if (inferred.memberCall === true) {
|
|
1478
|
+
grouped['@type-binding.member-call-return'] = syntheticCapture('@type-binding.member-call-return', nodeMap['@type-binding.name'], 'true');
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
// F6 — return-type bindings: `fn make() !*Thing` binds `make ↦ Thing`.
|
|
1482
|
+
// Only nominal returns bind (see `zigReturnTypeIsNominal`); a bare
|
|
1483
|
+
// `@This()` return names the enclosing container by name (the alias
|
|
1484
|
+
// rewrite below handles the `Self` spelling of the same thing).
|
|
1485
|
+
const returnAnchor = nodeMap['@type-binding.return'];
|
|
1486
|
+
if (returnAnchor !== undefined) {
|
|
1487
|
+
const retType = nodeMap['@type-binding.type'];
|
|
1488
|
+
if (retType === undefined || !zigReturnTypeIsNominal(retType))
|
|
1489
|
+
continue;
|
|
1490
|
+
if (retType.text.includes('@This()')) {
|
|
1491
|
+
const target = zigThisTargetFor(retType, fileStructName);
|
|
1492
|
+
if (target === undefined)
|
|
1493
|
+
continue;
|
|
1494
|
+
grouped['@type-binding.type'] = syntheticCapture('@type-binding.type', retType, retType.text.replace('@This()', target));
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
// `@This()` aliases in type position — `self: *Self` in a container that
|
|
1498
|
+
// declares `const Self = @This();`, or `self: *SigHandler` in
|
|
1499
|
+
// `Sighandler.zig` (`const SigHandler = @This();`) — name the enclosing
|
|
1500
|
+
// container. Rewrite the type text to that container's name so the
|
|
1501
|
+
// receiver binding resolves to the Struct (Rust does the same for `Self`,
|
|
1502
|
+
// rust/captures.ts). Only the nominal part is rewritten; sigils stay.
|
|
1503
|
+
const typeNode = nodeMap['@type-binding.type'];
|
|
1504
|
+
if (typeNode !== undefined && thisAliases.size > 0) {
|
|
1505
|
+
const rewritten = rewriteZigThisAlias(typeNode, thisAliases, grouped['@type-binding.type']?.text);
|
|
1506
|
+
if (rewritten !== undefined) {
|
|
1507
|
+
grouped['@type-binding.type'] = syntheticCapture('@type-binding.type', typeNode, rewritten);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
// The receiver is the FIRST parameter when it is named `self` or typed as
|
|
1511
|
+
// the enclosing container — `zigReceiverParameter` is the single rule,
|
|
1512
|
+
// shared with the method extractor so the two phases agree. Tag it so
|
|
1513
|
+
// `interpretZigTypeBinding` sources the binding as `self`; a later `self`
|
|
1514
|
+
// parameter (legal Zig) is an ordinary parameter, not a receiver. The
|
|
1515
|
+
// synthetic captures sit on the name node (smaller than the `parameter`
|
|
1516
|
+
// anchor), so they never displace the anchor.
|
|
1517
|
+
const paramAnchor = nodeMap['@type-binding.parameter'];
|
|
1518
|
+
const paramName = nodeMap['@type-binding.name'];
|
|
1519
|
+
if (paramAnchor !== undefined &&
|
|
1520
|
+
paramName !== undefined &&
|
|
1521
|
+
paramAnchor.previousNamedSibling === null) {
|
|
1522
|
+
grouped['@type-binding.first-parameter'] = syntheticCapture('@type-binding.first-parameter', paramName, 'true');
|
|
1523
|
+
const fnNode = paramAnchor.parent?.parent;
|
|
1524
|
+
if (fnNode !== null &&
|
|
1525
|
+
fnNode !== undefined &&
|
|
1526
|
+
zigReceiverParameter(fnNode, _filePath)?.id === paramAnchor.id) {
|
|
1527
|
+
grouped['@type-binding.receiver'] = syntheticCapture('@type-binding.receiver', paramName, 'true');
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
// Mark containers returned by a generic type constructor so
|
|
1531
|
+
// `zigBindingScopeFor` hoists their name to the module scope (beside the
|
|
1532
|
+
// Function def of the same name) instead of the fn body it sits in.
|
|
1533
|
+
for (const kind of ['@declaration.struct', '@declaration.union', '@declaration.enum']) {
|
|
1534
|
+
const containerAnchor = nodeMap[kind];
|
|
1535
|
+
const nameNode = nodeMap['@declaration.name'];
|
|
1536
|
+
if (containerAnchor !== undefined &&
|
|
1537
|
+
nameNode !== undefined &&
|
|
1538
|
+
zigTypeConstructorOf(containerAnchor) !== null) {
|
|
1539
|
+
grouped['@declaration.type-constructor'] = syntheticCapture('@declaration.type-constructor', nameNode, 'true');
|
|
1540
|
+
}
|
|
1541
|
+
// F8 — FUNCTION-LOCAL named container (`const R = struct {…}` inside a
|
|
1542
|
+
// fn body) and (8.5) CONTAINER-HOSTED named container (`pub const Item
|
|
1543
|
+
// = struct {…}` inside `A`): the def's qualified name is its identity
|
|
1544
|
+
// (`string$R`, `A.Item` — matching the graph node the structure phase
|
|
1545
|
+
// mints via `zigContainerName`), while the scope still binds the
|
|
1546
|
+
// spelling code uses (`R.get()`, `self: *R`, `Item{}`) — the Java
|
|
1547
|
+
// local / nested class split (`@declaration.binding-name`,
|
|
1548
|
+
// java/captures.ts).
|
|
1549
|
+
if (containerAnchor !== undefined && nameNode !== undefined) {
|
|
1550
|
+
const identity = zigContainerName(containerAnchor, _filePath);
|
|
1551
|
+
if (identity !== undefined && identity !== nameNode.text) {
|
|
1552
|
+
grouped['@declaration.binding-name'] = grouped['@declaration.name'];
|
|
1553
|
+
grouped['@declaration.name'] = syntheticCapture('@declaration.name', nameNode, identity);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
// Relabel container-nested fns Function → Method (provider labelOverride
|
|
1558
|
+
// parity). The anchor capture name carries the kind, so rebuild it.
|
|
1559
|
+
const fnAnchor = nodeMap['@declaration.function'];
|
|
1560
|
+
if (fnAnchor !== undefined && isZigContainerMethod(fnAnchor)) {
|
|
1561
|
+
const fnCapture = grouped['@declaration.function'];
|
|
1562
|
+
delete grouped['@declaration.function'];
|
|
1563
|
+
grouped['@declaration.method'] = { ...fnCapture, name: '@declaration.method' };
|
|
1564
|
+
}
|
|
1565
|
+
// `pub const X = @import("x.zig").X;` at file scope republishes `X`.
|
|
1566
|
+
if (nodeMap['@import.statement'] !== undefined &&
|
|
1567
|
+
nodeMap['@import.imported'] !== undefined &&
|
|
1568
|
+
isZigPublishingImport(nodeMap['@import.statement'])) {
|
|
1569
|
+
Object.assign(grouped, republishMarker(nodeMap['@import.statement']));
|
|
1570
|
+
}
|
|
1571
|
+
out.push(grouped);
|
|
1572
|
+
// F5 — field types. `session: *Session,` declares the TYPE of the member
|
|
1573
|
+
// `session`; the compound resolver reads member types from the owning
|
|
1574
|
+
// container's Class scope (`typeOfMemberOnClass` →
|
|
1575
|
+
// `classScope.typeBindings.get('session')`), so without a binding there
|
|
1576
|
+
// `self.session.name()` / `self.counter.incr()` never resolve (Lightpanda:
|
|
1577
|
+
// 9 of 2803 such calls, 0.3 %). Synthesize a `@type-binding.field` group
|
|
1578
|
+
// per typed field (Go does the same in go/captures.ts; Rust/C++ capture it
|
|
1579
|
+
// in the query). Enum variants carry no type and get no binding. The
|
|
1580
|
+
// group's anchor is the type node — inside the container, never equal to
|
|
1581
|
+
// its range — so the extractor hosts it in the container's Class scope
|
|
1582
|
+
// (the file's Class scope for a file-struct); `zigBindingScopeFor` hoists
|
|
1583
|
+
// only declaration NAMES, never `@type-binding.*`. `?*Self` /
|
|
1584
|
+
// `?*Page` (a `@This()` alias) is rewritten to the container name exactly
|
|
1585
|
+
// like a parameter type; `normalizeZigTypeName` reduces `?*Session`,
|
|
1586
|
+
// `[]const Counter` to the nominal name and the extractor keeps the
|
|
1587
|
+
// written spelling as `declaredSpelling`.
|
|
1588
|
+
const fieldAnchor = nodeMap['@declaration.field'];
|
|
1589
|
+
const fieldType = nodeMap['@declaration.field-type'];
|
|
1590
|
+
const fieldName = nodeMap['@declaration.name'];
|
|
1591
|
+
if (fieldAnchor !== undefined &&
|
|
1592
|
+
fieldType !== undefined &&
|
|
1593
|
+
fieldName !== undefined &&
|
|
1594
|
+
// An inline anonymous container (`opts: struct { a: u32 },`) names no
|
|
1595
|
+
// type; its own Class scope already owns its members.
|
|
1596
|
+
!ZIG_CONTAINER_TYPES.has(fieldType.type)) {
|
|
1597
|
+
const rewritten = thisAliases.size > 0 ? rewriteZigThisAlias(fieldType, thisAliases) : undefined;
|
|
1598
|
+
out.push({
|
|
1599
|
+
'@type-binding.field': nodeToCapture('@type-binding.field', fieldType),
|
|
1600
|
+
'@type-binding.name': nodeToCapture('@type-binding.name', fieldName),
|
|
1601
|
+
'@type-binding.type': rewritten === undefined
|
|
1602
|
+
? nodeToCapture('@type-binding.type', fieldType)
|
|
1603
|
+
: syntheticCapture('@type-binding.type', fieldType, rewritten),
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
else if (fieldAnchor !== undefined &&
|
|
1607
|
+
fieldType === undefined &&
|
|
1608
|
+
fieldName !== undefined &&
|
|
1609
|
+
fieldName.text !== '_' &&
|
|
1610
|
+
fieldAnchor.parent?.type === 'enum_declaration') {
|
|
1611
|
+
// An enum VARIANT has no written type, but it has one: the enum itself.
|
|
1612
|
+
// `Operation.create_accounts.event_max()` — a method called on a variant
|
|
1613
|
+
// reached through the type — needs `Operation.create_accounts` typed as
|
|
1614
|
+
// `Operation` for the compound resolver to walk the field like
|
|
1615
|
+
// `self.session.name()`. tigerbeetle writes this shape 147 times
|
|
1616
|
+
// (`Operation.<variant>.event_max(…)`, `TestOperation.create.event_size(`).
|
|
1617
|
+
const enumName = zigContainerBindingName(fieldAnchor.parent, _filePath) ??
|
|
1618
|
+
zigContainerName(fieldAnchor.parent, _filePath);
|
|
1619
|
+
if (enumName !== undefined) {
|
|
1620
|
+
out.push({
|
|
1621
|
+
'@type-binding.field': nodeToCapture('@type-binding.field', fieldName),
|
|
1622
|
+
'@type-binding.name': nodeToCapture('@type-binding.name', fieldName),
|
|
1623
|
+
'@type-binding.type': syntheticCapture('@type-binding.type', fieldName, enumName),
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
// `const Page = @import("Page.zig")` binds BOTH the module (namespace:
|
|
1628
|
+
// `Page.init()`) and, when the target is a file-struct, the type it
|
|
1629
|
+
// declares (`page: *Page`, `var p: Page`). The type is exported from the
|
|
1630
|
+
// target under its file stem, so emit a second, NAMED import of that stem
|
|
1631
|
+
// beside the namespace import. For a target that is only a namespace the
|
|
1632
|
+
// named import finds no such export and binds nothing.
|
|
1633
|
+
const importStmt = nodeMap['@import.statement'];
|
|
1634
|
+
if (importStmt !== undefined &&
|
|
1635
|
+
isZigKeywordDeclaration(importStmt) &&
|
|
1636
|
+
nodeMap['@import.imported'] === undefined &&
|
|
1637
|
+
nodeMap['@import.name'] !== undefined &&
|
|
1638
|
+
nodeMap['@import.source'] !== undefined) {
|
|
1639
|
+
const source = nodeMap['@import.source'].text.replace(/^["']|["']$/g, '');
|
|
1640
|
+
if (source.endsWith('.zig')) {
|
|
1641
|
+
out.push({
|
|
1642
|
+
'@import.statement': grouped['@import.statement'],
|
|
1643
|
+
'@import.name': grouped['@import.name'],
|
|
1644
|
+
'@import.source': grouped['@import.source'],
|
|
1645
|
+
'@import.imported': syntheticCapture('@import.imported', nodeMap['@import.name'], zigFileStructName(source)),
|
|
1646
|
+
// `pub const Arena = @import("Arena.zig");` republishes the TYPE too:
|
|
1647
|
+
// `lp.Arena` in a third file is the Struct `Arena.zig` declares.
|
|
1648
|
+
...republishMarker(importStmt),
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
// A generic type constructor yields two module-scope defs of one name: the
|
|
1654
|
+
// Function `Stack` and the Struct `Stack` it returns. Import materialization
|
|
1655
|
+
// keeps the FIRST def of a name (finalize `indexExportsByName`), and query
|
|
1656
|
+
// order puts the fn (outer node) first — so `const Stack =
|
|
1657
|
+
// @import("x.zig").Stack;` bound the Function and `Stack(u8){}` typed
|
|
1658
|
+
// nothing. Emit the container def ahead of its constructor: the type is what
|
|
1659
|
+
// an importer instantiates, and the free call `Stack(u8)` still resolves
|
|
1660
|
+
// (a Struct is a valid call target — the constructor-reference path).
|
|
1661
|
+
for (let i = 0; i < out.length; i++) {
|
|
1662
|
+
const group = out[i];
|
|
1663
|
+
if (group['@declaration.type-constructor'] === undefined)
|
|
1664
|
+
continue;
|
|
1665
|
+
const anchor = group['@declaration.struct'] ?? group['@declaration.union'] ?? group['@declaration.enum'];
|
|
1666
|
+
if (anchor === undefined)
|
|
1667
|
+
continue;
|
|
1668
|
+
for (let j = 0; j < i; j++) {
|
|
1669
|
+
const fn = out[j]['@declaration.function'];
|
|
1670
|
+
if (fn === undefined)
|
|
1671
|
+
continue;
|
|
1672
|
+
if (fn.range.startLine < anchor.range.startLine ||
|
|
1673
|
+
(fn.range.startLine === anchor.range.startLine &&
|
|
1674
|
+
fn.range.startCol <= anchor.range.startCol)) {
|
|
1675
|
+
if (fn.range.endLine > anchor.range.endLine ||
|
|
1676
|
+
(fn.range.endLine === anchor.range.endLine && fn.range.endCol >= anchor.range.endCol)) {
|
|
1677
|
+
out.splice(j, 0, ...out.splice(i, 1));
|
|
1678
|
+
break;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
// F8 — ANONYMOUS containers (`std.sort.pdq(…, struct { fn lessThan … }
|
|
1684
|
+
// .lessThan)`, `const cmp = struct { fn lt … }.lt;`, `clamp: ?struct { min:
|
|
1685
|
+
// u32 }`): no query rule binds them, so their Class scope owned no def and
|
|
1686
|
+
// their fns were ownerless, colliding Methods. Synthesize the def the
|
|
1687
|
+
// structure phase mints (`zigContainerName` → `build$1`), anchored on the
|
|
1688
|
+
// container node so it shares the `@scope.class` range: the def joins that
|
|
1689
|
+
// scope's ownedDefs (`populateClassOwnedMembers` then qualifies its members
|
|
1690
|
+
// `build$1.lessThan` and stamps their ownerId) and the name auto-hoists to
|
|
1691
|
+
// the enclosing scope, where a `const Self = @This();` rewrite can find it.
|
|
1692
|
+
out.push(...synthesizeZigAnonymousContainerDeclarations(root, _filePath));
|
|
1693
|
+
// Result-location sites — `const a: Counter = .init(1);`, `return .init(3);`,
|
|
1694
|
+
// `const b: Counter = .{ .n = 2 };` (8.6): the call / construction the query
|
|
1695
|
+
// cannot see because the type is written on the LEFT.
|
|
1696
|
+
out.push(...synthesizeZigResultLocationReferences(root, thisAliases, fileStructName));
|
|
1697
|
+
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, zigCallableCaptureOptions(fnLocalNames, hostValueNames)));
|
|
1698
|
+
// Use-site rewrites, in this order: a deep alias's receiver may itself name
|
|
1699
|
+
// a fn-local import (`const m = @import(…); const w = m.B.work;`), and the
|
|
1700
|
+
// second pass rewrites that name inside the receiver text it just minted.
|
|
1701
|
+
rewriteZigDeepAliasReferences(out, deepAliases.aliases);
|
|
1702
|
+
rewriteZigFunctionLocalImportNames(out, fnLocalImports);
|
|
1703
|
+
return stampZigStaticGating(out, tree.rootNode);
|
|
1704
|
+
}
|
|
1705
|
+
// ─── Use-site rewrites (8.4 / 8.9) and result-location sites (8.6) ────────────
|
|
1706
|
+
/** The unique spelling a function-local import binding gets: `m$<callable>`
|
|
1707
|
+
* — `m$f_sib_a`, `m$Reflect$string`, `m$test$L12`. `$` cannot appear in a
|
|
1708
|
+
* Zig identifier, so the key collides with nothing the source declares;
|
|
1709
|
+
* every non-word character of the callable's qualified name becomes `$` so
|
|
1710
|
+
* the key stays a single receiver segment (a `.` would split it). */
|
|
1711
|
+
function zigFunctionLocalImportKey(name, fn, filePath) {
|
|
1712
|
+
return `${name}$${zigCallableQualifiedName(fn, filePath).replace(/[^\w]/g, '$')}`;
|
|
1713
|
+
}
|
|
1714
|
+
/** The capture-side range of `node` (1-based lines, as `nodeToCapture`). */
|
|
1715
|
+
function zigNodeRange(node) {
|
|
1716
|
+
return {
|
|
1717
|
+
startLine: node.startPosition.row + 1,
|
|
1718
|
+
startCol: node.startPosition.column,
|
|
1719
|
+
endLine: node.endPosition.row + 1,
|
|
1720
|
+
endCol: node.endPosition.column,
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
function zigRangeWithin(inner, outer) {
|
|
1724
|
+
const startsAfter = inner.startLine > outer.startLine ||
|
|
1725
|
+
(inner.startLine === outer.startLine && inner.startCol >= outer.startCol);
|
|
1726
|
+
const endsBefore = inner.endLine < outer.endLine ||
|
|
1727
|
+
(inner.endLine === outer.endLine && inner.endCol <= outer.endCol);
|
|
1728
|
+
return startsAfter && endsBefore;
|
|
1729
|
+
}
|
|
1730
|
+
/** Replace every bare identifier token `name` in `text` — outside string
|
|
1731
|
+
* literals, and not the member of a `.name` access or the tail of a
|
|
1732
|
+
* `@builtin` — with `replacement`. Zig forbids shadowing, so inside the
|
|
1733
|
+
* region a rewrite applies to, every such token is the same binding. */
|
|
1734
|
+
function zigReplaceIdentifier(text, name, replacement) {
|
|
1735
|
+
let out = '';
|
|
1736
|
+
let i = 0;
|
|
1737
|
+
let inString = false;
|
|
1738
|
+
while (i < text.length) {
|
|
1739
|
+
const ch = text[i];
|
|
1740
|
+
if (inString) {
|
|
1741
|
+
out += ch;
|
|
1742
|
+
if (ch === '\\') {
|
|
1743
|
+
out += text[i + 1] ?? '';
|
|
1744
|
+
i += 2;
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
if (ch === '"')
|
|
1748
|
+
inString = false;
|
|
1749
|
+
i++;
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
if (ch === '"') {
|
|
1753
|
+
inString = true;
|
|
1754
|
+
out += ch;
|
|
1755
|
+
i++;
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
if (/[A-Za-z_]/.test(ch)) {
|
|
1759
|
+
let j = i;
|
|
1760
|
+
while (j < text.length && /\w/.test(text[j]))
|
|
1761
|
+
j++;
|
|
1762
|
+
const word = text.slice(i, j);
|
|
1763
|
+
const prev = i > 0 ? text[i - 1] : '';
|
|
1764
|
+
out += word === name && prev !== '.' && prev !== '@' ? replacement : word;
|
|
1765
|
+
i = j;
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
out += ch;
|
|
1769
|
+
i++;
|
|
1770
|
+
}
|
|
1771
|
+
return out;
|
|
1772
|
+
}
|
|
1773
|
+
/** Every capture whose text can spell a receiver, a type, a bound name or a
|
|
1774
|
+
* callable-flow cell — the ones a fn-local import name can appear in. */
|
|
1775
|
+
const ZIG_NAME_BEARING_TAGS = [
|
|
1776
|
+
'@import.name',
|
|
1777
|
+
'@reference.receiver',
|
|
1778
|
+
'@reference.name',
|
|
1779
|
+
'@type-binding.type',
|
|
1780
|
+
'@callable-flow.target-name',
|
|
1781
|
+
'@callable-flow.target-qualified-name',
|
|
1782
|
+
'@callable-flow.receiver',
|
|
1783
|
+
'@callable-flow.source',
|
|
1784
|
+
'@callable-flow.destination',
|
|
1785
|
+
'@callable-flow.callee',
|
|
1786
|
+
'@callable-flow.direct-callee-name',
|
|
1787
|
+
];
|
|
1788
|
+
/** 8.9 — rewrite a fn-local import's binding and its uses to its unique key
|
|
1789
|
+
* (`zigFunctionLocalImportKey`), within that fn's range only. */
|
|
1790
|
+
function rewriteZigFunctionLocalImportNames(out, fnLocalImports) {
|
|
1791
|
+
if (fnLocalImports.size === 0)
|
|
1792
|
+
return;
|
|
1793
|
+
for (const { fn, names } of fnLocalImports.values()) {
|
|
1794
|
+
const fnRange = zigNodeRange(fn);
|
|
1795
|
+
for (let i = 0; i < out.length; i++) {
|
|
1796
|
+
const group = out[i];
|
|
1797
|
+
let next;
|
|
1798
|
+
for (const tag of ZIG_NAME_BEARING_TAGS) {
|
|
1799
|
+
const cap = group[tag];
|
|
1800
|
+
if (cap === undefined || !zigRangeWithin(cap.range, fnRange))
|
|
1801
|
+
continue;
|
|
1802
|
+
let text = cap.text;
|
|
1803
|
+
for (const [name, key] of names)
|
|
1804
|
+
text = zigReplaceIdentifier(text, name, key);
|
|
1805
|
+
if (text === cap.text)
|
|
1806
|
+
continue;
|
|
1807
|
+
next ??= { ...group };
|
|
1808
|
+
next[tag] = { ...cap, text };
|
|
1809
|
+
}
|
|
1810
|
+
if (next !== undefined)
|
|
1811
|
+
out[i] = next;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
/** The member chain a declaration's value spells — `lib.B.work` →
|
|
1816
|
+
* `{ root: lib, members: [B, work] }`; undefined for anything but a
|
|
1817
|
+
* `field_expression` chain rooted in an identifier or an `@import(…)`. */
|
|
1818
|
+
function zigMemberChainOf(decl) {
|
|
1819
|
+
const named = decl.namedChildren.filter((c) => c !== null);
|
|
1820
|
+
if (named.length !== 2 || named[0].type !== 'identifier')
|
|
1821
|
+
return undefined;
|
|
1822
|
+
let cur = named[1];
|
|
1823
|
+
const members = [];
|
|
1824
|
+
while (cur !== null && cur.type === 'field_expression') {
|
|
1825
|
+
const member = cur.childForFieldName('member');
|
|
1826
|
+
const object = cur.childForFieldName('object');
|
|
1827
|
+
if (member === null || object === null)
|
|
1828
|
+
return undefined; // `.init` literal
|
|
1829
|
+
members.unshift(member);
|
|
1830
|
+
cur = object;
|
|
1831
|
+
}
|
|
1832
|
+
if (cur === null || members.length === 0)
|
|
1833
|
+
return undefined;
|
|
1834
|
+
if (cur.type !== 'identifier' && !isZigImportBuiltin(cur))
|
|
1835
|
+
return undefined;
|
|
1836
|
+
return { root: cur, members };
|
|
1837
|
+
}
|
|
1838
|
+
/** 8.4 — every deep member alias in the tree whose root is a module handle:
|
|
1839
|
+
* a file-level `@import` binding of this file, a fn-local one (its key is
|
|
1840
|
+
* applied by the later rewrite), or an inline `@import(…)`. Returns the
|
|
1841
|
+
* aliases, the declaring nodes (so their import / alias groups are handled
|
|
1842
|
+
* as deep aliases) and the inline-import roots (bound as namespaces). */
|
|
1843
|
+
function collectZigDeepAliases(root, importSources, fnLocalImports) {
|
|
1844
|
+
const aliases = [];
|
|
1845
|
+
const declIds = new Set();
|
|
1846
|
+
const inlineRoots = new Set();
|
|
1847
|
+
const visit = (node) => {
|
|
1848
|
+
if (node.type === 'variable_declaration' && isZigKeywordDeclaration(node)) {
|
|
1849
|
+
const chain = zigMemberChainOf(node);
|
|
1850
|
+
if (chain !== undefined && chain.members.length >= 2) {
|
|
1851
|
+
const fn = zigEnclosingFunction(node);
|
|
1852
|
+
const isHandle = isZigImportBuiltin(chain.root) ||
|
|
1853
|
+
importSources.has(chain.root.text) ||
|
|
1854
|
+
(fn !== null && fnLocalImports.get(fn.id)?.names.has(chain.root.text) === true);
|
|
1855
|
+
if (isHandle) {
|
|
1856
|
+
const host = zigIdentityHost(node);
|
|
1857
|
+
const receiver = [chain.root.text, ...chain.members.slice(0, -1).map((m) => m.text)].join('.');
|
|
1858
|
+
aliases.push({
|
|
1859
|
+
name: node.namedChild(0).text,
|
|
1860
|
+
receiver,
|
|
1861
|
+
member: chain.members[chain.members.length - 1].text,
|
|
1862
|
+
range: host === null || host.type === 'source_file' ? null : zigNodeRange(host),
|
|
1863
|
+
});
|
|
1864
|
+
declIds.add(node.id);
|
|
1865
|
+
if (isZigImportBuiltin(chain.root))
|
|
1866
|
+
inlineRoots.add(chain.root.id);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1871
|
+
const child = node.namedChild(i);
|
|
1872
|
+
if (child !== null)
|
|
1873
|
+
visit(child);
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
visit(root);
|
|
1877
|
+
return { aliases, declIds, inlineRoots };
|
|
1878
|
+
}
|
|
1879
|
+
/** 8.4 — turn a free call / bare construction of a deep alias (`chosen()`,
|
|
1880
|
+
* `Inner{}`) into the qualified reference it stands for (receiver `lib.B`,
|
|
1881
|
+
* member `work`), so it resolves through the alias's written owner path and
|
|
1882
|
+
* never through a same-named member elsewhere in the module. */
|
|
1883
|
+
function rewriteZigDeepAliasReferences(out, aliases) {
|
|
1884
|
+
if (aliases.length === 0)
|
|
1885
|
+
return;
|
|
1886
|
+
for (let i = 0; i < out.length; i++) {
|
|
1887
|
+
const group = out[i];
|
|
1888
|
+
const free = group['@reference.call.free'];
|
|
1889
|
+
const ctor = group['@reference.call.constructor'];
|
|
1890
|
+
if ((free === undefined && ctor === undefined) || group['@reference.receiver'] !== undefined) {
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
const nameCap = group['@reference.name'];
|
|
1894
|
+
if (nameCap === undefined)
|
|
1895
|
+
continue;
|
|
1896
|
+
const alias = aliases.find((a) => a.name === nameCap.text && (a.range === null || zigRangeWithin(nameCap.range, a.range)));
|
|
1897
|
+
if (alias === undefined)
|
|
1898
|
+
continue;
|
|
1899
|
+
const next = { ...group };
|
|
1900
|
+
if (free !== undefined) {
|
|
1901
|
+
delete next['@reference.call.free'];
|
|
1902
|
+
next['@reference.call.member'] = { ...free, name: '@reference.call.member' };
|
|
1903
|
+
}
|
|
1904
|
+
next['@reference.receiver'] = {
|
|
1905
|
+
name: '@reference.receiver',
|
|
1906
|
+
range: nameCap.range,
|
|
1907
|
+
text: alias.receiver,
|
|
1908
|
+
};
|
|
1909
|
+
next['@reference.name'] = { ...nameCap, text: alias.member };
|
|
1910
|
+
out[i] = next;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
/** 8.6 — reference sites for RESULT-LOCATION expressions: a decl literal
|
|
1914
|
+
* `.init(…)` or an anonymous literal `.{…}` whose type comes from where the
|
|
1915
|
+
* value lands — a declared variable (`const a: Counter = .init(1);`), a
|
|
1916
|
+
* function's return (`fn make() Counter { return .init(3); }`), a container
|
|
1917
|
+
* field's default (`n: Counter = .init(0),`). The query cannot see these:
|
|
1918
|
+
* its member-call rule needs an object and its constructor rule a type
|
|
1919
|
+
* identifier, and the annotation only typed the VARIABLE — the `init` call
|
|
1920
|
+
* and the `Counter{…}` construction event were absent from the graph. Each
|
|
1921
|
+
* site is emitted with the expected type as its receiver (`Counter`,
|
|
1922
|
+
* `stdx.Thing`, `@This()` and `Self` rewritten to the container), so it
|
|
1923
|
+
* resolves exactly as `Counter.init(1)` / `Counter{…}` would — through the
|
|
1924
|
+
* class binding or the namespace chain, never by simple name workspace-wide.
|
|
1925
|
+
* Arguments (`f(.init(1))`) are out: the expected type is the callee's
|
|
1926
|
+
* parameter, which needs the resolved callee. */
|
|
1927
|
+
function synthesizeZigResultLocationReferences(root, thisAliases, fileStructName) {
|
|
1928
|
+
const out = [];
|
|
1929
|
+
const emit = (expected, value) => {
|
|
1930
|
+
const inner = zigUnwrapValue(value);
|
|
1931
|
+
let isDeclLiteral = false;
|
|
1932
|
+
let member = null;
|
|
1933
|
+
if (inner.type === 'call_expression') {
|
|
1934
|
+
const callee = inner.childForFieldName('function');
|
|
1935
|
+
if (callee?.type !== 'field_expression' || callee.childForFieldName('object') !== null)
|
|
1936
|
+
return;
|
|
1937
|
+
member = callee.childForFieldName('member');
|
|
1938
|
+
if (member === null)
|
|
1939
|
+
return;
|
|
1940
|
+
isDeclLiteral = true;
|
|
1941
|
+
}
|
|
1942
|
+
else if (inner.type !== 'anonymous_struct_initializer') {
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
let text = expected.text;
|
|
1946
|
+
if (text.includes('@This()')) {
|
|
1947
|
+
const target = zigThisTargetFor(expected, fileStructName);
|
|
1948
|
+
if (target === undefined)
|
|
1949
|
+
return;
|
|
1950
|
+
text = text.replace('@This()', target);
|
|
1951
|
+
}
|
|
1952
|
+
else {
|
|
1953
|
+
text = rewriteZigThisAlias(expected, thisAliases) ?? text;
|
|
1954
|
+
}
|
|
1955
|
+
const nominal = normalizeZigTypeName(text);
|
|
1956
|
+
// A builtin / primitive (`u32`, `void`, `anyerror`) constructs nothing.
|
|
1957
|
+
if (!/^[A-Z@]/.test(nominal) && !nominal.includes('.'))
|
|
1958
|
+
return;
|
|
1959
|
+
if (isDeclLiteral) {
|
|
1960
|
+
out.push({
|
|
1961
|
+
'@reference.call.member': nodeToCapture('@reference.call.member', inner),
|
|
1962
|
+
'@reference.receiver': syntheticCapture('@reference.receiver', inner, nominal),
|
|
1963
|
+
'@reference.name': nodeToCapture('@reference.name', member),
|
|
1964
|
+
});
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
const dot = zigLastTopLevelDot(nominal);
|
|
1968
|
+
out.push(dot === -1
|
|
1969
|
+
? {
|
|
1970
|
+
'@reference.call.constructor': nodeToCapture('@reference.call.constructor', inner),
|
|
1971
|
+
'@reference.name': syntheticCapture('@reference.name', inner, nominal),
|
|
1972
|
+
}
|
|
1973
|
+
: {
|
|
1974
|
+
'@reference.call.constructor': nodeToCapture('@reference.call.constructor', inner),
|
|
1975
|
+
'@reference.receiver': syntheticCapture('@reference.receiver', inner, nominal.slice(0, dot)),
|
|
1976
|
+
'@reference.name': syntheticCapture('@reference.name', inner, nominal.slice(dot + 1)),
|
|
1977
|
+
});
|
|
1978
|
+
};
|
|
1979
|
+
const visit = (node) => {
|
|
1980
|
+
if (node.type === 'variable_declaration' && isZigKeywordDeclaration(node)) {
|
|
1981
|
+
const typeNode = node.childForFieldName('type');
|
|
1982
|
+
const named = node.namedChildren.filter((c) => c !== null);
|
|
1983
|
+
const last = named[named.length - 1];
|
|
1984
|
+
if (typeNode !== null && last !== undefined && last.id !== typeNode.id)
|
|
1985
|
+
emit(typeNode, last);
|
|
1986
|
+
}
|
|
1987
|
+
else if (node.type === 'return_expression') {
|
|
1988
|
+
const fn = zigEnclosingFunction(node);
|
|
1989
|
+
const ret = fn?.type === 'function_declaration' ? fn.childForFieldName('type') : null;
|
|
1990
|
+
const value = node.namedChild(0);
|
|
1991
|
+
if (ret !== null && ret !== undefined && value !== null && zigReturnTypeIsNominal(ret)) {
|
|
1992
|
+
emit(ret, value);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
else if (node.type === 'container_field') {
|
|
1996
|
+
const typeNode = node.childForFieldName('type');
|
|
1997
|
+
const nameNode = node.childForFieldName('name');
|
|
1998
|
+
const named = node.namedChildren.filter((c) => c !== null);
|
|
1999
|
+
const last = named[named.length - 1];
|
|
2000
|
+
if (typeNode !== null &&
|
|
2001
|
+
last !== undefined &&
|
|
2002
|
+
last.id !== typeNode.id &&
|
|
2003
|
+
last.id !== nameNode?.id) {
|
|
2004
|
+
emit(typeNode, last);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2008
|
+
const child = node.namedChild(i);
|
|
2009
|
+
if (child !== null)
|
|
2010
|
+
visit(child);
|
|
2011
|
+
}
|
|
2012
|
+
};
|
|
2013
|
+
visit(root);
|
|
2014
|
+
return out;
|
|
2015
|
+
}
|
|
2016
|
+
/** Index of the last `.` at nesting depth 0 and outside string literals —
|
|
2017
|
+
* the one that separates `@import("a.zig").Thing` or `stdx.List(u8).Node`
|
|
2018
|
+
* into receiver and member. -1 when there is none. */
|
|
2019
|
+
function zigLastTopLevelDot(text) {
|
|
2020
|
+
let depth = 0;
|
|
2021
|
+
let inString = false;
|
|
2022
|
+
let last = -1;
|
|
2023
|
+
for (let i = 0; i < text.length; i++) {
|
|
2024
|
+
const ch = text[i];
|
|
2025
|
+
if (inString) {
|
|
2026
|
+
if (ch === '\\')
|
|
2027
|
+
i++;
|
|
2028
|
+
else if (ch === '"')
|
|
2029
|
+
inString = false;
|
|
2030
|
+
continue;
|
|
2031
|
+
}
|
|
2032
|
+
if (ch === '"')
|
|
2033
|
+
inString = true;
|
|
2034
|
+
else if (ch === '(' || ch === '[')
|
|
2035
|
+
depth++;
|
|
2036
|
+
else if (ch === ')' || ch === ']')
|
|
2037
|
+
depth--;
|
|
2038
|
+
else if (ch === '.' && depth === 0)
|
|
2039
|
+
last = i;
|
|
2040
|
+
}
|
|
2041
|
+
return last;
|
|
2042
|
+
}
|
|
2043
|
+
/** One `@declaration.<kind>` group per anonymous container in the tree —
|
|
2044
|
+
* see the call site in `emitZigScopeCaptures` (F8). Named after
|
|
2045
|
+
* `synthesizeJavaAnonymousClassDeclarations`, which does the same for
|
|
2046
|
+
* `new Runnable() {…}` bodies. */
|
|
2047
|
+
function synthesizeZigAnonymousContainerDeclarations(root, filePath) {
|
|
2048
|
+
const out = [];
|
|
2049
|
+
const visit = (node) => {
|
|
2050
|
+
if (ZIG_CONTAINER_TYPES.has(node.type) && zigContainerBindingName(node) === undefined) {
|
|
2051
|
+
const identity = zigContainerName(node, filePath);
|
|
2052
|
+
const label = zigContainerLabel(node);
|
|
2053
|
+
if (identity !== undefined && label !== undefined) {
|
|
2054
|
+
const tag = `@declaration.${label.toLowerCase()}`;
|
|
2055
|
+
out.push({
|
|
2056
|
+
[tag]: nodeToCapture(tag, node),
|
|
2057
|
+
'@declaration.name': syntheticCapture('@declaration.name', node, identity),
|
|
2058
|
+
'@declaration.is-synthetic': syntheticCapture('@declaration.is-synthetic', node, 'true'),
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2063
|
+
const child = node.namedChild(i);
|
|
2064
|
+
if (child !== null)
|
|
2065
|
+
visit(child);
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
visit(root);
|
|
2069
|
+
return out;
|
|
2070
|
+
}
|