should be skipped (no false positive). */
cbm_free_result(r);
PASS();
}
TEST(tslsp_jsx_nested_component) {
CBMFileResult *r =
extract_tsx("function Inner(): JSX.Element { return
; }\n"
"function Outer(): JSX.Element { return
; }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, "Outer", "Inner"), 0);
cbm_free_result(r);
PASS();
}
/* ── Category 23: TSX combined ─────────────────────────────────────────────── */
TEST(tslsp_tsx_typed_props_method_call) {
CBMFileResult *r = extract_tsx("class Service { fetch(): string { return ''; } }\n"
"function App(props: { svc: Service }): JSX.Element {\n"
" props.svc.fetch();\n"
" return
;\n"
"}\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, "App", "fetch"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_tsx_class_component_method) {
CBMFileResult *r =
extract_tsx("class Logger { log(): void {} }\n"
"class App {\n"
" logger: Logger = new Logger();\n"
" render(): JSX.Element { this.logger.log(); return
; }\n"
"}\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, "App.render", "log"), 0);
cbm_free_result(r);
PASS();
}
/* ── Tuple types (TS) ─────────────────────────────────────────────────────── */
TEST(tslsp_tuple_destructure_method) {
CBMFileResult *r = extract_ts("class Box { run(): void {} }\n"
"function pair(): [Box, number] { return [new Box(), 42]; }\n"
"function go() { const [b, n] = pair(); b.run(); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "run"), 0);
cbm_free_result(r);
PASS();
}
/* ── Number wrapper class ──────────────────────────────────────────────────── */
TEST(tslsp_stdlib_number_toString) {
CBMFileResult *r = extract_ts("function go(n: number) { n.toString(); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "toString"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_stdlib_number_toFixed) {
CBMFileResult *r = extract_ts("function go(n: number) { n.toFixed(2); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "toFixed"), 0);
cbm_free_result(r);
PASS();
}
/* ── More crash-safety ─────────────────────────────────────────────────────── */
TEST(tslsp_nocrash_huge_union) {
CBMFileResult *r =
extract_ts("type T = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j';\n"
"function go(x: T) { console.log(x); }\n");
ASSERT_NOT_NULL(r);
cbm_free_result(r);
PASS();
}
TEST(tslsp_nocrash_intersection_chain) {
CBMFileResult *r = extract_ts(
"interface A { x: number; } interface B { y: string; } interface C { z: boolean; }\n"
"function go(o: A & B & C) { console.log(o.x, o.y, o.z); }\n");
ASSERT_NOT_NULL(r);
cbm_free_result(r);
PASS();
}
TEST(tslsp_nocrash_satisfies_keyword) {
CBMFileResult *r = extract_ts(
"type X = { name: string };\n"
"function go() { const obj = { name: 'a' } satisfies X; console.log(obj.name); }\n");
ASSERT_NOT_NULL(r);
cbm_free_result(r);
PASS();
}
/* ── Category 21: JSDoc inference (js_mode) ────────────────────────────────── */
TEST(tslsp_jsdoc_param_method_call) {
CBMFileResult *r = extract_js("class Database { query(sql) { return ''; } }\n"
"/** @param {Database} db */\n"
"function doWork(db) { db.query('SELECT 1'); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, "doWork", "query"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_jsdoc_returns_propagation) {
CBMFileResult *r = extract_js("class Conn { ping() {} }\n"
"/** @returns {Conn} */\n"
"function makeConn() { return new Conn(); }\n"
"function go() { const c = makeConn(); c.ping(); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "ping"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_jsdoc_multi_param) {
CBMFileResult *r = extract_js("class Logger { info(s) {} }\n"
"class Cache { put(k) {} }\n"
"/**\n"
" * @param {Logger} log\n"
" * @param {Cache} cache\n"
" */\n"
"function setup(log, cache) {\n"
" log.info('hi');\n"
" cache.put('k');\n"
"}\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, "setup", "info"), 0);
ASSERT_GTE(require_resolved(r, "setup", "put"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_jsdoc_array_generic) {
CBMFileResult *r = extract_js("/** @param {Array
} xs */\n"
"function go(xs) { xs.map(x => x); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "map"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_jsdoc_promise_returns) {
CBMFileResult *r = extract_js("/** @returns {Promise} */\n"
"function load() { return Promise.resolve('hi'); }\n"
"function go() { load().then(x => x); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "then"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_jsdoc_ignored_in_ts_mode) {
/* In TS mode, JSDoc is informative only — the inline annotations win. */
CBMFileResult *r = extract_ts("class Foo { run(): void {} }\n"
"/** @param {Foo} x */\n"
"function go(x: Foo) { x.run(); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "run"), 0);
cbm_free_result(r);
PASS();
}
/* ── Category 31: real-library patterns (smoke) ────────────────────────────── */
TEST(tslsp_real_express_handler) {
CBMFileResult *r = extract_ts(
"interface Request { params: { [key: string]: string }; }\n"
"interface Response { send(body: string): Response; status(code: number): Response; }\n"
"function handle(req: Request, res: Response) { res.status(200).send('ok'); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, "handle", "status"), 0);
ASSERT_GTE(require_resolved(r, "handle", "send"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_real_fluent_builder) {
CBMFileResult *r =
extract_ts("class QueryBuilder {\n"
" select(cols: string[]): QueryBuilder { return this; }\n"
" from(t: string): QueryBuilder { return this; }\n"
" where(c: string): QueryBuilder { return this; }\n"
" execute(): string[] { return []; }\n"
"}\n"
"function go() {\n"
" new QueryBuilder().select(['id']).from('users').where('1=1').execute();\n"
"}\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "select"), 0);
ASSERT_GTE(require_resolved(r, ".go", "from"), 0);
ASSERT_GTE(require_resolved(r, ".go", "where"), 0);
ASSERT_GTE(require_resolved(r, ".go", "execute"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_real_observer_pattern) {
CBMFileResult *r = extract_ts("interface Observer { update(value: number): void; }\n"
"class Subject {\n"
" notify(o: Observer): void { o.update(42); }\n"
"}\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, "Subject.notify", "update"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_real_event_emitter) {
CBMFileResult *r = extract_ts(
"class EventEmitter {\n"
" on(event: string, cb: (data: number) => void): EventEmitter { return this; }\n"
" emit(event: string, data: number): EventEmitter { return this; }\n"
"}\n"
"function go(em: EventEmitter) { em.on('x', d => d).emit('x', 1); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "on"), 0);
ASSERT_GTE(require_resolved(r, ".go", "emit"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_real_singleton) {
CBMFileResult *r =
extract_ts("class Singleton {\n"
" private static instance: Singleton;\n"
" static getInstance(): Singleton { return new Singleton(); }\n"
" run(): void {}\n"
"}\n"
"function go() { Singleton.getInstance().run(); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "getInstance"), 0);
ASSERT_GTE(require_resolved(r, ".go", "run"), 0);
cbm_free_result(r);
PASS();
}
/* ── Edge cases ────────────────────────────────────────────────────────────── */
TEST(tslsp_readonly_field_method) {
CBMFileResult *r = extract_ts("class Tool { fire(): void {} }\n"
"class Box { readonly tool: Tool = new Tool(); }\n"
"function go(b: Box) { b.tool.fire(); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "fire"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_constructor_method_call) {
CBMFileResult *r = extract_ts("class Foo { run(): void {} }\n"
"function go() { new Foo().run(); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "run"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_method_chained_through_field) {
CBMFileResult *r =
extract_ts("class Repo { all(): string[] { return []; } }\n"
"class App { repo: Repo = new Repo(); }\n"
"function go(app: App) { app.repo.all().map(s => s.toUpperCase()); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "all"), 0);
ASSERT_GTE(require_resolved(r, ".go", "map"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_callback_returning_method_call) {
CBMFileResult *r = extract_ts("class Box { run(): string { return ''; } }\n"
"function go(b: Box) { [1, 2].forEach(_n => b.run()); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "run"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_this_in_arrow_method) {
CBMFileResult *r = extract_ts("class Foo {\n"
" a: number = 1;\n"
" handler = () => { this.bar(); };\n"
" bar(): void {}\n"
"}\n");
ASSERT_NOT_NULL(r);
/* Arrow methods bind `this` lexically — accept smoke pass for v1 */
cbm_free_result(r);
PASS();
}
TEST(tslsp_optional_field_method) {
CBMFileResult *r = extract_ts("class Tool { fire(): void {} }\n"
"class Box { tool?: Tool; }\n"
"function go(b: Box) { if (b.tool) { b.tool.fire(); } }\n");
ASSERT_NOT_NULL(r);
/* Optional field narrowing — accept partial success for v1 */
cbm_free_result(r);
PASS();
}
/* ── More crash-safety ─────────────────────────────────────────────────────── */
TEST(tslsp_nocrash_enum_basic) {
CBMFileResult *r = extract_ts("enum Color { Red, Green, Blue }\n"
"function go(c: Color) { console.log(c); }\n");
ASSERT_NOT_NULL(r);
cbm_free_result(r);
PASS();
}
TEST(tslsp_nocrash_namespace) {
CBMFileResult *r = extract_ts("namespace MyNS { export function inner(): void {} }\n"
"function go() { MyNS.inner(); }\n");
ASSERT_NOT_NULL(r);
cbm_free_result(r);
PASS();
}
TEST(tslsp_nocrash_async_generator) {
CBMFileResult *r = extract_ts("async function* go() { yield 1; yield 2; }\n");
ASSERT_NOT_NULL(r);
cbm_free_result(r);
PASS();
}
TEST(tslsp_nocrash_template_literal_expr) {
CBMFileResult *r = extract_ts("function go(s: string) { console.log(`hello ${s.length}`); }\n");
ASSERT_NOT_NULL(r);
cbm_free_result(r);
PASS();
}
TEST(tslsp_nocrash_complex_generic_constraint) {
CBMFileResult *r =
extract_ts("function id(x: T): T { return x; }\n"
"function go(arr: number[]) { id(arr); }\n");
ASSERT_NOT_NULL(r);
cbm_free_result(r);
PASS();
}
/* ── Discriminated unions / dispatch ───────────────────────────────────────── */
TEST(tslsp_discriminated_union_method) {
CBMFileResult *r =
extract_ts("interface Circle { kind: 'circle'; radius: number; render(): void; }\n"
"interface Square { kind: 'square'; side: number; render(): void; }\n"
"function go(s: Circle | Square) { s.render(); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "render"), 0);
cbm_free_result(r);
PASS();
}
/* ── Abstract class + concrete subclass ────────────────────────────────────── */
TEST(tslsp_abstract_concrete_dispatch) {
CBMFileResult *r = extract_ts(
"abstract class Shape { abstract area(): number; describe(): string { return ''; } }\n"
"class Square extends Shape { area(): number { return 1; } }\n"
"function go(s: Square) { s.area(); s.describe(); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "area"), 0);
ASSERT_GTE(require_resolved(r, ".go", "describe"), 0);
cbm_free_result(r);
PASS();
}
/* ── Generics + inheritance ────────────────────────────────────────────────── */
TEST(tslsp_generic_class_method) {
CBMFileResult *r = extract_ts(
"class Container { items: T[] = []; add(x: T): void { this.items.push(x); } }\n"
"function go(c: Container) { c.add(1); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "add"), 0);
cbm_free_result(r);
PASS();
}
/* ── Static fields ─────────────────────────────────────────────────────────── */
TEST(tslsp_static_field_method) {
CBMFileResult *r = extract_ts("class Tool { fire(): void {} }\n"
"class Registry { static defaultTool: Tool = new Tool(); }\n"
"function go() { Registry.defaultTool.fire(); }\n");
ASSERT_NOT_NULL(r);
/* Static fields not yet first-class — accept smoke pass */
cbm_free_result(r);
PASS();
}
/* ── More JSDoc patterns ───────────────────────────────────────────────────── */
TEST(tslsp_jsdoc_param_after_text) {
CBMFileResult *r = extract_js("class Box { run() {} }\n"
"/**\n"
" * Helpful description.\n"
" * @param {Box} box - the box to use\n"
" */\n"
"function go(box) { box.run(); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "run"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_jsdoc_typedef_used_via_param) {
/* JSDoc @typedef is declaration-merging-like; v1 may not honour it,
* but parsing shouldn't crash. */
CBMFileResult *r = extract_js("/** @typedef {{ name: string }} User */\n"
"/** @param {User} u */\n"
"function go(u) { console.log(u.name); }\n");
ASSERT_NOT_NULL(r);
cbm_free_result(r);
PASS();
}
/* ── Real-library: Promise.all chain ───────────────────────────────────────── */
TEST(tslsp_real_promise_all_then) {
CBMFileResult *r =
extract_ts("function go() { Promise.all([1, 2]).then(arr => arr.length); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "all"), 0);
ASSERT_GTE(require_resolved(r, ".go", "then"), 0);
cbm_free_result(r);
PASS();
}
/* ── Edge: optional method on union ────────────────────────────────────────── */
TEST(tslsp_method_on_typeof_class) {
CBMFileResult *r = extract_ts("class Foo { run(): void {} }\n"
"function go(F: typeof Foo) { const f = new F(); f.run(); }\n");
ASSERT_NOT_NULL(r);
/* `typeof Foo` is a class constructor type — accept smoke pass for v1 */
cbm_free_result(r);
PASS();
}
/* ── Generic Promise chain ─────────────────────────────────────────────────── */
TEST(tslsp_promise_chain_via_local) {
CBMFileResult *r =
extract_ts("function load(): Promise { return Promise.resolve('hi'); }\n"
"function go() {\n"
" const p = load();\n"
" p.then(s => s).catch(_e => '');\n"
"}\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "then"), 0);
ASSERT_GTE(require_resolved(r, ".go", "catch"), 0);
cbm_free_result(r);
PASS();
}
/* ── Object literal nested resolution ──────────────────────────────────────── */
TEST(tslsp_obj_literal_method_property) {
CBMFileResult *r = extract_ts("class Logger { info(): void {} }\n"
"function go() {\n"
" const env = { logger: new Logger() };\n"
" env.logger.info();\n"
"}\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "info"), 0);
cbm_free_result(r);
PASS();
}
/* ── Contextual callback typing ─────────────────────────────────────────────── */
TEST(tslsp_ctx_array_map_callback) {
CBMFileResult *r = extract_ts("class Box { run(): void {} }\n"
"function go(boxes: Box[]) { boxes.map(b => b.run()); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "run"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_ctx_array_filter_callback) {
CBMFileResult *r =
extract_ts("class User { isActive(): boolean { return true; } }\n"
"function go(users: User[]) { users.filter(u => u.isActive()); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "isActive"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_ctx_array_find_callback) {
CBMFileResult *r =
extract_ts("class Item { matches(q: string): boolean { return false; } }\n"
"function go(items: Item[]) { items.find(i => i.matches('x')); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "matches"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_ctx_array_forEach_callback) {
CBMFileResult *r = extract_ts("class Job { execute(): void {} }\n"
"function go(jobs: Job[]) { jobs.forEach(j => j.execute()); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "execute"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_ctx_array_block_body_callback) {
CBMFileResult *r = extract_ts("class Foo { ping(): void {} bar(): string { return ''; } }\n"
"function go(xs: Foo[]) {\n"
" xs.forEach(x => { x.ping(); x.bar(); });\n"
"}\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "ping"), 0);
ASSERT_GTE(require_resolved(r, ".go", "bar"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_ctx_chain_filter_then_map) {
CBMFileResult *r =
extract_ts("class Row { active(): boolean { return true; } id(): number { return 0; } }\n"
"function go(rows: Row[]) {\n"
" rows.filter(r => r.active()).map(r => r.id());\n"
"}\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "active"), 0);
ASSERT_GTE(require_resolved(r, ".go", "id"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_ctx_user_defined_callback) {
CBMFileResult *r =
extract_ts("class Foo { run(): void {} }\n"
"function withFoo(f: Foo, cb: (x: Foo) => void): void { cb(f); }\n"
"function go(f: Foo) { withFoo(f, x => x.run()); }\n");
ASSERT_NOT_NULL(r);
ASSERT_GTE(require_resolved(r, ".go", "run"), 0);
cbm_free_result(r);
PASS();
}
/* ── Narrowing (real, not smoke) ───────────────────────────────────────────── */
TEST(tslsp_narrow_typeof_string_method) {
CBMFileResult *r = extract_ts("function go(x: string | number) {\n"
" if (typeof x === 'string') { x.toLowerCase(); }\n"
"}\n");
ASSERT_NOT_NULL(r);
/* Real narrowing — String.toLowerCase resolves through wrapper. */
ASSERT_GTE(require_resolved(r, ".go", "toLowerCase"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_narrow_instanceof_dispatch) {
CBMFileResult *r = extract_ts("class A { ping(): void {} }\n"
"class B { pong(): void {} }\n"
"function go(x: A | B) {\n"
" if (x instanceof A) { x.ping(); }\n"
" else { x.pong(); }\n"
"}\n");
ASSERT_NOT_NULL(r);
/* v2: real flow-sensitive narrowing — `x instanceof A` narrows x to A in the
* consequence, so x.ping() resolves via NAMED("test.main.A") directly. The
* else branch falls back to union dispatch which finds pong on B. */
ASSERT_GTE(require_resolved(r, ".go", "ping"), 0);
ASSERT_GTE(require_resolved(r, ".go", "pong"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_narrow_discriminated_kind) {
CBMFileResult *r = extract_ts("interface Circle { kind: 'circle'; r(): number; }\n"
"interface Square { kind: 'square'; s(): number; }\n"
"function go(shape: Circle | Square) {\n"
" if (shape.kind === 'circle') { shape.r(); }\n"
" else { shape.s(); }\n"
"}\n");
ASSERT_NOT_NULL(r);
/* Both methods resolve — basic union dispatch covers both branches. */
ASSERT_GTE(require_resolved(r, ".go", "r"), 0);
ASSERT_GTE(require_resolved(r, ".go", "s"), 0);
cbm_free_result(r);
PASS();
}
/* ── React-like patterns ───────────────────────────────────────────────────── */
TEST(tslsp_react_useState_pattern) {
CBMFileResult *r = extract_tsx(
"function useState(initial: T): [T, (v: T) => void] { return [initial, _v => {}]; }\n"
"function App(): JSX.Element {\n"
" const [count, setCount] = useState(0);\n"
" return {count}
;\n"
"}\n");
ASSERT_NOT_NULL(r);
/* useState resolves; setCount is callable */
ASSERT_GTE(require_resolved(r, "App", "useState"), 0);
cbm_free_result(r);
PASS();
}
TEST(tslsp_react_callback_in_jsx) {
CBMFileResult *r = extract_tsx(
"interface Service { fetch(): string; }\n"
"function Button(props: { onClick: () => void }): JSX.Element { return ; }\n"
"function App(svc: Service): JSX.Element { return