From 1b1e9a7861b9f6dbddf81676e606f243209147d1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 05:56:50 +0000 Subject: [PATCH] feat: implement oneof parsing in simplified proto parser - Implemented `extractBalancedBody` helper to robustly handle nested braces in proto files, replacing fragile regex-based parsing. - Updated `parseMessages` to use balanced brace extraction, allowing correct parsing of messages containing nested blocks like `oneof`. - Implemented `parseOneofs` to extract and parse `oneof` definitions within messages. - Verified changes by adding a test case with `oneof` fields in `packages/test-client/src/service.proto` and running `packages/test-client/test-parser.js`. --- packages/test-client/proto-parser.js | 85 ++++++++++++++++++++++---- packages/test-client/src/service.proto | 8 +++ packages/test-client/test-parser.js | 19 ++++++ 3 files changed, 99 insertions(+), 13 deletions(-) diff --git a/packages/test-client/proto-parser.js b/packages/test-client/proto-parser.js index 45c478a..8431723 100644 --- a/packages/test-client/proto-parser.js +++ b/packages/test-client/proto-parser.js @@ -110,23 +110,28 @@ function parseMethods(serviceBody) { */ function parseMessages(content) { const messages = []; - const messageRegex = /message\s+(\w+)\s*\{([^}]+)\}/g; + const messageRegex = /message\s+(\w+)\s*\{/g; let messageMatch; while ((messageMatch = messageRegex.exec(content)) !== null) { const messageName = messageMatch[1]; - const messageBody = messageMatch[2]; - - const message = { - name: messageName, - fields: parseFields(messageBody), - nestedMessages: [], // TODO: Handle nested messages - nestedEnums: [], // TODO: Handle nested enums - oneofs: [], // TODO: Handle oneofs - options: {} - }; - - messages.push(message); + const openBraceIndex = messageMatch.index + messageMatch[0].length - 1; + + const [messageBody, endIndex] = extractBalancedBody(content, openBraceIndex); + + if (messageBody !== null) { + const message = { + name: messageName, + fields: parseFields(messageBody), + nestedMessages: [], // TODO: Handle nested messages + nestedEnums: [], // TODO: Handle nested enums + oneofs: parseOneofs(messageBody), + options: {} + }; + + messages.push(message); + messageRegex.lastIndex = endIndex; + } } return messages; @@ -213,3 +218,57 @@ function parseEnumValues(enumBody) { module.exports = { parseProtoFile }; + +/** + * Parse oneof definitions from message body + */ +function parseOneofs(messageBody) { + const oneofs = []; + const oneofRegex = /oneof\s+(\w+)\s*\{/g; + let oneofMatch; + + while ((oneofMatch = oneofRegex.exec(messageBody)) !== null) { + const oneofName = oneofMatch[1]; + const openBraceIndex = oneofMatch.index + oneofMatch[0].length - 1; + + const [oneofBody, endIndex] = extractBalancedBody(messageBody, openBraceIndex); + + if (oneofBody !== null) { + const oneof = { + name: oneofName, + fields: parseFields(oneofBody) + }; + + oneofs.push(oneof); + oneofRegex.lastIndex = endIndex; + } + } + + return oneofs; +} + +/** + * Helper to extract content inside balanced braces + * @param {string} content - Full content + * @param {number} startIndex - Index of the opening brace + * @returns {[string|null, number]} - [extracted content, end index] + */ +function extractBalancedBody(content, startIndex) { + let depth = 1; + let i = startIndex + 1; + + while (i < content.length && depth > 0) { + if (content[i] === '{') { + depth++; + } else if (content[i] === '}') { + depth--; + } + i++; + } + + if (depth === 0) { + return [content.substring(startIndex + 1, i - 1), i]; + } + + return [null, i]; +} diff --git a/packages/test-client/src/service.proto b/packages/test-client/src/service.proto index 4f25f56..bb4b7d2 100644 --- a/packages/test-client/src/service.proto +++ b/packages/test-client/src/service.proto @@ -47,3 +47,11 @@ service UserService { // Bidirectional streaming RPC rpc Chat(stream StreamMessage) returns (stream StreamMessage); } + +message OneofMessage { + string id = 1; + oneof test_oneof { + string name = 2; + int32 age = 3; + } +} diff --git a/packages/test-client/test-parser.js b/packages/test-client/test-parser.js index 01ce259..2b83923 100644 --- a/packages/test-client/test-parser.js +++ b/packages/test-client/test-parser.js @@ -46,6 +46,13 @@ protoFile.messages.forEach(message => { const typeStr = field.repeated ? `repeated ${field.type}` : field.type; console.log(` • ${field.name}: ${typeStr} = ${field.number}`); }); + console.log(` - oneofs: ${message.oneofs.length}`); + message.oneofs.forEach(oneof => { + console.log(` • Oneof: ${oneof.name}`); + oneof.fields.forEach(field => { + console.log(` - ${field.name}: ${field.type} = ${field.number}`); + }); + }); }); console.log(); @@ -112,6 +119,18 @@ if (protoFile.messages.length > 0) { console.log(` ❌ Field missing fields: ${missingFieldFields.join(', ')}`); } } + + // Check oneof structure + if (message.oneofs.length > 0) { + const oneof = message.oneofs[0]; + const oneofFields = ['name', 'fields']; + const missingOneofFields = oneofFields.filter(f => !(f in oneof)); + if (missingOneofFields.length === 0) { + console.log(' ✅ Oneof structure valid'); + } else { + console.log(` ❌ Oneof missing fields: ${missingOneofFields.join(', ')}`); + } + } } console.log();