Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ambiguous_unknown read client/client#dailyReport(string) -> -
known_edge read client/client#dailyReport(string) -> shared/config#reportsUrl
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
78-cross-process-destinations [fields]
precision 0.0000 (0 correct, 0 wrong)
recall 0.0000 (0 of 1 the compiler resolved)
sites 1 resolved 0 (0.0%)
tiers ambiguous_unknown=1
precision 1.0000 (1 correct, 0 wrong)
recall 1.0000 (1 of 1 the compiler resolved)
sites 1 resolved 1 (100.0%)
tiers known_edge=1
access read=1
not scored: 1 rows whose target is not a client declaration
MISSING client/client#dailyReport(string) READ shared/config#reportsUrl
not scored: 0 rows whose target is not a client declaration
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ambiguous_unknown read local#drive() -> -
ambiguous_unknown read local#trimName() -> -
ambiguous_unknown read use#callNested() -> -
known_edge read local#drive() -> local#jobs
known_edge read local#trimName() -> local#name
known_edge read use#callNested() -> api#users
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
80-object-literal-member-receivers [fields]
precision 0.0000 (0 correct, 0 wrong)
recall 0.0000 (0 of 9 the compiler resolved)
sites 6 resolved 0 (0.0%)
tiers ambiguous_unknown=6
precision 1.0000 (3 correct, 0 wrong)
recall 0.3333 (3 of 9 the compiler resolved)
sites 6 resolved 6 (100.0%)
tiers known_edge=6
access read=6
not scored: 6 rows whose target is not a client declaration
MISSING local#drive() READ local#jobs
not scored: 0 rows whose target is not a client declaration
MISSING local#drive() READ local#run
MISSING local#trimName() READ local#name
MISSING use#callNested() READ api#byName
MISSING use#callNested() READ api#get
MISSING use#callNested() READ api#users
MISSING use#callStore() READ api#reset
MISSING use#callTop() READ api#find
MISSING use#callTop() READ api#remove
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
ambiguous_unknown read Bus#count(string) -> -
ambiguous_unknown read Indexer#start() -> -
ambiguous_unknown read app#<module-init>() -> -
ambiguous_unknown read app#create() -> -
ambiguous_unknown read app#size() -> -
known_edge read Bus#count(string) -> Bus#handlers
known_edge read Bus#dispatch(string,unknown) -> Bus#handlers
known_edge read Bus#subscribe(string,Handler) -> Bus#handlers
known_edge read Indexer#start() -> Indexer#b
known_edge read Indexer#start() -> topics#created
known_edge read Indexer#start() -> topics#removed
known_edge read MiniBus#fire(string,unknown) -> MiniBus#h
known_edge read MiniBus#on(string,(p: unknown) =) -> MiniBus#h
known_edge read app#<module-init>() -> topics#created
known_edge read app#create() -> topics#created
known_edge read app#relay({ type: string }) -> { type: string }#type
known_edge read app#size() -> topics#created
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
86-keyed-callback-registry [fields]
precision 0.8571 (6 correct, 1 wrong)
recall 0.5000 (6 of 12 the compiler resolved)
sites 17 resolved 11 (64.7%)
tiers ambiguous_unknown=6 known_edge=11
precision 0.9167 (11 correct, 1 wrong)
recall 0.9167 (11 of 12 the compiler resolved)
sites 17 resolved 16 (94.1%)
tiers ambiguous_unknown=1 known_edge=16
access read=17
not scored: 6 rows whose target is not a client declaration
not scored: 1 rows whose target is not a client declaration
WRONG app#relay({ type: string }) READ { type: string }#type
MISSING Indexer#start() READ topics#created
MISSING Indexer#start() READ topics#removed
MISSING app#<module-init>() READ topics#created
MISSING app#create() READ topics#created
MISSING app#relay({ type: string }) READ app#type
MISSING app#size() READ topics#created
11 changes: 10 additions & 1 deletion graph/test/typescript/tools/normalize_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,17 @@ def field_names(ir, prefix='typescript'):
out = {}
types = {t['tsTypeUniqueHash']: (t.get('name') or t.get('qualifiedName') or '?')
for t in rows(f'{ir}/all-{prefix}-types.csv')}
mods = {m['tsModuleUniqueHash']: m['qualifiedName'] or m['filePath']
for m in rows(f'{ir}/all-{prefix}-modules.csv')}
for r in rows(f'{ir}/all-{prefix}-fields.csv'):
owner = types.get(r.get('tsTypeLinkHash')) or r.get('ownerTypeName') or '?'
owner = types.get(r.get('tsTypeLinkHash'))
# A key of a module-level `const X = { a: … }` is owned by the variable X (or by the
# key holding a nested literal), which is no type. The compiler side names the nearest
# enclosing named declaration and the module otherwise, so `X.a` is `module#a` there;
# naming it `X#a` here scored every correct read as one wrong plus one missing.
if owner is None and r.get('memberKind') == 'OBJECT_LITERAL_PROPERTY':
owner = mods.get(r.get('tsModuleLinkHash'))
owner = owner or r.get('ownerTypeName') or '?'
# `static ` prefixes the NAME, as normalize_edges.Names does for a static method and
# as the compiler side's labelOf does. Without it a static member is named one way on
# each side and every access to it scores as both a miss and a false positive.
Expand Down
68 changes: 60 additions & 8 deletions graph/typescript/engine/call-edge-generation/field_access.dl
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,43 @@ field_site_excluded(e, "method_value") :- ts_field_site_is_method(e),
!expr_kind("client", "PROPERTY_ACCESS", "METHOD_NAME", e).

