@optave/codegraph 3.12.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 +83 -46
- 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/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +2 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/batch.d.ts.map +1 -1
- package/dist/cli/commands/batch.js +1 -0
- package/dist/cli/commands/batch.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +6 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +275 -0
- package/dist/cli/commands/config.js.map +1 -0
- 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/cli/commands/triage.js +1 -1
- package/dist/cli/commands/triage.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +10 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/shared/options.d.ts +2 -1
- package/dist/cli/shared/options.d.ts.map +1 -1
- package/dist/cli/shared/options.js +11 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/types.d.ts.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 +69 -1
- 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 +29 -13
- package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
- package/dist/domain/graph/builder/call-resolver.js +125 -205
- package/dist/domain/graph/builder/call-resolver.js.map +1 -1
- package/dist/domain/graph/builder/cha.d.ts +9 -1
- package/dist/domain/graph/builder/cha.d.ts.map +1 -1
- package/dist/domain/graph/builder/cha.js +17 -2
- package/dist/domain/graph/builder/cha.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 +24 -1
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +174 -65
- 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 +46 -5
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts +0 -2
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +554 -538
- 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 +3 -2
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +4 -0
- package/dist/domain/graph/builder/stages/finalize.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 +952 -343
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.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 +16 -5
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +58 -17
- 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 +13 -2
- 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 +26 -5
- package/dist/domain/wasm-worker-pool.js.map +1 -1
- package/dist/domain/wasm-worker-protocol.d.ts +8 -0
- package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
- package/dist/extractors/cpp.d.ts.map +1 -1
- package/dist/extractors/cpp.js +42 -1
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/cuda.d.ts.map +1 -1
- package/dist/extractors/cuda.js +42 -1
- package/dist/extractors/cuda.js.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 +15 -2
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +45 -1
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +85 -8
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +686 -169
- 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-query.d.ts +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +6 -6
- package/dist/features/structure-query.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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/config.d.ts +87 -4
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +424 -22
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +27 -7
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +79 -5
- 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/structure.d.ts +1 -1
- package/dist/presentation/structure.d.ts.map +1 -1
- package/dist/presentation/structure.js +2 -2
- package/dist/presentation/structure.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 +113 -1
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +7 -8
- 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/audit.ts +2 -1
- package/src/cli/commands/batch.ts +1 -0
- package/src/cli/commands/build.ts +6 -1
- package/src/cli/commands/config.ts +353 -0
- package/src/cli/commands/roles.ts +6 -1
- package/src/cli/commands/triage.ts +1 -1
- package/src/cli/index.ts +10 -0
- package/src/cli/shared/options.ts +11 -1
- package/src/cli/types.ts +2 -0
- 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 +69 -1
- 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 +156 -218
- package/src/domain/graph/builder/cha.ts +18 -1
- package/src/domain/graph/builder/context.ts +1 -0
- package/src/domain/graph/builder/helpers.ts +205 -67
- package/src/domain/graph/builder/incremental.ts +249 -119
- package/src/domain/graph/builder/pipeline.ts +59 -6
- package/src/domain/graph/builder/stages/build-edges.ts +783 -652
- package/src/domain/graph/builder/stages/collect-files.ts +12 -6
- package/src/domain/graph/builder/stages/detect-changes.ts +4 -2
- package/src/domain/graph/builder/stages/finalize.ts +4 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +1214 -398
- package/src/domain/graph/builder/stages/resolve-imports.ts +1 -1
- 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 +57 -16
- package/src/domain/queries.ts +1 -1
- package/src/domain/wasm-worker-entry.ts +13 -2
- package/src/domain/wasm-worker-pool.ts +29 -4
- package/src/domain/wasm-worker-protocol.ts +5 -0
- package/src/extractors/cpp.ts +44 -1
- package/src/extractors/cuda.ts +44 -1
- package/src/extractors/dart.ts +48 -3
- package/src/extractors/groovy.ts +62 -2
- package/src/extractors/helpers.ts +48 -2
- package/src/extractors/java.ts +88 -8
- package/src/extractors/javascript.ts +693 -167
- 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-query.ts +7 -7
- 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/index.ts +5 -1
- package/src/infrastructure/config.ts +511 -23
- package/src/infrastructure/registry.ts +117 -12
- 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/structure.ts +3 -3
- 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 +127 -1
|
@@ -11,15 +11,14 @@ 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';
|
|
17
18
|
import { findCaller, isModuleScopedLanguage, resolveCallTargets, resolveReceiverEdge, } from '../call-resolver.js';
|
|
18
19
|
import { buildChaContext, resolveChaTargets, resolveThisDispatch } from '../cha.js';
|
|
19
|
-
import { BUILTIN_RECEIVERS, batchInsertEdges, runChaPostPass } from '../helpers.js';
|
|
20
|
+
import { BUILTIN_RECEIVERS, batchInsertEdges, CHA_DISPATCH_PENALTY, CHA_TYPED_DISPATCH_CONFIDENCE, runChaPostPass, } from '../helpers.js';
|
|
20
21
|
import { getResolved, isBarrelFile, resolveBarrelExportCached } from './resolve-imports.js';
|
|
21
|
-
/** Phase 8.5: confidence penalty applied to CHA-dispatch edges. */
|
|
22
|
-
export const CHA_DISPATCH_PENALTY = 0.1;
|
|
23
22
|
// ── Node lookup setup ───────────────────────────────────────────────────
|
|
24
23
|
function makeGetNodeIdStmt(db) {
|
|
25
24
|
return {
|
|
@@ -72,7 +71,7 @@ function emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows
|
|
|
72
71
|
}
|
|
73
72
|
const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
|
|
74
73
|
if (candidates && candidates.length > 0) {
|
|
75
|
-
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]);
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
77
|
}
|
|
@@ -86,7 +85,7 @@ function emitEdgesForImport(ctx, imp, fileNodeId, relPath, getNodeIdStmt, allEdg
|
|
|
86
85
|
if (!targetRow)
|
|
87
86
|
return;
|
|
88
87
|
const edgeKind = importEdgeKind(imp);
|
|
89
|
-
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null]);
|
|
88
|
+
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null, null]);
|
|
90
89
|
if (imp.typeOnly) {
|
|
91
90
|
emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows);
|
|
92
91
|
}
|
|
@@ -124,7 +123,7 @@ function buildBarrelEdges(ctx, imp, resolvedPath, fileNodeId, edgeKind, getNodeI
|
|
|
124
123
|
: edgeKind === 'dynamic-imports'
|
|
125
124
|
? 'dynamic-imports'
|
|
126
125
|
: 'imports';
|
|
127
|
-
edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null]);
|
|
126
|
+
edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null, null]);
|
|
128
127
|
}
|
|
129
128
|
}
|
|
130
129
|
}
|
|
@@ -239,7 +238,7 @@ function buildImportEdgesNative(ctx, getNodeIdStmt, allEdgeRows, native) {
|
|
|
239
238
|
const symbolNodes = collectSymbolNodes(ctx);
|
|
240
239
|
const nativeEdges = native.buildImportEdges(files, resolvedImports, fileReexports, registry.ids, barrelFiles, ctx.rootDir, symbolNodes);
|
|
241
240
|
for (const e of nativeEdges) {
|
|
242
|
-
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]);
|
|
243
242
|
}
|
|
244
243
|
}
|
|
245
244
|
// ── Phase 8.2: Cross-file return-type propagation ───────────────────────
|
|
@@ -337,17 +336,35 @@ function buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodes, native)
|
|
|
337
336
|
nativeFiles.push({
|
|
338
337
|
file: relPath,
|
|
339
338
|
fileNodeId: fileNodeRow.id,
|
|
340
|
-
definitions: symbols.definitions.map((d) =>
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
339
|
+
definitions: symbols.definitions.map((d) => {
|
|
340
|
+
const params = d.children?.filter((c) => c.kind === 'parameter').map((c) => c.name);
|
|
341
|
+
return {
|
|
342
|
+
name: d.name,
|
|
343
|
+
kind: d.kind,
|
|
344
|
+
line: d.line,
|
|
345
|
+
endLine: d.endLine ?? null,
|
|
346
|
+
params: params?.length ? params : undefined,
|
|
347
|
+
};
|
|
348
|
+
}),
|
|
346
349
|
calls: symbols.calls,
|
|
347
350
|
importedNames,
|
|
348
351
|
classes: symbols.classes,
|
|
349
352
|
typeMap,
|
|
350
353
|
fnRefBindings: symbols.fnRefBindings?.length ? symbols.fnRefBindings : undefined,
|
|
354
|
+
paramBindings: symbols.paramBindings?.length ? symbols.paramBindings : undefined,
|
|
355
|
+
thisCallBindings: symbols.thisCallBindings?.length ? symbols.thisCallBindings : undefined,
|
|
356
|
+
arrayElemBindings: symbols.arrayElemBindings?.length ? symbols.arrayElemBindings : undefined,
|
|
357
|
+
spreadArgBindings: symbols.spreadArgBindings?.length ? symbols.spreadArgBindings : undefined,
|
|
358
|
+
forOfBindings: symbols.forOfBindings?.length ? symbols.forOfBindings : undefined,
|
|
359
|
+
arrayCallbackBindings: symbols.arrayCallbackBindings?.length
|
|
360
|
+
? symbols.arrayCallbackBindings
|
|
361
|
+
: undefined,
|
|
362
|
+
objectRestParamBindings: symbols.objectRestParamBindings?.length
|
|
363
|
+
? symbols.objectRestParamBindings
|
|
364
|
+
: undefined,
|
|
365
|
+
objectPropBindings: symbols.objectPropBindings?.length
|
|
366
|
+
? symbols.objectPropBindings
|
|
367
|
+
: undefined,
|
|
351
368
|
});
|
|
352
369
|
}
|
|
353
370
|
const nativeEdges = native.buildCallEdges(nativeFiles, allNodes, [
|
|
@@ -361,285 +378,10 @@ function buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodes, native)
|
|
|
361
378
|
e.confidence,
|
|
362
379
|
e.dynamic,
|
|
363
380
|
e.kind === 'calls' ? 'ts-native' : null,
|
|
381
|
+
e.dynamic_kind ?? null,
|
|
364
382
|
]);
|
|
365
383
|
}
|
|
366
384
|
}
|
|
367
|
-
/**
|
|
368
|
-
* Phase 8.3c pts post-pass for the native call-edge path.
|
|
369
|
-
*
|
|
370
|
-
* The native Rust engine builds call edges without knowledge of paramBindings,
|
|
371
|
-
* so `fn()` calls inside higher-order functions are not resolved to their
|
|
372
|
-
* concrete targets. This JS post-pass runs after the native edge pass and adds
|
|
373
|
-
* only the parameter-flow pts edges that the native engine missed.
|
|
374
|
-
*
|
|
375
|
-
* To avoid duplicating edges already emitted by the native engine, the current
|
|
376
|
-
* allEdgeRows snapshot is used to seed a seenByPair set before processing each
|
|
377
|
-
* file.
|
|
378
|
-
*/
|
|
379
|
-
function buildParamFlowPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup) {
|
|
380
|
-
// Only process files that actually have paramBindings (avoid useless work).
|
|
381
|
-
const filesWithParams = [...ctx.fileSymbols].filter(([, symbols]) => symbols.paramBindings && symbols.paramBindings.length > 0);
|
|
382
|
-
if (filesWithParams.length === 0)
|
|
383
|
-
return;
|
|
384
|
-
// Seed seenByPair from the existing rows so we don't duplicate native edges.
|
|
385
|
-
// This is O(|allEdgeRows|) once per post-pass, which is acceptable.
|
|
386
|
-
const seenByPair = new Set();
|
|
387
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
388
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
389
|
-
}
|
|
390
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
391
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
392
|
-
for (const [relPath, symbols] of filesWithParams) {
|
|
393
|
-
if (barrelOnlyFiles.has(relPath))
|
|
394
|
-
continue;
|
|
395
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
396
|
-
if (!fileNodeRow)
|
|
397
|
-
continue;
|
|
398
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
399
|
-
const typeMap = symbols.typeMap || new Map();
|
|
400
|
-
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
401
|
-
if (!ptsMap)
|
|
402
|
-
continue;
|
|
403
|
-
for (const call of symbols.calls) {
|
|
404
|
-
if (call.receiver || call.dynamic)
|
|
405
|
-
continue; // pts post-pass handles only param-flow (non-dynamic)
|
|
406
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
407
|
-
const scopedKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
|
|
408
|
-
if (!scopedKey || !ptsMap.has(scopedKey))
|
|
409
|
-
continue;
|
|
410
|
-
// Only resolve calls that had no direct targets (same guard as buildFileCallEdges).
|
|
411
|
-
const { targets } = resolveCallTargets(lookup, call, relPath, importedNames, typeMap);
|
|
412
|
-
if (targets.length > 0)
|
|
413
|
-
continue;
|
|
414
|
-
for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
|
|
415
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
|
|
416
|
-
for (const t of aliasTargets) {
|
|
417
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
418
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
419
|
-
const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
420
|
-
if (conf > 0) {
|
|
421
|
-
seenByPair.add(edgeKey);
|
|
422
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* bind/alias pts post-pass for the native call-edge path.
|
|
432
|
-
*
|
|
433
|
-
* The native Rust engine has no knowledge of JS-layer fnRefBindings (e.g.
|
|
434
|
-
* `const f = fn.bind(ctx)`), so calls to bind-created aliases are not resolved
|
|
435
|
-
* to their original function on the native path. This JS post-pass runs after
|
|
436
|
-
* the native edge pass and adds only the fnRefBindings-seeded pts edges that the
|
|
437
|
-
* native engine missed.
|
|
438
|
-
*
|
|
439
|
-
* Uses the same seenByPair dedup guard as buildParamFlowPtsPostPass to avoid
|
|
440
|
-
* duplicating edges already emitted by the native engine.
|
|
441
|
-
*/
|
|
442
|
-
function buildFnRefBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup) {
|
|
443
|
-
// Only process files that actually have fnRefBindings.
|
|
444
|
-
const filesWithBindings = [...ctx.fileSymbols].filter(([, symbols]) => symbols.fnRefBindings && symbols.fnRefBindings.length > 0);
|
|
445
|
-
if (filesWithBindings.length === 0)
|
|
446
|
-
return;
|
|
447
|
-
// Seed seenByPair from the existing rows so we don't duplicate native edges.
|
|
448
|
-
const seenByPair = new Set();
|
|
449
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
450
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
451
|
-
}
|
|
452
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
453
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
454
|
-
for (const [relPath, symbols] of filesWithBindings) {
|
|
455
|
-
if (barrelOnlyFiles.has(relPath))
|
|
456
|
-
continue;
|
|
457
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
458
|
-
if (!fileNodeRow)
|
|
459
|
-
continue;
|
|
460
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
461
|
-
const typeMap = symbols.typeMap || new Map();
|
|
462
|
-
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
463
|
-
if (!ptsMap)
|
|
464
|
-
continue;
|
|
465
|
-
// Only resolve calls whose name is an lhs in fnRefBindings — the same
|
|
466
|
-
// narrowed guard used in buildFileCallEdges case (c).
|
|
467
|
-
const fnRefBindingLhs = new Set(symbols.fnRefBindings.map((b) => b.lhs));
|
|
468
|
-
for (const call of symbols.calls) {
|
|
469
|
-
if (call.receiver || call.dynamic)
|
|
470
|
-
continue; // bind aliases are flat-keyed, never dynamic
|
|
471
|
-
if (!fnRefBindingLhs.has(call.name))
|
|
472
|
-
continue;
|
|
473
|
-
if (!ptsMap.has(call.name))
|
|
474
|
-
continue;
|
|
475
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
476
|
-
// Only resolve calls that had no direct targets (same guard as buildFileCallEdges).
|
|
477
|
-
const { targets } = resolveCallTargets(lookup, call, relPath, importedNames, typeMap);
|
|
478
|
-
if (targets.length > 0)
|
|
479
|
-
continue;
|
|
480
|
-
for (const alias of resolveViaPointsTo(call.name, ptsMap)) {
|
|
481
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
|
|
482
|
-
for (const t of aliasTargets) {
|
|
483
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
484
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
485
|
-
const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
486
|
-
if (conf > 0) {
|
|
487
|
-
seenByPair.add(edgeKey);
|
|
488
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* this-rebinding post-pass for the native call-edge path.
|
|
498
|
-
*
|
|
499
|
-
* When `fn.call(namedCtx, ...)` or `fn.apply(namedCtx, ...)` is extracted by the
|
|
500
|
-
* WASM layer, `thisCallBindings` records `{ callee: 'fn', thisArg: 'namedCtx' }`.
|
|
501
|
-
* The native Rust engine has no knowledge of these bindings, so `this()` calls
|
|
502
|
-
* inside `fn` remain unresolved. This JS post-pass adds the missing edges by
|
|
503
|
-
* resolving `this()` calls inside each `fn` that has a thisCallBinding.
|
|
504
|
-
*/
|
|
505
|
-
function buildThisCallBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup) {
|
|
506
|
-
const filesWithBindings = [...ctx.fileSymbols].filter(([, symbols]) => symbols.thisCallBindings && symbols.thisCallBindings.length > 0);
|
|
507
|
-
if (filesWithBindings.length === 0)
|
|
508
|
-
return;
|
|
509
|
-
const seenByPair = new Set();
|
|
510
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
511
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
512
|
-
}
|
|
513
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
514
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
515
|
-
for (const [relPath, symbols] of filesWithBindings) {
|
|
516
|
-
if (barrelOnlyFiles.has(relPath))
|
|
517
|
-
continue;
|
|
518
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
519
|
-
if (!fileNodeRow)
|
|
520
|
-
continue;
|
|
521
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
522
|
-
const typeMap = symbols.typeMap || new Map();
|
|
523
|
-
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
524
|
-
if (!ptsMap)
|
|
525
|
-
continue;
|
|
526
|
-
// Only process calls named 'this' (callee-not-receiver usage)
|
|
527
|
-
for (const call of symbols.calls) {
|
|
528
|
-
if (call.name !== 'this' || call.receiver)
|
|
529
|
-
continue;
|
|
530
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
531
|
-
if (caller.callerName == null)
|
|
532
|
-
continue;
|
|
533
|
-
const scopedKey = `${caller.callerName}::this`;
|
|
534
|
-
if (!ptsMap.has(scopedKey))
|
|
535
|
-
continue;
|
|
536
|
-
for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
|
|
537
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
|
|
538
|
-
for (const t of aliasTargets) {
|
|
539
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
540
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
541
|
-
const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
542
|
-
if (conf > 0) {
|
|
543
|
-
seenByPair.add(edgeKey);
|
|
544
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* Phase 8.3f post-pass for the native call-edge path.
|
|
554
|
-
*
|
|
555
|
-
* The native Rust engine builds call edges without knowledge of
|
|
556
|
-
* objectRestParamBindings, so `rest.method()` calls inside functions with
|
|
557
|
-
* object-destructuring rest parameters are not resolved via the typeMap chain.
|
|
558
|
-
* The Rust engine already resolves same-file and directly-imported callees
|
|
559
|
-
* (via steps 1–2 of its resolution logic), so this post-pass only adds edges
|
|
560
|
-
* that require the typeMap-chain path:
|
|
561
|
-
* typeMap[restName] → argName → typeMap[argName.method] → target
|
|
562
|
-
*
|
|
563
|
-
* Mirrors the seeding in buildCallEdgesJS (Phase 8.3f) to ensure both engine
|
|
564
|
-
* paths produce identical results for receiver-typed rest-param calls.
|
|
565
|
-
*/
|
|
566
|
-
function buildObjectRestParamPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup) {
|
|
567
|
-
const filesWithRestBindings = [...ctx.fileSymbols].filter(([, symbols]) => symbols.objectRestParamBindings &&
|
|
568
|
-
symbols.objectRestParamBindings.length > 0 &&
|
|
569
|
-
symbols.paramBindings &&
|
|
570
|
-
symbols.paramBindings.length > 0);
|
|
571
|
-
if (filesWithRestBindings.length === 0)
|
|
572
|
-
return;
|
|
573
|
-
const seenByPair = new Set();
|
|
574
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
575
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
576
|
-
}
|
|
577
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
578
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
579
|
-
for (const [relPath, symbols] of filesWithRestBindings) {
|
|
580
|
-
if (barrelOnlyFiles.has(relPath))
|
|
581
|
-
continue;
|
|
582
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
583
|
-
if (!fileNodeRow)
|
|
584
|
-
continue;
|
|
585
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
586
|
-
const typeMap = new Map(symbols.typeMap instanceof Map ? symbols.typeMap : []);
|
|
587
|
-
// Seed typeMap[callee::restName] = { type: argName } for each matching pair.
|
|
588
|
-
// Mirrors the seeding in buildCallEdgesJS Phase 8.3f. Keys are scoped by
|
|
589
|
-
// callee so two functions with the same rest-param name (e.g. `...rest`) in
|
|
590
|
-
// the same file don't collide (#1358).
|
|
591
|
-
// When only one callee uses a given rest name, also seed the unscoped key
|
|
592
|
-
// as a null-callerName fallback so edges aren't silently dropped if
|
|
593
|
-
// findCaller can't identify the enclosing function (#1358).
|
|
594
|
-
const restNameCallees = new Map();
|
|
595
|
-
for (const orpb of symbols.objectRestParamBindings) {
|
|
596
|
-
if (!restNameCallees.has(orpb.restName))
|
|
597
|
-
restNameCallees.set(orpb.restName, new Set());
|
|
598
|
-
restNameCallees.get(orpb.restName).add(orpb.callee);
|
|
599
|
-
}
|
|
600
|
-
const restNames = new Set();
|
|
601
|
-
for (const orpb of symbols.objectRestParamBindings) {
|
|
602
|
-
for (const pb of symbols.paramBindings) {
|
|
603
|
-
if (pb.callee === orpb.callee && pb.argIndex === orpb.argIndex) {
|
|
604
|
-
const scopedKey = `${orpb.callee}::${orpb.restName}`;
|
|
605
|
-
if (!typeMap.has(scopedKey)) {
|
|
606
|
-
typeMap.set(scopedKey, { type: pb.argName, confidence: 0.65 });
|
|
607
|
-
if (restNameCallees.get(orpb.restName).size === 1 && !typeMap.has(orpb.restName)) {
|
|
608
|
-
typeMap.set(orpb.restName, { type: pb.argName, confidence: 0.65 });
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
// restNames tracks every rest-parameter name found, regardless of whether the
|
|
612
|
-
// scoped key was already in typeMap. This ensures the post-pass (below) processes
|
|
613
|
-
// all calls whose receiver matches a known rest binding — not just those whose
|
|
614
|
-
// typeMap entry was seeded in this iteration.
|
|
615
|
-
restNames.add(orpb.restName);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
if (restNames.size === 0)
|
|
620
|
-
continue;
|
|
621
|
-
for (const call of symbols.calls) {
|
|
622
|
-
// Only process calls whose receiver is a known rest-binding name.
|
|
623
|
-
if (!call.receiver || !restNames.has(call.receiver))
|
|
624
|
-
continue;
|
|
625
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
626
|
-
// Resolve with the enriched typeMap. callerName is passed so
|
|
627
|
-
// resolveByMethodOrGlobal can look up the scoped key callee::restName (#1358).
|
|
628
|
-
// seenByPair deduplicates edges the native engine already emitted.
|
|
629
|
-
const { targets, importedFrom } = resolveCallTargets(lookup, call, relPath, importedNames, typeMap, caller.callerName);
|
|
630
|
-
for (const t of targets) {
|
|
631
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
632
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
633
|
-
const conf = computeConfidence(relPath, t.file, importedFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
634
|
-
if (conf > 0) {
|
|
635
|
-
seenByPair.add(edgeKey);
|
|
636
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
385
|
/**
|
|
644
386
|
* Object.defineProperty accessor post-pass for the native call-edge path.
|
|
645
387
|
*
|
|
@@ -703,7 +445,7 @@ function buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLook
|
|
|
703
445
|
const conf = computeConfidence(relPath, t.file, null);
|
|
704
446
|
if (conf > 0) {
|
|
705
447
|
seenByPair.add(edgeKey);
|
|
706
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native']);
|
|
448
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native', null]);
|
|
707
449
|
}
|
|
708
450
|
}
|
|
709
451
|
}
|
|
@@ -715,11 +457,11 @@ function buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLook
|
|
|
715
457
|
*
|
|
716
458
|
* The native Rust engine has no knowledge of the CHA context, so `this.method()`
|
|
717
459
|
* calls and interface method dispatches are not expanded to their concrete
|
|
718
|
-
* implementations. This JS post-pass runs after the native edges
|
|
719
|
-
*
|
|
460
|
+
* implementations. This JS post-pass runs after the native edges and adds only
|
|
461
|
+
* the CHA-resolved edges that the native engine missed.
|
|
720
462
|
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
463
|
+
* Seeds seenByPair from the current allEdgeRows snapshot to avoid duplicating
|
|
464
|
+
* edges the native engine already produced.
|
|
723
465
|
*/
|
|
724
466
|
function buildChaPostPass(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
|
|
725
467
|
// Fast-exit when the CHA context is empty (no class hierarchy in the project)
|
|
@@ -748,8 +490,9 @@ function buildChaPostPass(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
|
|
|
748
490
|
continue;
|
|
749
491
|
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
750
492
|
let chaTargets = [];
|
|
493
|
+
let isTypedReceiverDispatch = false;
|
|
751
494
|
if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
|
|
752
|
-
chaTargets = resolveThisDispatch(call.name, caller.callerName, call.receiver, chaCtx, lookup);
|
|
495
|
+
chaTargets = resolveThisDispatch(call.name, caller.callerName, call.receiver, chaCtx, lookup, relPath);
|
|
753
496
|
}
|
|
754
497
|
else {
|
|
755
498
|
const typeEntry = typeMap.get(call.receiver);
|
|
@@ -760,15 +503,25 @@ function buildChaPostPass(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
|
|
|
760
503
|
: null;
|
|
761
504
|
if (typeName) {
|
|
762
505
|
chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
|
|
506
|
+
isTypedReceiverDispatch = true;
|
|
763
507
|
}
|
|
764
508
|
}
|
|
765
509
|
for (const t of chaTargets) {
|
|
766
510
|
const edgeKey = `${caller.id}|${t.id}`;
|
|
767
511
|
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
768
|
-
|
|
512
|
+
// Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
|
|
513
|
+
// — file proximity is not meaningful for virtual dispatch confidence.
|
|
514
|
+
// this/super dispatch keeps computeConfidence-based proximity scoring to
|
|
515
|
+
// match runPostNativeThisDispatch (native-orchestrator.ts).
|
|
516
|
+
const conf = isTypedReceiverDispatch
|
|
517
|
+
? CHA_TYPED_DISPATCH_CONFIDENCE
|
|
518
|
+
: computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
|
|
769
519
|
if (conf > 0) {
|
|
770
520
|
seenByPair.add(edgeKey);
|
|
771
|
-
|
|
521
|
+
// Tag super-dispatch edges distinctly so runChaPostPass can exclude them
|
|
522
|
+
// from further CHA expansion (super calls are not virtual dispatch).
|
|
523
|
+
const technique = call.receiver === 'super' ? 'super-dispatch' : 'cha';
|
|
524
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique, null]);
|
|
772
525
|
}
|
|
773
526
|
}
|
|
774
527
|
}
|
|
@@ -843,7 +596,11 @@ function buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
|
|
|
843
596
|
}
|
|
844
597
|
const seenCallEdges = new Set();
|
|
845
598
|
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
846
|
-
|
|
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);
|
|
847
604
|
buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows);
|
|
848
605
|
}
|
|
849
606
|
}
|
|
@@ -883,6 +640,33 @@ function buildImportedNamesMap(ctx, relPath, symbols, rootDir) {
|
|
|
883
640
|
}
|
|
884
641
|
return importedNames;
|
|
885
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
|
+
}
|
|
886
670
|
function makeContextLookup(ctx, getNodeIdStmt) {
|
|
887
671
|
return {
|
|
888
672
|
byNameAndFile: (name, file) => ctx.nodesByNameAndFile.get(`${name}|${file}`) ?? [],
|
|
@@ -951,196 +735,419 @@ function buildDefinitionParamsMap(definitions) {
|
|
|
951
735
|
}
|
|
952
736
|
return map;
|
|
953
737
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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) {
|
|
981
839
|
const lastDot = caller.callerName.lastIndexOf('.');
|
|
982
840
|
if (lastDot > 0) {
|
|
983
841
|
const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
|
|
984
|
-
const
|
|
985
|
-
const qualifiedName = `${className}.${call.name}`;
|
|
842
|
+
const callerClass = caller.callerName.slice(prevDot + 1, lastDot);
|
|
986
843
|
const qualified = lookup
|
|
987
|
-
.byNameAndFile(
|
|
988
|
-
.filter((n) => n.kind === 'method');
|
|
989
|
-
if (qualified.length > 0)
|
|
844
|
+
.byNameAndFile(`${callerClass}.${call.keyExpr}`, relPath)
|
|
845
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
846
|
+
if (qualified.length > 0)
|
|
990
847
|
targets = qualified;
|
|
991
|
-
}
|
|
992
848
|
}
|
|
993
849
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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];
|
|
1014
885
|
}
|
|
1015
886
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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);
|
|
1054
931
|
}
|
|
1055
932
|
}
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
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);
|
|
1056
948
|
}
|
|
1057
|
-
|
|
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) {
|
|
1058
1012
|
const edgeKey = `${caller.id}|${t.id}`;
|
|
1059
|
-
if (t.id !== caller.id) {
|
|
1060
|
-
const
|
|
1061
|
-
if (
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
if (ptsIdx !== undefined) {
|
|
1065
|
-
// A pts-resolved edge already exists for this caller→target pair with a
|
|
1066
|
-
// penalised confidence. Upgrade it to the direct-call confidence in-place,
|
|
1067
|
-
// then promote to seenCallEdges so no further processing is needed.
|
|
1068
|
-
const ptsRow = allEdgeRows[ptsIdx];
|
|
1069
|
-
if (ptsRow) {
|
|
1070
|
-
ptsRow[3] = confidence;
|
|
1071
|
-
ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
|
|
1072
|
-
ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
|
|
1073
|
-
}
|
|
1074
|
-
ptsEdgeRows.delete(edgeKey);
|
|
1075
|
-
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]);
|
|
1076
1018
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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]);
|
|
1080
1048
|
}
|
|
1081
1049
|
}
|
|
1082
1050
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
// that `for (const f of arr) { f(); }` at module scope resolves correctly.
|
|
1104
|
-
const modulePtsKey = caller.callerName === null && ptsMap?.has(`<module>::${call.name}`)
|
|
1105
|
-
? `<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
|
|
1106
1071
|
: null;
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
// Resolve the concrete alias target. Only `name` is needed here — receiver
|
|
1126
|
-
// and line are not relevant for alias resolution (we are looking up the
|
|
1127
|
-
// aliased function by name, not dispatching a method call).
|
|
1128
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
|
|
1129
|
-
for (const t of aliasTargets) {
|
|
1130
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
1131
|
-
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1132
|
-
const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
1133
|
-
if (conf > 0) {
|
|
1134
|
-
ptsEdgeRows.set(edgeKey, allEdgeRows.length);
|
|
1135
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
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]);
|
|
1139
1090
|
}
|
|
1140
1091
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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).
|
|
1144
1151
|
if (targets.length === 0 &&
|
|
1145
1152
|
call.receiver &&
|
|
1146
1153
|
!BUILTIN_RECEIVERS.has(call.receiver) &&
|
|
@@ -1148,62 +1155,40 @@ function buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCa
|
|
|
1148
1155
|
call.receiver !== 'self' &&
|
|
1149
1156
|
call.receiver !== 'super' &&
|
|
1150
1157
|
ptsMap) {
|
|
1151
|
-
|
|
1152
|
-
if (ptsMap.has(receiverKey)) {
|
|
1153
|
-
for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
|
|
1154
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
|
|
1155
|
-
for (const t of aliasTargets) {
|
|
1156
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
1157
|
-
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1158
|
-
const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
1159
|
-
if (conf > 0) {
|
|
1160
|
-
ptsEdgeRows.set(edgeKey, allEdgeRows.length);
|
|
1161
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1158
|
+
emitPtsReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, seenCallEdges, ptsEdgeRows, allEdgeRows);
|
|
1167
1159
|
}
|
|
1160
|
+
// Step 5: Emit receiver edge for external (non-this/self/super) receivers.
|
|
1168
1161
|
if (call.receiver &&
|
|
1169
1162
|
!BUILTIN_RECEIVERS.has(call.receiver) &&
|
|
1170
1163
|
call.receiver !== 'this' &&
|
|
1171
1164
|
call.receiver !== 'self' &&
|
|
1172
1165
|
call.receiver !== 'super') {
|
|
1173
|
-
const recv = resolveReceiverEdge(lookup, { name: call.name, receiver: call.receiver }, caller, relPath, typeMap, seenCallEdges);
|
|
1166
|
+
const recv = resolveReceiverEdge(lookup, { name: call.name, receiver: call.receiver }, caller, relPath, typeMap, seenCallEdges, importArtifactNames ?? importedNames);
|
|
1174
1167
|
if (recv) {
|
|
1175
|
-
allEdgeRows.push([
|
|
1168
|
+
allEdgeRows.push([
|
|
1169
|
+
recv.callerId,
|
|
1170
|
+
recv.receiverId,
|
|
1171
|
+
'receiver',
|
|
1172
|
+
recv.confidence,
|
|
1173
|
+
0,
|
|
1174
|
+
null,
|
|
1175
|
+
null,
|
|
1176
|
+
]);
|
|
1176
1177
|
}
|
|
1177
1178
|
}
|
|
1178
|
-
// Phase 8.5
|
|
1179
|
-
// For `this`/`self`/`super` calls: resolve through the class hierarchy instead
|
|
1180
|
-
// of relying solely on global name matching.
|
|
1181
|
-
// For typed receiver calls: expand to all instantiated concrete implementations.
|
|
1179
|
+
// Step 6: Phase 8.5 CHA + RTA dispatch expansion.
|
|
1182
1180
|
if (chaCtx && call.receiver) {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
if (typeName) {
|
|
1195
|
-
chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
for (const t of chaTargets) {
|
|
1199
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
1200
|
-
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1201
|
-
const conf = computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
|
|
1202
|
-
if (conf > 0) {
|
|
1203
|
-
seenCallEdges.add(edgeKey);
|
|
1204
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha']);
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
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]);
|
|
1207
1192
|
}
|
|
1208
1193
|
}
|
|
1209
1194
|
}
|
|
@@ -1219,7 +1204,7 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
|
|
|
1219
1204
|
const targetRows = (ctx.nodesByName.get(cls.extends) || []).filter((n) => EXTENDS_TARGET_KINDS.has(n.kind));
|
|
1220
1205
|
if (sourceRow) {
|
|
1221
1206
|
for (const t of targetRows) {
|
|
1222
|
-
allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null]);
|
|
1207
|
+
allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null, null]);
|
|
1223
1208
|
}
|
|
1224
1209
|
}
|
|
1225
1210
|
}
|
|
@@ -1228,7 +1213,7 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
|
|
|
1228
1213
|
const targetRows = (ctx.nodesByName.get(cls.implements) || []).filter((n) => IMPLEMENTS_TARGET_KINDS.has(n.kind));
|
|
1229
1214
|
if (sourceRow) {
|
|
1230
1215
|
for (const t of targetRows) {
|
|
1231
|
-
allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null]);
|
|
1216
|
+
allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null, null]);
|
|
1232
1217
|
}
|
|
1233
1218
|
}
|
|
1234
1219
|
}
|
|
@@ -1237,7 +1222,8 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
|
|
|
1237
1222
|
// ── Native bulk-insert technique back-fill ──────────────────────────────
|
|
1238
1223
|
/**
|
|
1239
1224
|
* After native bulkInsertEdges (which does not write the technique column),
|
|
1240
|
-
* 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.
|
|
1241
1227
|
*
|
|
1242
1228
|
* Rows with an explicit technique get a targeted UPDATE by (source_id, target_id).
|
|
1243
1229
|
* The catch-all 'ts-native' tag is scoped to only the source_ids present in this
|
|
@@ -1254,6 +1240,8 @@ function applyEdgeTechniquesAfterNativeInsert(db, rows) {
|
|
|
1254
1240
|
const sourceIds = [...new Set(callRows.map((r) => r[0]))];
|
|
1255
1241
|
// Chunk to stay within SQLite's SQLITE_LIMIT_VARIABLE_NUMBER (999 on older builds).
|
|
1256
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);
|
|
1257
1245
|
const tx = db.transaction(() => {
|
|
1258
1246
|
if (taggedRows.length > 0) {
|
|
1259
1247
|
const stmt = db.prepare("UPDATE edges SET technique = ? WHERE kind = 'calls' AND source_id = ? AND target_id = ? AND technique IS NULL");
|
|
@@ -1264,6 +1252,24 @@ function applyEdgeTechniquesAfterNativeInsert(db, rows) {
|
|
|
1264
1252
|
const chunk = sourceIds.slice(i, i + CHUNK_SIZE);
|
|
1265
1253
|
const placeholders = chunk.map(() => '?').join(',');
|
|
1266
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]);
|
|
1267
1273
|
}
|
|
1268
1274
|
});
|
|
1269
1275
|
tx();
|
|
@@ -1292,6 +1298,7 @@ function reconnectReverseDepEdges(ctx) {
|
|
|
1292
1298
|
saved.confidence,
|
|
1293
1299
|
saved.dynamic,
|
|
1294
1300
|
saved.technique,
|
|
1301
|
+
saved.dynamicKind ?? null,
|
|
1295
1302
|
]);
|
|
1296
1303
|
}
|
|
1297
1304
|
else {
|
|
@@ -1330,7 +1337,7 @@ function reconnectReverseDepEdges(ctx) {
|
|
|
1330
1337
|
* their import targets. Falls back to loading ALL nodes for full builds or
|
|
1331
1338
|
* larger incremental changes.
|
|
1332
1339
|
*/
|
|
1333
|
-
const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant')`;
|
|
1340
|
+
const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant','variable')`;
|
|
1334
1341
|
function loadNodes(ctx) {
|
|
1335
1342
|
const { db, fileSymbols, isFullBuild, batchResolved } = ctx;
|
|
1336
1343
|
const nodeKindFilter = NODE_KIND_FILTER_SQL;
|
|
@@ -1399,7 +1406,15 @@ export async function buildEdges(ctx) {
|
|
|
1399
1406
|
// Enrich typeMap for .ts/.tsx files using the TypeScript compiler API.
|
|
1400
1407
|
// Runs before call-edge construction so the accurate types are available
|
|
1401
1408
|
// for method-call resolution. Gated on config so users can opt out.
|
|
1402
|
-
|
|
1409
|
+
//
|
|
1410
|
+
// Skip for small incremental builds: TypeScript program creation requires
|
|
1411
|
+
// loading the entire tsconfig file list (~700ms startup on the codegraph
|
|
1412
|
+
// corpus), which dominates the 1-file rebuild time. Native engine bypasses
|
|
1413
|
+
// this entirely via the Rust orchestrator; WASM/JS engines need this gate
|
|
1414
|
+
// to match native's effective behaviour on tiny incremental changes.
|
|
1415
|
+
// Mirrors the smallFilesThreshold gates for nativeDb and native call-edges.
|
|
1416
|
+
const isSmallIncremental = !ctx.isFullBuild && ctx.fileSymbols.size <= ctx.config.build.smallFilesThreshold;
|
|
1417
|
+
if (ctx.config.build.typescriptResolver && !isSmallIncremental) {
|
|
1403
1418
|
await enrichTypeMapWithTsc(ctx.rootDir, ctx.fileSymbols);
|
|
1404
1419
|
}
|
|
1405
1420
|
const native = engineName === 'native' ? loadNative() : null;
|
|
@@ -1454,26 +1469,12 @@ export async function buildEdges(ctx) {
|
|
|
1454
1469
|
(ctx.isFullBuild || ctx.fileSymbols.size > ctx.config.build.smallFilesThreshold);
|
|
1455
1470
|
if (useNativeCallEdges) {
|
|
1456
1471
|
buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodesBefore, native);
|
|
1457
|
-
//
|
|
1458
|
-
//
|
|
1472
|
+
// The native engine receives all pts bindings (paramBindings,
|
|
1473
|
+
// fnRefBindings, thisCallBindings, objectRestParamBindings, …) through
|
|
1474
|
+
// NativeFileEntry and runs the same points-to solver as the JS path, so
|
|
1475
|
+
// no pts post-passes are needed here. Only capabilities that remain
|
|
1476
|
+
// JS-only run as post-passes below.
|
|
1459
1477
|
const sharedLookup = makeContextLookup(ctx, getNodeIdStmt);
|
|
1460
|
-
// Phase 8.3c post-pass: augment native call edges with parameter-flow pts
|
|
1461
|
-
// edges. The native Rust engine has no knowledge of paramBindings, so any
|
|
1462
|
-
// `fn()` call inside a higher-order function would be missed. This JS pass
|
|
1463
|
-
// runs on top of the native edges and adds only the pts-resolved edges that
|
|
1464
|
-
// the native engine could not produce.
|
|
1465
|
-
buildParamFlowPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1466
|
-
// bind/alias post-pass: augment native call edges with fnRefBindings-seeded
|
|
1467
|
-
// pts edges. The native Rust engine has no knowledge of JS fnRefBindings
|
|
1468
|
-
// (e.g. `const f = fn.bind(ctx)`), so calls to bind-created aliases are
|
|
1469
|
-
// not resolved to their original function on the native path.
|
|
1470
|
-
buildFnRefBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1471
|
-
// this-rebinding post-pass: resolve `this()` calls inside functions that
|
|
1472
|
-
// were invoked via `.call(namedCtx, ...)` / `.apply(namedCtx, ...)`.
|
|
1473
|
-
buildThisCallBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1474
|
-
// Phase 8.3f post-pass: augment native call edges with object rest-param
|
|
1475
|
-
// receiver resolution — typeMap[restName] → argName → typeMap[argName.method].
|
|
1476
|
-
buildObjectRestParamPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1477
1478
|
// Object.defineProperty accessor post-pass: resolve this-dispatch inside
|
|
1478
1479
|
// getter/setter functions registered via Object.defineProperty.
|
|
1479
1480
|
buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
@@ -1485,6 +1486,21 @@ export async function buildEdges(ctx) {
|
|
|
1485
1486
|
else {
|
|
1486
1487
|
buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx);
|
|
1487
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
|
+
}
|
|
1488
1504
|
// When using native edge insert, skip JS insert here — do it after tx commits.
|
|
1489
1505
|
// Otherwise insert edges within this transaction for atomicity.
|
|
1490
1506
|
const useNativeEdgeInsert = ctx.engineName === 'native' && !!ctx.nativeDb?.bulkInsertEdges;
|