Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

415 lines
21 KiB
C

/*
* repro_lsp_ts.c — EXHAUSTIVE per-LSP-pass invariant suite for the TypeScript /
* JavaScript / JSX hybrid LSP (internal/cbm/lsp/ts_lsp.c).
*
* WHAT THIS ASSERTS — the LSP RESOLUTION CONTRACT, one invariant per strategy.
* The TS cross resolver resolves each call via a specific STRATEGY and tags the
* resulting CALLS edge in its properties_json with
* "strategy":"lsp_<name>"
* (see ts_emit_resolved_call, ts_lsp.c:109-120; every concrete emit site passes
* a literal "lsp_ts..." string). Each strategy keys on a precise TS/TSX
* construct. This suite builds the MINIMAL fixture that exercises exactly one
* strategy, indexes it through the full production pipeline, and asserts TWO
* things:
* (a) callable-sourcing — the inner call is sourced at a Function/Method
* node, never at a Module/File node (inv_count_calls_by_source →
* module_sourced == 0). A Module-sourced call is the #554 attribution
* bug; this is the broad correctness floor.
* (b) strategy-presence — some CALLS edge carries "lsp_<strategy>" in its
* properties_json (inv_edge_has_strategy). This is the PRECISE per-pass
* invariant: it proves that exact resolution path fired and survived into
* the graph.
*
* RED vs GREEN — this is a STATUS BOARD, not a pass/fail gate (runs only under
* make test-repro / bug-repro.yml, never the branch-protection ci-ok gate):
* - GREEN = the LSP strategy works end-to-end = a permanent regression
* guard that it keeps working.
* - RED = the strategy is dropped, or the call lands Module-sourced, or
* the rescue is discarded. Either way the per-pass TEST DOCUMENTS
* the exact gap for the eventual fixer.
*
* TIE TO repro_invariant_lsp_rescue.c — that file pins the MECHANISM by which
* these can silently fail: cbm_pipeline_find_lsp_resolution joins each
* LSP-resolved call to the tree-sitter call by EXACT caller-QN string equality.
* When tree-sitter's enclosing-func walk falls back to the MODULE QN but the
* LSP built the real method QN, the strcmp never matches, the LSP rescue is
* discarded, and the edge stays Module-sourced with a registry strategy —
* NEVER an "lsp_" strategy. So a strategy that is correctly EMITTED by ts_lsp.c
* can still be ABSENT from the graph here: the exact-QN join suppresses it.
* Whenever a strategy below is RED, suspect that join first (a same-file
* in-function fixture sidesteps it; a cross-file fixture exercises it).
*
* STRATEGY INVENTORY — every literal "lsp_..." emitted by ts_lsp.c, grepped from
* the source (grep '"lsp_' internal/cbm/lsp/ts_lsp.c), with its keying site:
* lsp_ts_local (ts_lsp.c:2322) bare identifier call f() resolving to a
* module-local function (call_expression
* function is an `identifier`, found in the
* module registry).
* lsp_ts_method (ts_lsp.c:2284) obj.method() type-based dispatch on a
* receiver whose type is a NAMED in-file
* class (member_expression, lookup_method
* hits).
* lsp_ts_namespace (ts_lsp.c:2246) Ns.fn() where Ns is a namespace import
* (`import * as Ns from "./mod"`); the
* member_expression object is an identifier
* matching an import local name, fn resolves
* in that module's registry.
* lsp_ts_import (ts_lsp.c:2334) bare identifier call to an imported
* function (`import { helper } ...`); the
* identifier matches an import local name and
* resolves in the imported module's registry.
* lsp_ts_jsx (ts_lsp.c:2647) <Comp/> JSX element whose tag is a
* module-local component function (TSX only;
* uppercase tag, resolves via the module
* registry).
* lsp_ts_jsx_import (ts_lsp.c:2657) <Comp/> JSX element whose tag is an
* imported component (TSX only; tag matches
* an import local name → synthetic
* "<module>.<Comp>" QN). NOTE: this site
* builds the callee QN WITHOUT verifying the
* symbol exists in the registry, so it can
* emit even when the import target is absent.
* lsp_ts (ts_lsp.c:116) DEFAULT fallback inside ts_emit_resolved_call
* used only when a caller passes a NULL
* strategy. Every concrete emit site passes a
* literal "lsp_ts..." string, so "lsp_ts" is
* (as of this writing) never emitted as a
* distinct tag — expected ABSENT (RED). This
* TEST documents that the bare-"lsp_ts" path
* has no live caller; if it ever goes GREEN a
* new NULL-strategy emit site appeared.
* lsp_unresolved (ts_lsp.c:128) fallback marker for an unresolved call
* (ts_emit_unresolved_call, confidence 0.0).
* A 0.0-confidence unresolved entry is
* typically NOT promoted into a CALLS edge
* with the strategy tag, so this is expected
* ABSENT (RED) — it documents whether
* "lsp_unresolved" surfaces in the graph.
*
* LANGUAGE SELECTION — the filename extension picks the language exactly as the
* production indexer does: ".ts" → CBM_LANG_TYPESCRIPT, ".tsx" → CBM_LANG_TSX.
* jsx_mode (required by resolve_jsx_element, ts_lsp.c:2620) is enabled ONLY for
* CBM_LANG_TSX (cbm.c:619, pass_lsp_cross.c:267), so the two JSX fixtures use
* ".tsx" files; the non-JSX fixtures use ".ts".
*
* NOTE: line comments only inside this header (no nested block comments, per
* coding rules).
*/
#include "test_framework.h"
#include "repro_invariant_lib.h"
#include <store/store.h>
#include <string.h>
/* ── Shared per-strategy runners (DRY) ───────────────────────────────────── */
/*
* assert_lsp_strategy_files
*
* Index an N-file fixture and assert the per-pass LSP RESOLUTION CONTRACT:
* 1. the store opened (precondition — a setup failure is a FAIL, not a skip);
* 2. callable-sourcing: NO CALLS edge is Module/File-sourced, and at least one
* callable-sourced CALLS edge exists (else there is no signal at all);
* 3. strategy-presence: some CALLS edge carries "lsp_<strategy>" in its
* properties_json.
*
* The filename extension selects the language exactly as the production indexer
* does (".ts" → TypeScript, ".tsx" → TSX). Returns 0 on PASS (GREEN), non-zero
* on FAIL (RED) — the redness is the documented per-pass status.
*/
static int assert_lsp_strategy_files(const RFile *files, int nfiles,
const char *strategy) {
RProj lp;
cbm_store_t *store = rh_index_files(&lp, files, nfiles);
if (!store) {
printf(" %sFAIL%s %s:%d: index failed for strategy %s\n", tf_red(),
tf_reset(), __FILE__, __LINE__, strategy);
rh_cleanup(&lp, store);
return 1;
}
int module_sourced = -1;
int callable_sourced = -1;
inv_count_calls_by_source(store, lp.project, &module_sourced,
&callable_sourced);
int has_strategy = inv_edge_has_strategy(store, lp.project, strategy);
int rc = 0;
/* (a) callable-sourcing floor: zero Module/File-sourced CALLS edges. */
if (module_sourced != 0) {
printf(" %sFAIL%s %s:%d: strategy %s: %d Module-sourced CALLS "
"(expected 0)\n",
tf_red(), tf_reset(), __FILE__, __LINE__, strategy,
module_sourced);
rc = 1;
}
/* There must be a callable-sourced CALLS edge, else the fixture produced no
* call signal and the strategy assertion below would be vacuous. */
if (callable_sourced <= 0) {
printf(" %sFAIL%s %s:%d: strategy %s: no callable-sourced CALLS edge "
"(callable=%d)\n",
tf_red(), tf_reset(), __FILE__, __LINE__, strategy,
callable_sourced);
rc = 1;
}
/* (b) the precise per-pass invariant: the resolution strategy is present. */
if (!has_strategy) {
printf(" %sFAIL%s %s:%d: strategy %s ABSENT from any CALLS edge "
"properties_json\n",
tf_red(), tf_reset(), __FILE__, __LINE__, strategy);
rc = 1;
}
rh_cleanup(&lp, store);
return rc;
}
/* Single-file convenience wrapper. */
static int assert_lsp_strategy(const char *filename, const char *src,
const char *strategy) {
RFile f = {filename, src};
return assert_lsp_strategy_files(&f, 1, strategy);
}
/*
* assert_no_resolvable_edge — the ACCURATE invariant for a call whose callee is
* genuinely UNRESOLVABLE (undeclared symbol). No node can exist for it, so no
* CALLS edge can ever form and no resolution strategy can land on an edge. Index
* the single-file fixture and assert NO CALLS edge targets a node whose QN
* contains `callee_substr`. Returns 0 on PASS, non-zero on FAIL.
*/
static int assert_no_resolvable_edge(const char *filename, const char *src,
const char *callee_substr) {
RProj lp;
cbm_store_t *store = rh_index(&lp, filename, src);
if (!store) {
printf(" %sFAIL%s %s:%d: index failed for no-edge callee %s\n", tf_red(),
tf_reset(), __FILE__, __LINE__, callee_substr);
rh_cleanup(&lp, store);
return 1;
}
int rc = 0;
/* Exercised-check: the fixture MUST produce at least one callable-sourced
* CALLS edge (its in-fixture control call). Without it the "no edge to
* <callee>" invariant is VACUOUS — it also passes when extraction silently
* produced nothing, so a green would not prove the unresolvable call was
* actually processed and correctly dropped. */
int module_sourced = -1;
int callable_sourced = -1;
inv_count_calls_by_source(store, lp.project, &module_sourced, &callable_sourced);
(void)module_sourced;
if (callable_sourced <= 0) {
printf(" %sFAIL%s %s:%d: no callable-sourced CALLS edge — fixture not "
"exercised; the no-edge invariant for %s is vacuous\n",
tf_red(), tf_reset(), __FILE__, __LINE__, callee_substr);
rc = 1;
}
if (!inv_no_calls_edge_to_qn(store, lp.project, callee_substr)) {
printf(" %sFAIL%s %s:%d: a CALLS edge unexpectedly targets %s "
"(expected NONE — callee is unresolvable)\n",
tf_red(), tf_reset(), __FILE__, __LINE__, callee_substr);
rc = 1;
}
rh_cleanup(&lp, store);
return rc;
}
/*
* assert_strategy_absent — assert a given strategy tag NEVER surfaces on any
* CALLS edge. Used for the bare "lsp_ts" probe: the default fallback tag is
* never emitted as a distinct strategy (every concrete site passes a literal
* "lsp_ts_*"), and the fixture is an UNRESOLVED call (no "lsp_ts_*" edge to
* substring-alias against), so its absence is the accurate, intended invariant.
* Returns 0 on PASS (tag absent), non-zero on FAIL (tag unexpectedly present).
*/
static int assert_strategy_absent(const char *filename, const char *src,
const char *strategy) {
RProj lp;
cbm_store_t *store = rh_index(&lp, filename, src);
if (!store) {
printf(" %sFAIL%s %s:%d: index failed for absent-strategy %s\n", tf_red(),
tf_reset(), __FILE__, __LINE__, strategy);
rh_cleanup(&lp, store);
return 1;
}
int rc = 0;
if (inv_edge_has_strategy(store, lp.project, strategy)) {
printf(" %sFAIL%s %s:%d: strategy %s unexpectedly PRESENT on a CALLS "
"edge (expected ABSENT — bare fallback tag is never emitted)\n",
tf_red(), tf_reset(), __FILE__, __LINE__, strategy);
rc = 1;
}
rh_cleanup(&lp, store);
return rc;
}
/* ── Fixtures ────────────────────────────────────────────────────────────────
*
* Each fixture is the MINIMAL construct ts_lsp.c keys on for one strategy. The
* call we care about always lives inside a function or method so callable-
* sourcing is testable; the callee is also defined in-file (or in a sibling file
* for the cross-file import strategies) so the registry can resolve it.
* ───────────────────────────────────────────────────────────────────────── */
/* lsp_ts_local — bare identifier call f() that resolves to a module-local
* function (ts_lsp.c:2310-2322: call_expression function is an `identifier`,
* cbm_registry_lookup_symbol_by_args hits on the module QN). */
static const char kTsLocal[] =
"function helper(x: number): number { return x + 1; }\n"
"function caller(v: number): number { return helper(v); }\n";
/* lsp_ts_method — obj.method() type-based dispatch on a NAMED in-file class
* receiver (ts_lsp.c:2257-2284: member_expression, ts_eval_expr_type gives the
* receiver's NAMED type, lookup_method finds the method). */
static const char kTsMethod[] =
"class Counter {\n"
" inc(x: number): number { return x + 1; }\n"
"}\n"
"function caller(): number {\n"
" const c = new Counter();\n"
" return c.inc(1);\n"
"}\n";
/* lsp_ts_namespace — Ns.fn() where Ns is a namespace import
* (`import * as Ns from "./mod"`). ts_lsp.c:2233-2246: the member_expression
* object is an `identifier` matching an import local name; fn resolves in that
* imported module's registry → lsp_ts_namespace. Cross-file: util.ts exports the
* function, main.ts imports the namespace and calls Util.compute(). */
static const RFile kTsNamespace[] = {
{"util.ts",
"export function compute(x: number): number { return x * 3; }\n"},
{"main.ts",
"import * as Util from \"./util\";\n"
"function caller(v: number): number { return Util.compute(v); }\n"},
};
/* lsp_ts_import — bare identifier call to an imported function
* (`import { helper } from "./mod"`). ts_lsp.c:2327-2334: the call_expression
* function is an `identifier` matching an import local name; helper resolves in
* the imported module's registry → lsp_ts_import. Cross-file: util.ts exports
* helper, main.ts imports it by name and calls it bare. */
static const RFile kTsImport[] = {
{"util.ts",
"export function helper(x: number): number { return x + 5; }\n"},
{"main.ts",
"import { helper } from \"./util\";\n"
"function caller(v: number): number { return helper(v); }\n"},
};
/* lsp_ts_jsx — <Comp/> JSX element whose tag is a module-local component
* function (ts_lsp.c:2643-2647). TSX only (jsx_mode); the tag's first letter is
* uppercase so it is NOT treated as an intrinsic HTML element; it resolves via
* cbm_registry_lookup_symbol on the module QN. App() renders <Widget/> defined
* in the same file. */
static const char kTsxJsx[] =
"function Widget(): any { return null; }\n"
"function App(): any {\n"
" return <Widget />;\n"
"}\n";
/* lsp_ts_jsx_import — <Comp/> JSX element whose tag is an imported component
* (ts_lsp.c:2652-2657). TSX only; the tag matches an import local name → a
* synthetic "<module>.<Comp>" callee QN is emitted (this site does NOT verify
* the symbol is in the registry). Cross-file: widget.tsx exports Widget,
* app.tsx imports it and renders <Widget/>. */
static const RFile kTsxJsxImport[] = {
{"widget.tsx",
"export function Widget(): any { return null; }\n"},
{"app.tsx",
"import { Widget } from \"./widget\";\n"
"function App(): any {\n"
" return <Widget />;\n"
"}\n"},
};
/* lsp_ts — the DEFAULT fallback strategy inside ts_emit_resolved_call
* (ts_lsp.c:116): used only when a caller passes a NULL strategy. Every concrete
* emit site passes a literal "lsp_ts..." string, so "lsp_ts" is never emitted as
* a distinct tag. This fixture is an ordinary resolved local call; we assert
* whether the bare "lsp_ts" tag ever surfaces. EXPECTED ABSENT (RED): if it goes
* GREEN, a new NULL-strategy emit site has appeared and should be audited.
* NOTE: inv_edge_has_strategy does a substring match, and "lsp_ts" is a prefix of
* "lsp_ts_local"/"lsp_ts_method"/etc., so a local-call fixture would substring-
* match "lsp_ts" via "lsp_ts_local" and report a false GREEN. To probe the bare
* tag in isolation we use an UNRESOLVED call (totallyUnknownFn) whose only
* possible tag is the unresolved marker — there is no "lsp_ts_*" edge to alias
* against, so a GREEN here would mean a literal bare "lsp_ts" edge exists. */
static const char kTsDefault[] =
"function caller(v: number): number { return totallyUnknownFn(v); }\n";
/* lsp_unresolved — a call to a function not in the registry; the resolver
* records the fallback marker via ts_emit_unresolved_call (ts_lsp.c:122-132,
* strategy = "lsp_unresolved", confidence 0.0). A 0.0-confidence unresolved entry
* is typically NOT promoted into a CALLS edge carrying the strategy tag, so this
* is EXPECTED ABSENT (RED) — it documents whether "lsp_unresolved" surfaces in
* the graph. */
static const char kTsUnresolved[] =
"function known(x: number): number { return x + 1; }\n"
"function caller(v: number): number { return known(v) + totallyUnknownFn(v); }\n";
/* ── Per-strategy tests ──────────────────────────────────────────────────── */
TEST(repro_lsp_ts_local) {
return assert_lsp_strategy("main.ts", kTsLocal, "lsp_ts_local");
}
TEST(repro_lsp_ts_method) {
return assert_lsp_strategy("main.ts", kTsMethod, "lsp_ts_method");
}
TEST(repro_lsp_ts_namespace) {
return assert_lsp_strategy_files(kTsNamespace,
(int)(sizeof(kTsNamespace) /
sizeof(kTsNamespace[0])),
"lsp_ts_namespace");
}
TEST(repro_lsp_ts_import) {
return assert_lsp_strategy_files(
kTsImport, (int)(sizeof(kTsImport) / sizeof(kTsImport[0])),
"lsp_ts_import");
}
TEST(repro_lsp_ts_jsx) {
return assert_lsp_strategy("app.tsx", kTsxJsx, "lsp_ts_jsx");
}
TEST(repro_lsp_ts_jsx_import) {
return assert_lsp_strategy_files(kTsxJsxImport,
(int)(sizeof(kTsxJsxImport) /
sizeof(kTsxJsxImport[0])),
"lsp_ts_jsx_import");
}
TEST(repro_lsp_ts_default) {
/* The bare "lsp_ts" fallback tag is never emitted as a distinct strategy
* (every concrete site passes a literal "lsp_ts_*"); the fixture is an
* UNRESOLVED call with no "lsp_ts_*" edge to substring-alias against. Per the
* fixture header, the accurate invariant is that "lsp_ts" is ABSENT. */
return assert_strategy_absent("main.ts", kTsDefault, "lsp_ts");
}
TEST(repro_lsp_ts_unresolved) {
/* totallyUnknownFn is UNDECLARED — no node can exist for it, so no CALLS
* edge can ever form. Assert the accurate no-resolvable-edge behaviour
* instead of a resolution strategy on an edge (unachievable by design). */
return assert_no_resolvable_edge("main.ts", kTsUnresolved, "totallyUnknownFn");
}
/* ── Suite ───────────────────────────────────────────────────────────────── */
SUITE(repro_lsp_ts) {
RUN_TEST(repro_lsp_ts_local);
RUN_TEST(repro_lsp_ts_method);
RUN_TEST(repro_lsp_ts_namespace);
RUN_TEST(repro_lsp_ts_import);
RUN_TEST(repro_lsp_ts_jsx);
RUN_TEST(repro_lsp_ts_jsx_import);
RUN_TEST(repro_lsp_ts_default);
RUN_TEST(repro_lsp_ts_unresolved);
}