// ── RESOLUTION — the three receiver-typing paths expr-type.dl already has ───
ts_field_access_target(e, f) :- property_access_name(e, n),
ts_field_access_typed(e, f) :- property_access_name(e, n),
expr_child("client", e, "RECEIVER", _, q),
expr_type(q, _, qt),
field_in_scope(qt, n, "false", f).
ts_field_access_target(e, f) :- property_access_name(e, n),
ts_field_access_typed(e, f) :- property_access_name(e, n),
expr_child("client", e, "RECEIVER", _, q),
expr_static_type(q, _, qt),
field_in_scope(qt, n, "true", f).
ts_field_access_target(e, f) :- property_access_name(e, n),
ts_field_access_typed(e, f) :- property_access_name(e, n),
expr_child("client", e, "RECEIVER", _, q),
expr_shape(q, qs),
field_in_scope(qs, n, _, f).
ts_field_access_target(e, f) :- ts_field_access_typed(e, f).
ts_field_access_is_typed(e) :- ts_field_access_typed(e, _).

// ── A KEY OF A MODULE-LEVEL CONST TABLE — `TOPICS.orderEvents` ──────────────
// `export const TOPICS = { orderEvents: '…' } as const`: the parser declares each key
// as an OBJECT_LITERAL_PROPERTY owned by the VARIABLE (a nested literal's keys by the
// property holding it). The owner is no type, so none of the three joins above can
// reach it; the receiver names the owner instead — the variable itself, an import of
// it, a namespace import's export of it, or a key of an outer table. Only where the
// typed lookup found nothing: `{ … } satisfies Config` is read as Config's member.
ts_objlit_owner(q, v) :- expr_referenced("client", "VARIABLE", v, q).
ts_objlit_owner(q, v) :- expr_referenced("client", "IMPORT_BINDING", ih, q),
import_binds(ih, _, "VARIABLE", v).
ts_objlit_owner(q, v) :- property_access_name(q, n),
expr_child("client", q, "RECEIVER", _, r),
expr_namespace(r, _, m),
module_export(_, m, n, "VARIABLE", v).
ts_objlit_owner(q, pf) :- ts_field_access_target(q, pf),
field_kind("client", "OBJECT_LITERAL_PROPERTY", _, _, pf).
ts_field_access_target(e, f) :- property_access_name(e, n),
expr_child("client", e, "RECEIVER", _, q),
ts_objlit_owner(q, o),
field_decl("client", n, _, o, f),
field_kind("client", "OBJECT_LITERAL_PROPERTY", _, _, f),
!ts_field_access_is_typed(e).

