mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-10 22:43:40 +00:00
fix(zig): type the optional-grammar gate by grammar key, not language
The gitnexus-check finding on parser-loader-abi.test.ts:155 is right, and none of the gates caught it: `tsconfig.json` includes only `src/**/*`, so neither `tsc --noEmit` nor CI typechecks the test tree, and vitest strips types without checking them. Confirmed with a scoped tsc run over the file: `error TS2345: Argument of type 'string' is not assignable to parameter of type 'SupportedLanguages'`. Not fixed with the proposed `key as SupportedLanguages` cast, which would assert something false: `listGrammarSources()` yields one row per SOURCES entry, including variants like `typescript:tsx` that are not enum members. `isOptionalGrammarRequired` now takes the grammar KEY it is really given, and the registry keeps a `satisfies Partial<Record<SupportedLanguages, string>>` so every key we write is still pinned to a real language. Two new cases cover the failure mode the type error was pointing at — a registry key that can never match what the ABI smoke passes, leaving the gate configured-looking and permanently inert: every OPTIONAL_GRAMMAR_ENV key must be a key `listGrammarSources()` yields and must be marked optional there, and an unregistered variant (`typescript:tsx`) must not be required even with the variable set. Same blind spot, two more latent errors in files this PR adds, both fixed: `Parser.Language` is not an exported member (use the `setLanguage` parameter type, as parser-loader-abi.test.ts already does), and the `ParsedImport` filter did not narrow the union, so `localName` was read through a `!` on an arm that has no such property — now a type predicate. The one remaining error under the same probe, in `resolvers/callable-value-flow.test.ts:319`, predates this branch (authored 2026-07-17, on main) and is left alone. structural-pair-coverage's optional-grammar case switches from `it.concurrent.each` to `it.concurrent.for`: only `for` passes the test context as a second argument (`each`'s callback is `(...args: T[])`), and that context carries the dynamic `skip()` the per-language gate calls. Behaviour is unchanged — grammar present: 10 passed; grammar forced away: 9 passed, 1 skipped. Whole test tree typechecking is a separate, much larger job: the same probe over `test/**` minus fixtures reports 734 pre-existing errors across the repo. Out of scope here.
This commit is contained in:
parent
d51fbdbf9a
commit
ef391cc672
4 changed files with 44 additions and 9 deletions
|
|
@ -19,15 +19,24 @@ import { isLanguageAvailable } from '../../src/core/tree-sitter/parser-loader.js
|
|||
* platform its grammar publishes a prebuild for; otherwise the gate would fail
|
||||
* a job it cannot satisfy.
|
||||
*/
|
||||
export const OPTIONAL_GRAMMAR_ENV: Partial<Record<SupportedLanguages, string>> = {
|
||||
export const OPTIONAL_GRAMMAR_ENV: Readonly<Partial<Record<string, string>>> = {
|
||||
// @tree-sitter-grammars/tree-sitter-zig@1.1.2 publishes prebuilds for
|
||||
// {darwin,linux,win32}-{x64,arm64} — every OS in the CI matrix.
|
||||
[SupportedLanguages.Zig]: 'GITNEXUS_REQUIRE_ZIG',
|
||||
};
|
||||
} satisfies Partial<Record<SupportedLanguages, string>>;
|
||||
|
||||
/** True when CI declared `language`'s optional grammar mandatory on this runner. */
|
||||
export const isOptionalGrammarRequired = (language: SupportedLanguages): boolean => {
|
||||
const envVar = OPTIONAL_GRAMMAR_ENV[language];
|
||||
/**
|
||||
* True when CI declared this grammar mandatory on the current runner.
|
||||
*
|
||||
* Keyed by GRAMMAR key, not by `SupportedLanguages`: the registry the ABI
|
||||
* load-smoke walks (`listGrammarSources()`) yields one row per `SOURCES` entry,
|
||||
* which includes variants such as `typescript:tsx` that are not enum members.
|
||||
* Widening the parameter is what keeps that call honest — narrowing the key
|
||||
* with a cast would claim every grammar row is a language, which is false.
|
||||
* `satisfies` above still pins every key WE write to a real language.
|
||||
*/
|
||||
export const isOptionalGrammarRequired = (grammarKey: string): boolean => {
|
||||
const envVar = OPTIONAL_GRAMMAR_ENV[grammarKey];
|
||||
return envVar !== undefined && process.env[envVar] === '1';
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -248,7 +248,10 @@ describeIfWorkerBuilt('RELATION_SCHEMA covers the non-bridge emitters', () => {
|
|||
},
|
||||
);
|
||||
|
||||
it.concurrent.each(OPTIONAL_GRAMMAR_CORPUS)(
|
||||
// `.for`, not `.each`: only `for` passes the test context as a second
|
||||
// argument (`each`'s callback is `(...args: T[])`), and the context is what
|
||||
// carries the dynamic `skip()` this per-language gate needs.
|
||||
it.concurrent.for(OPTIONAL_GRAMMAR_CORPUS)(
|
||||
'$fixture emits only declared FROM/TO pairs, and still reaches $emitter',
|
||||
async ({ fixture, language, sentinels }, ctx) => {
|
||||
if (!isLanguageAvailable(language)) ctx.skip();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { listGrammarSources } from '../../src/core/tree-sitter/parser-loader.js';
|
||||
import { OPTIONAL_GRAMMAR_ENV, isOptionalGrammarRequired } from '../helpers/optional-grammar.js';
|
||||
|
||||
/**
|
||||
|
|
@ -45,6 +46,25 @@ describe('optional grammar required-gate', () => {
|
|||
expect(isOptionalGrammarRequired(SupportedLanguages.Zig)).toBe(false);
|
||||
});
|
||||
|
||||
// The ABI load-smoke passes a GRAMMAR key, not a SupportedLanguages value:
|
||||
// listGrammarSources() has one row per SOURCES entry, including variants like
|
||||
// `typescript:tsx`. A registry key no row ever yields would leave the gate
|
||||
// permanently inert — it would look configured and require nothing.
|
||||
it('registers only keys the grammar registry actually yields', () => {
|
||||
const sources = listGrammarSources();
|
||||
for (const key of Object.keys(OPTIONAL_GRAMMAR_ENV)) {
|
||||
const source = sources.find((s) => s.key === key);
|
||||
expect(source, `${key} is not a grammar key listGrammarSources() yields`).toBeDefined();
|
||||
// Requiring a grammar that is already mandatory would be a no-op gate.
|
||||
expect(source?.optional, `${key} is not an optional grammar`).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('is inert for a grammar-key variant nobody registered', () => {
|
||||
process.env[envVar] = '1';
|
||||
expect(isOptionalGrammarRequired('typescript:tsx')).toBe(false);
|
||||
});
|
||||
|
||||
// Languages with no entry must never be gated — an unregistered language
|
||||
// reading a stray env var would fail jobs on platforms with no prebuild.
|
||||
it('never requires a language that is not registered', () => {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ describe.skipIf(!isOptionalGrammarRequired(SupportedLanguages.Zig))(
|
|||
|
||||
const parser = new Parser();
|
||||
const parse = (code: string) => {
|
||||
parser.setLanguage(Zig as Parser.Language);
|
||||
parser.setLanguage(Zig as Parameters<Parser['setLanguage']>[0]);
|
||||
return parser.parse(code);
|
||||
};
|
||||
|
||||
|
|
@ -1178,8 +1178,11 @@ fn f() void {
|
|||
const imports = emitZigScopeCaptures(src, 'lp.zig')
|
||||
.filter((m) => m['@import.source'] !== undefined)
|
||||
.map((m) => interpretZigImport(m))
|
||||
.filter((i) => i !== null && (i.kind === 'named' || i.kind === 'alias'))
|
||||
.map((i) => [i!.localName, (i as { reexportsName?: boolean }).reexportsName === true]);
|
||||
.filter(
|
||||
(i): i is Extract<NonNullable<typeof i>, { kind: 'named' | 'alias' }> =>
|
||||
i !== null && (i.kind === 'named' || i.kind === 'alias'),
|
||||
)
|
||||
.map((i) => [i.localName, (i as { reexportsName?: boolean }).reexportsName === true]);
|
||||
expect(imports).toEqual([
|
||||
['Arena', true], // the file-struct TYPE twin of a pub namespace import
|
||||
['Foo', true],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue