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
2 changes: 1 addition & 1 deletion gemini-extension.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "axiomcode",
"version": "0.2.2",
"version": "0.2.3",
"description": "Ask your repository how its code connects: who calls this, what breaks if I change it, which tests an edit reaches, how A reaches B. Answers come from a resolved call graph and are verified against it; nothing is guessed.",
"contextFileName": "plugins/axiomcode/AGENTS.md",
"mcpServers": {
Expand Down
16 changes: 16 additions & 0 deletions graph/test/typescript/cases/91-construct-through-shape/src/x.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// `new C(spec)` where C's type is a construct signature written as a type literal, an alias
// of one, a constructor type, or an interface. A factory that takes "the class to build"
// is written this way, and only the interface form resolved before.
interface WidgetDef { kind: string }
class Button { constructor(public spec: WidgetDef) {} }

type WidgetClass<T> = { new (spec: WidgetDef): T };
function viaAlias<T>(C: WidgetClass<T>): T { return new C({ kind: "a" }); }
function viaLiteral(C: { new (spec: WidgetDef): Button }): Button { return new C({ kind: "b" }); }

// controls: a constructor type and an interface with a construct signature
function viaCtorType(C: new (spec: WidgetDef) => Button): Button { return new C({ kind: "c" }); }
interface ButtonCtor { new (spec: WidgetDef): Button }
function viaInterface(C: ButtonCtor): Button { return new C({ kind: "d" }); }
const made = viaAlias(Button);
export { viaAlias, viaLiteral, viaCtorType, viaInterface, made };
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type Scorer = (input: string) => number;
export function makeScorer(base: number): Scorer {
const fn: Scorer = (input) => input.length + base;
return fn;
}
// a function-typed const whose value is a factory's result: no literal to read
export const score: Scorer = makeScorer(1);
export const scoreArrow: Scorer = (input) => input.length;
export function plain(input: string): number { return input.length; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// `ns.score(x)` reads the const off a namespace import. The named import of the same const
// resolved and the namespace read did not.
import * as scorers from "./scorers";
import { score } from "./scorers";
export function viaNamespace(s: string): number { return scorers.score(s); }
// controls: the named import, a const holding an arrow, and an exported function
export function viaNamed(s: string): number { return score(s); }
export function viaArrow(s: string): number { return scorers.scoreArrow(s); }
export function viaPlain(s: string): number { return scorers.plain(s); }
17 changes: 17 additions & 0 deletions graph/test/typescript/cases/93-literal-member-chain/src/x.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// A member chain through nested inline object types. Each level reads a shape-typed
// property off a shape, and the chain stopped after the first.
class Logger { write(msg: string): void {} }
interface Order {
meta: { audit: { log: Logger; nested: { deep: { log: Logger } } } };
}
function record(o: Order): void {
o.meta.audit.log.write("a");
o.meta.audit.nested.deep.log.write("b");
}
// controls: one level of literal, and a named type at every level
interface Flat { meta: { log: Logger } }
interface Named { meta: Meta }
interface Meta { log: Logger }
function flat(f: Flat): void { f.meta.log.write("c"); }
function named(n: Named): void { n.meta.log.write("d"); }
export { record, flat, named };
31 changes: 31 additions & 0 deletions graph/test/typescript/cases/94-thistype-object-literal/src/x.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// `this` in the methods of an object literal written as `X & ThisType<T>`: the compiler
// types it as T. A prototype table or a mixin is written this way, and every `this.m()` in
// one resolved to nothing. Covered: the marker through a generic alias bound by an explicit
// type argument, through an annotation, and written inline.
interface Greeter { name: string; greet(): string; shout(): string; hail(): string; wave(): string }
type Members<T> = { [K in keyof T]?: T[K] } & ThisType<T>;
interface Mixin<T> { make(): T }
declare function mixin<T>(name: string, members?: Members<T>): Mixin<T>;

const viaTypeArgument = mixin<Greeter>("Greeter", {
greet() { return this.shout(); },
shout: function shout() { return this.greet(); },
});
const viaAnnotation: Members<Greeter> = {
hail() { return this.greet(); },
};
interface Waver { wave?(): string }
const inline: Waver & ThisType<Greeter> = {
wave() { return this.shout(); },
};

// controls: without the marker `this` is the literal; an arrow property keeps the class's
const plainLiteral = {
a() { return 1; },
b() { return this.a(); },
};
class Host {
run(): string { return "r"; }
table = mixin<Greeter>("Host", { greet: () => this.run() });
}
export { viaTypeArgument, viaAnnotation, inline, plainLiteral, Host };
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// ============================================================================
// The case's OWN standard library: the minimum the checker needs under `noLib`.
// ============================================================================
// WHY noLib. This mechanism's targets are declared in the REAL `lib.es5.d.ts`, and a
// case never has it: the case oracle only records a target inside the case's own files,
// and the engine is handed only `--library <this dir's IR>`. So with the real library
// neither side can name the target and the case measures nothing. Declaring `Function`
// and `CallableFunction` HERE puts them on both sides of the comparison.
//
// This is not a mock of the behaviour. The checker resolves `CallableFunction` by NAME
// from whatever is in scope and then applies `strictBindCallApply` itself, so these
// declarations exercise the real decision. Verified: the same two declarations answer
// CallableFunction under `strict: true` and Function under
// `strict: false, strictBindCallApply: false`.
//
// Everything other than Function/CallableFunction is here only because `noLib` removes
// it and the checker requires it. Keep this file minimal: an interface added here is a
// declaration the engine can resolve to, and an accidental one is an accidental answer.

interface Object { toString(): string; }
interface Boolean {}
interface Number {}
interface String { padStart(width: number): string; padEnd(width: number): string; trim(): string; }
interface Array<T> { length: number; }
interface ReadonlyArray<T> { length: number; }
interface IArguments {}
interface RegExp {}

// ── the two declarations the flag chooses between ───────────────────────────
// Mirrors lib.es5.d.ts: `Function` declares all three, and `CallableFunction extends
// Function` REDECLARES the same three with precise generic signatures. Both are in
// scope at every site; only the flag decides.
// NO explicit `this` parameter on these. lib.es5.d.ts declares them with one, but the
// case oracle's edge label includes a `this` parameter in the signature while the engine
// (correctly) does not treat it as a parameter. So `this: Function` renders as
// `Function#apply(Function,any,any)` on one side and `Function#apply(any,any)` on the
// other, and every site scores as a disagreement the engine did not actually make.
interface Function {
apply(thisArg: any, argArray?: any): any;
call(thisArg: any, ...argArray: any[]): any;
bind(thisArg: any, ...argArray: any[]): any;
readonly name: string;
toString(): string;
}

interface CallableFunction extends Function {
// Simply typed on purpose. lib.es5.d.ts gives these precise generic signatures
// (`this: (this: T, ...args: A) => R`), and this fixture does not need them: what it
// tests is that TWO declarations of the same member names exist and that
// `strictBindCallApply` picks between them, which the checker decides from the flag
// and not from how well a signature fits. A function-typed parameter also renders
// badly in the case oracle's edge labels: the label splits on the commas inside
// `(this: T, ...args: A) => R` and emits `apply(args: A) =,T,T)`, which can never
// match the engine's label, so every site scored as a disagreement.
//
// Arguments are optional and returns are `any` so every call form in the case source
// typechecks. The two declarations stay distinguishable without help: an edge label
// carries the OWNER, so `CallableFunction#call` and `Function#call` never collide, and
// that owner is exactly what the flag decides.
call(thisArg: any, arg0?: any, arg1?: any): any;
apply(thisArg: any, args?: any): any;
bind(thisArg: any, arg0?: any): any;
}


interface NewableFunction extends Function {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"strict": true,
"noLib": true
}
}
21 changes: 21 additions & 0 deletions graph/test/typescript/cases/95-bind-on-method-value/src/x.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// `call` / `apply` / `bind` on a METHOD read as a value, `this.render.bind(this)`. The
// receiver names a method, so it had no type for the Function members to attach to.
// src/globals.d.ts and noLib, as in case 21, put the targets inside the case.
class View {
render(): string { return "v"; }
get label(): () => string { return () => "l"; }
handler: (x: number) => number = (x) => x;
wire(): void {
this.render = this.render.bind(this);
const r = this.render.call(this);
const s = this.render.apply(this, []);
// the bound result runs the method
const bound = this.render.bind(this);
const b = bound();
// controls: a function-typed field already resolved; a getter's value is what it returns
const h = this.handler.bind(this);
const l = this.label.bind(this);
}
}
function runOn(v: View): string { return v.render.call(v); }
export { View, runOn };
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ ambiguous_unknown METHOD_CALL values#drive() @L29 -> -
callback_registered FUNCTION_CALL values#useLibrary() @L40 -> values#local(?)
known_edge CONSTRUCTOR_CALL values#drive() @L27 -> Pipeline#<new>()
known_edge FUNCTION_CALL values#apply(Transform,string) @L12 -> values#<arrow@4>(string)
known_edge FUNCTION_CALL values#drive() @L30 -> values#<arrow@4>(string)
known_edge FUNCTION_CALL values#drive() @L30 -> values#apply(Transform,string)
known_edge METHOD_CALL Pipeline#run(string) @L19 -> values#<arrow@4>(string)
known_edge METHOD_CALL values#drive() @L30 -> Pipeline#run(string)
multi_inferred FUNCTION_CALL values#drive() @L30 -> Pipeline#tag(string)
multi_inferred FUNCTION_CALL values#drive() @L30 -> values#<arrow@4>(string)
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ value values#<function-type>@4:25 -> values#Pipeline.tag
value_call values#Pipeline.run -> values#<arrow>@6
value_call values#apply -> values#<arrow>@6
value_call values#apply -> values#<arrow>@7
value_call values#drive -> values#Pipeline.tag
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ boundary_lib FUNCTION_CALL values#useLibrary() @L40 -> hooks#withHook(Hook,strin
callback_registered FUNCTION_CALL values#useLibrary() @L40 -> values#local(?)
known_edge CONSTRUCTOR_CALL values#drive() @L27 -> Pipeline#<new>()
known_edge FUNCTION_CALL values#apply(Transform,string) @L12 -> values#<arrow@4>(string)
known_edge FUNCTION_CALL values#drive() @L30 -> values#<arrow@4>(string)
known_edge FUNCTION_CALL values#drive() @L30 -> values#apply(Transform,string)
known_edge METHOD_CALL Pipeline#run(string) @L19 -> values#<arrow@4>(string)
known_edge METHOD_CALL values#drive() @L30 -> Pipeline#run(string)
multi_inferred FUNCTION_CALL values#drive() @L30 -> Pipeline#tag(string)
multi_inferred FUNCTION_CALL values#drive() @L30 -> values#<arrow@4>(string)
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ value values#<function-type>@4:25 -> values#Pipeline.tag
value_call values#Pipeline.run -> values#<arrow>@6
value_call values#apply -> values#<arrow>@6
value_call values#apply -> values#<arrow>@7
value_call values#drive -> values#Pipeline.tag
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
oracle=8 engine=9 agree=8 missing=0 (known 0, NEW 0) extra=1
oracle=8 engine=10 agree=8 missing=0 (known 0, NEW 0) extra=2
extra values#drive() -> Pipeline#tag(string)
extra values#useLibrary() -> hooks#identity(?)
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
oracle=6 engine=6 agree=6 missing=0 (known 0, NEW 0) extra=0
oracle=6 engine=7 agree=6 missing=0 (known 0, NEW 0) extra=1
extra values#drive() -> Pipeline#tag(string)
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
ambient_terminal FUNCTION_CALL Widget#now(number[]) @L40 -> globals#setTimeout(() =,number)
ambient_terminal FUNCTION_CALL Widget#wire(Target) @L24 -> globals#setTimeout(() =,number)
ambiguous_unknown METHOD_CALL Widget#now(number[]) @L40 -> -
ambiguous_unknown METHOD_CALL Widget#start(number[]) @L18 -> -
ambiguous_unknown METHOD_CALL Widget#wire(Target) @L24 -> -
callback_registered FUNCTION_CALL Widget#wire(Target) @L24 -> Widget#onHover()
callback_registered METHOD_CALL Widget#relay(Widget,number[]) @L29 -> Widget#handle(number)
callback_registered METHOD_CALL Widget#start(number[]) @L18 -> Widget#handle(number)
callback_registered METHOD_CALL Widget#start(number[]) @L19 -> Widget#handle(number)
callback_registered METHOD_CALL Widget#tap(Target) @L34 -> Widget#<arrow@34>()
callback_registered METHOD_CALL Widget#wire(Target) @L23 -> Widget#onClick()
known_edge METHOD_CALL Widget#<arrow@34>() @L34 -> Widget#onTap()
known_edge METHOD_CALL Widget#now(number[]) @L40 -> Function#call(unknown,unknown[])
known_edge METHOD_CALL Widget#now(number[]) @L41 -> Array#forEach((v: T) =,unknown)
known_edge METHOD_CALL Widget#now(number[]) @L42 -> Array#forEach((v: T) =,unknown)
known_edge METHOD_CALL Widget#relay(Widget,number[]) @L29 -> Array#forEach((v: T) =,unknown)
known_edge METHOD_CALL Widget#start(number[]) @L18 -> Array#forEach((v: T) =,unknown)
known_edge METHOD_CALL Widget#start(number[]) @L18 -> Function#bind(unknown,unknown[])
known_edge METHOD_CALL Widget#start(number[]) @L19 -> Array#map((v: T) =,unknown)
known_edge METHOD_CALL Widget#tap(Target) @L34 -> Target#addEventListener(string,() =)
known_edge METHOD_CALL Widget#wire(Target) @L23 -> Target#addEventListener(string,() =)
known_edge METHOD_CALL Widget#wire(Target) @L24 -> Function#bind(unknown,unknown[])
known_edge PROPERTY_READ Widget#now(number[]) @L42 -> Widget#current()
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
oracle=8 engine=10 agree=8 missing=0 (known 0, NEW 0) extra=2
oracle=8 engine=13 agree=8 missing=0 (known 0, NEW 0) extra=5
extra Widget#now(number[]) -> Function#call(unknown,unknown[])
extra Widget#now(number[]) -> globals#setTimeout(() =,number)
extra Widget#start(number[]) -> Function#bind(unknown,unknown[])
extra Widget#wire(Target) -> Function#bind(unknown,unknown[])
extra Widget#wire(Target) -> globals#setTimeout(() =,number)
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ambiguous_unknown CONSTRUCTOR_CALL handlers#main() @L52 -> -
ambiguous_unknown METHOD_CALL handlers#register(HandlerClass) @L38 -> -
known_edge CONSTRUCTOR_CALL handlers#main() @L46 -> Built#<new>()
known_edge CONSTRUCTOR_CALL handlers#main() @L47 -> Base#<new>()
known_edge CONSTRUCTOR_CALL handlers#main() @L52 -> handlers#<arrow@36>()
known_edge FUNCTION_CALL handlers#<module-init>() @L39 -> handlers#register(HandlerClass)
known_edge FUNCTION_CALL handlers#main() @L50 -> handlers#dispatch(Handler,string)
known_edge FUNCTION_CALL handlers#main() @L51 -> handlers#dispatch(Handler,string)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
oracle=6 engine=12 agree=5 missing=1 (known 1, NEW 0) extra=7
known handlers#main() -> handlers#<arrow@36>()
oracle=6 engine=13 agree=6 missing=0 (known 0, NEW 0) extra=7
extra handlers#dispatch(Handler,string) -> Base#handle(string)
extra handlers#dispatch(Handler,string) -> Built#handle(string)
extra handlers#dispatch(Handler,string) -> Orphan#handle(string)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
known_edge CONSTRUCTOR_CALL x#viaAlias(WidgetClass) @L8 -> x#<arrow@7>(WidgetDef)
known_edge CONSTRUCTOR_CALL x#viaCtorType(new (spec: WidgetDef) =) @L12 -> x#<arrow@12>(WidgetDef)
known_edge CONSTRUCTOR_CALL x#viaInterface(ButtonCtor) @L14 -> ButtonCtor#<arrow@13>(WidgetDef)
known_edge CONSTRUCTOR_CALL x#viaLiteral({ new (spec: WidgetDef): Button }) @L9 -> x#<arrow@9>(WidgetDef)
known_edge FUNCTION_CALL x#<module-init>() @L15 -> x#viaAlias(WidgetClass)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
── entry_point (5) ──
exported_from_entry_module x#viaAlias x.ts:8
exported_from_entry_module x#viaCtorType x.ts:12
exported_from_entry_module x#viaInterface x.ts:14
exported_from_entry_module x#viaLiteral x.ts:9
unimported_module x#<module> x.ts:1
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
91-construct-through-shape [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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
oracle=5 engine=5 agree=5 missing=0 (known 0, NEW 0) extra=0
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ambiguous_unknown OBJECT_CREATION_TYPE 0 x [EXPRESSION] -> -
known_edge METHOD_PARAM 0 Button [METHOD_PARAM] -> WidgetDef
known_edge METHOD_PARAM 0 ButtonCtor [METHOD_PARAM] -> WidgetDef
known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> ButtonCtor
known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> WidgetClass
known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> WidgetDef
known_edge METHOD_PARAM 1 x [METHOD_PARAM] -> WidgetDef
known_edge METHOD_RETURN 0 ButtonCtor [METHOD] -> Button
known_edge METHOD_RETURN 0 x [METHOD] -> Button
known_edge METHOD_RETURN 1 x [METHOD_PARAM] -> Button
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
91-construct-through-shape [types]
precision 1.0000 (7 correct, 0 wrong)
recall 0.8750 (7 of 8 the compiler resolved)
sites 17 resolved 13 (76.5%)
tiers ambiguous_unknown=4 known_edge=13
contexts METHOD_PARAM=7 METHOD_RETURN=6 OBJECT_CREATION_TYPE=4
not scored: 4 rows whose target is not a client declaration
MISSING WidgetClass USES WidgetDef
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
known_edge FUNCTION_CALL scorers#<module-init>() @L7 -> scorers#makeScorer(number)
known_edge FUNCTION_CALL x#viaNamed(string) @L7 -> scorers#<arrow@1>(string)
known_edge METHOD_CALL x#viaArrow(string) @L8 -> scorers#scoreArrow(?)
known_edge METHOD_CALL x#viaNamespace(string) @L5 -> scorers#<arrow@1>(string)
known_edge METHOD_CALL x#viaPlain(string) @L9 -> scorers#plain(string)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
── entry_point (5) ──
exported_from_entry_module x#viaArrow x.ts:8
exported_from_entry_module x#viaNamed x.ts:7
exported_from_entry_module x#viaNamespace x.ts:5
exported_from_entry_module x#viaPlain x.ts:9
unimported_module x#<module> x.ts:1
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
value scorers#<function-type>@1:22 -> scorers#<arrow>@3
value scorers#<function-type>@1:22 -> scorers#<arrow>@8
value_call x#viaNamed -> scorers#<arrow>@3
value_call x#viaNamespace -> scorers#<arrow>@3
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ambiguous_unknown read scorers#fn(?) -> -
ambiguous_unknown read scorers#plain(string) -> -
ambiguous_unknown read scorers#scoreArrow(?) -> -
known_edge read x#viaNamed(string) -> scorers#score
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
92-namespace-typed-const [fields]
precision 0.0000 (0 correct, 0 wrong)
recall 0.0000 (0 of 0 the compiler resolved)
sites 4 resolved 1 (25.0%)
tiers ambiguous_unknown=3 known_edge=1
access read=4
not scored: 4 rows whose target is not a client declaration
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Accepted gaps for 92-namespace-typed-const: each line is an edge the TypeScript compiler
# resolves and this engine does not. A NEW missing edge fails the suite;
# a line here that STARTS working also fails, so the debt cannot rot.
# A const that HOLDS an arrow: the compiler names the annotation's signature, the engine names
# the arrow's body, as it does for the same const read by bare name. Kept on purpose: adding the
# bodiless signature beside the body would hedge a site whose implementation is known.
x#viaArrow(string) -> scorers#<arrow@1>(string)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
oracle=5 engine=5 agree=4 missing=1 (known 1, NEW 0) extra=1
known x#viaArrow(string) -> scorers#<arrow@1>(string)
extra x#viaArrow(string) -> scorers#scoreArrow(?)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
known_edge METHOD_RETURN 0 scorers [METHOD] -> Scorer
known_edge VARIABLE_TYPE 0 scorers [VARIABLE] -> Scorer
Loading
Loading