From 0000f6722b91c0c8c3cfd580222b8594f14a75f2 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Sun, 17 May 2026 22:40:02 -0600 Subject: [PATCH 1/2] =?UTF-8?q?test(parsers):=20gate=20LANGUAGE=5FREGISTRY?= =?UTF-8?q?=20=E2=86=94=20NATIVE=5FSUPPORTED=5FEXTENSIONS=20parity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1121. The drift guard between NATIVE_SUPPORTED_EXTENSIONS and parser_registry.rs covered link 2 ↔ 3, but link 1 ↔ 2 (LANGUAGE_REGISTRY ↔ NATIVE_SUPPORTED_EXTENSIONS) had no test. A WASM-only language added to the registry would silently degrade the native engine without flagging. Adds three tests with an explicit WASM_ONLY_ALLOWLIST (currently empty) so the allowlist itself can't rot — entries must reference a real registry extension and must not duplicate a language that's already been ported. --- .../native-drop-classification.test.ts | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/parsers/native-drop-classification.test.ts b/tests/parsers/native-drop-classification.test.ts index ec50b0f7..99d61e23 100644 --- a/tests/parsers/native-drop-classification.test.ts +++ b/tests/parsers/native-drop-classification.test.ts @@ -5,6 +5,7 @@ import { describe, expect, it } from 'vitest'; import { classifyNativeDrops, formatDropExtensionSummary, + LANGUAGE_REGISTRY, NATIVE_SUPPORTED_EXTENSIONS, } from '../../src/domain/parser.js'; @@ -203,3 +204,80 @@ describe('NATIVE_SUPPORTED_EXTENSIONS drift guard', () => { ).toEqual([]); }); }); + +/** + * Parity gate for `LANGUAGE_REGISTRY` ↔ `NATIVE_SUPPORTED_EXTENSIONS`. + * + * Acceptance criterion from #1071 (tracked in #1121): a CI gate prevents + * future drift between the JS `LANGUAGE_REGISTRY` and the Rust extractor + * coverage. The existing drift guard above covers + * `NATIVE_SUPPORTED_EXTENSIONS ↔ parser_registry.rs`, but the link from + * `LANGUAGE_REGISTRY` (the source of truth for languages we support at all) + * to `NATIVE_SUPPORTED_EXTENSIONS` (the hand-maintained mirror of the Rust + * enum) had no test — silently adding a WASM-only language would degrade the + * native engine without flagging the regression. + * + * This test closes that gap. Every extension declared in `LANGUAGE_REGISTRY` + * must either: + * 1. Be present in `NATIVE_SUPPORTED_EXTENSIONS` (i.e. a Rust extractor + * exists), or + * 2. Appear in `WASM_ONLY_ALLOWLIST` below, with a comment explaining why + * the language is intentionally WASM-only. + * + * Adding an extension to the allowlist is a deliberate choice: prefer porting + * the extractor to Rust. The allowlist exists so a contributor can land a + * WASM-only grammar (e.g. while a Rust port is in flight) without bypassing + * the gate entirely, but every entry should have a tracking issue. + */ +describe('LANGUAGE_REGISTRY ↔ NATIVE_SUPPORTED_EXTENSIONS parity', () => { + // Extensions intentionally left WASM-only. Currently empty: every language + // in `LANGUAGE_REGISTRY` has a corresponding Rust extractor. If you must + // add an entry, include a comment with the language id and the issue + // tracking the Rust port. + const WASM_ONLY_ALLOWLIST: ReadonlySet = new Set(); + + it('every LANGUAGE_REGISTRY extension has a Rust extractor or is on the allowlist', () => { + const registryExts = new Set(); + for (const entry of LANGUAGE_REGISTRY) { + for (const ext of entry.extensions) { + registryExts.add(ext.toLowerCase()); + } + } + const missingFromNative = [...registryExts] + .filter((ext) => !NATIVE_SUPPORTED_EXTENSIONS.has(ext)) + .filter((ext) => !WASM_ONLY_ALLOWLIST.has(ext)) + .sort(); + expect( + missingFromNative, + `LANGUAGE_REGISTRY extensions without a Rust extractor (and not on WASM_ONLY_ALLOWLIST): ${missingFromNative.join( + ', ', + )}. Either port the extractor to Rust and add the extension to NATIVE_SUPPORTED_EXTENSIONS, or add it to WASM_ONLY_ALLOWLIST with a justification.`, + ).toEqual([]); + }); + + it('WASM_ONLY_ALLOWLIST does not list extensions that already have a Rust extractor', () => { + // Catches stale allowlist entries: once a language is ported to Rust the + // allowlist line should be deleted, not left behind as dead config. + const stale = [...WASM_ONLY_ALLOWLIST].filter((ext) => NATIVE_SUPPORTED_EXTENSIONS.has(ext)); + expect( + stale, + `WASM_ONLY_ALLOWLIST entries that already have a Rust extractor — remove them: ${stale.join(', ')}`, + ).toEqual([]); + }); + + it('WASM_ONLY_ALLOWLIST only references extensions that LANGUAGE_REGISTRY declares', () => { + // Catches typos and dead entries: an allowlist line for an extension no + // longer in the registry is silently useless. + const registryExts = new Set(); + for (const entry of LANGUAGE_REGISTRY) { + for (const ext of entry.extensions) { + registryExts.add(ext.toLowerCase()); + } + } + const orphans = [...WASM_ONLY_ALLOWLIST].filter((ext) => !registryExts.has(ext)); + expect( + orphans, + `WASM_ONLY_ALLOWLIST entries not declared in LANGUAGE_REGISTRY — likely a typo: ${orphans.join(', ')}`, + ).toEqual([]); + }); +}); From 57eb7693711bb86853f6e586a465874c266c3b6b Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Mon, 18 May 2026 00:29:46 -0600 Subject: [PATCH 2/2] fix(tests): normalize WASM_ONLY_ALLOWLIST case in parity checks (#1154) Lowercase allowlist entries before comparing against `registryExts` and `NATIVE_SUPPORTED_EXTENSIONS` so a mixed-case future entry can't slip past the staleness and orphan checks. Both reference sets are already lowercased, so without this normalization a capitalized allowlist entry would be silently skipped by test 2 and falsely flagged by test 3. --- tests/parsers/native-drop-classification.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/parsers/native-drop-classification.test.ts b/tests/parsers/native-drop-classification.test.ts index 99d61e23..9c380870 100644 --- a/tests/parsers/native-drop-classification.test.ts +++ b/tests/parsers/native-drop-classification.test.ts @@ -258,7 +258,9 @@ describe('LANGUAGE_REGISTRY ↔ NATIVE_SUPPORTED_EXTENSIONS parity', () => { it('WASM_ONLY_ALLOWLIST does not list extensions that already have a Rust extractor', () => { // Catches stale allowlist entries: once a language is ported to Rust the // allowlist line should be deleted, not left behind as dead config. - const stale = [...WASM_ONLY_ALLOWLIST].filter((ext) => NATIVE_SUPPORTED_EXTENSIONS.has(ext)); + const stale = [...WASM_ONLY_ALLOWLIST] + .map((ext) => ext.toLowerCase()) + .filter((ext) => NATIVE_SUPPORTED_EXTENSIONS.has(ext)); expect( stale, `WASM_ONLY_ALLOWLIST entries that already have a Rust extractor — remove them: ${stale.join(', ')}`, @@ -274,7 +276,9 @@ describe('LANGUAGE_REGISTRY ↔ NATIVE_SUPPORTED_EXTENSIONS parity', () => { registryExts.add(ext.toLowerCase()); } } - const orphans = [...WASM_ONLY_ALLOWLIST].filter((ext) => !registryExts.has(ext)); + const orphans = [...WASM_ONLY_ALLOWLIST] + .map((ext) => ext.toLowerCase()) + .filter((ext) => !registryExts.has(ext)); expect( orphans, `WASM_ONLY_ALLOWLIST entries not declared in LANGUAGE_REGISTRY — likely a typo: ${orphans.join(', ')}`,