Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
0a6c391
javascript: path says unknown, not independent, when one side calls a…
swapnilpaliwal-sd Sep 28, 2026
61bb4ad
javascript: path says unknown for a field set to null, a static or al…
swapnilpaliwal-sd Sep 28, 2026
d544d9b
path/impact: a name no declaration carries is not a type (#1602)
swapnilpaliwal-sd Sep 28, 2026
bb5b020
impact: a private class field (Owner.#name) resolves under its # spel…
swapnilpaliwal-sd Sep 28, 2026
bf88511
context/impact/path: --in matches a repository path by prefix, and im…
swapnilpaliwal-sd Sep 28, 2026
0e388b7
release 0.1.7: every package and plugin manifest at 0.1.7 (the parser…
swapnilpaliwal-sd Sep 28, 2026
a8f0fec
typescript: a global declared in one program is not in scope in anoth…
swapnilpaliwal-sd Sep 28, 2026
c562e22
Merge pull request #1711 from AxiomCodeAI/loop/ty-is1584
swapnilpaliwal-sd Sep 28, 2026
218e6de
Merge pull request #1704 from AxiomCodeAI/loop/ja-is1698
swapnilpaliwal-sd Sep 28, 2026
3776686
Merge pull request #1696 from AxiomCodeAI/loop/ja-is1675
swapnilpaliwal-sd Sep 28, 2026
b151f52
Merge pull request #1669 from AxiomCodeAI/loop/ty-is1602
swapnilpaliwal-sd Sep 28, 2026
dfb874f
Merge pull request #1693 from AxiomCodeAI/loop/ty-is1574
swapnilpaliwal-sd Sep 28, 2026
5b71fe1
Merge dev (the 0.1.6 release) into 0.1.7
swapnilpaliwal-sd Sep 28, 2026
e130ebc
Merge pull request #1714 from AxiomCodeAI/port/dev-into-0.1.7
swapnilpaliwal-sd Sep 28, 2026
954e21a
javascript: a wrapper that writes its parameter answers with the writ…
swapnilpaliwal-sd Sep 28, 2026
c0ee951
plugin: a corrupt graph.sqlite is said, moved aside and rebuilt, neve…
swapnilpaliwal-sd Sep 28, 2026
32fee0b
javascript: path says unknown when a call goes through an imported ex…
swapnilpaliwal-sd Sep 28, 2026
b7697b5
Merge pull request #1717 from AxiomCodeAI/loop/ja-is1706
swapnilpaliwal-sd Sep 28, 2026
beed758
Merge pull request #1716 from AxiomCodeAI/loop/to-p0-corrupt-graph
swapnilpaliwal-sd Sep 28, 2026
bf4019e
typescript: cross-process remote_edge for HTTP routes, and context li…
swapnilpaliwal-sd Sep 28, 2026
03772cc
Merge pull request #1710 from AxiomCodeAI/loop/ty-is1293
swapnilpaliwal-sd Sep 28, 2026
b9bfcc8
Merge pull request #1718 from AxiomCodeAI/loop/ja-is1708
swapnilpaliwal-sd Sep 28, 2026
c5ccd1c
tests: cases 68 and 69 expect the remote_unsent section #1710 adds
swapnilpaliwal-sd Sep 28, 2026
6c46698
Merge pull request #1720 from AxiomCodeAI/fix/ts-68-69-remote-unsent-…
swapnilpaliwal-sd Sep 28, 2026
1b2db3e
run-souffle: one engine compile per engine id; concurrent misses wait…
swapnilpaliwal-sd Sep 28, 2026
fdcc99e
test-impact: run Python tests with the project's own runner, one lang…
Sep 28, 2026
f517b1a
javascript: fields owned by their class, computed-key members named b…
swapnilpaliwal-sd Sep 28, 2026
70bb065
javascript: a wrapper closure keeps its fallback, and a site with no …
swapnilpaliwal-sd Sep 28, 2026
e2af2c3
javascript: a wrapper whose closure is a function declared inside it …
swapnilpaliwal-sd Sep 28, 2026
b6c628c
tests: 64-memoize-wrapper-result expects the value_callee diagnostic …
swapnilpaliwal-sd Sep 28, 2026
ebefddd
typescript: a bare decorator application is a call site
swapnilpaliwal-sd Sep 28, 2026
4baef39
Merge pull request #1721 from AxiomCodeAI/fix/souffle-compile-lock
swapnilpaliwal-sd Sep 28, 2026
1dc12ab
hooks, impact, path: one reader for why nothing calls a method, and c…
swapnilpaliwal-sd Sep 28, 2026
cce65b3
Merge pull request #1694 from AxiomCodeAI/loop/ja-is695
swapnilpaliwal-sd Sep 28, 2026
90a0163
Merge pull request #1686 from AxiomCodeAI/loop/ja-is638
swapnilpaliwal-sd Sep 28, 2026
2e1cf6c
Merge pull request #1719 from AxiomCodeAI/loop/ty-is233
swapnilpaliwal-sd Sep 28, 2026
b556764
impact: label each link of a chained route registration with its own …
swapnilpaliwal-sd Sep 28, 2026
4e1a8ac
impact, path, context: when the graph has no answer, give the text ma…
swapnilpaliwal-sd Sep 28, 2026
007325f
tests: a case's own case.json quoted back as [text] is not the answer…
swapnilpaliwal-sd Sep 28, 2026
0350ae5
path: name a chained route link's own line as where its direct caller…
swapnilpaliwal-sd Sep 28, 2026
00e32ae
javascript: a call through an object-literal table with a computed ke…
swapnilpaliwal-sd Sep 28, 2026
8db4323
javascript: this.x() with no declared x is a value callee, so path an…
swapnilpaliwal-sd Sep 28, 2026
a47312d
Merge pull request #1699 from AxiomCodeAI/loop/to-text-fallback
swapnilpaliwal-sd Sep 29, 2026
04c8761
Merge pull request #1722 from AxiomCodeAI/loop/ja-chained-route-verbs…
swapnilpaliwal-sd Sep 29, 2026
2cba28c
Merge pull request #1723 from AxiomCodeAI/loop/ja-dispatch-map-comput…
swapnilpaliwal-sd Sep 29, 2026
1c4e6e5
Merge pull request #1724 from AxiomCodeAI/loop/ja-is1709
swapnilpaliwal-sd Sep 29, 2026
3d88c0a
typescript: resolve calls on members of an unannotated object literal
swapnilpaliwal-sd Sep 28, 2026
21d5653
typescript: record the member-of-literal edges cases 64, 74 and 78 no…
swapnilpaliwal-sd Sep 29, 2026
445691d
typescript: a method passed by reference registers with the function …
swapnilpaliwal-sd Sep 29, 2026
aaacf38
impact: a later page continues the rows, and a one-page answer has no…
swapnilpaliwal-sd Sep 28, 2026
b57355b
typescript: type a receiver from a generic signature's own T and from…
swapnilpaliwal-sd Sep 29, 2026
a70d90e
javascript: new this.constructor() builds the receiver's class at eac…
swapnilpaliwal-sd Sep 29, 2026
f179d85
path / impact: a file:line outside the file is refused with the file'…
swapnilpaliwal-sd Sep 29, 2026
989852c
tests: case 80's oracle summary counts the call CI's TypeScript compi…
swapnilpaliwal-sd Sep 29, 2026
5aa6a66
test-impact: the by-name tier offers only tests by the changed file's…
swapnilpaliwal-sd Sep 29, 2026
922f405
Merge pull request #1730 from AxiomCodeAI/loop/ty-ts-generic-return-r…
swapnilpaliwal-sd Sep 29, 2026
d3bda87
impact, test-impact: a script-style test counts as a test and runs by…
swapnilpaliwal-sd Sep 28, 2026
04efea7
test-impact: a script test runs by bare interpreter only when it guar…
swapnilpaliwal-sd Sep 29, 2026
87a5441
Merge pull request #1729 from AxiomCodeAI/loop/to-t2-1
swapnilpaliwal-sd Sep 29, 2026
2f2cea3
Merge pull request #1728 from AxiomCodeAI/loop/ty-ts-method-reference…
swapnilpaliwal-sd Sep 29, 2026
1bf369e
Merge pull request #1726 from AxiomCodeAI/loop/ty-ts-object-literal-m…
swapnilpaliwal-sd Sep 29, 2026
49702cd
Merge pull request #1733 from AxiomCodeAI/loop/ja-is465
swapnilpaliwal-sd Sep 29, 2026
8157a50
Merge pull request #1734 from AxiomCodeAI/loop/ja-is1601
swapnilpaliwal-sd Sep 29, 2026
19bd139
impact / path: --in chooses among the files a partial file:line path …
swapnilpaliwal-sd Sep 29, 2026
ebcc41d
Merge pull request #1735 from AxiomCodeAI/loop/ja-is1666
swapnilpaliwal-sd Sep 29, 2026
130d7d2
Merge pull request #1736 from AxiomCodeAI/loop/to-t2-2
swapnilpaliwal-sd Sep 29, 2026
e60e3c9
Merge pull request #1737 from AxiomCodeAI/0.1.7
swapnilpaliwal-sd Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion gemini-extension.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "axiomcode",
"version": "0.1.6",
"version": "0.1.7",
"description": "Ask your repository how its code connects: who calls this, what breaks if I change it, which tests an edit reaches, how A reaches B. Answers come from a resolved call graph and are verified against it; nothing is guessed.",
"contextFileName": "plugins/axiomcode/AGENTS.md",
"mcpServers": {
Expand Down
9 changes: 6 additions & 3 deletions graph/javascript/engine/call-edge-generation/call_chain.dl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ object_prototype_call_on_bare_type(ce) :- invocation_site(ce, ck), call_kind_is_
// Python rule sets: a capped site is a declared unknown (`fan_capped`), never a
// silent drop and never a 70-way edge set a reader would have to distrust.
call_over_cap(ce) :- call_target_count(ce, n), dispatch_cap(s), n > to_number(s).
call_over_cap(ce) :- open_dispatch_width(ce, n), dispatch_cap(s), n > to_number(s).
// A site whose callee was a computed read swallowed by a default has more callees
// than the value layer found, so one resolved target is not THE target (#931).
call_class(ce, "known_edge") :- call_target_count(ce, 1), !call_ambient_alternative(ce),
Expand All @@ -111,14 +112,16 @@ call_class(ce, "multi_inferred") :- call_target_count(ce, 1), partial_dispatch(c
partial_dispatch_target(ce, _).
call_class(ce, "multi_inferred") :- call_target_count(ce, n), n >= 2, !call_over_cap(ce).
call_class(ce, "multi_inferred") :- call_target_count(ce, 1), call_ambient_alternative(ce).
// An open read of an object-literal table (partial-dispatch.dl): one of its entries.
call_class(ce, "multi_inferred") :- open_dispatch(ce), !call_over_cap(ce).
call_class(ce, "fan_capped") :- call_over_cap(ce).
call_class(ce, "implicit_constructor") :- call_target_count(ce, 0), call_target_is_implicit_ctor(ce).
call_class(ce, "dynamic_terminal") :- call_target_count(ce, 0), !call_target_is_implicit_ctor(ce),
invocation_site(ce, ck), call_kind_is_dynamic(ck).
invocation_site(ce, ck), call_kind_is_dynamic(ck), !open_dispatch(ce).
call_class(ce, "ambient_terminal") :- call_target_count(ce, 0), !call_target_is_implicit_ctor(ce),
invocation_site(ce, ck), !call_kind_is_dynamic(ck), ambient_site(ce).
invocation_site(ce, ck), !call_kind_is_dynamic(ck), ambient_site(ce), !open_dispatch(ce).
call_class(ce, "ambiguous_unknown") :- call_target_count(ce, 0), !call_target_is_implicit_ctor(ce),
invocation_site(ce, ck), !call_kind_is_dynamic(ck), !ambient_site(ce).
invocation_site(ce, ck), !call_kind_is_dynamic(ck), !ambient_site(ce), !open_dispatch(ce).

// ── method_has_unknown_call(Caller, CallExpr) ───────────────────────────────
method_has_unknown_call(caller, ce) :- call_class(ce, "ambiguous_unknown"), call_from(ce, caller).
Expand Down
107 changes: 105 additions & 2 deletions graph/javascript/engine/call-edge-generation/calls.dl
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,116 @@ unresolved_receiver(ce, cn, ck, "other_form") :- call_site("client", ck, cn, _,
// name narrows what it calls, so any function that escapes as a value may be the
// callee, and a question that finds no chain from its caller must answer "unknown",
// never "independent" (#1649). Binding: parameter | loop_variable | local |
// computed | dynamic_code. An imported binding is not one: it names a module
// member, and a module-level binding is left to the by-name leads.
// computed | dynamic_code | module_variable | field | instance_member |
// parameter_member | expression | imported_variable. An imported binding is one only
// when it names a variable its module reassigns (below); otherwise it names a module
// member.
value_callee_open(ce, c) :- call_site("client", ck, _, _, _, _, ce, _, _), !call_kind_is_dynamic(ck),
!call_resolved(ce), !callee_value_known(ce), expr_child(_, ce, "CALLEE", _, c).
unresolved_value_callee(ce, "parameter") :- value_callee_open(ce, c), expr_param(_, _, c).
unresolved_value_callee(ce, "loop_variable") :- value_callee_open(ce, c), expr_binding(_, v, c), loop_head(v, _).
unresolved_value_callee(ce, "local") :- value_callee_open(ce, c), expr_binding(_, v, c), !loop_head(v, _),
var_owner_method("client", _, v), !var_import(_, _, v), !expr_param(_, _, c).
// The two below keep a callee whose value IS known but calls nothing the graph holds
// (`let handler = null`, a constructed `Function`): the value in the graph is not
// the one the call runs.
value_callee_unresolved(ce, c) :- call_site("client", ck, _, _, _, _, ce, _, _), !call_kind_is_dynamic(ck),
!call_resolved(ce), expr_child(_, ce, "CALLEE", _, c).
// `let handler = null; … handler()`: a module-level variable holds whatever a setter
// or an importer stored in it (#1675). One holding a platform function
// (`let f = Math.floor`) is a platform call; `null` is a literal, not a function.
unresolved_value_callee(ce, "module_variable") :- value_callee_unresolved(ce, c), expr_binding(_, v, c),
var_decl("client", _, _, _, _, _, v), !var_owner_method("client", _, v), !var_import(_, _, v), !expr_param(_, _, c),
!callee_platform_function(ce), !module_variable_from_call(v).
callee_platform_function(ce) :- callee_value(ce, "ambient", n), n != "LITERAL".
// `const log = require('debug')('app')`: a variable only ever holding what a call
// returned holds a function the library made, not one a setter stored. One that
// was handed a function (`const Card = memo(Inner)`) may call it, so stays open.
module_variable_from_call(v) :- var_init("client", _, e, v), expr_kind(_, k, _, e), (k = "CALL" ; k = "NEW"),
!variable_reassigned(v), !call_passes_function(e).
call_passes_function(e) :- call_arg(e, _, a), expr_value(a, "func", _).
call_passes_function(e) :- call_arg(e, _, a), expr_introduces(_, _, a).
variable_reassigned(v) :- expr_kind(_, "ASSIGNMENT", _, a), expr_child(_, a, "ASSIGNMENT_TARGET", _, tgt),
expr_binding(_, v, tgt).
// `(c ? a : b)()`, `(0, cb)()`, `make()()`: the callee is computed by an expression.
// `new Function(s)()` / `Function(s)()` runs code built at run time.
unresolved_value_callee(ce, "dynamic_code") :- value_callee_unresolved(ce, c), callee_builds_code(c).
unresolved_value_callee(ce, "expression") :- value_callee_open(ce, c), expr_kind(_, k, _, c),
k != "IDENTIFIER", k != "THIS", k != "SUPER", !callee_builds_code(c).
callee_builds_code(c) :- expr_kind(_, k, _, c), (k = "NEW" ; k = "CALL"), expr_child(_, c, "CALLEE", _, f),
expr_text(_, "Function", f), expr_binding_resolution(_, "GLOBAL_BUILTIN", f).
// `this.cb()` where `cb` is a field (`this.cb = cb`), not a method: the function
// stored there is whatever the constructor or a setter was handed. The same for
// `self.cb()` (`const self = this`), a static `this.cb()`, `new this.Ctor()`, and a
// field that holds only `null` until a setter runs.
unresolved_value_callee(ce, "field") :- field_call(ce, k, t, n), !call_resolved(ce), !callee_holds_function(ce),
this_field_named(k, t, n).
// `this.cb = cb || function () {}`: the call resolves to the fallback, but the
// parameter it falls back from is a function no caller in the graph supplies.
unresolved_value_callee(ce, "field") :- field_call(ce, k, t, n), field_open_fallback(k, t, n).
// `this.hook()` where neither the class, an ancestor, nor any write the graph can
// follow gives it a `hook`: the function is stored on the instance from outside,
// through another reference (`e.hook = fn`) or a copy (`Object.assign(this, opts)`)
// (#1709). A class with a base the graph could not resolve is left out: that base
// may declare `hook`, and then the call names a method, not a value.
unresolved_value_callee(ce, "instance_member") :- field_call(ce, "inst", t, n), !call_resolved(ce),
!callee_value_known(ce), !ambient_site(ce), !this_field_named("inst", t, n), !this_type_open(t),
expr_child(_, ce, "RECEIVER", _, r), expr_kind(_, "THIS", _, r).
this_type_open(t) :- heritage_unresolved(t, _, _).
this_type_open(t) :- heritage_dynamic(t, _).
this_type_open(t) :- type_ancestor(t, a), heritage_unresolved(a, _, _).
this_type_open(t) :- type_ancestor(t, a), heritage_dynamic(a, _).
field_call(ce, k, t, n) :- call_site("client", ck, n, "SYNTACTIC", _, _, ce, _, _),
(call_kind_is_member_form(ck) ; ck = "CONSTRUCTOR_CALL"), !call_kind_is_dynamic(ck),
expr_child(_, ce, "RECEIVER", _, r), instance_or_class_value(r, k, t).
instance_or_class_value(r, k, t) :- expr_kind(_, rk, _, r), (rk = "THIS" ; rk = "IDENTIFIER"), expr_value(r, k, t),
(k = "inst" ; k = "ctor").
callee_holds_function(ce) :- callee_value(ce, k, n), (k = "func" ; k = "ctor" ; (k = "ambient", n != "LITERAL")).
callee_holds_function(ce) :- new_callee_value(ce, _, _).
this_field_named(k, t, n) :- field_decl("client", n, t, _, _, _, _, _), (k = "inst" ; k = "ctor").
this_field_named(k, t, n) :- type_ancestor(t, a), field_decl("client", n, a, _, _, _, _, _), (k = "inst" ; k = "ctor").
this_field_named(k, t, n) :- field_assignment(k, t, n, _).
field_assignment(k, t, n, val) :- expr_kind(_, "ASSIGNMENT", _, a), expr_child(_, a, "ASSIGNMENT_TARGET", _, tgt),
expr_kind(_, "PROPERTY_ACCESS", _, tgt), expr_name(_, n, tgt), expr_child(_, tgt, "ACCESS_TARGET", _, r),
instance_or_class_value(r, k, t), expr_child(_, a, "ASSIGNMENT_VALUE", _, val).
field_open_fallback(k, t, n) :- field_assignment(k, t, n, val), expr_kind(_, "BINARY", _, val), expr_operator(_, op, val),
(op = "||" ; op = "??"), expr_child(_, val, "OPERAND", _, p), expr_param(_, _, p), !expr_value(p, "func", _).
// A callback in an options object: a member of an untyped parameter that the code
// treats as an optional FUNCTION — `opts.onReady?.()`, `typeof opts.onReady ===
// 'function'`, `opts.onReady && opts.onReady()`. A bare `res.send()` / `s.trim()` on
// an untyped parameter is left out: without a type it reads the same as a library
// or platform method, and counting it made 10 of 12 independent pairs on a real
// Express app unknown.
unresolved_value_callee(ce, "parameter_member") :- call_site("client", ck, n, "SYNTACTIC", _, _, ce, _, _),
call_kind_is_member_form(ck), !call_kind_is_dynamic(ck), !call_resolved(ce), !callee_value_known(ce),
!ambient_site(ce), expr_child(_, ce, "RECEIVER", _, r), expr_param(_, p, r),
(ck = "OPTIONAL_CALL" ; param_member_tested(p, n)).
param_member_tested(p, n) :- expr_kind(_, "UNARY", _, u), expr_operator(_, "typeof", u), expr_child(_, u, _, _, a),
param_member_read(a, p, n).
param_member_tested(p, n) :- expr_kind(_, "BINARY", _, b), expr_operator(_, op, b), (op = "&&" ; op = "||" ; op = "??"),
expr_child(_, b, "OPERAND", _, a), param_member_read(a, p, n).
param_member_tested(p, n) :- expr_child(_, _, "CONDITION", _, a), param_member_read(a, p, n).
param_member_tested(p, n) :- expr_root(_, "CONDITION", a), param_member_read(a, p, n).
param_member_read(a, p, n) :- expr_kind(_, k, _, a), (k = "PROPERTY_ACCESS" ; k = "OPTIONAL_ACCESS"), expr_name(_, n, a),
expr_child(_, a, "ACCESS_TARGET", _, t), expr_param(_, p, t).
unresolved_value_callee(ce, "computed") :- call_site("client", "COMPUTED_CALL", _, _, _, _, ce, _, _), !call_resolved(ce).
unresolved_value_callee(ce, "dynamic_code") :- call_site("client", "DYNAMIC_CODE_CALL", _, _, _, _, ce, _, _), !call_resolved(ce).
// `import { handler } from './hub'; handler()` or `hub.handler()` through a namespace,
// where hub does `export let handler = null` and reassigns it in a setter (#1708): an
// ES import is a LIVE binding, so the call runs whatever the setter stored last. The
// callee's value may be known (`null`) and still not be the one the call runs. An
// export the module never reassigns keeps its answer: its value is the one it holds.
unresolved_value_callee(ce, "imported_variable") :- value_callee_unresolved(ce, c), expr_binding(_, v, c),
var_import("client", imp, v), import_decl(_, _, _, bf, n, _, _, _, imp), import_binding_is_named(bf), n != "",
import_module(imp, mod), live_export_variable(mod, n).
unresolved_value_callee(ce, "imported_variable") :- call_site("client", ck, n, _, _, _, ce, _, _),
call_kind_is_member_form(ck), !call_kind_is_dynamic(ck), !call_resolved(ce),
expr_child(_, ce, "RECEIVER", _, r), expr_value(r, "module", mod), live_export_variable(mod, n).
// live_export_variable(Module, Name): the module exports, under Name, a variable some
// code assigns after its declaration — directly, or re-exported by name or by `export *`.
live_export_variable(mod, n) :- export_decl(_, n, _, _, _, "VARIABLE", v, _, mod, x), n != "", !export_overwritten(_, x),
variable_reassigned(v).
live_export_variable(mod, en) :- export_decl(_, en, ln, form, _, _, _, _, mod, x), form != "EXPORT_ALL", ln != "",
export_reexport(_, _, imp, x), import_module(imp, src), live_export_variable(src, ln).
live_export_variable(mod, n) :- export_decl(_, en, _, "EXPORT_ALL", _, _, _, _, mod, x), export_all_is_spread(en),
export_reexport(_, _, imp, x), import_module(imp, src), live_export_variable(src, n), n != "default".
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,38 @@ partial_dispatch_target(ce, m) :- call_site(_, "COMPUTED_CALL", _, _, _, _, ce,
expr_child(_, ce, "RECEIVER", _, r), expr_value(r, "obj", i),
prop_value("obj", i, _, "func", m).

// ============================================================================
// AN OPEN READ: THE SAME TABLE WITH NOTHING BESIDE IT
//
// const h = handlers[action.type]; h(state, p) a Redux-style reducer
// handlers[type](state, p) a command map
// cli.commands[argv[0]](argv) CLI dispatch
//
// No default merges here, so nothing was asserted wrongly: the site was a dead end
// (dynamic_terminal, or ambiguous_unknown through the const), and every entry of the
// table had no caller at all. The key is unknown; the table is not. The call runs one
// of its function values, which is what an array read with a computed index already
// says (resolution/arrays.dl). Emitted as edges for the reason above, and only where
// the value layer found NO target: a key whose value it could read keeps its answer.
// ============================================================================
open_read(e, i) :- expr_kind(_, "ELEMENT_ACCESS", _, e),
expr_child(_, e, "COMPUTED_KEY", _, key), !expr_kind(_, "LITERAL", _, key),
expr_child(_, e, "ACCESS_TARGET", _, r), expr_value(r, "obj", i).

open_dispatch_target(ce, m) :- call_target_count(ce, 0), expr_child(_, ce, "CALLEE", _, c),
open_read(c, i), prop_value("obj", i, _, "func", m).
open_dispatch_target(ce, m) :- call_target_count(ce, 0), expr_child(_, ce, "CALLEE", _, c),
expr_binding(_, v, c), var_init(_, _, e, v), open_read(e, i), prop_value("obj", i, _, "func", m).
open_dispatch_target(ce, m) :- call_target_count(ce, 0), call_site(_, "COMPUTED_CALL", _, _, _, _, ce, _, _),
expr_child(_, ce, "COMPUTED_KEY", _, key), !expr_kind(_, "LITERAL", _, key),
expr_child(_, ce, "RECEIVER", _, r), expr_value(r, "obj", i), prop_value("obj", i, _, "func", m).

open_dispatch(ce) :- open_dispatch_target(ce, _).
open_dispatch_width(ce, n) :- open_dispatch(ce), n = count : { open_dispatch_target(ce, _) }.
partial_dispatch(ce) :- open_dispatch(ce).
partial_dispatch_target(ce, m) :- open_dispatch_target(ce, m).

// ── the edges, and the tier that stops asserting ───────────────────────────
call_chain_edge(ce, caller, "-", m, "client", "multi_inferred", kind) :-
partial_dispatch_target(ce, m), !expr_resolves_to_method(ce, m),
partial_dispatch_target(ce, m), !expr_resolves_to_method(ce, m), !call_over_cap(ce),
call_from(ce, caller), invocation_site(ce, kind).
5 changes: 5 additions & 0 deletions graph/javascript/engine/projections/blocks.dl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ block_condition("lib", k, c, h) :- lib_js_block(k, _, _, _, _, _, _, c, _, _, _,
block_child("client", p, k, idx, h) :- js_block(k, _, p, _, idx, _, _, _, _, _, _, _, _, _, _, _, h), p != "".
block_child("lib", p, k, idx, h) :- lib_js_block(k, _, p, _, idx, _, _, _, _, _, _, _, _, _, _, _, h), p != "".

// ── block_owner(Kind, OwnerMethod, BlockHash) ──────────────────────────────
// The callable whose body holds the block: a write is conditional when a branching
// block of its OWN callable contains it (#1706).
block_owner(k, m, h) :- js_block(k, _, _, _, _, _, _, _, m, _, _, _, _, _, _, _, h), m != "".

// ── block_span(Prov, OwnerModule, StartLine, StartCol, EndLine, EndCol, BlockHash) ──
// A block opens no scope of its own unless it declares something, so the span is
// the one handle on "the expressions inside this block" (#701).
Expand Down
Loading
Loading