diff --git a/graph/bundle/build.ts b/graph/bundle/build.ts index 072e9594..84c9fcb9 100644 --- a/graph/bundle/build.ts +++ b/graph/bundle/build.ts @@ -529,6 +529,21 @@ export async function buildCore(inp: BuildInputs): Promise { } } } + // a field access whose site is no expression: a module variable read in a type (`typeof X`) + if (A.ir.fieldSites && fieldSites.size > 0) { + const L = A.ir.fieldSites; + const src = await clientSource(inp.clientIrDir, L.file); + if (src) { + const h = src.header; + const ci = h.col(L.id), cl = h.col(L.startLine), cel = h.col(L.endLine); + for await (const r of rowsOf(src)) { + const fa = fieldSites.get(r[ci] ?? ''); + if (!fa) continue; + const file = fileOf(L.fileVia, h, r); + for (const fr of fa) { fill(fr, 6, file); fill(fr, 7, int(r[cl])); fill(fr, 9, int(r[cel])); } + } + } + } // a site the IR writes no name for, where the engine derived the accessor it calls (#1441) if (A.raw.siteNames) { for (const [site, name] of await readSource(rawDir, A.raw.siteNames)) { diff --git a/graph/bundle/languages.ts b/graph/bundle/languages.ts index 286ec18a..e4ec00d9 100644 --- a/graph/bundle/languages.ts +++ b/graph/bundle/languages.ts @@ -166,6 +166,8 @@ export interface LanguageAdapter { callSites?: CallSitesIR; decorators?: DecoratorsIR; localSites?: LocalSitesIR; + /** field access sites that are not expressions (TypeScript: a `typeof X` type reference), positioned from this table */ + fieldSites?: LocalSitesIR; /** absent where the front end writes no skipped-files report */ skipped?: SkippedIR; }; @@ -277,6 +279,11 @@ const TYPESCRIPT: LanguageAdapter = { startLine: 'startLine', startColumn: 'startColumn', fileVia: { column: 'tsModuleLinkHash', through: 'modules' }, }, + // a module variable read in a TYPE (`typeof X`): the field_access site is the type reference + fieldSites: { + file: 'all-typescript-type-references.csv', id: 'tsTypeReferenceUniqueHash', startLine: 'startLine', endLine: 'endLine', + fileVia: { column: 'tsModuleLinkHash', through: 'modules' }, + }, skipped: { file: 'skipped-typescript-files.csv', filePath: 'filePath', reason: 'reason', detail: 'detail' }, }, }; @@ -337,6 +344,9 @@ const JAVASCRIPT: LanguageAdapter = { typeAncestors: { file: 'resolution-type-ancestor.csv', columns: [0, 1] }, entryPoints: { file: 'entry-point.csv', columns: [0, 1] }, entryReachable: { file: 'entry-reachable.csv', columns: [0] }, + // site, caller, variable, provenance, tier, access: a module variable an identifier reads, + // directly or through an import binding (the field is the variable's own hash) + fieldAccess: { file: 'field-access.csv', columns: [0, 1, 2, 3, 4, 5] }, }, ir: { // No signature and no owner qualified name: JavaScript declares neither. diff --git a/graph/javascript/engine/call-edge-generation/field_access.dl b/graph/javascript/engine/call-edge-generation/field_access.dl new file mode 100644 index 00000000..066a5aee --- /dev/null +++ b/graph/javascript/engine/call-edge-generation/field_access.dl @@ -0,0 +1,60 @@ +// ============================================================================ +// CALL-EDGE-GEN · A MODULE VARIABLE READ BY NAME, AS A DATA EDGE +// +// `CFG.name`, `use(SCHEMA)`, `${TOKENS.store}`: the identifier is bound by the binder +// (expr_binding) to a variable -- the module's own top-level one, or an import binding. +// This engine follows an import to the VALUE the export holds (import_value), which is +// what a call needs, and never to the exported VARIABLE, which is what "who reads this +// const" needs. So a read of an imported const was answered by name, and the name +// matched every same-named const in every sibling package. +// +// The export surface is walked here once more, keeping the variable: export_decl names +// it (`export const x` and `export { x }` carry target kind VARIABLE), and a re-export +// (`export * from`, `export { x } from`) passes it on exactly as module_export_value +// passes a value on. A variable holding a function or a class is left out: its uses +// are calls and constructions, which call edges already carry. +// +// field_access(Site, Caller, Variable, "client", "known_edge", Access), the shape the +// Java and TypeScript engines export, so the bundle reads one relation for all three. +// ============================================================================ + +js_module_var(v) :- var_decl("client", _, reg, _, _, _, v), !var_owner_method("client", _, v), + !js_not_a_data_binding(reg), + !var_init("client", "FUNCTION", _, v), !var_init("client", "CLASS", _, v), + !var_import("client", _, v). +js_not_a_data_binding("IMPORT_BINDING"). +js_not_a_data_binding("FUNCTION_DECLARATION_HOISTED"). +js_not_a_data_binding("CLASS_TDZ"). +js_not_a_data_binding("CATCH_PARAMETER"). + +// ── export_var(Module, ExportedName, Variable) — the surface, by variable ─── +export_var(mod, n, v) :- export_decl("client", n, _, _, _, "VARIABLE", v, _, mod, _), n != "", js_module_var(v). +export_var(mod, n, v) :- export_decl("client", en, _, "EXPORT_ALL", _, _, _, _, mod, x), export_all_is_spread(en), + export_reexport(_, _, imp, x), import_module(imp, src), + export_var(src, n, v), n != "default". +export_var(mod, en, v) :- export_decl("client", en, ln, form, _, _, _, _, mod, x), form != "EXPORT_ALL", + export_reexport(_, _, imp, x), import_module(imp, src), ln != "", + export_var(src, ln, v). +// ── import_var(ImportHash, Variable) — what a named import binding stands for ─ +import_var(imp, v) :- import_decl("client", _, _, bf, n, _, _, _, imp), import_binding_is_named(bf), n != "", + !contains(".", n), import_module(imp, mod), export_var(mod, n, v). + +// ── the site ──────────────────────────────────────────────────────────────── +js_var_read_target(e, v) :- expr_kind("client", "IDENTIFIER", _, e), expr_binding("client", v, e), js_module_var(v). +js_var_read_target(e, v) :- expr_kind("client", "IDENTIFIER", _, e), expr_binding("client", lv, e), + var_import("client", imp, lv), import_var(imp, v). +// A plain `=` never reads the old value; `+=` and `++` read and then write. +js_var_written(e) :- js_var_read_target(e, _), expr_kind(_, "ASSIGNMENT", _, a), expr_child(_, a, "ASSIGNMENT_TARGET", _, e). +js_var_written(e) :- js_var_read_target(e, _), expr_kind(_, "UNARY", _, u), expr_operator(_, op, u), update_operator(op), + expr_child(_, u, _, _, e). +js_var_write_only(e) :- js_var_read_target(e, _), expr_kind(_, "ASSIGNMENT", _, a), expr_operator(_, "=", a), + expr_child(_, a, "ASSIGNMENT_TARGET", _, e). +js_var_access(e, "write") :- js_var_written(e), js_var_write_only(e). +js_var_access(e, "readwrite") :- js_var_written(e), !js_var_write_only(e). +js_var_access(e, "read") :- js_var_read_target(e, _), !js_var_written(e). +// the caller: the callable the expression is written in, else the module initializer +js_var_read_from(e, m) :- js_var_read_target(e, _), expr_owner("client", m, _, e), m != "". +js_var_read_from(e, init) :- js_var_read_target(e, _), expr_owner("client", "", mod, e), module_init(_, init, mod). + +field_access(e, caller, v, "client", "known_edge", acc) :- js_var_read_target(e, v), + js_var_read_from(e, caller), js_var_access(e, acc). diff --git a/graph/javascript/souffle/decls_all.dl b/graph/javascript/souffle/decls_all.dl index 7f73c459..66bbbbab 100644 --- a/graph/javascript/souffle/decls_all.dl +++ b/graph/javascript/souffle/decls_all.dl @@ -232,6 +232,18 @@ .decl var_owner_method(c0:symbol, c1:symbol, c2:symbol) .decl var_type_name(c0:symbol, c1:symbol, c2:symbol, c3:symbol) .decl var_pattern_root(c0:symbol, c1:symbol, c2:symbol) + +// ── call-edge-generation/field_access.dl ── +.decl js_module_var(c0:symbol) +.decl js_not_a_data_binding(c0:symbol) +.decl export_var(c0:symbol, c1:symbol, c2:symbol) +.decl import_var(c0:symbol, c1:symbol) +.decl js_var_read_target(c0:symbol, c1:symbol) +.decl js_var_written(c0:symbol) +.decl js_var_write_only(c0:symbol) +.decl js_var_access(c0:symbol, c1:symbol) +.decl js_var_read_from(c0:symbol, c1:symbol) +.decl field_access(c0:symbol, c1:symbol, c2:symbol, c3:symbol, c4:symbol, c5:symbol) .decl var_binding_form(c0:symbol, c1:symbol, c2:symbol) // ── resolution/module-graph.dl ── diff --git a/graph/javascript/souffle/export_manifest.tsv b/graph/javascript/souffle/export_manifest.tsv index 1e8b5aac..cd4d00c1 100644 --- a/graph/javascript/souffle/export_manifest.tsv +++ b/graph/javascript/souffle/export_manifest.tsv @@ -42,3 +42,4 @@ package_entry package-entry.csv import_staged_package_unreached import-staged-package-unreached.csv member_write_refused member-write-refused.csv jsx_renders jsx-renders.csv +field_access field-access.csv diff --git a/graph/test/typescript/cases/86-module-variable-reads/src/a/src/cfg.ts b/graph/test/typescript/cases/86-module-variable-reads/src/a/src/cfg.ts new file mode 100644 index 00000000..f78a28dc --- /dev/null +++ b/graph/test/typescript/cases/86-module-variable-reads/src/a/src/cfg.ts @@ -0,0 +1,21 @@ +// A module-scope const read through member access, as a call argument, in a template and +// under a type query: every use of CFG / S / TOKENS below binds to THIS file's declaration. +export const CFG = { name: 'a', port: 1 } as const; +export const S = mk({}); +export const TOKENS = { Store: 'store' } as const; +export let counter = 0; +// a const that holds a function is a function: its uses are calls, not reads (control) +export const make = (n: number) => n + 1; + +export function mk(o: object): object { + return o; +} + +export function localUse(): string { + return CFG.name; +} + +export function bump(): void { + counter += 1; + counter = 0; +} diff --git a/graph/test/typescript/cases/86-module-variable-reads/src/a/src/index.ts b/graph/test/typescript/cases/86-module-variable-reads/src/a/src/index.ts new file mode 100644 index 00000000..348509d6 --- /dev/null +++ b/graph/test/typescript/cases/86-module-variable-reads/src/a/src/index.ts @@ -0,0 +1 @@ +export * from './cfg'; diff --git a/graph/test/typescript/cases/86-module-variable-reads/src/a/test/t.ts b/graph/test/typescript/cases/86-module-variable-reads/src/a/test/t.ts new file mode 100644 index 00000000..97b17b45 --- /dev/null +++ b/graph/test/typescript/cases/86-module-variable-reads/src/a/test/t.ts @@ -0,0 +1,47 @@ +import { CFG, S, TOKENS, make } from '../src'; + +function use(x: unknown): void {} + +export function viaMember(): void { + use(CFG.name); +} + +export function asArgument(): void { + use(S); +} + +export function inTemplate(): string { + return `${CFG.name}-${TOKENS.Store}`; +} + +export function callsTheFunction(): number { + return make(1); +} + +export function shadowed(): string { + // a local of the same name: the read is the local's, not the module's (control) + const CFG = { name: 'local' }; + return CFG.name; +} + +// a read in a type: at module level it is the module's, in a signature the function's +type T = typeof S; + +export function annotated(x: typeof CFG): string { + return x.name; +} + +// a parameter of the same name: `typeof TOKENS` here is the parameter's type (control) +export function shadowedInType(TOKENS: number): typeof TOKENS { + return TOKENS; +} + +function Route(x: unknown): MethodDecorator { return () => {}; } +function Body(x: unknown): ParameterDecorator { return () => {}; } +class Pipe { constructor(readonly s: unknown) {} } + +export class Handler { + // a read in a decorator is the decorated method's: on the method, and on its parameter + @Route(TOKENS.Store) + handle(@Body(new Pipe(S)) body: unknown): void {} +} diff --git a/graph/test/typescript/cases/86-module-variable-reads/src/b/src/settings.ts b/graph/test/typescript/cases/86-module-variable-reads/src/b/src/settings.ts new file mode 100644 index 00000000..74ad08dd --- /dev/null +++ b/graph/test/typescript/cases/86-module-variable-reads/src/b/src/settings.ts @@ -0,0 +1,3 @@ +// The same names, declared again in a sibling package: reads here bind here (control). +export const CFG = { name: 'b', port: 2 } as const; +export const S = {}; diff --git a/graph/test/typescript/cases/86-module-variable-reads/src/b/test/sibling.ts b/graph/test/typescript/cases/86-module-variable-reads/src/b/test/sibling.ts new file mode 100644 index 00000000..90a49c65 --- /dev/null +++ b/graph/test/typescript/cases/86-module-variable-reads/src/b/test/sibling.ts @@ -0,0 +1,5 @@ +import { CFG, S } from '../src/settings'; + +export function siblingRead(): unknown { + return [CFG.port, S]; +} diff --git a/graph/test/typescript/cases/86-module-variable-reads/src/tsconfig.json b/graph/test/typescript/cases/86-module-variable-reads/src/tsconfig.json new file mode 100644 index 00000000..65ed3564 --- /dev/null +++ b/graph/test/typescript/cases/86-module-variable-reads/src/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "experimentalDecorators": true, + "noEmit": true + } +} diff --git a/graph/test/typescript/expected/17-constrained-generics.fields b/graph/test/typescript/expected/17-constrained-generics.fields index 51e56615..f0dbf2a4 100644 --- a/graph/test/typescript/expected/17-constrained-generics.fields +++ b/graph/test/typescript/expected/17-constrained-generics.fields @@ -1,3 +1,7 @@ ambiguous_unknown read consumer#ownConstraint(T) -> - ambiguous_unknown read consumer#ownConstraintNamed(T) -> - ambiguous_unknown read curried-local#widest(unknown) -> - +known_edge read consumer#dataFirst() -> consumer#rows +known_edge read consumer#localDataFirst() -> consumer#rows +known_edge read consumer#localWidest() -> consumer#rows +known_edge read consumer#widestFirst() -> consumer#rows diff --git a/graph/test/typescript/expected/17-constrained-generics.fields-oracle b/graph/test/typescript/expected/17-constrained-generics.fields-oracle index 320aafb9..d6d71ba2 100644 --- a/graph/test/typescript/expected/17-constrained-generics.fields-oracle +++ b/graph/test/typescript/expected/17-constrained-generics.fields-oracle @@ -1,7 +1,7 @@ 17-constrained-generics [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 0 the compiler resolved) - sites 3 resolved 0 (0.0%) - tiers ambiguous_unknown=3 - access read=3 - not scored: 3 rows whose target is not a client declaration + sites 7 resolved 4 (57.1%) + tiers ambiguous_unknown=3 known_edge=4 + access read=7 + not scored: 7 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/36-function-value-containers.fields b/graph/test/typescript/expected/36-function-value-containers.fields index e69de29b..741ac6a1 100644 --- a/graph/test/typescript/expected/36-function-value-containers.fields +++ b/graph/test/typescript/expected/36-function-value-containers.fields @@ -0,0 +1 @@ +known_edge read r229#callObjectLiteral(number) -> r229#handlers diff --git a/graph/test/typescript/expected/36-function-value-containers.fields-oracle b/graph/test/typescript/expected/36-function-value-containers.fields-oracle index 6fb4ee24..152dfcc5 100644 --- a/graph/test/typescript/expected/36-function-value-containers.fields-oracle +++ b/graph/test/typescript/expected/36-function-value-containers.fields-oracle @@ -1,9 +1,9 @@ 36-function-value-containers [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 2 the compiler resolved) - sites 0 resolved 0 - tiers - access - not scored: 0 rows whose target is not a client declaration + sites 2 resolved 2 (100.0%) + tiers known_edge=2 + access read=2 + not scored: 2 rows whose target is not a client declaration MISSING r229#callClassMembers(number) READ Holder#viaField MISSING r229#callObjectLiteral(number) READ r229#viaArrow diff --git a/graph/test/typescript/expected/49-indexed-access-return.fields b/graph/test/typescript/expected/49-indexed-access-return.fields index 64f7525f..3f375c0b 100644 --- a/graph/test/typescript/expected/49-indexed-access-return.fields +++ b/graph/test/typescript/expected/49-indexed-access-return.fields @@ -1,2 +1,5 @@ known_edge read main#getDog() -> Zoo#dog +known_edge read main#getDog() -> main#zoo known_edge read main#getDogPlain() -> Zoo#dog +known_edge read main#getDogPlain() -> main#zoo +known_edge read main#pick(T) -> main#zoo diff --git a/graph/test/typescript/expected/49-indexed-access-return.fields-oracle b/graph/test/typescript/expected/49-indexed-access-return.fields-oracle index 2ff6d466..198a9f5b 100644 --- a/graph/test/typescript/expected/49-indexed-access-return.fields-oracle +++ b/graph/test/typescript/expected/49-indexed-access-return.fields-oracle @@ -1,7 +1,7 @@ 49-indexed-access-return [fields] precision 1.0000 (2 correct, 0 wrong) recall 1.0000 (2 of 2 the compiler resolved) - sites 2 resolved 2 (100.0%) - tiers known_edge=2 - access read=2 - not scored: 0 rows whose target is not a client declaration + sites 5 resolved 5 (100.0%) + tiers known_edge=5 + access read=5 + not scored: 3 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/62-field-access-and-type-use.fields b/graph/test/typescript/expected/62-field-access-and-type-use.fields index 32b614fb..5659500c 100644 --- a/graph/test/typescript/expected/62-field-access-and-type-use.fields +++ b/graph/test/typescript/expected/62-field-access-and-type-use.fields @@ -8,6 +8,7 @@ known_edge read Box#run() -> Box#label known_edge read Box#staticRead() -> Box#static total known_edge read Box#viaInterface(Shaped) -> Shaped#width known_edge read Narrow#own() -> Narrow#width +known_edge read members#Shaped2() -> members#Marker2 known_edge readwrite Box#compound(number) -> Box#width known_edge readwrite Box#decrement() -> Box#hidden known_edge readwrite Box#increment() -> Box#hidden diff --git a/graph/test/typescript/expected/62-field-access-and-type-use.fields-oracle b/graph/test/typescript/expected/62-field-access-and-type-use.fields-oracle index 5964a26b..e23a07d7 100644 --- a/graph/test/typescript/expected/62-field-access-and-type-use.fields-oracle +++ b/graph/test/typescript/expected/62-field-access-and-type-use.fields-oracle @@ -1,7 +1,7 @@ 62-field-access-and-type-use [fields] precision 1.0000 (20 correct, 0 wrong) recall 1.0000 (20 of 20 the compiler resolved) - sites 19 resolved 18 (94.7%) - tiers ambiguous_unknown=1 known_edge=18 - access read=11 readwrite=3 write=5 - not scored: 1 rows whose target is not a client declaration + sites 20 resolved 19 (95.0%) + tiers ambiguous_unknown=1 known_edge=19 + access read=12 readwrite=3 write=5 + not scored: 2 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/63-annotated-callable-member.fields b/graph/test/typescript/expected/63-annotated-callable-member.fields index e69de29b..fe88d80b 100644 --- a/graph/test/typescript/expected/63-annotated-callable-member.fields +++ b/graph/test/typescript/expected/63-annotated-callable-member.fields @@ -0,0 +1 @@ +known_edge read members#callVarAmbient() -> members#varAmbient diff --git a/graph/test/typescript/expected/63-annotated-callable-member.fields-oracle b/graph/test/typescript/expected/63-annotated-callable-member.fields-oracle index 8e35ea27..146bd47f 100644 --- a/graph/test/typescript/expected/63-annotated-callable-member.fields-oracle +++ b/graph/test/typescript/expected/63-annotated-callable-member.fields-oracle @@ -1,9 +1,9 @@ 63-annotated-callable-member [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 2 the compiler resolved) - sites 0 resolved 0 - tiers - access - not scored: 0 rows whose target is not a client declaration + 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 members#callPropInline(PropInline) READ PropInline#run MISSING members#callPropNamed(PropNamed) READ PropNamed#run diff --git a/graph/test/typescript/expected/64-array-dispatch.fields b/graph/test/typescript/expected/64-array-dispatch.fields index b1252eaf..12ff9d9f 100644 --- a/graph/test/typescript/expected/64-array-dispatch.fields +++ b/graph/test/typescript/expected/64-array-dispatch.fields @@ -1 +1,4 @@ +known_edge read pipeline#runByName(Node) -> pipeline#byName +known_edge read pipeline#runIndexed(Node) -> pipeline#steps +known_edge read pipeline#runIterated(Node) -> pipeline#steps known_edge read steps#measure(Node) -> { width: number }#width diff --git a/graph/test/typescript/expected/64-array-dispatch.fields-oracle b/graph/test/typescript/expected/64-array-dispatch.fields-oracle index cedeaacf..86bafa38 100644 --- a/graph/test/typescript/expected/64-array-dispatch.fields-oracle +++ b/graph/test/typescript/expected/64-array-dispatch.fields-oracle @@ -1,10 +1,10 @@ 64-array-dispatch [fields] precision 0.0000 (0 correct, 1 wrong) recall 0.0000 (0 of 2 the compiler resolved) - sites 1 resolved 1 (100.0%) - tiers known_edge=1 - access read=1 - not scored: 0 rows whose target is not a client declaration + sites 4 resolved 4 (100.0%) + tiers known_edge=4 + access read=4 + not scored: 3 rows whose target is not a client declaration WRONG steps#measure(Node) READ { width: number }#width MISSING pipeline#runByName(Node) READ pipeline#square MISSING steps#measure(Node) READ steps#width diff --git a/graph/test/typescript/expected/65-annotated-callable-fewer-parameters.fields b/graph/test/typescript/expected/65-annotated-callable-fewer-parameters.fields index e69de29b..5310006c 100644 --- a/graph/test/typescript/expected/65-annotated-callable-fewer-parameters.fields +++ b/graph/test/typescript/expected/65-annotated-callable-fewer-parameters.fields @@ -0,0 +1,3 @@ +known_edge read fewer#callAmbient() -> fewer#ambient +known_edge read fewer#callSiblingsOne() -> fewer#siblings +known_edge read fewer#callSiblingsTwo() -> fewer#siblings diff --git a/graph/test/typescript/expected/65-annotated-callable-fewer-parameters.fields-oracle b/graph/test/typescript/expected/65-annotated-callable-fewer-parameters.fields-oracle index 0d95ec28..a5e21475 100644 --- a/graph/test/typescript/expected/65-annotated-callable-fewer-parameters.fields-oracle +++ b/graph/test/typescript/expected/65-annotated-callable-fewer-parameters.fields-oracle @@ -1,8 +1,8 @@ 65-annotated-callable-fewer-parameters [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 1 the compiler resolved) - sites 0 resolved 0 - tiers - access - not scored: 0 rows whose target is not a client declaration + sites 3 resolved 3 (100.0%) + tiers known_edge=3 + access read=3 + not scored: 3 rows whose target is not a client declaration MISSING fewer#callHolderRun(Holder) READ Holder#run diff --git a/graph/test/typescript/expected/69-named-and-inline-handlers.fields b/graph/test/typescript/expected/69-named-and-inline-handlers.fields index e69de29b..ff8c71ee 100644 --- a/graph/test/typescript/expected/69-named-and-inline-handlers.fields +++ b/graph/test/typescript/expected/69-named-and-inline-handlers.fields @@ -0,0 +1,3 @@ +known_edge read routes#() -> routes#app +known_edge read routes#() -> routes#cache +known_edge read routes#() -> routes#headers diff --git a/graph/test/typescript/expected/69-named-and-inline-handlers.fields-oracle b/graph/test/typescript/expected/69-named-and-inline-handlers.fields-oracle index ba7a4326..f63426fd 100644 --- a/graph/test/typescript/expected/69-named-and-inline-handlers.fields-oracle +++ b/graph/test/typescript/expected/69-named-and-inline-handlers.fields-oracle @@ -1,7 +1,7 @@ 69-named-and-inline-handlers [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 0 the compiler resolved) - sites 0 resolved 0 - tiers - access - not scored: 0 rows whose target is not a client declaration + sites 4 resolved 4 (100.0%) + tiers known_edge=4 + access read=4 + not scored: 4 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/72-object-literal-and-expression-callees.fields b/graph/test/typescript/expected/72-object-literal-and-expression-callees.fields index 97e25270..c12de5c7 100644 --- a/graph/test/typescript/expected/72-object-literal-and-expression-callees.fields +++ b/graph/test/typescript/expected/72-object-literal-and-expression-callees.fields @@ -1 +1,2 @@ known_edge read fetcher#get(string,Options) -> Options#fetch +known_edge read pool#main() -> pool#fixed diff --git a/graph/test/typescript/expected/72-object-literal-and-expression-callees.fields-oracle b/graph/test/typescript/expected/72-object-literal-and-expression-callees.fields-oracle index 91b3cf30..7a227b92 100644 --- a/graph/test/typescript/expected/72-object-literal-and-expression-callees.fields-oracle +++ b/graph/test/typescript/expected/72-object-literal-and-expression-callees.fields-oracle @@ -1,8 +1,8 @@ 72-object-literal-and-expression-callees [fields] precision 1.0000 (1 correct, 0 wrong) recall 0.5000 (1 of 2 the compiler resolved) - sites 1 resolved 1 (100.0%) - tiers known_edge=1 - access read=1 - not scored: 0 rows whose target is not a client declaration + sites 2 resolved 2 (100.0%) + tiers known_edge=2 + access read=2 + not scored: 1 rows whose target is not a client declaration MISSING registry#price(number) READ Registry#static format diff --git a/graph/test/typescript/expected/74-jsx-component-forms.fields b/graph/test/typescript/expected/74-jsx-component-forms.fields index 1aa6da98..6dcde21d 100644 --- a/graph/test/typescript/expected/74-jsx-component-forms.fields +++ b/graph/test/typescript/expected/74-jsx-component-forms.fields @@ -1,6 +1,11 @@ ambiguous_unknown read parts#Badge(?) -> - known_edge read Panel#render() -> Panel#props known_edge read Panel#render() -> { title: string }#title +known_edge read app#App() -> app#Settings +known_edge read app#App() -> parts#Field +known_edge read app#App() -> parts#UserCard +known_edge read app#App() -> parts#ui +known_edge read app#useFactory() -> parts#factory known_edge read parts#Avatar({ src: string }) -> { src: string }#src known_edge read parts#Chip({ text: string }) -> { text: string }#text known_edge read parts#FieldImpl({ label: string },unknown) -> { label: string }#label diff --git a/graph/test/typescript/expected/74-jsx-component-forms.fields-oracle b/graph/test/typescript/expected/74-jsx-component-forms.fields-oracle index da9a8ee2..e2cf577f 100644 --- a/graph/test/typescript/expected/74-jsx-component-forms.fields-oracle +++ b/graph/test/typescript/expected/74-jsx-component-forms.fields-oracle @@ -1,10 +1,10 @@ 74-jsx-component-forms [fields] precision 0.2857 (2 correct, 5 wrong) recall 0.2500 (2 of 8 the compiler resolved) - sites 8 resolved 7 (87.5%) - tiers ambiguous_unknown=1 known_edge=7 - access read=7 write=1 - not scored: 1 rows whose target is not a client declaration + sites 13 resolved 12 (92.3%) + tiers ambiguous_unknown=1 known_edge=12 + access read=12 write=1 + not scored: 6 rows whose target is not a client declaration WRONG Panel#render() READ { title: string }#title WRONG parts#Avatar({ src: string }) READ { src: string }#src WRONG parts#Chip({ text: string }) READ { text: string }#text diff --git a/graph/test/typescript/expected/75-jsx-wrapper-guards.fields b/graph/test/typescript/expected/75-jsx-wrapper-guards.fields index 3da15a75..82a233a4 100644 --- a/graph/test/typescript/expected/75-jsx-wrapper-guards.fields +++ b/graph/test/typescript/expected/75-jsx-wrapper-guards.fields @@ -2,4 +2,14 @@ ambiguous_unknown read app#(?) -> - ambiguous_unknown read app#(?) -> - ambiguous_unknown read app#(?) -> - ambiguous_unknown read app#() -> - +known_edge read app#Page() -> app#DynAwait +known_edge read app#Page() -> app#DynDefault +known_edge read app#Page() -> app#DynNamed +known_edge read app#Page() -> app#LazyBlock +known_edge read app#Page() -> app#LazyDefault +known_edge read app#Page() -> app#LazyNamed +known_edge read app#Page() -> app#Made +known_edge read app#Page() -> app#Memoed +known_edge read app#Page() -> app#Observed +known_edge read app#Page() -> app#Thing known_edge read ui/Button#Button({ label: string }) -> { label: string }#label diff --git a/graph/test/typescript/expected/75-jsx-wrapper-guards.fields-oracle b/graph/test/typescript/expected/75-jsx-wrapper-guards.fields-oracle index 3617bfdb..aaddbd64 100644 --- a/graph/test/typescript/expected/75-jsx-wrapper-guards.fields-oracle +++ b/graph/test/typescript/expected/75-jsx-wrapper-guards.fields-oracle @@ -1,9 +1,9 @@ 75-jsx-wrapper-guards [fields] precision 0.0000 (0 correct, 1 wrong) recall 0.0000 (0 of 1 the compiler resolved) - sites 5 resolved 1 (20.0%) - tiers ambiguous_unknown=4 known_edge=1 - access read=5 - not scored: 4 rows whose target is not a client declaration + sites 15 resolved 11 (73.3%) + tiers ambiguous_unknown=4 known_edge=11 + access read=15 + not scored: 14 rows whose target is not a client declaration WRONG ui/Button#Button({ label: string }) READ { label: string }#label MISSING ui/Button#Button({ label: string }) READ ui/Button#label diff --git a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields index ce8e4343..da638d2c 100644 --- a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields +++ b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields @@ -1,2 +1,6 @@ ambiguous_unknown read app#(?) -> - ambiguous_unknown read app#useName() -> - +known_edge read app#App() -> app#LazyDefault +known_edge read app#App() -> app#LazyNamedObj +known_edge read app#App() -> app#MemoPicked +known_edge read app#App() -> parts#MemoFwd diff --git a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields-oracle b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields-oracle index c4ca47b6..371b7582 100644 --- a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields-oracle +++ b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields-oracle @@ -1,7 +1,7 @@ 76-jsx-default-nested-wrappers [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 0 the compiler resolved) - sites 2 resolved 0 (0.0%) - tiers ambiguous_unknown=2 - access read=2 - not scored: 2 rows whose target is not a client declaration + sites 6 resolved 4 (66.7%) + tiers ambiguous_unknown=2 known_edge=4 + access read=6 + not scored: 6 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/77-package-published-entries.fields b/graph/test/typescript/expected/77-package-published-entries.fields index 0233e076..ccc83f66 100644 --- a/graph/test/typescript/expected/77-package-published-entries.fields +++ b/graph/test/typescript/expected/77-package-published-entries.fields @@ -1,2 +1,3 @@ ambiguous_unknown read index#useStore(number) -> - ambiguous_unknown read plugins/logger#() -> - +known_edge read vanilla#() -> vanilla#DEFAULT_INITIAL diff --git a/graph/test/typescript/expected/77-package-published-entries.fields-oracle b/graph/test/typescript/expected/77-package-published-entries.fields-oracle index 85d59b99..72c9bcde 100644 --- a/graph/test/typescript/expected/77-package-published-entries.fields-oracle +++ b/graph/test/typescript/expected/77-package-published-entries.fields-oracle @@ -1,8 +1,8 @@ 77-package-published-entries [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 1 the compiler resolved) - sites 2 resolved 0 (0.0%) - tiers ambiguous_unknown=2 - access read=2 - not scored: 2 rows whose target is not a client declaration + sites 3 resolved 1 (33.3%) + tiers ambiguous_unknown=2 known_edge=1 + access read=3 + not scored: 3 rows whose target is not a client declaration MISSING index#useStore(number) READ vanilla#value diff --git a/graph/test/typescript/expected/78-callable-collections.fields b/graph/test/typescript/expected/78-callable-collections.fields index b5e742ff..a43f3a76 100644 --- a/graph/test/typescript/expected/78-callable-collections.fields +++ b/graph/test/typescript/expected/78-callable-collections.fields @@ -1,2 +1,17 @@ ambiguous_unknown read pipeline#countIdle() -> - +known_edge read pipeline#() -> pipeline#registry +known_edge read pipeline#() -> pipeline#steps +known_edge read pipeline#countIdle() -> pipeline#idle +known_edge read pipeline#keysOnly() -> pipeline#table +known_edge read pipeline#runByKey(Node,'grow' | 'shrink') -> pipeline#table +known_edge read pipeline#runByName(Node) -> pipeline#table +known_edge read pipeline#runForEach(Node) -> pipeline#steps +known_edge read pipeline#runFrozen(Node) -> pipeline#frozen +known_edge read pipeline#runImported(Node) -> steps#exportedSteps +known_edge read pipeline#runIndexed(Node,number) -> pipeline#steps +known_edge read pipeline#runIterated(Node) -> pipeline#steps +known_edge read pipeline#runReduce(Node) -> pipeline#frozen +known_edge read pipeline#runRegistry(Node) -> pipeline#extended +known_edge read pipeline#runRegistry(Node) -> pipeline#registry +known_edge read pipeline#runValues(Node) -> pipeline#table known_edge read steps#measure(Node) -> { width: number }#width diff --git a/graph/test/typescript/expected/78-callable-collections.fields-oracle b/graph/test/typescript/expected/78-callable-collections.fields-oracle index 8fc5cf9a..0fe6772f 100644 --- a/graph/test/typescript/expected/78-callable-collections.fields-oracle +++ b/graph/test/typescript/expected/78-callable-collections.fields-oracle @@ -1,10 +1,10 @@ 78-callable-collections [fields] precision 0.0000 (0 correct, 1 wrong) recall 0.0000 (0 of 2 the compiler resolved) - sites 2 resolved 1 (50.0%) - tiers ambiguous_unknown=1 known_edge=1 - access read=2 - not scored: 1 rows whose target is not a client declaration + sites 18 resolved 17 (94.4%) + tiers ambiguous_unknown=1 known_edge=17 + access read=18 + not scored: 17 rows whose target is not a client declaration WRONG steps#measure(Node) READ { width: number }#width MISSING pipeline#runByName(Node) READ pipeline#shrink MISSING steps#measure(Node) READ steps#width diff --git a/graph/test/typescript/expected/78-cross-process-destinations.fields b/graph/test/typescript/expected/78-cross-process-destinations.fields index 4ff36cd9..1d6b842c 100644 --- a/graph/test/typescript/expected/78-cross-process-destinations.fields +++ b/graph/test/typescript/expected/78-cross-process-destinations.fields @@ -1 +1,14 @@ +known_edge read client/axios#() -> axios.d#axios +known_edge read client/client#() -> axios.d#axios +known_edge read client/client#audit() -> axios.d#axios +known_edge read client/client#cached() -> client#cache +known_edge read client/client#dailyReport(string) -> axios.d#axios +known_edge read client/client#dailyReport(string) -> config#config known_edge read client/client#dailyReport(string) -> shared/config#reportsUrl +known_edge read client/client#invoice(string) -> client#billing +known_edge read client/client#listAll() -> paths#ORDERS +known_edge read client/client#placeOrder(unknown) -> axios.d#axios +known_edge read client/client#placeOrder(unknown) -> paths#ORDERS +known_edge read server/orders#() -> orders#app +known_edge read server/orders#() -> orders#router +known_edge read server/orders#() -> paths#ORDERS diff --git a/graph/test/typescript/expected/78-cross-process-destinations.fields-oracle b/graph/test/typescript/expected/78-cross-process-destinations.fields-oracle index 0539a6dc..2244ae08 100644 --- a/graph/test/typescript/expected/78-cross-process-destinations.fields-oracle +++ b/graph/test/typescript/expected/78-cross-process-destinations.fields-oracle @@ -1,7 +1,7 @@ 78-cross-process-destinations [fields] 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: 0 rows whose target is not a client declaration + sites 18 resolved 18 (100.0%) + tiers known_edge=18 + access read=18 + not scored: 17 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/78-hof-callback-at-library-boundary.fields b/graph/test/typescript/expected/78-hof-callback-at-library-boundary.fields index e69de29b..ca48bcea 100644 --- a/graph/test/typescript/expected/78-hof-callback-at-library-boundary.fields +++ b/graph/test/typescript/expected/78-hof-callback-at-library-boundary.fields @@ -0,0 +1,12 @@ +known_edge read app#(?) -> rate#RATE +known_edge read app#() -> rate#RATE +known_edge read app#(?) -> rate#RATE +known_edge read app#boot() -> app#host +known_edge read app#boot() -> plugin#plugin +known_edge read app#bootBare() -> app#host +known_edge read app#bootSettings() -> app#host +known_edge read app#bootSettings() -> plugin#settings +known_edge read app#record(number) -> app#seen +known_edge read store#known() -> store#subs +known_edge read store#store() -> store#handlers +known_edge read store#store() -> store#subs diff --git a/graph/test/typescript/expected/78-hof-callback-at-library-boundary.fields-oracle b/graph/test/typescript/expected/78-hof-callback-at-library-boundary.fields-oracle index be4f8be6..8c2412a1 100644 --- a/graph/test/typescript/expected/78-hof-callback-at-library-boundary.fields-oracle +++ b/graph/test/typescript/expected/78-hof-callback-at-library-boundary.fields-oracle @@ -1,7 +1,7 @@ 78-hof-callback-at-library-boundary [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 0 the compiler resolved) - sites 0 resolved 0 - tiers - access - not scored: 0 rows whose target is not a client declaration + sites 12 resolved 12 (100.0%) + tiers known_edge=12 + access read=12 + not scored: 12 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/79-package-published-members.fields b/graph/test/typescript/expected/79-package-published-members.fields index e69de29b..ec69ab45 100644 --- a/graph/test/typescript/expected/79-package-published-members.fields +++ b/graph/test/typescript/expected/79-package-published-members.fields @@ -0,0 +1,3 @@ +known_edge read src/bound#() -> bound#engine +known_edge read src/index.test#() -> api#api +known_edge read src/index.test#() -> bound#produce diff --git a/graph/test/typescript/expected/79-package-published-members.fields-oracle b/graph/test/typescript/expected/79-package-published-members.fields-oracle index bf1e0d9b..f5234231 100644 --- a/graph/test/typescript/expected/79-package-published-members.fields-oracle +++ b/graph/test/typescript/expected/79-package-published-members.fields-oracle @@ -1,7 +1,7 @@ 79-package-published-members [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 0 the compiler resolved) - sites 0 resolved 0 - tiers - access - not scored: 0 rows whose target is not a client declaration + sites 5 resolved 5 (100.0%) + tiers known_edge=5 + access read=5 + not scored: 5 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/80-generic-return-receiver.fields b/graph/test/typescript/expected/80-generic-return-receiver.fields index b7d27851..e6f9deac 100644 --- a/graph/test/typescript/expected/80-generic-return-receiver.fields +++ b/graph/test/typescript/expected/80-generic-return-receiver.fields @@ -1,2 +1,10 @@ known_edge read Circ#area() -> Circ#r known_edge read Sq#area() -> Sq#s +known_edge read use#() -> use#WARRIOR +known_edge read use#() -> use#container +known_edge read use#ctlAnyIndex(string) -> use#anyBag +known_edge read use#indexValue(string) -> use#shapes +known_edge read use#recordArea(string,number) -> use#makers +known_edge read use#run() -> use#WARRIOR +known_edge read use#run() -> use#container +known_edge read use#totalArea(string,number) -> use#factories diff --git a/graph/test/typescript/expected/80-generic-return-receiver.fields-oracle b/graph/test/typescript/expected/80-generic-return-receiver.fields-oracle index f770da18..09fc58b0 100644 --- a/graph/test/typescript/expected/80-generic-return-receiver.fields-oracle +++ b/graph/test/typescript/expected/80-generic-return-receiver.fields-oracle @@ -1,7 +1,7 @@ 80-generic-return-receiver [fields] precision 1.0000 (2 correct, 0 wrong) recall 1.0000 (2 of 2 the compiler resolved) - sites 4 resolved 4 (100.0%) - tiers known_edge=4 - access read=4 - not scored: 0 rows whose target is not a client declaration + sites 12 resolved 12 (100.0%) + tiers known_edge=12 + access read=12 + not scored: 8 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/80-object-literal-member-receivers.fields b/graph/test/typescript/expected/80-object-literal-member-receivers.fields index 5903cc81..493c648a 100644 --- a/graph/test/typescript/expected/80-object-literal-member-receivers.fields +++ b/graph/test/typescript/expected/80-object-literal-member-receivers.fields @@ -1,3 +1,9 @@ +known_edge read local#drive() -> local#handlers known_edge read local#drive() -> local#jobs +known_edge read local#trimName() -> local#cfg known_edge read local#trimName() -> local#name +known_edge read use#callGreeter() -> api#greeter +known_edge read use#callNested() -> api#api known_edge read use#callNested() -> api#users +known_edge read use#callTop() -> api#api +known_edge read use#keepUnrelated() -> use#unrelated diff --git a/graph/test/typescript/expected/80-object-literal-member-receivers.fields-oracle b/graph/test/typescript/expected/80-object-literal-member-receivers.fields-oracle index 15e91dc5..b41e6134 100644 --- a/graph/test/typescript/expected/80-object-literal-member-receivers.fields-oracle +++ b/graph/test/typescript/expected/80-object-literal-member-receivers.fields-oracle @@ -1,10 +1,10 @@ 80-object-literal-member-receivers [fields] 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: 0 rows whose target is not a client declaration + sites 17 resolved 17 (100.0%) + tiers known_edge=17 + access read=17 + not scored: 11 rows whose target is not a client declaration MISSING local#drive() READ local#run MISSING use#callNested() READ api#byName MISSING use#callNested() READ api#get diff --git a/graph/test/typescript/expected/80-vue-definecomponent-jsx.fields b/graph/test/typescript/expected/80-vue-definecomponent-jsx.fields index e69de29b..d5c739df 100644 --- a/graph/test/typescript/expected/80-vue-definecomponent-jsx.fields +++ b/graph/test/typescript/expected/80-vue-definecomponent-jsx.fields @@ -0,0 +1,9 @@ +known_edge read app#() -> parts#Child +known_edge read app#() -> parts#FnForm +known_edge read app#() -> parts#NuxtCard +known_edge read app#() -> parts#Panel +known_edge read app#() -> parts#Store +known_edge read app#() -> reg#Button +known_edge read app#() -> reg#Local +known_edge read card#render() -> parts#Child +known_edge read reg#() -> reg#_Local diff --git a/graph/test/typescript/expected/80-vue-definecomponent-jsx.fields-oracle b/graph/test/typescript/expected/80-vue-definecomponent-jsx.fields-oracle index 7aa79d05..cc14c676 100644 --- a/graph/test/typescript/expected/80-vue-definecomponent-jsx.fields-oracle +++ b/graph/test/typescript/expected/80-vue-definecomponent-jsx.fields-oracle @@ -1,7 +1,7 @@ 80-vue-definecomponent-jsx [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 0 the compiler resolved) - sites 0 resolved 0 - tiers - access - not scored: 0 rows whose target is not a client declaration + sites 9 resolved 9 (100.0%) + tiers known_edge=9 + access read=9 + not scored: 9 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/80-vue-sfc.fields b/graph/test/typescript/expected/80-vue-sfc.fields index 2c586263..7fae20e7 100644 --- a/graph/test/typescript/expected/80-vue-sfc.fields +++ b/graph/test/typescript/expected/80-vue-sfc.fields @@ -1 +1,3 @@ +known_edge read Comp.vue#() -> Comp#total known_edge read child#Child({ msg: string }) -> { msg: string }#msg +known_edge read shims-vue#() -> shims-vue.d#component diff --git a/graph/test/typescript/expected/80-vue-sfc.fields-oracle b/graph/test/typescript/expected/80-vue-sfc.fields-oracle index 706b5d61..4288e38b 100644 --- a/graph/test/typescript/expected/80-vue-sfc.fields-oracle +++ b/graph/test/typescript/expected/80-vue-sfc.fields-oracle @@ -1,9 +1,9 @@ 80-vue-sfc [fields] precision 0.0000 (0 correct, 1 wrong) recall 0.0000 (0 of 1 the compiler resolved) - sites 1 resolved 1 (100.0%) - tiers known_edge=1 - access read=1 - not scored: 0 rows whose target is not a client declaration + sites 4 resolved 4 (100.0%) + tiers known_edge=4 + access read=4 + not scored: 3 rows whose target is not a client declaration WRONG child#Child({ msg: string }) READ { msg: string }#msg MISSING child#Child({ msg: string }) READ child#msg diff --git a/graph/test/typescript/expected/81-vue-component-tag.fields b/graph/test/typescript/expected/81-vue-component-tag.fields index e69de29b..d2a02654 100644 --- a/graph/test/typescript/expected/81-vue-component-tag.fields +++ b/graph/test/typescript/expected/81-vue-component-tag.fields @@ -0,0 +1 @@ +known_edge read shims-vue#() -> shims-vue.d#component diff --git a/graph/test/typescript/expected/81-vue-component-tag.fields-oracle b/graph/test/typescript/expected/81-vue-component-tag.fields-oracle index 7a65896b..21219347 100644 --- a/graph/test/typescript/expected/81-vue-component-tag.fields-oracle +++ b/graph/test/typescript/expected/81-vue-component-tag.fields-oracle @@ -1,7 +1,7 @@ 81-vue-component-tag [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 0 the compiler resolved) - sites 0 resolved 0 - tiers - access - not scored: 0 rows whose target is not a client declaration + sites 1 resolved 1 (100.0%) + tiers known_edge=1 + access read=1 + not scored: 1 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/83-closed-world-dispatch.fields b/graph/test/typescript/expected/83-closed-world-dispatch.fields index e69de29b..aa725fb8 100644 --- a/graph/test/typescript/expected/83-closed-world-dispatch.fields +++ b/graph/test/typescript/expected/83-closed-world-dispatch.fields @@ -0,0 +1,2 @@ +known_edge read handlers#main() -> handlers#registry +known_edge read handlers#register(HandlerClass) -> handlers#registry diff --git a/graph/test/typescript/expected/83-closed-world-dispatch.fields-oracle b/graph/test/typescript/expected/83-closed-world-dispatch.fields-oracle index 3355e740..966f7948 100644 --- a/graph/test/typescript/expected/83-closed-world-dispatch.fields-oracle +++ b/graph/test/typescript/expected/83-closed-world-dispatch.fields-oracle @@ -1,7 +1,7 @@ 83-closed-world-dispatch [fields] precision 0.0000 (0 correct, 0 wrong) recall 0.0000 (0 of 0 the compiler resolved) - sites 0 resolved 0 - tiers - access - not scored: 0 rows whose target is not a client declaration + sites 2 resolved 2 (100.0%) + tiers known_edge=2 + access read=2 + not scored: 2 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/85-workspace-package-import.fields b/graph/test/typescript/expected/85-workspace-package-import.fields index 565a7ee6..be580e37 100644 --- a/graph/test/typescript/expected/85-workspace-package-import.fields +++ b/graph/test/typescript/expected/85-workspace-package-import.fields @@ -1 +1,3 @@ known_edge read S#run() -> S#bus +known_edge read S#run() -> index#K +known_edge read S#run() -> zod#z diff --git a/graph/test/typescript/expected/85-workspace-package-import.fields-oracle b/graph/test/typescript/expected/85-workspace-package-import.fields-oracle index 44b1f6d7..346e6652 100644 --- a/graph/test/typescript/expected/85-workspace-package-import.fields-oracle +++ b/graph/test/typescript/expected/85-workspace-package-import.fields-oracle @@ -1,7 +1,7 @@ 85-workspace-package-import [fields] 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: 0 rows whose target is not a client declaration + sites 3 resolved 3 (100.0%) + tiers known_edge=3 + access read=3 + not scored: 2 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/86-keyed-callback-registry.fields b/graph/test/typescript/expected/86-keyed-callback-registry.fields index 905b11ec..7d5b88cf 100644 --- a/graph/test/typescript/expected/86-keyed-callback-registry.fields +++ b/graph/test/typescript/expected/86-keyed-callback-registry.fields @@ -1,13 +1,26 @@ ambiguous_unknown read Bus#count(string) -> - known_edge read Bus#count(string) -> Bus#handlers known_edge read Bus#dispatch(string,unknown) -> Bus#handlers +known_edge read Bus#dispatch(string,unknown) -> bus#ANY known_edge read Bus#subscribe(string,Handler) -> Bus#handlers known_edge read Indexer#start() -> Indexer#b +known_edge read Indexer#start() -> topics#TOPIC 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#() -> app#bus +known_edge read app#() -> app#mini +known_edge read app#() -> bus#ANY +known_edge read app#() -> topics#TOPIC known_edge read app#() -> topics#created +known_edge read app#create() -> app#bus +known_edge read app#create() -> topics#TOPIC known_edge read app#create() -> topics#created +known_edge read app#go() -> app#mini +known_edge read app#relay({ type: string }) -> app#bus known_edge read app#relay({ type: string }) -> { type: string }#type +known_edge read app#remove() -> app#bus +known_edge read app#size() -> app#bus +known_edge read app#size() -> topics#TOPIC known_edge read app#size() -> topics#created diff --git a/graph/test/typescript/expected/86-keyed-callback-registry.fields-oracle b/graph/test/typescript/expected/86-keyed-callback-registry.fields-oracle index 161a25a4..f6c67a1e 100644 --- a/graph/test/typescript/expected/86-keyed-callback-registry.fields-oracle +++ b/graph/test/typescript/expected/86-keyed-callback-registry.fields-oracle @@ -1,9 +1,9 @@ 86-keyed-callback-registry [fields] 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: 1 rows whose target is not a client declaration + sites 36 resolved 35 (97.2%) + tiers ambiguous_unknown=1 known_edge=35 + access read=36 + not scored: 20 rows whose target is not a client declaration WRONG app#relay({ type: string }) READ { type: string }#type MISSING app#relay({ type: string }) READ app#type diff --git a/graph/test/typescript/expected/86-module-variable-reads.edges b/graph/test/typescript/expected/86-module-variable-reads.edges new file mode 100644 index 00000000..c94f0255 --- /dev/null +++ b/graph/test/typescript/expected/86-module-variable-reads.edges @@ -0,0 +1,7 @@ +known_edge CONSTRUCTOR_CALL Handler#handle(unknown) @L46 -> Pipe#(unknown) +known_edge DECORATOR_CALL Handler#handle(unknown) @L45 -> a/test/t#Route(unknown) +known_edge DECORATOR_CALL Handler#handle(unknown) @L46 -> a/test/t#Body(unknown) +known_edge FUNCTION_CALL a/src/cfg#() @L4 -> a/src/cfg#mk(object) +known_edge FUNCTION_CALL a/test/t#asArgument() @L10 -> a/test/t#use(unknown) +known_edge FUNCTION_CALL a/test/t#callsTheFunction() @L18 -> a/src/cfg#make(number) +known_edge FUNCTION_CALL a/test/t#viaMember() @L6 -> a/test/t#use(unknown) diff --git a/graph/test/typescript/expected/86-module-variable-reads.entries b/graph/test/typescript/expected/86-module-variable-reads.entries new file mode 100644 index 00000000..9b291f2d --- /dev/null +++ b/graph/test/typescript/expected/86-module-variable-reads.entries @@ -0,0 +1,15 @@ +── entry_point (14) ── + exported_from_entry_module a/src/cfg#bump cfg.ts:18 + exported_from_entry_module a/src/cfg#localUse cfg.ts:14 + exported_from_entry_module a/src/cfg#mk cfg.ts:10 + exported_from_entry_module a/test/t#annotated t.ts:30 + exported_from_entry_module a/test/t#asArgument t.ts:9 + exported_from_entry_module a/test/t#callsTheFunction t.ts:17 + exported_from_entry_module a/test/t#inTemplate t.ts:13 + exported_from_entry_module a/test/t#shadowed t.ts:21 + exported_from_entry_module a/test/t#shadowedInType t.ts:35 + exported_from_entry_module a/test/t#viaMember t.ts:5 + exported_from_entry_module b/test/sibling#siblingRead sibling.ts:3 + unimported_module a/src/cfg# cfg.ts:1 + unimported_module a/test/t# t.ts:1 + unimported_module b/test/sibling# sibling.ts:1 diff --git a/graph/test/typescript/expected/86-module-variable-reads.fields b/graph/test/typescript/expected/86-module-variable-reads.fields new file mode 100644 index 00000000..3ea4db04 --- /dev/null +++ b/graph/test/typescript/expected/86-module-variable-reads.fields @@ -0,0 +1,21 @@ +ambiguous_unknown read a/test/t#annotated(typeof CFG) -> - +ambiguous_unknown read a/test/t#shadowed() -> - +known_edge read Handler#handle(unknown) -> a/src/cfg#Store +known_edge read Handler#handle(unknown) -> cfg#S +known_edge read Handler#handle(unknown) -> cfg#TOKENS +known_edge read a/src/cfg#localUse() -> a/src/cfg#name +known_edge read a/src/cfg#localUse() -> cfg#CFG +known_edge read a/test/t#() -> cfg#S +known_edge read a/test/t#annotated(typeof CFG) -> cfg#CFG +known_edge read a/test/t#asArgument() -> cfg#S +known_edge read a/test/t#inTemplate() -> a/src/cfg#Store +known_edge read a/test/t#inTemplate() -> a/src/cfg#name +known_edge read a/test/t#inTemplate() -> cfg#CFG +known_edge read a/test/t#inTemplate() -> cfg#TOKENS +known_edge read a/test/t#viaMember() -> a/src/cfg#name +known_edge read a/test/t#viaMember() -> cfg#CFG +known_edge read b/test/sibling#siblingRead() -> b/src/settings#port +known_edge read b/test/sibling#siblingRead() -> settings#CFG +known_edge read b/test/sibling#siblingRead() -> settings#S +known_edge readwrite a/src/cfg#bump() -> cfg#counter +known_edge write a/src/cfg#bump() -> cfg#counter diff --git a/graph/test/typescript/expected/86-module-variable-reads.fields-oracle b/graph/test/typescript/expected/86-module-variable-reads.fields-oracle new file mode 100644 index 00000000..67805e29 --- /dev/null +++ b/graph/test/typescript/expected/86-module-variable-reads.fields-oracle @@ -0,0 +1,9 @@ +86-module-variable-reads [fields] + precision 1.0000 (6 correct, 0 wrong) + recall 0.7500 (6 of 8 the compiler resolved) + sites 21 resolved 19 (90.5%) + tiers ambiguous_unknown=2 known_edge=19 + access read=19 readwrite=1 write=1 + not scored: 15 rows whose target is not a client declaration + MISSING a/test/t#annotated(typeof CFG) READ a/src/cfg#name + MISSING a/test/t#shadowed() READ a/test/t#name diff --git a/graph/test/typescript/expected/86-module-variable-reads.oracle b/graph/test/typescript/expected/86-module-variable-reads.oracle new file mode 100644 index 00000000..fa4000b6 --- /dev/null +++ b/graph/test/typescript/expected/86-module-variable-reads.oracle @@ -0,0 +1 @@ +oracle=7 engine=7 agree=7 missing=0 (known 0, NEW 0) extra=0 diff --git a/graph/test/typescript/expected/86-module-variable-reads.type-use b/graph/test/typescript/expected/86-module-variable-reads.type-use new file mode 100644 index 00000000..a403269b --- /dev/null +++ b/graph/test/typescript/expected/86-module-variable-reads.type-use @@ -0,0 +1,5 @@ +ambiguous_unknown AS_TARGET 0 a/src/cfg [EXPRESSION] -> - +ambiguous_unknown AS_TARGET 0 b/src/settings [EXPRESSION] -> - +ambiguous_unknown DECORATOR_TYPE 0 Handler [DECORATOR] -> - +ambiguous_unknown METHOD_RETURN 0 a/test/t [METHOD] -> - +known_edge OBJECT_CREATION_TYPE 0 Handler [EXPRESSION] -> Pipe diff --git a/graph/test/typescript/expected/86-module-variable-reads.types-oracle b/graph/test/typescript/expected/86-module-variable-reads.types-oracle new file mode 100644 index 00000000..e0d90aa4 --- /dev/null +++ b/graph/test/typescript/expected/86-module-variable-reads.types-oracle @@ -0,0 +1,7 @@ +86-module-variable-reads [types] + precision 1.0000 (1 correct, 0 wrong) + recall 1.0000 (1 of 1 the compiler resolved) + sites 8 resolved 1 (12.5%) + tiers ambiguous_unknown=7 known_edge=1 + contexts AS_TARGET=3 DECORATOR_TYPE=2 METHOD_RETURN=2 OBJECT_CREATION_TYPE=1 + not scored: 7 rows whose target is not a client declaration diff --git a/graph/test/typescript/tools/normalize_members.py b/graph/test/typescript/tools/normalize_members.py index f4a41d79..b6698c7c 100644 --- a/graph/test/typescript/tools/normalize_members.py +++ b/graph/test/typescript/tools/normalize_members.py @@ -44,6 +44,14 @@ def field_names(ir, prefix='typescript'): return out +def module_var_names(ir, prefix='typescript'): + """variable hash -> (module label, name), for a module-scope variable read by name: its owner is its + module, labelled by the file's stem as the module initializer is (`members#()`).""" + return {r['tsVariableUniqueHash']: (os.path.splitext(os.path.basename(r.get('filePath') or '?'))[0], r['name']) + for r in rows(f'{ir}/all-{prefix}-variables.csv') + if r.get('scopeKind') in ('MODULE_SCOPE', 'GLOBAL_SCOPE', 'AMBIENT_SCOPE', 'NAMESPACE_SCOPE')} + + def type_names(ir, prefix='typescript'): return {t['tsTypeUniqueHash']: (t.get('name') or t.get('qualifiedName') or '?') for t in rows(f'{ir}/all-{prefix}-types.csv')} @@ -62,6 +70,9 @@ def main(): ir, out = args[0], args[1] n = Names(ir, lib_ir) fields = field_names(ir) + # the compiler oracle scores member accesses only: a module variable's read is golden-checked, not oracle-paired + mvars = module_var_names(ir) + fields.update({k: v for k, v in mvars.items() if k not in fields}) types = type_names(ir) if lib_ir and os.path.isdir(lib_ir): fields.update({k: v for k, v in field_names(lib_ir).items() if k not in fields}) @@ -81,7 +92,7 @@ def main(): f"{fields[field][0]}#{fields[field][1]}" if field in fields else f"") if pairs: - if field == '-' or field not in fields: + if field == '-' or field not in fields or field in mvars: continue for d in (['READ'] if access == 'read' else ['WRITE'] if access == 'write' else ['READ', 'WRITE']): diff --git a/graph/typescript/engine/call-edge-generation/field_access.dl b/graph/typescript/engine/call-edge-generation/field_access.dl index d25646d2..39396ab7 100644 --- a/graph/typescript/engine/call-edge-generation/field_access.dl +++ b/graph/typescript/engine/call-edge-generation/field_access.dl @@ -202,3 +202,88 @@ ts_field_access_has_row(e) :- ts_field_access_target(e, f), ts_field_prov(_, f). field_access(e, caller, "-", "-", "ambiguous_unknown", acc) :- ts_field_access_site(e), !ts_field_access_has_row(e), ts_field_access_from(e, caller), ts_field_access_kind(e, acc). + +// ── A MODULE-SCOPE VARIABLE, READ BY NAME ─────────────────────────────────── +// `SERVICE.name`, `f(EnvSchema)`, `TOKENS.IntentStore`: the receiver or argument is an +// IDENTIFIER_REFERENCE that the binder already tied to one declaration -- a module-scope +// variable of its own file (VARIABLE), or an import whose binding import_binds follows, +// through every re-export, to the exported variable (IMPORT_BINDING). Nothing recorded +// that tie as a data edge, so "who reads this exported const" was answered by name, and +// the name matched every same-named const in every sibling package. +// +// The variable is the field of the row: a module is the owner of its variables the way a +// class is the owner of its fields, and consumers read one relation for both. A variable +// that holds a function is a function (`const f = () => ...`): its uses are calls, which +// call_edges already carries, so it is left out here. +ts_module_var(v) :- var_decl("client", _, _, sk, _, _, v), scope_is_module_level(sk), + !var_is_function(v, _). +ts_var_read_target(e, v) :- expr_kind("client", "IDENTIFIER_REFERENCE", _, e), + expr_referenced("client", "VARIABLE", v, e), ts_module_var(v). +ts_var_read_target(e, v) :- expr_kind("client", "IDENTIFIER_REFERENCE", _, e), + expr_referenced("client", "IMPORT_BINDING", ih, e), + import_binds(ih, "client", "VARIABLE", v), ts_module_var(v). +// the direction, by the same law property_written applies to a member +ts_var_written(e) :- ts_var_read_target(e, _), expr_child("client", asg, "LEFT_OPERAND", _, e), + expr_kind("client", ak, _, asg), assignment_kind(ak). +ts_var_written(e) :- ts_var_read_target(e, _), expr_child("client", u, "UNARY_OPERAND", _, e), + expr_kind("client", "UNARY_EXPRESSION", _, u), expr_operator("client", op, u), update_operator(op). +ts_var_write_only(e) :- ts_var_read_target(e, _), expr_child("client", asg, "LEFT_OPERAND", _, e), + expr_kind("client", "ASSIGNMENT_EXPRESSION", _, asg). +ts_var_read_kind(e, "write") :- ts_var_written(e), ts_var_write_only(e). +ts_var_read_kind(e, "readwrite") :- ts_var_written(e), !ts_var_write_only(e). +ts_var_read_kind(e, "read") :- ts_var_read_target(e, _), !ts_var_written(e). +// the caller, as for a property access: the enclosing function, else the module. +// A DECORATOR on a method or on one of its parameters is that method's: `place(@Payload(new +// Pipe(CommandSchema)) c)` reads CommandSchema for place(). The parser gives an expression +// inside a decorator no owner hash, and expr_enclosing_method routes it to the module +// initializer, so the read is found by walking down from the decorator's own expression. +ts_decorated_method(dx, m) :- decorator_expr("client", dx, d), + annotation_on("client", _, _, "METHOD_DECLARATION", m, d), m != "". +ts_decorated_method(dx, m) :- decorator_expr("client", dx, d), + annotation_on("client", _, _, "PARAMETER_DECLARATION", p, d), param_decl("client", _, _, _, m, p), m != "". +ts_under_decorator(dx, dx) :- ts_decorated_method(dx, _). +ts_under_decorator(dx, c) :- ts_under_decorator(dx, p), expr_child("client", p, _, _, c). +ts_var_read_decorated(e, m) :- ts_var_read_target(e, _), expr_owner("client", "DECORATOR", _, _, e), + ts_under_decorator(dx, e), ts_decorated_method(dx, m). +ts_var_read_named(e) :- ts_var_read_decorated(e, _). +ts_var_read_named(e) :- ts_var_read_target(e, _), expr_enclosing_method(e, m), m != "". +ts_var_read_from(e, m) :- ts_var_read_decorated(e, m). +ts_var_read_from(e, m) :- ts_var_read_target(e, _), !ts_var_read_decorated(e, _), + expr_enclosing_method(e, m), m != "". +ts_var_read_from(e, mod) :- ts_var_read_target(e, _), !ts_var_read_named(e), + expr_module("client", mod, e), mod != "". +// one declaration, bound by the binder: known_edge +field_access(e, caller, v, "client", "known_edge", acc) :- ts_var_read_target(e, v), + ts_var_read_from(e, caller), ts_var_read_kind(e, acc). + +// ── THE SAME VARIABLE, READ IN A TYPE: `typeof X` ─────────────────────────── +// `type OrderDto = z.infer` depends on OrderSchema exactly as +// `f(OrderSchema)` does, but the name sits in a TYPE_QUERY type reference, not in an +// expression, so the binder's expression tie above never sees it. The name is bound +// through the module's own scope (name_binds_any: a declaration of this file, or a named +// import followed to the exported variable). Only the bare form `typeof X`: in +// `typeof A.b` the variable read is A, and the reference names b. A parameter or a local +// of the same name in the enclosing callable shadows the module's (control). +// The site is the type reference; the bundle positions it from the type-reference table. +ts_type_query_site(ref, tn, mod) :- type_ref("client", "TYPE_QUERY", _, tn, ctn, _, _, ref), + tn != "", ctn = cat("typeof ", tn), type_ref_module("client", mod, ref). +ts_type_query_method(ref, m) :- ts_type_query_site(ref, _, _), type_ref("client", _, _, _, _, m, "METHOD", ref). +ts_type_query_method(ref, m) :- ts_type_query_site(ref, _, _), type_ref("client", _, _, _, _, p, "METHOD_PARAM", ref), + param_decl("client", _, _, _, m, p). +ts_type_query_method(ref, m) :- ts_type_query_site(ref, _, _), type_ref("client", _, _, _, _, lv, "VARIABLE", ref), + var_decl("client", _, _, _, m, _, lv), m != "". +ts_type_query_method(ref, m) :- ts_type_query_site(ref, _, _), type_ref("client", _, _, _, _, x, "EXPRESSION", ref), + expr_enclosing_method(x, m), m != "". +ts_type_query_shadowed(ref) :- ts_type_query_site(ref, tn, _), ts_type_query_method(ref, m), + param_decl("client", tn, _, _, m, _). +ts_type_query_shadowed(ref) :- ts_type_query_site(ref, tn, _), ts_type_query_method(ref, m), + var_decl("client", tn, _, _, m, _, _). +ts_type_query_read(ref, v) :- ts_type_query_site(ref, tn, mod), !ts_type_query_shadowed(ref), + name_binds_any(mod, tn, "client", "VARIABLE", v), ts_module_var(v). +ts_type_query_has_method(ref) :- ts_type_query_method(ref, _). +field_access(ref, m, v, "client", "known_edge", "read") :- ts_type_query_read(ref, v), + ts_type_query_method(ref, m). +// a type alias, an interface, a class member's annotation: the module's initializer, as for +// any module-level read +field_access(ref, init, v, "client", "known_edge", "read") :- ts_type_query_read(ref, v), + !ts_type_query_has_method(ref), ts_type_query_site(ref, _, mod), module_init("client", init, mod). diff --git a/graph/typescript/souffle/decls_all.dl b/graph/typescript/souffle/decls_all.dl index c380b26e..2b9b1f0d 100644 --- a/graph/typescript/souffle/decls_all.dl +++ b/graph/typescript/souffle/decls_all.dl @@ -649,6 +649,21 @@ .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) +.decl ts_module_var(c0:symbol) +.decl ts_var_read_target(c0:symbol,c1:symbol) +.decl ts_var_read_from(c0:symbol,c1:symbol) +.decl ts_var_read_named(c0:symbol) +.decl ts_var_written(c0:symbol) +.decl ts_var_write_only(c0:symbol) +.decl ts_var_read_kind(c0:symbol,c1:symbol) +.decl ts_decorated_method(c0:symbol,c1:symbol) +.decl ts_under_decorator(c0:symbol,c1:symbol) +.decl ts_var_read_decorated(c0:symbol,c1:symbol) +.decl ts_type_query_site(c0:symbol,c1:symbol,c2:symbol) +.decl ts_type_query_method(c0:symbol,c1:symbol) +.decl ts_type_query_shadowed(c0:symbol) +.decl ts_type_query_read(c0:symbol,c1:symbol) +.decl ts_type_query_has_method(c0:symbol) // ── TYPE USE (#663) — call-edge-generation/type_use.dl .decl ts_type_use_kind_names_a_type(c0:symbol) diff --git a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-index b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-index index 777a8caf..0aa79c54 100755 --- a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-index +++ b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-index @@ -101,7 +101,9 @@ A = { litKinds={'LITERAL'}, litType=('literalType', 'STRING'), litValue='literalValue'), comments=dict(file='all-typescript-comments.csv', text='commentText', kind='commentKind', line='startLine', filePath='filePath'), typeRefs=dict(file='all-typescript-type-references.csv', name='typeName', context='context', ownerKind='referenceOwnerKind', line='startLine', fileVia=('modules', 'tsModuleLinkHash')), - decls=[dict(file='all-typescript-variables.csv', only=lambda r: r.get('scopeKind') == 'MODULE_SCOPE', + # the variable's own hash: the engine's field_access names a module variable by it when an identifier the binder + # tied to it (directly, or through an import binding) reads it + decls=[dict(file='all-typescript-variables.csv', id='tsVariableUniqueHash', only=lambda r: r.get('scopeKind') == 'MODULE_SCOPE', kind=lambda r: 'const' if r.get('isConst') == 'true' else 'variable', name='name', owner=None, filePath='filePath', line='startLine', end='endLine'), dict(file='all-typescript-fields.csv', id='tsFieldUniqueHash', kind=lambda r: 'field', name='name', owner='ownerQualifiedName', filePath='filePath', line='startLine', end='endLine'), dict(file='all-typescript-enum-members.csv', kind=lambda r: 'enum_member', name='name', owner='ownerQualifiedName', filePath='filePath', line='startLine', end='endLine')], @@ -140,7 +142,7 @@ A = { # a `function f` or `class C` is also a binding (FUNCTION_DECLARATION_HOISTED, CLASS_TDZ), and `const { C } = # require('./m')` is an import in all but syntax (it carries an importLinkHash): none of them is a variable, and # each made the function or class it names look declared as a second kind, so `impact C` refused as ambiguous - decls=[dict(file='all-javascript-variables.csv', only=lambda r: not r.get('ownerMethodLinkHash') and r.get('bindingRegime') not in ('IMPORT_BINDING', 'FUNCTION_DECLARATION_HOISTED', 'CLASS_TDZ') and not r.get('importLinkHash'), + decls=[dict(file='all-javascript-variables.csv', id='jsVariableUniqueHash', only=lambda r: not r.get('ownerMethodLinkHash') and r.get('bindingRegime') not in ('IMPORT_BINDING', 'FUNCTION_DECLARATION_HOISTED', 'CLASS_TDZ') and not r.get('importLinkHash'), kind=lambda r: 'const' if r.get('bindingRegime', '').startswith('CONST') else 'variable', name='name', owner=None, fileVia=('modules', 'ownerModuleLinkHash'), line='startLine', end='endLine'), # a field's owner is its class by hash (`this.x = …` in a constructor, a class field, `F.prototype.x = …`), so it # displays as `Store.items`, not `items`; a computed key (`[Symbol.iterator] = …`) is named by its key expression diff --git a/plugins/axiomcode/skills/axiomcode/scripts/dl/impact.dl b/plugins/axiomcode/skills/axiomcode/scripts/dl/impact.dl index b106e9d7..c87d8a9c 100644 --- a/plugins/axiomcode/skills/axiomcode/scripts/dl/impact.dl +++ b/plugins/axiomcode/skills/axiomcode/scripts/dl/impact.dl @@ -519,6 +519,10 @@ const_holds(ff, fll, m) :- init_alias(ff, fll, n), named(n, m). .decl const_handed(q:symbol, c:symbol, f:symbol, l:number, ff:symbol, fll:number) const_handed(q, c, f, l, ff, fll) :- target(q, "field", fl, _), field(fl, _, n, ff, fll), callable_const(ff, fll), fref(q, c, _, f, l), route_site(f, l), route_arg(c, f, l, n). +// the same where the engine bound the name to this const (an imported or module-level const read through its binding): +// fref leaves such a line to fa_bound, and the hand-off is still a registration, not a read +const_handed(q, c, f, l, ff, fll) :- target(q, "field", fl, _), field(fl, _, n, ff, fll), callable_const(ff, fll), + fa_bound(c, fl, _, f, l), route_site(f, l), route_arg(c, f, l, n). .decl const_route_edge(q:symbol, c:symbol, m:symbol, f:symbol, l:number) const_route_edge(q, c, m, f, l) :- const_handed(q, c, f, l, ff, fll), const_holds(ff, fll, m), handoff_at(c, m, f, l). .decl const_route(q:symbol, c:symbol, cert:symbol, f:symbol, l:number) @@ -540,7 +544,7 @@ const_routed(q, c, f, l) :- const_route_byname(q, c, f, l). fa_bound(c, fl, acc, f, l) :- faccess(c, fl, acc, tier, f, l), tier != "ambiguous_unknown". direct(q, c, "produces", "writes it", "resolved", f, l) :- target(q, "field", fl, _), fa_bound(c, fl, "write", f, l). direct(q, c, "produces", "writes it", "resolved", f, l) :- target(q, "field", fl, _), fa_bound(c, fl, "readwrite", f, l). -direct(q, c, "uses", "reads it", "resolved", f, l) :- target(q, "field", fl, _), fa_bound(c, fl, "read", f, l). +direct(q, c, "uses", "reads it", "resolved", f, l) :- target(q, "field", fl, _), fa_bound(c, fl, "read", f, l), !const_routed(q, c, f, l). direct(q, c, "uses", "reads it", "resolved", f, l) :- target(q, "field", fl, _), fa_bound(c, fl, "readwrite", f, l). // a caller the engine bound — to this field, or to a DIFFERENT field of the same name. Either way its // name matches say nothing further: the first is already reported above with its direction, and the diff --git a/tests/cases/javascript/module-level-const/case.json b/tests/cases/javascript/module-level-const/case.json new file mode 100644 index 00000000..85110780 --- /dev/null +++ b/tests/cases/javascript/module-level-const/case.json @@ -0,0 +1,18 @@ +{"lang": "javascript", "src": "src", + "checks": [ + {"why": "an imported const read through a member access, as an argument, or through an `export *` barrel is resolved to the declaration the import binds, not matched by name", + "run": ["impact", "src/consts.js:2"], + "want": ["[resolved] memberRead", "[resolved] throughBarrel"], + "avoid": ["[by name"]}, + {"why": "the same for a const that holds a call's result, handed to a call", + "run": ["impact", "src/consts.js:3"], + "want": ["[resolved] argRead"], + "avoid": ["[by name"]}, + {"why": "a bare read of an imported const", + "run": ["impact", "src/consts.js:1"], + "want": ["[resolved] topLevelUse"], + "avoid": ["siblingRead"]}, + {"why": "control: a file that imports the same name from ANOTHER declaration is not a reader of this one, and that declaration keeps its own reader", + "run": ["impact", "src/sibling/consts.js:3"], + "want": ["[resolved] siblingRead"], + "avoid": ["memberRead", "throughBarrel"]}]} diff --git a/tests/cases/javascript/module-level-const/src/barrelUser.js b/tests/cases/javascript/module-level-const/src/barrelUser.js new file mode 100644 index 00000000..455a0720 --- /dev/null +++ b/tests/cases/javascript/module-level-const/src/barrelUser.js @@ -0,0 +1,3 @@ +import { CFG } from './index.js'; + +export function throughBarrel() { return CFG.port; } diff --git a/tests/cases/javascript/module-level-const/src/consts.js b/tests/cases/javascript/module-level-const/src/consts.js new file mode 100644 index 00000000..ea851142 --- /dev/null +++ b/tests/cases/javascript/module-level-const/src/consts.js @@ -0,0 +1,5 @@ +export const LIMIT = 10; +export const CFG = { name: 'a', port: 1 }; +export const SCHEMA = shape({ id: 'string' }); + +export function shape(o) { return o; } diff --git a/tests/cases/javascript/module-level-const/src/index.js b/tests/cases/javascript/module-level-const/src/index.js new file mode 100644 index 00000000..83aef343 --- /dev/null +++ b/tests/cases/javascript/module-level-const/src/index.js @@ -0,0 +1 @@ +export * from './consts.js'; diff --git a/tests/cases/javascript/module-level-const/src/sibling/consts.js b/tests/cases/javascript/module-level-const/src/sibling/consts.js new file mode 100644 index 00000000..d1a12e29 --- /dev/null +++ b/tests/cases/javascript/module-level-const/src/sibling/consts.js @@ -0,0 +1,3 @@ +// the same names, declared again: a reader here reads THIS file's declarations +export const LIMIT = 3; +export const CFG = { name: 'b', port: 2 }; diff --git a/tests/cases/javascript/module-level-const/src/sibling/reader.js b/tests/cases/javascript/module-level-const/src/sibling/reader.js new file mode 100644 index 00000000..7d41995c --- /dev/null +++ b/tests/cases/javascript/module-level-const/src/sibling/reader.js @@ -0,0 +1,3 @@ +import { CFG, LIMIT } from './consts.js'; + +export function siblingRead() { return CFG.port + LIMIT; } diff --git a/tests/cases/javascript/module-level-const/src/user.js b/tests/cases/javascript/module-level-const/src/user.js new file mode 100644 index 00000000..18914f01 --- /dev/null +++ b/tests/cases/javascript/module-level-const/src/user.js @@ -0,0 +1,9 @@ +import { LIMIT, CFG, SCHEMA } from './consts.js'; + +function use(x) { return x; } + +export function topLevelUse() { return LIMIT + 1; } + +export function memberRead() { return use(CFG.name); } + +export function argRead() { return use(SCHEMA); } diff --git a/tests/cases/javascript/wrapped-handler-route/case.json b/tests/cases/javascript/wrapped-handler-route/case.json index 6b329131..e8dd8894 100644 --- a/tests/cases/javascript/wrapped-handler-route/case.json +++ b/tests/cases/javascript/wrapped-handler-route/case.json @@ -10,7 +10,7 @@ "avoid": ["reads it"]}, {"why": "a file:line on a const targets the const. It used to resolve to the enclosing (other.js:2) or to the arrow in the initializer (adminController.js:2), so a const could only be asked by its bare name, which answers for every const so named", "run": ["impact", "src/routes/other.js:2"], - "want": ["const listAdmins (at src/routes/other.js:2) [field]", "[in scope] other. src/routes/other.js:3"], + "want": ["const listAdmins (at src/routes/other.js:2) [field]", "[resolved] other. src/routes/other.js:3"], "avoid": ["other. (at src/routes/other.js:2)", "registered as a GET route"]}, {"why": "and a file that declares its own const of the name reads its own, not the wrapped handler's: other.js's `['root']` is not a reader of the controller's listAdmins", "run": ["impact", "src/controllers/adminController.js:2"], @@ -73,7 +73,7 @@ "avoid": ["registered as a"]}, {"why": "a const passed to a validation middleware on a route line (`validate(bodySchema)`) is an argument of that call, not a handler the router calls; and a schema built by a library (`Joi.object({…})`) is not a function even where it sits in a handler position (`.post('/schema-direct', bodySchema, h)`)", "run": ["impact", "bodySchema"], - "want": ["[in scope] validated. src/routes/validated.js:8 — reads it"], + "want": ["[resolved] validated. src/routes/validated.js:8 — reads it"], "avoid": ["registered as a"]}, {"why": "an options object passed positionally (`router.post('/opts', routeOptions, h)`) is not callable: read, not registered", "run": ["impact", "routeOptions"], @@ -95,7 +95,7 @@ "avoid": ["registered as a"]}, {"why": "a router module's own router const, the receiver of each route line (`router.get('/admins', listAdmins)`), is read on those lines, not registered on them, here or in the other files that declare a `router` of their own", "run": ["impact", "src/routes/admin.js:4"], - "want": ["[in scope] admin. src/routes/admin.js:5 — reads it"], + "want": ["[resolved] admin. src/routes/admin.js:5 — reads it"], "avoid": ["registered as a"]}, {"why": "a wrapped handler handed over inside an array of handlers (`.get('/array', [validate(s), ctrl.listAdmins])`) is registered: a router flattens the array", "run": ["impact", "src/controllers/adminController.js:2"], diff --git a/tests/cases/typescript/module-level-const/case.json b/tests/cases/typescript/module-level-const/case.json index 74c67b03..113ffec2 100644 --- a/tests/cases/typescript/module-level-const/case.json +++ b/tests/cases/typescript/module-level-const/case.json @@ -1,10 +1,34 @@ {"lang": "typescript", "src": "src", "checks": [ {"why": "a module-level exported const has the modules that import it as dependents, instead of nothing at all", - "run": ["impact", "LIMIT", "--kind", "field"], + "run": ["impact", "src/consts.ts:1"], "want": ["topLevelUse", "Holder", "arrowUse"], "avoid": ["nothing the graph can see"]}, {"why": "and --delete does not call it safe", - "run": ["impact", "LIMIT", "--kind", "field", "--delete"], + "run": ["impact", "src/consts.ts:1", "--delete"], "want": ["NOT SAFE"], - "avoid": ["no dependent at any certainty"]}]} + "avoid": ["no dependent at any certainty"]}, + {"why": "an imported const read through a member access or passed as an argument is resolved to the declaration the import binds, not matched by name", + "run": ["impact", "src/consts.ts:2"], + "want": ["[resolved] memberRead"], + "avoid": ["[by name"]}, + {"why": "the same for a const that holds a call's result, handed to a call", + "run": ["impact", "src/consts.ts:3"], + "want": ["[resolved] argRead"], + "avoid": ["[by name"]}, + {"why": "a const read in a type (`typeof X`): in a parameter's type it is the function's read, in a type alias the module's, each at its own line", + "run": ["impact", "src/consts.ts:3"], + "want": ["[resolved] typedRead", "src/user.ts:17"], + "avoid": ["[by name"]}, + {"why": "and at module level", + "run": ["impact", "src/consts.ts:2"], + "want": ["src/user.ts:19"], + "avoid": ["[by name"]}, + {"why": "control: a file that imports the same name from ANOTHER declaration is not a reader of this one", + "run": ["impact", "src/consts.ts:2"], + "want": [], + "avoid": ["siblingRead", "sibling/reader.ts"]}, + {"why": "control: and the sibling declaration keeps its own reader", + "run": ["impact", "src/sibling/consts.ts:3"], + "want": ["[resolved] siblingRead"], + "avoid": ["memberRead", "src/user.ts"]}]} diff --git a/tests/cases/typescript/module-level-const/src/consts.ts b/tests/cases/typescript/module-level-const/src/consts.ts index 4a3dc15e..28a387c8 100644 --- a/tests/cases/typescript/module-level-const/src/consts.ts +++ b/tests/cases/typescript/module-level-const/src/consts.ts @@ -1 +1,5 @@ export const LIMIT = 10; +export const CFG = { name: 'a', port: 1 } as const; +export const SCHEMA = shape({ id: 'string' }); + +export function shape(o: object): object { return o; } diff --git a/tests/cases/typescript/module-level-const/src/sibling/consts.ts b/tests/cases/typescript/module-level-const/src/sibling/consts.ts new file mode 100644 index 00000000..f1d033d2 --- /dev/null +++ b/tests/cases/typescript/module-level-const/src/sibling/consts.ts @@ -0,0 +1,3 @@ +// the same names, declared again: a reader here reads THIS file's declarations +export const LIMIT = 3; +export const CFG = { name: 'b', port: 2 } as const; diff --git a/tests/cases/typescript/module-level-const/src/sibling/reader.ts b/tests/cases/typescript/module-level-const/src/sibling/reader.ts new file mode 100644 index 00000000..ea3fbcf3 --- /dev/null +++ b/tests/cases/typescript/module-level-const/src/sibling/reader.ts @@ -0,0 +1,3 @@ +import { CFG, LIMIT } from './consts.js'; + +export function siblingRead(): number { return CFG.port + LIMIT; } diff --git a/tests/cases/typescript/module-level-const/src/user.ts b/tests/cases/typescript/module-level-const/src/user.ts index 8be77cbf..4d1263ae 100644 --- a/tests/cases/typescript/module-level-const/src/user.ts +++ b/tests/cases/typescript/module-level-const/src/user.ts @@ -5,3 +5,15 @@ export function topLevelUse(): number { return LIMIT + 1; } export class Holder { cap(): number { return LIMIT * 2; } } export const arrowUse = () => LIMIT - 1; + +import { CFG, SCHEMA } from './consts.js'; + +function use(x: unknown): void {} + +export function memberRead(): void { use(CFG.name); } + +export function argRead(): void { use(SCHEMA); } + +export function typedRead(s: typeof SCHEMA): void { use(s); } + +export type CfgShape = typeof CFG; diff --git a/tests/cases/typescript/wrapped-handler-route/case.json b/tests/cases/typescript/wrapped-handler-route/case.json index 73fff518..cb82b081 100644 --- a/tests/cases/typescript/wrapped-handler-route/case.json +++ b/tests/cases/typescript/wrapped-handler-route/case.json @@ -24,7 +24,7 @@ "avoid": ["registered as a"]}, {"why": "a router mounted with `app.use('/api', api)` is a mount, and the app is the receiver: neither is registered as a route", "run": ["impact", "src/app.ts:4"], - "want": ["[by name] server. src/server.ts:3 — reads it"], + "want": ["[resolved] server. src/server.ts:3 — reads it"], "avoid": ["registered as a"]}, {"why": "…nor the app", "run": ["impact", "src/app.ts:3"],