Files
SnowLuma/packages/proton/test/__tests__/runtime-map-fallback.test.ts
DrSmoothl 194165f49d feat(proton): add @snowluma/proton — compile-time protobuf DSL
Vendors SnowLuma/protobuf-dsl into the monorepo as @snowluma/proton, with
edge-case fixes applied on top of the upstream source. Reference copy of
the unmodified upstream is kept under dev/protobuf-dsl/.

Edge cases fixed (each with a regression test):
  - Aliased pb / pb_repeated marker imports (`import { pb as P }`)
    — collector now resolves the marker name through the import resolver
  - Concrete interface with a generic-instantiated message field
    (`wrapped: pb<5, Wrapper<uint_32>>`) — field typeName now resolves
    to the mangled form, and the inner instantiation is queued for
    monomorphization
  - Generic template with a generic-instantiated field
    (`interface Outer<U> { x: pb<5, Wrapper<U>> }`) — the inner
    instantiation is now re-substituted and re-monomorphized at outer
    instantiation time
  - Wrapper-binding synthetic SourceFile was empty-text — getText()
    returned garbage, breaking mangled name computation downstream
  - Cross-file forwarded wrappers (chain → imported base) were not
    detected — parseFileForDefinitions now resolves imports recursively
    (value imports only, with cycle protection)
  - matchForwardedKnownWrapper did not propagate typePattern — chain
    treated <X> as the encoded type instead of Wrapper<X>
  - Synthetic-SF WeakMap was private to analyzer.ts — replacer could
    not see it. Moved to utils.ts so typeNodeToMangledName picks it up
    automatically.

Defensive guard: any field whose typeName resolves to neither a primitive
nor a known message now throws at analyze() time, instead of silently
leaking the WireType.Varint placeholder into codegen.

95/95 tests pass; tsc --noEmit clean.
2026-05-19 08:19:13 +08:00

99 lines
3.0 KiB
TypeScript

import { afterEach, describe, expect, it } from 'vitest';
import { analyzeSource } from '../../src/ast/analyzer';
import { createRuntimeMap } from '../../src/runtime-map';
import {
protobuf_decode,
protobuf_disableRuntimeMapFallback,
protobuf_enableRuntimeMapFallback,
protobuf_encode,
} from '../../src/runtime';
function captureFrameFromThrow(fn: () => unknown, sourceFile: string): { line: number; column: number } {
try {
fn();
} catch (err) {
const stack = err instanceof Error ? err.stack ?? '' : '';
for (const raw of stack.split('\n')) {
const line = raw.trim();
if (!line.includes(sourceFile)) continue;
const location = line.endsWith(')') && line.includes('(')
? line.slice(line.lastIndexOf('(') + 1, -1)
: line.replace(/^at\s+/, '');
const lastColon = location.lastIndexOf(':');
const secondLastColon = location.lastIndexOf(':', lastColon - 1);
if (lastColon < 0 || secondLastColon < 0) continue;
const parsedLine = Number(location.slice(secondLastColon + 1, lastColon));
const parsedColumn = Number(location.slice(lastColon + 1));
if (Number.isFinite(parsedLine) && Number.isFinite(parsedColumn)) {
return { line: parsedLine, column: parsedColumn };
}
}
}
throw new Error(`Unable to capture stack frame for ${sourceFile}`);
}
function invokeEncode(): Uint8Array {
return new Function(
'protobuf_encode',
`\n//# sourceURL=runtime-map-encode.js\nreturn protobuf_encode({ value: 7 });`,
)(protobuf_encode) as Uint8Array;
}
function invokeDecode(data: Uint8Array): any {
return new Function(
'protobuf_decode',
'data',
`\n//# sourceURL=runtime-map-decode.js\nreturn protobuf_decode(data);`,
)(protobuf_decode, data);
}
describe('runtime map fallback', () => {
afterEach(() => {
protobuf_disableRuntimeMapFallback();
});
it('dynamically generates encode/decode from runtime map when enabled', () => {
const schema = `
interface SimpleMsg {
value: pb<1, uint_32>;
}
`;
const registry = analyzeSource(schema, 'schema.ts');
const encodeFrame = captureFrameFromThrow(invokeEncode, 'runtime-map-encode.js');
const decodeFrame = captureFrameFromThrow(() => invokeDecode(new Uint8Array([0x08, 0x07])), 'runtime-map-decode.js');
const runtimeMap = createRuntimeMap({
messages: registry,
callSites: [
{
file: 'runtime-map-encode.js',
line: encodeFrame.line,
column: encodeFrame.column,
fnName: 'protobuf_encode',
typeName: 'SimpleMsg',
},
{
file: 'runtime-map-decode.js',
line: decodeFrame.line,
column: decodeFrame.column,
fnName: 'protobuf_decode',
typeName: 'SimpleMsg',
},
],
});
protobuf_enableRuntimeMapFallback(runtimeMap);
const encoded = invokeEncode();
expect([...encoded]).toEqual([0x08, 0x07]);
const decoded = invokeDecode(encoded);
expect(decoded.value).toBe(7);
});
});