@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,16 +11,26 @@ 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 type {
|
|
16
|
+
ArrayCallbackBinding,
|
|
17
|
+
ArrayElemBinding,
|
|
15
18
|
BetterSqlite3Database,
|
|
16
19
|
Call,
|
|
17
20
|
ClassRelation,
|
|
18
21
|
Definition,
|
|
22
|
+
DynamicKind,
|
|
19
23
|
ExtractorOutput,
|
|
20
24
|
FnRefBinding,
|
|
25
|
+
ForOfBinding,
|
|
21
26
|
Import,
|
|
22
27
|
NativeAddon,
|
|
23
28
|
NodeRow,
|
|
29
|
+
ObjectPropBinding,
|
|
30
|
+
ObjectRestParamBinding,
|
|
31
|
+
ParamBinding,
|
|
32
|
+
SpreadArgBinding,
|
|
33
|
+
ThisCallBinding,
|
|
24
34
|
TypeMapEntry,
|
|
25
35
|
} from '../../../../types.js';
|
|
26
36
|
import { computeConfidence } from '../../resolve.js';
|
|
@@ -37,12 +47,19 @@ import {
|
|
|
37
47
|
import type { ChaContext } from '../cha.js';
|
|
38
48
|
import { buildChaContext, resolveChaTargets, resolveThisDispatch } from '../cha.js';
|
|
39
49
|
import type { PipelineContext } from '../context.js';
|
|
40
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
BUILTIN_RECEIVERS,
|
|
52
|
+
batchInsertEdges,
|
|
53
|
+
CHA_DISPATCH_PENALTY,
|
|
54
|
+
CHA_TYPED_DISPATCH_CONFIDENCE,
|
|
55
|
+
runChaPostPass,
|
|
56
|
+
} from '../helpers.js';
|
|
41
57
|
import { getResolved, isBarrelFile, resolveBarrelExportCached } from './resolve-imports.js';
|
|
42
58
|
|
|
43
59
|
// ── Local types ──────────────────────────────────────────────────────────
|
|
44
60
|
|
|
45
|
-
type EdgeRowTuple = [number, number, string, number, number, string | null];
|
|
61
|
+
type EdgeRowTuple = [number, number, string, number, number, string | null, string | null];
|
|
62
|
+
// src tgt kind conf dyn technique dynamic_kind
|
|
46
63
|
|
|
47
64
|
interface NodeIdStmt {
|
|
48
65
|
get(name: string, kind: string, file: string, line: number): { id: number } | undefined;
|
|
@@ -61,13 +78,27 @@ interface QueryNodeRow {
|
|
|
61
78
|
interface NativeFileEntry {
|
|
62
79
|
file: string;
|
|
63
80
|
fileNodeId: number;
|
|
64
|
-
definitions: Array<{
|
|
81
|
+
definitions: Array<{
|
|
82
|
+
name: string;
|
|
83
|
+
kind: string;
|
|
84
|
+
line: number;
|
|
85
|
+
endLine: number | null;
|
|
86
|
+
params?: string[];
|
|
87
|
+
}>;
|
|
65
88
|
calls: Call[];
|
|
66
89
|
importedNames: Array<{ name: string; file: string }>;
|
|
67
90
|
classes: ClassRelation[];
|
|
68
91
|
typeMap: Array<{ name: string; typeName: string; confidence: number }>;
|
|
69
92
|
/** Phase 8.3: function-reference bindings for pts analysis. */
|
|
70
93
|
fnRefBindings?: Array<{ lhs: string; rhs: string; rhsReceiver?: string }>;
|
|
94
|
+
paramBindings?: ParamBinding[];
|
|
95
|
+
thisCallBindings?: ThisCallBinding[];
|
|
96
|
+
arrayElemBindings?: ArrayElemBinding[];
|
|
97
|
+
spreadArgBindings?: SpreadArgBinding[];
|
|
98
|
+
forOfBindings?: ForOfBinding[];
|
|
99
|
+
arrayCallbackBindings?: ArrayCallbackBinding[];
|
|
100
|
+
objectRestParamBindings?: ObjectRestParamBinding[];
|
|
101
|
+
objectPropBindings?: ObjectPropBinding[];
|
|
71
102
|
}
|
|
72
103
|
|
|
73
104
|
/** Shape returned by native buildCallEdges. */
|
|
@@ -77,11 +108,9 @@ interface NativeEdge {
|
|
|
77
108
|
kind: string;
|
|
78
109
|
confidence: number;
|
|
79
110
|
dynamic: number;
|
|
111
|
+
dynamic_kind?: string | null;
|
|
80
112
|
}
|
|
81
113
|
|
|
82
|
-
/** Phase 8.5: confidence penalty applied to CHA-dispatch edges. */
|
|
83
|
-
export const CHA_DISPATCH_PENALTY = 0.1;
|
|
84
|
-
|
|
85
114
|
// ── Node lookup setup ───────────────────────────────────────────────────
|
|
86
115
|
|
|
87
116
|
function makeGetNodeIdStmt(db: BetterSqlite3Database): NodeIdStmt {
|
|
@@ -138,7 +167,7 @@ function emitTypeOnlySymbolEdges(
|
|
|
138
167
|
}
|
|
139
168
|
const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
|
|
140
169
|
if (candidates && candidates.length > 0) {
|
|
141
|
-
allEdgeRows.push([fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0, null]);
|
|
170
|
+
allEdgeRows.push([fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0, null, null]);
|
|
142
171
|
}
|
|
143
172
|
}
|
|
144
173
|
}
|
|
@@ -160,7 +189,7 @@ function emitEdgesForImport(
|
|
|
160
189
|
if (!targetRow) return;
|
|
161
190
|
|
|
162
191
|
const edgeKind = importEdgeKind(imp);
|
|
163
|
-
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null]);
|
|
192
|
+
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null, null]);
|
|
164
193
|
|
|
165
194
|
if (imp.typeOnly) {
|
|
166
195
|
emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows);
|
|
@@ -215,7 +244,7 @@ function buildBarrelEdges(
|
|
|
215
244
|
: edgeKind === 'dynamic-imports'
|
|
216
245
|
? 'dynamic-imports'
|
|
217
246
|
: 'imports';
|
|
218
|
-
edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null]);
|
|
247
|
+
edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null, null]);
|
|
219
248
|
}
|
|
220
249
|
}
|
|
221
250
|
}
|
|
@@ -398,7 +427,7 @@ function buildImportEdgesNative(
|
|
|
398
427
|
) as NativeEdge[];
|
|
399
428
|
|
|
400
429
|
for (const e of nativeEdges) {
|
|
401
|
-
allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic, null]);
|
|
430
|
+
allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic, null, null]);
|
|
402
431
|
}
|
|
403
432
|
}
|
|
404
433
|
|
|
@@ -509,17 +538,35 @@ function buildCallEdgesNative(
|
|
|
509
538
|
nativeFiles.push({
|
|
510
539
|
file: relPath,
|
|
511
540
|
fileNodeId: fileNodeRow.id,
|
|
512
|
-
definitions: symbols.definitions.map((d) =>
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
541
|
+
definitions: symbols.definitions.map((d) => {
|
|
542
|
+
const params = d.children?.filter((c) => c.kind === 'parameter').map((c) => c.name);
|
|
543
|
+
return {
|
|
544
|
+
name: d.name,
|
|
545
|
+
kind: d.kind,
|
|
546
|
+
line: d.line,
|
|
547
|
+
endLine: d.endLine ?? null,
|
|
548
|
+
params: params?.length ? params : undefined,
|
|
549
|
+
};
|
|
550
|
+
}),
|
|
518
551
|
calls: symbols.calls,
|
|
519
552
|
importedNames,
|
|
520
553
|
classes: symbols.classes,
|
|
521
554
|
typeMap,
|
|
522
555
|
fnRefBindings: symbols.fnRefBindings?.length ? symbols.fnRefBindings : undefined,
|
|
556
|
+
paramBindings: symbols.paramBindings?.length ? symbols.paramBindings : undefined,
|
|
557
|
+
thisCallBindings: symbols.thisCallBindings?.length ? symbols.thisCallBindings : undefined,
|
|
558
|
+
arrayElemBindings: symbols.arrayElemBindings?.length ? symbols.arrayElemBindings : undefined,
|
|
559
|
+
spreadArgBindings: symbols.spreadArgBindings?.length ? symbols.spreadArgBindings : undefined,
|
|
560
|
+
forOfBindings: symbols.forOfBindings?.length ? symbols.forOfBindings : undefined,
|
|
561
|
+
arrayCallbackBindings: symbols.arrayCallbackBindings?.length
|
|
562
|
+
? symbols.arrayCallbackBindings
|
|
563
|
+
: undefined,
|
|
564
|
+
objectRestParamBindings: symbols.objectRestParamBindings?.length
|
|
565
|
+
? symbols.objectRestParamBindings
|
|
566
|
+
: undefined,
|
|
567
|
+
objectPropBindings: symbols.objectPropBindings?.length
|
|
568
|
+
? symbols.objectPropBindings
|
|
569
|
+
: undefined,
|
|
523
570
|
});
|
|
524
571
|
}
|
|
525
572
|
|
|
@@ -534,367 +581,11 @@ function buildCallEdgesNative(
|
|
|
534
581
|
e.confidence,
|
|
535
582
|
e.dynamic,
|
|
536
583
|
e.kind === 'calls' ? 'ts-native' : null,
|
|
584
|
+
e.dynamic_kind ?? null,
|
|
537
585
|
]);
|
|
538
586
|
}
|
|
539
587
|
}
|
|
540
588
|
|
|
541
|
-
/**
|
|
542
|
-
* Phase 8.3c pts post-pass for the native call-edge path.
|
|
543
|
-
*
|
|
544
|
-
* The native Rust engine builds call edges without knowledge of paramBindings,
|
|
545
|
-
* so `fn()` calls inside higher-order functions are not resolved to their
|
|
546
|
-
* concrete targets. This JS post-pass runs after the native edge pass and adds
|
|
547
|
-
* only the parameter-flow pts edges that the native engine missed.
|
|
548
|
-
*
|
|
549
|
-
* To avoid duplicating edges already emitted by the native engine, the current
|
|
550
|
-
* allEdgeRows snapshot is used to seed a seenByPair set before processing each
|
|
551
|
-
* file.
|
|
552
|
-
*/
|
|
553
|
-
function buildParamFlowPtsPostPass(
|
|
554
|
-
ctx: PipelineContext,
|
|
555
|
-
getNodeIdStmt: NodeIdStmt,
|
|
556
|
-
allEdgeRows: EdgeRowTuple[],
|
|
557
|
-
sharedLookup?: CallNodeLookup,
|
|
558
|
-
): void {
|
|
559
|
-
// Only process files that actually have paramBindings (avoid useless work).
|
|
560
|
-
const filesWithParams = [...ctx.fileSymbols].filter(
|
|
561
|
-
([, symbols]) => symbols.paramBindings && symbols.paramBindings.length > 0,
|
|
562
|
-
);
|
|
563
|
-
if (filesWithParams.length === 0) return;
|
|
564
|
-
|
|
565
|
-
// Seed seenByPair from the existing rows so we don't duplicate native edges.
|
|
566
|
-
// This is O(|allEdgeRows|) once per post-pass, which is acceptable.
|
|
567
|
-
const seenByPair = new Set<string>();
|
|
568
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
569
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
573
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
574
|
-
|
|
575
|
-
for (const [relPath, symbols] of filesWithParams) {
|
|
576
|
-
if (barrelOnlyFiles.has(relPath)) continue;
|
|
577
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
578
|
-
if (!fileNodeRow) continue;
|
|
579
|
-
|
|
580
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
581
|
-
const typeMap: Map<string, TypeMapEntry | string> = symbols.typeMap || new Map();
|
|
582
|
-
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
583
|
-
if (!ptsMap) continue;
|
|
584
|
-
|
|
585
|
-
for (const call of symbols.calls) {
|
|
586
|
-
if (call.receiver || call.dynamic) continue; // pts post-pass handles only param-flow (non-dynamic)
|
|
587
|
-
|
|
588
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
589
|
-
const scopedKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
|
|
590
|
-
if (!scopedKey || !ptsMap.has(scopedKey)) continue;
|
|
591
|
-
|
|
592
|
-
// Only resolve calls that had no direct targets (same guard as buildFileCallEdges).
|
|
593
|
-
const { targets } = resolveCallTargets(
|
|
594
|
-
lookup,
|
|
595
|
-
call,
|
|
596
|
-
relPath,
|
|
597
|
-
importedNames,
|
|
598
|
-
typeMap as Map<string, unknown>,
|
|
599
|
-
);
|
|
600
|
-
if (targets.length > 0) continue;
|
|
601
|
-
|
|
602
|
-
for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
|
|
603
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
|
|
604
|
-
lookup,
|
|
605
|
-
{ name: alias },
|
|
606
|
-
relPath,
|
|
607
|
-
importedNames,
|
|
608
|
-
typeMap as Map<string, unknown>,
|
|
609
|
-
);
|
|
610
|
-
for (const t of aliasTargets) {
|
|
611
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
612
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
613
|
-
const conf =
|
|
614
|
-
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
615
|
-
if (conf > 0) {
|
|
616
|
-
seenByPair.add(edgeKey);
|
|
617
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* bind/alias pts post-pass for the native call-edge path.
|
|
628
|
-
*
|
|
629
|
-
* The native Rust engine has no knowledge of JS-layer fnRefBindings (e.g.
|
|
630
|
-
* `const f = fn.bind(ctx)`), so calls to bind-created aliases are not resolved
|
|
631
|
-
* to their original function on the native path. This JS post-pass runs after
|
|
632
|
-
* the native edge pass and adds only the fnRefBindings-seeded pts edges that the
|
|
633
|
-
* native engine missed.
|
|
634
|
-
*
|
|
635
|
-
* Uses the same seenByPair dedup guard as buildParamFlowPtsPostPass to avoid
|
|
636
|
-
* duplicating edges already emitted by the native engine.
|
|
637
|
-
*/
|
|
638
|
-
function buildFnRefBindingsPtsPostPass(
|
|
639
|
-
ctx: PipelineContext,
|
|
640
|
-
getNodeIdStmt: NodeIdStmt,
|
|
641
|
-
allEdgeRows: EdgeRowTuple[],
|
|
642
|
-
sharedLookup?: CallNodeLookup,
|
|
643
|
-
): void {
|
|
644
|
-
// Only process files that actually have fnRefBindings.
|
|
645
|
-
const filesWithBindings = [...ctx.fileSymbols].filter(
|
|
646
|
-
([, symbols]) => symbols.fnRefBindings && symbols.fnRefBindings.length > 0,
|
|
647
|
-
);
|
|
648
|
-
if (filesWithBindings.length === 0) return;
|
|
649
|
-
|
|
650
|
-
// Seed seenByPair from the existing rows so we don't duplicate native edges.
|
|
651
|
-
const seenByPair = new Set<string>();
|
|
652
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
653
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
657
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
658
|
-
|
|
659
|
-
for (const [relPath, symbols] of filesWithBindings) {
|
|
660
|
-
if (barrelOnlyFiles.has(relPath)) continue;
|
|
661
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
662
|
-
if (!fileNodeRow) continue;
|
|
663
|
-
|
|
664
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
665
|
-
const typeMap: Map<string, TypeMapEntry | string> = symbols.typeMap || new Map();
|
|
666
|
-
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
667
|
-
if (!ptsMap) continue;
|
|
668
|
-
|
|
669
|
-
// Only resolve calls whose name is an lhs in fnRefBindings — the same
|
|
670
|
-
// narrowed guard used in buildFileCallEdges case (c).
|
|
671
|
-
const fnRefBindingLhs = new Set(symbols.fnRefBindings!.map((b) => b.lhs));
|
|
672
|
-
|
|
673
|
-
for (const call of symbols.calls) {
|
|
674
|
-
if (call.receiver || call.dynamic) continue; // bind aliases are flat-keyed, never dynamic
|
|
675
|
-
if (!fnRefBindingLhs.has(call.name)) continue;
|
|
676
|
-
if (!ptsMap.has(call.name)) continue;
|
|
677
|
-
|
|
678
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
679
|
-
|
|
680
|
-
// Only resolve calls that had no direct targets (same guard as buildFileCallEdges).
|
|
681
|
-
const { targets } = resolveCallTargets(
|
|
682
|
-
lookup,
|
|
683
|
-
call,
|
|
684
|
-
relPath,
|
|
685
|
-
importedNames,
|
|
686
|
-
typeMap as Map<string, unknown>,
|
|
687
|
-
);
|
|
688
|
-
if (targets.length > 0) continue;
|
|
689
|
-
|
|
690
|
-
for (const alias of resolveViaPointsTo(call.name, ptsMap)) {
|
|
691
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
|
|
692
|
-
lookup,
|
|
693
|
-
{ name: alias },
|
|
694
|
-
relPath,
|
|
695
|
-
importedNames,
|
|
696
|
-
typeMap as Map<string, unknown>,
|
|
697
|
-
);
|
|
698
|
-
for (const t of aliasTargets) {
|
|
699
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
700
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
701
|
-
const conf =
|
|
702
|
-
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
703
|
-
if (conf > 0) {
|
|
704
|
-
seenByPair.add(edgeKey);
|
|
705
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
* this-rebinding post-pass for the native call-edge path.
|
|
716
|
-
*
|
|
717
|
-
* When `fn.call(namedCtx, ...)` or `fn.apply(namedCtx, ...)` is extracted by the
|
|
718
|
-
* WASM layer, `thisCallBindings` records `{ callee: 'fn', thisArg: 'namedCtx' }`.
|
|
719
|
-
* The native Rust engine has no knowledge of these bindings, so `this()` calls
|
|
720
|
-
* inside `fn` remain unresolved. This JS post-pass adds the missing edges by
|
|
721
|
-
* resolving `this()` calls inside each `fn` that has a thisCallBinding.
|
|
722
|
-
*/
|
|
723
|
-
function buildThisCallBindingsPtsPostPass(
|
|
724
|
-
ctx: PipelineContext,
|
|
725
|
-
getNodeIdStmt: NodeIdStmt,
|
|
726
|
-
allEdgeRows: EdgeRowTuple[],
|
|
727
|
-
sharedLookup?: CallNodeLookup,
|
|
728
|
-
): void {
|
|
729
|
-
const filesWithBindings = [...ctx.fileSymbols].filter(
|
|
730
|
-
([, symbols]) => symbols.thisCallBindings && symbols.thisCallBindings.length > 0,
|
|
731
|
-
);
|
|
732
|
-
if (filesWithBindings.length === 0) return;
|
|
733
|
-
|
|
734
|
-
const seenByPair = new Set<string>();
|
|
735
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
736
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
740
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
741
|
-
|
|
742
|
-
for (const [relPath, symbols] of filesWithBindings) {
|
|
743
|
-
if (barrelOnlyFiles.has(relPath)) continue;
|
|
744
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
745
|
-
if (!fileNodeRow) continue;
|
|
746
|
-
|
|
747
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
748
|
-
const typeMap: Map<string, TypeMapEntry | string> = symbols.typeMap || new Map();
|
|
749
|
-
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
750
|
-
if (!ptsMap) continue;
|
|
751
|
-
|
|
752
|
-
// Only process calls named 'this' (callee-not-receiver usage)
|
|
753
|
-
for (const call of symbols.calls) {
|
|
754
|
-
if (call.name !== 'this' || call.receiver) continue;
|
|
755
|
-
|
|
756
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
757
|
-
if (caller.callerName == null) continue;
|
|
758
|
-
|
|
759
|
-
const scopedKey = `${caller.callerName}::this`;
|
|
760
|
-
if (!ptsMap.has(scopedKey)) continue;
|
|
761
|
-
|
|
762
|
-
for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
|
|
763
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
|
|
764
|
-
lookup,
|
|
765
|
-
{ name: alias },
|
|
766
|
-
relPath,
|
|
767
|
-
importedNames,
|
|
768
|
-
typeMap as Map<string, unknown>,
|
|
769
|
-
);
|
|
770
|
-
for (const t of aliasTargets) {
|
|
771
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
772
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
773
|
-
const conf =
|
|
774
|
-
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
775
|
-
if (conf > 0) {
|
|
776
|
-
seenByPair.add(edgeKey);
|
|
777
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
/**
|
|
787
|
-
* Phase 8.3f post-pass for the native call-edge path.
|
|
788
|
-
*
|
|
789
|
-
* The native Rust engine builds call edges without knowledge of
|
|
790
|
-
* objectRestParamBindings, so `rest.method()` calls inside functions with
|
|
791
|
-
* object-destructuring rest parameters are not resolved via the typeMap chain.
|
|
792
|
-
* The Rust engine already resolves same-file and directly-imported callees
|
|
793
|
-
* (via steps 1–2 of its resolution logic), so this post-pass only adds edges
|
|
794
|
-
* that require the typeMap-chain path:
|
|
795
|
-
* typeMap[restName] → argName → typeMap[argName.method] → target
|
|
796
|
-
*
|
|
797
|
-
* Mirrors the seeding in buildCallEdgesJS (Phase 8.3f) to ensure both engine
|
|
798
|
-
* paths produce identical results for receiver-typed rest-param calls.
|
|
799
|
-
*/
|
|
800
|
-
function buildObjectRestParamPostPass(
|
|
801
|
-
ctx: PipelineContext,
|
|
802
|
-
getNodeIdStmt: NodeIdStmt,
|
|
803
|
-
allEdgeRows: EdgeRowTuple[],
|
|
804
|
-
sharedLookup?: CallNodeLookup,
|
|
805
|
-
): void {
|
|
806
|
-
const filesWithRestBindings = [...ctx.fileSymbols].filter(
|
|
807
|
-
([, symbols]) =>
|
|
808
|
-
symbols.objectRestParamBindings &&
|
|
809
|
-
symbols.objectRestParamBindings.length > 0 &&
|
|
810
|
-
symbols.paramBindings &&
|
|
811
|
-
symbols.paramBindings.length > 0,
|
|
812
|
-
);
|
|
813
|
-
if (filesWithRestBindings.length === 0) return;
|
|
814
|
-
|
|
815
|
-
const seenByPair = new Set<string>();
|
|
816
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
817
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
821
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
822
|
-
|
|
823
|
-
for (const [relPath, symbols] of filesWithRestBindings) {
|
|
824
|
-
if (barrelOnlyFiles.has(relPath)) continue;
|
|
825
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
826
|
-
if (!fileNodeRow) continue;
|
|
827
|
-
|
|
828
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
829
|
-
const typeMap: Map<string, TypeMapEntry | string> = new Map(
|
|
830
|
-
symbols.typeMap instanceof Map ? symbols.typeMap : [],
|
|
831
|
-
);
|
|
832
|
-
|
|
833
|
-
// Seed typeMap[callee::restName] = { type: argName } for each matching pair.
|
|
834
|
-
// Mirrors the seeding in buildCallEdgesJS Phase 8.3f. Keys are scoped by
|
|
835
|
-
// callee so two functions with the same rest-param name (e.g. `...rest`) in
|
|
836
|
-
// the same file don't collide (#1358).
|
|
837
|
-
// When only one callee uses a given rest name, also seed the unscoped key
|
|
838
|
-
// as a null-callerName fallback so edges aren't silently dropped if
|
|
839
|
-
// findCaller can't identify the enclosing function (#1358).
|
|
840
|
-
const restNameCallees = new Map<string, Set<string>>();
|
|
841
|
-
for (const orpb of symbols.objectRestParamBindings!) {
|
|
842
|
-
if (!restNameCallees.has(orpb.restName)) restNameCallees.set(orpb.restName, new Set());
|
|
843
|
-
restNameCallees.get(orpb.restName)!.add(orpb.callee);
|
|
844
|
-
}
|
|
845
|
-
const restNames = new Set<string>();
|
|
846
|
-
for (const orpb of symbols.objectRestParamBindings!) {
|
|
847
|
-
for (const pb of symbols.paramBindings!) {
|
|
848
|
-
if (pb.callee === orpb.callee && pb.argIndex === orpb.argIndex) {
|
|
849
|
-
const scopedKey = `${orpb.callee}::${orpb.restName}`;
|
|
850
|
-
if (!typeMap.has(scopedKey)) {
|
|
851
|
-
typeMap.set(scopedKey, { type: pb.argName, confidence: 0.65 });
|
|
852
|
-
if (restNameCallees.get(orpb.restName)!.size === 1 && !typeMap.has(orpb.restName)) {
|
|
853
|
-
typeMap.set(orpb.restName, { type: pb.argName, confidence: 0.65 });
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
// restNames tracks every rest-parameter name found, regardless of whether the
|
|
857
|
-
// scoped key was already in typeMap. This ensures the post-pass (below) processes
|
|
858
|
-
// all calls whose receiver matches a known rest binding — not just those whose
|
|
859
|
-
// typeMap entry was seeded in this iteration.
|
|
860
|
-
restNames.add(orpb.restName);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
if (restNames.size === 0) continue;
|
|
865
|
-
|
|
866
|
-
for (const call of symbols.calls) {
|
|
867
|
-
// Only process calls whose receiver is a known rest-binding name.
|
|
868
|
-
if (!call.receiver || !restNames.has(call.receiver)) continue;
|
|
869
|
-
|
|
870
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
871
|
-
|
|
872
|
-
// Resolve with the enriched typeMap. callerName is passed so
|
|
873
|
-
// resolveByMethodOrGlobal can look up the scoped key callee::restName (#1358).
|
|
874
|
-
// seenByPair deduplicates edges the native engine already emitted.
|
|
875
|
-
const { targets, importedFrom } = resolveCallTargets(
|
|
876
|
-
lookup,
|
|
877
|
-
call,
|
|
878
|
-
relPath,
|
|
879
|
-
importedNames,
|
|
880
|
-
typeMap as Map<string, unknown>,
|
|
881
|
-
caller.callerName,
|
|
882
|
-
);
|
|
883
|
-
for (const t of targets) {
|
|
884
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
885
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
886
|
-
const conf =
|
|
887
|
-
computeConfidence(relPath, t.file, importedFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
888
|
-
if (conf > 0) {
|
|
889
|
-
seenByPair.add(edgeKey);
|
|
890
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
|
|
898
589
|
/**
|
|
899
590
|
* Object.defineProperty accessor post-pass for the native call-edge path.
|
|
900
591
|
*
|
|
@@ -975,7 +666,7 @@ function buildDefinePropertyPostPass(
|
|
|
975
666
|
const conf = computeConfidence(relPath, t.file, null);
|
|
976
667
|
if (conf > 0) {
|
|
977
668
|
seenByPair.add(edgeKey);
|
|
978
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native']);
|
|
669
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native', null]);
|
|
979
670
|
}
|
|
980
671
|
}
|
|
981
672
|
}
|
|
@@ -988,11 +679,11 @@ function buildDefinePropertyPostPass(
|
|
|
988
679
|
*
|
|
989
680
|
* The native Rust engine has no knowledge of the CHA context, so `this.method()`
|
|
990
681
|
* calls and interface method dispatches are not expanded to their concrete
|
|
991
|
-
* implementations. This JS post-pass runs after the native edges
|
|
992
|
-
*
|
|
682
|
+
* implementations. This JS post-pass runs after the native edges and adds only
|
|
683
|
+
* the CHA-resolved edges that the native engine missed.
|
|
993
684
|
*
|
|
994
|
-
*
|
|
995
|
-
*
|
|
685
|
+
* Seeds seenByPair from the current allEdgeRows snapshot to avoid duplicating
|
|
686
|
+
* edges the native engine already produced.
|
|
996
687
|
*/
|
|
997
688
|
function buildChaPostPass(
|
|
998
689
|
ctx: PipelineContext,
|
|
@@ -1026,6 +717,7 @@ function buildChaPostPass(
|
|
|
1026
717
|
|
|
1027
718
|
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
1028
719
|
let chaTargets: ReadonlyArray<{ id: number; file: string }> = [];
|
|
720
|
+
let isTypedReceiverDispatch = false;
|
|
1029
721
|
|
|
1030
722
|
if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
|
|
1031
723
|
chaTargets = resolveThisDispatch(
|
|
@@ -1034,6 +726,7 @@ function buildChaPostPass(
|
|
|
1034
726
|
call.receiver,
|
|
1035
727
|
chaCtx,
|
|
1036
728
|
lookup,
|
|
729
|
+
relPath,
|
|
1037
730
|
);
|
|
1038
731
|
} else {
|
|
1039
732
|
const typeEntry = typeMap.get(call.receiver);
|
|
@@ -1044,16 +737,26 @@ function buildChaPostPass(
|
|
|
1044
737
|
: null;
|
|
1045
738
|
if (typeName) {
|
|
1046
739
|
chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
|
|
740
|
+
isTypedReceiverDispatch = true;
|
|
1047
741
|
}
|
|
1048
742
|
}
|
|
1049
743
|
|
|
1050
744
|
for (const t of chaTargets) {
|
|
1051
745
|
const edgeKey = `${caller.id}|${t.id}`;
|
|
1052
746
|
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
1053
|
-
|
|
747
|
+
// Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
|
|
748
|
+
// — file proximity is not meaningful for virtual dispatch confidence.
|
|
749
|
+
// this/super dispatch keeps computeConfidence-based proximity scoring to
|
|
750
|
+
// match runPostNativeThisDispatch (native-orchestrator.ts).
|
|
751
|
+
const conf = isTypedReceiverDispatch
|
|
752
|
+
? CHA_TYPED_DISPATCH_CONFIDENCE
|
|
753
|
+
: computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
|
|
1054
754
|
if (conf > 0) {
|
|
1055
755
|
seenByPair.add(edgeKey);
|
|
1056
|
-
|
|
756
|
+
// Tag super-dispatch edges distinctly so runChaPostPass can exclude them
|
|
757
|
+
// from further CHA expansion (super calls are not virtual dispatch).
|
|
758
|
+
const technique = call.receiver === 'super' ? 'super-dispatch' : 'cha';
|
|
759
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique, null]);
|
|
1057
760
|
}
|
|
1058
761
|
}
|
|
1059
762
|
}
|
|
@@ -1141,6 +844,16 @@ function buildCallEdgesJS(
|
|
|
1141
844
|
|
|
1142
845
|
const seenCallEdges = new Set<string>();
|
|
1143
846
|
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
847
|
+
// Build the import-artifact name set: importedNames plus CJS require bindings.
|
|
848
|
+
// Used only by resolveReceiverEdge to distinguish local definitions from CJS
|
|
849
|
+
// import shadows — does NOT affect call-target resolution or DB edges (#1661).
|
|
850
|
+
const importArtifactNames = buildImportArtifactNames(
|
|
851
|
+
importedNames,
|
|
852
|
+
symbols,
|
|
853
|
+
ctx,
|
|
854
|
+
relPath,
|
|
855
|
+
rootDir,
|
|
856
|
+
);
|
|
1144
857
|
|
|
1145
858
|
buildFileCallEdges(
|
|
1146
859
|
relPath,
|
|
@@ -1153,6 +866,7 @@ function buildCallEdgesJS(
|
|
|
1153
866
|
typeMap,
|
|
1154
867
|
ptsMap,
|
|
1155
868
|
chaCtx,
|
|
869
|
+
importArtifactNames,
|
|
1156
870
|
);
|
|
1157
871
|
buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows);
|
|
1158
872
|
}
|
|
@@ -1197,6 +911,38 @@ function buildImportedNamesMap(
|
|
|
1197
911
|
return importedNames;
|
|
1198
912
|
}
|
|
1199
913
|
|
|
914
|
+
/**
|
|
915
|
+
* Build a map of all names that are import artifacts in this file — includes
|
|
916
|
+
* both ES module imports (already in importedNames) and CJS require destructuring
|
|
917
|
+
* bindings (`const { X } = require('./path')`). Used exclusively by resolveReceiverEdge
|
|
918
|
+
* to classify same-file function-kind nodes as import artifacts vs. local definitions.
|
|
919
|
+
* Does NOT affect call resolution or DB edge creation (#1661).
|
|
920
|
+
*/
|
|
921
|
+
function buildImportArtifactNames(
|
|
922
|
+
importedNames: Map<string, string>,
|
|
923
|
+
symbols: ExtractorOutput,
|
|
924
|
+
ctx: PipelineContext,
|
|
925
|
+
relPath: string,
|
|
926
|
+
rootDir: string,
|
|
927
|
+
): ReadonlyMap<string, string> {
|
|
928
|
+
if (!symbols.cjsRequireBindings?.length) return importedNames;
|
|
929
|
+
const combined = new Map(importedNames);
|
|
930
|
+
const traceBarrel = (resolvedPath: string, cleanName: string): string => {
|
|
931
|
+
if (!isBarrelFile(ctx, resolvedPath)) return resolvedPath;
|
|
932
|
+
const actual = resolveBarrelExportCached(ctx, resolvedPath, cleanName);
|
|
933
|
+
return actual ?? resolvedPath;
|
|
934
|
+
};
|
|
935
|
+
for (const binding of symbols.cjsRequireBindings) {
|
|
936
|
+
const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), binding.source);
|
|
937
|
+
for (const name of binding.names) {
|
|
938
|
+
if (!combined.has(name)) {
|
|
939
|
+
combined.set(name, traceBarrel(resolvedPath, name));
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return combined;
|
|
944
|
+
}
|
|
945
|
+
|
|
1200
946
|
function makeContextLookup(ctx: PipelineContext, getNodeIdStmt: NodeIdStmt): CallNodeLookup {
|
|
1201
947
|
return {
|
|
1202
948
|
byNameAndFile: (name, file) => ctx.nodesByNameAndFile.get(`${name}|${file}`) ?? [],
|
|
@@ -1293,6 +1039,509 @@ function buildDefinitionParamsMap(
|
|
|
1293
1039
|
return map;
|
|
1294
1040
|
}
|
|
1295
1041
|
|
|
1042
|
+
// ── Per-call resolution helpers ─────────────────────────────────────────
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Resolve targets for a single call site with all JS-path fallbacks applied.
|
|
1046
|
+
*
|
|
1047
|
+
* Runs in order:
|
|
1048
|
+
* 1. Primary resolution via `resolveCallTargets` (importedNames + typeMap).
|
|
1049
|
+
* 2. Same-class `this.method()` fallback (non-super receivers only).
|
|
1050
|
+
* 3. Same-class bare-call fallback for non-JS/TS class-scoped languages.
|
|
1051
|
+
* 4. Object.defineProperty accessor fallback (this-calls inside getter/setter).
|
|
1052
|
+
*
|
|
1053
|
+
* Returns the resolved targets array and the importedFrom hint for confidence scoring.
|
|
1054
|
+
*/
|
|
1055
|
+
function resolveFallbackTargets(
|
|
1056
|
+
call: Call,
|
|
1057
|
+
caller: { id: number; callerName: string | null },
|
|
1058
|
+
relPath: string,
|
|
1059
|
+
importedNames: Map<string, string>,
|
|
1060
|
+
lookup: CallNodeLookup,
|
|
1061
|
+
typeMap: Map<string, TypeMapEntry | string>,
|
|
1062
|
+
definePropertyReceivers: Map<string, string> | undefined,
|
|
1063
|
+
): {
|
|
1064
|
+
targets: ReadonlyArray<{ id: number; file: string; kind?: string }>;
|
|
1065
|
+
importedFrom: string | null | undefined;
|
|
1066
|
+
} {
|
|
1067
|
+
// RES-4: Kotlin member callable reference — `Greeter::greet` emits
|
|
1068
|
+
// { name: 'greet', receiver: 'Greeter', dynamicKind: 'reflection' }.
|
|
1069
|
+
// The receiver is the class qualifier (not a typeMap variable), so
|
|
1070
|
+
// resolveCallTargets would find a same-named top-level function via
|
|
1071
|
+
// byNameAndFile('greet', relPath) before the qualified form is tried.
|
|
1072
|
+
// Prefer `Greeter.greet` in the same file first; fall through to the
|
|
1073
|
+
// normal path only when no qualified match exists.
|
|
1074
|
+
let preQualifiedTargets: ReadonlyArray<{ id: number; file: string; kind?: string }> = [];
|
|
1075
|
+
if (
|
|
1076
|
+
call.dynamicKind === 'reflection' &&
|
|
1077
|
+
call.receiver &&
|
|
1078
|
+
!call.keyExpr &&
|
|
1079
|
+
!isModuleScopedLanguage(relPath)
|
|
1080
|
+
) {
|
|
1081
|
+
preQualifiedTargets = lookup
|
|
1082
|
+
.byNameAndFile(`${call.receiver}.${call.name}`, relPath)
|
|
1083
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
let { targets, importedFrom } =
|
|
1087
|
+
preQualifiedTargets.length > 0
|
|
1088
|
+
? {
|
|
1089
|
+
targets: preQualifiedTargets as Array<{ id: number; file: string; kind?: string }>,
|
|
1090
|
+
importedFrom: undefined as string | undefined,
|
|
1091
|
+
}
|
|
1092
|
+
: resolveCallTargets(
|
|
1093
|
+
lookup,
|
|
1094
|
+
call,
|
|
1095
|
+
relPath,
|
|
1096
|
+
importedNames,
|
|
1097
|
+
typeMap as Map<string, unknown>,
|
|
1098
|
+
caller.callerName,
|
|
1099
|
+
);
|
|
1100
|
+
|
|
1101
|
+
// Same-class `this.method()` fallback: when the call receiver is `this` and
|
|
1102
|
+
// resolveCallTargets found nothing, derive the enclosing class name from the
|
|
1103
|
+
// caller (e.g. `Logger.info` → class prefix `Logger`) and retry with the
|
|
1104
|
+
// qualified method name `Logger._write`. This mirrors what the native Rust
|
|
1105
|
+
// engine does implicitly via its class-scoped symbol table.
|
|
1106
|
+
// NOTE: restricted to `this` only — `super.method()` targets a parent class,
|
|
1107
|
+
// not the enclosing class, so qualifying with the child class name would
|
|
1108
|
+
// produce a false edge when the child also defines a same-named method.
|
|
1109
|
+
if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
|
|
1110
|
+
const lastDot = caller.callerName.lastIndexOf('.');
|
|
1111
|
+
if (lastDot > 0) {
|
|
1112
|
+
const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
|
|
1113
|
+
const className = caller.callerName.slice(prevDot + 1, lastDot);
|
|
1114
|
+
const qualified = lookup
|
|
1115
|
+
.byNameAndFile(`${className}.${call.name}`, relPath)
|
|
1116
|
+
.filter((n) => n.kind === 'method');
|
|
1117
|
+
if (qualified.length > 0) targets = qualified;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Same-class bare-call fallback: when a no-receiver call can't be resolved
|
|
1122
|
+
// globally, try the caller's own class as a qualifier. Handles C# static
|
|
1123
|
+
// sibling calls: `IsValidEmail()` inside `Validators.ValidateUser` resolves
|
|
1124
|
+
// to `Validators.IsValidEmail`. Skipped for JS/TS where bare calls are
|
|
1125
|
+
// module-scoped, not class-scoped.
|
|
1126
|
+
if (
|
|
1127
|
+
targets.length === 0 &&
|
|
1128
|
+
!call.receiver &&
|
|
1129
|
+
caller.callerName != null &&
|
|
1130
|
+
!isModuleScopedLanguage(relPath)
|
|
1131
|
+
) {
|
|
1132
|
+
const lastDot = caller.callerName.lastIndexOf('.');
|
|
1133
|
+
if (lastDot > 0) {
|
|
1134
|
+
const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
|
|
1135
|
+
const className = caller.callerName.slice(prevDot + 1, lastDot);
|
|
1136
|
+
const qualified = lookup
|
|
1137
|
+
.byNameAndFile(`${className}.${call.name}`, relPath)
|
|
1138
|
+
.filter((n) => n.kind === 'method');
|
|
1139
|
+
if (qualified.length > 0) targets = qualified;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// RES-3: reflection with literal method name — JVM getMethod("name") / invokeMethod("name").
|
|
1144
|
+
// Java/Scala/Groovy methods are stored as class-qualified names (e.g. Reflection.greet),
|
|
1145
|
+
// so lookup.byNameAndFile('greet', relPath) finds nothing. When dynamicKind='reflection'
|
|
1146
|
+
// and keyExpr is set (a string-literal method name was captured), try the qualified form:
|
|
1147
|
+
// 1. typeMap[receiver] → resolvedType → lookup `resolvedType.keyExpr` (type-annotated local)
|
|
1148
|
+
// 2. callerName class prefix → `CallerClass.keyExpr` (same-class sibling, e.g. Groovy obj)
|
|
1149
|
+
// Scoped to non-JS/TS files to avoid interfering with the JS reflection path.
|
|
1150
|
+
if (
|
|
1151
|
+
targets.length === 0 &&
|
|
1152
|
+
call.dynamicKind === 'reflection' &&
|
|
1153
|
+
call.keyExpr &&
|
|
1154
|
+
call.receiver &&
|
|
1155
|
+
!isModuleScopedLanguage(relPath)
|
|
1156
|
+
) {
|
|
1157
|
+
const typeEntry = typeMap.get(call.receiver);
|
|
1158
|
+
const resolvedType = typeEntry
|
|
1159
|
+
? typeof typeEntry === 'string'
|
|
1160
|
+
? typeEntry
|
|
1161
|
+
: (typeEntry as { type?: string }).type
|
|
1162
|
+
: null;
|
|
1163
|
+
if (resolvedType) {
|
|
1164
|
+
const qualified = lookup
|
|
1165
|
+
.byNameAndFile(`${resolvedType}.${call.keyExpr}`, relPath)
|
|
1166
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
1167
|
+
if (qualified.length > 0) targets = qualified;
|
|
1168
|
+
}
|
|
1169
|
+
if (targets.length === 0 && caller.callerName != null) {
|
|
1170
|
+
const lastDot = caller.callerName.lastIndexOf('.');
|
|
1171
|
+
if (lastDot > 0) {
|
|
1172
|
+
const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
|
|
1173
|
+
const callerClass = caller.callerName.slice(prevDot + 1, lastDot);
|
|
1174
|
+
const qualified = lookup
|
|
1175
|
+
.byNameAndFile(`${callerClass}.${call.keyExpr}`, relPath)
|
|
1176
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
1177
|
+
if (qualified.length > 0) targets = qualified;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Object.defineProperty accessor fallback: when a function is registered as
|
|
1183
|
+
// a getter/setter via `Object.defineProperty(obj, "bar", { get: getter })`,
|
|
1184
|
+
// calls to `this.X()` inside `getter` resolve against `obj` (this === obj
|
|
1185
|
+
// when the accessor is invoked). If the same-class fallback above found
|
|
1186
|
+
// nothing, try treating `obj` as the receiver and look up `obj.X` in the
|
|
1187
|
+
// typeMap, or fall back to a same-file lookup of any definition named X
|
|
1188
|
+
// that belongs to the object literal or its type.
|
|
1189
|
+
if (
|
|
1190
|
+
targets.length === 0 &&
|
|
1191
|
+
call.receiver === 'this' &&
|
|
1192
|
+
caller.callerName != null &&
|
|
1193
|
+
definePropertyReceivers
|
|
1194
|
+
) {
|
|
1195
|
+
const receiverVarName = definePropertyReceivers.get(caller.callerName);
|
|
1196
|
+
if (receiverVarName) {
|
|
1197
|
+
const typeEntry = typeMap.get(receiverVarName);
|
|
1198
|
+
const typeName = typeEntry
|
|
1199
|
+
? typeof typeEntry === 'string'
|
|
1200
|
+
? typeEntry
|
|
1201
|
+
: (typeEntry as { type?: string }).type
|
|
1202
|
+
: null;
|
|
1203
|
+
if (typeName) {
|
|
1204
|
+
const qualified = lookup.byNameAndFile(`${typeName}.${call.name}`, relPath);
|
|
1205
|
+
if (qualified.length > 0) targets = [...qualified];
|
|
1206
|
+
}
|
|
1207
|
+
// If still no targets, search for any definition named `call.name` in
|
|
1208
|
+
// the same file — handles plain object literals where the method isn't
|
|
1209
|
+
// qualified (e.g. `const obj = { baz() {} }` defines `baz` directly).
|
|
1210
|
+
// Note: this is intentionally broad — it matches any same-file definition
|
|
1211
|
+
// with the called name, not just members of the receiver object. This is
|
|
1212
|
+
// the same behaviour used by the native post-pass path (buildDefinePropertyPostPass).
|
|
1213
|
+
if (targets.length === 0) {
|
|
1214
|
+
const sameFile = lookup.byNameAndFile(call.name, relPath);
|
|
1215
|
+
if (sameFile.length > 0) targets = [...sameFile];
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return { targets, importedFrom };
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Emit direct-call edges for the resolved targets of a single call site.
|
|
1225
|
+
*
|
|
1226
|
+
* Sorts targets by confidence descending first, then for each target:
|
|
1227
|
+
* - Skips self-edges and already-seen edges.
|
|
1228
|
+
* - If a pts edge already exists for this pair, upgrades it in-place to
|
|
1229
|
+
* direct-call confidence and promotes to seenCallEdges.
|
|
1230
|
+
* - If a dyn=0 edge already exists and the incoming call has an explicit
|
|
1231
|
+
* dynamicKind (e.g. 'reflection' for bare decorators), upgrades the
|
|
1232
|
+
* existing row to dyn=1 in-place so the semantic classification wins.
|
|
1233
|
+
* - Otherwise records a new `calls` edge with `ts-native` technique.
|
|
1234
|
+
*/
|
|
1235
|
+
function emitDirectCallEdgesForCall(
|
|
1236
|
+
caller: { id: number },
|
|
1237
|
+
targets: ReadonlyArray<{ id: number; file: string }>,
|
|
1238
|
+
importedFrom: string | null | undefined,
|
|
1239
|
+
isDynamic: number,
|
|
1240
|
+
hasDynamicKind: boolean,
|
|
1241
|
+
relPath: string,
|
|
1242
|
+
seenCallEdges: Set<string>,
|
|
1243
|
+
ptsEdgeRows: Map<string, number>,
|
|
1244
|
+
allEdgeRows: EdgeRowTuple[],
|
|
1245
|
+
dynZeroEdgeRows?: Map<string, number>,
|
|
1246
|
+
): void {
|
|
1247
|
+
// Sort targets by confidence descending before emitting edges.
|
|
1248
|
+
// For multi-target calls with duplicate (source_id, target_id) pairs the
|
|
1249
|
+
// stored confidence depends on which duplicate is processed last — sorting
|
|
1250
|
+
// here guarantees the highest-confidence target wins on dedup, matching the
|
|
1251
|
+
// native engine's sort_targets_by_confidence call in build_edges.rs.
|
|
1252
|
+
const sorted =
|
|
1253
|
+
targets.length > 1
|
|
1254
|
+
? [...targets].sort(
|
|
1255
|
+
(a, b) =>
|
|
1256
|
+
computeConfidence(relPath, b.file, importedFrom ?? null) -
|
|
1257
|
+
computeConfidence(relPath, a.file, importedFrom ?? null),
|
|
1258
|
+
)
|
|
1259
|
+
: targets;
|
|
1260
|
+
|
|
1261
|
+
for (const t of sorted) {
|
|
1262
|
+
const edgeKey = `${caller.id}|${t.id}`;
|
|
1263
|
+
if (t.id === caller.id) continue;
|
|
1264
|
+
const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
|
|
1265
|
+
if (seenCallEdges.has(edgeKey)) {
|
|
1266
|
+
// Edge already emitted. If the incoming call carries an explicit semantic
|
|
1267
|
+
// dynamic classification (dynamicKind set — e.g. 'reflection' for bare
|
|
1268
|
+
// decorators) and the existing edge was recorded with dyn=0, upgrade it
|
|
1269
|
+
// in-place so the more specific classification wins.
|
|
1270
|
+
// Generic dynamic=true without dynamicKind (alias/callback calls) does
|
|
1271
|
+
// NOT override dyn=0 to avoid false positives on f.call/f.bind patterns.
|
|
1272
|
+
if (isDynamic === 1 && hasDynamicKind && dynZeroEdgeRows) {
|
|
1273
|
+
const dynZeroIdx = dynZeroEdgeRows.get(edgeKey);
|
|
1274
|
+
if (dynZeroIdx !== undefined) {
|
|
1275
|
+
const row = allEdgeRows[dynZeroIdx];
|
|
1276
|
+
if (row) row[4] = 1;
|
|
1277
|
+
dynZeroEdgeRows.delete(edgeKey);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
const ptsIdx = ptsEdgeRows.get(edgeKey);
|
|
1283
|
+
if (ptsIdx !== undefined) {
|
|
1284
|
+
// A pts-resolved edge already exists for this caller→target pair with a
|
|
1285
|
+
// penalised confidence. Upgrade it to the direct-call confidence in-place,
|
|
1286
|
+
// then promote to seenCallEdges so no further processing is needed.
|
|
1287
|
+
const ptsRow = allEdgeRows[ptsIdx];
|
|
1288
|
+
if (ptsRow) {
|
|
1289
|
+
ptsRow[3] = confidence;
|
|
1290
|
+
ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
|
|
1291
|
+
ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
|
|
1292
|
+
}
|
|
1293
|
+
ptsEdgeRows.delete(edgeKey);
|
|
1294
|
+
seenCallEdges.add(edgeKey);
|
|
1295
|
+
} else {
|
|
1296
|
+
seenCallEdges.add(edgeKey);
|
|
1297
|
+
const newIdx = allEdgeRows.length;
|
|
1298
|
+
allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic, 'ts-native', null]);
|
|
1299
|
+
// Track dyn=0 edges so a later dyn=1+dynamicKind call for the same pair
|
|
1300
|
+
// can upgrade them (e.g. bare decorator after call-expression decorator).
|
|
1301
|
+
if (isDynamic === 0 && dynZeroEdgeRows) {
|
|
1302
|
+
dynZeroEdgeRows.set(edgeKey, newIdx);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* Phase 8.3 / 8.3c / bind: emit pts-resolved edges for unresolved no-receiver calls.
|
|
1310
|
+
*
|
|
1311
|
+
* Fires for three cases:
|
|
1312
|
+
* (a) dynamic=true: alias calls emitted by extractCallbackReferenceCalls.
|
|
1313
|
+
* Looks up `call.name` directly (alias entries are flat-keyed).
|
|
1314
|
+
* (b) non-dynamic: parameter variable calls (fn() where fn is a param).
|
|
1315
|
+
* Looks up the scoped key `callerName::call.name` to avoid spurious
|
|
1316
|
+
* edges from same-named parameters across different functions.
|
|
1317
|
+
* (c) non-dynamic: module-level alias bindings — `f = fn.bind(ctx)` or
|
|
1318
|
+
* `const f = handler` — where pts('f') was seeded by fnRefBindings.
|
|
1319
|
+
* Checked against fnRefBindingLhs so case (c) only fires for genuine
|
|
1320
|
+
* bind/alias entries and never for self-seeded local definitions.
|
|
1321
|
+
*
|
|
1322
|
+
* Pts edges are added to ptsEdgeRows (not seenCallEdges) so that a later
|
|
1323
|
+
* direct call to the same target can upgrade confidence rather than being
|
|
1324
|
+
* silently dropped by the dedup guard.
|
|
1325
|
+
*/
|
|
1326
|
+
function emitPtsNoReceiverEdges(
|
|
1327
|
+
call: Call,
|
|
1328
|
+
caller: { id: number; callerName: string | null },
|
|
1329
|
+
isDynamic: number,
|
|
1330
|
+
relPath: string,
|
|
1331
|
+
importedNames: Map<string, string>,
|
|
1332
|
+
lookup: CallNodeLookup,
|
|
1333
|
+
typeMap: Map<string, TypeMapEntry | string>,
|
|
1334
|
+
ptsMap: PointsToMap,
|
|
1335
|
+
fnRefBindingLhs: Set<string>,
|
|
1336
|
+
seenCallEdges: Set<string>,
|
|
1337
|
+
ptsEdgeRows: Map<string, number>,
|
|
1338
|
+
allEdgeRows: EdgeRowTuple[],
|
|
1339
|
+
): void {
|
|
1340
|
+
const scopedPtsKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
|
|
1341
|
+
// Module-level calls (callerName === null) use the '<module>' sentinel emitted by
|
|
1342
|
+
// extractSpreadForOfWalk for top-level for-of loops. Look it up as a fallback so
|
|
1343
|
+
// that `for (const f of arr) { f(); }` at module scope resolves correctly.
|
|
1344
|
+
const modulePtsKey =
|
|
1345
|
+
caller.callerName === null && ptsMap.has(`<module>::${call.name}`)
|
|
1346
|
+
? `<module>::${call.name}`
|
|
1347
|
+
: null;
|
|
1348
|
+
const flatPtsKey =
|
|
1349
|
+
!call.dynamic && fnRefBindingLhs.has(call.name) && ptsMap.has(call.name) ? call.name : null;
|
|
1350
|
+
|
|
1351
|
+
if (
|
|
1352
|
+
!(
|
|
1353
|
+
call.dynamic ||
|
|
1354
|
+
(scopedPtsKey != null && ptsMap.has(scopedPtsKey)) ||
|
|
1355
|
+
modulePtsKey != null ||
|
|
1356
|
+
flatPtsKey != null
|
|
1357
|
+
)
|
|
1358
|
+
)
|
|
1359
|
+
return;
|
|
1360
|
+
|
|
1361
|
+
const ptsLookupName = call.dynamic
|
|
1362
|
+
? call.name
|
|
1363
|
+
: scopedPtsKey != null && ptsMap.has(scopedPtsKey)
|
|
1364
|
+
? scopedPtsKey
|
|
1365
|
+
: modulePtsKey != null
|
|
1366
|
+
? modulePtsKey
|
|
1367
|
+
: // flatPtsKey != null is guaranteed: if neither call.dynamic nor scopedPtsKey
|
|
1368
|
+
// nor modulePtsKey matched, flatPtsKey must be non-null.
|
|
1369
|
+
flatPtsKey!;
|
|
1370
|
+
|
|
1371
|
+
for (const alias of resolveViaPointsTo(ptsLookupName, ptsMap)) {
|
|
1372
|
+
// Resolve the concrete alias target. Only `name` is needed here — receiver
|
|
1373
|
+
// and line are not relevant for alias resolution (we are looking up the
|
|
1374
|
+
// aliased function by name, not dispatching a method call).
|
|
1375
|
+
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
|
|
1376
|
+
lookup,
|
|
1377
|
+
{ name: alias },
|
|
1378
|
+
relPath,
|
|
1379
|
+
importedNames,
|
|
1380
|
+
typeMap as Map<string, unknown>,
|
|
1381
|
+
);
|
|
1382
|
+
const sortedAliasTargets =
|
|
1383
|
+
aliasTargets.length > 1
|
|
1384
|
+
? [...aliasTargets].sort(
|
|
1385
|
+
(a, b) =>
|
|
1386
|
+
computeConfidence(relPath, b.file, aliasFrom ?? null) -
|
|
1387
|
+
computeConfidence(relPath, a.file, aliasFrom ?? null),
|
|
1388
|
+
)
|
|
1389
|
+
: aliasTargets;
|
|
1390
|
+
for (const t of sortedAliasTargets) {
|
|
1391
|
+
const edgeKey = `${caller.id}|${t.id}`;
|
|
1392
|
+
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1393
|
+
const conf =
|
|
1394
|
+
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
1395
|
+
if (conf > 0) {
|
|
1396
|
+
ptsEdgeRows.set(edgeKey, allEdgeRows.length);
|
|
1397
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to', null]);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/**
|
|
1405
|
+
* Phase 8.3f: emit pts-resolved edges for unresolved receiver calls via
|
|
1406
|
+
* object-rest param bindings.
|
|
1407
|
+
*
|
|
1408
|
+
* Fires when `rest.prop()` is encountered and `rest` was seeded as
|
|
1409
|
+
* `pts["rest.prop"]` by the object-rest dispatch chain
|
|
1410
|
+
* (ObjectRestParamBinding + paramBinding + ObjectPropBinding).
|
|
1411
|
+
*/
|
|
1412
|
+
function emitPtsReceiverEdges(
|
|
1413
|
+
call: Call,
|
|
1414
|
+
caller: { id: number; callerName: string | null },
|
|
1415
|
+
isDynamic: number,
|
|
1416
|
+
relPath: string,
|
|
1417
|
+
importedNames: Map<string, string>,
|
|
1418
|
+
lookup: CallNodeLookup,
|
|
1419
|
+
typeMap: Map<string, TypeMapEntry | string>,
|
|
1420
|
+
ptsMap: PointsToMap,
|
|
1421
|
+
seenCallEdges: Set<string>,
|
|
1422
|
+
ptsEdgeRows: Map<string, number>,
|
|
1423
|
+
allEdgeRows: EdgeRowTuple[],
|
|
1424
|
+
): void {
|
|
1425
|
+
const receiverKey = `${call.receiver}.${call.name}`;
|
|
1426
|
+
if (!ptsMap.has(receiverKey)) return;
|
|
1427
|
+
|
|
1428
|
+
for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
|
|
1429
|
+
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
|
|
1430
|
+
lookup,
|
|
1431
|
+
{ name: alias },
|
|
1432
|
+
relPath,
|
|
1433
|
+
importedNames,
|
|
1434
|
+
typeMap as Map<string, unknown>,
|
|
1435
|
+
);
|
|
1436
|
+
const sortedAliasTargets =
|
|
1437
|
+
aliasTargets.length > 1
|
|
1438
|
+
? [...aliasTargets].sort(
|
|
1439
|
+
(a, b) =>
|
|
1440
|
+
computeConfidence(relPath, b.file, aliasFrom ?? null) -
|
|
1441
|
+
computeConfidence(relPath, a.file, aliasFrom ?? null),
|
|
1442
|
+
)
|
|
1443
|
+
: aliasTargets;
|
|
1444
|
+
for (const t of sortedAliasTargets) {
|
|
1445
|
+
const edgeKey = `${caller.id}|${t.id}`;
|
|
1446
|
+
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1447
|
+
const conf =
|
|
1448
|
+
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
1449
|
+
if (conf > 0) {
|
|
1450
|
+
ptsEdgeRows.set(edgeKey, allEdgeRows.length);
|
|
1451
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to', null]);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Phase 8.5: emit CHA + RTA dispatch edges for a single call site.
|
|
1460
|
+
*
|
|
1461
|
+
* For `this`/`self`/`super` calls: resolve through the class hierarchy.
|
|
1462
|
+
* For typed receiver calls: expand to all instantiated concrete implementations.
|
|
1463
|
+
*/
|
|
1464
|
+
function emitChaCallEdgesForCall(
|
|
1465
|
+
call: Call,
|
|
1466
|
+
caller: { id: number; callerName: string | null },
|
|
1467
|
+
relPath: string,
|
|
1468
|
+
typeMap: Map<string, TypeMapEntry | string>,
|
|
1469
|
+
lookup: CallNodeLookup,
|
|
1470
|
+
chaCtx: ChaContext,
|
|
1471
|
+
seenCallEdges: Set<string>,
|
|
1472
|
+
ptsEdgeRows: Map<string, number>,
|
|
1473
|
+
allEdgeRows: EdgeRowTuple[],
|
|
1474
|
+
): void {
|
|
1475
|
+
let chaTargets: ReadonlyArray<{ id: number; file: string }> = [];
|
|
1476
|
+
let isTypedReceiverDispatch = false;
|
|
1477
|
+
|
|
1478
|
+
if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
|
|
1479
|
+
chaTargets = resolveThisDispatch(
|
|
1480
|
+
call.name,
|
|
1481
|
+
caller.callerName,
|
|
1482
|
+
call.receiver,
|
|
1483
|
+
chaCtx,
|
|
1484
|
+
lookup,
|
|
1485
|
+
relPath,
|
|
1486
|
+
);
|
|
1487
|
+
} else if (!BUILTIN_RECEIVERS.has(call.receiver!)) {
|
|
1488
|
+
const typeEntry = typeMap.get(call.receiver!);
|
|
1489
|
+
const typeName = typeEntry
|
|
1490
|
+
? typeof typeEntry === 'string'
|
|
1491
|
+
? typeEntry
|
|
1492
|
+
: (typeEntry as { type?: string }).type
|
|
1493
|
+
: null;
|
|
1494
|
+
if (typeName) {
|
|
1495
|
+
chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
|
|
1496
|
+
isTypedReceiverDispatch = true;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
for (const t of chaTargets) {
|
|
1501
|
+
const edgeKey = `${caller.id}|${t.id}`;
|
|
1502
|
+
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1503
|
+
// Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
|
|
1504
|
+
// — file proximity is not meaningful for virtual dispatch confidence.
|
|
1505
|
+
// this/super dispatch keeps computeConfidence-based proximity scoring to
|
|
1506
|
+
// match runPostNativeThisDispatch (native-orchestrator.ts).
|
|
1507
|
+
const conf = isTypedReceiverDispatch
|
|
1508
|
+
? CHA_TYPED_DISPATCH_CONFIDENCE
|
|
1509
|
+
: computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
|
|
1510
|
+
if (conf > 0) {
|
|
1511
|
+
seenCallEdges.add(edgeKey);
|
|
1512
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha', null]);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Dynamic kinds that cannot be resolved statically — emit a sink edge to the
|
|
1520
|
+
* file node instead of silently dropping the call site. confidence=0.0 keeps
|
|
1521
|
+
* these below DEFAULT_MIN_CONFIDENCE so they never appear in normal query results.
|
|
1522
|
+
* Includes reflection so that Reflect.apply/getMethod/callable-ref calls whose
|
|
1523
|
+
* target is not found in the codebase still produce a visible sink edge.
|
|
1524
|
+
*/
|
|
1525
|
+
const FLAG_ONLY_KINDS: ReadonlySet<DynamicKind> = new Set([
|
|
1526
|
+
'eval',
|
|
1527
|
+
'computed-key',
|
|
1528
|
+
'reflection',
|
|
1529
|
+
'unresolved-dynamic',
|
|
1530
|
+
]);
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Build call edges for all calls in a single file (WASM/JS engine path).
|
|
1534
|
+
*
|
|
1535
|
+
* Iterates over `symbols.calls` and dispatches each call through the full
|
|
1536
|
+
* JS resolution cascade:
|
|
1537
|
+
* 1. `resolveFallbackTargets` — primary + class-fallback + defineProperty fallback
|
|
1538
|
+
* 2. `emitDirectCallEdgesForCall` — emit direct-call edges (upgrading any pts pair)
|
|
1539
|
+
* 3. `emitPtsNoReceiverEdges` — Phase 8.3/8.3c pts fallback for no-receiver calls
|
|
1540
|
+
* 4. `emitPtsReceiverEdges` — Phase 8.3f pts fallback for rest-param receiver calls
|
|
1541
|
+
* 5. Inline `resolveReceiverEdge` — emit `receiver` edge for external receivers
|
|
1542
|
+
* 6. `emitChaCallEdgesForCall` — Phase 8.5 CHA + RTA dispatch expansion
|
|
1543
|
+
* 7. Sink edge for flag-only dynamic kinds (eval, computed-key, reflection, unresolved-dynamic)
|
|
1544
|
+
*/
|
|
1296
1545
|
function buildFileCallEdges(
|
|
1297
1546
|
relPath: string,
|
|
1298
1547
|
symbols: ExtractorOutput,
|
|
@@ -1304,6 +1553,7 @@ function buildFileCallEdges(
|
|
|
1304
1553
|
typeMap: Map<string, TypeMapEntry | string>,
|
|
1305
1554
|
ptsMap?: PointsToMap | null,
|
|
1306
1555
|
chaCtx?: ChaContext,
|
|
1556
|
+
importArtifactNames?: ReadonlyMap<string, string>,
|
|
1307
1557
|
): void {
|
|
1308
1558
|
// Tracks edges that were inserted by the pts fallback (edgeKey → allEdgeRows index).
|
|
1309
1559
|
// Kept separate from seenCallEdges so that a subsequent direct-call edge for the same
|
|
@@ -1312,6 +1562,12 @@ function buildFileCallEdges(
|
|
|
1312
1562
|
// no longer tracked here.
|
|
1313
1563
|
const ptsEdgeRows = new Map<string, number>();
|
|
1314
1564
|
|
|
1565
|
+
// Tracks direct-call edges emitted with dyn=0 (edgeKey → allEdgeRows index).
|
|
1566
|
+
// When a later call to the same target has dyn=1 (e.g. a bare decorator `@Log`
|
|
1567
|
+
// processed after the call-expression `@Log()` in the query path), the existing
|
|
1568
|
+
// dyn=0 row is upgraded in-place so the more specific dynamic classification wins.
|
|
1569
|
+
const dynZeroEdgeRows = new Map<string, number>();
|
|
1570
|
+
|
|
1315
1571
|
// Pre-compute the set of names that appear as lhs in fnRefBindings so that
|
|
1316
1572
|
// case (c) of the pts gate below only fires for names that are genuine
|
|
1317
1573
|
// bind/alias entries, not for every locally-defined function or import that
|
|
@@ -1323,205 +1579,51 @@ function buildFileCallEdges(
|
|
|
1323
1579
|
|
|
1324
1580
|
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
1325
1581
|
const isDynamic: number = call.dynamic ? 1 : 0;
|
|
1326
|
-
|
|
1327
|
-
|
|
1582
|
+
|
|
1583
|
+
// Step 1: Resolve targets with all JS-path fallbacks.
|
|
1584
|
+
const { targets, importedFrom } = resolveFallbackTargets(
|
|
1328
1585
|
call,
|
|
1586
|
+
caller,
|
|
1329
1587
|
relPath,
|
|
1330
1588
|
importedNames,
|
|
1331
|
-
|
|
1332
|
-
|
|
1589
|
+
lookup,
|
|
1590
|
+
typeMap,
|
|
1591
|
+
symbols.definePropertyReceivers,
|
|
1333
1592
|
);
|
|
1334
1593
|
|
|
1335
|
-
//
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
const qualifiedName = `${className}.${call.name}`;
|
|
1349
|
-
const qualified = lookup
|
|
1350
|
-
.byNameAndFile(qualifiedName, relPath)
|
|
1351
|
-
.filter((n) => n.kind === 'method');
|
|
1352
|
-
if (qualified.length > 0) {
|
|
1353
|
-
targets = qualified;
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
// Same-class bare-call fallback: when a no-receiver call can't be resolved
|
|
1359
|
-
// globally, try the caller's own class as a qualifier. Handles C# static
|
|
1360
|
-
// sibling calls: `IsValidEmail()` inside `Validators.ValidateUser` resolves
|
|
1361
|
-
// to `Validators.IsValidEmail`. Skipped for JS/TS where bare calls are
|
|
1362
|
-
// module-scoped, not class-scoped.
|
|
1363
|
-
if (
|
|
1364
|
-
targets.length === 0 &&
|
|
1365
|
-
!call.receiver &&
|
|
1366
|
-
caller.callerName != null &&
|
|
1367
|
-
!isModuleScopedLanguage(relPath)
|
|
1368
|
-
) {
|
|
1369
|
-
const lastDot = caller.callerName.lastIndexOf('.');
|
|
1370
|
-
if (lastDot > 0) {
|
|
1371
|
-
const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
|
|
1372
|
-
const className = caller.callerName.slice(prevDot + 1, lastDot);
|
|
1373
|
-
const qualifiedName = `${className}.${call.name}`;
|
|
1374
|
-
const qualified = lookup
|
|
1375
|
-
.byNameAndFile(qualifiedName, relPath)
|
|
1376
|
-
.filter((n) => n.kind === 'method');
|
|
1377
|
-
if (qualified.length > 0) {
|
|
1378
|
-
targets = qualified;
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
// Object.defineProperty accessor fallback: when a function is registered as
|
|
1384
|
-
// a getter/setter via `Object.defineProperty(obj, "bar", { get: getter })`,
|
|
1385
|
-
// calls to `this.X()` inside `getter` resolve against `obj` (this === obj
|
|
1386
|
-
// when the accessor is invoked). If the same-class fallback above found
|
|
1387
|
-
// nothing, try treating `obj` as the receiver and look up `obj.X` in the
|
|
1388
|
-
// typeMap, or fall back to a same-file lookup of any definition named X
|
|
1389
|
-
// that belongs to the object literal or its type.
|
|
1390
|
-
if (
|
|
1391
|
-
targets.length === 0 &&
|
|
1392
|
-
call.receiver === 'this' &&
|
|
1393
|
-
caller.callerName != null &&
|
|
1394
|
-
symbols.definePropertyReceivers
|
|
1395
|
-
) {
|
|
1396
|
-
const receiverVarName = symbols.definePropertyReceivers.get(caller.callerName);
|
|
1397
|
-
if (receiverVarName) {
|
|
1398
|
-
// Try typeMap lookup for receiver.methodName
|
|
1399
|
-
const typeEntry = typeMap.get(receiverVarName);
|
|
1400
|
-
const typeName = typeEntry
|
|
1401
|
-
? typeof typeEntry === 'string'
|
|
1402
|
-
? typeEntry
|
|
1403
|
-
: (typeEntry as { type?: string }).type
|
|
1404
|
-
: null;
|
|
1405
|
-
if (typeName) {
|
|
1406
|
-
const qualifiedName = `${typeName}.${call.name}`;
|
|
1407
|
-
const qualified = lookup.byNameAndFile(qualifiedName, relPath);
|
|
1408
|
-
if (qualified.length > 0) {
|
|
1409
|
-
targets = [...qualified];
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
// If still no targets, search for any definition named `call.name` in
|
|
1413
|
-
// the same file — handles plain object literals where the method isn't
|
|
1414
|
-
// qualified (e.g. `const obj = { baz() {} }` defines `baz` directly).
|
|
1415
|
-
// Note: this is intentionally broad — it matches any same-file definition
|
|
1416
|
-
// with the called name, not just members of the receiver object. This is
|
|
1417
|
-
// the same behaviour used by the native post-pass path (buildDefinePropertyPostPass).
|
|
1418
|
-
if (targets.length === 0) {
|
|
1419
|
-
const sameFile = lookup.byNameAndFile(call.name, relPath);
|
|
1420
|
-
if (sameFile.length > 0) {
|
|
1421
|
-
targets = [...sameFile];
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
for (const t of targets) {
|
|
1428
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
1429
|
-
if (t.id !== caller.id) {
|
|
1430
|
-
const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
|
|
1431
|
-
if (seenCallEdges.has(edgeKey)) continue;
|
|
1432
|
-
const ptsIdx = ptsEdgeRows.get(edgeKey);
|
|
1433
|
-
if (ptsIdx !== undefined) {
|
|
1434
|
-
// A pts-resolved edge already exists for this caller→target pair with a
|
|
1435
|
-
// penalised confidence. Upgrade it to the direct-call confidence in-place,
|
|
1436
|
-
// then promote to seenCallEdges so no further processing is needed.
|
|
1437
|
-
const ptsRow = allEdgeRows[ptsIdx];
|
|
1438
|
-
if (ptsRow) {
|
|
1439
|
-
ptsRow[3] = confidence;
|
|
1440
|
-
ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
|
|
1441
|
-
ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
|
|
1442
|
-
}
|
|
1443
|
-
ptsEdgeRows.delete(edgeKey);
|
|
1444
|
-
seenCallEdges.add(edgeKey);
|
|
1445
|
-
} else {
|
|
1446
|
-
seenCallEdges.add(edgeKey);
|
|
1447
|
-
allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic, 'ts-native']);
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1594
|
+
// Step 2: Emit direct-call edges (upgrades any pending pts edge in-place).
|
|
1595
|
+
emitDirectCallEdgesForCall(
|
|
1596
|
+
caller,
|
|
1597
|
+
targets,
|
|
1598
|
+
importedFrom,
|
|
1599
|
+
isDynamic,
|
|
1600
|
+
!!call.dynamicKind,
|
|
1601
|
+
relPath,
|
|
1602
|
+
seenCallEdges,
|
|
1603
|
+
ptsEdgeRows,
|
|
1604
|
+
allEdgeRows,
|
|
1605
|
+
dynZeroEdgeRows,
|
|
1606
|
+
);
|
|
1451
1607
|
|
|
1452
|
-
// Phase 8.3
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
// rather than being silently dropped by the dedup guard.
|
|
1469
|
-
const scopedPtsKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
|
|
1470
|
-
// Module-level calls (callerName === null) use the '<module>' sentinel emitted by
|
|
1471
|
-
// extractSpreadForOfWalk for top-level for-of loops. Look it up as a fallback so
|
|
1472
|
-
// that `for (const f of arr) { f(); }` at module scope resolves correctly.
|
|
1473
|
-
const modulePtsKey =
|
|
1474
|
-
caller.callerName === null && ptsMap?.has(`<module>::${call.name}`)
|
|
1475
|
-
? `<module>::${call.name}`
|
|
1476
|
-
: null;
|
|
1477
|
-
const flatPtsKey =
|
|
1478
|
-
!call.dynamic && fnRefBindingLhs.has(call.name) && ptsMap?.has(call.name) ? call.name : null;
|
|
1479
|
-
if (
|
|
1480
|
-
targets.length === 0 &&
|
|
1481
|
-
!call.receiver &&
|
|
1482
|
-
ptsMap &&
|
|
1483
|
-
(call.dynamic ||
|
|
1484
|
-
(scopedPtsKey != null && ptsMap.has(scopedPtsKey)) ||
|
|
1485
|
-
modulePtsKey != null ||
|
|
1486
|
-
flatPtsKey != null)
|
|
1487
|
-
) {
|
|
1488
|
-
const ptsLookupName = call.dynamic
|
|
1489
|
-
? call.name
|
|
1490
|
-
: scopedPtsKey != null && ptsMap.has(scopedPtsKey)
|
|
1491
|
-
? scopedPtsKey
|
|
1492
|
-
: modulePtsKey != null
|
|
1493
|
-
? modulePtsKey
|
|
1494
|
-
: // flatPtsKey != null is guaranteed by the outer if condition: if neither
|
|
1495
|
-
// call.dynamic nor scopedPtsKey nor modulePtsKey matched, flatPtsKey must be non-null.
|
|
1496
|
-
flatPtsKey!;
|
|
1497
|
-
for (const alias of resolveViaPointsTo(ptsLookupName, ptsMap)) {
|
|
1498
|
-
// Resolve the concrete alias target. Only `name` is needed here — receiver
|
|
1499
|
-
// and line are not relevant for alias resolution (we are looking up the
|
|
1500
|
-
// aliased function by name, not dispatching a method call).
|
|
1501
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
|
|
1502
|
-
lookup,
|
|
1503
|
-
{ name: alias },
|
|
1504
|
-
relPath,
|
|
1505
|
-
importedNames,
|
|
1506
|
-
typeMap as Map<string, unknown>,
|
|
1507
|
-
);
|
|
1508
|
-
for (const t of aliasTargets) {
|
|
1509
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
1510
|
-
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1511
|
-
const conf =
|
|
1512
|
-
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
1513
|
-
if (conf > 0) {
|
|
1514
|
-
ptsEdgeRows.set(edgeKey, allEdgeRows.length);
|
|
1515
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1608
|
+
// Step 3: Phase 8.3/8.3c pts fallback for unresolved no-receiver calls.
|
|
1609
|
+
if (targets.length === 0 && !call.receiver && ptsMap) {
|
|
1610
|
+
emitPtsNoReceiverEdges(
|
|
1611
|
+
call,
|
|
1612
|
+
caller,
|
|
1613
|
+
isDynamic,
|
|
1614
|
+
relPath,
|
|
1615
|
+
importedNames,
|
|
1616
|
+
lookup,
|
|
1617
|
+
typeMap,
|
|
1618
|
+
ptsMap,
|
|
1619
|
+
fnRefBindingLhs,
|
|
1620
|
+
seenCallEdges,
|
|
1621
|
+
ptsEdgeRows,
|
|
1622
|
+
allEdgeRows,
|
|
1623
|
+
);
|
|
1520
1624
|
}
|
|
1521
1625
|
|
|
1522
|
-
// Phase 8.3f
|
|
1523
|
-
// Fires when `rest.prop()` is encountered and `rest` was seeded as `pts["rest.prop"]`
|
|
1524
|
-
// by the object-rest dispatch chain (ObjectRestParamBinding + paramBinding + ObjectPropBinding).
|
|
1626
|
+
// Step 4: Phase 8.3f pts fallback for unresolved receiver calls (rest params).
|
|
1525
1627
|
if (
|
|
1526
1628
|
targets.length === 0 &&
|
|
1527
1629
|
call.receiver &&
|
|
@@ -1531,31 +1633,22 @@ function buildFileCallEdges(
|
|
|
1531
1633
|
call.receiver !== 'super' &&
|
|
1532
1634
|
ptsMap
|
|
1533
1635
|
) {
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
const conf =
|
|
1548
|
-
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
1549
|
-
if (conf > 0) {
|
|
1550
|
-
ptsEdgeRows.set(edgeKey, allEdgeRows.length);
|
|
1551
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1636
|
+
emitPtsReceiverEdges(
|
|
1637
|
+
call,
|
|
1638
|
+
caller,
|
|
1639
|
+
isDynamic,
|
|
1640
|
+
relPath,
|
|
1641
|
+
importedNames,
|
|
1642
|
+
lookup,
|
|
1643
|
+
typeMap,
|
|
1644
|
+
ptsMap,
|
|
1645
|
+
seenCallEdges,
|
|
1646
|
+
ptsEdgeRows,
|
|
1647
|
+
allEdgeRows,
|
|
1648
|
+
);
|
|
1557
1649
|
}
|
|
1558
1650
|
|
|
1651
|
+
// Step 5: Emit receiver edge for external (non-this/self/super) receivers.
|
|
1559
1652
|
if (
|
|
1560
1653
|
call.receiver &&
|
|
1561
1654
|
!BUILTIN_RECEIVERS.has(call.receiver) &&
|
|
@@ -1570,46 +1663,45 @@ function buildFileCallEdges(
|
|
|
1570
1663
|
relPath,
|
|
1571
1664
|
typeMap as Map<string, unknown>,
|
|
1572
1665
|
seenCallEdges,
|
|
1666
|
+
importArtifactNames ?? importedNames,
|
|
1573
1667
|
);
|
|
1574
1668
|
if (recv) {
|
|
1575
|
-
allEdgeRows.push([
|
|
1669
|
+
allEdgeRows.push([
|
|
1670
|
+
recv.callerId,
|
|
1671
|
+
recv.receiverId,
|
|
1672
|
+
'receiver',
|
|
1673
|
+
recv.confidence,
|
|
1674
|
+
0,
|
|
1675
|
+
null,
|
|
1676
|
+
null,
|
|
1677
|
+
]);
|
|
1576
1678
|
}
|
|
1577
1679
|
}
|
|
1578
1680
|
|
|
1579
|
-
// Phase 8.5
|
|
1580
|
-
// For `this`/`self`/`super` calls: resolve through the class hierarchy instead
|
|
1581
|
-
// of relying solely on global name matching.
|
|
1582
|
-
// For typed receiver calls: expand to all instantiated concrete implementations.
|
|
1681
|
+
// Step 6: Phase 8.5 CHA + RTA dispatch expansion.
|
|
1583
1682
|
if (chaCtx && call.receiver) {
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1607
|
-
const conf = computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
|
|
1608
|
-
if (conf > 0) {
|
|
1609
|
-
seenCallEdges.add(edgeKey);
|
|
1610
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha']);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1683
|
+
emitChaCallEdgesForCall(
|
|
1684
|
+
call,
|
|
1685
|
+
caller,
|
|
1686
|
+
relPath,
|
|
1687
|
+
typeMap,
|
|
1688
|
+
lookup,
|
|
1689
|
+
chaCtx,
|
|
1690
|
+
seenCallEdges,
|
|
1691
|
+
ptsEdgeRows,
|
|
1692
|
+
allEdgeRows,
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// Step 7: Flag-only dynamic kinds with no resolved target → sink edge to the
|
|
1697
|
+
// file node. confidence=0.0 keeps it below DEFAULT_MIN_CONFIDENCE so it never
|
|
1698
|
+
// appears in normal query results, but is queryable via `codegraph roles --dynamic`.
|
|
1699
|
+
if (targets.length === 0 && call.dynamicKind && FLAG_ONLY_KINDS.has(call.dynamicKind)) {
|
|
1700
|
+
// Key per (caller, file, kind) so each kind gets at most one sink edge per caller.
|
|
1701
|
+
const sinkKey = `${caller.id}:${fileNodeRow.id}:${call.dynamicKind}`;
|
|
1702
|
+
if (!seenCallEdges.has(sinkKey)) {
|
|
1703
|
+
seenCallEdges.add(sinkKey);
|
|
1704
|
+
allEdgeRows.push([caller.id, fileNodeRow.id, 'calls', 0.0, 1, null, call.dynamicKind]);
|
|
1613
1705
|
}
|
|
1614
1706
|
}
|
|
1615
1707
|
}
|
|
@@ -1637,7 +1729,7 @@ function buildClassHierarchyEdges(
|
|
|
1637
1729
|
);
|
|
1638
1730
|
if (sourceRow) {
|
|
1639
1731
|
for (const t of targetRows) {
|
|
1640
|
-
allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null]);
|
|
1732
|
+
allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null, null]);
|
|
1641
1733
|
}
|
|
1642
1734
|
}
|
|
1643
1735
|
}
|
|
@@ -1651,7 +1743,7 @@ function buildClassHierarchyEdges(
|
|
|
1651
1743
|
);
|
|
1652
1744
|
if (sourceRow) {
|
|
1653
1745
|
for (const t of targetRows) {
|
|
1654
|
-
allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null]);
|
|
1746
|
+
allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null, null]);
|
|
1655
1747
|
}
|
|
1656
1748
|
}
|
|
1657
1749
|
}
|
|
@@ -1662,7 +1754,8 @@ function buildClassHierarchyEdges(
|
|
|
1662
1754
|
|
|
1663
1755
|
/**
|
|
1664
1756
|
* After native bulkInsertEdges (which does not write the technique column),
|
|
1665
|
-
* apply technique values from the in-memory row array back to the DB
|
|
1757
|
+
* apply technique values from the in-memory row array back to the DB, and lift
|
|
1758
|
+
* any resolved ts-native edge below TS_NATIVE_CONFIDENCE_FLOOR to that floor.
|
|
1666
1759
|
*
|
|
1667
1760
|
* Rows with an explicit technique get a targeted UPDATE by (source_id, target_id).
|
|
1668
1761
|
* The catch-all 'ts-native' tag is scoped to only the source_ids present in this
|
|
@@ -1683,6 +1776,9 @@ function applyEdgeTechniquesAfterNativeInsert(
|
|
|
1683
1776
|
// Chunk to stay within SQLite's SQLITE_LIMIT_VARIABLE_NUMBER (999 on older builds).
|
|
1684
1777
|
const CHUNK_SIZE = 500;
|
|
1685
1778
|
|
|
1779
|
+
// Rows that carry an explicit dynamic_kind (sink edges for flagged dynamic calls).
|
|
1780
|
+
const dynamicKindRows = callRows.filter((r) => r[6] != null);
|
|
1781
|
+
|
|
1686
1782
|
const tx = db.transaction(() => {
|
|
1687
1783
|
if (taggedRows.length > 0) {
|
|
1688
1784
|
const stmt = db.prepare(
|
|
@@ -1696,6 +1792,27 @@ function applyEdgeTechniquesAfterNativeInsert(
|
|
|
1696
1792
|
db.prepare(
|
|
1697
1793
|
`UPDATE edges SET technique = 'ts-native' WHERE kind = 'calls' AND technique IS NULL AND source_id IN (${placeholders})`,
|
|
1698
1794
|
).run(...chunk);
|
|
1795
|
+
// Lift resolved ts-native edges below the confidence floor for this chunk.
|
|
1796
|
+
db.prepare(
|
|
1797
|
+
`UPDATE edges SET confidence = ?
|
|
1798
|
+
WHERE kind = 'calls' AND technique = 'ts-native'
|
|
1799
|
+
AND confidence > 0 AND confidence < ?
|
|
1800
|
+
AND source_id IN (${placeholders})`,
|
|
1801
|
+
).run(TS_NATIVE_CONFIDENCE_FLOOR, TS_NATIVE_CONFIDENCE_FLOOR, ...chunk);
|
|
1802
|
+
}
|
|
1803
|
+
// Back-fill dynamic_kind for flagged sink edges emitted by the native engine.
|
|
1804
|
+
// Native bulkInsertEdges uses INSERT OR IGNORE and does not write dynamic_kind, so
|
|
1805
|
+
// this UPDATE is the only way to set it for natively-inserted sink edges.
|
|
1806
|
+
//
|
|
1807
|
+
// Scope to confidence=0.0 AND dynamic=1 so we only touch sink edges (never normal
|
|
1808
|
+
// call edges that happen to share the same (source_id, target_id) pair).
|
|
1809
|
+
// Include dynamic_kind in the WHERE so two sink edges from the same caller to the
|
|
1810
|
+
// same file with different kinds don't clobber each other across incremental runs.
|
|
1811
|
+
if (dynamicKindRows.length > 0) {
|
|
1812
|
+
const stmt = db.prepare(
|
|
1813
|
+
"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 = ?)",
|
|
1814
|
+
);
|
|
1815
|
+
for (const r of dynamicKindRows) stmt.run(r[6], r[0], r[1], r[6]);
|
|
1699
1816
|
}
|
|
1700
1817
|
});
|
|
1701
1818
|
tx();
|
|
@@ -1734,6 +1851,7 @@ function reconnectReverseDepEdges(ctx: PipelineContext): void {
|
|
|
1734
1851
|
saved.confidence,
|
|
1735
1852
|
saved.dynamic,
|
|
1736
1853
|
saved.technique,
|
|
1854
|
+
saved.dynamicKind ?? null,
|
|
1737
1855
|
]);
|
|
1738
1856
|
} else {
|
|
1739
1857
|
// Target was removed or renamed in the changed file — edge is stale
|
|
@@ -1775,7 +1893,7 @@ function reconnectReverseDepEdges(ctx: PipelineContext): void {
|
|
|
1775
1893
|
* their import targets. Falls back to loading ALL nodes for full builds or
|
|
1776
1894
|
* larger incremental changes.
|
|
1777
1895
|
*/
|
|
1778
|
-
const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant')`;
|
|
1896
|
+
const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant','variable')`;
|
|
1779
1897
|
|
|
1780
1898
|
function loadNodes(ctx: PipelineContext): { rows: QueryNodeRow[]; scoped: boolean } {
|
|
1781
1899
|
const { db, fileSymbols, isFullBuild, batchResolved } = ctx;
|
|
@@ -1858,7 +1976,16 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
|
|
|
1858
1976
|
// Enrich typeMap for .ts/.tsx files using the TypeScript compiler API.
|
|
1859
1977
|
// Runs before call-edge construction so the accurate types are available
|
|
1860
1978
|
// for method-call resolution. Gated on config so users can opt out.
|
|
1861
|
-
|
|
1979
|
+
//
|
|
1980
|
+
// Skip for small incremental builds: TypeScript program creation requires
|
|
1981
|
+
// loading the entire tsconfig file list (~700ms startup on the codegraph
|
|
1982
|
+
// corpus), which dominates the 1-file rebuild time. Native engine bypasses
|
|
1983
|
+
// this entirely via the Rust orchestrator; WASM/JS engines need this gate
|
|
1984
|
+
// to match native's effective behaviour on tiny incremental changes.
|
|
1985
|
+
// Mirrors the smallFilesThreshold gates for nativeDb and native call-edges.
|
|
1986
|
+
const isSmallIncremental =
|
|
1987
|
+
!ctx.isFullBuild && ctx.fileSymbols.size <= ctx.config.build.smallFilesThreshold;
|
|
1988
|
+
if (ctx.config.build.typescriptResolver && !isSmallIncremental) {
|
|
1862
1989
|
await enrichTypeMapWithTsc(ctx.rootDir, ctx.fileSymbols);
|
|
1863
1990
|
}
|
|
1864
1991
|
|
|
@@ -1921,26 +2048,12 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
|
|
|
1921
2048
|
(ctx.isFullBuild || ctx.fileSymbols.size > ctx.config.build.smallFilesThreshold);
|
|
1922
2049
|
if (useNativeCallEdges) {
|
|
1923
2050
|
buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodesBefore, native!);
|
|
1924
|
-
//
|
|
1925
|
-
//
|
|
2051
|
+
// The native engine receives all pts bindings (paramBindings,
|
|
2052
|
+
// fnRefBindings, thisCallBindings, objectRestParamBindings, …) through
|
|
2053
|
+
// NativeFileEntry and runs the same points-to solver as the JS path, so
|
|
2054
|
+
// no pts post-passes are needed here. Only capabilities that remain
|
|
2055
|
+
// JS-only run as post-passes below.
|
|
1926
2056
|
const sharedLookup = makeContextLookup(ctx, getNodeIdStmt);
|
|
1927
|
-
// Phase 8.3c post-pass: augment native call edges with parameter-flow pts
|
|
1928
|
-
// edges. The native Rust engine has no knowledge of paramBindings, so any
|
|
1929
|
-
// `fn()` call inside a higher-order function would be missed. This JS pass
|
|
1930
|
-
// runs on top of the native edges and adds only the pts-resolved edges that
|
|
1931
|
-
// the native engine could not produce.
|
|
1932
|
-
buildParamFlowPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1933
|
-
// bind/alias post-pass: augment native call edges with fnRefBindings-seeded
|
|
1934
|
-
// pts edges. The native Rust engine has no knowledge of JS fnRefBindings
|
|
1935
|
-
// (e.g. `const f = fn.bind(ctx)`), so calls to bind-created aliases are
|
|
1936
|
-
// not resolved to their original function on the native path.
|
|
1937
|
-
buildFnRefBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1938
|
-
// this-rebinding post-pass: resolve `this()` calls inside functions that
|
|
1939
|
-
// were invoked via `.call(namedCtx, ...)` / `.apply(namedCtx, ...)`.
|
|
1940
|
-
buildThisCallBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1941
|
-
// Phase 8.3f post-pass: augment native call edges with object rest-param
|
|
1942
|
-
// receiver resolution — typeMap[restName] → argName → typeMap[argName.method].
|
|
1943
|
-
buildObjectRestParamPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1944
2057
|
// Object.defineProperty accessor post-pass: resolve this-dispatch inside
|
|
1945
2058
|
// getter/setter functions registered via Object.defineProperty.
|
|
1946
2059
|
buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
@@ -1952,6 +2065,24 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
|
|
|
1952
2065
|
buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx);
|
|
1953
2066
|
}
|
|
1954
2067
|
|
|
2068
|
+
// Apply ts-native confidence floor to allEdgeRows in-memory. The proximity
|
|
2069
|
+
// heuristic returns 0.3 for cross-module calls with no import-path evidence,
|
|
2070
|
+
// but both WASM and native engines perform actual name-based symbol lookup,
|
|
2071
|
+
// which is stronger evidence than pure proximity. Clamping to
|
|
2072
|
+
// TS_NATIVE_CONFIDENCE_FLOOR (0.5) avoids unfairly dragging down the
|
|
2073
|
+
// call-confidence metric. Sink edges (confidence = 0.0) are excluded so
|
|
2074
|
+
// they remain below DEFAULT_MIN_CONFIDENCE.
|
|
2075
|
+
for (const r of allEdgeRows) {
|
|
2076
|
+
if (
|
|
2077
|
+
r[2] === 'calls' &&
|
|
2078
|
+
r[5] === 'ts-native' &&
|
|
2079
|
+
(r[3] as number) > 0 &&
|
|
2080
|
+
(r[3] as number) < TS_NATIVE_CONFIDENCE_FLOOR
|
|
2081
|
+
) {
|
|
2082
|
+
r[3] = TS_NATIVE_CONFIDENCE_FLOOR;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
|
|
1955
2086
|
// When using native edge insert, skip JS insert here — do it after tx commits.
|
|
1956
2087
|
// Otherwise insert edges within this transaction for atomicity.
|
|
1957
2088
|
const useNativeEdgeInsert = ctx.engineName === 'native' && !!ctx.nativeDb?.bulkInsertEdges;
|