mirror of
https://github.com/giancarloerra/socraticode.git
synced 2026-07-03 14:05:21 +02:00
e6ce32710a
Resolves #43. On Linux/Node combinations where one ast-grep grammar package's prebuilt parser binary is missing for the host architecture, the v1.8.3 loader silently failed to register every dynamic grammar in the batch, not just the broken one. registerDynamicLanguage iterates and accesses each module's lazy libraryPath getter; one throwing getter aborts the call atomically and zero grammars end up registered. Fix: pre-validate each grammar's libraryPath getter inside the per- grammar try/catch so a missing prebuild is contained to that grammar. Build the batch object with only the survivors and make ONE atomic registerDynamicLanguage call. Standard environments are unaffected because all grammars pass pre-validation. Affected environments lose only the unloadable grammar, the rest register cleanly. Also captures the actual error reason (the previous empty `catch {}` discarded it), bumps symbol- and import-extraction failure logs from debug to warn with one-shot dedupe per language, exposes loaded/failed grammars via a new getDynamicLanguageStatus() API, and renders an "AST grammars" block in codebase_graph_status output so users see loader state without enabling debug logging. Empirically verified against @ast-grep/napi@0.40.5 in a clean Node environment. Two probes confirmed the napi semantics: sequential register({A}); register({B}) calls are REPLACING (so per-language registration is broken), and batch register with one bad getter is ATOMIC (so pre-validation before the batch call is the only correct pattern). All 721 existing unit tests continue to pass unchanged. Adds 9 new tests for the loader status API; total 730 pass. typecheck and biome clean. CodeRabbit returned no findings on this diff. Co-authored-by: X-Adam <X-Adam@users.noreply.github.com>
110 lines
4.1 KiB
TypeScript
110 lines
4.1 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-only
|
|
// Copyright (C) 2026 Giancarlo Erra - Altaire Limited
|
|
//
|
|
// Tests for the dynamic ast-grep grammar loader.
|
|
//
|
|
// Background: in 1.8.3 the loader called `registerDynamicLanguage` once with
|
|
// every loaded grammar in a single batch. Per `@ast-grep/napi`, that call is
|
|
// atomic: a single throwing `libraryPath` getter aborts the whole batch and
|
|
// no grammar gets registered. On environments where one prebuilt binary is
|
|
// missing for the host architecture, this manifested as all dynamic
|
|
// languages being silently broken (issue #43).
|
|
//
|
|
// 1.8.4 fixes this by pre-validating each grammar's `libraryPath` getter in
|
|
// the inner per-grammar try/catch so a missing prebuild is contained to that
|
|
// one grammar. The `getDynamicLanguageStatus` API exposes which grammars
|
|
// registered successfully and which failed (with the underlying reason),
|
|
// surfaced in `codebase_graph_status`.
|
|
|
|
import { beforeAll, describe, expect, it } from "vitest";
|
|
import {
|
|
ensureDynamicLanguages,
|
|
getDynamicLanguageStatus,
|
|
} from "../../src/services/code-graph.js";
|
|
|
|
beforeAll(() => {
|
|
ensureDynamicLanguages();
|
|
});
|
|
|
|
describe("dynamic-language-loader", () => {
|
|
describe("ensureDynamicLanguages", () => {
|
|
it("is synchronous (returns void, not a Promise)", () => {
|
|
const result = ensureDynamicLanguages();
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it("is idempotent — calling repeatedly does not change registration state", () => {
|
|
const before = getDynamicLanguageStatus();
|
|
ensureDynamicLanguages();
|
|
ensureDynamicLanguages();
|
|
const after = getDynamicLanguageStatus();
|
|
|
|
expect(after.loaded).toEqual(before.loaded);
|
|
expect(after.failed.map((f) => f.name)).toEqual(before.failed.map((f) => f.name));
|
|
});
|
|
});
|
|
|
|
describe("getDynamicLanguageStatus", () => {
|
|
it("returns loaded and failed arrays", () => {
|
|
const status = getDynamicLanguageStatus();
|
|
expect(status).toHaveProperty("loaded");
|
|
expect(status).toHaveProperty("failed");
|
|
expect(Array.isArray(status.loaded)).toBe(true);
|
|
expect(Array.isArray(status.failed)).toBe(true);
|
|
});
|
|
|
|
it("loaded entries are unique strings", () => {
|
|
const status = getDynamicLanguageStatus();
|
|
const seen = new Set<string>();
|
|
for (const name of status.loaded) {
|
|
expect(typeof name).toBe("string");
|
|
expect(seen.has(name)).toBe(false);
|
|
seen.add(name);
|
|
}
|
|
});
|
|
|
|
it("failed entries each include name and error", () => {
|
|
const status = getDynamicLanguageStatus();
|
|
for (const f of status.failed) {
|
|
expect(typeof f.name).toBe("string");
|
|
expect(typeof f.error).toBe("string");
|
|
expect(f.error.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("loaded list is sorted alphabetically", () => {
|
|
const status = getDynamicLanguageStatus();
|
|
const sorted = [...status.loaded].sort();
|
|
expect(status.loaded).toEqual(sorted);
|
|
});
|
|
|
|
it("failed list is sorted alphabetically by name", () => {
|
|
const status = getDynamicLanguageStatus();
|
|
const names = status.failed.map((f) => f.name);
|
|
const sorted = [...names].sort();
|
|
expect(names).toEqual(sorted);
|
|
});
|
|
|
|
it("loaded and failed sets are disjoint", () => {
|
|
const status = getDynamicLanguageStatus();
|
|
const failedNames = new Set(status.failed.map((f) => f.name));
|
|
for (const loaded of status.loaded) {
|
|
expect(failedNames.has(loaded)).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("at least one expected dynamic grammar registers in this environment", () => {
|
|
// On every supported dev environment for this repo (macOS, common
|
|
// Linux distros), the pre-validation step should let at least a few
|
|
// of these load successfully. If this fails, either the environment
|
|
// is unusual (and we want to know) or there is a real regression in
|
|
// the loader.
|
|
const status = getDynamicLanguageStatus();
|
|
const loaded = new Set(status.loaded);
|
|
const expected = ["python", "go", "java", "php", "ruby"];
|
|
const found = expected.filter((n) => loaded.has(n));
|
|
expect(found.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
});
|