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.
99 lines
3.0 KiB
TypeScript
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);
|
|
});
|
|
});
|