homegraph 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +709 -0
- package/dist/bin/codegraph.d.ts +26 -0
- package/dist/bin/codegraph.d.ts.map +1 -0
- package/dist/bin/codegraph.js +1572 -0
- package/dist/bin/codegraph.js.map +1 -0
- package/dist/bin/homegraph.d.ts +26 -0
- package/dist/bin/homegraph.d.ts.map +1 -0
- package/dist/bin/homegraph.js +1572 -0
- package/dist/bin/homegraph.js.map +1 -0
- package/dist/bin/node-version-check.d.ts +37 -0
- package/dist/bin/node-version-check.d.ts.map +1 -0
- package/dist/bin/node-version-check.js +79 -0
- package/dist/bin/node-version-check.js.map +1 -0
- package/dist/bin/uninstall.d.ts +14 -0
- package/dist/bin/uninstall.d.ts.map +1 -0
- package/dist/bin/uninstall.js +36 -0
- package/dist/bin/uninstall.js.map +1 -0
- package/dist/context/formatter.d.ts +30 -0
- package/dist/context/formatter.d.ts.map +1 -0
- package/dist/context/formatter.js +263 -0
- package/dist/context/formatter.js.map +1 -0
- package/dist/context/index.d.ts +119 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +1289 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/markers.d.ts +19 -0
- package/dist/context/markers.d.ts.map +1 -0
- package/dist/context/markers.js +22 -0
- package/dist/context/markers.js.map +1 -0
- package/dist/db/index.d.ts +101 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +250 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations.d.ts +44 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +131 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/queries.d.ts +386 -0
- package/dist/db/queries.d.ts.map +1 -0
- package/dist/db/queries.js +1562 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.sql +151 -0
- package/dist/db/sqlite-adapter.d.ts +53 -0
- package/dist/db/sqlite-adapter.d.ts.map +1 -0
- package/dist/db/sqlite-adapter.js +117 -0
- package/dist/db/sqlite-adapter.js.map +1 -0
- package/dist/directory.d.ts +57 -0
- package/dist/directory.d.ts.map +1 -0
- package/dist/directory.js +253 -0
- package/dist/directory.js.map +1 -0
- package/dist/errors.d.ts +136 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +219 -0
- package/dist/errors.js.map +1 -0
- package/dist/extraction/arkanalyzer-extractor.d.ts +6 -0
- package/dist/extraction/arkanalyzer-extractor.d.ts.map +1 -0
- package/dist/extraction/arkanalyzer-extractor.js +11 -0
- package/dist/extraction/arkanalyzer-extractor.js.map +1 -0
- package/dist/extraction/arkanalyzer-project.d.ts +20 -0
- package/dist/extraction/arkanalyzer-project.d.ts.map +1 -0
- package/dist/extraction/arkanalyzer-project.js +596 -0
- package/dist/extraction/arkanalyzer-project.js.map +1 -0
- package/dist/extraction/context.d.ts +12 -0
- package/dist/extraction/context.d.ts.map +1 -0
- package/dist/extraction/context.js +29 -0
- package/dist/extraction/context.js.map +1 -0
- package/dist/extraction/dfm-extractor.d.ts +31 -0
- package/dist/extraction/dfm-extractor.d.ts.map +1 -0
- package/dist/extraction/dfm-extractor.js +151 -0
- package/dist/extraction/dfm-extractor.js.map +1 -0
- package/dist/extraction/extraction-version.d.ts +25 -0
- package/dist/extraction/extraction-version.d.ts.map +1 -0
- package/dist/extraction/extraction-version.js +28 -0
- package/dist/extraction/extraction-version.js.map +1 -0
- package/dist/extraction/generated-detection.d.ts +30 -0
- package/dist/extraction/generated-detection.d.ts.map +1 -0
- package/dist/extraction/generated-detection.js +80 -0
- package/dist/extraction/generated-detection.js.map +1 -0
- package/dist/extraction/grammars.d.ts +108 -0
- package/dist/extraction/grammars.d.ts.map +1 -0
- package/dist/extraction/grammars.js +465 -0
- package/dist/extraction/grammars.js.map +1 -0
- package/dist/extraction/index.d.ts +138 -0
- package/dist/extraction/index.d.ts.map +1 -0
- package/dist/extraction/index.js +1396 -0
- package/dist/extraction/index.js.map +1 -0
- package/dist/extraction/languages/arkts.d.ts +18 -0
- package/dist/extraction/languages/arkts.d.ts.map +1 -0
- package/dist/extraction/languages/arkts.js +878 -0
- package/dist/extraction/languages/arkts.js.map +1 -0
- package/dist/extraction/languages/c-cpp.d.ts +4 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
- package/dist/extraction/languages/c-cpp.js +173 -0
- package/dist/extraction/languages/c-cpp.js.map +1 -0
- package/dist/extraction/languages/csharp.d.ts +25 -0
- package/dist/extraction/languages/csharp.d.ts.map +1 -0
- package/dist/extraction/languages/csharp.js +120 -0
- package/dist/extraction/languages/csharp.js.map +1 -0
- package/dist/extraction/languages/dart.d.ts +3 -0
- package/dist/extraction/languages/dart.d.ts.map +1 -0
- package/dist/extraction/languages/dart.js +192 -0
- package/dist/extraction/languages/dart.js.map +1 -0
- package/dist/extraction/languages/go.d.ts +3 -0
- package/dist/extraction/languages/go.d.ts.map +1 -0
- package/dist/extraction/languages/go.js +74 -0
- package/dist/extraction/languages/go.js.map +1 -0
- package/dist/extraction/languages/index.d.ts +10 -0
- package/dist/extraction/languages/index.d.ts.map +1 -0
- package/dist/extraction/languages/index.js +51 -0
- package/dist/extraction/languages/index.js.map +1 -0
- package/dist/extraction/languages/java.d.ts +3 -0
- package/dist/extraction/languages/java.d.ts.map +1 -0
- package/dist/extraction/languages/java.js +74 -0
- package/dist/extraction/languages/java.js.map +1 -0
- package/dist/extraction/languages/javascript.d.ts +3 -0
- package/dist/extraction/languages/javascript.d.ts.map +1 -0
- package/dist/extraction/languages/javascript.js +90 -0
- package/dist/extraction/languages/javascript.js.map +1 -0
- package/dist/extraction/languages/kotlin.d.ts +3 -0
- package/dist/extraction/languages/kotlin.d.ts.map +1 -0
- package/dist/extraction/languages/kotlin.js +285 -0
- package/dist/extraction/languages/kotlin.js.map +1 -0
- package/dist/extraction/languages/lua.d.ts +3 -0
- package/dist/extraction/languages/lua.d.ts.map +1 -0
- package/dist/extraction/languages/lua.js +150 -0
- package/dist/extraction/languages/lua.js.map +1 -0
- package/dist/extraction/languages/luau.d.ts +3 -0
- package/dist/extraction/languages/luau.d.ts.map +1 -0
- package/dist/extraction/languages/luau.js +37 -0
- package/dist/extraction/languages/luau.js.map +1 -0
- package/dist/extraction/languages/objc.d.ts +3 -0
- package/dist/extraction/languages/objc.d.ts.map +1 -0
- package/dist/extraction/languages/objc.js +133 -0
- package/dist/extraction/languages/objc.js.map +1 -0
- package/dist/extraction/languages/pascal.d.ts +3 -0
- package/dist/extraction/languages/pascal.d.ts.map +1 -0
- package/dist/extraction/languages/pascal.js +66 -0
- package/dist/extraction/languages/pascal.js.map +1 -0
- package/dist/extraction/languages/php.d.ts +3 -0
- package/dist/extraction/languages/php.d.ts.map +1 -0
- package/dist/extraction/languages/php.js +120 -0
- package/dist/extraction/languages/php.js.map +1 -0
- package/dist/extraction/languages/python.d.ts +3 -0
- package/dist/extraction/languages/python.d.ts.map +1 -0
- package/dist/extraction/languages/python.js +56 -0
- package/dist/extraction/languages/python.js.map +1 -0
- package/dist/extraction/languages/ruby.d.ts +3 -0
- package/dist/extraction/languages/ruby.d.ts.map +1 -0
- package/dist/extraction/languages/ruby.js +149 -0
- package/dist/extraction/languages/ruby.js.map +1 -0
- package/dist/extraction/languages/rust.d.ts +3 -0
- package/dist/extraction/languages/rust.d.ts.map +1 -0
- package/dist/extraction/languages/rust.js +113 -0
- package/dist/extraction/languages/rust.js.map +1 -0
- package/dist/extraction/languages/scala.d.ts +3 -0
- package/dist/extraction/languages/scala.d.ts.map +1 -0
- package/dist/extraction/languages/scala.js +174 -0
- package/dist/extraction/languages/scala.js.map +1 -0
- package/dist/extraction/languages/swift.d.ts +3 -0
- package/dist/extraction/languages/swift.d.ts.map +1 -0
- package/dist/extraction/languages/swift.js +91 -0
- package/dist/extraction/languages/swift.js.map +1 -0
- package/dist/extraction/languages/typescript.d.ts +3 -0
- package/dist/extraction/languages/typescript.d.ts.map +1 -0
- package/dist/extraction/languages/typescript.js +129 -0
- package/dist/extraction/languages/typescript.js.map +1 -0
- package/dist/extraction/liquid-extractor.d.ts +59 -0
- package/dist/extraction/liquid-extractor.d.ts.map +1 -0
- package/dist/extraction/liquid-extractor.js +354 -0
- package/dist/extraction/liquid-extractor.js.map +1 -0
- package/dist/extraction/mybatis-extractor.d.ts +48 -0
- package/dist/extraction/mybatis-extractor.d.ts.map +1 -0
- package/dist/extraction/mybatis-extractor.js +198 -0
- package/dist/extraction/mybatis-extractor.js.map +1 -0
- package/dist/extraction/parse-worker.d.ts +8 -0
- package/dist/extraction/parse-worker.d.ts.map +1 -0
- package/dist/extraction/parse-worker.js +94 -0
- package/dist/extraction/parse-worker.js.map +1 -0
- package/dist/extraction/razor-extractor.d.ts +42 -0
- package/dist/extraction/razor-extractor.d.ts.map +1 -0
- package/dist/extraction/razor-extractor.js +285 -0
- package/dist/extraction/razor-extractor.js.map +1 -0
- package/dist/extraction/svelte-extractor.d.ts +56 -0
- package/dist/extraction/svelte-extractor.d.ts.map +1 -0
- package/dist/extraction/svelte-extractor.js +272 -0
- package/dist/extraction/svelte-extractor.js.map +1 -0
- package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
- package/dist/extraction/tree-sitter-helpers.js +103 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -0
- package/dist/extraction/tree-sitter-types.d.ts +210 -0
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
- package/dist/extraction/tree-sitter-types.js +10 -0
- package/dist/extraction/tree-sitter-types.js.map +1 -0
- package/dist/extraction/tree-sitter.d.ts +455 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -0
- package/dist/extraction/tree-sitter.js +4089 -0
- package/dist/extraction/tree-sitter.js.map +1 -0
- package/dist/extraction/vue-extractor.d.ts +51 -0
- package/dist/extraction/vue-extractor.d.ts.map +1 -0
- package/dist/extraction/vue-extractor.js +251 -0
- package/dist/extraction/vue-extractor.js.map +1 -0
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/dist/extraction/wasm-runtime-flags.d.ts +38 -0
- package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -0
- package/dist/extraction/wasm-runtime-flags.js +106 -0
- package/dist/extraction/wasm-runtime-flags.js.map +1 -0
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +13 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/queries.d.ts +106 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +339 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/traversal.d.ts +127 -0
- package/dist/graph/traversal.d.ts.map +1 -0
- package/dist/graph/traversal.js +531 -0
- package/dist/graph/traversal.js.map +1 -0
- package/dist/index.d.ts +522 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +947 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/config-writer.d.ts +28 -0
- package/dist/installer/config-writer.d.ts.map +1 -0
- package/dist/installer/config-writer.js +91 -0
- package/dist/installer/config-writer.js.map +1 -0
- package/dist/installer/index.d.ts +100 -0
- package/dist/installer/index.d.ts.map +1 -0
- package/dist/installer/index.js +497 -0
- package/dist/installer/index.js.map +1 -0
- package/dist/installer/instructions-template.d.ts +18 -0
- package/dist/installer/instructions-template.d.ts.map +1 -0
- package/dist/installer/instructions-template.js +21 -0
- package/dist/installer/instructions-template.js.map +1 -0
- package/dist/installer/targets/antigravity.d.ts +57 -0
- package/dist/installer/targets/antigravity.d.ts.map +1 -0
- package/dist/installer/targets/antigravity.js +308 -0
- package/dist/installer/targets/antigravity.js.map +1 -0
- package/dist/installer/targets/claude.d.ts +56 -0
- package/dist/installer/targets/claude.d.ts.map +1 -0
- package/dist/installer/targets/claude.js +388 -0
- package/dist/installer/targets/claude.js.map +1 -0
- package/dist/installer/targets/codex.d.ts +18 -0
- package/dist/installer/targets/codex.d.ts.map +1 -0
- package/dist/installer/targets/codex.js +187 -0
- package/dist/installer/targets/codex.js.map +1 -0
- package/dist/installer/targets/cursor.d.ts +35 -0
- package/dist/installer/targets/cursor.d.ts.map +1 -0
- package/dist/installer/targets/cursor.js +254 -0
- package/dist/installer/targets/cursor.js.map +1 -0
- package/dist/installer/targets/gemini.d.ts +26 -0
- package/dist/installer/targets/gemini.d.ts.map +1 -0
- package/dist/installer/targets/gemini.js +167 -0
- package/dist/installer/targets/gemini.js.map +1 -0
- package/dist/installer/targets/hermes.d.ts +18 -0
- package/dist/installer/targets/hermes.d.ts.map +1 -0
- package/dist/installer/targets/hermes.js +359 -0
- package/dist/installer/targets/hermes.js.map +1 -0
- package/dist/installer/targets/kiro.d.ts +27 -0
- package/dist/installer/targets/kiro.d.ts.map +1 -0
- package/dist/installer/targets/kiro.js +178 -0
- package/dist/installer/targets/kiro.js.map +1 -0
- package/dist/installer/targets/opencode.d.ts +30 -0
- package/dist/installer/targets/opencode.d.ts.map +1 -0
- package/dist/installer/targets/opencode.js +237 -0
- package/dist/installer/targets/opencode.js.map +1 -0
- package/dist/installer/targets/registry.d.ts +35 -0
- package/dist/installer/targets/registry.d.ts.map +1 -0
- package/dist/installer/targets/registry.js +91 -0
- package/dist/installer/targets/registry.js.map +1 -0
- package/dist/installer/targets/shared.d.ts +77 -0
- package/dist/installer/targets/shared.d.ts.map +1 -0
- package/dist/installer/targets/shared.js +247 -0
- package/dist/installer/targets/shared.js.map +1 -0
- package/dist/installer/targets/toml.d.ts +52 -0
- package/dist/installer/targets/toml.d.ts.map +1 -0
- package/dist/installer/targets/toml.js +147 -0
- package/dist/installer/targets/toml.js.map +1 -0
- package/dist/installer/targets/types.d.ts +101 -0
- package/dist/installer/targets/types.d.ts.map +1 -0
- package/dist/installer/targets/types.js +16 -0
- package/dist/installer/targets/types.js.map +1 -0
- package/dist/mcp/daemon-paths.d.ts +46 -0
- package/dist/mcp/daemon-paths.d.ts.map +1 -0
- package/dist/mcp/daemon-paths.js +125 -0
- package/dist/mcp/daemon-paths.js.map +1 -0
- package/dist/mcp/daemon.d.ts +220 -0
- package/dist/mcp/daemon.d.ts.map +1 -0
- package/dist/mcp/daemon.js +616 -0
- package/dist/mcp/daemon.js.map +1 -0
- package/dist/mcp/engine.d.ts +105 -0
- package/dist/mcp/engine.d.ts.map +1 -0
- package/dist/mcp/engine.js +270 -0
- package/dist/mcp/engine.js.map +1 -0
- package/dist/mcp/index.d.ts +112 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +478 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/ppid-watchdog.d.ts +44 -0
- package/dist/mcp/ppid-watchdog.d.ts.map +1 -0
- package/dist/mcp/ppid-watchdog.js +27 -0
- package/dist/mcp/ppid-watchdog.js.map +1 -0
- package/dist/mcp/proxy.d.ts +81 -0
- package/dist/mcp/proxy.d.ts.map +1 -0
- package/dist/mcp/proxy.js +584 -0
- package/dist/mcp/proxy.js.map +1 -0
- package/dist/mcp/server-instructions.d.ts +19 -0
- package/dist/mcp/server-instructions.d.ts.map +1 -0
- package/dist/mcp/server-instructions.js +74 -0
- package/dist/mcp/server-instructions.js.map +1 -0
- package/dist/mcp/session.d.ts +77 -0
- package/dist/mcp/session.d.ts.map +1 -0
- package/dist/mcp/session.js +294 -0
- package/dist/mcp/session.js.map +1 -0
- package/dist/mcp/tools.d.ts +413 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +3049 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/transport.d.ts +188 -0
- package/dist/mcp/transport.d.ts.map +1 -0
- package/dist/mcp/transport.js +343 -0
- package/dist/mcp/transport.js.map +1 -0
- package/dist/mcp/version.d.ts +19 -0
- package/dist/mcp/version.d.ts.map +1 -0
- package/dist/mcp/version.js +71 -0
- package/dist/mcp/version.js.map +1 -0
- package/dist/resolution/callback-synthesizer.d.ts +10 -0
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -0
- package/dist/resolution/callback-synthesizer.js +1818 -0
- package/dist/resolution/callback-synthesizer.js.map +1 -0
- package/dist/resolution/frameworks/cargo-workspace.d.ts +18 -0
- package/dist/resolution/frameworks/cargo-workspace.d.ts.map +1 -0
- package/dist/resolution/frameworks/cargo-workspace.js +225 -0
- package/dist/resolution/frameworks/cargo-workspace.js.map +1 -0
- package/dist/resolution/frameworks/csharp.d.ts +8 -0
- package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
- package/dist/resolution/frameworks/csharp.js +241 -0
- package/dist/resolution/frameworks/csharp.js.map +1 -0
- package/dist/resolution/frameworks/drupal.d.ts +51 -0
- package/dist/resolution/frameworks/drupal.d.ts.map +1 -0
- package/dist/resolution/frameworks/drupal.js +367 -0
- package/dist/resolution/frameworks/drupal.js.map +1 -0
- package/dist/resolution/frameworks/expo-modules.d.ts +3 -0
- package/dist/resolution/frameworks/expo-modules.d.ts.map +1 -0
- package/dist/resolution/frameworks/expo-modules.js +148 -0
- package/dist/resolution/frameworks/expo-modules.js.map +1 -0
- package/dist/resolution/frameworks/express.d.ts +8 -0
- package/dist/resolution/frameworks/express.d.ts.map +1 -0
- package/dist/resolution/frameworks/express.js +308 -0
- package/dist/resolution/frameworks/express.js.map +1 -0
- package/dist/resolution/frameworks/fabric.d.ts +3 -0
- package/dist/resolution/frameworks/fabric.d.ts.map +1 -0
- package/dist/resolution/frameworks/fabric.js +354 -0
- package/dist/resolution/frameworks/fabric.js.map +1 -0
- package/dist/resolution/frameworks/go.d.ts +8 -0
- package/dist/resolution/frameworks/go.d.ts.map +1 -0
- package/dist/resolution/frameworks/go.js +161 -0
- package/dist/resolution/frameworks/go.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts +48 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -0
- package/dist/resolution/frameworks/index.js +161 -0
- package/dist/resolution/frameworks/index.js.map +1 -0
- package/dist/resolution/frameworks/java.d.ts +8 -0
- package/dist/resolution/frameworks/java.d.ts.map +1 -0
- package/dist/resolution/frameworks/java.js +504 -0
- package/dist/resolution/frameworks/java.js.map +1 -0
- package/dist/resolution/frameworks/laravel.d.ts +13 -0
- package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
- package/dist/resolution/frameworks/laravel.js +257 -0
- package/dist/resolution/frameworks/laravel.js.map +1 -0
- package/dist/resolution/frameworks/nestjs.d.ts +26 -0
- package/dist/resolution/frameworks/nestjs.d.ts.map +1 -0
- package/dist/resolution/frameworks/nestjs.js +698 -0
- package/dist/resolution/frameworks/nestjs.js.map +1 -0
- package/dist/resolution/frameworks/play.d.ts +19 -0
- package/dist/resolution/frameworks/play.d.ts.map +1 -0
- package/dist/resolution/frameworks/play.js +111 -0
- package/dist/resolution/frameworks/play.js.map +1 -0
- package/dist/resolution/frameworks/python.d.ts +10 -0
- package/dist/resolution/frameworks/python.d.ts.map +1 -0
- package/dist/resolution/frameworks/python.js +400 -0
- package/dist/resolution/frameworks/python.js.map +1 -0
- package/dist/resolution/frameworks/react-native.d.ts +3 -0
- package/dist/resolution/frameworks/react-native.d.ts.map +1 -0
- package/dist/resolution/frameworks/react-native.js +410 -0
- package/dist/resolution/frameworks/react-native.js.map +1 -0
- package/dist/resolution/frameworks/react.d.ts +8 -0
- package/dist/resolution/frameworks/react.d.ts.map +1 -0
- package/dist/resolution/frameworks/react.js +365 -0
- package/dist/resolution/frameworks/react.js.map +1 -0
- package/dist/resolution/frameworks/ruby.d.ts +8 -0
- package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
- package/dist/resolution/frameworks/ruby.js +302 -0
- package/dist/resolution/frameworks/ruby.js.map +1 -0
- package/dist/resolution/frameworks/rust.d.ts +8 -0
- package/dist/resolution/frameworks/rust.d.ts.map +1 -0
- package/dist/resolution/frameworks/rust.js +304 -0
- package/dist/resolution/frameworks/rust.js.map +1 -0
- package/dist/resolution/frameworks/svelte.d.ts +9 -0
- package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
- package/dist/resolution/frameworks/svelte.js +249 -0
- package/dist/resolution/frameworks/svelte.js.map +1 -0
- package/dist/resolution/frameworks/swift-objc.d.ts +37 -0
- package/dist/resolution/frameworks/swift-objc.d.ts.map +1 -0
- package/dist/resolution/frameworks/swift-objc.js +252 -0
- package/dist/resolution/frameworks/swift-objc.js.map +1 -0
- package/dist/resolution/frameworks/swift.d.ts +10 -0
- package/dist/resolution/frameworks/swift.d.ts.map +1 -0
- package/dist/resolution/frameworks/swift.js +400 -0
- package/dist/resolution/frameworks/swift.js.map +1 -0
- package/dist/resolution/frameworks/vue.d.ts +9 -0
- package/dist/resolution/frameworks/vue.d.ts.map +1 -0
- package/dist/resolution/frameworks/vue.js +306 -0
- package/dist/resolution/frameworks/vue.js.map +1 -0
- package/dist/resolution/go-module.d.ts +26 -0
- package/dist/resolution/go-module.d.ts.map +1 -0
- package/dist/resolution/go-module.js +78 -0
- package/dist/resolution/go-module.js.map +1 -0
- package/dist/resolution/import-resolver.d.ts +68 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -0
- package/dist/resolution/import-resolver.js +1726 -0
- package/dist/resolution/import-resolver.js.map +1 -0
- package/dist/resolution/index.d.ts +159 -0
- package/dist/resolution/index.d.ts.map +1 -0
- package/dist/resolution/index.js +1025 -0
- package/dist/resolution/index.js.map +1 -0
- package/dist/resolution/lru-cache.d.ts +24 -0
- package/dist/resolution/lru-cache.d.ts.map +1 -0
- package/dist/resolution/lru-cache.js +62 -0
- package/dist/resolution/lru-cache.js.map +1 -0
- package/dist/resolution/name-matcher.d.ts +52 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -0
- package/dist/resolution/name-matcher.js +707 -0
- package/dist/resolution/name-matcher.js.map +1 -0
- package/dist/resolution/path-aliases.d.ts +68 -0
- package/dist/resolution/path-aliases.d.ts.map +1 -0
- package/dist/resolution/path-aliases.js +238 -0
- package/dist/resolution/path-aliases.js.map +1 -0
- package/dist/resolution/strip-comments.d.ts +27 -0
- package/dist/resolution/strip-comments.d.ts.map +1 -0
- package/dist/resolution/strip-comments.js +441 -0
- package/dist/resolution/strip-comments.js.map +1 -0
- package/dist/resolution/swift-objc-bridge.d.ts +134 -0
- package/dist/resolution/swift-objc-bridge.d.ts.map +1 -0
- package/dist/resolution/swift-objc-bridge.js +256 -0
- package/dist/resolution/swift-objc-bridge.js.map +1 -0
- package/dist/resolution/types.d.ts +216 -0
- package/dist/resolution/types.d.ts.map +1 -0
- package/dist/resolution/types.js +8 -0
- package/dist/resolution/types.js.map +1 -0
- package/dist/resolution/workspace-packages.d.ts +48 -0
- package/dist/resolution/workspace-packages.d.ts.map +1 -0
- package/dist/resolution/workspace-packages.js +208 -0
- package/dist/resolution/workspace-packages.js.map +1 -0
- package/dist/search/query-parser.d.ts +57 -0
- package/dist/search/query-parser.d.ts.map +1 -0
- package/dist/search/query-parser.js +177 -0
- package/dist/search/query-parser.js.map +1 -0
- package/dist/search/query-utils.d.ts +71 -0
- package/dist/search/query-utils.d.ts.map +1 -0
- package/dist/search/query-utils.js +380 -0
- package/dist/search/query-utils.js.map +1 -0
- package/dist/sync/git-hooks.d.ts +45 -0
- package/dist/sync/git-hooks.d.ts.map +1 -0
- package/dist/sync/git-hooks.js +225 -0
- package/dist/sync/git-hooks.js.map +1 -0
- package/dist/sync/index.d.ts +19 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +35 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/watch-policy.d.ts +48 -0
- package/dist/sync/watch-policy.d.ts.map +1 -0
- package/dist/sync/watch-policy.js +124 -0
- package/dist/sync/watch-policy.js.map +1 -0
- package/dist/sync/watcher.d.ts +283 -0
- package/dist/sync/watcher.d.ts.map +1 -0
- package/dist/sync/watcher.js +606 -0
- package/dist/sync/watcher.js.map +1 -0
- package/dist/sync/worktree.d.ts +54 -0
- package/dist/sync/worktree.d.ts.map +1 -0
- package/dist/sync/worktree.js +137 -0
- package/dist/sync/worktree.js.map +1 -0
- package/dist/types.d.ts +377 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +80 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/glyphs.d.ts +42 -0
- package/dist/ui/glyphs.d.ts.map +1 -0
- package/dist/ui/glyphs.js +78 -0
- package/dist/ui/glyphs.js.map +1 -0
- package/dist/ui/shimmer-progress.d.ts +11 -0
- package/dist/ui/shimmer-progress.d.ts.map +1 -0
- package/dist/ui/shimmer-progress.js +90 -0
- package/dist/ui/shimmer-progress.js.map +1 -0
- package/dist/ui/shimmer-worker.d.ts +2 -0
- package/dist/ui/shimmer-worker.d.ts.map +1 -0
- package/dist/ui/shimmer-worker.js +118 -0
- package/dist/ui/shimmer-worker.js.map +1 -0
- package/dist/ui/types.d.ts +17 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +3 -0
- package/dist/ui/types.js.map +1 -0
- package/dist/upgrade/index.d.ts +132 -0
- package/dist/upgrade/index.d.ts.map +1 -0
- package/dist/upgrade/index.js +462 -0
- package/dist/upgrade/index.js.map +1 -0
- package/dist/utils.d.ts +205 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +549 -0
- package/dist/utils.js.map +1 -0
- package/package.json +57 -0
- package/scripts/add-lang/bench.sh +60 -0
- package/scripts/add-lang/check-grammar.mjs +75 -0
- package/scripts/add-lang/dump-ast.mjs +103 -0
- package/scripts/add-lang/verify-extraction.mjs +70 -0
- package/scripts/agent-eval/arms-F.sh +21 -0
- package/scripts/agent-eval/arms-matrix.sh +37 -0
- package/scripts/agent-eval/audit.sh +68 -0
- package/scripts/agent-eval/bench-readme.sh +28 -0
- package/scripts/agent-eval/bench-why-repo.sh +22 -0
- package/scripts/agent-eval/block-read-hook.sh +19 -0
- package/scripts/agent-eval/hook-settings.json +15 -0
- package/scripts/agent-eval/itrun.sh +120 -0
- package/scripts/agent-eval/parse-arms.mjs +116 -0
- package/scripts/agent-eval/parse-bench-readme.mjs +84 -0
- package/scripts/agent-eval/parse-run.mjs +45 -0
- package/scripts/agent-eval/parse-session.mjs +93 -0
- package/scripts/agent-eval/probe-context.mjs +21 -0
- package/scripts/agent-eval/probe-explore.mjs +40 -0
- package/scripts/agent-eval/probe-node.mjs +20 -0
- package/scripts/agent-eval/probe-sweep.mjs +119 -0
- package/scripts/agent-eval/probe-trace.mjs +20 -0
- package/scripts/agent-eval/run-agent.sh +34 -0
- package/scripts/agent-eval/run-all.sh +67 -0
- package/scripts/agent-eval/run-arms.sh +56 -0
- package/scripts/agent-eval/seq-matrix.mjs +137 -0
- package/scripts/build-bundle.sh +118 -0
- package/scripts/extract-release-notes.mjs +130 -0
- package/scripts/local-install.sh +41 -0
- package/scripts/npm-sdk.js +75 -0
- package/scripts/npm-shim.js +246 -0
- package/scripts/pack-npm.sh +119 -0
- package/scripts/prepare-release.mjs +270 -0
|
@@ -0,0 +1,4089 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tree-sitter Parser Wrapper
|
|
4
|
+
*
|
|
5
|
+
* Handles parsing source code and extracting structural information.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.TreeSitterExtractor = exports.generateNodeId = void 0;
|
|
42
|
+
exports.extractFromSource = extractFromSource;
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const grammars_1 = require("./grammars");
|
|
45
|
+
const tree_sitter_helpers_1 = require("./tree-sitter-helpers");
|
|
46
|
+
const languages_1 = require("./languages");
|
|
47
|
+
const liquid_extractor_1 = require("./liquid-extractor");
|
|
48
|
+
const razor_extractor_1 = require("./razor-extractor");
|
|
49
|
+
const svelte_extractor_1 = require("./svelte-extractor");
|
|
50
|
+
const dfm_extractor_1 = require("./dfm-extractor");
|
|
51
|
+
const vue_extractor_1 = require("./vue-extractor");
|
|
52
|
+
const mybatis_extractor_1 = require("./mybatis-extractor");
|
|
53
|
+
const arkts_1 = require("./languages/arkts");
|
|
54
|
+
const frameworks_1 = require("../resolution/frameworks");
|
|
55
|
+
// Re-export for backward compatibility
|
|
56
|
+
var tree_sitter_helpers_2 = require("./tree-sitter-helpers");
|
|
57
|
+
Object.defineProperty(exports, "generateNodeId", { enumerable: true, get: function () { return tree_sitter_helpers_2.generateNodeId; } });
|
|
58
|
+
/**
|
|
59
|
+
* Extract the name from a node based on language
|
|
60
|
+
*/
|
|
61
|
+
function extractName(node, source, extractor) {
|
|
62
|
+
const hookName = extractor.resolveName?.(node, source);
|
|
63
|
+
if (hookName)
|
|
64
|
+
return hookName;
|
|
65
|
+
// Try field name first
|
|
66
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, extractor.nameField);
|
|
67
|
+
if (nameNode) {
|
|
68
|
+
// Unwrap pointer_declarator(s) for C/C++ pointer return types
|
|
69
|
+
let resolved = nameNode;
|
|
70
|
+
while (resolved.type === 'pointer_declarator') {
|
|
71
|
+
const inner = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'declarator') || resolved.namedChild(0);
|
|
72
|
+
if (!inner)
|
|
73
|
+
break;
|
|
74
|
+
resolved = inner;
|
|
75
|
+
}
|
|
76
|
+
// Handle complex declarators (C/C++)
|
|
77
|
+
if (resolved.type === 'function_declarator' || resolved.type === 'declarator') {
|
|
78
|
+
const innerName = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'declarator') || resolved.namedChild(0);
|
|
79
|
+
return innerName ? (0, tree_sitter_helpers_1.getNodeText)(innerName, source) : (0, tree_sitter_helpers_1.getNodeText)(resolved, source);
|
|
80
|
+
}
|
|
81
|
+
// Lua: `function t.f()` / `function t:m()` — the name node is a dot/method
|
|
82
|
+
// index expression; the simple name is the trailing field/method (the table
|
|
83
|
+
// receiver is captured separately via getReceiverType).
|
|
84
|
+
if (resolved.type === 'dot_index_expression') {
|
|
85
|
+
const field = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'field');
|
|
86
|
+
if (field)
|
|
87
|
+
return (0, tree_sitter_helpers_1.getNodeText)(field, source);
|
|
88
|
+
}
|
|
89
|
+
if (resolved.type === 'method_index_expression') {
|
|
90
|
+
const method = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'method');
|
|
91
|
+
if (method)
|
|
92
|
+
return (0, tree_sitter_helpers_1.getNodeText)(method, source);
|
|
93
|
+
}
|
|
94
|
+
return (0, tree_sitter_helpers_1.getNodeText)(resolved, source);
|
|
95
|
+
}
|
|
96
|
+
// For Dart method_signature, look inside inner signature types
|
|
97
|
+
if (node.type === 'method_signature') {
|
|
98
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
99
|
+
const child = node.namedChild(i);
|
|
100
|
+
if (child && (child.type === 'function_signature' ||
|
|
101
|
+
child.type === 'getter_signature' ||
|
|
102
|
+
child.type === 'setter_signature' ||
|
|
103
|
+
child.type === 'constructor_signature' ||
|
|
104
|
+
child.type === 'factory_constructor_signature')) {
|
|
105
|
+
// Find identifier inside the inner signature
|
|
106
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
107
|
+
const inner = child.namedChild(j);
|
|
108
|
+
if (inner?.type === 'identifier') {
|
|
109
|
+
return (0, tree_sitter_helpers_1.getNodeText)(inner, source);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Arrow/function expressions get their name from the parent variable_declarator,
|
|
116
|
+
// not from identifiers in their body. Without this, single-expression arrow
|
|
117
|
+
// functions like `const fn = () => someIdentifier` get named "someIdentifier"
|
|
118
|
+
// instead of "fn", because the fallback below finds the body identifier.
|
|
119
|
+
if (node.type === 'arrow_function' || node.type === 'function_expression') {
|
|
120
|
+
return '<anonymous>';
|
|
121
|
+
}
|
|
122
|
+
// Fall back to first identifier child
|
|
123
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
124
|
+
const child = node.namedChild(i);
|
|
125
|
+
if (child &&
|
|
126
|
+
(child.type === 'identifier' ||
|
|
127
|
+
child.type === 'type_identifier' ||
|
|
128
|
+
child.type === 'simple_identifier' ||
|
|
129
|
+
child.type === 'constant')) {
|
|
130
|
+
return (0, tree_sitter_helpers_1.getNodeText)(child, source);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return '<anonymous>';
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Resolve a Scala type node to its base type NAME for name-matching — unwrapping
|
|
137
|
+
* `generic_type` (`Monoid[Int]` → `Monoid`), taking the last segment of a
|
|
138
|
+
* qualified `stable_type_identifier` (`cats.Functor` → `Functor`), and falling
|
|
139
|
+
* back to a descendant `type_identifier`. Returns null for non-type nodes.
|
|
140
|
+
* Shared by Scala inheritance and type-reference extraction.
|
|
141
|
+
*/
|
|
142
|
+
function scalaBaseTypeName(node, source) {
|
|
143
|
+
if (!node)
|
|
144
|
+
return null;
|
|
145
|
+
switch (node.type) {
|
|
146
|
+
case 'type_identifier':
|
|
147
|
+
case 'identifier':
|
|
148
|
+
return (0, tree_sitter_helpers_1.getNodeText)(node, source);
|
|
149
|
+
case 'generic_type':
|
|
150
|
+
// `<base> type_arguments` — the base type is the first named child.
|
|
151
|
+
return scalaBaseTypeName(node.namedChild(0), source);
|
|
152
|
+
case 'stable_type_identifier':
|
|
153
|
+
case 'stable_identifier': {
|
|
154
|
+
// Qualified `a.b.C` — match on the simple (last) segment.
|
|
155
|
+
const ids = node.namedChildren.filter((c) => c.type === 'type_identifier' || c.type === 'identifier');
|
|
156
|
+
const last = ids[ids.length - 1];
|
|
157
|
+
return last ? (0, tree_sitter_helpers_1.getNodeText)(last, source) : null;
|
|
158
|
+
}
|
|
159
|
+
default: {
|
|
160
|
+
const id = node.namedChildren.find((c) => c.type === 'type_identifier');
|
|
161
|
+
return id ? (0, tree_sitter_helpers_1.getNodeText)(id, source) : null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* PHP type-position wrapper node kinds (a type-hint is `named_type`,
|
|
167
|
+
* `?Foo` is `optional_type`, `A|B` is `union_type`, `A&B` is
|
|
168
|
+
* `intersection_type`). Used to find the type subtree inside a parameter /
|
|
169
|
+
* property / return position before walking it for class references.
|
|
170
|
+
*/
|
|
171
|
+
const PHP_TYPE_NODES = new Set([
|
|
172
|
+
'named_type', 'optional_type', 'nullable_type',
|
|
173
|
+
'union_type', 'intersection_type', 'disjunctive_normal_form_type',
|
|
174
|
+
'primitive_type',
|
|
175
|
+
]);
|
|
176
|
+
/**
|
|
177
|
+
* Member-access node kinds whose receiver, when it's a capitalized
|
|
178
|
+
* type/enum/class name, is a real dependency — `Enum.value`, `Type.CONST`,
|
|
179
|
+
* `Foo::BAR`. These VALUE reads (as opposed to `Type.method()` calls, already
|
|
180
|
+
* handled) produced no edge, so a type used only via a static member or enum
|
|
181
|
+
* value looked like nothing depended on it. See {@link extractStaticMemberRef}.
|
|
182
|
+
*/
|
|
183
|
+
const MEMBER_ACCESS_TYPES = new Set([
|
|
184
|
+
'field_access', // java (`Foo.BAR`)
|
|
185
|
+
'member_access_expression', // c# (`Foo.Bar`)
|
|
186
|
+
'navigation_expression', // kotlin / swift (`Foo.bar`)
|
|
187
|
+
'field_expression', // scala (`Foo.bar`)
|
|
188
|
+
'class_constant_access_expression', // php (`Foo::CONST`, `Foo::class`)
|
|
189
|
+
'scoped_property_access_expression', // php (`Foo::$bar`)
|
|
190
|
+
'qualified_identifier', // c++ (`Foo::bar`)
|
|
191
|
+
]);
|
|
192
|
+
/**
|
|
193
|
+
* Languages whose types are Capitalized by convention, so a capitalized
|
|
194
|
+
* member-access receiver is reliably a type (not a local/variable). The
|
|
195
|
+
* static-member/value-read pass is gated to these — the ones where it was the
|
|
196
|
+
* confirmed residual frontier (enum-value / static-field reads). TS/JS/Python
|
|
197
|
+
* are deliberately excluded, and a measured A/B confirms the call: extending the
|
|
198
|
+
* pass to them adds ZERO coverage — in import-based languages you must `import` a
|
|
199
|
+
* type before any `Type.MEMBER` read, so the import edge already covers it (the
|
|
200
|
+
* static read is pure duplication) — while adding real graph noise (+1813 edges /
|
|
201
|
+
* +2448 `references` on excalidraw, the retrieval-perf benchmark, all pointing at
|
|
202
|
+
* already-covered types). Don't re-add `member_expression`/`attribute` here.
|
|
203
|
+
*/
|
|
204
|
+
const STATIC_MEMBER_LANGS = new Set([
|
|
205
|
+
'java', 'csharp', 'kotlin', 'swift', 'scala', 'dart', 'php', 'cpp',
|
|
206
|
+
]);
|
|
207
|
+
/**
|
|
208
|
+
* Tree-sitter node kinds that represent constructor invocations
|
|
209
|
+
* (`new Foo()` and friends). Used by extractInstantiation to emit
|
|
210
|
+
* an `instantiates` reference targeting the class name.
|
|
211
|
+
*/
|
|
212
|
+
const INSTANTIATION_KINDS = new Set([
|
|
213
|
+
'new_expression', // typescript / javascript / tsx / jsx
|
|
214
|
+
'object_creation_expression', // java / c#
|
|
215
|
+
'instance_creation_expression', // some grammars
|
|
216
|
+
'composite_literal', // go — `Widget{...}` / `pkga.Widget{...}`
|
|
217
|
+
'struct_expression', // rust — `Widget { n: 1 }` / `m::Widget { .. }`
|
|
218
|
+
'instance_expression', // scala — `new Monoid[Int] { ... }`
|
|
219
|
+
]);
|
|
220
|
+
/**
|
|
221
|
+
* TreeSitterExtractor - Main extraction class
|
|
222
|
+
*/
|
|
223
|
+
class TreeSitterExtractor {
|
|
224
|
+
filePath;
|
|
225
|
+
language;
|
|
226
|
+
source;
|
|
227
|
+
tree = null;
|
|
228
|
+
nodes = [];
|
|
229
|
+
edges = [];
|
|
230
|
+
unresolvedReferences = [];
|
|
231
|
+
errors = [];
|
|
232
|
+
extractor = null;
|
|
233
|
+
nodeStack = []; // Stack of parent node IDs
|
|
234
|
+
methodIndex = null; // lookup key → node ID for Pascal defProc lookup
|
|
235
|
+
constructor(filePath, source, language) {
|
|
236
|
+
this.filePath = filePath;
|
|
237
|
+
this.source = source;
|
|
238
|
+
this.language = language || (0, grammars_1.detectLanguage)(filePath, source);
|
|
239
|
+
this.extractor = languages_1.EXTRACTORS[this.language] || null;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Parse and extract from the source code
|
|
243
|
+
*/
|
|
244
|
+
extract() {
|
|
245
|
+
const startTime = Date.now();
|
|
246
|
+
if (!(0, grammars_1.isLanguageSupported)(this.language)) {
|
|
247
|
+
return {
|
|
248
|
+
nodes: [],
|
|
249
|
+
edges: [],
|
|
250
|
+
unresolvedReferences: [],
|
|
251
|
+
errors: [
|
|
252
|
+
{
|
|
253
|
+
message: `Unsupported language: ${this.language}`,
|
|
254
|
+
filePath: this.filePath,
|
|
255
|
+
severity: 'error',
|
|
256
|
+
code: 'unsupported_language',
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
durationMs: Date.now() - startTime,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const parser = (0, grammars_1.getParser)(this.language);
|
|
263
|
+
if (!parser) {
|
|
264
|
+
return {
|
|
265
|
+
nodes: [],
|
|
266
|
+
edges: [],
|
|
267
|
+
unresolvedReferences: [],
|
|
268
|
+
errors: [
|
|
269
|
+
{
|
|
270
|
+
message: `Failed to get parser for language: ${this.language}`,
|
|
271
|
+
filePath: this.filePath,
|
|
272
|
+
severity: 'error',
|
|
273
|
+
code: 'parser_error',
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
durationMs: Date.now() - startTime,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
// Optional pre-parse source transform (offset-preserving) to work around
|
|
281
|
+
// grammar gaps — e.g. C# blanks conditional-compilation directive lines
|
|
282
|
+
// the grammar mis-parses inside enum bodies (#237). We reassign
|
|
283
|
+
// this.source so downstream getNodeText reads the same bytes the parser
|
|
284
|
+
// saw (identical outside the blanked directive lines).
|
|
285
|
+
if (this.extractor?.preParse) {
|
|
286
|
+
this.source = this.extractor.preParse(this.source);
|
|
287
|
+
}
|
|
288
|
+
this.tree = parser.parse(this.source) ?? null;
|
|
289
|
+
if (!this.tree) {
|
|
290
|
+
throw new Error('Parser returned null tree');
|
|
291
|
+
}
|
|
292
|
+
// Create file node representing the source file
|
|
293
|
+
const fileNode = {
|
|
294
|
+
id: `file:${this.filePath}`,
|
|
295
|
+
kind: 'file',
|
|
296
|
+
name: path.basename(this.filePath),
|
|
297
|
+
qualifiedName: this.filePath,
|
|
298
|
+
filePath: this.filePath,
|
|
299
|
+
language: this.language,
|
|
300
|
+
startLine: 1,
|
|
301
|
+
endLine: this.source.split('\n').length,
|
|
302
|
+
startColumn: 0,
|
|
303
|
+
endColumn: 0,
|
|
304
|
+
isExported: false,
|
|
305
|
+
updatedAt: Date.now(),
|
|
306
|
+
};
|
|
307
|
+
this.nodes.push(fileNode);
|
|
308
|
+
// Push file node onto stack so top-level declarations get contains edges
|
|
309
|
+
this.nodeStack.push(fileNode.id);
|
|
310
|
+
// File-level package declaration (Kotlin/Java). Creates an implicit
|
|
311
|
+
// `namespace` node wrapping every top-level declaration so their
|
|
312
|
+
// qualifiedName carries the FQN — required for cross-file import
|
|
313
|
+
// resolution on JVM languages where filename ≠ class name.
|
|
314
|
+
const packageNodeId = this.extractFilePackage(this.tree.rootNode);
|
|
315
|
+
if (packageNodeId)
|
|
316
|
+
this.nodeStack.push(packageNodeId);
|
|
317
|
+
this.visitNode(this.tree.rootNode);
|
|
318
|
+
if (packageNodeId)
|
|
319
|
+
this.nodeStack.pop();
|
|
320
|
+
this.nodeStack.pop();
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
324
|
+
// WASM memory errors leave the module in a corrupted state — all subsequent
|
|
325
|
+
// parses would also fail. Re-throw so the worker can detect and crash,
|
|
326
|
+
// forcing a clean restart with a fresh heap.
|
|
327
|
+
if (msg.includes('memory access out of bounds') || msg.includes('out of memory')) {
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
this.errors.push({
|
|
331
|
+
message: `Parse error: ${msg}`,
|
|
332
|
+
filePath: this.filePath,
|
|
333
|
+
severity: 'error',
|
|
334
|
+
code: 'parse_error',
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
// Free tree-sitter WASM memory immediately — trees hold native heap memory
|
|
339
|
+
// invisible to V8's GC that accumulates across thousands of files.
|
|
340
|
+
if (this.tree) {
|
|
341
|
+
this.tree.delete();
|
|
342
|
+
this.tree = null;
|
|
343
|
+
}
|
|
344
|
+
// Release source string to reduce GC pressure
|
|
345
|
+
this.source = '';
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
nodes: this.nodes,
|
|
349
|
+
edges: this.edges,
|
|
350
|
+
unresolvedReferences: this.unresolvedReferences,
|
|
351
|
+
errors: this.errors,
|
|
352
|
+
durationMs: Date.now() - startTime,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Visit a node and extract information
|
|
357
|
+
*/
|
|
358
|
+
visitNode(node) {
|
|
359
|
+
if (!this.extractor)
|
|
360
|
+
return;
|
|
361
|
+
const nodeType = node.type;
|
|
362
|
+
let skipChildren = false;
|
|
363
|
+
// Language-specific custom visitor hook
|
|
364
|
+
if (this.extractor.visitNode) {
|
|
365
|
+
const ctx = this.makeExtractorContext();
|
|
366
|
+
const handled = this.extractor.visitNode(node, ctx);
|
|
367
|
+
if (handled)
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
// Pascal-specific AST handling
|
|
371
|
+
if (this.language === 'pascal') {
|
|
372
|
+
skipChildren = this.visitPascalNode(node);
|
|
373
|
+
if (skipChildren)
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
// Check for function declarations
|
|
377
|
+
// For Python/Ruby, function_definition inside a class should be treated as method
|
|
378
|
+
if (this.extractor.functionTypes.includes(nodeType)) {
|
|
379
|
+
if (this.isInsideClassLikeNode() && this.extractor.methodTypes.includes(nodeType)) {
|
|
380
|
+
// Inside a class - treat as method
|
|
381
|
+
this.extractMethod(node);
|
|
382
|
+
skipChildren = true; // extractMethod visits children via visitFunctionBody
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
this.extractFunction(node);
|
|
386
|
+
skipChildren = true; // extractFunction visits children via visitFunctionBody
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// Check for class declarations
|
|
390
|
+
else if (this.extractor.classTypes.includes(nodeType)) {
|
|
391
|
+
// Some languages reuse class_declaration for structs/enums (e.g. Swift)
|
|
392
|
+
const classification = this.extractor.classifyClassNode?.(node) ?? 'class';
|
|
393
|
+
if (classification === 'struct') {
|
|
394
|
+
this.extractStruct(node);
|
|
395
|
+
}
|
|
396
|
+
else if (classification === 'enum') {
|
|
397
|
+
this.extractEnum(node);
|
|
398
|
+
}
|
|
399
|
+
else if (classification === 'interface') {
|
|
400
|
+
this.extractInterface(node);
|
|
401
|
+
}
|
|
402
|
+
else if (classification === 'trait') {
|
|
403
|
+
this.extractClass(node, 'trait');
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
this.extractClass(node);
|
|
407
|
+
}
|
|
408
|
+
skipChildren = true; // extractClass visits body children
|
|
409
|
+
}
|
|
410
|
+
// Extra class node types (e.g. Dart mixin_declaration, extension_declaration)
|
|
411
|
+
else if (this.extractor.extraClassNodeTypes?.includes(nodeType)) {
|
|
412
|
+
this.extractClass(node);
|
|
413
|
+
skipChildren = true;
|
|
414
|
+
}
|
|
415
|
+
// Check for method declarations (only if not already handled by functionTypes)
|
|
416
|
+
else if (this.extractor.methodTypes.includes(nodeType)) {
|
|
417
|
+
this.extractMethod(node);
|
|
418
|
+
skipChildren = true; // extractMethod visits children via visitFunctionBody
|
|
419
|
+
}
|
|
420
|
+
// Check for interface/protocol/trait declarations
|
|
421
|
+
else if (this.extractor.interfaceTypes.includes(nodeType)) {
|
|
422
|
+
this.extractInterface(node);
|
|
423
|
+
skipChildren = true; // extractInterface visits body children
|
|
424
|
+
}
|
|
425
|
+
// Check for struct declarations
|
|
426
|
+
else if (this.extractor.structTypes.includes(nodeType)) {
|
|
427
|
+
this.extractStruct(node);
|
|
428
|
+
skipChildren = true; // extractStruct visits body children
|
|
429
|
+
}
|
|
430
|
+
// Check for enum declarations
|
|
431
|
+
else if (this.extractor.enumTypes.includes(nodeType)) {
|
|
432
|
+
this.extractEnum(node);
|
|
433
|
+
skipChildren = true; // extractEnum visits body children
|
|
434
|
+
}
|
|
435
|
+
// Check for type alias declarations (e.g. `type X = ...` in TypeScript)
|
|
436
|
+
// For Go, type_spec wraps struct/interface definitions — resolveTypeAliasKind
|
|
437
|
+
// detects these and extractTypeAlias creates the correct node kind.
|
|
438
|
+
else if (this.extractor.typeAliasTypes.includes(nodeType)) {
|
|
439
|
+
skipChildren = this.extractTypeAlias(node);
|
|
440
|
+
}
|
|
441
|
+
// Check for class properties (e.g. C# property_declaration)
|
|
442
|
+
else if (this.extractor.propertyTypes?.includes(nodeType) && this.isInsideClassLikeNode()) {
|
|
443
|
+
this.extractProperty(node);
|
|
444
|
+
skipChildren = true;
|
|
445
|
+
}
|
|
446
|
+
// Check for class fields (e.g. Java field_declaration, C# field_declaration)
|
|
447
|
+
else if (this.extractor.fieldTypes?.includes(nodeType) && this.isInsideClassLikeNode()) {
|
|
448
|
+
this.extractField(node);
|
|
449
|
+
skipChildren = true;
|
|
450
|
+
}
|
|
451
|
+
// Check for variable declarations (const, let, var, etc.)
|
|
452
|
+
// Only extract top-level variables (not inside functions/methods)
|
|
453
|
+
else if (this.extractor.variableTypes.includes(nodeType) && !this.isInsideClassLikeNode()) {
|
|
454
|
+
this.extractVariable(node);
|
|
455
|
+
skipChildren = true; // extractVariable handles children
|
|
456
|
+
}
|
|
457
|
+
// Swift stored properties inside a type. Swift instance properties aren't
|
|
458
|
+
// extracted as their own nodes, but a property's PROPERTY WRAPPER
|
|
459
|
+
// (`@Argument`/`@Published`/`@State`/custom) and declared type ARE
|
|
460
|
+
// dependencies — attribute them to the enclosing type so the wrapper/type
|
|
461
|
+
// files get dependents. Don't skipChildren: an initializer's calls still
|
|
462
|
+
// matter. (Other languages extract properties via property/field types.)
|
|
463
|
+
else if (this.language === 'swift' &&
|
|
464
|
+
nodeType === 'property_declaration' &&
|
|
465
|
+
this.isInsideClassLikeNode()) {
|
|
466
|
+
const ownerId = this.nodeStack[this.nodeStack.length - 1];
|
|
467
|
+
if (ownerId) {
|
|
468
|
+
this.extractDecoratorsFor(node, ownerId);
|
|
469
|
+
this.extractVariableTypeAnnotation(node, ownerId);
|
|
470
|
+
// Fluent / SwiftUI property-wrapper attributes often reference a model or
|
|
471
|
+
// type by metatype in their ARGUMENTS — `@Siblings(through: Pivot.self,
|
|
472
|
+
// …)`, `@Group(…)`. extractDecoratorsFor captures the wrapper type
|
|
473
|
+
// (`Siblings`); this pulls the TYPE out of the argument expressions
|
|
474
|
+
// (`Pivot.self` → a dependency on Pivot), so a model reached ONLY through
|
|
475
|
+
// a relationship (a many-to-many pivot/join model) isn't left orphaned.
|
|
476
|
+
// extractStaticMemberRef self-filters to `Type.member` navigation, so the
|
|
477
|
+
// `\.$keypath` arguments and the wrapper `user_type` are skipped.
|
|
478
|
+
const modifiers = node.namedChildren.find((c) => c.type === 'modifiers');
|
|
479
|
+
if (modifiers) {
|
|
480
|
+
const walkAttrArgs = (n) => {
|
|
481
|
+
this.extractStaticMemberRef(n);
|
|
482
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
483
|
+
const c = n.namedChild(i);
|
|
484
|
+
if (c)
|
|
485
|
+
walkAttrArgs(c);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
walkAttrArgs(modifiers);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// `export_statement` itself is not extracted — the walker descends
|
|
493
|
+
// into children, where the inner declaration (lexical_declaration,
|
|
494
|
+
// function_declaration, class_declaration, etc.) is dispatched to
|
|
495
|
+
// its own extractor. `isExported` walks the parent chain, so the
|
|
496
|
+
// exported flag is preserved automatically.
|
|
497
|
+
//
|
|
498
|
+
// Calling extractExportedVariables here AND descending caused every
|
|
499
|
+
// `export const X = ...` to produce two nodes for the same symbol —
|
|
500
|
+
// one kind:'variable' from extractExportedVariables and one
|
|
501
|
+
// kind:'constant' from extractVariable. The dedicated dispatch is
|
|
502
|
+
// the correct one (it picks kind from isConst, captures the
|
|
503
|
+
// initializer signature, and walks type annotations); the
|
|
504
|
+
// export-statement helper was redundant.
|
|
505
|
+
// Check for imports
|
|
506
|
+
else if (this.extractor.importTypes.includes(nodeType)) {
|
|
507
|
+
this.extractImport(node);
|
|
508
|
+
}
|
|
509
|
+
// Re-export from another module — `export { X } from './y'` (TS/JS). A
|
|
510
|
+
// re-export is a dependency on the source module just like an import, but
|
|
511
|
+
// the export_statement is otherwise only descended into (no declaration to
|
|
512
|
+
// extract), so a barrel that ONLY re-exports produced zero edges and showed
|
|
513
|
+
// 0 dependents. Link each re-exported name to its definition. Children are
|
|
514
|
+
// still visited (a non-re-export `export const X = …` has no `source` and
|
|
515
|
+
// falls through to its normal declaration extraction).
|
|
516
|
+
else if (nodeType === 'export_statement' &&
|
|
517
|
+
(this.language === 'typescript' || this.language === 'tsx' ||
|
|
518
|
+
this.language === 'javascript' || this.language === 'jsx') &&
|
|
519
|
+
(0, tree_sitter_helpers_1.getChildByField)(node, 'source')) {
|
|
520
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
521
|
+
if (parentId)
|
|
522
|
+
this.emitReExportRefs(node, parentId);
|
|
523
|
+
}
|
|
524
|
+
// Check for function calls
|
|
525
|
+
else if (this.extractor.callTypes.includes(nodeType)) {
|
|
526
|
+
this.extractCall(node);
|
|
527
|
+
}
|
|
528
|
+
// `new Foo(...)` / `Foo::new(...)` / object_creation_expression —
|
|
529
|
+
// produce an `instantiates` reference. Children still walked so
|
|
530
|
+
// nested calls inside the constructor args (`new Foo(bar())`) get
|
|
531
|
+
// their own `calls` refs.
|
|
532
|
+
else if (INSTANTIATION_KINDS.has(nodeType)) {
|
|
533
|
+
this.extractInstantiation(node);
|
|
534
|
+
// Java/C# `new T(...) { ... }` — anonymous class with body. Without
|
|
535
|
+
// extracting it as a class node + its methods, the interface→impl
|
|
536
|
+
// synthesizer (Phase 5.5) can't bridge T's abstract methods to the
|
|
537
|
+
// anonymous overrides, and an agent investigating a call through T
|
|
538
|
+
// (`strategy.iterator(...)` where strategy is a Strategy lambda body)
|
|
539
|
+
// has to Read the file to find the actual implementation.
|
|
540
|
+
const anonBody = this.findAnonymousClassBody(node);
|
|
541
|
+
if (anonBody) {
|
|
542
|
+
this.extractAnonymousClass(node, anonBody);
|
|
543
|
+
skipChildren = true;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// (Decorator handling lives inside the symbol-creating extractors
|
|
547
|
+
// — extractClass / extractFunction / extractProperty — because the
|
|
548
|
+
// decorator node sits BEFORE the symbol in the AST and the walker
|
|
549
|
+
// would otherwise see the wrong nodeStack head.)
|
|
550
|
+
// Rust: `impl Trait for Type { ... }` — creates implements edge from Type to Trait
|
|
551
|
+
else if (nodeType === 'impl_item') {
|
|
552
|
+
this.extractRustImplItem(node);
|
|
553
|
+
}
|
|
554
|
+
// TypeScript interface members: property_signature (`foo: T`, `foo?: T`)
|
|
555
|
+
// and method_signature (`foo(arg: A): R`) both carry type annotations the
|
|
556
|
+
// interface walker would otherwise drop. Extract them as `references`
|
|
557
|
+
// edges from the interface so resolvers can wire callers/impact for
|
|
558
|
+
// types that only appear in interface members.
|
|
559
|
+
else if ((nodeType === 'property_signature' || nodeType === 'method_signature') &&
|
|
560
|
+
this.isInsideClassLikeNode() &&
|
|
561
|
+
this.TYPE_ANNOTATION_LANGUAGES.has(this.language)) {
|
|
562
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
563
|
+
if (parentId) {
|
|
564
|
+
this.extractTypeAnnotations(node, parentId);
|
|
565
|
+
}
|
|
566
|
+
// don't skipChildren — nested signatures still need traversal
|
|
567
|
+
}
|
|
568
|
+
// Visit children (unless the extract method already visited them)
|
|
569
|
+
if (!skipChildren) {
|
|
570
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
571
|
+
const child = node.namedChild(i);
|
|
572
|
+
if (child) {
|
|
573
|
+
this.visitNode(child);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Create a Node object
|
|
580
|
+
*/
|
|
581
|
+
createNode(kind, name, node, extra) {
|
|
582
|
+
// Skip nodes with empty/missing names — they are not meaningful symbols
|
|
583
|
+
// and would cause FK violations when edges reference them (see issue #42)
|
|
584
|
+
if (!name) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
const id = (0, tree_sitter_helpers_1.generateNodeId)(this.filePath, kind, name, node.startPosition.row + 1);
|
|
588
|
+
// Some grammars (e.g. Dart) model a function/method body as a *sibling* of
|
|
589
|
+
// the signature node, so the declaration node's own range is just the
|
|
590
|
+
// signature line. Extend endLine to the resolved body when it sits beyond
|
|
591
|
+
// the node so the node spans its body — required for any body-level analysis
|
|
592
|
+
// (callees, the callback synthesizer's body scan, context slices). Guarded to
|
|
593
|
+
// only ever extend: for child-body grammars the body is within range (no-op).
|
|
594
|
+
let endLine = node.endPosition.row + 1;
|
|
595
|
+
if (kind === 'function' || kind === 'method') {
|
|
596
|
+
const body = this.extractor?.resolveBody?.(node, this.extractor.bodyField);
|
|
597
|
+
if (body && body.endPosition.row + 1 > endLine) {
|
|
598
|
+
endLine = body.endPosition.row + 1;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const newNode = {
|
|
602
|
+
id,
|
|
603
|
+
kind,
|
|
604
|
+
name,
|
|
605
|
+
qualifiedName: this.buildQualifiedName(name),
|
|
606
|
+
filePath: this.filePath,
|
|
607
|
+
language: this.language,
|
|
608
|
+
startLine: node.startPosition.row + 1,
|
|
609
|
+
endLine,
|
|
610
|
+
startColumn: node.startPosition.column,
|
|
611
|
+
endColumn: node.endPosition.column,
|
|
612
|
+
updatedAt: Date.now(),
|
|
613
|
+
...extra,
|
|
614
|
+
};
|
|
615
|
+
// Persist extra symbol-level modifiers (e.g. Kotlin `expect`/`actual`) onto
|
|
616
|
+
// the node's decorators list so the resolver can pair multiplatform
|
|
617
|
+
// declarations with their implementations. Merged, not overwritten, so a
|
|
618
|
+
// language that also captures real annotations keeps both.
|
|
619
|
+
const mods = this.extractor?.extractModifiers?.(node);
|
|
620
|
+
if (mods && mods.length > 0) {
|
|
621
|
+
newNode.decorators = [...(newNode.decorators ?? []), ...mods];
|
|
622
|
+
}
|
|
623
|
+
this.nodes.push(newNode);
|
|
624
|
+
// Add containment edge from parent
|
|
625
|
+
if (this.nodeStack.length > 0) {
|
|
626
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
627
|
+
if (parentId) {
|
|
628
|
+
this.edges.push({
|
|
629
|
+
source: parentId,
|
|
630
|
+
target: id,
|
|
631
|
+
kind: 'contains',
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return newNode;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Find first named child whose type is in the given list.
|
|
639
|
+
* Used to locate inner type nodes (e.g. enum_specifier inside a typedef).
|
|
640
|
+
*/
|
|
641
|
+
findChildByTypes(node, types) {
|
|
642
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
643
|
+
const child = node.namedChild(i);
|
|
644
|
+
if (child && types.includes(child.type))
|
|
645
|
+
return child;
|
|
646
|
+
}
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Find a `packageTypes` child under the root, create a `namespace` node
|
|
651
|
+
* for it, and return its id so the caller can scope top-level
|
|
652
|
+
* declarations underneath. Returns null when no package header is
|
|
653
|
+
* present (script files, .kts without a package).
|
|
654
|
+
*/
|
|
655
|
+
extractFilePackage(rootNode) {
|
|
656
|
+
const types = this.extractor?.packageTypes;
|
|
657
|
+
if (!types || types.length === 0 || !this.extractor?.extractPackage)
|
|
658
|
+
return null;
|
|
659
|
+
let pkgNode = null;
|
|
660
|
+
for (let i = 0; i < rootNode.namedChildCount; i++) {
|
|
661
|
+
const child = rootNode.namedChild(i);
|
|
662
|
+
if (child && types.includes(child.type)) {
|
|
663
|
+
pkgNode = child;
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (!pkgNode)
|
|
668
|
+
return null;
|
|
669
|
+
const pkgName = this.extractor.extractPackage(pkgNode, this.source);
|
|
670
|
+
if (!pkgName)
|
|
671
|
+
return null;
|
|
672
|
+
const ns = this.createNode('namespace', pkgName, pkgNode);
|
|
673
|
+
return ns?.id ?? null;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Build qualified name from node stack
|
|
677
|
+
*/
|
|
678
|
+
buildQualifiedName(name) {
|
|
679
|
+
// Build a qualified name from the semantic hierarchy only (no file path).
|
|
680
|
+
// The file path is stored separately in filePath and pollutes FTS if included here.
|
|
681
|
+
const parts = [];
|
|
682
|
+
for (const nodeId of this.nodeStack) {
|
|
683
|
+
const node = this.nodes.find((n) => n.id === nodeId);
|
|
684
|
+
if (node && node.kind !== 'file') {
|
|
685
|
+
parts.push(node.name);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
parts.push(name);
|
|
689
|
+
return parts.join('::');
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Build an ExtractorContext for passing to language-specific visitNode hooks.
|
|
693
|
+
*/
|
|
694
|
+
makeExtractorContext() {
|
|
695
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
696
|
+
const self = this;
|
|
697
|
+
return {
|
|
698
|
+
createNode: (kind, name, node, extra) => self.createNode(kind, name, node, extra),
|
|
699
|
+
visitNode: (node) => self.visitNode(node),
|
|
700
|
+
visitFunctionBody: (body, functionId) => self.visitFunctionBody(body, functionId),
|
|
701
|
+
addUnresolvedReference: (ref) => self.unresolvedReferences.push(ref),
|
|
702
|
+
pushScope: (nodeId) => self.nodeStack.push(nodeId),
|
|
703
|
+
popScope: () => self.nodeStack.pop(),
|
|
704
|
+
get filePath() { return self.filePath; },
|
|
705
|
+
get source() { return self.source; },
|
|
706
|
+
get nodeStack() { return self.nodeStack; },
|
|
707
|
+
get nodes() { return self.nodes; },
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Check if the current node stack indicates we are inside a class-like node
|
|
712
|
+
* (class, struct, interface, trait). File nodes do not count as class-like.
|
|
713
|
+
*/
|
|
714
|
+
isInsideClassLikeNode() {
|
|
715
|
+
if (this.nodeStack.length === 0)
|
|
716
|
+
return false;
|
|
717
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
718
|
+
if (!parentId)
|
|
719
|
+
return false;
|
|
720
|
+
const parentNode = this.nodes.find((n) => n.id === parentId);
|
|
721
|
+
if (!parentNode)
|
|
722
|
+
return false;
|
|
723
|
+
return (parentNode.kind === 'class' ||
|
|
724
|
+
parentNode.kind === 'struct' ||
|
|
725
|
+
parentNode.kind === 'interface' ||
|
|
726
|
+
parentNode.kind === 'trait' ||
|
|
727
|
+
parentNode.kind === 'enum' ||
|
|
728
|
+
parentNode.kind === 'module');
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Extract a function
|
|
732
|
+
*/
|
|
733
|
+
extractFunction(node, nameOverride) {
|
|
734
|
+
if (!this.extractor)
|
|
735
|
+
return;
|
|
736
|
+
// If the language provides getReceiverType and this function has a receiver
|
|
737
|
+
// (e.g., Rust function_item inside an impl block), extract as method instead
|
|
738
|
+
if (this.extractor.getReceiverType?.(node, this.source)) {
|
|
739
|
+
this.extractMethod(node);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
// nameOverride is supplied only for explicitly-named anonymous functions the
|
|
743
|
+
// caller resolved itself (e.g. arrow values of exported-const object members
|
|
744
|
+
// — SvelteKit actions). Inline-object arrows reached by the general walker
|
|
745
|
+
// get no override, so they still fall through to the <anonymous> skip below.
|
|
746
|
+
let name = nameOverride ?? extractName(node, this.source, this.extractor);
|
|
747
|
+
// For arrow functions and function expressions assigned to variables,
|
|
748
|
+
// resolve the name from the parent variable_declarator.
|
|
749
|
+
// e.g. `export const useAuth = () => { ... }` — the arrow_function node
|
|
750
|
+
// has no `name` field; the name lives on the variable_declarator.
|
|
751
|
+
if (!nameOverride &&
|
|
752
|
+
name === '<anonymous>' &&
|
|
753
|
+
(node.type === 'arrow_function' || node.type === 'function_expression')) {
|
|
754
|
+
const parent = node.parent;
|
|
755
|
+
if (parent?.type === 'variable_declarator') {
|
|
756
|
+
const varName = (0, tree_sitter_helpers_1.getChildByField)(parent, 'name');
|
|
757
|
+
if (varName) {
|
|
758
|
+
name = (0, tree_sitter_helpers_1.getNodeText)(varName, this.source);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (name === '<anonymous>') {
|
|
763
|
+
// Don't emit a node for the anonymous wrapper itself, but still visit its
|
|
764
|
+
// body: AMD/RequireJS and CommonJS module wrappers (`define([], function(){…})`,
|
|
765
|
+
// `(function(){…})()`) hold named inner functions and calls that would
|
|
766
|
+
// otherwise be lost — the dispatcher set skipChildren, so nothing else
|
|
767
|
+
// descends into this subtree. (#528)
|
|
768
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
769
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
770
|
+
if (body) {
|
|
771
|
+
this.visitFunctionBody(body, '');
|
|
772
|
+
}
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
// Check for misparse artifacts (e.g. C++ macros causing "namespace detail" functions)
|
|
776
|
+
// Skip the node but still visit the body for calls and structural nodes
|
|
777
|
+
if (this.extractor.isMisparsedFunction?.(name, node)) {
|
|
778
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
779
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
780
|
+
if (body) {
|
|
781
|
+
this.visitFunctionBody(body, '');
|
|
782
|
+
}
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
786
|
+
const signature = this.extractor.getSignature?.(node, this.source);
|
|
787
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
788
|
+
const isExported = this.extractor.isExported?.(node, this.source);
|
|
789
|
+
const isAsync = this.extractor.isAsync?.(node);
|
|
790
|
+
const isStatic = this.extractor.isStatic?.(node);
|
|
791
|
+
const funcNode = this.createNode('function', name, node, {
|
|
792
|
+
docstring,
|
|
793
|
+
signature,
|
|
794
|
+
visibility,
|
|
795
|
+
isExported,
|
|
796
|
+
isAsync,
|
|
797
|
+
isStatic,
|
|
798
|
+
});
|
|
799
|
+
if (!funcNode)
|
|
800
|
+
return;
|
|
801
|
+
// Extract type annotations (parameter types and return type)
|
|
802
|
+
this.extractTypeAnnotations(node, funcNode.id);
|
|
803
|
+
// Extract decorators applied to the function (rare in JS/TS but
|
|
804
|
+
// present in Python `@decorator def f():` and Java/Kotlin
|
|
805
|
+
// annotations on free functions).
|
|
806
|
+
this.extractDecoratorsFor(node, funcNode.id);
|
|
807
|
+
// Push to stack and visit body
|
|
808
|
+
this.nodeStack.push(funcNode.id);
|
|
809
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
810
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
811
|
+
if (body) {
|
|
812
|
+
this.visitFunctionBody(body, funcNode.id);
|
|
813
|
+
}
|
|
814
|
+
this.nodeStack.pop();
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Extract a class
|
|
818
|
+
*/
|
|
819
|
+
extractClass(node, kind = 'class') {
|
|
820
|
+
if (!this.extractor)
|
|
821
|
+
return;
|
|
822
|
+
const name = extractName(node, this.source, this.extractor);
|
|
823
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
824
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
825
|
+
const isExported = this.extractor.isExported?.(node, this.source);
|
|
826
|
+
const classNode = this.createNode(kind, name, node, {
|
|
827
|
+
docstring,
|
|
828
|
+
visibility,
|
|
829
|
+
isExported,
|
|
830
|
+
});
|
|
831
|
+
if (!classNode)
|
|
832
|
+
return;
|
|
833
|
+
// Extract extends/implements
|
|
834
|
+
this.extractInheritance(node, classNode.id);
|
|
835
|
+
// C# primary-constructor parameter dependencies (`class Svc(IRepo r, …)`).
|
|
836
|
+
this.extractCsharpPrimaryCtorParamRefs(node, classNode.id);
|
|
837
|
+
// Extract decorators applied to the class (`@Foo class X {}`).
|
|
838
|
+
this.extractDecoratorsFor(node, classNode.id);
|
|
839
|
+
// Push to stack and visit body
|
|
840
|
+
this.nodeStack.push(classNode.id);
|
|
841
|
+
let body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
842
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
843
|
+
if (!body)
|
|
844
|
+
body = node;
|
|
845
|
+
// Visit all children for methods and properties
|
|
846
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
847
|
+
const child = body.namedChild(i);
|
|
848
|
+
if (child) {
|
|
849
|
+
this.visitNode(child);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
this.nodeStack.pop();
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Extract a method
|
|
856
|
+
*/
|
|
857
|
+
extractMethod(node) {
|
|
858
|
+
if (!this.extractor)
|
|
859
|
+
return;
|
|
860
|
+
// For languages with receiver types (Go, Rust), include receiver in qualified name
|
|
861
|
+
// so FTS can match "scrapeLoop.run" → qualified_name "...::scrapeLoop::run"
|
|
862
|
+
const receiverType = this.extractor.getReceiverType?.(node, this.source);
|
|
863
|
+
// For most languages, only extract as method if inside a class-like node
|
|
864
|
+
// Languages with methodsAreTopLevel (e.g. Go) always treat them as methods
|
|
865
|
+
// Languages with getReceiverType (e.g. Rust) extract as method when receiver is found
|
|
866
|
+
if (!this.isInsideClassLikeNode() && !this.extractor.methodsAreTopLevel && !receiverType) {
|
|
867
|
+
// Skip method_definition nodes inside object literals (getters/setters/methods
|
|
868
|
+
// in inline objects). These are ephemeral and create noise (e.g., Svelte context
|
|
869
|
+
// objects: `ctx.set({ get view() { ... } })`).
|
|
870
|
+
if (node.parent?.type === 'object' || node.parent?.type === 'object_expression') {
|
|
871
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
872
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
873
|
+
if (body) {
|
|
874
|
+
this.visitFunctionBody(body, '');
|
|
875
|
+
}
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
// Not inside a class-like node and no receiver type, treat as function
|
|
879
|
+
this.extractFunction(node);
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
const name = extractName(node, this.source, this.extractor);
|
|
883
|
+
// Check for misparse artifacts (e.g. C++ "switch" inside macro-confused class body)
|
|
884
|
+
if (this.extractor.isMisparsedFunction?.(name, node)) {
|
|
885
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
886
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
887
|
+
if (body) {
|
|
888
|
+
this.visitFunctionBody(body, '');
|
|
889
|
+
}
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
893
|
+
const signature = this.extractor.getSignature?.(node, this.source);
|
|
894
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
895
|
+
const isAsync = this.extractor.isAsync?.(node);
|
|
896
|
+
const isStatic = this.extractor.isStatic?.(node);
|
|
897
|
+
const extraProps = {
|
|
898
|
+
docstring,
|
|
899
|
+
signature,
|
|
900
|
+
visibility,
|
|
901
|
+
isAsync,
|
|
902
|
+
isStatic,
|
|
903
|
+
};
|
|
904
|
+
if (receiverType) {
|
|
905
|
+
extraProps.qualifiedName = `${receiverType}::${name}`;
|
|
906
|
+
}
|
|
907
|
+
const methodNode = this.createNode('method', name, node, extraProps);
|
|
908
|
+
if (!methodNode)
|
|
909
|
+
return;
|
|
910
|
+
// For methods with a receiver type but no class-like parent on the stack
|
|
911
|
+
// (e.g., Rust impl blocks), add a contains edge from the owning struct/trait
|
|
912
|
+
if (receiverType && !this.isInsideClassLikeNode()) {
|
|
913
|
+
const ownerNode = this.nodes.find((n) => n.name === receiverType &&
|
|
914
|
+
n.filePath === this.filePath &&
|
|
915
|
+
(n.kind === 'struct' || n.kind === 'class' || n.kind === 'enum' || n.kind === 'trait'));
|
|
916
|
+
if (ownerNode) {
|
|
917
|
+
this.edges.push({
|
|
918
|
+
source: ownerNode.id,
|
|
919
|
+
target: methodNode.id,
|
|
920
|
+
kind: 'contains',
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
// Extract type annotations (parameter types and return type)
|
|
925
|
+
this.extractTypeAnnotations(node, methodNode.id);
|
|
926
|
+
// Extract decorators (`@Get('/list') list() {}`).
|
|
927
|
+
this.extractDecoratorsFor(node, methodNode.id);
|
|
928
|
+
// Push to stack and visit body
|
|
929
|
+
this.nodeStack.push(methodNode.id);
|
|
930
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
931
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
932
|
+
if (body) {
|
|
933
|
+
this.visitFunctionBody(body, methodNode.id);
|
|
934
|
+
}
|
|
935
|
+
this.nodeStack.pop();
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Extract an interface/protocol/trait
|
|
939
|
+
*/
|
|
940
|
+
extractInterface(node) {
|
|
941
|
+
if (!this.extractor)
|
|
942
|
+
return;
|
|
943
|
+
const name = extractName(node, this.source, this.extractor);
|
|
944
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
945
|
+
const isExported = this.extractor.isExported?.(node, this.source);
|
|
946
|
+
const kind = this.extractor.interfaceKind ?? 'interface';
|
|
947
|
+
const interfaceNode = this.createNode(kind, name, node, {
|
|
948
|
+
docstring,
|
|
949
|
+
isExported,
|
|
950
|
+
});
|
|
951
|
+
if (!interfaceNode)
|
|
952
|
+
return;
|
|
953
|
+
// Extract extends (interface inheritance)
|
|
954
|
+
this.extractInheritance(node, interfaceNode.id);
|
|
955
|
+
// Visit body children for interface methods and nested types
|
|
956
|
+
this.nodeStack.push(interfaceNode.id);
|
|
957
|
+
let body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
958
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
959
|
+
if (!body)
|
|
960
|
+
body = node;
|
|
961
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
962
|
+
const child = body.namedChild(i);
|
|
963
|
+
if (child) {
|
|
964
|
+
this.visitNode(child);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
this.nodeStack.pop();
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Extract a struct
|
|
971
|
+
*/
|
|
972
|
+
extractStruct(node) {
|
|
973
|
+
if (!this.extractor)
|
|
974
|
+
return;
|
|
975
|
+
// Skip forward declarations and type references (no body = not a definition)
|
|
976
|
+
const body = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
977
|
+
if (!body)
|
|
978
|
+
return;
|
|
979
|
+
const name = extractName(node, this.source, this.extractor);
|
|
980
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
981
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
982
|
+
const isExported = this.extractor.isExported?.(node, this.source);
|
|
983
|
+
const structNode = this.createNode('struct', name, node, {
|
|
984
|
+
docstring,
|
|
985
|
+
visibility,
|
|
986
|
+
isExported,
|
|
987
|
+
});
|
|
988
|
+
if (!structNode)
|
|
989
|
+
return;
|
|
990
|
+
// Extract inheritance (e.g. Swift: struct HTTPMethod: RawRepresentable)
|
|
991
|
+
this.extractInheritance(node, structNode.id);
|
|
992
|
+
// C# primary-constructor parameter dependencies (`struct P(int x)`, and
|
|
993
|
+
// `record struct M(decimal Amount)` which the grammar nests here).
|
|
994
|
+
this.extractCsharpPrimaryCtorParamRefs(node, structNode.id);
|
|
995
|
+
// Push to stack for field extraction
|
|
996
|
+
this.nodeStack.push(structNode.id);
|
|
997
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
998
|
+
const child = body.namedChild(i);
|
|
999
|
+
if (child) {
|
|
1000
|
+
this.visitNode(child);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
this.nodeStack.pop();
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Extract an enum
|
|
1007
|
+
*/
|
|
1008
|
+
extractEnum(node) {
|
|
1009
|
+
if (!this.extractor)
|
|
1010
|
+
return;
|
|
1011
|
+
// Skip forward declarations and type references (no body = not a definition)
|
|
1012
|
+
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
1013
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
1014
|
+
if (!body)
|
|
1015
|
+
return;
|
|
1016
|
+
const name = extractName(node, this.source, this.extractor);
|
|
1017
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1018
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
1019
|
+
const isExported = this.extractor.isExported?.(node, this.source);
|
|
1020
|
+
const enumNode = this.createNode('enum', name, node, {
|
|
1021
|
+
docstring,
|
|
1022
|
+
visibility,
|
|
1023
|
+
isExported,
|
|
1024
|
+
});
|
|
1025
|
+
if (!enumNode)
|
|
1026
|
+
return;
|
|
1027
|
+
// Extract inheritance (e.g. Swift: enum AFError: Error)
|
|
1028
|
+
this.extractInheritance(node, enumNode.id);
|
|
1029
|
+
// Push to stack and visit body children (enum members, nested types, methods)
|
|
1030
|
+
this.nodeStack.push(enumNode.id);
|
|
1031
|
+
const memberTypes = this.extractor.enumMemberTypes;
|
|
1032
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
1033
|
+
const child = body.namedChild(i);
|
|
1034
|
+
if (!child)
|
|
1035
|
+
continue;
|
|
1036
|
+
if (memberTypes?.includes(child.type)) {
|
|
1037
|
+
this.extractEnumMembers(child);
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
this.visitNode(child);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
this.nodeStack.pop();
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Extract enum member names from an enum member node.
|
|
1047
|
+
* Handles multi-case declarations (Swift: `case put, delete`) and single-case patterns.
|
|
1048
|
+
*/
|
|
1049
|
+
extractEnumMembers(node) {
|
|
1050
|
+
// Try field-based name first (e.g. Rust enum_variant has a 'name' field)
|
|
1051
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
1052
|
+
if (nameNode) {
|
|
1053
|
+
this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source), node);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
// Check for identifier-like children (Swift: simple_identifier, TS: property_identifier)
|
|
1057
|
+
let found = false;
|
|
1058
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1059
|
+
const child = node.namedChild(i);
|
|
1060
|
+
if (child && (child.type === 'simple_identifier' || child.type === 'identifier' || child.type === 'property_identifier')) {
|
|
1061
|
+
this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(child, this.source), child);
|
|
1062
|
+
found = true;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
// If the node itself IS the identifier (e.g. TS property_identifier directly in enum body)
|
|
1066
|
+
if (!found && node.namedChildCount === 0) {
|
|
1067
|
+
this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(node, this.source), node);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Extract a class property declaration (e.g. C# `public string Name { get; set; }`).
|
|
1072
|
+
* Extracts as 'property' kind node inside the owning class.
|
|
1073
|
+
*/
|
|
1074
|
+
extractProperty(node) {
|
|
1075
|
+
if (!this.extractor)
|
|
1076
|
+
return;
|
|
1077
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1078
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
1079
|
+
const isStatic = this.extractor.isStatic?.(node) ?? false;
|
|
1080
|
+
const hookName = this.extractor.extractPropertyName?.(node, this.source);
|
|
1081
|
+
const nameNode = hookName
|
|
1082
|
+
? null
|
|
1083
|
+
: (0, tree_sitter_helpers_1.getChildByField)(node, 'name') || node.namedChildren.find(c => c.type === 'identifier');
|
|
1084
|
+
const name = hookName ?? (nameNode ? (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source) : null);
|
|
1085
|
+
if (!name)
|
|
1086
|
+
return;
|
|
1087
|
+
// Get property type from the type child (first named child that isn't modifier or identifier)
|
|
1088
|
+
const typeNode = node.namedChildren.find(c => c.type !== 'modifier' && c.type !== 'modifiers'
|
|
1089
|
+
&& c.type !== 'identifier' && c.type !== 'accessor_list'
|
|
1090
|
+
&& c.type !== 'accessors' && c.type !== 'equals_value_clause');
|
|
1091
|
+
const typeText = typeNode ? (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source) : undefined;
|
|
1092
|
+
const signature = typeText ? `${typeText} ${name}` : name;
|
|
1093
|
+
const propNode = this.createNode('property', name, node, {
|
|
1094
|
+
docstring,
|
|
1095
|
+
signature,
|
|
1096
|
+
visibility,
|
|
1097
|
+
isStatic,
|
|
1098
|
+
});
|
|
1099
|
+
// `@Inject() private svc: Foo` and similar — capture the
|
|
1100
|
+
// decorator->target relationship for class properties too.
|
|
1101
|
+
if (propNode) {
|
|
1102
|
+
this.extractDecoratorsFor(node, propNode.id);
|
|
1103
|
+
// Emit `references` edges from the property to types named in its
|
|
1104
|
+
// type annotation (#381). The generic walker handles TS-style
|
|
1105
|
+
// `type_annotation` children; the C# branch walks the `type` field.
|
|
1106
|
+
this.extractTypeAnnotations(node, propNode.id);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Extract a class field declaration (e.g. Java field_declaration, C# field_declaration).
|
|
1111
|
+
* Extracts each declarator as a 'field' kind node inside the owning class.
|
|
1112
|
+
*/
|
|
1113
|
+
extractField(node) {
|
|
1114
|
+
if (!this.extractor)
|
|
1115
|
+
return;
|
|
1116
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1117
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
1118
|
+
const isStatic = this.extractor.isStatic?.(node) ?? false;
|
|
1119
|
+
// Java field_declaration: "private final String name = value;" → variable_declarator(s) are direct children
|
|
1120
|
+
// C# field_declaration: wraps in variable_declaration → variable_declarator(s)
|
|
1121
|
+
let declarators = node.namedChildren.filter(c => c.type === 'variable_declarator');
|
|
1122
|
+
// C#: look inside variable_declaration wrapper
|
|
1123
|
+
if (declarators.length === 0) {
|
|
1124
|
+
const varDecl = node.namedChildren.find(c => c.type === 'variable_declaration');
|
|
1125
|
+
if (varDecl) {
|
|
1126
|
+
declarators = varDecl.namedChildren.filter(c => c.type === 'variable_declarator');
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
// PHP property_declaration: property_element → variable_name → name
|
|
1130
|
+
if (declarators.length === 0) {
|
|
1131
|
+
const propElements = node.namedChildren.filter(c => c.type === 'property_element');
|
|
1132
|
+
if (propElements.length > 0) {
|
|
1133
|
+
// Get type annotation if present (e.g. "string", "int", "?Foo")
|
|
1134
|
+
const typeNode = node.namedChildren.find(c => c.type !== 'visibility_modifier' && c.type !== 'static_modifier'
|
|
1135
|
+
&& c.type !== 'readonly_modifier' && c.type !== 'property_element'
|
|
1136
|
+
&& c.type !== 'var_modifier');
|
|
1137
|
+
const typeText = typeNode ? (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source) : undefined;
|
|
1138
|
+
for (const elem of propElements) {
|
|
1139
|
+
const varName = elem.namedChildren.find(c => c.type === 'variable_name');
|
|
1140
|
+
const nameNode = varName?.namedChildren.find(c => c.type === 'name');
|
|
1141
|
+
if (!nameNode)
|
|
1142
|
+
continue;
|
|
1143
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1144
|
+
const signature = typeText ? `${typeText} $${name}` : `$${name}`;
|
|
1145
|
+
this.createNode('field', name, elem, {
|
|
1146
|
+
docstring,
|
|
1147
|
+
signature,
|
|
1148
|
+
visibility,
|
|
1149
|
+
isStatic,
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if (declarators.length > 0) {
|
|
1156
|
+
// Get field type from the type child
|
|
1157
|
+
// Java: type is a direct child of field_declaration
|
|
1158
|
+
// C#: type is inside variable_declaration wrapper
|
|
1159
|
+
const varDecl = node.namedChildren.find(c => c.type === 'variable_declaration');
|
|
1160
|
+
const typeSearchNode = varDecl ?? node;
|
|
1161
|
+
const typeNode = typeSearchNode.namedChildren.find(c => c.type !== 'modifiers' && c.type !== 'modifier' && c.type !== 'variable_declarator'
|
|
1162
|
+
&& c.type !== 'variable_declaration' && c.type !== 'marker_annotation' && c.type !== 'annotation');
|
|
1163
|
+
const typeText = typeNode ? (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source) : undefined;
|
|
1164
|
+
for (const decl of declarators) {
|
|
1165
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(decl, 'name')
|
|
1166
|
+
|| decl.namedChildren.find(c => c.type === 'identifier');
|
|
1167
|
+
if (!nameNode)
|
|
1168
|
+
continue;
|
|
1169
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1170
|
+
const signature = typeText ? `${typeText} ${name}` : name;
|
|
1171
|
+
const fieldNode = this.createNode('field', name, decl, {
|
|
1172
|
+
docstring,
|
|
1173
|
+
signature,
|
|
1174
|
+
visibility,
|
|
1175
|
+
isStatic,
|
|
1176
|
+
});
|
|
1177
|
+
// Java/Kotlin annotations / TS field decorators sit on the
|
|
1178
|
+
// outer field_declaration, not on the individual declarator.
|
|
1179
|
+
if (fieldNode) {
|
|
1180
|
+
this.extractDecoratorsFor(node, fieldNode.id);
|
|
1181
|
+
// Same as properties: emit `references` to the field's annotated
|
|
1182
|
+
// type. The outer `field_declaration` is the right scope to
|
|
1183
|
+
// search from — C# carries the `type` inside `variable_declaration`
|
|
1184
|
+
// and the language-aware path in `extractTypeAnnotations` descends
|
|
1185
|
+
// into that wrapper (#381).
|
|
1186
|
+
this.extractTypeAnnotations(node, fieldNode.id);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
// Fallback: try to find an identifier child directly
|
|
1192
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name')
|
|
1193
|
+
|| node.namedChildren.find(c => c.type === 'identifier');
|
|
1194
|
+
if (nameNode) {
|
|
1195
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1196
|
+
this.createNode('field', name, node, {
|
|
1197
|
+
docstring,
|
|
1198
|
+
visibility,
|
|
1199
|
+
isStatic,
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Extract function-valued properties of an object literal as named function
|
|
1206
|
+
* nodes (named by their property key). Shared by the two object-of-functions
|
|
1207
|
+
* shapes in extractVariable: the object as a direct const value, and the
|
|
1208
|
+
* object returned by a store-initializer call. Handles both `key: () => {}` /
|
|
1209
|
+
* `key: function() {}` pairs and method shorthand `key() {}`.
|
|
1210
|
+
*/
|
|
1211
|
+
extractObjectLiteralFunctions(obj) {
|
|
1212
|
+
for (let i = 0; i < obj.namedChildCount; i++) {
|
|
1213
|
+
const member = obj.namedChild(i);
|
|
1214
|
+
if (!member)
|
|
1215
|
+
continue;
|
|
1216
|
+
if (member.type === 'pair') {
|
|
1217
|
+
const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
|
|
1218
|
+
const value = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
|
|
1219
|
+
if (key && value && (value.type === 'arrow_function' || value.type === 'function_expression')) {
|
|
1220
|
+
this.extractFunction(value, this.objectKeyName(key));
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
else if (member.type === 'method_definition') {
|
|
1224
|
+
// Method shorthand: `{ fetchUser() {...} }`. extractMethod deliberately
|
|
1225
|
+
// skips object-literal methods, so route through extractFunction with an
|
|
1226
|
+
// explicit name (method_definition exposes a `body` field, so resolveBody
|
|
1227
|
+
// falls through to it and the node spans the full method).
|
|
1228
|
+
const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'name');
|
|
1229
|
+
if (key)
|
|
1230
|
+
this.extractFunction(member, this.objectKeyName(key));
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
/** Property-key text with surrounding quotes stripped (`'foo'` → `foo`). */
|
|
1235
|
+
objectKeyName(key) {
|
|
1236
|
+
return (0, tree_sitter_helpers_1.getNodeText)(key, this.source).replace(/^['"`]|['"`]$/g, '');
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Given a `call_expression` initializer (`create((set, get) => ({...}))`),
|
|
1240
|
+
* find the object literal RETURNED by a function argument — descending through
|
|
1241
|
+
* nested call_expression arguments so middleware wrappers are unwrapped
|
|
1242
|
+
* (`create(persist((set, get) => ({...}), {...}))`, devtools, immer,
|
|
1243
|
+
* subscribeWithSelector). Returns null when no such object is found — the
|
|
1244
|
+
* common case for ordinary call initializers — so this stays cheap and silent
|
|
1245
|
+
* rather than guessing. Keyed purely on AST shape; no library names.
|
|
1246
|
+
*/
|
|
1247
|
+
findInitializerReturnedObject(callNode, depth = 0) {
|
|
1248
|
+
if (depth > 4)
|
|
1249
|
+
return null;
|
|
1250
|
+
const args = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'arguments');
|
|
1251
|
+
if (!args)
|
|
1252
|
+
return null;
|
|
1253
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
1254
|
+
const arg = args.namedChild(i);
|
|
1255
|
+
if (!arg)
|
|
1256
|
+
continue;
|
|
1257
|
+
if (arg.type === 'arrow_function' || arg.type === 'function_expression') {
|
|
1258
|
+
const obj = this.functionReturnedObject(arg);
|
|
1259
|
+
if (obj)
|
|
1260
|
+
return obj;
|
|
1261
|
+
}
|
|
1262
|
+
else if (arg.type === 'call_expression') {
|
|
1263
|
+
const obj = this.findInitializerReturnedObject(arg, depth + 1);
|
|
1264
|
+
if (obj)
|
|
1265
|
+
return obj;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return null;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* The object literal a function expression returns — either the `=> ({...})`
|
|
1272
|
+
* arrow form (a parenthesized_expression wrapping an object) or a
|
|
1273
|
+
* `=> { return {...} }` block. Returns null for any other body shape.
|
|
1274
|
+
*/
|
|
1275
|
+
functionReturnedObject(fnNode) {
|
|
1276
|
+
const body = (0, tree_sitter_helpers_1.getChildByField)(fnNode, 'body');
|
|
1277
|
+
if (!body)
|
|
1278
|
+
return null;
|
|
1279
|
+
const asObject = (n) => {
|
|
1280
|
+
if (!n)
|
|
1281
|
+
return null;
|
|
1282
|
+
if (n.type === 'object' || n.type === 'object_expression')
|
|
1283
|
+
return n;
|
|
1284
|
+
if (n.type === 'parenthesized_expression') {
|
|
1285
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1286
|
+
const inner = asObject(n.namedChild(i));
|
|
1287
|
+
if (inner)
|
|
1288
|
+
return inner;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
return null;
|
|
1292
|
+
};
|
|
1293
|
+
// `(set, get) => ({...})` — body is the (parenthesized) object directly.
|
|
1294
|
+
const direct = asObject(body);
|
|
1295
|
+
if (direct)
|
|
1296
|
+
return direct;
|
|
1297
|
+
// `(set, get) => { return {...} }` — scan top-level return statements.
|
|
1298
|
+
if (body.type === 'statement_block') {
|
|
1299
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
1300
|
+
const stmt = body.namedChild(i);
|
|
1301
|
+
if (stmt?.type !== 'return_statement')
|
|
1302
|
+
continue;
|
|
1303
|
+
for (let j = 0; j < stmt.namedChildCount; j++) {
|
|
1304
|
+
const obj = asObject(stmt.namedChild(j));
|
|
1305
|
+
if (obj)
|
|
1306
|
+
return obj;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
return null;
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Extract a variable declaration (const, let, var, etc.)
|
|
1314
|
+
*
|
|
1315
|
+
* Extracts top-level and module-level variable declarations.
|
|
1316
|
+
* Captures the variable name and first 100 chars of initializer in signature for searchability.
|
|
1317
|
+
*/
|
|
1318
|
+
extractVariable(node) {
|
|
1319
|
+
if (!this.extractor)
|
|
1320
|
+
return;
|
|
1321
|
+
// Different languages have different variable declaration structures
|
|
1322
|
+
// TypeScript/JavaScript: lexical_declaration contains variable_declarator children
|
|
1323
|
+
// Python: assignment has left (identifier) and right (value)
|
|
1324
|
+
// Go: var_declaration, short_var_declaration, const_declaration
|
|
1325
|
+
const isConst = this.extractor.isConst?.(node) ?? false;
|
|
1326
|
+
const kind = isConst ? 'constant' : 'variable';
|
|
1327
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1328
|
+
const isExported = this.extractor.isExported?.(node, this.source) ?? false;
|
|
1329
|
+
// Extract variable declarators based on language
|
|
1330
|
+
if (this.language === 'typescript' || this.language === 'javascript' ||
|
|
1331
|
+
this.language === 'tsx' || this.language === 'jsx') {
|
|
1332
|
+
// Handle lexical_declaration and variable_declaration
|
|
1333
|
+
// These contain one or more variable_declarator children
|
|
1334
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1335
|
+
const child = node.namedChild(i);
|
|
1336
|
+
if (child?.type === 'variable_declarator') {
|
|
1337
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'name');
|
|
1338
|
+
const valueNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'value');
|
|
1339
|
+
if (nameNode) {
|
|
1340
|
+
// Skip destructured patterns (e.g., `let { x, y } = $props()` in Svelte)
|
|
1341
|
+
// These produce ugly multi-line names like "{ class: className }"
|
|
1342
|
+
if (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern') {
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1346
|
+
// Arrow functions / function expressions: extract as function instead of variable
|
|
1347
|
+
if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) {
|
|
1348
|
+
this.extractFunction(valueNode);
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
// Capture first 100 chars of initializer for context (stored in signature for searchability)
|
|
1352
|
+
const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
|
|
1353
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1354
|
+
const varNode = this.createNode(kind, name, child, {
|
|
1355
|
+
docstring,
|
|
1356
|
+
signature: initSignature,
|
|
1357
|
+
isExported,
|
|
1358
|
+
});
|
|
1359
|
+
// Extract type annotation references (e.g., const x: ITextModel = ...)
|
|
1360
|
+
if (varNode) {
|
|
1361
|
+
this.extractVariableTypeAnnotation(child, varNode.id);
|
|
1362
|
+
}
|
|
1363
|
+
// Exported const object-of-functions — extract each function-valued
|
|
1364
|
+
// property as a function named by its key + walk its body so its
|
|
1365
|
+
// calls are captured. Two shapes, both keyed on AST shape (not on any
|
|
1366
|
+
// library name):
|
|
1367
|
+
// `export const actions = { default: async () => {} }` — object is
|
|
1368
|
+
// the DIRECT value (SvelteKit form actions / handler maps / route
|
|
1369
|
+
// tables).
|
|
1370
|
+
// `export const useStore = create((set, get) => ({ fetchUser:
|
|
1371
|
+
// async () => {} }))` — object is RETURNED by an initializer call,
|
|
1372
|
+
// possibly through middleware wrappers (persist/devtools/immer).
|
|
1373
|
+
// Covers Zustand/Redux/Pinia/MobX stores generically. Without
|
|
1374
|
+
// this, store actions exist only as object-literal properties —
|
|
1375
|
+
// never nodes — so `node`/`callers` on `fetchUser` return "not
|
|
1376
|
+
// found" and the agent Reads the store to reconstruct the flow.
|
|
1377
|
+
// Scoped to EXPORTED consts to exclude inline-object noise
|
|
1378
|
+
// (`ctx.set({...})`) the object-method skip deliberately avoids.
|
|
1379
|
+
const objectOfFns = valueNode && (valueNode.type === 'object' || valueNode.type === 'object_expression')
|
|
1380
|
+
? valueNode
|
|
1381
|
+
: valueNode?.type === 'call_expression'
|
|
1382
|
+
? this.findInitializerReturnedObject(valueNode)
|
|
1383
|
+
: null;
|
|
1384
|
+
const extractObjectMethods = isExported && !!objectOfFns;
|
|
1385
|
+
// Visit the initializer body for calls — EXCEPT object literals (their
|
|
1386
|
+
// function-valued properties are extracted below) and the store-factory
|
|
1387
|
+
// call whose returned object we extract method-by-method below (walking
|
|
1388
|
+
// the whole call would re-visit those method arrows and mis-attribute
|
|
1389
|
+
// their inner calls to the file/module scope).
|
|
1390
|
+
if (valueNode &&
|
|
1391
|
+
valueNode.type !== 'object' &&
|
|
1392
|
+
valueNode.type !== 'object_expression' &&
|
|
1393
|
+
!(extractObjectMethods && valueNode.type === 'call_expression')) {
|
|
1394
|
+
this.visitFunctionBody(valueNode, '');
|
|
1395
|
+
}
|
|
1396
|
+
if (extractObjectMethods && objectOfFns) {
|
|
1397
|
+
this.extractObjectLiteralFunctions(objectOfFns);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
else if (this.language === 'python' || this.language === 'ruby') {
|
|
1404
|
+
// Python/Ruby assignment: left = right
|
|
1405
|
+
const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left') || node.namedChild(0);
|
|
1406
|
+
const right = (0, tree_sitter_helpers_1.getChildByField)(node, 'right') || node.namedChild(1);
|
|
1407
|
+
if (left && left.type === 'identifier') {
|
|
1408
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(left, this.source);
|
|
1409
|
+
// Skip if name starts with lowercase and looks like a function call result
|
|
1410
|
+
// Python constants are usually UPPER_CASE
|
|
1411
|
+
const initValue = right ? (0, tree_sitter_helpers_1.getNodeText)(right, this.source).slice(0, 100) : undefined;
|
|
1412
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1413
|
+
this.createNode(kind, name, node, {
|
|
1414
|
+
docstring,
|
|
1415
|
+
signature: initSignature,
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
else if (this.language === 'go') {
|
|
1420
|
+
// Go: var_declaration, short_var_declaration, const_declaration
|
|
1421
|
+
// These can have multiple identifiers on the left
|
|
1422
|
+
const specs = node.namedChildren.filter(c => c.type === 'var_spec' || c.type === 'const_spec');
|
|
1423
|
+
for (const spec of specs) {
|
|
1424
|
+
const nameNode = spec.namedChild(0);
|
|
1425
|
+
if (nameNode && nameNode.type === 'identifier') {
|
|
1426
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1427
|
+
const valueNode = spec.namedChildCount > 1 ? spec.namedChild(spec.namedChildCount - 1) : null;
|
|
1428
|
+
const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
|
|
1429
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1430
|
+
this.createNode(node.type === 'const_declaration' ? 'constant' : 'variable', name, spec, {
|
|
1431
|
+
docstring,
|
|
1432
|
+
signature: initSignature,
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
// Walk the initializer so composite literals and calls in a
|
|
1436
|
+
// package-level `var Query Binding = queryBinding{}` (a registry of
|
|
1437
|
+
// implementations) or `var c = pkg.New()` are extracted as
|
|
1438
|
+
// instantiates/calls dependencies — the body walker only covers
|
|
1439
|
+
// initializers inside functions, not these top-level declarations.
|
|
1440
|
+
const valueField = (0, tree_sitter_helpers_1.getChildByField)(spec, 'value');
|
|
1441
|
+
if (valueField)
|
|
1442
|
+
this.visitFunctionBody(valueField, '');
|
|
1443
|
+
}
|
|
1444
|
+
// Handle short_var_declaration (:=)
|
|
1445
|
+
if (node.type === 'short_var_declaration') {
|
|
1446
|
+
const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left');
|
|
1447
|
+
const right = (0, tree_sitter_helpers_1.getChildByField)(node, 'right');
|
|
1448
|
+
if (left) {
|
|
1449
|
+
// Can be expression_list with multiple identifiers
|
|
1450
|
+
const identifiers = left.type === 'expression_list'
|
|
1451
|
+
? left.namedChildren.filter(c => c.type === 'identifier')
|
|
1452
|
+
: [left];
|
|
1453
|
+
for (const id of identifiers) {
|
|
1454
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(id, this.source);
|
|
1455
|
+
const initValue = right ? (0, tree_sitter_helpers_1.getNodeText)(right, this.source).slice(0, 100) : undefined;
|
|
1456
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1457
|
+
this.createNode('variable', name, node, {
|
|
1458
|
+
docstring,
|
|
1459
|
+
signature: initSignature,
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
else if (this.language === 'lua' || this.language === 'luau') {
|
|
1466
|
+
// Lua/Luau: variable_declaration → assignment_statement → variable_list
|
|
1467
|
+
// (name: identifier...) = expression_list. `local x, y = 1, 2`
|
|
1468
|
+
// declares multiple names; only plain identifiers are locals.
|
|
1469
|
+
const assign = node.namedChildren.find((c) => c.type === 'assignment_statement') ?? node;
|
|
1470
|
+
const varList = assign.namedChildren.find((c) => c.type === 'variable_list');
|
|
1471
|
+
const exprList = assign.namedChildren.find((c) => c.type === 'expression_list');
|
|
1472
|
+
const values = exprList ? exprList.namedChildren : [];
|
|
1473
|
+
const names = varList ? varList.namedChildren.filter((c) => c.type === 'identifier') : [];
|
|
1474
|
+
names.forEach((nameNode, i) => {
|
|
1475
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1476
|
+
if (!name)
|
|
1477
|
+
return;
|
|
1478
|
+
const valueNode = values[i];
|
|
1479
|
+
const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
|
|
1480
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1481
|
+
this.createNode(kind, name, nameNode, { docstring, signature: initSignature, isExported });
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
else {
|
|
1485
|
+
// Generic fallback for other languages
|
|
1486
|
+
// Try to find identifier children
|
|
1487
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1488
|
+
const child = node.namedChild(i);
|
|
1489
|
+
if (child?.type === 'identifier' || child?.type === 'variable_declarator') {
|
|
1490
|
+
const name = child.type === 'identifier'
|
|
1491
|
+
? (0, tree_sitter_helpers_1.getNodeText)(child, this.source)
|
|
1492
|
+
: extractName(child, this.source, this.extractor);
|
|
1493
|
+
if (name && name !== '<anonymous>') {
|
|
1494
|
+
this.createNode(kind, name, child, {
|
|
1495
|
+
docstring,
|
|
1496
|
+
isExported,
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Extract a type alias (e.g. `export type X = ...` in TypeScript).
|
|
1505
|
+
* For languages like Go, resolveTypeAliasKind detects when the type_spec
|
|
1506
|
+
* wraps a struct or interface definition and creates the correct node kind.
|
|
1507
|
+
* Returns true if children should be skipped (struct/interface handled body visiting).
|
|
1508
|
+
*/
|
|
1509
|
+
extractTypeAlias(node) {
|
|
1510
|
+
if (!this.extractor)
|
|
1511
|
+
return false;
|
|
1512
|
+
const name = extractName(node, this.source, this.extractor);
|
|
1513
|
+
if (name === '<anonymous>')
|
|
1514
|
+
return false;
|
|
1515
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1516
|
+
const isExported = this.extractor.isExported?.(node, this.source);
|
|
1517
|
+
// Check if this type alias is actually a struct or interface definition
|
|
1518
|
+
// (e.g. Go: `type Foo struct { ... }` is a type_spec wrapping struct_type)
|
|
1519
|
+
const resolvedKind = this.extractor.resolveTypeAliasKind?.(node, this.source);
|
|
1520
|
+
if (resolvedKind === 'struct') {
|
|
1521
|
+
const structNode = this.createNode('struct', name, node, { docstring, isExported });
|
|
1522
|
+
if (!structNode)
|
|
1523
|
+
return true;
|
|
1524
|
+
// Visit body children for field extraction
|
|
1525
|
+
this.nodeStack.push(structNode.id);
|
|
1526
|
+
// Try Go-style 'type' field first, then find inner struct child (C typedef struct)
|
|
1527
|
+
const typeChild = (0, tree_sitter_helpers_1.getChildByField)(node, 'type')
|
|
1528
|
+
|| this.findChildByTypes(node, this.extractor.structTypes);
|
|
1529
|
+
if (typeChild) {
|
|
1530
|
+
// Extract struct embedding (e.g. Go: `type DB struct { *Head; Queryable }`)
|
|
1531
|
+
this.extractInheritance(typeChild, structNode.id);
|
|
1532
|
+
const body = (0, tree_sitter_helpers_1.getChildByField)(typeChild, this.extractor.bodyField) || typeChild;
|
|
1533
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
1534
|
+
const child = body.namedChild(i);
|
|
1535
|
+
if (child)
|
|
1536
|
+
this.visitNode(child);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
this.nodeStack.pop();
|
|
1540
|
+
return true;
|
|
1541
|
+
}
|
|
1542
|
+
if (resolvedKind === 'enum') {
|
|
1543
|
+
const enumNode = this.createNode('enum', name, node, { docstring, isExported });
|
|
1544
|
+
if (!enumNode)
|
|
1545
|
+
return true;
|
|
1546
|
+
this.nodeStack.push(enumNode.id);
|
|
1547
|
+
// Find the inner enum type child (e.g. C: typedef enum { ... } name)
|
|
1548
|
+
const innerEnum = this.findChildByTypes(node, this.extractor.enumTypes);
|
|
1549
|
+
if (innerEnum) {
|
|
1550
|
+
this.extractInheritance(innerEnum, enumNode.id);
|
|
1551
|
+
const body = this.extractor.resolveBody?.(innerEnum, this.extractor.bodyField)
|
|
1552
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(innerEnum, this.extractor.bodyField);
|
|
1553
|
+
if (body) {
|
|
1554
|
+
const memberTypes = this.extractor.enumMemberTypes;
|
|
1555
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
1556
|
+
const child = body.namedChild(i);
|
|
1557
|
+
if (!child)
|
|
1558
|
+
continue;
|
|
1559
|
+
if (memberTypes?.includes(child.type)) {
|
|
1560
|
+
this.extractEnumMembers(child);
|
|
1561
|
+
}
|
|
1562
|
+
else {
|
|
1563
|
+
this.visitNode(child);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
this.nodeStack.pop();
|
|
1569
|
+
return true;
|
|
1570
|
+
}
|
|
1571
|
+
if (resolvedKind === 'interface') {
|
|
1572
|
+
const kind = this.extractor.interfaceKind ?? 'interface';
|
|
1573
|
+
const interfaceNode = this.createNode(kind, name, node, { docstring, isExported });
|
|
1574
|
+
if (!interfaceNode)
|
|
1575
|
+
return true;
|
|
1576
|
+
// Extract interface inheritance from the inner type node
|
|
1577
|
+
const typeChild = (0, tree_sitter_helpers_1.getChildByField)(node, 'type');
|
|
1578
|
+
if (typeChild)
|
|
1579
|
+
this.extractInheritance(typeChild, interfaceNode.id);
|
|
1580
|
+
// Go: extract the interface's method specs as `method` nodes so implicit
|
|
1581
|
+
// interface satisfaction (a struct's method set ⊇ the interface's) and
|
|
1582
|
+
// impl-navigation can see the contract. Go has no `implements` keyword, so
|
|
1583
|
+
// without the interface's method set there's nothing to match against.
|
|
1584
|
+
if (this.language === 'go' && typeChild) {
|
|
1585
|
+
this.extractGoInterfaceMethods(typeChild, interfaceNode.id);
|
|
1586
|
+
}
|
|
1587
|
+
return true;
|
|
1588
|
+
}
|
|
1589
|
+
const typeAliasNode = this.createNode('type_alias', name, node, {
|
|
1590
|
+
docstring,
|
|
1591
|
+
isExported,
|
|
1592
|
+
});
|
|
1593
|
+
// Extract type references from the alias value (e.g., `type X = ITextModel | null`)
|
|
1594
|
+
if (typeAliasNode && this.TYPE_ANNOTATION_LANGUAGES.has(this.language)) {
|
|
1595
|
+
// The value is everything after the `=`, which is typically the last named child
|
|
1596
|
+
// In tree-sitter TS: type_alias_declaration has name + value children
|
|
1597
|
+
const value = (0, tree_sitter_helpers_1.getChildByField)(node, 'value');
|
|
1598
|
+
if (value) {
|
|
1599
|
+
this.extractTypeRefsFromSubtree(value, typeAliasNode.id);
|
|
1600
|
+
// `type X = { foo: T; bar(): T }` — make the members first-class
|
|
1601
|
+
// property/method nodes under the type alias so `recorder.stop()`
|
|
1602
|
+
// can attach the call edge to `RecorderHandle.stop` instead of
|
|
1603
|
+
// an unrelated class method picked by path-proximity (#359).
|
|
1604
|
+
if (this.language === 'typescript' || this.language === 'tsx') {
|
|
1605
|
+
this.extractTsTypeAliasMembers(value, typeAliasNode);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Extract the method specs of a Go `interface_type` body as `method` nodes
|
|
1613
|
+
* contained by the interface (e.g. `Marshal`, `Unmarshal` of a `Core`
|
|
1614
|
+
* interface). tree-sitter-go names these `method_elem` (newer) or
|
|
1615
|
+
* `method_spec` (older). Embedded interfaces (`Reader` inside `ReadWriter`)
|
|
1616
|
+
* are `type_identifier`s, not methods, and are left to inheritance extraction.
|
|
1617
|
+
*/
|
|
1618
|
+
extractGoInterfaceMethods(interfaceType, ifaceId) {
|
|
1619
|
+
this.nodeStack.push(ifaceId);
|
|
1620
|
+
for (let i = 0; i < interfaceType.namedChildCount; i++) {
|
|
1621
|
+
const m = interfaceType.namedChild(i);
|
|
1622
|
+
if (!m || (m.type !== 'method_elem' && m.type !== 'method_spec'))
|
|
1623
|
+
continue;
|
|
1624
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(m, 'name') ?? m.namedChild(0);
|
|
1625
|
+
if (!nameNode)
|
|
1626
|
+
continue;
|
|
1627
|
+
const mname = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1628
|
+
if (mname) {
|
|
1629
|
+
this.createNode('method', mname, m, {
|
|
1630
|
+
signature: this.extractor?.getSignature?.(m, this.source),
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
this.nodeStack.pop();
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Surface the members of a TypeScript `type X = { ... }` (or intersection
|
|
1638
|
+
* thereof) as `property` / `method` nodes under the type-alias node. Only
|
|
1639
|
+
* walks the immediate object_type / intersection operands so anonymous
|
|
1640
|
+
* nested object types inside generic arguments (`Promise<{ ok: true }>`)
|
|
1641
|
+
* don't produce phantom members.
|
|
1642
|
+
*/
|
|
1643
|
+
extractTsTypeAliasMembers(value, typeAliasNode) {
|
|
1644
|
+
const objectTypes = [];
|
|
1645
|
+
if (value.type === 'object_type') {
|
|
1646
|
+
objectTypes.push(value);
|
|
1647
|
+
}
|
|
1648
|
+
else if (value.type === 'intersection_type') {
|
|
1649
|
+
for (let i = 0; i < value.namedChildCount; i++) {
|
|
1650
|
+
const op = value.namedChild(i);
|
|
1651
|
+
if (op && op.type === 'object_type')
|
|
1652
|
+
objectTypes.push(op);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
else {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
this.nodeStack.push(typeAliasNode.id);
|
|
1659
|
+
for (const objType of objectTypes) {
|
|
1660
|
+
for (let i = 0; i < objType.namedChildCount; i++) {
|
|
1661
|
+
const child = objType.namedChild(i);
|
|
1662
|
+
if (!child)
|
|
1663
|
+
continue;
|
|
1664
|
+
if (child.type !== 'property_signature' && child.type !== 'method_signature')
|
|
1665
|
+
continue;
|
|
1666
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'name');
|
|
1667
|
+
const memberName = nameNode ? (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source) : '';
|
|
1668
|
+
if (!memberName)
|
|
1669
|
+
continue;
|
|
1670
|
+
// `foo: () => T` and `foo(): T` are functionally a method on the
|
|
1671
|
+
// type contract. Treat the property_signature with a function-typed
|
|
1672
|
+
// annotation as a method too so call sites can resolve to it.
|
|
1673
|
+
const memberKind = child.type === 'method_signature'
|
|
1674
|
+
? 'method'
|
|
1675
|
+
: this.isTsFunctionTypedProperty(child) ? 'method' : 'property';
|
|
1676
|
+
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(child, this.source);
|
|
1677
|
+
const signature = (0, tree_sitter_helpers_1.getNodeText)(child, this.source);
|
|
1678
|
+
this.createNode(memberKind, memberName, child, {
|
|
1679
|
+
docstring,
|
|
1680
|
+
signature,
|
|
1681
|
+
qualifiedName: `${typeAliasNode.name}::${memberName}`,
|
|
1682
|
+
});
|
|
1683
|
+
// Emit `references` edges from the type alias to types named in the
|
|
1684
|
+
// member's signature, matching the interface-member behavior added in
|
|
1685
|
+
// #432. We attach refs to the type-alias parent (consistent with
|
|
1686
|
+
// interface property_signature treatment).
|
|
1687
|
+
this.extractTypeAnnotations(child, typeAliasNode.id);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
this.nodeStack.pop();
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* `foo: () => T` → property_signature whose type_annotation contains a
|
|
1694
|
+
* `function_type`. Treat that as a method-shaped contract member, since
|
|
1695
|
+
* the call site `obj.foo()` has identical semantics to `bar(): T`.
|
|
1696
|
+
*/
|
|
1697
|
+
isTsFunctionTypedProperty(propertySignature) {
|
|
1698
|
+
const typeAnno = (0, tree_sitter_helpers_1.getChildByField)(propertySignature, 'type');
|
|
1699
|
+
if (!typeAnno)
|
|
1700
|
+
return false;
|
|
1701
|
+
for (let i = 0; i < typeAnno.namedChildCount; i++) {
|
|
1702
|
+
const inner = typeAnno.namedChild(i);
|
|
1703
|
+
if (inner && inner.type === 'function_type')
|
|
1704
|
+
return true;
|
|
1705
|
+
}
|
|
1706
|
+
return false;
|
|
1707
|
+
}
|
|
1708
|
+
// extractExportedVariables removed — the walker now descends into
|
|
1709
|
+
// export_statement children and the inner declaration's dedicated
|
|
1710
|
+
// extractor (extractVariable, extractFunction, extractClass, etc.)
|
|
1711
|
+
// handles the symbol with isExported=true via parent-walk in the
|
|
1712
|
+
// language extractor's isExported predicate.
|
|
1713
|
+
/**
|
|
1714
|
+
* Extract an import
|
|
1715
|
+
*
|
|
1716
|
+
* Creates an import node with the full import statement stored in signature for searchability.
|
|
1717
|
+
* Also creates unresolved references for resolution purposes.
|
|
1718
|
+
*/
|
|
1719
|
+
extractImport(node) {
|
|
1720
|
+
if (!this.extractor)
|
|
1721
|
+
return;
|
|
1722
|
+
const importText = (0, tree_sitter_helpers_1.getNodeText)(node, this.source).trim();
|
|
1723
|
+
// Try language-specific hook first
|
|
1724
|
+
if (this.extractor.extractImport) {
|
|
1725
|
+
const info = this.extractor.extractImport(node, this.source);
|
|
1726
|
+
if (info) {
|
|
1727
|
+
this.createNode('import', info.moduleName, node, {
|
|
1728
|
+
signature: info.signature,
|
|
1729
|
+
});
|
|
1730
|
+
// Create unresolved reference unless the hook handled it
|
|
1731
|
+
if (!info.handledRefs && info.moduleName && this.nodeStack.length > 0) {
|
|
1732
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1733
|
+
if (parentId) {
|
|
1734
|
+
this.unresolvedReferences.push({
|
|
1735
|
+
fromNodeId: parentId,
|
|
1736
|
+
referenceName: info.moduleName,
|
|
1737
|
+
referenceKind: 'imports',
|
|
1738
|
+
line: node.startPosition.row + 1,
|
|
1739
|
+
column: node.startPosition.column,
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
// Link each imported binding to its definition so imported-but-not-
|
|
1744
|
+
// called/typed symbols still record a cross-file dependency (TS/JS only).
|
|
1745
|
+
if (this.language === 'typescript' || this.language === 'tsx' ||
|
|
1746
|
+
this.language === 'javascript' || this.language === 'jsx') {
|
|
1747
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1748
|
+
if (parentId)
|
|
1749
|
+
this.emitImportBindingRefs(node, parentId);
|
|
1750
|
+
}
|
|
1751
|
+
// Python `from module import X, Y` — link each imported name to its
|
|
1752
|
+
// definition (covers `__init__.py` re-export barrels, which are just
|
|
1753
|
+
// `from .sub import X`). Same recall gap as TS: a name imported and
|
|
1754
|
+
// used in a non-call position created no dependency edge.
|
|
1755
|
+
if (this.language === 'python' && node.type === 'import_from_statement') {
|
|
1756
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1757
|
+
if (parentId)
|
|
1758
|
+
this.emitPyFromImportRefs(node, parentId);
|
|
1759
|
+
}
|
|
1760
|
+
// Rust `use crate::m::Item;` / `pub use self::sub::Item;` — link each
|
|
1761
|
+
// imported leaf to its definition. Covers `pub use` re-export hubs
|
|
1762
|
+
// (a `mod.rs` re-exporting submodule items, e.g. tokio's `fs/mod.rs`)
|
|
1763
|
+
// and items imported but used in non-call/non-type positions.
|
|
1764
|
+
if (this.language === 'rust' && node.type === 'use_declaration') {
|
|
1765
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1766
|
+
if (parentId)
|
|
1767
|
+
this.emitRustUseBindingRefs(node, parentId);
|
|
1768
|
+
}
|
|
1769
|
+
// PHP `use Foo\Bar\Baz;` — link to the namespace-qualified definition so
|
|
1770
|
+
// an imported-but-DI-injected contract (Laravel's pattern) records a
|
|
1771
|
+
// cross-file dependency. Grouped imports are handled in their own branch.
|
|
1772
|
+
if (this.language === 'php' && node.type === 'namespace_use_declaration') {
|
|
1773
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1774
|
+
if (parentId)
|
|
1775
|
+
this.emitPhpUseRefs(node, parentId);
|
|
1776
|
+
}
|
|
1777
|
+
// Ruby `require "lib/foo"` / `require_relative "../foo"` — resolve to the
|
|
1778
|
+
// required FILE so a file pulled in only by `require` (config-loaded
|
|
1779
|
+
// components, gems that don't autoload) records a cross-file dependency.
|
|
1780
|
+
if (this.language === 'ruby' && node.type === 'call') {
|
|
1781
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1782
|
+
if (parentId)
|
|
1783
|
+
this.emitRubyRequireRefs(node, parentId);
|
|
1784
|
+
}
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
// Hook returned null — fall through to multi-import inline handlers only
|
|
1788
|
+
// (hook returning null means "I didn't handle this" for multi-import cases,
|
|
1789
|
+
// NOT "use generic fallback" — the hook already declined)
|
|
1790
|
+
}
|
|
1791
|
+
// Multi-import cases that create multiple nodes (can't be expressed with single-return hook)
|
|
1792
|
+
// Python import_statement: import os, sys (creates one import per module)
|
|
1793
|
+
if (this.language === 'python' && node.type === 'import_statement') {
|
|
1794
|
+
const importParentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1795
|
+
// A bare `import a.b.c` of an internal module (the standard Django
|
|
1796
|
+
// `AppConfig.ready(): import myapp.signals` registration pattern, and any
|
|
1797
|
+
// `import pkg.mod` used for its side effects) had no edge to the module
|
|
1798
|
+
// file — only `from x import y` was linked. Push an `imports` ref (like
|
|
1799
|
+
// Go) so the resolver maps the dotted path to its file. Stdlib/external
|
|
1800
|
+
// modules naturally don't resolve (no `os.py` file node in the repo).
|
|
1801
|
+
const pushModuleRef = (dotted) => {
|
|
1802
|
+
if (!importParentId)
|
|
1803
|
+
return;
|
|
1804
|
+
this.unresolvedReferences.push({
|
|
1805
|
+
fromNodeId: importParentId,
|
|
1806
|
+
referenceName: (0, tree_sitter_helpers_1.getNodeText)(dotted, this.source),
|
|
1807
|
+
referenceKind: 'imports',
|
|
1808
|
+
line: dotted.startPosition.row + 1,
|
|
1809
|
+
column: dotted.startPosition.column,
|
|
1810
|
+
});
|
|
1811
|
+
};
|
|
1812
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1813
|
+
const child = node.namedChild(i);
|
|
1814
|
+
if (child?.type === 'dotted_name') {
|
|
1815
|
+
this.createNode('import', (0, tree_sitter_helpers_1.getNodeText)(child, this.source), node, {
|
|
1816
|
+
signature: importText,
|
|
1817
|
+
});
|
|
1818
|
+
pushModuleRef(child);
|
|
1819
|
+
}
|
|
1820
|
+
else if (child?.type === 'aliased_import') {
|
|
1821
|
+
const dottedName = child.namedChildren.find(c => c.type === 'dotted_name');
|
|
1822
|
+
if (dottedName) {
|
|
1823
|
+
this.createNode('import', (0, tree_sitter_helpers_1.getNodeText)(dottedName, this.source), node, {
|
|
1824
|
+
signature: importText,
|
|
1825
|
+
});
|
|
1826
|
+
pushModuleRef(dottedName);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
// Go imports: single or grouped (creates one import per spec)
|
|
1833
|
+
if (this.language === 'go') {
|
|
1834
|
+
const parentId = this.nodeStack.length > 0 ? this.nodeStack[this.nodeStack.length - 1] : null;
|
|
1835
|
+
const extractFromSpec = (spec) => {
|
|
1836
|
+
const stringLiteral = spec.namedChildren.find(c => c.type === 'interpreted_string_literal');
|
|
1837
|
+
if (stringLiteral) {
|
|
1838
|
+
const importPath = (0, tree_sitter_helpers_1.getNodeText)(stringLiteral, this.source).replace(/['"]/g, '');
|
|
1839
|
+
if (importPath) {
|
|
1840
|
+
this.createNode('import', importPath, spec, {
|
|
1841
|
+
signature: (0, tree_sitter_helpers_1.getNodeText)(spec, this.source).trim(),
|
|
1842
|
+
});
|
|
1843
|
+
// Create unresolved reference so the resolver can create imports edges
|
|
1844
|
+
if (parentId) {
|
|
1845
|
+
this.unresolvedReferences.push({
|
|
1846
|
+
fromNodeId: parentId,
|
|
1847
|
+
referenceName: importPath,
|
|
1848
|
+
referenceKind: 'imports',
|
|
1849
|
+
line: spec.startPosition.row + 1,
|
|
1850
|
+
column: spec.startPosition.column,
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
const importSpecList = node.namedChildren.find(c => c.type === 'import_spec_list');
|
|
1857
|
+
if (importSpecList) {
|
|
1858
|
+
for (const spec of importSpecList.namedChildren.filter(c => c.type === 'import_spec')) {
|
|
1859
|
+
extractFromSpec(spec);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
else {
|
|
1863
|
+
const importSpec = node.namedChildren.find(c => c.type === 'import_spec');
|
|
1864
|
+
if (importSpec) {
|
|
1865
|
+
extractFromSpec(importSpec);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
// PHP grouped imports: use X\{A, B} (creates one import per item)
|
|
1871
|
+
if (this.language === 'php') {
|
|
1872
|
+
const namespacePrefix = node.namedChildren.find(c => c.type === 'namespace_name');
|
|
1873
|
+
const useGroup = node.namedChildren.find(c => c.type === 'namespace_use_group');
|
|
1874
|
+
if (namespacePrefix && useGroup) {
|
|
1875
|
+
const prefix = (0, tree_sitter_helpers_1.getNodeText)(namespacePrefix, this.source);
|
|
1876
|
+
const useClauses = useGroup.namedChildren.filter((c) => c.type === 'namespace_use_group_clause' || c.type === 'namespace_use_clause');
|
|
1877
|
+
for (const clause of useClauses) {
|
|
1878
|
+
const nsName = clause.namedChildren.find((c) => c.type === 'namespace_name');
|
|
1879
|
+
const name = nsName
|
|
1880
|
+
? nsName.namedChildren.find((c) => c.type === 'name')
|
|
1881
|
+
: clause.namedChildren.find((c) => c.type === 'name');
|
|
1882
|
+
if (name) {
|
|
1883
|
+
const fullPath = `${prefix}\\${(0, tree_sitter_helpers_1.getNodeText)(name, this.source)}`;
|
|
1884
|
+
this.createNode('import', fullPath, node, {
|
|
1885
|
+
signature: importText,
|
|
1886
|
+
});
|
|
1887
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1888
|
+
if (parentId)
|
|
1889
|
+
this.pushPhpUseRef(fullPath, parentId, node);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
// If a hook exists but returned null, it intentionally declined this node — don't create fallback
|
|
1896
|
+
if (this.extractor.extractImport)
|
|
1897
|
+
return;
|
|
1898
|
+
// Generic fallback for languages without hooks
|
|
1899
|
+
this.createNode('import', importText, node, {
|
|
1900
|
+
signature: importText,
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Emit one `imports` reference per named/default import binding (TS/JS family),
|
|
1905
|
+
* attributed to the file node — so the resolver links each imported symbol to
|
|
1906
|
+
* the file that DEFINES it.
|
|
1907
|
+
*
|
|
1908
|
+
* Importing a symbol IS a dependency, but extraction only emits references for
|
|
1909
|
+
* calls, instantiations, type annotations, and inheritance. A symbol that's
|
|
1910
|
+
* imported and then only re-exported (`export { X } from './x'`), placed in a
|
|
1911
|
+
* registry array (`[expressResolver, …]`), passed as an argument, or used in
|
|
1912
|
+
* JSX produced NO cross-file edge at all — so the providing file showed a
|
|
1913
|
+
* false "0 dependents" and was invisible to blast-radius / `affected`. The
|
|
1914
|
+
* resolver maps the local name (alias-aware) to the provider's definition and
|
|
1915
|
+
* creates a cross-file `imports` edge; `getFileDependents` picks it up, while
|
|
1916
|
+
* `getImpactRadius` keeps it as a bounded leaf (the importing file node).
|
|
1917
|
+
*
|
|
1918
|
+
* Namespace imports (`import * as NS`) bind a whole module: `NS.member` calls
|
|
1919
|
+
* resolve on their own, but a namespace used ONLY via a value-member read
|
|
1920
|
+
* (`NS.SOME_CONST`) would leave no edge — so we also emit the namespace local
|
|
1921
|
+
* name, which the resolver links to the module FILE as a dependency backstop.
|
|
1922
|
+
*/
|
|
1923
|
+
emitImportBindingRefs(node, fromNodeId) {
|
|
1924
|
+
const clause = node.namedChildren.find((c) => c.type === 'import_clause');
|
|
1925
|
+
if (!clause)
|
|
1926
|
+
return; // side-effect import (`import './x'`) — no bindings
|
|
1927
|
+
const pushRef = (nameNode) => {
|
|
1928
|
+
if (!nameNode)
|
|
1929
|
+
return;
|
|
1930
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1931
|
+
if (!name)
|
|
1932
|
+
return;
|
|
1933
|
+
this.unresolvedReferences.push({
|
|
1934
|
+
fromNodeId,
|
|
1935
|
+
referenceName: name,
|
|
1936
|
+
referenceKind: 'imports',
|
|
1937
|
+
line: nameNode.startPosition.row + 1,
|
|
1938
|
+
column: nameNode.startPosition.column,
|
|
1939
|
+
});
|
|
1940
|
+
};
|
|
1941
|
+
for (const child of clause.namedChildren) {
|
|
1942
|
+
if (child.type === 'identifier') {
|
|
1943
|
+
// default import: `import Foo from './x'`
|
|
1944
|
+
pushRef(child);
|
|
1945
|
+
}
|
|
1946
|
+
else if (child.type === 'named_imports') {
|
|
1947
|
+
// `import { A, B as C } from './x'` — link the LOCAL name (alias if any)
|
|
1948
|
+
for (const spec of child.namedChildren) {
|
|
1949
|
+
if (spec.type !== 'import_specifier')
|
|
1950
|
+
continue;
|
|
1951
|
+
pushRef((0, tree_sitter_helpers_1.getChildByField)(spec, 'alias') ?? (0, tree_sitter_helpers_1.getChildByField)(spec, 'name') ?? spec.namedChild(0));
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
else if (child.type === 'namespace_import') {
|
|
1955
|
+
// `import * as NS from './x'` — emit NS so the module-import backstop can
|
|
1956
|
+
// record the file dependency even if NS is only used by value-member read.
|
|
1957
|
+
pushRef(child.namedChildren.find((c) => c.type === 'identifier') ?? child.namedChild(0));
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Emit one `imports` reference per re-exported binding of a
|
|
1963
|
+
* `export { A, B as C } from './y'` statement, attributed to the file node —
|
|
1964
|
+
* so a barrel that re-exports from another module records a dependency on it.
|
|
1965
|
+
*
|
|
1966
|
+
* Links the SOURCE-side name (`A`, the `name` field — not the local alias
|
|
1967
|
+
* `C`), since that is what the source module defines. `export * from './y'`
|
|
1968
|
+
* has no named bindings to attribute and `export { default as X }` can't be
|
|
1969
|
+
* name-matched, so both are skipped.
|
|
1970
|
+
*/
|
|
1971
|
+
emitReExportRefs(node, fromNodeId) {
|
|
1972
|
+
const clause = node.namedChildren.find((c) => c.type === 'export_clause');
|
|
1973
|
+
if (!clause)
|
|
1974
|
+
return; // `export * from './y'` — no named bindings
|
|
1975
|
+
for (const spec of clause.namedChildren) {
|
|
1976
|
+
if (spec.type !== 'export_specifier')
|
|
1977
|
+
continue;
|
|
1978
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(spec, 'name') ?? spec.namedChild(0);
|
|
1979
|
+
if (!nameNode)
|
|
1980
|
+
continue;
|
|
1981
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1982
|
+
if (!name || name === 'default')
|
|
1983
|
+
continue;
|
|
1984
|
+
this.unresolvedReferences.push({
|
|
1985
|
+
fromNodeId,
|
|
1986
|
+
referenceName: name,
|
|
1987
|
+
referenceKind: 'imports',
|
|
1988
|
+
line: nameNode.startPosition.row + 1,
|
|
1989
|
+
column: nameNode.startPosition.column,
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* Emit one `imports` reference per binding of a Rust `use` declaration —
|
|
1995
|
+
* `use crate::m::Item`, `use crate::m::{A, B as C}`, `pub use self::sub::Item`.
|
|
1996
|
+
* Emits the FULL path (e.g. `self::sub::Item`, not just `Item`) so the resolver
|
|
1997
|
+
* can resolve the module prefix to a file and find the leaf symbol there —
|
|
1998
|
+
* disambiguating common-name re-exports (`pub use self::read::read`, where the
|
|
1999
|
+
* leaf `read` collides with many same-named symbols). Falls back to name-match
|
|
2000
|
+
* on the leaf when the path can't be resolved. `use ...::*` has no leaf binding.
|
|
2001
|
+
*/
|
|
2002
|
+
emitRustUseBindingRefs(node, fromNodeId) {
|
|
2003
|
+
const paths = [];
|
|
2004
|
+
const join = (prefix, seg) => (prefix ? `${prefix}::${seg}` : seg);
|
|
2005
|
+
const collect = (n, prefix) => {
|
|
2006
|
+
switch (n.type) {
|
|
2007
|
+
case 'identifier':
|
|
2008
|
+
paths.push({ text: join(prefix, (0, tree_sitter_helpers_1.getNodeText)(n, this.source)), node: n });
|
|
2009
|
+
break;
|
|
2010
|
+
case 'scoped_identifier': {
|
|
2011
|
+
// Full scoped path (`a::b::C`); combine with any outer group prefix.
|
|
2012
|
+
const full = (0, tree_sitter_helpers_1.getNodeText)(n, this.source).trim();
|
|
2013
|
+
paths.push({ text: prefix ? `${prefix}::${full}` : full, node: n });
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
case 'scoped_use_list': {
|
|
2017
|
+
// `path::{ ... }` — the group's path becomes the prefix for each item.
|
|
2018
|
+
const pathNode = (0, tree_sitter_helpers_1.getChildByField)(n, 'path');
|
|
2019
|
+
const seg = pathNode ? (0, tree_sitter_helpers_1.getNodeText)(pathNode, this.source).trim() : '';
|
|
2020
|
+
const newPrefix = seg ? join(prefix, seg) : prefix;
|
|
2021
|
+
const list = (0, tree_sitter_helpers_1.getChildByField)(n, 'list') ?? n.namedChildren.find((c) => c.type === 'use_list');
|
|
2022
|
+
if (list)
|
|
2023
|
+
collect(list, newPrefix);
|
|
2024
|
+
break;
|
|
2025
|
+
}
|
|
2026
|
+
case 'use_list':
|
|
2027
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2028
|
+
const c = n.namedChild(i);
|
|
2029
|
+
if (c)
|
|
2030
|
+
collect(c, prefix);
|
|
2031
|
+
}
|
|
2032
|
+
break;
|
|
2033
|
+
case 'use_as_clause': {
|
|
2034
|
+
// `Path as Alias` → link the source path (the definition), not the alias.
|
|
2035
|
+
const p = (0, tree_sitter_helpers_1.getChildByField)(n, 'path') ?? n.namedChild(0);
|
|
2036
|
+
if (p)
|
|
2037
|
+
collect(p, prefix);
|
|
2038
|
+
break;
|
|
2039
|
+
}
|
|
2040
|
+
// use_wildcard → no specific binding to link.
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2044
|
+
const c = node.namedChild(i);
|
|
2045
|
+
if (c)
|
|
2046
|
+
collect(c, '');
|
|
2047
|
+
}
|
|
2048
|
+
for (const p of paths) {
|
|
2049
|
+
// The leaf must be a real name (skip a path that is only `self`/`super`/`crate`).
|
|
2050
|
+
const leaf = p.text.split('::').pop();
|
|
2051
|
+
if (!leaf || leaf === 'self' || leaf === 'super' || leaf === 'crate' || leaf === '*')
|
|
2052
|
+
continue;
|
|
2053
|
+
this.unresolvedReferences.push({
|
|
2054
|
+
fromNodeId,
|
|
2055
|
+
referenceName: p.text,
|
|
2056
|
+
referenceKind: 'imports',
|
|
2057
|
+
line: p.node.startPosition.row + 1,
|
|
2058
|
+
column: p.node.startPosition.column,
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Emit an `imports` reference for a single PHP `use Foo\Bar\Baz;` (grouped
|
|
2064
|
+
* imports `use Foo\{A, B}` are handled where their per-item nodes are created).
|
|
2065
|
+
* The reference targets the namespace-qualified `Foo\Bar::Baz` form classes are
|
|
2066
|
+
* stored under (see the PHP `namespace` capture), so it resolves to the RIGHT
|
|
2067
|
+
* definition — Laravel has many same-named contracts (`Factory`, `Dispatcher`,
|
|
2068
|
+
* `Guard`) across namespaces that a bare-name match can't disambiguate.
|
|
2069
|
+
*/
|
|
2070
|
+
emitPhpUseRefs(node, fromNodeId) {
|
|
2071
|
+
const clause = node.namedChildren.find((c) => c.type === 'namespace_use_clause');
|
|
2072
|
+
if (!clause)
|
|
2073
|
+
return;
|
|
2074
|
+
const qn = clause.namedChildren.find((c) => c.type === 'qualified_name')
|
|
2075
|
+
?? clause.namedChildren.find((c) => c.type === 'name');
|
|
2076
|
+
if (qn)
|
|
2077
|
+
this.pushPhpUseRef((0, tree_sitter_helpers_1.getNodeText)(qn, this.source), fromNodeId, node);
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Ruby `require`/`require_relative` → an `imports` ref to the required FILE.
|
|
2081
|
+
* `require "sidekiq/fetch"` is load-path-relative (matched by file-path suffix
|
|
2082
|
+
* via {@link matchByFilePath}); `require_relative "../foo"` is resolved against
|
|
2083
|
+
* this file's directory. Bare gem/stdlib requires (`require "json"`, no slash)
|
|
2084
|
+
* are skipped — they're external. The path form (a `/` + `.rb`) makes the ref
|
|
2085
|
+
* resolve to the file node, so a file pulled in only by `require` — not by a
|
|
2086
|
+
* resolved constant/call — still records a cross-file dependency.
|
|
2087
|
+
*/
|
|
2088
|
+
emitRubyRequireRefs(node, fromNodeId) {
|
|
2089
|
+
const method = node.namedChildren.find((c) => c.type === 'identifier');
|
|
2090
|
+
const mname = method ? (0, tree_sitter_helpers_1.getNodeText)(method, this.source) : '';
|
|
2091
|
+
if (mname !== 'require' && mname !== 'require_relative')
|
|
2092
|
+
return;
|
|
2093
|
+
const argList = node.namedChildren.find((c) => c.type === 'argument_list');
|
|
2094
|
+
const str = argList?.namedChildren.find((c) => c.type === 'string');
|
|
2095
|
+
const content = str?.namedChildren.find((c) => c.type === 'string_content');
|
|
2096
|
+
if (!content)
|
|
2097
|
+
return;
|
|
2098
|
+
const req = (0, tree_sitter_helpers_1.getNodeText)(content, this.source).trim();
|
|
2099
|
+
if (!req)
|
|
2100
|
+
return;
|
|
2101
|
+
let refPath;
|
|
2102
|
+
if (mname === 'require_relative') {
|
|
2103
|
+
const slash = this.filePath.lastIndexOf('/');
|
|
2104
|
+
const dir = slash >= 0 ? this.filePath.slice(0, slash) : '';
|
|
2105
|
+
refPath = path.posix.normalize(dir ? `${dir}/${req}` : req);
|
|
2106
|
+
}
|
|
2107
|
+
else {
|
|
2108
|
+
refPath = req; // load-path require — suffix-matched against the file path
|
|
2109
|
+
}
|
|
2110
|
+
if (!refPath.includes('/'))
|
|
2111
|
+
return; // bare gem/stdlib require — external
|
|
2112
|
+
if (!refPath.endsWith('.rb'))
|
|
2113
|
+
refPath += '.rb';
|
|
2114
|
+
this.unresolvedReferences.push({
|
|
2115
|
+
fromNodeId,
|
|
2116
|
+
referenceName: refPath,
|
|
2117
|
+
referenceKind: 'imports',
|
|
2118
|
+
line: node.startPosition.row + 1,
|
|
2119
|
+
column: node.startPosition.column,
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
/** Convert a PHP FQN `Foo\Bar\Baz` to the stored `Foo\Bar::Baz` and emit an `imports` ref. */
|
|
2123
|
+
pushPhpUseRef(fqn, fromNodeId, node) {
|
|
2124
|
+
const clean = fqn.replace(/^\\/, '');
|
|
2125
|
+
const lastSep = clean.lastIndexOf('\\');
|
|
2126
|
+
if (lastSep < 0)
|
|
2127
|
+
return; // global-namespace class — already matches by simple name
|
|
2128
|
+
this.unresolvedReferences.push({
|
|
2129
|
+
fromNodeId,
|
|
2130
|
+
referenceName: `${clean.slice(0, lastSep)}::${clean.slice(lastSep + 1)}`,
|
|
2131
|
+
referenceKind: 'imports',
|
|
2132
|
+
line: node.startPosition.row + 1,
|
|
2133
|
+
column: node.startPosition.column,
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Emit one `imports` reference per name imported in a Python
|
|
2138
|
+
* `from module import A, B as C` statement, attributed to the file node — so
|
|
2139
|
+
* the resolver links each imported name to the module that DEFINES it.
|
|
2140
|
+
*
|
|
2141
|
+
* Same recall gap as TS: extraction only emitted references for calls,
|
|
2142
|
+
* instantiations, and inheritance, so a name imported and then used in a
|
|
2143
|
+
* non-call position (a list/dict literal, a default argument, a decorator
|
|
2144
|
+
* target, or simply re-exported through an `__init__.py` barrel) produced no
|
|
2145
|
+
* cross-file edge — the providing module showed a false "0 dependents". Links
|
|
2146
|
+
* the LOCAL name (alias when present, since that's what the resolver's import
|
|
2147
|
+
* mapping keys on); `from module import *` has no names to attribute.
|
|
2148
|
+
*/
|
|
2149
|
+
emitPyFromImportRefs(node, fromNodeId) {
|
|
2150
|
+
const moduleNameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'module_name');
|
|
2151
|
+
for (const child of node.namedChildren) {
|
|
2152
|
+
// Skip the `from <module>` part itself and `import *`.
|
|
2153
|
+
if (moduleNameNode &&
|
|
2154
|
+
child.startIndex === moduleNameNode.startIndex &&
|
|
2155
|
+
child.endIndex === moduleNameNode.endIndex)
|
|
2156
|
+
continue;
|
|
2157
|
+
if (child.type === 'wildcard_import')
|
|
2158
|
+
continue;
|
|
2159
|
+
let nameNode = null;
|
|
2160
|
+
if (child.type === 'aliased_import') {
|
|
2161
|
+
nameNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'alias') ?? (0, tree_sitter_helpers_1.getChildByField)(child, 'name') ?? child.namedChild(0);
|
|
2162
|
+
}
|
|
2163
|
+
else if (child.type === 'dotted_name') {
|
|
2164
|
+
nameNode = child;
|
|
2165
|
+
}
|
|
2166
|
+
if (!nameNode)
|
|
2167
|
+
continue;
|
|
2168
|
+
const raw = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
2169
|
+
// Imported names are simple identifiers; defensively take the last segment.
|
|
2170
|
+
const local = raw.includes('.') ? raw.split('.').pop() : raw;
|
|
2171
|
+
if (!local)
|
|
2172
|
+
continue;
|
|
2173
|
+
this.unresolvedReferences.push({
|
|
2174
|
+
fromNodeId,
|
|
2175
|
+
referenceName: local,
|
|
2176
|
+
referenceKind: 'imports',
|
|
2177
|
+
line: nameNode.startPosition.row + 1,
|
|
2178
|
+
column: nameNode.startPosition.column,
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2183
|
+
* Extract a function call
|
|
2184
|
+
*/
|
|
2185
|
+
extractCall(node) {
|
|
2186
|
+
if (this.nodeStack.length === 0)
|
|
2187
|
+
return;
|
|
2188
|
+
const callerId = this.nodeStack[this.nodeStack.length - 1];
|
|
2189
|
+
if (!callerId)
|
|
2190
|
+
return;
|
|
2191
|
+
// Get the function/method being called
|
|
2192
|
+
let calleeName = '';
|
|
2193
|
+
// Java/Kotlin method_invocation has 'object' + 'name' fields instead of 'function'
|
|
2194
|
+
// PHP member_call_expression has 'object' + 'name', scoped_call_expression has 'scope' + 'name'
|
|
2195
|
+
const nameField = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
2196
|
+
const objectField = (0, tree_sitter_helpers_1.getChildByField)(node, 'object') || (0, tree_sitter_helpers_1.getChildByField)(node, 'scope');
|
|
2197
|
+
if (nameField && objectField && (node.type === 'method_invocation' || node.type === 'member_call_expression' || node.type === 'scoped_call_expression')) {
|
|
2198
|
+
// Method call with explicit receiver: receiver.method() / $receiver->method() / ClassName::method()
|
|
2199
|
+
const methodName = (0, tree_sitter_helpers_1.getNodeText)(nameField, this.source);
|
|
2200
|
+
// Java `this.userbo.toLogin2()` parses as method_invocation(object=field_access(this, userbo)).
|
|
2201
|
+
// Without unwrapping, receiverName is `this.userbo` and the name-matcher's
|
|
2202
|
+
// single-dot receiver regex fails. Pull out the immediate field after `this.`
|
|
2203
|
+
// so the receiver is the field name (`userbo`), which the resolver can then
|
|
2204
|
+
// look up in the enclosing class's field declarations.
|
|
2205
|
+
let receiverName;
|
|
2206
|
+
if (objectField.type === 'field_access') {
|
|
2207
|
+
const inner = (0, tree_sitter_helpers_1.getChildByField)(objectField, 'object');
|
|
2208
|
+
const fld = (0, tree_sitter_helpers_1.getChildByField)(objectField, 'field');
|
|
2209
|
+
if (inner && fld && (inner.type === 'this' || inner.type === 'this_expression')) {
|
|
2210
|
+
receiverName = (0, tree_sitter_helpers_1.getNodeText)(fld, this.source);
|
|
2211
|
+
}
|
|
2212
|
+
else {
|
|
2213
|
+
receiverName = (0, tree_sitter_helpers_1.getNodeText)(objectField, this.source);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
else {
|
|
2217
|
+
receiverName = (0, tree_sitter_helpers_1.getNodeText)(objectField, this.source);
|
|
2218
|
+
}
|
|
2219
|
+
// Strip PHP $ prefix from variable names
|
|
2220
|
+
receiverName = receiverName.replace(/^\$/, '');
|
|
2221
|
+
if (methodName) {
|
|
2222
|
+
// Skip self/this/parent/static receivers — they don't aid resolution
|
|
2223
|
+
const SKIP_RECEIVERS = new Set(['self', 'this', 'cls', 'super', 'parent', 'static']);
|
|
2224
|
+
if (SKIP_RECEIVERS.has(receiverName)) {
|
|
2225
|
+
calleeName = methodName;
|
|
2226
|
+
}
|
|
2227
|
+
else {
|
|
2228
|
+
calleeName = `${receiverName}.${methodName}`;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
else if (node.type === 'message_expression') {
|
|
2233
|
+
// ObjC message expressions emit one `method` field child per selector
|
|
2234
|
+
// keyword: `[obj a:1 b:2 c:3]` has three `method=identifier` siblings.
|
|
2235
|
+
// Joining them with `:` reconstructs the full selector and matches the
|
|
2236
|
+
// multi-part selector names produced by the ObjC method_definition
|
|
2237
|
+
// extractor (`extractObjcMethodName` in languages/objc.ts). Without this
|
|
2238
|
+
// join, multi-keyword call sites only emitted the first keyword and never
|
|
2239
|
+
// resolved to their target methods (e.g. `GET:parameters:headers:...` had
|
|
2240
|
+
// zero callers despite obviously being called).
|
|
2241
|
+
const methodKeywords = [];
|
|
2242
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2243
|
+
if (node.fieldNameForNamedChild(i) === 'method') {
|
|
2244
|
+
const kw = node.namedChild(i);
|
|
2245
|
+
if (kw)
|
|
2246
|
+
methodKeywords.push((0, tree_sitter_helpers_1.getNodeText)(kw, this.source));
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
if (methodKeywords.length > 0) {
|
|
2250
|
+
// A selector keyword takes a `:` when it has an argument. A SINGLE
|
|
2251
|
+
// keyword can be unary (`[c reset]` → `reset`) OR take one argument
|
|
2252
|
+
// (`[c storeImage:k]` → `storeImage:`) — distinguished by whether the
|
|
2253
|
+
// message has a `:` token. Without this, every single-argument message
|
|
2254
|
+
// (the most common form: `addObject:`, `storeImage:`, …) was named
|
|
2255
|
+
// without the colon and never matched its `storeImage:` method.
|
|
2256
|
+
let hasColon = false;
|
|
2257
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2258
|
+
if (node.child(i)?.type === ':') {
|
|
2259
|
+
hasColon = true;
|
|
2260
|
+
break;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
const methodName = hasColon
|
|
2264
|
+
? methodKeywords.map((k) => `${k}:`).join('')
|
|
2265
|
+
: methodKeywords[0];
|
|
2266
|
+
const receiverField = (0, tree_sitter_helpers_1.getChildByField)(node, 'receiver');
|
|
2267
|
+
const SKIP_RECEIVERS = new Set(['self', 'super']);
|
|
2268
|
+
if (receiverField && receiverField.type !== 'message_expression') {
|
|
2269
|
+
const receiverName = (0, tree_sitter_helpers_1.getNodeText)(receiverField, this.source);
|
|
2270
|
+
if (receiverName && !SKIP_RECEIVERS.has(receiverName)) {
|
|
2271
|
+
calleeName = `${receiverName}.${methodName}`;
|
|
2272
|
+
// A CLASS-message receiver (`[SDImageCache alloc]`,
|
|
2273
|
+
// `[SDImageCache sharedCache]`) is a capitalized class name. The
|
|
2274
|
+
// call resolves the method (`alloc`/`sharedCache`), but the CLASS
|
|
2275
|
+
// itself — whose @interface lives in the header — would otherwise
|
|
2276
|
+
// never be referenced. Emit a `references` edge to it so a class
|
|
2277
|
+
// used only via class messages (alloc/init, singletons, factories)
|
|
2278
|
+
// and its header record a dependent.
|
|
2279
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(receiverName)) {
|
|
2280
|
+
this.unresolvedReferences.push({
|
|
2281
|
+
fromNodeId: callerId,
|
|
2282
|
+
referenceName: receiverName,
|
|
2283
|
+
referenceKind: 'references',
|
|
2284
|
+
line: receiverField.startPosition.row + 1,
|
|
2285
|
+
column: receiverField.startPosition.column,
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
else {
|
|
2290
|
+
calleeName = methodName;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
else {
|
|
2294
|
+
calleeName = methodName;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
else {
|
|
2299
|
+
const func = (0, tree_sitter_helpers_1.getChildByField)(node, 'function') || node.namedChild(0);
|
|
2300
|
+
if (func) {
|
|
2301
|
+
if (func.type === 'member_expression' || func.type === 'attribute' || func.type === 'selector_expression' || func.type === 'navigation_expression' || func.type === 'field_expression') {
|
|
2302
|
+
// Method call: obj.method() or obj.field.method()
|
|
2303
|
+
// Go uses selector_expression with 'field', JS/TS uses member_expression with 'property'
|
|
2304
|
+
// Kotlin uses navigation_expression with navigation_suffix > simple_identifier
|
|
2305
|
+
// C/C++ use field_expression for both `obj.method()` and `ptr->method()`
|
|
2306
|
+
let property = (0, tree_sitter_helpers_1.getChildByField)(func, 'property') || (0, tree_sitter_helpers_1.getChildByField)(func, 'field');
|
|
2307
|
+
if (!property) {
|
|
2308
|
+
const child1 = func.namedChild(1);
|
|
2309
|
+
// Kotlin: navigation_suffix wraps the method name — extract simple_identifier from it
|
|
2310
|
+
if (child1?.type === 'navigation_suffix') {
|
|
2311
|
+
property = child1.namedChildren.find((c) => c.type === 'simple_identifier') ?? child1;
|
|
2312
|
+
}
|
|
2313
|
+
else {
|
|
2314
|
+
property = child1;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
if (property) {
|
|
2318
|
+
const methodName = (0, tree_sitter_helpers_1.getNodeText)(property, this.source);
|
|
2319
|
+
// Include receiver name for qualified resolution (e.g., console.print → "console.print")
|
|
2320
|
+
// This helps the resolver distinguish method calls from bare function calls
|
|
2321
|
+
// (e.g., Python's console.print() vs builtin print())
|
|
2322
|
+
// Skip self/this/cls as they don't aid resolution
|
|
2323
|
+
const receiver = (0, tree_sitter_helpers_1.getChildByField)(func, 'object') ||
|
|
2324
|
+
(0, tree_sitter_helpers_1.getChildByField)(func, 'operand') ||
|
|
2325
|
+
(0, tree_sitter_helpers_1.getChildByField)(func, 'argument') ||
|
|
2326
|
+
func.namedChild(0);
|
|
2327
|
+
const SKIP_RECEIVERS = new Set(['self', 'this', 'cls', 'super']);
|
|
2328
|
+
if (receiver && (receiver.type === 'identifier' || receiver.type === 'simple_identifier' || receiver.type === 'field_identifier')) {
|
|
2329
|
+
const receiverName = (0, tree_sitter_helpers_1.getNodeText)(receiver, this.source);
|
|
2330
|
+
if (!SKIP_RECEIVERS.has(receiverName)) {
|
|
2331
|
+
calleeName = `${receiverName}.${methodName}`;
|
|
2332
|
+
}
|
|
2333
|
+
else {
|
|
2334
|
+
calleeName = methodName;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
else {
|
|
2338
|
+
calleeName = methodName;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
else if (func.type === 'scoped_identifier' || func.type === 'scoped_call_expression') {
|
|
2343
|
+
// Scoped call: Module::function()
|
|
2344
|
+
calleeName = (0, tree_sitter_helpers_1.getNodeText)(func, this.source);
|
|
2345
|
+
}
|
|
2346
|
+
else {
|
|
2347
|
+
calleeName = (0, tree_sitter_helpers_1.getNodeText)(func, this.source);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
// Parenthesized type conversions — Go `(*T)(x)` / `(T)(x)` (and a
|
|
2352
|
+
// parenthesized callee generally) parse as a call whose "function" is a
|
|
2353
|
+
// parenthesized type/expression, so the callee text is the un-resolvable
|
|
2354
|
+
// literal `(*T)`. Normalize to the inner name so it resolves to `T` (a real
|
|
2355
|
+
// dependency on the converted-to type) instead of dropping on the floor.
|
|
2356
|
+
if (calleeName) {
|
|
2357
|
+
const conv = calleeName.match(/^\(\s*\*?\s*([A-Za-z_][\w.]*)\s*\)$/);
|
|
2358
|
+
if (conv && conv[1])
|
|
2359
|
+
calleeName = conv[1];
|
|
2360
|
+
}
|
|
2361
|
+
if (calleeName) {
|
|
2362
|
+
this.unresolvedReferences.push({
|
|
2363
|
+
fromNodeId: callerId,
|
|
2364
|
+
referenceName: calleeName,
|
|
2365
|
+
referenceKind: 'calls',
|
|
2366
|
+
line: node.startPosition.row + 1,
|
|
2367
|
+
column: node.startPosition.column,
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
/**
|
|
2372
|
+
* `new Foo(...)` / `Foo::new(...)` / object_creation_expression —
|
|
2373
|
+
* emit an `instantiates` reference to the class name. The resolver
|
|
2374
|
+
* then links it to the class node, producing the `instantiates`
|
|
2375
|
+
* edge that powers "what creates instances of X" queries.
|
|
2376
|
+
*
|
|
2377
|
+
* Children are still walked so nested calls inside the constructor
|
|
2378
|
+
* arguments (`new Foo(bar())`) get their own `calls` references.
|
|
2379
|
+
*/
|
|
2380
|
+
extractInstantiation(node) {
|
|
2381
|
+
if (this.nodeStack.length === 0)
|
|
2382
|
+
return;
|
|
2383
|
+
const fromId = this.nodeStack[this.nodeStack.length - 1];
|
|
2384
|
+
if (!fromId)
|
|
2385
|
+
return;
|
|
2386
|
+
// The class name is in the `constructor`/`type`/first-named-child
|
|
2387
|
+
// depending on grammar.
|
|
2388
|
+
const ctor = (0, tree_sitter_helpers_1.getChildByField)(node, 'constructor') ||
|
|
2389
|
+
(0, tree_sitter_helpers_1.getChildByField)(node, 'type') ||
|
|
2390
|
+
(0, tree_sitter_helpers_1.getChildByField)(node, 'name') ||
|
|
2391
|
+
node.namedChild(0);
|
|
2392
|
+
if (!ctor)
|
|
2393
|
+
return;
|
|
2394
|
+
// Go composite literals: `Widget{...}` (same package) and `pkga.Widget{...}`
|
|
2395
|
+
// (cross-package). Only a directly-named struct type is a meaningful
|
|
2396
|
+
// instantiation target — skip slice/map/array literals (`[]T{}`,
|
|
2397
|
+
// `map[K]V{}`) whose `type` field is a composite type, not a named type.
|
|
2398
|
+
// Unlike `new ns.Foo()`, KEEP the package qualifier (`pkga.Widget`) so the
|
|
2399
|
+
// Go cross-package resolver can disambiguate it to the right package's type.
|
|
2400
|
+
if (node.type === 'composite_literal') {
|
|
2401
|
+
if (ctor.type !== 'type_identifier' && ctor.type !== 'qualified_type')
|
|
2402
|
+
return;
|
|
2403
|
+
let goType = (0, tree_sitter_helpers_1.getNodeText)(ctor, this.source).trim();
|
|
2404
|
+
const brIdx = goType.indexOf('['); // strip Go generic args: `Box[T]{}` -> `Box`
|
|
2405
|
+
if (brIdx > 0)
|
|
2406
|
+
goType = goType.slice(0, brIdx).trim();
|
|
2407
|
+
if (goType) {
|
|
2408
|
+
this.unresolvedReferences.push({
|
|
2409
|
+
fromNodeId: fromId,
|
|
2410
|
+
referenceName: goType,
|
|
2411
|
+
referenceKind: 'instantiates',
|
|
2412
|
+
line: node.startPosition.row + 1,
|
|
2413
|
+
column: node.startPosition.column,
|
|
2414
|
+
});
|
|
2415
|
+
}
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
// Scala: `new Monoid[Int] { ... }` — the constructor is a `generic_type`
|
|
2419
|
+
// (or qualified `stable_type_identifier`) using `[...]` type args, which the
|
|
2420
|
+
// generic `<...>` strip below misses. Unwrap to the base type name.
|
|
2421
|
+
if (node.type === 'instance_expression') {
|
|
2422
|
+
const name = scalaBaseTypeName(ctor, this.source);
|
|
2423
|
+
if (name) {
|
|
2424
|
+
this.unresolvedReferences.push({
|
|
2425
|
+
fromNodeId: fromId,
|
|
2426
|
+
referenceName: name,
|
|
2427
|
+
referenceKind: 'instantiates',
|
|
2428
|
+
line: node.startPosition.row + 1,
|
|
2429
|
+
column: node.startPosition.column,
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
let className = (0, tree_sitter_helpers_1.getNodeText)(ctor, this.source);
|
|
2435
|
+
// Strip type-argument suffix first: `new Map<K, V>()` would
|
|
2436
|
+
// otherwise produce className 'Map<K, V>' (the constructor
|
|
2437
|
+
// field is a `generic_type` node) and resolution would fail
|
|
2438
|
+
// because no class is named with the angle-bracket suffix.
|
|
2439
|
+
const ltIdx = className.indexOf('<');
|
|
2440
|
+
if (ltIdx > 0)
|
|
2441
|
+
className = className.slice(0, ltIdx);
|
|
2442
|
+
// For namespaced/qualified constructors (`new ns.Foo()`,
|
|
2443
|
+
// `new ns::Foo()`) keep the trailing identifier — that's what
|
|
2444
|
+
// matches a class node in the index.
|
|
2445
|
+
const lastDot = Math.max(className.lastIndexOf('.'), className.lastIndexOf('::'));
|
|
2446
|
+
if (lastDot >= 0)
|
|
2447
|
+
className = className.slice(lastDot + 1).replace(/^[:.]/, '');
|
|
2448
|
+
className = className.trim();
|
|
2449
|
+
if (className) {
|
|
2450
|
+
this.unresolvedReferences.push({
|
|
2451
|
+
fromNodeId: fromId,
|
|
2452
|
+
referenceName: className,
|
|
2453
|
+
referenceKind: 'instantiates',
|
|
2454
|
+
line: node.startPosition.row + 1,
|
|
2455
|
+
column: node.startPosition.column,
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* Static-member / value-read pass. A type/enum/class used only via a member
|
|
2461
|
+
* VALUE — `Enum.value`, `Type.CONST`, `Colors.red`, `Foo::BAR` — recorded no
|
|
2462
|
+
* edge, because the body walker only handled CALLS (`Type.method()`). So a
|
|
2463
|
+
* type referenced only by an enum value or a static field looked like nothing
|
|
2464
|
+
* depended on it (the residual frontier across Dart/Java/C#/Swift/Kotlin/PHP).
|
|
2465
|
+
* Emit a `references` edge to the capitalized receiver. Gated to languages
|
|
2466
|
+
* where types are Capitalized by convention, and skipped when the access is a
|
|
2467
|
+
* call's callee (the call extractor already links the method).
|
|
2468
|
+
*/
|
|
2469
|
+
extractStaticMemberRef(node) {
|
|
2470
|
+
if (!STATIC_MEMBER_LANGS.has(this.language))
|
|
2471
|
+
return;
|
|
2472
|
+
if (this.nodeStack.length === 0)
|
|
2473
|
+
return;
|
|
2474
|
+
const ownerId = this.nodeStack[this.nodeStack.length - 1];
|
|
2475
|
+
if (!ownerId)
|
|
2476
|
+
return;
|
|
2477
|
+
// Dart structures member access as an `identifier` + a sibling `selector`,
|
|
2478
|
+
// not a single node. A value-read selector (no `argument_part`) whose
|
|
2479
|
+
// previous sibling is a capitalized identifier is `Enum.value`.
|
|
2480
|
+
if (this.language === 'dart') {
|
|
2481
|
+
if (node.type !== 'selector')
|
|
2482
|
+
return;
|
|
2483
|
+
if (node.namedChildren.some((c) => c.type === 'argument_part'))
|
|
2484
|
+
return;
|
|
2485
|
+
const prev = node.previousNamedSibling;
|
|
2486
|
+
if (prev?.type === 'identifier' && /^[A-Z][A-Za-z0-9_]*$/.test(prev.text)) {
|
|
2487
|
+
this.pushStaticMemberRef(prev.text, ownerId, prev);
|
|
2488
|
+
}
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
if (!MEMBER_ACCESS_TYPES.has(node.type))
|
|
2492
|
+
return;
|
|
2493
|
+
// Skip `Type.method()` — the access is the callee of a call, already linked.
|
|
2494
|
+
const parent = node.parent;
|
|
2495
|
+
if (parent && this.extractor.callTypes.includes(parent.type)) {
|
|
2496
|
+
const callee = (0, tree_sitter_helpers_1.getChildByField)(parent, 'function') ??
|
|
2497
|
+
(0, tree_sitter_helpers_1.getChildByField)(parent, 'method') ??
|
|
2498
|
+
parent.namedChild(0);
|
|
2499
|
+
if (callee && callee.startIndex === node.startIndex)
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
// The receiver must be a SIMPLE capitalized identifier — `Type.X`, not the
|
|
2503
|
+
// nested `a.B.c` (whose own head member-access is visited separately) nor a
|
|
2504
|
+
// lowercase `obj.field` / `pkg.func`.
|
|
2505
|
+
const recv = (0, tree_sitter_helpers_1.getChildByField)(node, 'object') ??
|
|
2506
|
+
(0, tree_sitter_helpers_1.getChildByField)(node, 'expression') ??
|
|
2507
|
+
(0, tree_sitter_helpers_1.getChildByField)(node, 'scope') ??
|
|
2508
|
+
node.namedChild(0);
|
|
2509
|
+
if (!recv)
|
|
2510
|
+
return;
|
|
2511
|
+
const t = recv.type;
|
|
2512
|
+
if (t === 'identifier' || t === 'type_identifier' || t === 'simple_identifier' ||
|
|
2513
|
+
t === 'name' || t === 'scoped_type_identifier') {
|
|
2514
|
+
const text = (0, tree_sitter_helpers_1.getNodeText)(recv, this.source);
|
|
2515
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(text))
|
|
2516
|
+
this.pushStaticMemberRef(text, ownerId, recv);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
pushStaticMemberRef(name, ownerId, node) {
|
|
2520
|
+
this.unresolvedReferences.push({
|
|
2521
|
+
fromNodeId: ownerId,
|
|
2522
|
+
referenceName: name,
|
|
2523
|
+
referenceKind: 'references',
|
|
2524
|
+
line: node.startPosition.row + 1,
|
|
2525
|
+
column: node.startPosition.column,
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
/**
|
|
2529
|
+
* Find a `class_body` child of an `object_creation_expression` — the
|
|
2530
|
+
* marker for an anonymous class (`new T() { ... }`). Returns the body
|
|
2531
|
+
* node so the caller can walk it as the anon class's members.
|
|
2532
|
+
*/
|
|
2533
|
+
findAnonymousClassBody(node) {
|
|
2534
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2535
|
+
const child = node.namedChild(i);
|
|
2536
|
+
// Java: `class_body`. C# uses the same node kind.
|
|
2537
|
+
if (child && (child.type === 'class_body' || child.type === 'declaration_list')) {
|
|
2538
|
+
return child;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
return null;
|
|
2542
|
+
}
|
|
2543
|
+
/**
|
|
2544
|
+
* Extract a Java/C# anonymous class — `new T() { ...members }`. Emits a
|
|
2545
|
+
* `class` node named `<T$anon@line>`, an `extends` reference to T (so
|
|
2546
|
+
* Phase 5.5 interface-impl can bridge), and walks the body so its
|
|
2547
|
+
* `method_declaration` members become method nodes under the anon class.
|
|
2548
|
+
*
|
|
2549
|
+
* Why this matters: without anon-class extraction, the overrides inside
|
|
2550
|
+
* a lambda-returned `new T() { @Override int foo(){...} }` are not nodes,
|
|
2551
|
+
* so a call through T.foo (the abstract parent method) has no static
|
|
2552
|
+
* target — the agent has to Read the file to find the implementation.
|
|
2553
|
+
*/
|
|
2554
|
+
extractAnonymousClass(node, body) {
|
|
2555
|
+
if (!this.extractor)
|
|
2556
|
+
return;
|
|
2557
|
+
// The instantiated type sits in the same field/position that
|
|
2558
|
+
// extractInstantiation reads from. Use the same lookup so the anon
|
|
2559
|
+
// class's `extends` target matches the `instantiates` edge.
|
|
2560
|
+
const typeNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'constructor') ||
|
|
2561
|
+
(0, tree_sitter_helpers_1.getChildByField)(node, 'type') ||
|
|
2562
|
+
(0, tree_sitter_helpers_1.getChildByField)(node, 'name') ||
|
|
2563
|
+
node.namedChild(0);
|
|
2564
|
+
let typeName = typeNode ? (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source) : 'Object';
|
|
2565
|
+
const ltIdx = typeName.indexOf('<');
|
|
2566
|
+
if (ltIdx > 0)
|
|
2567
|
+
typeName = typeName.slice(0, ltIdx);
|
|
2568
|
+
const lastDot = Math.max(typeName.lastIndexOf('.'), typeName.lastIndexOf('::'));
|
|
2569
|
+
if (lastDot >= 0)
|
|
2570
|
+
typeName = typeName.slice(lastDot + 1).replace(/^[:.]/, '');
|
|
2571
|
+
typeName = typeName.trim() || 'Object';
|
|
2572
|
+
const anonName = `<${typeName}$anon@${node.startPosition.row + 1}>`;
|
|
2573
|
+
const classNode = this.createNode('class', anonName, node, {});
|
|
2574
|
+
if (!classNode)
|
|
2575
|
+
return;
|
|
2576
|
+
// The anonymous class implicitly extends/implements the named type.
|
|
2577
|
+
// We can't tell at extraction time whether T is a class or an interface,
|
|
2578
|
+
// so emit `extends`. Resolution will still bind T to whatever it is, and
|
|
2579
|
+
// Phase 5.5 (which already handles both `extends` and `implements`) will
|
|
2580
|
+
// bridge T's methods to the override names found in the anon body.
|
|
2581
|
+
this.unresolvedReferences.push({
|
|
2582
|
+
fromNodeId: classNode.id,
|
|
2583
|
+
referenceName: typeName,
|
|
2584
|
+
referenceKind: 'extends',
|
|
2585
|
+
line: typeNode?.startPosition.row ?? node.startPosition.row,
|
|
2586
|
+
column: typeNode?.startPosition.column ?? node.startPosition.column,
|
|
2587
|
+
});
|
|
2588
|
+
// Walk the body's children so method_declaration nodes inside become
|
|
2589
|
+
// method nodes scoped to the anon class.
|
|
2590
|
+
this.nodeStack.push(classNode.id);
|
|
2591
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
2592
|
+
const child = body.namedChild(i);
|
|
2593
|
+
if (child)
|
|
2594
|
+
this.visitNode(child);
|
|
2595
|
+
}
|
|
2596
|
+
this.nodeStack.pop();
|
|
2597
|
+
}
|
|
2598
|
+
/**
|
|
2599
|
+
* Scan `declNode` and its preceding siblings (within the parent's
|
|
2600
|
+
* named children) for decorator nodes, emitting a `decorates`
|
|
2601
|
+
* reference from `decoratedId` to each decorator's function name.
|
|
2602
|
+
*
|
|
2603
|
+
* Why preceding siblings: in TypeScript, `@Foo class Bar {}` parses
|
|
2604
|
+
* as an `export_statement` (or top-level wrapper) with the
|
|
2605
|
+
* `decorator` as a child *before* the `class_declaration` — so the
|
|
2606
|
+
* decorator isn't a child of the class itself. For methods/
|
|
2607
|
+
* properties, the decorator IS a direct child of the declaration,
|
|
2608
|
+
* so we also scan declNode.namedChildren.
|
|
2609
|
+
*
|
|
2610
|
+
* Idempotent across grammars: if neither location yields decorators
|
|
2611
|
+
* (most non-decorator-using languages), the function is a no-op.
|
|
2612
|
+
*/
|
|
2613
|
+
extractDecoratorsFor(declNode, decoratedId) {
|
|
2614
|
+
const consider = (n) => {
|
|
2615
|
+
if (!n)
|
|
2616
|
+
return;
|
|
2617
|
+
// `marker_annotation` is Java's grammar for arg-less annotations
|
|
2618
|
+
// (`@Override`, `@Deprecated`); `attribute` is Swift's grammar for
|
|
2619
|
+
// attributes and PROPERTY WRAPPERS (`@objc`, `@Argument`, `@Published`,
|
|
2620
|
+
// `@State`). Without these, those usages would be silently skipped.
|
|
2621
|
+
if (n.type !== 'decorator' &&
|
|
2622
|
+
n.type !== 'annotation' &&
|
|
2623
|
+
n.type !== 'marker_annotation' &&
|
|
2624
|
+
n.type !== 'attribute') {
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
// Find the leading identifier: skip the `@` punct, unwrap
|
|
2628
|
+
// a call_expression if the decorator is invoked with args.
|
|
2629
|
+
let target = null;
|
|
2630
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
2631
|
+
const child = n.namedChild(i);
|
|
2632
|
+
if (!child)
|
|
2633
|
+
continue;
|
|
2634
|
+
if (child.type === 'call_expression') {
|
|
2635
|
+
const fn = (0, tree_sitter_helpers_1.getChildByField)(child, 'function') ?? child.namedChild(0);
|
|
2636
|
+
if (fn)
|
|
2637
|
+
target = fn;
|
|
2638
|
+
if (target)
|
|
2639
|
+
break;
|
|
2640
|
+
}
|
|
2641
|
+
if (child.type === 'identifier' ||
|
|
2642
|
+
child.type === 'member_expression' ||
|
|
2643
|
+
child.type === 'scoped_identifier' ||
|
|
2644
|
+
child.type === 'navigation_expression' ||
|
|
2645
|
+
child.type === 'user_type' || // swift attribute → user_type (`@Argument`)
|
|
2646
|
+
child.type === 'type_identifier') {
|
|
2647
|
+
target = child;
|
|
2648
|
+
break;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
if (!target)
|
|
2652
|
+
return;
|
|
2653
|
+
let name = (0, tree_sitter_helpers_1.getNodeText)(target, this.source);
|
|
2654
|
+
const lt = name.indexOf('<'); // strip generic args: `@Argument<T>` → `Argument`
|
|
2655
|
+
if (lt > 0)
|
|
2656
|
+
name = name.slice(0, lt);
|
|
2657
|
+
const lastDot = Math.max(name.lastIndexOf('.'), name.lastIndexOf('::'));
|
|
2658
|
+
if (lastDot >= 0)
|
|
2659
|
+
name = name.slice(lastDot + 1).replace(/^[:.]/, '');
|
|
2660
|
+
name = name.trim();
|
|
2661
|
+
if (!name)
|
|
2662
|
+
return;
|
|
2663
|
+
this.unresolvedReferences.push({
|
|
2664
|
+
fromNodeId: decoratedId,
|
|
2665
|
+
referenceName: name,
|
|
2666
|
+
referenceKind: 'decorates',
|
|
2667
|
+
line: n.startPosition.row + 1,
|
|
2668
|
+
column: n.startPosition.column,
|
|
2669
|
+
});
|
|
2670
|
+
};
|
|
2671
|
+
// 1. Decorators that are direct children of the declaration
|
|
2672
|
+
// (method/property style, also some grammars for class).
|
|
2673
|
+
for (let i = 0; i < declNode.namedChildCount; i++) {
|
|
2674
|
+
const child = declNode.namedChild(i);
|
|
2675
|
+
consider(child);
|
|
2676
|
+
// Java/Kotlin/C# put annotations INSIDE a `modifiers` node
|
|
2677
|
+
// (`@MyAnno public class X` → class_declaration → modifiers → annotation),
|
|
2678
|
+
// so descend into it — otherwise every annotation usage is silently
|
|
2679
|
+
// dropped and annotation types show zero dependents.
|
|
2680
|
+
if (child && child.type === 'modifiers') {
|
|
2681
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
2682
|
+
consider(child.namedChild(j));
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
// 2. Decorators that are PRECEDING siblings of the declaration
|
|
2687
|
+
// inside the parent's children (TypeScript class style).
|
|
2688
|
+
// Walk BACKWARDS from the declaration and stop at the first
|
|
2689
|
+
// non-decorator sibling — without that stop, decorators
|
|
2690
|
+
// belonging to an EARLIER unrelated declaration leak in
|
|
2691
|
+
// (e.g. `@A class Foo {} @B class Bar {}` would otherwise
|
|
2692
|
+
// attribute @A to Bar).
|
|
2693
|
+
//
|
|
2694
|
+
// Note on identity: tree-sitter web bindings return fresh JS
|
|
2695
|
+
// wrapper objects from `parent`/`namedChild` navigation, so
|
|
2696
|
+
// `sibling === declNode` is unreliable — `startIndex` does
|
|
2697
|
+
// the matching instead.
|
|
2698
|
+
const parent = declNode.parent;
|
|
2699
|
+
if (parent) {
|
|
2700
|
+
const declStart = declNode.startIndex;
|
|
2701
|
+
let declIdx = -1;
|
|
2702
|
+
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
2703
|
+
const sibling = parent.namedChild(i);
|
|
2704
|
+
if (sibling && sibling.startIndex === declStart) {
|
|
2705
|
+
declIdx = i;
|
|
2706
|
+
break;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
if (declIdx > 0) {
|
|
2710
|
+
for (let j = declIdx - 1; j >= 0; j--) {
|
|
2711
|
+
const sibling = parent.namedChild(j);
|
|
2712
|
+
if (!sibling)
|
|
2713
|
+
continue;
|
|
2714
|
+
if (sibling.type !== 'decorator' && sibling.type !== 'annotation' && sibling.type !== 'marker_annotation') {
|
|
2715
|
+
break; // non-decorator separator → stop consuming
|
|
2716
|
+
}
|
|
2717
|
+
consider(sibling);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Visit function body and extract calls (and structural nodes).
|
|
2724
|
+
*
|
|
2725
|
+
* In addition to call expressions, this also detects class/struct/enum
|
|
2726
|
+
* definitions inside function bodies. This handles two cases:
|
|
2727
|
+
* 1. Local class/struct/enum definitions (valid in C++, Java, etc.)
|
|
2728
|
+
* 2. C++ macro misparsing — macros like NLOHMANN_JSON_NAMESPACE_BEGIN cause
|
|
2729
|
+
* tree-sitter to interpret the namespace block as a function_definition,
|
|
2730
|
+
* hiding real class/struct/enum nodes inside the "function body".
|
|
2731
|
+
*/
|
|
2732
|
+
/**
|
|
2733
|
+
* Rocket route-registration macros — `routes![a::b::handler, c::d::other]`
|
|
2734
|
+
* and `catchers![not_found]`. Tree-sitter leaves a macro body as a flat
|
|
2735
|
+
* `token_tree` of raw tokens (`identifier`, `::`, `,`), so the handler paths
|
|
2736
|
+
* are never seen as references and each handler fn looks like it has no caller
|
|
2737
|
+
* — it's mounted by Rocket at runtime, not called by in-repo code, so its file
|
|
2738
|
+
* shows 0 dependents. Walk the token tree, reconstruct each comma-separated
|
|
2739
|
+
* path, and emit a `references` edge; the Rust path resolver
|
|
2740
|
+
* (`resolveRustPathReference`) then links it to the handler fn. The handler
|
|
2741
|
+
* names are explicit in source, so this is precise static extraction, not a
|
|
2742
|
+
* heuristic — no false edges (resolution still validates each path).
|
|
2743
|
+
*/
|
|
2744
|
+
extractRustRouteMacro(node) {
|
|
2745
|
+
if (this.language !== 'rust')
|
|
2746
|
+
return;
|
|
2747
|
+
const macroName = node.namedChild(0);
|
|
2748
|
+
if (!macroName)
|
|
2749
|
+
return;
|
|
2750
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(macroName, this.source);
|
|
2751
|
+
if (name !== 'routes' && name !== 'catchers')
|
|
2752
|
+
return;
|
|
2753
|
+
const tokenTree = node.namedChildren.find((c) => c.type === 'token_tree');
|
|
2754
|
+
if (!tokenTree)
|
|
2755
|
+
return;
|
|
2756
|
+
const fromId = this.nodeStack[this.nodeStack.length - 1];
|
|
2757
|
+
if (!fromId)
|
|
2758
|
+
return;
|
|
2759
|
+
// The token tree is a flat stream: `[ id :: id :: id , id … ]`. Group runs
|
|
2760
|
+
// of `identifier` tokens (the `::` joiners are anonymous) into one path; a
|
|
2761
|
+
// `,` (or the closing `]`) ends a path.
|
|
2762
|
+
let parts = [];
|
|
2763
|
+
let line = 0;
|
|
2764
|
+
let column = 0;
|
|
2765
|
+
const flush = () => {
|
|
2766
|
+
if (parts.length > 0) {
|
|
2767
|
+
this.unresolvedReferences.push({
|
|
2768
|
+
fromNodeId: fromId,
|
|
2769
|
+
referenceName: parts.join('::'),
|
|
2770
|
+
referenceKind: 'references',
|
|
2771
|
+
line,
|
|
2772
|
+
column,
|
|
2773
|
+
});
|
|
2774
|
+
parts = [];
|
|
2775
|
+
}
|
|
2776
|
+
};
|
|
2777
|
+
for (let i = 0; i < tokenTree.childCount; i++) {
|
|
2778
|
+
const t = tokenTree.child(i);
|
|
2779
|
+
if (!t)
|
|
2780
|
+
continue;
|
|
2781
|
+
if (t.type === 'identifier') {
|
|
2782
|
+
if (parts.length === 0) {
|
|
2783
|
+
line = t.startPosition.row + 1;
|
|
2784
|
+
column = t.startPosition.column;
|
|
2785
|
+
}
|
|
2786
|
+
parts.push((0, tree_sitter_helpers_1.getNodeText)(t, this.source));
|
|
2787
|
+
}
|
|
2788
|
+
else if (t.type === ',') {
|
|
2789
|
+
flush();
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
flush();
|
|
2793
|
+
}
|
|
2794
|
+
visitFunctionBody(body, _functionId) {
|
|
2795
|
+
if (!this.extractor)
|
|
2796
|
+
return;
|
|
2797
|
+
const visitForCallsAndStructure = (node) => {
|
|
2798
|
+
const nodeType = node.type;
|
|
2799
|
+
// Rocket route-registration macros (`routes![…]` / `catchers![…]`): the
|
|
2800
|
+
// handler paths live in a raw token tree the call walker can't see.
|
|
2801
|
+
if (nodeType === 'macro_invocation')
|
|
2802
|
+
this.extractRustRouteMacro(node);
|
|
2803
|
+
if (this.extractor.callTypes.includes(nodeType)) {
|
|
2804
|
+
this.extractCall(node);
|
|
2805
|
+
}
|
|
2806
|
+
else if (INSTANTIATION_KINDS.has(nodeType)) {
|
|
2807
|
+
// `new Foo()` inside a function body — emit an `instantiates`
|
|
2808
|
+
// reference. Without this branch the body walker only knew
|
|
2809
|
+
// about `call_expression`, so constructor invocations
|
|
2810
|
+
// produced no graph edges at all.
|
|
2811
|
+
this.extractInstantiation(node);
|
|
2812
|
+
// Anonymous class with body: `new T() { ... }` (Java/C#). Extract as
|
|
2813
|
+
// a class so interface-impl synthesis (Phase 5.5) can bridge T's
|
|
2814
|
+
// methods to the overrides — same rationale as in visitNode.
|
|
2815
|
+
const anonBody = this.findAnonymousClassBody(node);
|
|
2816
|
+
if (anonBody) {
|
|
2817
|
+
this.extractAnonymousClass(node, anonBody);
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
else if (this.extractor.extractBareCall) {
|
|
2822
|
+
const calleeName = this.extractor.extractBareCall(node, this.source);
|
|
2823
|
+
if (calleeName && this.nodeStack.length > 0) {
|
|
2824
|
+
const callerId = this.nodeStack[this.nodeStack.length - 1];
|
|
2825
|
+
if (callerId) {
|
|
2826
|
+
this.unresolvedReferences.push({
|
|
2827
|
+
fromNodeId: callerId,
|
|
2828
|
+
referenceName: calleeName,
|
|
2829
|
+
referenceKind: 'calls',
|
|
2830
|
+
line: node.startPosition.row + 1,
|
|
2831
|
+
column: node.startPosition.column,
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
// Static-member / value-read: `Enum.value`, `Type.CONST`, `Foo::BAR`.
|
|
2837
|
+
this.extractStaticMemberRef(node);
|
|
2838
|
+
// Local variable type annotations inside a body — `const items: Foo[] = []`,
|
|
2839
|
+
// `const x: SomeType = svc.load()`. We deliberately do NOT create nodes for
|
|
2840
|
+
// locals (that would explode the graph — the data-flow frontier we leave
|
|
2841
|
+
// uncovered), but the TYPE a local is annotated with is a real dependency of
|
|
2842
|
+
// the enclosing function, so attribute a `references` edge to it. Without
|
|
2843
|
+
// this, a function that uses a type ONLY in its body (very common — e.g. a
|
|
2844
|
+
// resolver building `const nodes: Node[] = []`) produced no edge to that
|
|
2845
|
+
// type, so impact / `affected` missed the dependency entirely. We fall
|
|
2846
|
+
// through to the default recursion below so the initializer's calls (and any
|
|
2847
|
+
// nested declarators) are still walked.
|
|
2848
|
+
if (nodeType === 'variable_declarator' &&
|
|
2849
|
+
this.TYPE_ANNOTATION_LANGUAGES.has(this.language)) {
|
|
2850
|
+
const ownerId = this.nodeStack[this.nodeStack.length - 1];
|
|
2851
|
+
if (ownerId)
|
|
2852
|
+
this.extractVariableTypeAnnotation(node, ownerId);
|
|
2853
|
+
}
|
|
2854
|
+
// Nested NAMED functions inside a body — function declarations and named
|
|
2855
|
+
// function expressions like `.on('mount', function onmount(){})` — become
|
|
2856
|
+
// their own nodes so the graph can link to them (callback handlers, local
|
|
2857
|
+
// helpers). Anonymous arrows/expressions fall through to the default
|
|
2858
|
+
// recursion below, keeping their inner calls attributed to the enclosing
|
|
2859
|
+
// function: this bounds the new nodes to NAMED functions only (no explosion,
|
|
2860
|
+
// no lost edges). extractFunction walks the nested body itself, so we return.
|
|
2861
|
+
if (this.extractor.functionTypes.includes(nodeType)) {
|
|
2862
|
+
const nestedName = extractName(node, this.source, this.extractor);
|
|
2863
|
+
if (nestedName && nestedName !== '<anonymous>') {
|
|
2864
|
+
this.extractFunction(node);
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
// Extract structural nodes found inside function bodies.
|
|
2869
|
+
// Each extract method visits its own children, so we return after extracting.
|
|
2870
|
+
if (this.extractor.classTypes.includes(nodeType)) {
|
|
2871
|
+
const classification = this.extractor.classifyClassNode?.(node) ?? 'class';
|
|
2872
|
+
if (classification === 'struct')
|
|
2873
|
+
this.extractStruct(node);
|
|
2874
|
+
else if (classification === 'enum')
|
|
2875
|
+
this.extractEnum(node);
|
|
2876
|
+
else if (classification === 'interface')
|
|
2877
|
+
this.extractInterface(node);
|
|
2878
|
+
else if (classification === 'trait')
|
|
2879
|
+
this.extractClass(node, 'trait');
|
|
2880
|
+
else
|
|
2881
|
+
this.extractClass(node);
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
if (this.extractor.structTypes.includes(nodeType)) {
|
|
2885
|
+
this.extractStruct(node);
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
if (this.extractor.enumTypes.includes(nodeType)) {
|
|
2889
|
+
this.extractEnum(node);
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
if (this.extractor.interfaceTypes.includes(nodeType)) {
|
|
2893
|
+
this.extractInterface(node);
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2897
|
+
const child = node.namedChild(i);
|
|
2898
|
+
if (child) {
|
|
2899
|
+
visitForCallsAndStructure(child);
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
};
|
|
2903
|
+
visitForCallsAndStructure(body);
|
|
2904
|
+
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Extract inheritance relationships
|
|
2907
|
+
*/
|
|
2908
|
+
extractInheritance(node, classId) {
|
|
2909
|
+
// Objective-C @interface MyClass : NSObject <ProtoA, ProtoB>
|
|
2910
|
+
if (node.type === 'class_interface') {
|
|
2911
|
+
const superclass = (0, tree_sitter_helpers_1.getChildByField)(node, 'superclass');
|
|
2912
|
+
if (superclass) {
|
|
2913
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(superclass, this.source);
|
|
2914
|
+
this.unresolvedReferences.push({
|
|
2915
|
+
fromNodeId: classId,
|
|
2916
|
+
referenceName: name,
|
|
2917
|
+
referenceKind: 'extends',
|
|
2918
|
+
line: superclass.startPosition.row + 1,
|
|
2919
|
+
column: superclass.startPosition.column,
|
|
2920
|
+
});
|
|
2921
|
+
}
|
|
2922
|
+
for (let j = 0; j < node.namedChildCount; j++) {
|
|
2923
|
+
const argList = node.namedChild(j);
|
|
2924
|
+
if (argList?.type !== 'parameterized_arguments')
|
|
2925
|
+
continue;
|
|
2926
|
+
for (let k = 0; k < argList.namedChildCount; k++) {
|
|
2927
|
+
const typeName = argList.namedChild(k);
|
|
2928
|
+
if (!typeName)
|
|
2929
|
+
continue;
|
|
2930
|
+
const typeId = typeName.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'identifier');
|
|
2931
|
+
if (!typeId)
|
|
2932
|
+
continue;
|
|
2933
|
+
const protocolName = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
2934
|
+
this.unresolvedReferences.push({
|
|
2935
|
+
fromNodeId: classId,
|
|
2936
|
+
referenceName: protocolName,
|
|
2937
|
+
referenceKind: 'implements',
|
|
2938
|
+
line: typeId.startPosition.row + 1,
|
|
2939
|
+
column: typeId.startPosition.column,
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
// Look for extends/implements clauses
|
|
2946
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2947
|
+
const child = node.namedChild(i);
|
|
2948
|
+
if (!child)
|
|
2949
|
+
continue;
|
|
2950
|
+
if (child.type === 'extends_clause' ||
|
|
2951
|
+
child.type === 'superclass' ||
|
|
2952
|
+
child.type === 'base_clause' || // PHP class extends
|
|
2953
|
+
child.type === 'extends_interfaces' // Java interface extends
|
|
2954
|
+
) {
|
|
2955
|
+
// Scala: `extends A[X] with B with C` packs EVERY supertype into the
|
|
2956
|
+
// one extends_clause (separated by `with`), each a `generic_type` /
|
|
2957
|
+
// `type_identifier` / `stable_type_identifier`. The generic path below
|
|
2958
|
+
// takes only namedChild(0) and keeps the full text (`A[X]`), so a
|
|
2959
|
+
// parameterized supertype — every typeclass in cats/algebra — never
|
|
2960
|
+
// matched and `with`-mixed traits past the first were dropped. Iterate
|
|
2961
|
+
// all supertypes and unwrap each to its base type name.
|
|
2962
|
+
if (this.language === 'scala') {
|
|
2963
|
+
for (const target of child.namedChildren) {
|
|
2964
|
+
const name = scalaBaseTypeName(target, this.source);
|
|
2965
|
+
if (name) {
|
|
2966
|
+
this.unresolvedReferences.push({
|
|
2967
|
+
fromNodeId: classId,
|
|
2968
|
+
referenceName: name,
|
|
2969
|
+
referenceKind: 'extends',
|
|
2970
|
+
line: target.startPosition.row + 1,
|
|
2971
|
+
column: target.startPosition.column,
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
continue;
|
|
2976
|
+
}
|
|
2977
|
+
// Dart: `class C extends Base with M1, M2` — the `superclass` node holds
|
|
2978
|
+
// the extends type as a direct `type_identifier` AND a `mixins` child
|
|
2979
|
+
// listing the `with` mixins (and `class C with M` has ONLY mixins, no
|
|
2980
|
+
// extends type). The generic `namedChild(0)` path would read the
|
|
2981
|
+
// `mixins` node itself as the superclass and drop every mixin — yet
|
|
2982
|
+
// mixins are Dart's core composition mechanism (Flutter is built on
|
|
2983
|
+
// them). Emit `extends` for the base and `implements` for each mixin.
|
|
2984
|
+
if (this.language === 'dart' && child.type === 'superclass') {
|
|
2985
|
+
for (const t of child.namedChildren) {
|
|
2986
|
+
if (t.type === 'mixins') {
|
|
2987
|
+
for (const m of t.namedChildren) {
|
|
2988
|
+
if (m.type === 'type_identifier') {
|
|
2989
|
+
this.unresolvedReferences.push({
|
|
2990
|
+
fromNodeId: classId,
|
|
2991
|
+
referenceName: (0, tree_sitter_helpers_1.getNodeText)(m, this.source),
|
|
2992
|
+
referenceKind: 'implements',
|
|
2993
|
+
line: m.startPosition.row + 1,
|
|
2994
|
+
column: m.startPosition.column,
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
else if (t.type === 'type_identifier') {
|
|
3000
|
+
this.unresolvedReferences.push({
|
|
3001
|
+
fromNodeId: classId,
|
|
3002
|
+
referenceName: (0, tree_sitter_helpers_1.getNodeText)(t, this.source),
|
|
3003
|
+
referenceKind: 'extends',
|
|
3004
|
+
line: t.startPosition.row + 1,
|
|
3005
|
+
column: t.startPosition.column,
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
continue;
|
|
3010
|
+
}
|
|
3011
|
+
// Extract parent class/interface names
|
|
3012
|
+
// Java uses type_list wrapper: superclass -> type_identifier, extends_interfaces -> type_list -> type_identifier
|
|
3013
|
+
const typeList = child.namedChildren.find((c) => c.type === 'type_list');
|
|
3014
|
+
const targets = typeList ? typeList.namedChildren : [child.namedChild(0)];
|
|
3015
|
+
for (const target of targets) {
|
|
3016
|
+
if (target) {
|
|
3017
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(target, this.source);
|
|
3018
|
+
this.unresolvedReferences.push({
|
|
3019
|
+
fromNodeId: classId,
|
|
3020
|
+
referenceName: name,
|
|
3021
|
+
referenceKind: 'extends',
|
|
3022
|
+
line: target.startPosition.row + 1,
|
|
3023
|
+
column: target.startPosition.column,
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
// C++ base classes: `class Derived : public Base, private Other` →
|
|
3029
|
+
// base_class_clause holds access specifiers + base type(s). Emit an extends
|
|
3030
|
+
// ref per base type (skip the public/private/protected keywords).
|
|
3031
|
+
if (child.type === 'base_class_clause') {
|
|
3032
|
+
for (const t of child.namedChildren) {
|
|
3033
|
+
if (t.type === 'type_identifier' ||
|
|
3034
|
+
t.type === 'qualified_identifier' ||
|
|
3035
|
+
t.type === 'template_type') {
|
|
3036
|
+
this.unresolvedReferences.push({
|
|
3037
|
+
fromNodeId: classId,
|
|
3038
|
+
referenceName: (0, tree_sitter_helpers_1.getNodeText)(t, this.source),
|
|
3039
|
+
referenceKind: 'extends',
|
|
3040
|
+
line: t.startPosition.row + 1,
|
|
3041
|
+
column: t.startPosition.column,
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
if (child.type === 'implements_clause' ||
|
|
3047
|
+
child.type === 'class_interface_clause' ||
|
|
3048
|
+
child.type === 'super_interfaces' || // Java class implements
|
|
3049
|
+
child.type === 'interfaces' // Dart
|
|
3050
|
+
) {
|
|
3051
|
+
// Extract implemented interfaces
|
|
3052
|
+
// Java uses type_list wrapper: super_interfaces -> type_list -> type_identifier
|
|
3053
|
+
const typeList = child.namedChildren.find((c) => c.type === 'type_list');
|
|
3054
|
+
const targets = typeList ? typeList.namedChildren : child.namedChildren;
|
|
3055
|
+
for (const iface of targets) {
|
|
3056
|
+
if (iface) {
|
|
3057
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(iface, this.source);
|
|
3058
|
+
this.unresolvedReferences.push({
|
|
3059
|
+
fromNodeId: classId,
|
|
3060
|
+
referenceName: name,
|
|
3061
|
+
referenceKind: 'implements',
|
|
3062
|
+
line: iface.startPosition.row + 1,
|
|
3063
|
+
column: iface.startPosition.column,
|
|
3064
|
+
});
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
// Python superclass list: `class Flask(Scaffold, Mixin):`
|
|
3069
|
+
// argument_list contains identifier children for each parent class
|
|
3070
|
+
if (child.type === 'argument_list' && node.type === 'class_definition') {
|
|
3071
|
+
for (const arg of child.namedChildren) {
|
|
3072
|
+
if (arg.type === 'identifier' || arg.type === 'attribute') {
|
|
3073
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(arg, this.source);
|
|
3074
|
+
this.unresolvedReferences.push({
|
|
3075
|
+
fromNodeId: classId,
|
|
3076
|
+
referenceName: name,
|
|
3077
|
+
referenceKind: 'extends',
|
|
3078
|
+
line: arg.startPosition.row + 1,
|
|
3079
|
+
column: arg.startPosition.column,
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
// Go interface embedding: `type Querier interface { LabelQuerier; ... }`
|
|
3085
|
+
// constraint_elem wraps the embedded interface type identifier
|
|
3086
|
+
if (child.type === 'constraint_elem') {
|
|
3087
|
+
const typeId = child.namedChildren.find((c) => c.type === 'type_identifier');
|
|
3088
|
+
if (typeId) {
|
|
3089
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
3090
|
+
this.unresolvedReferences.push({
|
|
3091
|
+
fromNodeId: classId,
|
|
3092
|
+
referenceName: name,
|
|
3093
|
+
referenceKind: 'extends',
|
|
3094
|
+
line: typeId.startPosition.row + 1,
|
|
3095
|
+
column: typeId.startPosition.column,
|
|
3096
|
+
});
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
// Go struct embedding: field_declaration without field_identifier
|
|
3100
|
+
// e.g. `type DB struct { *Head; Queryable }` — no field name means embedded type
|
|
3101
|
+
if (child.type === 'field_declaration') {
|
|
3102
|
+
const hasFieldIdentifier = child.namedChildren.some((c) => c.type === 'field_identifier');
|
|
3103
|
+
if (!hasFieldIdentifier) {
|
|
3104
|
+
const typeId = child.namedChildren.find((c) => c.type === 'type_identifier');
|
|
3105
|
+
if (typeId) {
|
|
3106
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
3107
|
+
this.unresolvedReferences.push({
|
|
3108
|
+
fromNodeId: classId,
|
|
3109
|
+
referenceName: name,
|
|
3110
|
+
referenceKind: 'extends',
|
|
3111
|
+
line: typeId.startPosition.row + 1,
|
|
3112
|
+
column: typeId.startPosition.column,
|
|
3113
|
+
});
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
// Rust trait supertraits: `trait SubTrait: SuperTrait + Display { ... }`
|
|
3118
|
+
// trait_bounds contains type_identifier, generic_type, or higher_ranked_trait_bound children
|
|
3119
|
+
if (child.type === 'trait_bounds') {
|
|
3120
|
+
for (const bound of child.namedChildren) {
|
|
3121
|
+
let typeName;
|
|
3122
|
+
let posNode;
|
|
3123
|
+
if (bound.type === 'type_identifier') {
|
|
3124
|
+
typeName = (0, tree_sitter_helpers_1.getNodeText)(bound, this.source);
|
|
3125
|
+
posNode = bound;
|
|
3126
|
+
}
|
|
3127
|
+
else if (bound.type === 'generic_type') {
|
|
3128
|
+
// e.g. `Deserialize<'de>`
|
|
3129
|
+
const inner = bound.namedChildren.find((c) => c.type === 'type_identifier');
|
|
3130
|
+
if (inner) {
|
|
3131
|
+
typeName = (0, tree_sitter_helpers_1.getNodeText)(inner, this.source);
|
|
3132
|
+
posNode = inner;
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
else if (bound.type === 'higher_ranked_trait_bound') {
|
|
3136
|
+
// e.g. `for<'de> Deserialize<'de>`
|
|
3137
|
+
const generic = bound.namedChildren.find((c) => c.type === 'generic_type');
|
|
3138
|
+
const typeId = generic?.namedChildren.find((c) => c.type === 'type_identifier')
|
|
3139
|
+
?? bound.namedChildren.find((c) => c.type === 'type_identifier');
|
|
3140
|
+
if (typeId) {
|
|
3141
|
+
typeName = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
3142
|
+
posNode = typeId;
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
if (typeName && posNode) {
|
|
3146
|
+
this.unresolvedReferences.push({
|
|
3147
|
+
fromNodeId: classId,
|
|
3148
|
+
referenceName: typeName,
|
|
3149
|
+
referenceKind: 'extends',
|
|
3150
|
+
line: posNode.startPosition.row + 1,
|
|
3151
|
+
column: posNode.startPosition.column,
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
// C#: `class Movie : BaseItem, IPlugin` → base_list with identifier children
|
|
3157
|
+
// base_list combines both base class and interfaces in a single colon-separated list.
|
|
3158
|
+
// We emit all as 'extends' since the syntax doesn't distinguish them.
|
|
3159
|
+
if (child.type === 'base_list') {
|
|
3160
|
+
for (const baseType of child.namedChildren) {
|
|
3161
|
+
if (baseType) {
|
|
3162
|
+
// For generic base types like `ClientBase<T>`, extract just the type name
|
|
3163
|
+
const name = baseType.type === 'generic_name'
|
|
3164
|
+
? (0, tree_sitter_helpers_1.getNodeText)(baseType.namedChildren.find((c) => c.type === 'identifier') ?? baseType, this.source)
|
|
3165
|
+
: (0, tree_sitter_helpers_1.getNodeText)(baseType, this.source);
|
|
3166
|
+
this.unresolvedReferences.push({
|
|
3167
|
+
fromNodeId: classId,
|
|
3168
|
+
referenceName: name,
|
|
3169
|
+
referenceKind: 'extends',
|
|
3170
|
+
line: baseType.startPosition.row + 1,
|
|
3171
|
+
column: baseType.startPosition.column,
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
// Kotlin: `class Foo : Bar, Baz` → delegation_specifier > user_type > type_identifier
|
|
3177
|
+
// Also handles `class Foo : Bar()` → delegation_specifier > constructor_invocation > user_type
|
|
3178
|
+
if (child.type === 'delegation_specifier') {
|
|
3179
|
+
const userType = child.namedChildren.find((c) => c.type === 'user_type');
|
|
3180
|
+
const constructorInvocation = child.namedChildren.find((c) => c.type === 'constructor_invocation');
|
|
3181
|
+
const target = userType ?? constructorInvocation;
|
|
3182
|
+
if (target) {
|
|
3183
|
+
const typeId = target.type === 'user_type'
|
|
3184
|
+
? target.namedChildren.find((c) => c.type === 'type_identifier') ?? target
|
|
3185
|
+
: target.namedChildren.find((c) => c.type === 'user_type')?.namedChildren.find((c) => c.type === 'type_identifier')
|
|
3186
|
+
?? target.namedChildren.find((c) => c.type === 'user_type') ?? target;
|
|
3187
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
3188
|
+
this.unresolvedReferences.push({
|
|
3189
|
+
fromNodeId: classId,
|
|
3190
|
+
referenceName: name,
|
|
3191
|
+
referenceKind: 'extends',
|
|
3192
|
+
line: typeId.startPosition.row + 1,
|
|
3193
|
+
column: typeId.startPosition.column,
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
// Swift: inheritance_specifier > user_type > type_identifier
|
|
3198
|
+
// Used for class inheritance, protocol conformance, and protocol inheritance
|
|
3199
|
+
if (child.type === 'inheritance_specifier') {
|
|
3200
|
+
const userType = child.namedChildren.find((c) => c.type === 'user_type');
|
|
3201
|
+
const typeId = userType?.namedChildren.find((c) => c.type === 'type_identifier');
|
|
3202
|
+
if (typeId) {
|
|
3203
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
|
|
3204
|
+
this.unresolvedReferences.push({
|
|
3205
|
+
fromNodeId: classId,
|
|
3206
|
+
referenceName: name,
|
|
3207
|
+
referenceKind: 'extends',
|
|
3208
|
+
line: typeId.startPosition.row + 1,
|
|
3209
|
+
column: typeId.startPosition.column,
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
// JavaScript class_heritage has bare identifier without extends_clause wrapper
|
|
3214
|
+
// e.g. `class Foo extends Bar {}` → class_heritage → identifier("Bar")
|
|
3215
|
+
if ((child.type === 'identifier' || child.type === 'type_identifier') &&
|
|
3216
|
+
node.type === 'class_heritage') {
|
|
3217
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(child, this.source);
|
|
3218
|
+
this.unresolvedReferences.push({
|
|
3219
|
+
fromNodeId: classId,
|
|
3220
|
+
referenceName: name,
|
|
3221
|
+
referenceKind: 'extends',
|
|
3222
|
+
line: child.startPosition.row + 1,
|
|
3223
|
+
column: child.startPosition.column,
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3226
|
+
// Recurse into container nodes (e.g. field_declaration_list in Go structs,
|
|
3227
|
+
// class_heritage in TypeScript which wraps extends_clause/implements_clause)
|
|
3228
|
+
if (child.type === 'field_declaration_list' || child.type === 'class_heritage') {
|
|
3229
|
+
this.extractInheritance(child, classId);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* Rust `impl Trait for Type` — creates an implements edge from Type to Trait.
|
|
3235
|
+
* For plain `impl Type { ... }` (no trait), no inheritance edge is needed.
|
|
3236
|
+
*/
|
|
3237
|
+
extractRustImplItem(node) {
|
|
3238
|
+
// Check if this is `impl Trait for Type` by looking for a `for` keyword
|
|
3239
|
+
const hasFor = node.children.some((c) => c.type === 'for' && !c.isNamed);
|
|
3240
|
+
if (!hasFor)
|
|
3241
|
+
return;
|
|
3242
|
+
// In `impl Trait for Type`, the type_identifiers are:
|
|
3243
|
+
// first = Trait name, last = implementing Type name
|
|
3244
|
+
// Also handle generic types like `impl<T> Trait for MyStruct<T>`
|
|
3245
|
+
const typeIdents = node.namedChildren.filter((c) => c.type === 'type_identifier' || c.type === 'generic_type' || c.type === 'scoped_type_identifier');
|
|
3246
|
+
if (typeIdents.length < 2)
|
|
3247
|
+
return;
|
|
3248
|
+
const traitNode = typeIdents[0];
|
|
3249
|
+
const typeNode = typeIdents[typeIdents.length - 1];
|
|
3250
|
+
// Get the trait name (handle scoped paths like std::fmt::Display)
|
|
3251
|
+
const traitName = traitNode.type === 'scoped_type_identifier'
|
|
3252
|
+
? this.source.substring(traitNode.startIndex, traitNode.endIndex)
|
|
3253
|
+
: (0, tree_sitter_helpers_1.getNodeText)(traitNode, this.source);
|
|
3254
|
+
// Get the implementing type name (extract inner type_identifier for generics)
|
|
3255
|
+
let typeName;
|
|
3256
|
+
if (typeNode.type === 'generic_type') {
|
|
3257
|
+
const inner = typeNode.namedChildren.find((c) => c.type === 'type_identifier');
|
|
3258
|
+
typeName = inner ? (0, tree_sitter_helpers_1.getNodeText)(inner, this.source) : (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source);
|
|
3259
|
+
}
|
|
3260
|
+
else {
|
|
3261
|
+
typeName = (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source);
|
|
3262
|
+
}
|
|
3263
|
+
// Find the struct/type node for the implementing type
|
|
3264
|
+
const typeNodeId = this.findNodeByName(typeName);
|
|
3265
|
+
if (typeNodeId) {
|
|
3266
|
+
this.unresolvedReferences.push({
|
|
3267
|
+
fromNodeId: typeNodeId,
|
|
3268
|
+
referenceName: traitName,
|
|
3269
|
+
referenceKind: 'implements',
|
|
3270
|
+
line: traitNode.startPosition.row + 1,
|
|
3271
|
+
column: traitNode.startPosition.column,
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
/**
|
|
3276
|
+
* Find a previously-extracted node by name (used for back-references like impl blocks)
|
|
3277
|
+
*/
|
|
3278
|
+
findNodeByName(name) {
|
|
3279
|
+
for (const node of this.nodes) {
|
|
3280
|
+
if (node.name === name && (node.kind === 'struct' || node.kind === 'enum' || node.kind === 'class')) {
|
|
3281
|
+
return node.id;
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
return undefined;
|
|
3285
|
+
}
|
|
3286
|
+
/**
|
|
3287
|
+
* Languages that support type annotations (TypeScript, etc.)
|
|
3288
|
+
*/
|
|
3289
|
+
TYPE_ANNOTATION_LANGUAGES = new Set([
|
|
3290
|
+
'typescript', 'tsx', 'dart', 'kotlin', 'swift', 'rust', 'go', 'java', 'csharp', 'scala', 'php',
|
|
3291
|
+
]);
|
|
3292
|
+
/**
|
|
3293
|
+
* PHP pseudo-types and `self`/`static`/`parent` that aren't project symbols.
|
|
3294
|
+
* (Scalar primitives parse as `primitive_type` and are skipped structurally.)
|
|
3295
|
+
*/
|
|
3296
|
+
PHP_PSEUDO_TYPES = new Set([
|
|
3297
|
+
'self', 'static', 'parent', 'mixed', 'object', 'iterable', 'callable', 'void',
|
|
3298
|
+
'null', 'false', 'true', 'never', 'array', 'int', 'float', 'string', 'bool',
|
|
3299
|
+
]);
|
|
3300
|
+
/**
|
|
3301
|
+
* Built-in/primitive type names that shouldn't create references
|
|
3302
|
+
*/
|
|
3303
|
+
BUILTIN_TYPES = new Set([
|
|
3304
|
+
'string', 'number', 'boolean', 'void', 'null', 'undefined', 'never', 'any', 'unknown',
|
|
3305
|
+
'object', 'symbol', 'bigint', 'true', 'false',
|
|
3306
|
+
// Rust
|
|
3307
|
+
'str', 'bool', 'i8', 'i16', 'i32', 'i64', 'i128', 'isize',
|
|
3308
|
+
'u8', 'u16', 'u32', 'u64', 'u128', 'usize', 'f32', 'f64', 'char',
|
|
3309
|
+
// Java/C#
|
|
3310
|
+
'int', 'long', 'short', 'byte', 'float', 'double', 'char',
|
|
3311
|
+
// Go
|
|
3312
|
+
'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64',
|
|
3313
|
+
'float32', 'float64', 'complex64', 'complex128', 'rune', 'error',
|
|
3314
|
+
// Scala (capitalized primitives + ubiquitous stdlib aliases)
|
|
3315
|
+
'Int', 'Long', 'Short', 'Byte', 'Float', 'Double', 'Boolean', 'Char', 'Unit',
|
|
3316
|
+
'String', 'Any', 'AnyRef', 'AnyVal', 'Nothing', 'Null',
|
|
3317
|
+
]);
|
|
3318
|
+
/**
|
|
3319
|
+
* Extract type references from type annotations on a function/method/field node.
|
|
3320
|
+
* Creates 'references' edges for parameter types, return types, and field types.
|
|
3321
|
+
*/
|
|
3322
|
+
extractTypeAnnotations(node, nodeId) {
|
|
3323
|
+
if (!this.extractor)
|
|
3324
|
+
return;
|
|
3325
|
+
if (!this.TYPE_ANNOTATION_LANGUAGES.has(this.language))
|
|
3326
|
+
return;
|
|
3327
|
+
// C# tree-sitter doesn't produce `type_identifier` leaves — it uses
|
|
3328
|
+
// `identifier`, `predefined_type`, `qualified_name`, `generic_name`,
|
|
3329
|
+
// etc. — so the generic walker below emits zero references for it.
|
|
3330
|
+
// Dispatch to a C#-aware path that only walks type-position subtrees
|
|
3331
|
+
// (the `type` field of a parameter/method/property/field), so
|
|
3332
|
+
// parameter NAMES never accidentally surface as type refs (#381).
|
|
3333
|
+
if (this.language === 'csharp') {
|
|
3334
|
+
this.extractCsharpTypeRefs(node, nodeId);
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
// PHP type-hints are `named_type`/`optional_type`/`union_type` wrapping a
|
|
3338
|
+
// `name`/`qualified_name` — never `type_identifier` — so the generic walker
|
|
3339
|
+
// below emits nothing for them. Dispatch to a PHP-aware path that walks only
|
|
3340
|
+
// type positions (parameter / return / property types), so type-hinted
|
|
3341
|
+
// dependencies (the constructor-injected contracts that dominate Laravel) are
|
|
3342
|
+
// recorded and a `variable_name` like `$events` never mis-emits as a ref.
|
|
3343
|
+
if (this.language === 'php') {
|
|
3344
|
+
this.extractPhpTypeRefs(node, nodeId);
|
|
3345
|
+
return;
|
|
3346
|
+
}
|
|
3347
|
+
// Dart: a `method_signature` wraps the real `function_signature` (where the
|
|
3348
|
+
// params and return type live), and the return type is a bare
|
|
3349
|
+
// `type_identifier` child, not a `type` field — so getChildByField below
|
|
3350
|
+
// finds neither. Walk the inner signature: param names / the method name are
|
|
3351
|
+
// `identifier` (not `type_identifier`), so only types surface.
|
|
3352
|
+
if (this.language === 'dart') {
|
|
3353
|
+
let sig = node;
|
|
3354
|
+
if (node.type === 'method_signature') {
|
|
3355
|
+
sig = node.namedChildren.find((c) => c.type === 'function_signature' ||
|
|
3356
|
+
c.type === 'getter_signature' ||
|
|
3357
|
+
c.type === 'setter_signature' ||
|
|
3358
|
+
c.type === 'constructor_signature' ||
|
|
3359
|
+
c.type === 'factory_constructor_signature') ?? node;
|
|
3360
|
+
}
|
|
3361
|
+
this.extractTypeRefsFromSubtree(sig, nodeId);
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
// Extract parameter type annotations. Scala curries — `def f(a)(implicit
|
|
3365
|
+
// M: TC)` has MULTIPLE `parameters` siblings, and the typeclass is almost
|
|
3366
|
+
// always in the trailing implicit list — so walk every parameter list, not
|
|
3367
|
+
// just getChildByField's first match.
|
|
3368
|
+
if (this.language === 'scala') {
|
|
3369
|
+
for (const pc of node.namedChildren) {
|
|
3370
|
+
if (pc.type === 'parameters')
|
|
3371
|
+
this.extractTypeRefsFromSubtree(pc, nodeId);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
else {
|
|
3375
|
+
const params = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.paramsField || 'parameters');
|
|
3376
|
+
if (params) {
|
|
3377
|
+
this.extractTypeRefsFromSubtree(params, nodeId);
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
// Extract return type annotation
|
|
3381
|
+
const returnType = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.returnField || 'return_type');
|
|
3382
|
+
if (returnType) {
|
|
3383
|
+
this.extractTypeRefsFromSubtree(returnType, nodeId);
|
|
3384
|
+
}
|
|
3385
|
+
// Scala context bounds / type-parameter bounds: `def f[A: Monoid]`,
|
|
3386
|
+
// `[F[_]: Monad]`, `[A <: Foo]` carry the bound type inside `type_parameters`.
|
|
3387
|
+
// This is THE pervasive way a typeclass is required in Scala, yet the bound
|
|
3388
|
+
// never appears in the value parameters. Param NAMES are `identifier` (not
|
|
3389
|
+
// `type_identifier`), so only the bound types surface. Scala-only: in other
|
|
3390
|
+
// languages a `type_parameters` child holds declaration names as
|
|
3391
|
+
// `type_identifier` (TS `<T>`), which would wrongly surface as refs.
|
|
3392
|
+
if (this.language === 'scala') {
|
|
3393
|
+
const typeParams = node.namedChildren.find((c) => c.type === 'type_parameters');
|
|
3394
|
+
if (typeParams) {
|
|
3395
|
+
this.extractTypeRefsFromSubtree(typeParams, nodeId);
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
// Extract direct type annotation (for class fields like `model: ITextModel`)
|
|
3399
|
+
const typeAnnotation = node.namedChildren.find((c) => c.type === 'type_annotation');
|
|
3400
|
+
if (typeAnnotation) {
|
|
3401
|
+
this.extractTypeRefsFromSubtree(typeAnnotation, nodeId);
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
/**
|
|
3405
|
+
* Extract C# type references from a node that owns a type position —
|
|
3406
|
+
* a method/constructor declaration, a property declaration, or a
|
|
3407
|
+
* field declaration (which wraps `variable_declaration → type`).
|
|
3408
|
+
*
|
|
3409
|
+
* Walks ONLY into known type fields, so parameter names like
|
|
3410
|
+
* `request` in `Build(UserDto request)` are never mis-emitted as
|
|
3411
|
+
* type references. Once inside a type subtree, `walkCsharpTypePosition`
|
|
3412
|
+
* recognizes C#'s actual type-leaf node kinds (`identifier`,
|
|
3413
|
+
* `qualified_name`, `generic_name`, `array_type`, `nullable_type`,
|
|
3414
|
+
* `tuple_type`, …) — none of which are `type_identifier`. Closes #381.
|
|
3415
|
+
*/
|
|
3416
|
+
extractCsharpTypeRefs(node, nodeId) {
|
|
3417
|
+
// A property's type is under the `type` field; a method/constructor's RETURN
|
|
3418
|
+
// type is under `returns` (tree-sitter-c-sharp 0.23.x — older builds used
|
|
3419
|
+
// `type` for both). A node carries only one of the two, so checking both
|
|
3420
|
+
// covers return types and property types without conflating them.
|
|
3421
|
+
const directType = (0, tree_sitter_helpers_1.getChildByField)(node, 'type') ?? (0, tree_sitter_helpers_1.getChildByField)(node, 'returns');
|
|
3422
|
+
if (directType)
|
|
3423
|
+
this.walkCsharpTypePosition(directType, nodeId);
|
|
3424
|
+
// Field declarations wrap declarators in a `variable_declaration`
|
|
3425
|
+
// whose `type` field carries the type. The outer `field_declaration`
|
|
3426
|
+
// has no `type` field of its own, so the call above is a no-op here
|
|
3427
|
+
// and we descend one level.
|
|
3428
|
+
const varDecl = node.namedChildren.find((c) => c.type === 'variable_declaration');
|
|
3429
|
+
if (varDecl) {
|
|
3430
|
+
const vdType = (0, tree_sitter_helpers_1.getChildByField)(varDecl, 'type');
|
|
3431
|
+
if (vdType)
|
|
3432
|
+
this.walkCsharpTypePosition(vdType, nodeId);
|
|
3433
|
+
}
|
|
3434
|
+
// Method / constructor parameters. The field name on
|
|
3435
|
+
// `method_declaration` is `parameters`; it points at a
|
|
3436
|
+
// `parameter_list` whose `parameter` children each have their own
|
|
3437
|
+
// `type` field. Walking ONLY the type field skips parameter NAMES,
|
|
3438
|
+
// which would otherwise mis-emit as type references.
|
|
3439
|
+
const params = (0, tree_sitter_helpers_1.getChildByField)(node, 'parameters');
|
|
3440
|
+
if (params) {
|
|
3441
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
3442
|
+
const child = params.namedChild(i);
|
|
3443
|
+
if (!child || child.type !== 'parameter')
|
|
3444
|
+
continue;
|
|
3445
|
+
const paramType = (0, tree_sitter_helpers_1.getChildByField)(child, 'type');
|
|
3446
|
+
if (paramType)
|
|
3447
|
+
this.walkCsharpTypePosition(paramType, nodeId);
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
/**
|
|
3452
|
+
* Record the dependencies declared by a C# PRIMARY CONSTRUCTOR
|
|
3453
|
+
* (`class Svc(IRepo repo, [FromKeyedServices("k")] ICache cache) { … }`,
|
|
3454
|
+
* C# 12+). The parameter list hangs off the class/struct/record declaration
|
|
3455
|
+
* as an unnamed-field `parameter_list` child (not the `parameters` field a
|
|
3456
|
+
* method uses), so it's found by node type. Each parameter's declared type
|
|
3457
|
+
* becomes a `references` edge from the owning type — these are exactly the
|
|
3458
|
+
* services a DI-registered type depends on, so impact/blast-radius and
|
|
3459
|
+
* "who depends on this contract" now see them. No-op when there's no primary
|
|
3460
|
+
* constructor. (#237)
|
|
3461
|
+
*/
|
|
3462
|
+
extractCsharpPrimaryCtorParamRefs(node, ownerId) {
|
|
3463
|
+
if (this.language !== 'csharp')
|
|
3464
|
+
return;
|
|
3465
|
+
const paramList = node.namedChildren.find((c) => c.type === 'parameter_list');
|
|
3466
|
+
if (!paramList)
|
|
3467
|
+
return;
|
|
3468
|
+
for (let i = 0; i < paramList.namedChildCount; i++) {
|
|
3469
|
+
const param = paramList.namedChild(i);
|
|
3470
|
+
if (!param || param.type !== 'parameter')
|
|
3471
|
+
continue;
|
|
3472
|
+
const paramType = (0, tree_sitter_helpers_1.getChildByField)(param, 'type');
|
|
3473
|
+
if (paramType)
|
|
3474
|
+
this.walkCsharpTypePosition(paramType, ownerId);
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
/**
|
|
3478
|
+
* Walk a C# subtree that is KNOWN to be in a type position
|
|
3479
|
+
* (return type, parameter type, property type, field type, generic
|
|
3480
|
+
* argument). Identifiers here are type names, not parameter names.
|
|
3481
|
+
*/
|
|
3482
|
+
walkCsharpTypePosition(node, fromNodeId) {
|
|
3483
|
+
// `predefined_type` is int/string/bool/etc. — never a project ref.
|
|
3484
|
+
if (node.type === 'predefined_type')
|
|
3485
|
+
return;
|
|
3486
|
+
// Bare type name: `Foo` in `Foo bar`, or the `Foo` inside `List<Foo>`.
|
|
3487
|
+
if (node.type === 'identifier') {
|
|
3488
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(node, this.source);
|
|
3489
|
+
if (name && !this.BUILTIN_TYPES.has(name)) {
|
|
3490
|
+
this.unresolvedReferences.push({
|
|
3491
|
+
fromNodeId,
|
|
3492
|
+
referenceName: name,
|
|
3493
|
+
referenceKind: 'references',
|
|
3494
|
+
line: node.startPosition.row + 1,
|
|
3495
|
+
column: node.startPosition.column,
|
|
3496
|
+
});
|
|
3497
|
+
}
|
|
3498
|
+
return;
|
|
3499
|
+
}
|
|
3500
|
+
// `Namespace.Foo` → the rightmost identifier is the type. Emit the
|
|
3501
|
+
// full qualified name as the reference; the resolver can still match
|
|
3502
|
+
// on the trailing simple name when needed.
|
|
3503
|
+
if (node.type === 'qualified_name') {
|
|
3504
|
+
const text = (0, tree_sitter_helpers_1.getNodeText)(node, this.source);
|
|
3505
|
+
const last = text.split('.').pop() ?? text;
|
|
3506
|
+
if (last && !this.BUILTIN_TYPES.has(last)) {
|
|
3507
|
+
this.unresolvedReferences.push({
|
|
3508
|
+
fromNodeId,
|
|
3509
|
+
referenceName: last,
|
|
3510
|
+
referenceKind: 'references',
|
|
3511
|
+
line: node.startPosition.row + 1,
|
|
3512
|
+
column: node.startPosition.column,
|
|
3513
|
+
});
|
|
3514
|
+
}
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
// `(int Code, Foo Payload)` — tuple element has BOTH a `type` and a
|
|
3518
|
+
// `name` field; descending into all named children would mis-emit
|
|
3519
|
+
// the element name (`Code`, `Payload`) as a type ref. Walk only the
|
|
3520
|
+
// type field.
|
|
3521
|
+
if (node.type === 'tuple_element') {
|
|
3522
|
+
const t = (0, tree_sitter_helpers_1.getChildByField)(node, 'type');
|
|
3523
|
+
if (t)
|
|
3524
|
+
this.walkCsharpTypePosition(t, fromNodeId);
|
|
3525
|
+
return;
|
|
3526
|
+
}
|
|
3527
|
+
// Composite type nodes — recurse into named children. Covers
|
|
3528
|
+
// `generic_name` (head identifier + `type_argument_list`),
|
|
3529
|
+
// `nullable_type`, `array_type`, `pointer_type`, `tuple_type`,
|
|
3530
|
+
// `ref_type`, and any newer wrapping shapes the grammar adds.
|
|
3531
|
+
// Identifiers reached here are all type-positional (parameter/field
|
|
3532
|
+
// names are gated out before we descend).
|
|
3533
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3534
|
+
const child = node.namedChild(i);
|
|
3535
|
+
if (child)
|
|
3536
|
+
this.walkCsharpTypePosition(child, fromNodeId);
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
/**
|
|
3540
|
+
* Extract PHP type references from a method/function/property declaration.
|
|
3541
|
+
* Walks ONLY type positions: each parameter's type child (inside
|
|
3542
|
+
* `formal_parameters`), the return type, and a property's type — all
|
|
3543
|
+
* `named_type` / `optional_type` / `union_type` / … direct children. Parameter
|
|
3544
|
+
* and property NAMES are `variable_name` (`$x`), never type nodes, so they
|
|
3545
|
+
* can't be mis-emitted.
|
|
3546
|
+
*/
|
|
3547
|
+
extractPhpTypeRefs(node, nodeId) {
|
|
3548
|
+
const params = node.namedChildren.find((c) => c.type === 'formal_parameters');
|
|
3549
|
+
if (params) {
|
|
3550
|
+
for (const p of params.namedChildren) {
|
|
3551
|
+
// simple_parameter / property_promotion_parameter / variadic_parameter
|
|
3552
|
+
for (const c of p.namedChildren) {
|
|
3553
|
+
if (PHP_TYPE_NODES.has(c.type))
|
|
3554
|
+
this.walkPhpTypePosition(c, nodeId);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
// Return type (method/function) and property type are TYPE nodes that are
|
|
3559
|
+
// DIRECT children of the declaration.
|
|
3560
|
+
for (const c of node.namedChildren) {
|
|
3561
|
+
if (PHP_TYPE_NODES.has(c.type))
|
|
3562
|
+
this.walkPhpTypePosition(c, nodeId);
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
/** Walk a PHP subtree KNOWN to be in a type position; emit class/interface refs. */
|
|
3566
|
+
walkPhpTypePosition(node, fromNodeId) {
|
|
3567
|
+
if (node.type === 'primitive_type')
|
|
3568
|
+
return; // int/string/void/…
|
|
3569
|
+
if (node.type === 'name') {
|
|
3570
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(node, this.source);
|
|
3571
|
+
if (name && !this.PHP_PSEUDO_TYPES.has(name)) {
|
|
3572
|
+
this.unresolvedReferences.push({
|
|
3573
|
+
fromNodeId, referenceName: name, referenceKind: 'references',
|
|
3574
|
+
line: node.startPosition.row + 1, column: node.startPosition.column,
|
|
3575
|
+
});
|
|
3576
|
+
}
|
|
3577
|
+
return;
|
|
3578
|
+
}
|
|
3579
|
+
if (node.type === 'qualified_name') {
|
|
3580
|
+
// `App\Contracts\Logger` → match on the trailing simple name (what the
|
|
3581
|
+
// class node is stored as, and what a `use` import brings into scope).
|
|
3582
|
+
const last = (0, tree_sitter_helpers_1.getNodeText)(node, this.source).split('\\').pop() ?? '';
|
|
3583
|
+
if (last && !this.PHP_PSEUDO_TYPES.has(last)) {
|
|
3584
|
+
this.unresolvedReferences.push({
|
|
3585
|
+
fromNodeId, referenceName: last, referenceKind: 'references',
|
|
3586
|
+
line: node.startPosition.row + 1, column: node.startPosition.column,
|
|
3587
|
+
});
|
|
3588
|
+
}
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
// optional_type / nullable_type / union_type / intersection_type / named_type → recurse
|
|
3592
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3593
|
+
const child = node.namedChild(i);
|
|
3594
|
+
if (child)
|
|
3595
|
+
this.walkPhpTypePosition(child, fromNodeId);
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
/**
|
|
3599
|
+
* Extract type references from a variable's type annotation.
|
|
3600
|
+
*/
|
|
3601
|
+
extractVariableTypeAnnotation(node, nodeId) {
|
|
3602
|
+
if (!this.TYPE_ANNOTATION_LANGUAGES.has(this.language))
|
|
3603
|
+
return;
|
|
3604
|
+
// Find type_annotation child (covers TS `: Type`, Rust `: Type`, etc.)
|
|
3605
|
+
const typeAnnotation = node.namedChildren.find((c) => c.type === 'type_annotation');
|
|
3606
|
+
if (typeAnnotation) {
|
|
3607
|
+
this.extractTypeRefsFromSubtree(typeAnnotation, nodeId);
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
/**
|
|
3611
|
+
* Recursively walk a subtree and extract all type_identifier references.
|
|
3612
|
+
* Handles unions, intersections, generics, arrays, etc.
|
|
3613
|
+
*/
|
|
3614
|
+
extractTypeRefsFromSubtree(node, fromNodeId) {
|
|
3615
|
+
if (node.type === 'type_identifier') {
|
|
3616
|
+
const typeName = (0, tree_sitter_helpers_1.getNodeText)(node, this.source);
|
|
3617
|
+
if (typeName && !this.BUILTIN_TYPES.has(typeName)) {
|
|
3618
|
+
this.unresolvedReferences.push({
|
|
3619
|
+
fromNodeId,
|
|
3620
|
+
referenceName: typeName,
|
|
3621
|
+
referenceKind: 'references',
|
|
3622
|
+
line: node.startPosition.row + 1,
|
|
3623
|
+
column: node.startPosition.column,
|
|
3624
|
+
});
|
|
3625
|
+
}
|
|
3626
|
+
return; // type_identifier is a leaf
|
|
3627
|
+
}
|
|
3628
|
+
// Recurse into children (handles union_type, intersection_type, generic_type, etc.)
|
|
3629
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3630
|
+
const child = node.namedChild(i);
|
|
3631
|
+
if (child) {
|
|
3632
|
+
this.extractTypeRefsFromSubtree(child, fromNodeId);
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Handle Pascal-specific AST structures.
|
|
3638
|
+
* Returns true if the node was fully handled and children should be skipped.
|
|
3639
|
+
*/
|
|
3640
|
+
visitPascalNode(node) {
|
|
3641
|
+
const nodeType = node.type;
|
|
3642
|
+
// Unit/Program/Library → module node
|
|
3643
|
+
if (nodeType === 'unit' || nodeType === 'program' || nodeType === 'library') {
|
|
3644
|
+
const moduleNameNode = node.namedChildren.find((c) => c.type === 'moduleName');
|
|
3645
|
+
const name = moduleNameNode ? (0, tree_sitter_helpers_1.getNodeText)(moduleNameNode, this.source) : '';
|
|
3646
|
+
// Fallback to filename without extension if module name is empty
|
|
3647
|
+
const moduleName = name || path.basename(this.filePath).replace(/\.[^.]+$/, '');
|
|
3648
|
+
this.createNode('module', moduleName, node);
|
|
3649
|
+
// Continue visiting children (interface/implementation sections)
|
|
3650
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3651
|
+
const child = node.namedChild(i);
|
|
3652
|
+
if (child)
|
|
3653
|
+
this.visitNode(child);
|
|
3654
|
+
}
|
|
3655
|
+
return true;
|
|
3656
|
+
}
|
|
3657
|
+
// declType wraps declClass/declIntf/declEnum/type-alias
|
|
3658
|
+
// The name lives on declType, the inner node determines the kind
|
|
3659
|
+
if (nodeType === 'declType') {
|
|
3660
|
+
this.extractPascalDeclType(node);
|
|
3661
|
+
return true;
|
|
3662
|
+
}
|
|
3663
|
+
// declUses → import nodes for each unit name
|
|
3664
|
+
if (nodeType === 'declUses') {
|
|
3665
|
+
this.extractPascalUses(node);
|
|
3666
|
+
return true;
|
|
3667
|
+
}
|
|
3668
|
+
// declConsts → container; visit children for individual declConst
|
|
3669
|
+
if (nodeType === 'declConsts') {
|
|
3670
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3671
|
+
const child = node.namedChild(i);
|
|
3672
|
+
if (child?.type === 'declConst') {
|
|
3673
|
+
this.extractPascalConst(child);
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
return true;
|
|
3677
|
+
}
|
|
3678
|
+
// declConst at top level (outside declConsts)
|
|
3679
|
+
if (nodeType === 'declConst') {
|
|
3680
|
+
this.extractPascalConst(node);
|
|
3681
|
+
return true;
|
|
3682
|
+
}
|
|
3683
|
+
// declTypes → container for type declarations
|
|
3684
|
+
if (nodeType === 'declTypes') {
|
|
3685
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3686
|
+
const child = node.namedChild(i);
|
|
3687
|
+
if (child)
|
|
3688
|
+
this.visitNode(child);
|
|
3689
|
+
}
|
|
3690
|
+
return true;
|
|
3691
|
+
}
|
|
3692
|
+
// declVars → container for variable declarations
|
|
3693
|
+
if (nodeType === 'declVars') {
|
|
3694
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3695
|
+
const child = node.namedChild(i);
|
|
3696
|
+
if (child?.type === 'declVar') {
|
|
3697
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'name');
|
|
3698
|
+
if (nameNode) {
|
|
3699
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
3700
|
+
this.createNode('variable', name, child);
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
return true;
|
|
3705
|
+
}
|
|
3706
|
+
// defProc in implementation section → extract calls but don't create duplicate nodes
|
|
3707
|
+
if (nodeType === 'defProc') {
|
|
3708
|
+
this.extractPascalDefProc(node);
|
|
3709
|
+
return true;
|
|
3710
|
+
}
|
|
3711
|
+
// declProp → property node
|
|
3712
|
+
if (nodeType === 'declProp') {
|
|
3713
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
3714
|
+
if (nameNode) {
|
|
3715
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
3716
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
3717
|
+
this.createNode('property', name, node, { visibility });
|
|
3718
|
+
}
|
|
3719
|
+
return true;
|
|
3720
|
+
}
|
|
3721
|
+
// declField → field node
|
|
3722
|
+
if (nodeType === 'declField') {
|
|
3723
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
3724
|
+
if (nameNode) {
|
|
3725
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
3726
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
3727
|
+
this.createNode('field', name, node, { visibility });
|
|
3728
|
+
}
|
|
3729
|
+
return true;
|
|
3730
|
+
}
|
|
3731
|
+
// declSection → visit children (propagates visibility via getVisibility)
|
|
3732
|
+
if (nodeType === 'declSection') {
|
|
3733
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3734
|
+
const child = node.namedChild(i);
|
|
3735
|
+
if (child)
|
|
3736
|
+
this.visitNode(child);
|
|
3737
|
+
}
|
|
3738
|
+
return true;
|
|
3739
|
+
}
|
|
3740
|
+
// exprCall → extract function call reference
|
|
3741
|
+
if (nodeType === 'exprCall') {
|
|
3742
|
+
this.extractPascalCall(node);
|
|
3743
|
+
return true;
|
|
3744
|
+
}
|
|
3745
|
+
// interface/implementation sections → visit children
|
|
3746
|
+
if (nodeType === 'interface' || nodeType === 'implementation') {
|
|
3747
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3748
|
+
const child = node.namedChild(i);
|
|
3749
|
+
if (child)
|
|
3750
|
+
this.visitNode(child);
|
|
3751
|
+
}
|
|
3752
|
+
return true;
|
|
3753
|
+
}
|
|
3754
|
+
// block (begin..end) → visit for calls
|
|
3755
|
+
if (nodeType === 'block') {
|
|
3756
|
+
this.visitPascalBlock(node);
|
|
3757
|
+
return true;
|
|
3758
|
+
}
|
|
3759
|
+
return false;
|
|
3760
|
+
}
|
|
3761
|
+
/**
|
|
3762
|
+
* Extract a Pascal declType node (class, interface, enum, or type alias)
|
|
3763
|
+
*/
|
|
3764
|
+
extractPascalDeclType(node) {
|
|
3765
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
3766
|
+
if (!nameNode)
|
|
3767
|
+
return;
|
|
3768
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
3769
|
+
// Find the inner type declaration
|
|
3770
|
+
const declClass = node.namedChildren.find((c) => c.type === 'declClass');
|
|
3771
|
+
const declIntf = node.namedChildren.find((c) => c.type === 'declIntf');
|
|
3772
|
+
const typeChild = node.namedChildren.find((c) => c.type === 'type');
|
|
3773
|
+
if (declClass) {
|
|
3774
|
+
const classNode = this.createNode('class', name, node);
|
|
3775
|
+
if (classNode) {
|
|
3776
|
+
// Extract inheritance from typeref children of declClass
|
|
3777
|
+
this.extractPascalInheritance(declClass, classNode.id);
|
|
3778
|
+
// Visit class body
|
|
3779
|
+
this.nodeStack.push(classNode.id);
|
|
3780
|
+
for (let i = 0; i < declClass.namedChildCount; i++) {
|
|
3781
|
+
const child = declClass.namedChild(i);
|
|
3782
|
+
if (child)
|
|
3783
|
+
this.visitNode(child);
|
|
3784
|
+
}
|
|
3785
|
+
this.nodeStack.pop();
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
else if (declIntf) {
|
|
3789
|
+
const ifaceNode = this.createNode('interface', name, node);
|
|
3790
|
+
if (ifaceNode) {
|
|
3791
|
+
// Visit interface members
|
|
3792
|
+
this.nodeStack.push(ifaceNode.id);
|
|
3793
|
+
for (let i = 0; i < declIntf.namedChildCount; i++) {
|
|
3794
|
+
const child = declIntf.namedChild(i);
|
|
3795
|
+
if (child)
|
|
3796
|
+
this.visitNode(child);
|
|
3797
|
+
}
|
|
3798
|
+
this.nodeStack.pop();
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
else if (typeChild) {
|
|
3802
|
+
// Check if it contains a declEnum
|
|
3803
|
+
const declEnum = typeChild.namedChildren.find((c) => c.type === 'declEnum');
|
|
3804
|
+
if (declEnum) {
|
|
3805
|
+
const enumNode = this.createNode('enum', name, node);
|
|
3806
|
+
if (enumNode) {
|
|
3807
|
+
// Extract enum members
|
|
3808
|
+
this.nodeStack.push(enumNode.id);
|
|
3809
|
+
for (let i = 0; i < declEnum.namedChildCount; i++) {
|
|
3810
|
+
const child = declEnum.namedChild(i);
|
|
3811
|
+
if (child?.type === 'declEnumValue') {
|
|
3812
|
+
const memberName = (0, tree_sitter_helpers_1.getChildByField)(child, 'name');
|
|
3813
|
+
if (memberName) {
|
|
3814
|
+
this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(memberName, this.source), child);
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
this.nodeStack.pop();
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
else {
|
|
3822
|
+
// Simple type alias: type TFoo = string / type TFoo = Integer
|
|
3823
|
+
this.createNode('type_alias', name, node);
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
else {
|
|
3827
|
+
// Fallback: could be a forward declaration or simple alias
|
|
3828
|
+
this.createNode('type_alias', name, node);
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
/**
|
|
3832
|
+
* Extract Pascal uses clause into individual import nodes
|
|
3833
|
+
*/
|
|
3834
|
+
extractPascalUses(node) {
|
|
3835
|
+
const importText = (0, tree_sitter_helpers_1.getNodeText)(node, this.source).trim();
|
|
3836
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3837
|
+
const child = node.namedChild(i);
|
|
3838
|
+
if (child?.type === 'moduleName') {
|
|
3839
|
+
const unitName = (0, tree_sitter_helpers_1.getNodeText)(child, this.source);
|
|
3840
|
+
this.createNode('import', unitName, child, {
|
|
3841
|
+
signature: importText,
|
|
3842
|
+
});
|
|
3843
|
+
// Create unresolved reference for resolution
|
|
3844
|
+
if (this.nodeStack.length > 0) {
|
|
3845
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
3846
|
+
if (parentId) {
|
|
3847
|
+
this.unresolvedReferences.push({
|
|
3848
|
+
fromNodeId: parentId,
|
|
3849
|
+
referenceName: unitName,
|
|
3850
|
+
referenceKind: 'imports',
|
|
3851
|
+
line: child.startPosition.row + 1,
|
|
3852
|
+
column: child.startPosition.column,
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
/**
|
|
3860
|
+
* Extract a Pascal constant declaration
|
|
3861
|
+
*/
|
|
3862
|
+
extractPascalConst(node) {
|
|
3863
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
3864
|
+
if (!nameNode)
|
|
3865
|
+
return;
|
|
3866
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
3867
|
+
const defaultValue = node.namedChildren.find((c) => c.type === 'defaultValue');
|
|
3868
|
+
const sig = defaultValue ? (0, tree_sitter_helpers_1.getNodeText)(defaultValue, this.source) : undefined;
|
|
3869
|
+
this.createNode('constant', name, node, { signature: sig });
|
|
3870
|
+
}
|
|
3871
|
+
/**
|
|
3872
|
+
* Extract Pascal inheritance (extends/implements) from declClass typeref children
|
|
3873
|
+
*/
|
|
3874
|
+
extractPascalInheritance(declClass, classId) {
|
|
3875
|
+
const typerefs = declClass.namedChildren.filter((c) => c.type === 'typeref');
|
|
3876
|
+
for (let i = 0; i < typerefs.length; i++) {
|
|
3877
|
+
const ref = typerefs[i];
|
|
3878
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(ref, this.source);
|
|
3879
|
+
this.unresolvedReferences.push({
|
|
3880
|
+
fromNodeId: classId,
|
|
3881
|
+
referenceName: name,
|
|
3882
|
+
referenceKind: i === 0 ? 'extends' : 'implements',
|
|
3883
|
+
line: ref.startPosition.row + 1,
|
|
3884
|
+
column: ref.startPosition.column,
|
|
3885
|
+
});
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
/**
|
|
3889
|
+
* Extract calls and resolve method context from a Pascal defProc (implementation body).
|
|
3890
|
+
* Does not create a new node — the declaration was already captured from the interface section.
|
|
3891
|
+
*/
|
|
3892
|
+
extractPascalDefProc(node) {
|
|
3893
|
+
// Find the matching declaration node by name to use as call parent
|
|
3894
|
+
const declProc = node.namedChildren.find((c) => c.type === 'declProc');
|
|
3895
|
+
if (!declProc)
|
|
3896
|
+
return;
|
|
3897
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(declProc, 'name');
|
|
3898
|
+
if (!nameNode)
|
|
3899
|
+
return;
|
|
3900
|
+
const fullName = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source).trim();
|
|
3901
|
+
// fullName is like "TAuthService.Create"
|
|
3902
|
+
const shortName = fullName.includes('.') ? fullName.split('.').pop() : fullName;
|
|
3903
|
+
const fullNameKey = fullName.toLowerCase();
|
|
3904
|
+
const shortNameKey = shortName.toLowerCase();
|
|
3905
|
+
// Build method index on first use (O(n) once, then O(1) per lookup)
|
|
3906
|
+
if (!this.methodIndex) {
|
|
3907
|
+
this.methodIndex = new Map();
|
|
3908
|
+
for (const n of this.nodes) {
|
|
3909
|
+
if (n.kind === 'method' || n.kind === 'function') {
|
|
3910
|
+
const nameKey = n.name.toLowerCase();
|
|
3911
|
+
// Keep first seen short-name mapping to avoid silently overwriting earlier entries.
|
|
3912
|
+
if (!this.methodIndex.has(nameKey)) {
|
|
3913
|
+
this.methodIndex.set(nameKey, n.id);
|
|
3914
|
+
}
|
|
3915
|
+
// For Pascal methods, also index qualified forms (e.g. TAuthService.Create).
|
|
3916
|
+
if (n.kind === 'method') {
|
|
3917
|
+
const qualifiedParts = n.qualifiedName.split('::');
|
|
3918
|
+
if (qualifiedParts.length >= 2) {
|
|
3919
|
+
// Create suffix keys so both "Module.Class.Method" and "Class.Method" can resolve.
|
|
3920
|
+
for (let i = 0; i < qualifiedParts.length - 1; i++) {
|
|
3921
|
+
const scopedName = qualifiedParts.slice(i).join('.').toLowerCase();
|
|
3922
|
+
this.methodIndex.set(scopedName, n.id);
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
const parentId = this.methodIndex.get(fullNameKey) ||
|
|
3930
|
+
this.methodIndex.get(shortNameKey) ||
|
|
3931
|
+
this.nodeStack[this.nodeStack.length - 1];
|
|
3932
|
+
if (!parentId)
|
|
3933
|
+
return;
|
|
3934
|
+
// Visit the block for calls
|
|
3935
|
+
const block = node.namedChildren.find((c) => c.type === 'block');
|
|
3936
|
+
if (block) {
|
|
3937
|
+
this.nodeStack.push(parentId);
|
|
3938
|
+
this.visitPascalBlock(block);
|
|
3939
|
+
this.nodeStack.pop();
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
/**
|
|
3943
|
+
* Extract function calls from a Pascal expression
|
|
3944
|
+
*/
|
|
3945
|
+
extractPascalCall(node) {
|
|
3946
|
+
if (this.nodeStack.length === 0)
|
|
3947
|
+
return;
|
|
3948
|
+
const callerId = this.nodeStack[this.nodeStack.length - 1];
|
|
3949
|
+
if (!callerId)
|
|
3950
|
+
return;
|
|
3951
|
+
// Get the callee name — first child is typically the identifier or exprDot
|
|
3952
|
+
const firstChild = node.namedChild(0);
|
|
3953
|
+
if (!firstChild)
|
|
3954
|
+
return;
|
|
3955
|
+
let calleeName = '';
|
|
3956
|
+
if (firstChild.type === 'exprDot') {
|
|
3957
|
+
// Qualified call: Obj.Method(...)
|
|
3958
|
+
const identifiers = firstChild.namedChildren.filter((c) => c.type === 'identifier');
|
|
3959
|
+
if (identifiers.length > 0) {
|
|
3960
|
+
calleeName = identifiers.map((id) => (0, tree_sitter_helpers_1.getNodeText)(id, this.source)).join('.');
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
else if (firstChild.type === 'identifier') {
|
|
3964
|
+
calleeName = (0, tree_sitter_helpers_1.getNodeText)(firstChild, this.source);
|
|
3965
|
+
}
|
|
3966
|
+
if (calleeName) {
|
|
3967
|
+
this.unresolvedReferences.push({
|
|
3968
|
+
fromNodeId: callerId,
|
|
3969
|
+
referenceName: calleeName,
|
|
3970
|
+
referenceKind: 'calls',
|
|
3971
|
+
line: node.startPosition.row + 1,
|
|
3972
|
+
column: node.startPosition.column,
|
|
3973
|
+
});
|
|
3974
|
+
}
|
|
3975
|
+
// Also visit arguments for nested calls
|
|
3976
|
+
const args = node.namedChildren.find((c) => c.type === 'exprArgs');
|
|
3977
|
+
if (args) {
|
|
3978
|
+
this.visitPascalBlock(args);
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
/**
|
|
3982
|
+
* Recursively visit a Pascal block/statement tree for call expressions
|
|
3983
|
+
*/
|
|
3984
|
+
visitPascalBlock(node) {
|
|
3985
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3986
|
+
const child = node.namedChild(i);
|
|
3987
|
+
if (!child)
|
|
3988
|
+
continue;
|
|
3989
|
+
if (child.type === 'exprCall') {
|
|
3990
|
+
this.extractPascalCall(child);
|
|
3991
|
+
}
|
|
3992
|
+
else if (child.type === 'exprDot') {
|
|
3993
|
+
// Check if exprDot contains an exprCall
|
|
3994
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
3995
|
+
const grandchild = child.namedChild(j);
|
|
3996
|
+
if (grandchild?.type === 'exprCall') {
|
|
3997
|
+
this.extractPascalCall(grandchild);
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
else {
|
|
4002
|
+
this.visitPascalBlock(child);
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
exports.TreeSitterExtractor = TreeSitterExtractor;
|
|
4008
|
+
/**
|
|
4009
|
+
* Extract nodes and edges from source code.
|
|
4010
|
+
*
|
|
4011
|
+
* If `frameworkNames` is provided, framework-specific extractors matching
|
|
4012
|
+
* those names and the file's language are run after the tree-sitter pass.
|
|
4013
|
+
* Their nodes/references/errors are merged into the returned result.
|
|
4014
|
+
*/
|
|
4015
|
+
function extractFromSource(filePath, source, language, frameworkNames) {
|
|
4016
|
+
const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath, source);
|
|
4017
|
+
const fileExtension = path.extname(filePath).toLowerCase();
|
|
4018
|
+
let result;
|
|
4019
|
+
// Use custom extractor for Svelte
|
|
4020
|
+
if (detectedLanguage === 'svelte') {
|
|
4021
|
+
const extractor = new svelte_extractor_1.SvelteExtractor(filePath, source);
|
|
4022
|
+
result = extractor.extract();
|
|
4023
|
+
}
|
|
4024
|
+
else if (detectedLanguage === 'vue') {
|
|
4025
|
+
// Use custom extractor for Vue
|
|
4026
|
+
const extractor = new vue_extractor_1.VueExtractor(filePath, source);
|
|
4027
|
+
result = extractor.extract();
|
|
4028
|
+
}
|
|
4029
|
+
else if (detectedLanguage === 'liquid') {
|
|
4030
|
+
// Use custom extractor for Liquid
|
|
4031
|
+
const extractor = new liquid_extractor_1.LiquidExtractor(filePath, source);
|
|
4032
|
+
result = extractor.extract();
|
|
4033
|
+
}
|
|
4034
|
+
else if (detectedLanguage === 'razor') {
|
|
4035
|
+
// Use custom extractor for ASP.NET Razor (.cshtml) / Blazor (.razor) markup
|
|
4036
|
+
const extractor = new razor_extractor_1.RazorExtractor(filePath, source);
|
|
4037
|
+
result = extractor.extract();
|
|
4038
|
+
}
|
|
4039
|
+
else if (detectedLanguage === 'xml') {
|
|
4040
|
+
// Custom extractor for MyBatis mapper XML. Non-mapper XML returns just a
|
|
4041
|
+
// file node so the watcher tracks it without emitting symbols.
|
|
4042
|
+
const extractor = new mybatis_extractor_1.MyBatisExtractor(filePath, source);
|
|
4043
|
+
result = extractor.extract();
|
|
4044
|
+
}
|
|
4045
|
+
else if (detectedLanguage === 'arkts') {
|
|
4046
|
+
const extractor = new arkts_1.ArkTSExtractor(filePath, source);
|
|
4047
|
+
result = extractor.extract();
|
|
4048
|
+
}
|
|
4049
|
+
else if ((0, grammars_1.isFileLevelOnlyLanguage)(detectedLanguage)) {
|
|
4050
|
+
// No symbol extraction at this stage — files are tracked at the file-record
|
|
4051
|
+
// level only. Framework extractors (Drupal routing yml, Spring `@Value`
|
|
4052
|
+
// resolution against application.yml/application.properties) run later and
|
|
4053
|
+
// add per-file nodes/references when they apply.
|
|
4054
|
+
result = { nodes: [], edges: [], unresolvedReferences: [], errors: [], durationMs: 0 };
|
|
4055
|
+
}
|
|
4056
|
+
else if (detectedLanguage === 'pascal' &&
|
|
4057
|
+
(fileExtension === '.dfm' || fileExtension === '.fmx')) {
|
|
4058
|
+
// Use custom extractor for DFM/FMX form files
|
|
4059
|
+
const extractor = new dfm_extractor_1.DfmExtractor(filePath, source);
|
|
4060
|
+
result = extractor.extract();
|
|
4061
|
+
}
|
|
4062
|
+
else {
|
|
4063
|
+
const extractor = new TreeSitterExtractor(filePath, source, detectedLanguage);
|
|
4064
|
+
result = extractor.extract();
|
|
4065
|
+
}
|
|
4066
|
+
// Framework-specific extraction (routes, middleware, etc.)
|
|
4067
|
+
if (frameworkNames && frameworkNames.length > 0) {
|
|
4068
|
+
const allResolvers = (0, frameworks_1.getAllFrameworkResolvers)();
|
|
4069
|
+
const applicable = (0, frameworks_1.getApplicableFrameworks)(allResolvers.filter((r) => frameworkNames.includes(r.name)), detectedLanguage);
|
|
4070
|
+
for (const fw of applicable) {
|
|
4071
|
+
if (!fw.extract)
|
|
4072
|
+
continue;
|
|
4073
|
+
try {
|
|
4074
|
+
const fwResult = fw.extract(filePath, source);
|
|
4075
|
+
result.nodes.push(...fwResult.nodes);
|
|
4076
|
+
result.unresolvedReferences.push(...fwResult.references);
|
|
4077
|
+
}
|
|
4078
|
+
catch (err) {
|
|
4079
|
+
result.errors.push({
|
|
4080
|
+
message: `Framework extractor '${fw.name}' failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
4081
|
+
filePath,
|
|
4082
|
+
severity: 'warning',
|
|
4083
|
+
});
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
return result;
|
|
4088
|
+
}
|
|
4089
|
+
//# sourceMappingURL=tree-sitter.js.map
|