fix(speckl): strip inline // comments from event/interface field types (cycle 62)
End-to-end compile of FederatedMeetup.speckdl surfaced a real generator input bug: inline line comments on field declarations were bleeding into the parsed type expression. A field like mesh_ip: Bytes // 4 bytes (IPv4 overlay) produced a TypeExpr whose name was literally 'Bytes // 4 bytes (IPv4 overlay)' which the .proto generator emitted verbatim: bytes // 4 bytes (IPv4 overlay) mesh_ip = 2; — broken syntax (comment in the type position). Fix: both parseEventBlockMultiline and parseInterfaceBlockMultiline now strip '// ...' suffixes from each inner line BEFORE matching the 'name: type' regex or splitting on commas. The state-block parser already had this; cycle 62 brings event and interface to parity. Tests: 2 new parser tests covering event and interface blocks, both with the FederatedMeetup-style inline comment. All 54 tests pass (52 baseline + 2 new). tsc build clean. End-to-end verified: running generateProtobuf against the real FederatedMeetup.speckdl now emits clean 'bytes mesh_ip = 2;' for the InitialMeshPeer interface, where cycle 62's pre-fix output was the broken 'Bytes // 4 bytes (IPv4 overlay) mesh_ip = 2;'. This unblocks the next phase of the federated-meetup Speckl migration: the consumer Go code can now bind to the generated proto without a half-broken field in the way.
This commit is contained in:
parent
a6bdbce915
commit
0e03aa4e04
2 changed files with 56 additions and 2 deletions
|
|
@ -794,4 +794,43 @@ speck Second {
|
|||
expect(b.specks[0].eventSuffix).toBe('Payload');
|
||||
});
|
||||
});
|
||||
|
||||
// Cycle 62: inline `//` comments on field declarations were bleeding
|
||||
// into the type expression. Real federated-meetup had:
|
||||
// mesh_ip: Bytes // 4 bytes (IPv4 overlay)
|
||||
// emitted as `Bytes // 4 bytes (IPv4 overlay) mesh_ip = 2;` —
|
||||
// broken syntax. The fix strips the comment before parsing the type.
|
||||
describe('inline comment stripping (cycle 62)', () => {
|
||||
it('should strip inline // comments from event field types', () => {
|
||||
const content = `speck One {
|
||||
event InitialMeshPeer {
|
||||
host_wg_key: Bytes, // 32 bytes
|
||||
mesh_ip: Bytes // 4 bytes (IPv4 overlay)
|
||||
}
|
||||
}`;
|
||||
const ast = parseSpeckContent(content);
|
||||
const ev = ast.specks[0].members.find(m => m.name === 'InitialMeshPeer');
|
||||
if (ev?.type === 'event') {
|
||||
const meshIp = ev.fields.find(f => f.name === 'mesh_ip');
|
||||
expect(meshIp?.type.name).toBe('Bytes');
|
||||
expect(meshIp?.type.name).not.toContain('IPv4');
|
||||
}
|
||||
});
|
||||
|
||||
it('should strip inline // comments from interface field types', () => {
|
||||
const content = `speck One {
|
||||
interface InitialMeshPeer {
|
||||
host_wg_key: Bytes, // X25519
|
||||
mesh_ip: Bytes // 4 bytes (IPv4 overlay)
|
||||
}
|
||||
}`;
|
||||
const ast = parseSpeckContent(content);
|
||||
const iface = ast.specks[0].members.find(m => m.name === 'InitialMeshPeer');
|
||||
if (iface?.type === 'interface') {
|
||||
const meshIp = iface.fields.find(f => f.name === 'mesh_ip');
|
||||
expect(meshIp?.type.name).toBe('Bytes');
|
||||
expect(meshIp?.type.name).not.toContain('IPv4');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1294,7 +1294,15 @@ function parseEventBlockMultiline(lines: string[], startIndex: number, startBrac
|
|||
|
||||
const fields: { name: string; type: TypeExpr }[] = [];
|
||||
for (const line of innerLines) {
|
||||
const match = line.match(/^(\w+)\s*:\s*(.+?),?$/);
|
||||
// Cycle 62: strip inline `//` line-comments from the type expression
|
||||
// before parsing. Without this, an event field like
|
||||
// mesh_ip: Bytes // 4 bytes (IPv4 overlay)
|
||||
// produces a TypeExpr whose name includes the comment text, which
|
||||
// the .proto generator emits verbatim:
|
||||
// bytes // 4 bytes (IPv4 overlay) mesh_ip = 2;
|
||||
// (broken syntax — comment is in the type position).
|
||||
const noComment = line.replace(/\/\/.*$/, '').trim();
|
||||
const match = noComment.match(/^(\w+)\s*:\s*(.+?),?$/);
|
||||
if (match) {
|
||||
fields.push({ name: match[1], type: parseTypeExpr(match[2].replace(/,$/, '').trim()) });
|
||||
}
|
||||
|
|
@ -1313,8 +1321,15 @@ function parseInterfaceBlockMultiline(lines: string[], startIndex: number, start
|
|||
const fields: InterfaceField[] = [];
|
||||
|
||||
for (const line of innerLines) {
|
||||
// Cycle 62: strip inline `//` line-comments from each line before
|
||||
// token-splitting. Without this, a field like
|
||||
// mesh_ip: Bytes // 4 bytes (IPv4 overlay)
|
||||
// has its comment text bleed into the parsed type expression,
|
||||
// which the .proto generator emits verbatim — broken syntax.
|
||||
const noComment = line.replace(/\/\/.*$/, '').trim();
|
||||
if (!noComment) continue;
|
||||
// Split on comma so multi-variant lines like "Idle, Parsing, Done" all parse
|
||||
const tokens = line.split(',').map(s => s.trim()).filter(Boolean);
|
||||
const tokens = noComment.split(',').map(s => s.trim()).filter(Boolean);
|
||||
for (const token of tokens) {
|
||||
// Check 1: method signature form: name(params) -> returnType
|
||||
const methodMatch = token.match(/^(\w+)\s*\(([^)]*)\)\s*->\s*(.+?),?$/);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue