@optave/codegraph 3.13.0 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -34
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +38 -40
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/rules/b2.d.ts +7 -0
- package/dist/ast-analysis/rules/b2.d.ts.map +1 -0
- package/dist/ast-analysis/rules/b2.js +240 -0
- package/dist/ast-analysis/rules/b2.js.map +1 -0
- package/dist/ast-analysis/rules/b3.d.ts +6 -0
- package/dist/ast-analysis/rules/b3.d.ts.map +1 -0
- package/dist/ast-analysis/rules/b3.js +105 -0
- package/dist/ast-analysis/rules/b3.js.map +1 -0
- package/dist/ast-analysis/rules/b4.d.ts +9 -0
- package/dist/ast-analysis/rules/b4.d.ts.map +1 -0
- package/dist/ast-analysis/rules/b4.js +361 -0
- package/dist/ast-analysis/rules/b4.js.map +1 -0
- package/dist/ast-analysis/rules/b5.d.ts +4 -0
- package/dist/ast-analysis/rules/b5.d.ts.map +1 -0
- package/dist/ast-analysis/rules/b5.js +52 -0
- package/dist/ast-analysis/rules/b5.js.map +1 -0
- package/dist/ast-analysis/rules/c.d.ts +4 -0
- package/dist/ast-analysis/rules/c.d.ts.map +1 -0
- package/dist/ast-analysis/rules/c.js +143 -0
- package/dist/ast-analysis/rules/c.js.map +1 -0
- package/dist/ast-analysis/rules/index.d.ts.map +1 -1
- package/dist/ast-analysis/rules/index.js +34 -0
- package/dist/ast-analysis/rules/index.js.map +1 -1
- package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
- package/dist/ast-analysis/rules/javascript.js +3 -0
- package/dist/ast-analysis/rules/javascript.js.map +1 -1
- package/dist/ast-analysis/shared.d.ts.map +1 -1
- package/dist/ast-analysis/shared.js +2 -0
- package/dist/ast-analysis/shared.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts +1 -0
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +5 -0
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitor.js +60 -47
- package/dist/ast-analysis/visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/cfg-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/cfg-visitor.js +126 -76
- package/dist/ast-analysis/visitors/cfg-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +27 -15
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +54 -21
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/config.js +137 -134
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/roles.d.ts.map +1 -1
- package/dist/cli/commands/roles.js +6 -1
- package/dist/cli/commands/roles.js.map +1 -1
- package/dist/db/better-sqlite3.d.ts +2 -1
- package/dist/db/better-sqlite3.d.ts.map +1 -1
- package/dist/db/better-sqlite3.js.map +1 -1
- package/dist/db/connection.d.ts +7 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +20 -5
- package/dist/db/connection.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +68 -0
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/repository/build-stmts.d.ts.map +1 -1
- package/dist/db/repository/build-stmts.js +18 -0
- package/dist/db/repository/build-stmts.js.map +1 -1
- package/dist/db/repository/dataflow.d.ts +5 -0
- package/dist/db/repository/dataflow.d.ts.map +1 -1
- package/dist/db/repository/dataflow.js +14 -0
- package/dist/db/repository/dataflow.js.map +1 -1
- package/dist/db/repository/index.d.ts +1 -1
- package/dist/db/repository/index.d.ts.map +1 -1
- package/dist/db/repository/index.js +1 -1
- package/dist/db/repository/index.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +47 -34
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/domain/analysis/context.d.ts +2 -2
- package/dist/domain/analysis/dependencies.d.ts +2 -2
- package/dist/domain/analysis/diff-impact.d.ts +2 -2
- package/dist/domain/analysis/fn-impact.d.ts +3 -1
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +4 -0
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/implementations.d.ts +2 -2
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +32 -5
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/roles.d.ts +7 -1
- package/dist/domain/analysis/roles.d.ts.map +1 -1
- package/dist/domain/analysis/roles.js +16 -0
- package/dist/domain/analysis/roles.js.map +1 -1
- package/dist/domain/analysis/symbol-lookup.d.ts +4 -4
- package/dist/domain/graph/builder/call-resolver.d.ts +17 -5
- package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
- package/dist/domain/graph/builder/call-resolver.js +85 -220
- package/dist/domain/graph/builder/call-resolver.js.map +1 -1
- package/dist/domain/graph/builder/context.d.ts +1 -0
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +16 -1
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +162 -72
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +166 -97
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +10 -4
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +496 -250
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +10 -7
- package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +2 -1
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.js +895 -545
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
- package/dist/domain/graph/resolver/points-to.d.ts.map +1 -1
- package/dist/domain/graph/resolver/points-to.js +105 -57
- package/dist/domain/graph/resolver/points-to.js.map +1 -1
- package/dist/domain/graph/resolver/strategy.d.ts +61 -0
- package/dist/domain/graph/resolver/strategy.d.ts.map +1 -0
- package/dist/domain/graph/resolver/strategy.js +222 -0
- package/dist/domain/graph/resolver/strategy.js.map +1 -0
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +16 -9
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +12 -0
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +12 -2
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/queries.d.ts +1 -1
- package/dist/domain/queries.d.ts.map +1 -1
- package/dist/domain/queries.js +1 -1
- package/dist/domain/queries.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +3 -0
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/domain/wasm-worker-pool.d.ts.map +1 -1
- package/dist/domain/wasm-worker-pool.js +24 -5
- package/dist/domain/wasm-worker-pool.js.map +1 -1
- package/dist/domain/wasm-worker-protocol.d.ts +7 -0
- package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
- package/dist/extractors/dart.js +48 -3
- package/dist/extractors/dart.js.map +1 -1
- package/dist/extractors/groovy.js +62 -3
- package/dist/extractors/groovy.js.map +1 -1
- package/dist/extractors/helpers.d.ts +4 -2
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +5 -1
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.js +77 -1
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +549 -163
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +58 -3
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/objc.js +25 -2
- package/dist/extractors/objc.js.map +1 -1
- package/dist/extractors/scala.js +62 -2
- package/dist/extractors/scala.js.map +1 -1
- package/dist/extractors/swift.js +52 -3
- package/dist/extractors/swift.js.map +1 -1
- package/dist/features/audit.js +26 -23
- package/dist/features/audit.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +12 -9
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +25 -18
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +18 -5
- package/dist/features/check.js.map +1 -1
- package/dist/features/communities.d.ts +4 -2
- package/dist/features/communities.d.ts.map +1 -1
- package/dist/features/communities.js +6 -4
- package/dist/features/communities.js.map +1 -1
- package/dist/features/dataflow.d.ts +60 -0
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +530 -6
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/manifesto.d.ts.map +1 -1
- package/dist/features/manifesto.js +59 -72
- package/dist/features/manifesto.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +27 -22
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/snapshot.d.ts.map +1 -1
- package/dist/features/snapshot.js +36 -28
- package/dist/features/snapshot.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +150 -62
- package/dist/features/structure.js.map +1 -1
- package/dist/features/triage.d.ts.map +1 -1
- package/dist/features/triage.js +18 -11
- package/dist/features/triage.js.map +1 -1
- package/dist/graph/algorithms/bfs.d.ts +1 -1
- package/dist/graph/algorithms/bfs.d.ts.map +1 -1
- package/dist/graph/algorithms/bfs.js +14 -13
- package/dist/graph/algorithms/bfs.js.map +1 -1
- package/dist/graph/algorithms/tarjan.d.ts.map +1 -1
- package/dist/graph/algorithms/tarjan.js +5 -0
- package/dist/graph/algorithms/tarjan.js.map +1 -1
- package/dist/graph/builders/dependency.js +28 -22
- package/dist/graph/builders/dependency.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts +10 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +60 -6
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/infrastructure/config.d.ts +10 -0
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +31 -3
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +0 -7
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +29 -13
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/infrastructure/update-check.d.ts.map +1 -1
- package/dist/infrastructure/update-check.js +49 -31
- package/dist/infrastructure/update-check.js.map +1 -1
- package/dist/mcp/server.d.ts +2 -10
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/ast-query.d.ts +1 -1
- package/dist/mcp/tools/ast-query.d.ts.map +1 -1
- package/dist/mcp/tools/audit.d.ts +1 -1
- package/dist/mcp/tools/audit.d.ts.map +1 -1
- package/dist/mcp/tools/batch-query.d.ts +1 -1
- package/dist/mcp/tools/batch-query.d.ts.map +1 -1
- package/dist/mcp/tools/branch-compare.d.ts +1 -1
- package/dist/mcp/tools/branch-compare.d.ts.map +1 -1
- package/dist/mcp/tools/brief.d.ts +1 -1
- package/dist/mcp/tools/brief.d.ts.map +1 -1
- package/dist/mcp/tools/cfg.d.ts +1 -1
- package/dist/mcp/tools/cfg.d.ts.map +1 -1
- package/dist/mcp/tools/check.d.ts +1 -1
- package/dist/mcp/tools/check.d.ts.map +1 -1
- package/dist/mcp/tools/co-changes.d.ts +1 -1
- package/dist/mcp/tools/co-changes.d.ts.map +1 -1
- package/dist/mcp/tools/code-owners.d.ts +1 -1
- package/dist/mcp/tools/code-owners.d.ts.map +1 -1
- package/dist/mcp/tools/communities.d.ts +1 -1
- package/dist/mcp/tools/communities.d.ts.map +1 -1
- package/dist/mcp/tools/complexity.d.ts +1 -1
- package/dist/mcp/tools/complexity.d.ts.map +1 -1
- package/dist/mcp/tools/context.d.ts +1 -1
- package/dist/mcp/tools/context.d.ts.map +1 -1
- package/dist/mcp/tools/dataflow.d.ts +1 -1
- package/dist/mcp/tools/dataflow.d.ts.map +1 -1
- package/dist/mcp/tools/diff-impact.d.ts +1 -1
- package/dist/mcp/tools/diff-impact.d.ts.map +1 -1
- package/dist/mcp/tools/execution-flow.d.ts +1 -1
- package/dist/mcp/tools/execution-flow.d.ts.map +1 -1
- package/dist/mcp/tools/export-graph.d.ts +1 -1
- package/dist/mcp/tools/export-graph.d.ts.map +1 -1
- package/dist/mcp/tools/file-deps.d.ts +1 -1
- package/dist/mcp/tools/file-deps.d.ts.map +1 -1
- package/dist/mcp/tools/file-exports.d.ts +1 -1
- package/dist/mcp/tools/file-exports.d.ts.map +1 -1
- package/dist/mcp/tools/find-cycles.d.ts +1 -1
- package/dist/mcp/tools/find-cycles.d.ts.map +1 -1
- package/dist/mcp/tools/fn-impact.d.ts +1 -1
- package/dist/mcp/tools/fn-impact.d.ts.map +1 -1
- package/dist/mcp/tools/impact-analysis.d.ts +1 -1
- package/dist/mcp/tools/impact-analysis.d.ts.map +1 -1
- package/dist/mcp/tools/implementations.d.ts +1 -1
- package/dist/mcp/tools/implementations.d.ts.map +1 -1
- package/dist/mcp/tools/index.d.ts +2 -5
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/interfaces.d.ts +1 -1
- package/dist/mcp/tools/interfaces.d.ts.map +1 -1
- package/dist/mcp/tools/list-functions.d.ts +1 -1
- package/dist/mcp/tools/list-functions.d.ts.map +1 -1
- package/dist/mcp/tools/list-repos.d.ts +1 -1
- package/dist/mcp/tools/list-repos.d.ts.map +1 -1
- package/dist/mcp/tools/module-map.d.ts +1 -1
- package/dist/mcp/tools/module-map.d.ts.map +1 -1
- package/dist/mcp/tools/node-roles.d.ts +1 -1
- package/dist/mcp/tools/node-roles.d.ts.map +1 -1
- package/dist/mcp/tools/path.d.ts +1 -1
- package/dist/mcp/tools/path.d.ts.map +1 -1
- package/dist/mcp/tools/query.d.ts +1 -1
- package/dist/mcp/tools/query.d.ts.map +1 -1
- package/dist/mcp/tools/semantic-search.d.ts +1 -1
- package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
- package/dist/mcp/tools/sequence.d.ts +1 -1
- package/dist/mcp/tools/sequence.d.ts.map +1 -1
- package/dist/mcp/tools/structure.d.ts +1 -1
- package/dist/mcp/tools/structure.d.ts.map +1 -1
- package/dist/mcp/tools/symbol-children.d.ts +1 -1
- package/dist/mcp/tools/symbol-children.d.ts.map +1 -1
- package/dist/mcp/tools/triage.d.ts +1 -1
- package/dist/mcp/tools/triage.d.ts.map +1 -1
- package/dist/mcp/tools/where.d.ts +1 -1
- package/dist/mcp/tools/where.d.ts.map +1 -1
- package/dist/mcp/types.d.ts +19 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +6 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/presentation/queries-cli/index.d.ts +1 -1
- package/dist/presentation/queries-cli/index.d.ts.map +1 -1
- package/dist/presentation/queries-cli/index.js +1 -1
- package/dist/presentation/queries-cli/index.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts +1 -0
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +20 -1
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/queries-cli.d.ts +1 -1
- package/dist/presentation/queries-cli.d.ts.map +1 -1
- package/dist/presentation/queries-cli.js +1 -1
- package/dist/presentation/queries-cli.js.map +1 -1
- package/dist/presentation/viewer.d.ts.map +1 -1
- package/dist/presentation/viewer.js +45 -32
- package/dist/presentation/viewer.js.map +1 -1
- package/dist/shared/constants.d.ts +21 -0
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +25 -0
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/normalize.d.ts.map +1 -1
- package/dist/shared/normalize.js +12 -22
- package/dist/shared/normalize.js.map +1 -1
- package/dist/shared/paginate.d.ts +4 -17
- package/dist/shared/paginate.d.ts.map +1 -1
- package/dist/shared/paginate.js.map +1 -1
- package/dist/types.d.ts +76 -1
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/package.json +7 -7
- package/src/ast-analysis/engine.ts +43 -63
- package/src/ast-analysis/rules/b2.ts +263 -0
- package/src/ast-analysis/rules/b3.ts +127 -0
- package/src/ast-analysis/rules/b4.ts +378 -0
- package/src/ast-analysis/rules/b5.ts +65 -0
- package/src/ast-analysis/rules/c.ts +157 -0
- package/src/ast-analysis/rules/index.ts +34 -0
- package/src/ast-analysis/rules/javascript.ts +3 -0
- package/src/ast-analysis/shared.ts +2 -0
- package/src/ast-analysis/visitor-utils.ts +5 -0
- package/src/ast-analysis/visitor.ts +82 -52
- package/src/ast-analysis/visitors/cfg-visitor.ts +198 -84
- package/src/ast-analysis/visitors/complexity-visitor.ts +44 -16
- package/src/ast-analysis/visitors/dataflow-visitor.ts +68 -29
- package/src/cli/commands/config.ts +184 -184
- package/src/cli/commands/roles.ts +6 -1
- package/src/db/better-sqlite3.ts +5 -4
- package/src/db/connection.ts +23 -5
- package/src/db/index.ts +1 -0
- package/src/db/migrations.ts +68 -0
- package/src/db/repository/build-stmts.ts +30 -0
- package/src/db/repository/dataflow.ts +16 -0
- package/src/db/repository/index.ts +1 -1
- package/src/db/repository/native-repository.ts +56 -40
- package/src/domain/analysis/fn-impact.ts +4 -0
- package/src/domain/analysis/module-map.ts +38 -6
- package/src/domain/analysis/roles.ts +23 -0
- package/src/domain/graph/builder/call-resolver.ts +112 -232
- package/src/domain/graph/builder/context.ts +1 -0
- package/src/domain/graph/builder/helpers.ts +190 -72
- package/src/domain/graph/builder/incremental.ts +249 -120
- package/src/domain/graph/builder/pipeline.ts +11 -5
- package/src/domain/graph/builder/stages/build-edges.ts +696 -296
- package/src/domain/graph/builder/stages/collect-files.ts +12 -6
- package/src/domain/graph/builder/stages/detect-changes.ts +3 -1
- package/src/domain/graph/builder/stages/native-orchestrator.ts +1102 -590
- package/src/domain/graph/resolver/points-to.ts +182 -59
- package/src/domain/graph/resolver/strategy.ts +265 -0
- package/src/domain/graph/watcher.ts +19 -9
- package/src/domain/parser.ts +12 -2
- package/src/domain/queries.ts +1 -1
- package/src/domain/wasm-worker-entry.ts +3 -0
- package/src/domain/wasm-worker-pool.ts +28 -4
- package/src/domain/wasm-worker-protocol.ts +4 -0
- package/src/extractors/dart.ts +48 -3
- package/src/extractors/groovy.ts +62 -2
- package/src/extractors/helpers.ts +5 -2
- package/src/extractors/java.ts +80 -1
- package/src/extractors/javascript.ts +566 -161
- package/src/extractors/kotlin.ts +57 -3
- package/src/extractors/objc.ts +25 -1
- package/src/extractors/scala.ts +63 -1
- package/src/extractors/swift.ts +46 -3
- package/src/features/audit.ts +43 -34
- package/src/features/boundaries.ts +17 -9
- package/src/features/cfg.ts +31 -22
- package/src/features/check.ts +21 -5
- package/src/features/communities.ts +28 -19
- package/src/features/dataflow.ts +755 -6
- package/src/features/manifesto.ts +76 -75
- package/src/features/sequence.ts +29 -23
- package/src/features/snapshot.ts +36 -25
- package/src/features/structure.ts +185 -55
- package/src/features/triage.ts +28 -15
- package/src/graph/algorithms/bfs.ts +13 -12
- package/src/graph/algorithms/tarjan.ts +5 -0
- package/src/graph/builders/dependency.ts +35 -23
- package/src/graph/classifiers/roles.ts +74 -7
- package/src/infrastructure/config.ts +32 -3
- package/src/infrastructure/registry.ts +44 -20
- package/src/infrastructure/update-check.ts +55 -33
- package/src/mcp/server.ts +2 -8
- package/src/mcp/tools/ast-query.ts +1 -1
- package/src/mcp/tools/audit.ts +1 -1
- package/src/mcp/tools/batch-query.ts +1 -1
- package/src/mcp/tools/branch-compare.ts +1 -1
- package/src/mcp/tools/brief.ts +1 -1
- package/src/mcp/tools/cfg.ts +1 -1
- package/src/mcp/tools/check.ts +1 -1
- package/src/mcp/tools/co-changes.ts +1 -1
- package/src/mcp/tools/code-owners.ts +1 -1
- package/src/mcp/tools/communities.ts +1 -1
- package/src/mcp/tools/complexity.ts +1 -1
- package/src/mcp/tools/context.ts +1 -1
- package/src/mcp/tools/dataflow.ts +1 -1
- package/src/mcp/tools/diff-impact.ts +1 -1
- package/src/mcp/tools/execution-flow.ts +1 -1
- package/src/mcp/tools/export-graph.ts +1 -1
- package/src/mcp/tools/file-deps.ts +1 -1
- package/src/mcp/tools/file-exports.ts +1 -1
- package/src/mcp/tools/find-cycles.ts +1 -1
- package/src/mcp/tools/fn-impact.ts +1 -1
- package/src/mcp/tools/impact-analysis.ts +1 -1
- package/src/mcp/tools/implementations.ts +1 -1
- package/src/mcp/tools/index.ts +2 -5
- package/src/mcp/tools/interfaces.ts +1 -1
- package/src/mcp/tools/list-functions.ts +1 -1
- package/src/mcp/tools/list-repos.ts +1 -1
- package/src/mcp/tools/module-map.ts +1 -1
- package/src/mcp/tools/node-roles.ts +1 -1
- package/src/mcp/tools/path.ts +1 -1
- package/src/mcp/tools/query.ts +1 -1
- package/src/mcp/tools/semantic-search.ts +1 -1
- package/src/mcp/tools/sequence.ts +1 -1
- package/src/mcp/tools/structure.ts +1 -1
- package/src/mcp/tools/symbol-children.ts +1 -1
- package/src/mcp/tools/triage.ts +1 -1
- package/src/mcp/tools/where.ts +1 -1
- package/src/mcp/types.ts +21 -0
- package/src/presentation/queries-cli/index.ts +1 -1
- package/src/presentation/queries-cli/overview.ts +35 -1
- package/src/presentation/queries-cli.ts +1 -0
- package/src/presentation/viewer.ts +98 -87
- package/src/shared/constants.ts +26 -0
- package/src/shared/normalize.ts +13 -22
- package/src/shared/paginate.ts +4 -18
- package/src/types.ts +86 -1
|
@@ -11,6 +11,7 @@ import { setTypeMapEntry } from '../../../../extractors/helpers.js';
|
|
|
11
11
|
import { PROPAGATION_HOP_PENALTY } from '../../../../extractors/javascript.js';
|
|
12
12
|
import { debug } from '../../../../infrastructure/logger.js';
|
|
13
13
|
import { loadNative } from '../../../../infrastructure/native.js';
|
|
14
|
+
import { TS_NATIVE_CONFIDENCE_FLOOR } from '../../../../shared/constants.js';
|
|
14
15
|
import { computeConfidence } from '../../resolve.js';
|
|
15
16
|
import { buildPointsToMap, resolveViaPointsTo } from '../../resolver/points-to.js';
|
|
16
17
|
import { enrichTypeMapWithTsc } from '../../resolver/ts-resolver.js';
|
|
@@ -70,7 +71,7 @@ function emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows
|
|
|
70
71
|
}
|
|
71
72
|
const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
|
|
72
73
|
if (candidates && candidates.length > 0) {
|
|
73
|
-
allEdgeRows.push([fileNodeId, candidates[0].id, 'imports-type', 1.0, 0, null]);
|
|
74
|
+
allEdgeRows.push([fileNodeId, candidates[0].id, 'imports-type', 1.0, 0, null, null]);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -84,7 +85,7 @@ function emitEdgesForImport(ctx, imp, fileNodeId, relPath, getNodeIdStmt, allEdg
|
|
|
84
85
|
if (!targetRow)
|
|
85
86
|
return;
|
|
86
87
|
const edgeKind = importEdgeKind(imp);
|
|
87
|
-
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null]);
|
|
88
|
+
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null, null]);
|
|
88
89
|
if (imp.typeOnly) {
|
|
89
90
|
emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows);
|
|
90
91
|
}
|
|
@@ -122,7 +123,7 @@ function buildBarrelEdges(ctx, imp, resolvedPath, fileNodeId, edgeKind, getNodeI
|
|
|
122
123
|
: edgeKind === 'dynamic-imports'
|
|
123
124
|
? 'dynamic-imports'
|
|
124
125
|
: 'imports';
|
|
125
|
-
edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null]);
|
|
126
|
+
edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null, null]);
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
}
|
|
@@ -237,7 +238,7 @@ function buildImportEdgesNative(ctx, getNodeIdStmt, allEdgeRows, native) {
|
|
|
237
238
|
const symbolNodes = collectSymbolNodes(ctx);
|
|
238
239
|
const nativeEdges = native.buildImportEdges(files, resolvedImports, fileReexports, registry.ids, barrelFiles, ctx.rootDir, symbolNodes);
|
|
239
240
|
for (const e of nativeEdges) {
|
|
240
|
-
allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic, null]);
|
|
241
|
+
allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic, null, null]);
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
244
|
// ── Phase 8.2: Cross-file return-type propagation ───────────────────────
|
|
@@ -377,6 +378,7 @@ function buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodes, native)
|
|
|
377
378
|
e.confidence,
|
|
378
379
|
e.dynamic,
|
|
379
380
|
e.kind === 'calls' ? 'ts-native' : null,
|
|
381
|
+
e.dynamic_kind ?? null,
|
|
380
382
|
]);
|
|
381
383
|
}
|
|
382
384
|
}
|
|
@@ -443,7 +445,7 @@ function buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLook
|
|
|
443
445
|
const conf = computeConfidence(relPath, t.file, null);
|
|
444
446
|
if (conf > 0) {
|
|
445
447
|
seenByPair.add(edgeKey);
|
|
446
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native']);
|
|
448
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native', null]);
|
|
447
449
|
}
|
|
448
450
|
}
|
|
449
451
|
}
|
|
@@ -519,7 +521,7 @@ function buildChaPostPass(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
|
|
|
519
521
|
// Tag super-dispatch edges distinctly so runChaPostPass can exclude them
|
|
520
522
|
// from further CHA expansion (super calls are not virtual dispatch).
|
|
521
523
|
const technique = call.receiver === 'super' ? 'super-dispatch' : 'cha';
|
|
522
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique]);
|
|
524
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique, null]);
|
|
523
525
|
}
|
|
524
526
|
}
|
|
525
527
|
}
|
|
@@ -594,7 +596,11 @@ function buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
|
|
|
594
596
|
}
|
|
595
597
|
const seenCallEdges = new Set();
|
|
596
598
|
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
597
|
-
|
|
599
|
+
// Build the import-artifact name set: importedNames plus CJS require bindings.
|
|
600
|
+
// Used only by resolveReceiverEdge to distinguish local definitions from CJS
|
|
601
|
+
// import shadows — does NOT affect call-target resolution or DB edges (#1661).
|
|
602
|
+
const importArtifactNames = buildImportArtifactNames(importedNames, symbols, ctx, relPath, rootDir);
|
|
603
|
+
buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCallEdges, lookup, allEdgeRows, typeMap, ptsMap, chaCtx, importArtifactNames);
|
|
598
604
|
buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows);
|
|
599
605
|
}
|
|
600
606
|
}
|
|
@@ -634,6 +640,33 @@ function buildImportedNamesMap(ctx, relPath, symbols, rootDir) {
|
|
|
634
640
|
}
|
|
635
641
|
return importedNames;
|
|
636
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* Build a map of all names that are import artifacts in this file — includes
|
|
645
|
+
* both ES module imports (already in importedNames) and CJS require destructuring
|
|
646
|
+
* bindings (`const { X } = require('./path')`). Used exclusively by resolveReceiverEdge
|
|
647
|
+
* to classify same-file function-kind nodes as import artifacts vs. local definitions.
|
|
648
|
+
* Does NOT affect call resolution or DB edge creation (#1661).
|
|
649
|
+
*/
|
|
650
|
+
function buildImportArtifactNames(importedNames, symbols, ctx, relPath, rootDir) {
|
|
651
|
+
if (!symbols.cjsRequireBindings?.length)
|
|
652
|
+
return importedNames;
|
|
653
|
+
const combined = new Map(importedNames);
|
|
654
|
+
const traceBarrel = (resolvedPath, cleanName) => {
|
|
655
|
+
if (!isBarrelFile(ctx, resolvedPath))
|
|
656
|
+
return resolvedPath;
|
|
657
|
+
const actual = resolveBarrelExportCached(ctx, resolvedPath, cleanName);
|
|
658
|
+
return actual ?? resolvedPath;
|
|
659
|
+
};
|
|
660
|
+
for (const binding of symbols.cjsRequireBindings) {
|
|
661
|
+
const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), binding.source);
|
|
662
|
+
for (const name of binding.names) {
|
|
663
|
+
if (!combined.has(name)) {
|
|
664
|
+
combined.set(name, traceBarrel(resolvedPath, name));
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return combined;
|
|
669
|
+
}
|
|
637
670
|
function makeContextLookup(ctx, getNodeIdStmt) {
|
|
638
671
|
return {
|
|
639
672
|
byNameAndFile: (name, file) => ctx.nodesByNameAndFile.get(`${name}|${file}`) ?? [],
|
|
@@ -702,209 +735,419 @@ function buildDefinitionParamsMap(definitions) {
|
|
|
702
735
|
}
|
|
703
736
|
return map;
|
|
704
737
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
738
|
+
// ── Per-call resolution helpers ─────────────────────────────────────────
|
|
739
|
+
/**
|
|
740
|
+
* Resolve targets for a single call site with all JS-path fallbacks applied.
|
|
741
|
+
*
|
|
742
|
+
* Runs in order:
|
|
743
|
+
* 1. Primary resolution via `resolveCallTargets` (importedNames + typeMap).
|
|
744
|
+
* 2. Same-class `this.method()` fallback (non-super receivers only).
|
|
745
|
+
* 3. Same-class bare-call fallback for non-JS/TS class-scoped languages.
|
|
746
|
+
* 4. Object.defineProperty accessor fallback (this-calls inside getter/setter).
|
|
747
|
+
*
|
|
748
|
+
* Returns the resolved targets array and the importedFrom hint for confidence scoring.
|
|
749
|
+
*/
|
|
750
|
+
function resolveFallbackTargets(call, caller, relPath, importedNames, lookup, typeMap, definePropertyReceivers) {
|
|
751
|
+
// RES-4: Kotlin member callable reference — `Greeter::greet` emits
|
|
752
|
+
// { name: 'greet', receiver: 'Greeter', dynamicKind: 'reflection' }.
|
|
753
|
+
// The receiver is the class qualifier (not a typeMap variable), so
|
|
754
|
+
// resolveCallTargets would find a same-named top-level function via
|
|
755
|
+
// byNameAndFile('greet', relPath) before the qualified form is tried.
|
|
756
|
+
// Prefer `Greeter.greet` in the same file first; fall through to the
|
|
757
|
+
// normal path only when no qualified match exists.
|
|
758
|
+
let preQualifiedTargets = [];
|
|
759
|
+
if (call.dynamicKind === 'reflection' &&
|
|
760
|
+
call.receiver &&
|
|
761
|
+
!call.keyExpr &&
|
|
762
|
+
!isModuleScopedLanguage(relPath)) {
|
|
763
|
+
preQualifiedTargets = lookup
|
|
764
|
+
.byNameAndFile(`${call.receiver}.${call.name}`, relPath)
|
|
765
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
766
|
+
}
|
|
767
|
+
let { targets, importedFrom } = preQualifiedTargets.length > 0
|
|
768
|
+
? {
|
|
769
|
+
targets: preQualifiedTargets,
|
|
770
|
+
importedFrom: undefined,
|
|
771
|
+
}
|
|
772
|
+
: resolveCallTargets(lookup, call, relPath, importedNames, typeMap, caller.callerName);
|
|
773
|
+
// Same-class `this.method()` fallback: when the call receiver is `this` and
|
|
774
|
+
// resolveCallTargets found nothing, derive the enclosing class name from the
|
|
775
|
+
// caller (e.g. `Logger.info` → class prefix `Logger`) and retry with the
|
|
776
|
+
// qualified method name `Logger._write`. This mirrors what the native Rust
|
|
777
|
+
// engine does implicitly via its class-scoped symbol table.
|
|
778
|
+
// NOTE: restricted to `this` only — `super.method()` targets a parent class,
|
|
779
|
+
// not the enclosing class, so qualifying with the child class name would
|
|
780
|
+
// produce a false edge when the child also defines a same-named method.
|
|
781
|
+
if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
|
|
782
|
+
const lastDot = caller.callerName.lastIndexOf('.');
|
|
783
|
+
if (lastDot > 0) {
|
|
784
|
+
const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
|
|
785
|
+
const className = caller.callerName.slice(prevDot + 1, lastDot);
|
|
786
|
+
const qualified = lookup
|
|
787
|
+
.byNameAndFile(`${className}.${call.name}`, relPath)
|
|
788
|
+
.filter((n) => n.kind === 'method');
|
|
789
|
+
if (qualified.length > 0)
|
|
790
|
+
targets = qualified;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
// Same-class bare-call fallback: when a no-receiver call can't be resolved
|
|
794
|
+
// globally, try the caller's own class as a qualifier. Handles C# static
|
|
795
|
+
// sibling calls: `IsValidEmail()` inside `Validators.ValidateUser` resolves
|
|
796
|
+
// to `Validators.IsValidEmail`. Skipped for JS/TS where bare calls are
|
|
797
|
+
// module-scoped, not class-scoped.
|
|
798
|
+
if (targets.length === 0 &&
|
|
799
|
+
!call.receiver &&
|
|
800
|
+
caller.callerName != null &&
|
|
801
|
+
!isModuleScopedLanguage(relPath)) {
|
|
802
|
+
const lastDot = caller.callerName.lastIndexOf('.');
|
|
803
|
+
if (lastDot > 0) {
|
|
804
|
+
const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
|
|
805
|
+
const className = caller.callerName.slice(prevDot + 1, lastDot);
|
|
806
|
+
const qualified = lookup
|
|
807
|
+
.byNameAndFile(`${className}.${call.name}`, relPath)
|
|
808
|
+
.filter((n) => n.kind === 'method');
|
|
809
|
+
if (qualified.length > 0)
|
|
810
|
+
targets = qualified;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
// RES-3: reflection with literal method name — JVM getMethod("name") / invokeMethod("name").
|
|
814
|
+
// Java/Scala/Groovy methods are stored as class-qualified names (e.g. Reflection.greet),
|
|
815
|
+
// so lookup.byNameAndFile('greet', relPath) finds nothing. When dynamicKind='reflection'
|
|
816
|
+
// and keyExpr is set (a string-literal method name was captured), try the qualified form:
|
|
817
|
+
// 1. typeMap[receiver] → resolvedType → lookup `resolvedType.keyExpr` (type-annotated local)
|
|
818
|
+
// 2. callerName class prefix → `CallerClass.keyExpr` (same-class sibling, e.g. Groovy obj)
|
|
819
|
+
// Scoped to non-JS/TS files to avoid interfering with the JS reflection path.
|
|
820
|
+
if (targets.length === 0 &&
|
|
821
|
+
call.dynamicKind === 'reflection' &&
|
|
822
|
+
call.keyExpr &&
|
|
823
|
+
call.receiver &&
|
|
824
|
+
!isModuleScopedLanguage(relPath)) {
|
|
825
|
+
const typeEntry = typeMap.get(call.receiver);
|
|
826
|
+
const resolvedType = typeEntry
|
|
827
|
+
? typeof typeEntry === 'string'
|
|
828
|
+
? typeEntry
|
|
829
|
+
: typeEntry.type
|
|
830
|
+
: null;
|
|
831
|
+
if (resolvedType) {
|
|
832
|
+
const qualified = lookup
|
|
833
|
+
.byNameAndFile(`${resolvedType}.${call.keyExpr}`, relPath)
|
|
834
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
835
|
+
if (qualified.length > 0)
|
|
836
|
+
targets = qualified;
|
|
837
|
+
}
|
|
838
|
+
if (targets.length === 0 && caller.callerName != null) {
|
|
732
839
|
const lastDot = caller.callerName.lastIndexOf('.');
|
|
733
840
|
if (lastDot > 0) {
|
|
734
841
|
const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
|
|
735
|
-
const
|
|
736
|
-
const qualifiedName = `${className}.${call.name}`;
|
|
842
|
+
const callerClass = caller.callerName.slice(prevDot + 1, lastDot);
|
|
737
843
|
const qualified = lookup
|
|
738
|
-
.byNameAndFile(
|
|
739
|
-
.filter((n) => n.kind === 'method');
|
|
740
|
-
if (qualified.length > 0)
|
|
844
|
+
.byNameAndFile(`${callerClass}.${call.keyExpr}`, relPath)
|
|
845
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
846
|
+
if (qualified.length > 0)
|
|
741
847
|
targets = qualified;
|
|
742
|
-
}
|
|
743
848
|
}
|
|
744
849
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
850
|
+
}
|
|
851
|
+
// Object.defineProperty accessor fallback: when a function is registered as
|
|
852
|
+
// a getter/setter via `Object.defineProperty(obj, "bar", { get: getter })`,
|
|
853
|
+
// calls to `this.X()` inside `getter` resolve against `obj` (this === obj
|
|
854
|
+
// when the accessor is invoked). If the same-class fallback above found
|
|
855
|
+
// nothing, try treating `obj` as the receiver and look up `obj.X` in the
|
|
856
|
+
// typeMap, or fall back to a same-file lookup of any definition named X
|
|
857
|
+
// that belongs to the object literal or its type.
|
|
858
|
+
if (targets.length === 0 &&
|
|
859
|
+
call.receiver === 'this' &&
|
|
860
|
+
caller.callerName != null &&
|
|
861
|
+
definePropertyReceivers) {
|
|
862
|
+
const receiverVarName = definePropertyReceivers.get(caller.callerName);
|
|
863
|
+
if (receiverVarName) {
|
|
864
|
+
const typeEntry = typeMap.get(receiverVarName);
|
|
865
|
+
const typeName = typeEntry
|
|
866
|
+
? typeof typeEntry === 'string'
|
|
867
|
+
? typeEntry
|
|
868
|
+
: typeEntry.type
|
|
869
|
+
: null;
|
|
870
|
+
if (typeName) {
|
|
871
|
+
const qualified = lookup.byNameAndFile(`${typeName}.${call.name}`, relPath);
|
|
872
|
+
if (qualified.length > 0)
|
|
873
|
+
targets = [...qualified];
|
|
874
|
+
}
|
|
875
|
+
// If still no targets, search for any definition named `call.name` in
|
|
876
|
+
// the same file — handles plain object literals where the method isn't
|
|
877
|
+
// qualified (e.g. `const obj = { baz() {} }` defines `baz` directly).
|
|
878
|
+
// Note: this is intentionally broad — it matches any same-file definition
|
|
879
|
+
// with the called name, not just members of the receiver object. This is
|
|
880
|
+
// the same behaviour used by the native post-pass path (buildDefinePropertyPostPass).
|
|
881
|
+
if (targets.length === 0) {
|
|
882
|
+
const sameFile = lookup.byNameAndFile(call.name, relPath);
|
|
883
|
+
if (sameFile.length > 0)
|
|
884
|
+
targets = [...sameFile];
|
|
765
885
|
}
|
|
766
886
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
887
|
+
}
|
|
888
|
+
return { targets, importedFrom };
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Emit direct-call edges for the resolved targets of a single call site.
|
|
892
|
+
*
|
|
893
|
+
* Sorts targets by confidence descending first, then for each target:
|
|
894
|
+
* - Skips self-edges and already-seen edges.
|
|
895
|
+
* - If a pts edge already exists for this pair, upgrades it in-place to
|
|
896
|
+
* direct-call confidence and promotes to seenCallEdges.
|
|
897
|
+
* - If a dyn=0 edge already exists and the incoming call has an explicit
|
|
898
|
+
* dynamicKind (e.g. 'reflection' for bare decorators), upgrades the
|
|
899
|
+
* existing row to dyn=1 in-place so the semantic classification wins.
|
|
900
|
+
* - Otherwise records a new `calls` edge with `ts-native` technique.
|
|
901
|
+
*/
|
|
902
|
+
function emitDirectCallEdgesForCall(caller, targets, importedFrom, isDynamic, hasDynamicKind, relPath, seenCallEdges, ptsEdgeRows, allEdgeRows, dynZeroEdgeRows) {
|
|
903
|
+
// Sort targets by confidence descending before emitting edges.
|
|
904
|
+
// For multi-target calls with duplicate (source_id, target_id) pairs the
|
|
905
|
+
// stored confidence depends on which duplicate is processed last — sorting
|
|
906
|
+
// here guarantees the highest-confidence target wins on dedup, matching the
|
|
907
|
+
// native engine's sort_targets_by_confidence call in build_edges.rs.
|
|
908
|
+
const sorted = targets.length > 1
|
|
909
|
+
? [...targets].sort((a, b) => computeConfidence(relPath, b.file, importedFrom ?? null) -
|
|
910
|
+
computeConfidence(relPath, a.file, importedFrom ?? null))
|
|
911
|
+
: targets;
|
|
912
|
+
for (const t of sorted) {
|
|
913
|
+
const edgeKey = `${caller.id}|${t.id}`;
|
|
914
|
+
if (t.id === caller.id)
|
|
915
|
+
continue;
|
|
916
|
+
const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
|
|
917
|
+
if (seenCallEdges.has(edgeKey)) {
|
|
918
|
+
// Edge already emitted. If the incoming call carries an explicit semantic
|
|
919
|
+
// dynamic classification (dynamicKind set — e.g. 'reflection' for bare
|
|
920
|
+
// decorators) and the existing edge was recorded with dyn=0, upgrade it
|
|
921
|
+
// in-place so the more specific classification wins.
|
|
922
|
+
// Generic dynamic=true without dynamicKind (alias/callback calls) does
|
|
923
|
+
// NOT override dyn=0 to avoid false positives on f.call/f.bind patterns.
|
|
924
|
+
if (isDynamic === 1 && hasDynamicKind && dynZeroEdgeRows) {
|
|
925
|
+
const dynZeroIdx = dynZeroEdgeRows.get(edgeKey);
|
|
926
|
+
if (dynZeroIdx !== undefined) {
|
|
927
|
+
const row = allEdgeRows[dynZeroIdx];
|
|
928
|
+
if (row)
|
|
929
|
+
row[4] = 1;
|
|
930
|
+
dynZeroEdgeRows.delete(edgeKey);
|
|
805
931
|
}
|
|
806
932
|
}
|
|
933
|
+
continue;
|
|
807
934
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
935
|
+
const ptsIdx = ptsEdgeRows.get(edgeKey);
|
|
936
|
+
if (ptsIdx !== undefined) {
|
|
937
|
+
// A pts-resolved edge already exists for this caller→target pair with a
|
|
938
|
+
// penalised confidence. Upgrade it to the direct-call confidence in-place,
|
|
939
|
+
// then promote to seenCallEdges so no further processing is needed.
|
|
940
|
+
const ptsRow = allEdgeRows[ptsIdx];
|
|
941
|
+
if (ptsRow) {
|
|
942
|
+
ptsRow[3] = confidence;
|
|
943
|
+
ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
|
|
944
|
+
ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
|
|
945
|
+
}
|
|
946
|
+
ptsEdgeRows.delete(edgeKey);
|
|
947
|
+
seenCallEdges.add(edgeKey);
|
|
816
948
|
}
|
|
817
|
-
|
|
949
|
+
else {
|
|
950
|
+
seenCallEdges.add(edgeKey);
|
|
951
|
+
const newIdx = allEdgeRows.length;
|
|
952
|
+
allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic, 'ts-native', null]);
|
|
953
|
+
// Track dyn=0 edges so a later dyn=1+dynamicKind call for the same pair
|
|
954
|
+
// can upgrade them (e.g. bare decorator after call-expression decorator).
|
|
955
|
+
if (isDynamic === 0 && dynZeroEdgeRows) {
|
|
956
|
+
dynZeroEdgeRows.set(edgeKey, newIdx);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Phase 8.3 / 8.3c / bind: emit pts-resolved edges for unresolved no-receiver calls.
|
|
963
|
+
*
|
|
964
|
+
* Fires for three cases:
|
|
965
|
+
* (a) dynamic=true: alias calls emitted by extractCallbackReferenceCalls.
|
|
966
|
+
* Looks up `call.name` directly (alias entries are flat-keyed).
|
|
967
|
+
* (b) non-dynamic: parameter variable calls (fn() where fn is a param).
|
|
968
|
+
* Looks up the scoped key `callerName::call.name` to avoid spurious
|
|
969
|
+
* edges from same-named parameters across different functions.
|
|
970
|
+
* (c) non-dynamic: module-level alias bindings — `f = fn.bind(ctx)` or
|
|
971
|
+
* `const f = handler` — where pts('f') was seeded by fnRefBindings.
|
|
972
|
+
* Checked against fnRefBindingLhs so case (c) only fires for genuine
|
|
973
|
+
* bind/alias entries and never for self-seeded local definitions.
|
|
974
|
+
*
|
|
975
|
+
* Pts edges are added to ptsEdgeRows (not seenCallEdges) so that a later
|
|
976
|
+
* direct call to the same target can upgrade confidence rather than being
|
|
977
|
+
* silently dropped by the dedup guard.
|
|
978
|
+
*/
|
|
979
|
+
function emitPtsNoReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, fnRefBindingLhs, seenCallEdges, ptsEdgeRows, allEdgeRows) {
|
|
980
|
+
const scopedPtsKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
|
|
981
|
+
// Module-level calls (callerName === null) use the '<module>' sentinel emitted by
|
|
982
|
+
// extractSpreadForOfWalk for top-level for-of loops. Look it up as a fallback so
|
|
983
|
+
// that `for (const f of arr) { f(); }` at module scope resolves correctly.
|
|
984
|
+
const modulePtsKey = caller.callerName === null && ptsMap.has(`<module>::${call.name}`)
|
|
985
|
+
? `<module>::${call.name}`
|
|
986
|
+
: null;
|
|
987
|
+
const flatPtsKey = !call.dynamic && fnRefBindingLhs.has(call.name) && ptsMap.has(call.name) ? call.name : null;
|
|
988
|
+
if (!(call.dynamic ||
|
|
989
|
+
(scopedPtsKey != null && ptsMap.has(scopedPtsKey)) ||
|
|
990
|
+
modulePtsKey != null ||
|
|
991
|
+
flatPtsKey != null))
|
|
992
|
+
return;
|
|
993
|
+
const ptsLookupName = call.dynamic
|
|
994
|
+
? call.name
|
|
995
|
+
: scopedPtsKey != null && ptsMap.has(scopedPtsKey)
|
|
996
|
+
? scopedPtsKey
|
|
997
|
+
: modulePtsKey != null
|
|
998
|
+
? modulePtsKey
|
|
999
|
+
: // flatPtsKey != null is guaranteed: if neither call.dynamic nor scopedPtsKey
|
|
1000
|
+
// nor modulePtsKey matched, flatPtsKey must be non-null.
|
|
1001
|
+
flatPtsKey;
|
|
1002
|
+
for (const alias of resolveViaPointsTo(ptsLookupName, ptsMap)) {
|
|
1003
|
+
// Resolve the concrete alias target. Only `name` is needed here — receiver
|
|
1004
|
+
// and line are not relevant for alias resolution (we are looking up the
|
|
1005
|
+
// aliased function by name, not dispatching a method call).
|
|
1006
|
+
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
|
|
1007
|
+
const sortedAliasTargets = aliasTargets.length > 1
|
|
1008
|
+
? [...aliasTargets].sort((a, b) => computeConfidence(relPath, b.file, aliasFrom ?? null) -
|
|
1009
|
+
computeConfidence(relPath, a.file, aliasFrom ?? null))
|
|
1010
|
+
: aliasTargets;
|
|
1011
|
+
for (const t of sortedAliasTargets) {
|
|
818
1012
|
const edgeKey = `${caller.id}|${t.id}`;
|
|
819
|
-
if (t.id !== caller.id) {
|
|
820
|
-
const
|
|
821
|
-
if (
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
if (ptsIdx !== undefined) {
|
|
825
|
-
// A pts-resolved edge already exists for this caller→target pair with a
|
|
826
|
-
// penalised confidence. Upgrade it to the direct-call confidence in-place,
|
|
827
|
-
// then promote to seenCallEdges so no further processing is needed.
|
|
828
|
-
const ptsRow = allEdgeRows[ptsIdx];
|
|
829
|
-
if (ptsRow) {
|
|
830
|
-
ptsRow[3] = confidence;
|
|
831
|
-
ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
|
|
832
|
-
ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
|
|
833
|
-
}
|
|
834
|
-
ptsEdgeRows.delete(edgeKey);
|
|
835
|
-
seenCallEdges.add(edgeKey);
|
|
1013
|
+
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1014
|
+
const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
1015
|
+
if (conf > 0) {
|
|
1016
|
+
ptsEdgeRows.set(edgeKey, allEdgeRows.length);
|
|
1017
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to', null]);
|
|
836
1018
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Phase 8.3f: emit pts-resolved edges for unresolved receiver calls via
|
|
1025
|
+
* object-rest param bindings.
|
|
1026
|
+
*
|
|
1027
|
+
* Fires when `rest.prop()` is encountered and `rest` was seeded as
|
|
1028
|
+
* `pts["rest.prop"]` by the object-rest dispatch chain
|
|
1029
|
+
* (ObjectRestParamBinding + paramBinding + ObjectPropBinding).
|
|
1030
|
+
*/
|
|
1031
|
+
function emitPtsReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, seenCallEdges, ptsEdgeRows, allEdgeRows) {
|
|
1032
|
+
const receiverKey = `${call.receiver}.${call.name}`;
|
|
1033
|
+
if (!ptsMap.has(receiverKey))
|
|
1034
|
+
return;
|
|
1035
|
+
for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
|
|
1036
|
+
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
|
|
1037
|
+
const sortedAliasTargets = aliasTargets.length > 1
|
|
1038
|
+
? [...aliasTargets].sort((a, b) => computeConfidence(relPath, b.file, aliasFrom ?? null) -
|
|
1039
|
+
computeConfidence(relPath, a.file, aliasFrom ?? null))
|
|
1040
|
+
: aliasTargets;
|
|
1041
|
+
for (const t of sortedAliasTargets) {
|
|
1042
|
+
const edgeKey = `${caller.id}|${t.id}`;
|
|
1043
|
+
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1044
|
+
const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
1045
|
+
if (conf > 0) {
|
|
1046
|
+
ptsEdgeRows.set(edgeKey, allEdgeRows.length);
|
|
1047
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to', null]);
|
|
840
1048
|
}
|
|
841
1049
|
}
|
|
842
1050
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
// that `for (const f of arr) { f(); }` at module scope resolves correctly.
|
|
864
|
-
const modulePtsKey = caller.callerName === null && ptsMap?.has(`<module>::${call.name}`)
|
|
865
|
-
? `<module>::${call.name}`
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Phase 8.5: emit CHA + RTA dispatch edges for a single call site.
|
|
1055
|
+
*
|
|
1056
|
+
* For `this`/`self`/`super` calls: resolve through the class hierarchy.
|
|
1057
|
+
* For typed receiver calls: expand to all instantiated concrete implementations.
|
|
1058
|
+
*/
|
|
1059
|
+
function emitChaCallEdgesForCall(call, caller, relPath, typeMap, lookup, chaCtx, seenCallEdges, ptsEdgeRows, allEdgeRows) {
|
|
1060
|
+
let chaTargets = [];
|
|
1061
|
+
let isTypedReceiverDispatch = false;
|
|
1062
|
+
if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
|
|
1063
|
+
chaTargets = resolveThisDispatch(call.name, caller.callerName, call.receiver, chaCtx, lookup, relPath);
|
|
1064
|
+
}
|
|
1065
|
+
else if (!BUILTIN_RECEIVERS.has(call.receiver)) {
|
|
1066
|
+
const typeEntry = typeMap.get(call.receiver);
|
|
1067
|
+
const typeName = typeEntry
|
|
1068
|
+
? typeof typeEntry === 'string'
|
|
1069
|
+
? typeEntry
|
|
1070
|
+
: typeEntry.type
|
|
866
1071
|
: null;
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
// Resolve the concrete alias target. Only `name` is needed here — receiver
|
|
886
|
-
// and line are not relevant for alias resolution (we are looking up the
|
|
887
|
-
// aliased function by name, not dispatching a method call).
|
|
888
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
|
|
889
|
-
const sortedAliasTargets = aliasTargets.length > 1
|
|
890
|
-
? [...aliasTargets].sort((a, b) => computeConfidence(relPath, b.file, aliasFrom ?? null) -
|
|
891
|
-
computeConfidence(relPath, a.file, aliasFrom ?? null))
|
|
892
|
-
: aliasTargets;
|
|
893
|
-
for (const t of sortedAliasTargets) {
|
|
894
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
895
|
-
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
896
|
-
const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
897
|
-
if (conf > 0) {
|
|
898
|
-
ptsEdgeRows.set(edgeKey, allEdgeRows.length);
|
|
899
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
1072
|
+
if (typeName) {
|
|
1073
|
+
chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
|
|
1074
|
+
isTypedReceiverDispatch = true;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
for (const t of chaTargets) {
|
|
1078
|
+
const edgeKey = `${caller.id}|${t.id}`;
|
|
1079
|
+
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1080
|
+
// Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
|
|
1081
|
+
// — file proximity is not meaningful for virtual dispatch confidence.
|
|
1082
|
+
// this/super dispatch keeps computeConfidence-based proximity scoring to
|
|
1083
|
+
// match runPostNativeThisDispatch (native-orchestrator.ts).
|
|
1084
|
+
const conf = isTypedReceiverDispatch
|
|
1085
|
+
? CHA_TYPED_DISPATCH_CONFIDENCE
|
|
1086
|
+
: computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
|
|
1087
|
+
if (conf > 0) {
|
|
1088
|
+
seenCallEdges.add(edgeKey);
|
|
1089
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha', null]);
|
|
903
1090
|
}
|
|
904
1091
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Dynamic kinds that cannot be resolved statically — emit a sink edge to the
|
|
1096
|
+
* file node instead of silently dropping the call site. confidence=0.0 keeps
|
|
1097
|
+
* these below DEFAULT_MIN_CONFIDENCE so they never appear in normal query results.
|
|
1098
|
+
* Includes reflection so that Reflect.apply/getMethod/callable-ref calls whose
|
|
1099
|
+
* target is not found in the codebase still produce a visible sink edge.
|
|
1100
|
+
*/
|
|
1101
|
+
const FLAG_ONLY_KINDS = new Set([
|
|
1102
|
+
'eval',
|
|
1103
|
+
'computed-key',
|
|
1104
|
+
'reflection',
|
|
1105
|
+
'unresolved-dynamic',
|
|
1106
|
+
]);
|
|
1107
|
+
/**
|
|
1108
|
+
* Build call edges for all calls in a single file (WASM/JS engine path).
|
|
1109
|
+
*
|
|
1110
|
+
* Iterates over `symbols.calls` and dispatches each call through the full
|
|
1111
|
+
* JS resolution cascade:
|
|
1112
|
+
* 1. `resolveFallbackTargets` — primary + class-fallback + defineProperty fallback
|
|
1113
|
+
* 2. `emitDirectCallEdgesForCall` — emit direct-call edges (upgrading any pts pair)
|
|
1114
|
+
* 3. `emitPtsNoReceiverEdges` — Phase 8.3/8.3c pts fallback for no-receiver calls
|
|
1115
|
+
* 4. `emitPtsReceiverEdges` — Phase 8.3f pts fallback for rest-param receiver calls
|
|
1116
|
+
* 5. Inline `resolveReceiverEdge` — emit `receiver` edge for external receivers
|
|
1117
|
+
* 6. `emitChaCallEdgesForCall` — Phase 8.5 CHA + RTA dispatch expansion
|
|
1118
|
+
* 7. Sink edge for flag-only dynamic kinds (eval, computed-key, reflection, unresolved-dynamic)
|
|
1119
|
+
*/
|
|
1120
|
+
function buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCallEdges, lookup, allEdgeRows, typeMap, ptsMap, chaCtx, importArtifactNames) {
|
|
1121
|
+
// Tracks edges that were inserted by the pts fallback (edgeKey → allEdgeRows index).
|
|
1122
|
+
// Kept separate from seenCallEdges so that a subsequent direct-call edge for the same
|
|
1123
|
+
// caller→target pair can upgrade the confidence in-place rather than being silently
|
|
1124
|
+
// dropped by the dedup guard. Once upgraded, the key moves to seenCallEdges and is
|
|
1125
|
+
// no longer tracked here.
|
|
1126
|
+
const ptsEdgeRows = new Map();
|
|
1127
|
+
// Tracks direct-call edges emitted with dyn=0 (edgeKey → allEdgeRows index).
|
|
1128
|
+
// When a later call to the same target has dyn=1 (e.g. a bare decorator `@Log`
|
|
1129
|
+
// processed after the call-expression `@Log()` in the query path), the existing
|
|
1130
|
+
// dyn=0 row is upgraded in-place so the more specific dynamic classification wins.
|
|
1131
|
+
const dynZeroEdgeRows = new Map();
|
|
1132
|
+
// Pre-compute the set of names that appear as lhs in fnRefBindings so that
|
|
1133
|
+
// case (c) of the pts gate below only fires for names that are genuine
|
|
1134
|
+
// bind/alias entries, not for every locally-defined function or import that
|
|
1135
|
+
// buildPointsToMap seeds with a self-pointing entry.
|
|
1136
|
+
const fnRefBindingLhs = new Set(symbols.fnRefBindings?.map((b) => b.lhs) ?? []);
|
|
1137
|
+
for (const call of symbols.calls) {
|
|
1138
|
+
if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver))
|
|
1139
|
+
continue;
|
|
1140
|
+
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
1141
|
+
const isDynamic = call.dynamic ? 1 : 0;
|
|
1142
|
+
// Step 1: Resolve targets with all JS-path fallbacks.
|
|
1143
|
+
const { targets, importedFrom } = resolveFallbackTargets(call, caller, relPath, importedNames, lookup, typeMap, symbols.definePropertyReceivers);
|
|
1144
|
+
// Step 2: Emit direct-call edges (upgrades any pending pts edge in-place).
|
|
1145
|
+
emitDirectCallEdgesForCall(caller, targets, importedFrom, isDynamic, !!call.dynamicKind, relPath, seenCallEdges, ptsEdgeRows, allEdgeRows, dynZeroEdgeRows);
|
|
1146
|
+
// Step 3: Phase 8.3/8.3c pts fallback for unresolved no-receiver calls.
|
|
1147
|
+
if (targets.length === 0 && !call.receiver && ptsMap) {
|
|
1148
|
+
emitPtsNoReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, fnRefBindingLhs, seenCallEdges, ptsEdgeRows, allEdgeRows);
|
|
1149
|
+
}
|
|
1150
|
+
// Step 4: Phase 8.3f pts fallback for unresolved receiver calls (rest params).
|
|
908
1151
|
if (targets.length === 0 &&
|
|
909
1152
|
call.receiver &&
|
|
910
1153
|
!BUILTIN_RECEIVERS.has(call.receiver) &&
|
|
@@ -912,74 +1155,40 @@ function buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCa
|
|
|
912
1155
|
call.receiver !== 'self' &&
|
|
913
1156
|
call.receiver !== 'super' &&
|
|
914
1157
|
ptsMap) {
|
|
915
|
-
|
|
916
|
-
if (ptsMap.has(receiverKey)) {
|
|
917
|
-
for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
|
|
918
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
|
|
919
|
-
const sortedAliasTargets = aliasTargets.length > 1
|
|
920
|
-
? [...aliasTargets].sort((a, b) => computeConfidence(relPath, b.file, aliasFrom ?? null) -
|
|
921
|
-
computeConfidence(relPath, a.file, aliasFrom ?? null))
|
|
922
|
-
: aliasTargets;
|
|
923
|
-
for (const t of sortedAliasTargets) {
|
|
924
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
925
|
-
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
926
|
-
const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
927
|
-
if (conf > 0) {
|
|
928
|
-
ptsEdgeRows.set(edgeKey, allEdgeRows.length);
|
|
929
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
}
|
|
1158
|
+
emitPtsReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, seenCallEdges, ptsEdgeRows, allEdgeRows);
|
|
935
1159
|
}
|
|
1160
|
+
// Step 5: Emit receiver edge for external (non-this/self/super) receivers.
|
|
936
1161
|
if (call.receiver &&
|
|
937
1162
|
!BUILTIN_RECEIVERS.has(call.receiver) &&
|
|
938
1163
|
call.receiver !== 'this' &&
|
|
939
1164
|
call.receiver !== 'self' &&
|
|
940
1165
|
call.receiver !== 'super') {
|
|
941
|
-
const recv = resolveReceiverEdge(lookup, { name: call.name, receiver: call.receiver }, caller, relPath, typeMap, seenCallEdges, importedNames);
|
|
1166
|
+
const recv = resolveReceiverEdge(lookup, { name: call.name, receiver: call.receiver }, caller, relPath, typeMap, seenCallEdges, importArtifactNames ?? importedNames);
|
|
942
1167
|
if (recv) {
|
|
943
|
-
allEdgeRows.push([
|
|
1168
|
+
allEdgeRows.push([
|
|
1169
|
+
recv.callerId,
|
|
1170
|
+
recv.receiverId,
|
|
1171
|
+
'receiver',
|
|
1172
|
+
recv.confidence,
|
|
1173
|
+
0,
|
|
1174
|
+
null,
|
|
1175
|
+
null,
|
|
1176
|
+
]);
|
|
944
1177
|
}
|
|
945
1178
|
}
|
|
946
|
-
// Phase 8.5
|
|
947
|
-
// For `this`/`self`/`super` calls: resolve through the class hierarchy instead
|
|
948
|
-
// of relying solely on global name matching.
|
|
949
|
-
// For typed receiver calls: expand to all instantiated concrete implementations.
|
|
1179
|
+
// Step 6: Phase 8.5 CHA + RTA dispatch expansion.
|
|
950
1180
|
if (chaCtx && call.receiver) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
: null;
|
|
963
|
-
if (typeName) {
|
|
964
|
-
chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
|
|
965
|
-
isTypedReceiverDispatch = true;
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
for (const t of chaTargets) {
|
|
969
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
970
|
-
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
971
|
-
// Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
|
|
972
|
-
// — file proximity is not meaningful for virtual dispatch confidence.
|
|
973
|
-
// this/super dispatch keeps computeConfidence-based proximity scoring to
|
|
974
|
-
// match runPostNativeThisDispatch (native-orchestrator.ts).
|
|
975
|
-
const conf = isTypedReceiverDispatch
|
|
976
|
-
? CHA_TYPED_DISPATCH_CONFIDENCE
|
|
977
|
-
: computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
|
|
978
|
-
if (conf > 0) {
|
|
979
|
-
seenCallEdges.add(edgeKey);
|
|
980
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha']);
|
|
981
|
-
}
|
|
982
|
-
}
|
|
1181
|
+
emitChaCallEdgesForCall(call, caller, relPath, typeMap, lookup, chaCtx, seenCallEdges, ptsEdgeRows, allEdgeRows);
|
|
1182
|
+
}
|
|
1183
|
+
// Step 7: Flag-only dynamic kinds with no resolved target → sink edge to the
|
|
1184
|
+
// file node. confidence=0.0 keeps it below DEFAULT_MIN_CONFIDENCE so it never
|
|
1185
|
+
// appears in normal query results, but is queryable via `codegraph roles --dynamic`.
|
|
1186
|
+
if (targets.length === 0 && call.dynamicKind && FLAG_ONLY_KINDS.has(call.dynamicKind)) {
|
|
1187
|
+
// Key per (caller, file, kind) so each kind gets at most one sink edge per caller.
|
|
1188
|
+
const sinkKey = `${caller.id}:${fileNodeRow.id}:${call.dynamicKind}`;
|
|
1189
|
+
if (!seenCallEdges.has(sinkKey)) {
|
|
1190
|
+
seenCallEdges.add(sinkKey);
|
|
1191
|
+
allEdgeRows.push([caller.id, fileNodeRow.id, 'calls', 0.0, 1, null, call.dynamicKind]);
|
|
983
1192
|
}
|
|
984
1193
|
}
|
|
985
1194
|
}
|
|
@@ -995,7 +1204,7 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
|
|
|
995
1204
|
const targetRows = (ctx.nodesByName.get(cls.extends) || []).filter((n) => EXTENDS_TARGET_KINDS.has(n.kind));
|
|
996
1205
|
if (sourceRow) {
|
|
997
1206
|
for (const t of targetRows) {
|
|
998
|
-
allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null]);
|
|
1207
|
+
allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null, null]);
|
|
999
1208
|
}
|
|
1000
1209
|
}
|
|
1001
1210
|
}
|
|
@@ -1004,7 +1213,7 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
|
|
|
1004
1213
|
const targetRows = (ctx.nodesByName.get(cls.implements) || []).filter((n) => IMPLEMENTS_TARGET_KINDS.has(n.kind));
|
|
1005
1214
|
if (sourceRow) {
|
|
1006
1215
|
for (const t of targetRows) {
|
|
1007
|
-
allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null]);
|
|
1216
|
+
allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null, null]);
|
|
1008
1217
|
}
|
|
1009
1218
|
}
|
|
1010
1219
|
}
|
|
@@ -1013,7 +1222,8 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
|
|
|
1013
1222
|
// ── Native bulk-insert technique back-fill ──────────────────────────────
|
|
1014
1223
|
/**
|
|
1015
1224
|
* After native bulkInsertEdges (which does not write the technique column),
|
|
1016
|
-
* apply technique values from the in-memory row array back to the DB
|
|
1225
|
+
* apply technique values from the in-memory row array back to the DB, and lift
|
|
1226
|
+
* any resolved ts-native edge below TS_NATIVE_CONFIDENCE_FLOOR to that floor.
|
|
1017
1227
|
*
|
|
1018
1228
|
* Rows with an explicit technique get a targeted UPDATE by (source_id, target_id).
|
|
1019
1229
|
* The catch-all 'ts-native' tag is scoped to only the source_ids present in this
|
|
@@ -1030,6 +1240,8 @@ function applyEdgeTechniquesAfterNativeInsert(db, rows) {
|
|
|
1030
1240
|
const sourceIds = [...new Set(callRows.map((r) => r[0]))];
|
|
1031
1241
|
// Chunk to stay within SQLite's SQLITE_LIMIT_VARIABLE_NUMBER (999 on older builds).
|
|
1032
1242
|
const CHUNK_SIZE = 500;
|
|
1243
|
+
// Rows that carry an explicit dynamic_kind (sink edges for flagged dynamic calls).
|
|
1244
|
+
const dynamicKindRows = callRows.filter((r) => r[6] != null);
|
|
1033
1245
|
const tx = db.transaction(() => {
|
|
1034
1246
|
if (taggedRows.length > 0) {
|
|
1035
1247
|
const stmt = db.prepare("UPDATE edges SET technique = ? WHERE kind = 'calls' AND source_id = ? AND target_id = ? AND technique IS NULL");
|
|
@@ -1040,6 +1252,24 @@ function applyEdgeTechniquesAfterNativeInsert(db, rows) {
|
|
|
1040
1252
|
const chunk = sourceIds.slice(i, i + CHUNK_SIZE);
|
|
1041
1253
|
const placeholders = chunk.map(() => '?').join(',');
|
|
1042
1254
|
db.prepare(`UPDATE edges SET technique = 'ts-native' WHERE kind = 'calls' AND technique IS NULL AND source_id IN (${placeholders})`).run(...chunk);
|
|
1255
|
+
// Lift resolved ts-native edges below the confidence floor for this chunk.
|
|
1256
|
+
db.prepare(`UPDATE edges SET confidence = ?
|
|
1257
|
+
WHERE kind = 'calls' AND technique = 'ts-native'
|
|
1258
|
+
AND confidence > 0 AND confidence < ?
|
|
1259
|
+
AND source_id IN (${placeholders})`).run(TS_NATIVE_CONFIDENCE_FLOOR, TS_NATIVE_CONFIDENCE_FLOOR, ...chunk);
|
|
1260
|
+
}
|
|
1261
|
+
// Back-fill dynamic_kind for flagged sink edges emitted by the native engine.
|
|
1262
|
+
// Native bulkInsertEdges uses INSERT OR IGNORE and does not write dynamic_kind, so
|
|
1263
|
+
// this UPDATE is the only way to set it for natively-inserted sink edges.
|
|
1264
|
+
//
|
|
1265
|
+
// Scope to confidence=0.0 AND dynamic=1 so we only touch sink edges (never normal
|
|
1266
|
+
// call edges that happen to share the same (source_id, target_id) pair).
|
|
1267
|
+
// Include dynamic_kind in the WHERE so two sink edges from the same caller to the
|
|
1268
|
+
// same file with different kinds don't clobber each other across incremental runs.
|
|
1269
|
+
if (dynamicKindRows.length > 0) {
|
|
1270
|
+
const stmt = db.prepare("UPDATE edges SET dynamic_kind = ? WHERE kind = 'calls' AND source_id = ? AND target_id = ? AND confidence = 0.0 AND dynamic = 1 AND (dynamic_kind IS NULL OR dynamic_kind = ?)");
|
|
1271
|
+
for (const r of dynamicKindRows)
|
|
1272
|
+
stmt.run(r[6], r[0], r[1], r[6]);
|
|
1043
1273
|
}
|
|
1044
1274
|
});
|
|
1045
1275
|
tx();
|
|
@@ -1068,6 +1298,7 @@ function reconnectReverseDepEdges(ctx) {
|
|
|
1068
1298
|
saved.confidence,
|
|
1069
1299
|
saved.dynamic,
|
|
1070
1300
|
saved.technique,
|
|
1301
|
+
saved.dynamicKind ?? null,
|
|
1071
1302
|
]);
|
|
1072
1303
|
}
|
|
1073
1304
|
else {
|
|
@@ -1255,6 +1486,21 @@ export async function buildEdges(ctx) {
|
|
|
1255
1486
|
else {
|
|
1256
1487
|
buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx);
|
|
1257
1488
|
}
|
|
1489
|
+
// Apply ts-native confidence floor to allEdgeRows in-memory. The proximity
|
|
1490
|
+
// heuristic returns 0.3 for cross-module calls with no import-path evidence,
|
|
1491
|
+
// but both WASM and native engines perform actual name-based symbol lookup,
|
|
1492
|
+
// which is stronger evidence than pure proximity. Clamping to
|
|
1493
|
+
// TS_NATIVE_CONFIDENCE_FLOOR (0.5) avoids unfairly dragging down the
|
|
1494
|
+
// call-confidence metric. Sink edges (confidence = 0.0) are excluded so
|
|
1495
|
+
// they remain below DEFAULT_MIN_CONFIDENCE.
|
|
1496
|
+
for (const r of allEdgeRows) {
|
|
1497
|
+
if (r[2] === 'calls' &&
|
|
1498
|
+
r[5] === 'ts-native' &&
|
|
1499
|
+
r[3] > 0 &&
|
|
1500
|
+
r[3] < TS_NATIVE_CONFIDENCE_FLOOR) {
|
|
1501
|
+
r[3] = TS_NATIVE_CONFIDENCE_FLOOR;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1258
1504
|
// When using native edge insert, skip JS insert here — do it after tx commits.
|
|
1259
1505
|
// Otherwise insert edges within this transaction for atomicity.
|
|
1260
1506
|
const useNativeEdgeInsert = ctx.engineName === 'native' && !!ctx.nativeDb?.bulkInsertEdges;
|