// ── ACCESS DIRECTION ────────────────────────────────────────────────────────
// property_written and property_write_only are accessors.dl's, and are reused rather
Expand All @@ -124,12 +149,39 @@ ts_field_access_class(e, "ambiguous_unknown") :- ts_field_access_count(e, 0).
// The enclosing function of the ACCESS, by expr_enclosing_method, exactly as accessors.dl
// attributes an accessor invocation: there is no call site to attribute through. Top-level
// code is the module initializer, which that relation already supplies.
// A read inside a METHOD'S DECORATOR is that method's: `@EventPattern(TOPICS.orderEvents)`
// binds the handler to the topic, and the module initializer that evaluates the
// decorator is no user of it. A decorator's expressions carry no owner hash, so the
// subtree is walked down from the decorator's own expression.
ts_deco_expr_method(e, m) :- decorator_expr("client", e, d), decorator_target("client", _, m, d), m != "".
// ...and so is a read in a PARAMETER'S decorator: `constructor(@inject(TOKENS.Store) s)`
// binds the constructor to the token. The decorator's owner is the parameter.
ts_deco_expr_method(e, m) :- decorator_expr("client", e, d),
ts_decorator(_, _, "PARAMETER_DECLARATION", p, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, d),
param_decl("client", _, _, _, m, p), m != "".
ts_deco_expr_method(c, m) :- ts_deco_expr_method(p, m), expr_child("client", p, _, _, c).
ts_field_access_in_decorator(e) :- ts_deco_expr_method(e, _).
// A read in a parameter's DEFAULT is its method's too: `constructor(topic = TOPICS.payments)`
// is evaluated on every call that omits the argument, and the extractor places it in the
// module initializer. Only there: a function written as the default keeps its own reads.
ts_param_default_expr(e, m) :- ts_method_parameter(_, _, m, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, e, _, _, _, _),
e != "", m != "".
ts_param_default_expr(c, m) :- ts_param_default_expr(p, m), expr_child("client", p, _, _, c).
ts_field_access_in_function(e) :- expr_enclosing_method(e, m), m != "", !module_init("client", m, _).
ts_param_default_method(e, m) :- ts_param_default_expr(e, m),
!ts_field_access_in_function(e), !ts_field_access_in_decorator(e).
ts_field_access_named(e) :- expr_enclosing_method(e, m), m != "".
ts_field_access_from(e, m) :- ts_field_access_site(e), expr_enclosing_method(e, m), m != "".
// ...and the MODULE itself for the residue expr_enclosing_method cannot place, which is
// the marker call_sites.caller_id already uses in TypeScript rather than a blank. Never
// NULL, so a site can always be attributed to the code that contains it.
ts_field_access_from(e, mod) :- ts_field_access_site(e), !ts_field_access_named(e),
ts_field_access_placed(e) :- ts_field_access_named(e).
ts_field_access_placed(e) :- ts_field_access_in_decorator(e).
ts_field_access_placed(e) :- ts_param_default_method(e, _).
ts_field_access_from(e, m) :- ts_field_access_site(e), ts_deco_expr_method(e, m).
ts_field_access_from(e, m) :- ts_field_access_site(e), ts_param_default_method(e, m).
ts_field_access_from(e, m) :- ts_field_access_site(e), expr_enclosing_method(e, m), m != "",
!ts_field_access_in_decorator(e), !ts_param_default_method(e, _).
// ...and the MODULE itself for the residue none of those can place, which is the marker
// call_sites.caller_id already uses in TypeScript rather than a blank. Never NULL, so a
// site can always be attributed to the code that contains it.
ts_field_access_from(e, mod) :- ts_field_access_site(e), !ts_field_access_placed(e),
expr_module("client", mod, e), mod != "".

// ── THE EXPORTED ROW — field_access(Site, Caller, Field, Prov, Tier, Access) ─
Expand Down
9 changes: 9 additions & 0 deletions graph/typescript/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -631,12 +631,21 @@
.decl ts_field_site_is_method(c0:symbol)
.decl field_site_excluded(c0:symbol,c1:symbol)
.decl ts_field_access_target(c0:symbol,c1:symbol)
.decl ts_field_access_typed(c0:symbol,c1:symbol)
.decl ts_field_access_is_typed(c0:symbol)
.decl ts_objlit_owner(c0:symbol,c1:symbol)
.decl ts_field_access_kind(c0:symbol,c1:symbol)
.decl ts_field_access_count(c0:symbol,c1:number)
.decl ts_field_prov(c0:symbol,c1:symbol)
.decl ts_field_site_leaves_client(c0:symbol)
.decl ts_field_access_class(c0:symbol,c1:symbol)
.decl ts_field_access_named(c0:symbol)
.decl ts_deco_expr_method(c0:symbol,c1:symbol)
.decl ts_field_access_in_decorator(c0:symbol)
.decl ts_param_default_method(c0:symbol,c1:symbol)
.decl ts_param_default_expr(c0:symbol,c1:symbol)
.decl ts_field_access_in_function(c0:symbol)
.decl ts_field_access_placed(c0:symbol)
.decl ts_field_access_from(c0:symbol,c1:symbol)
.decl ts_field_access_has_row(c0:symbol)
.decl field_access(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol)
Expand Down
Loading
Loading