From 46378768245bca4f62937b074c755d657fa82aec Mon Sep 17 00:00:00 2001 From: Dario Gabriel Lipicar Date: Wed, 3 Jun 2026 17:54:56 -0300 Subject: [PATCH] preserve line breaks in method descriptions Join doc-comment lines with newlines instead of spaces (markers stripped, leading/trailing blank lines dropped, interior blanks kept), and escape \n when emitting the description into the generated getMethods(). Both codegen paths updated; docs corrected. Co-Authored-By: Claude Opus 4.8 (1M context) --- cpp-generator/docs/spec.md | 10 ++++++---- .../experimental/impl_header_parser.cpp | 16 ++++++++++++---- .../experimental/lidl_gen_provider.cpp | 1 + cpp-generator/legacy/generator_lib.cpp | 18 ++++++++++++++---- cpp-generator/legacy/main.cpp | 1 + 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/cpp-generator/docs/spec.md b/cpp-generator/docs/spec.md index 2484f83..af823ef 100644 --- a/cpp-generator/docs/spec.md +++ b/cpp-generator/docs/spec.md @@ -153,9 +153,11 @@ returns, the description flows — with no extra call — to `lm methods`, Only **doc comments** are captured: `///` line comments and `/** … */` / `/*! … */` block comments. Plain `//` and `/* … */` comments are ignored, so -section separators and incidental notes don't leak into the API. Multi-line doc -comments are joined into a single-line description, and only comments -*immediately adjacent* to the declaration (no blank line in between) attach. +section separators and incidental notes don't leak into the API. A multi-line +doc comment is preserved with its line breaks (markers stripped, lines joined +with `\n`; leading/trailing blank lines dropped, interior blank lines kept), and +only comments *immediately adjacent* to the declaration (no blank line in +between) attach. ```cpp class WalletModuleImpl : public LogosModuleContext { @@ -167,7 +169,7 @@ public: ``` → the `transfer` entry in `getMethods()` gains -`"description": "Transfers `amount` from the active account to `toAddress`. Returns the resulting transaction hash."` +`"description": "Transfers `amount` from the active account to `toAddress`.\nReturns the resulting transaction hash."` (the two lines preserved, joined with `\n`) The same applies to the legacy `--provider-header` mode (`LOGOS_METHOD`-marked declarations): a doc comment above the declaration becomes the method's diff --git a/cpp-generator/experimental/impl_header_parser.cpp b/cpp-generator/experimental/impl_header_parser.cpp index df43d15..00bd4f1 100644 --- a/cpp-generator/experimental/impl_header_parser.cpp +++ b/cpp-generator/experimental/impl_header_parser.cpp @@ -190,6 +190,14 @@ static bool parseMethodLine(const QString& line, MethodDecl& out) // Main entry point // --------------------------------------------------------------------------- +// Join doc-comment lines preserving line breaks (drop leading/trailing blanks). +static QString joinDocLines(QStringList lines) +{ + while (!lines.isEmpty() && lines.first().trimmed().isEmpty()) lines.removeFirst(); + while (!lines.isEmpty() && lines.last().trimmed().isEmpty()) lines.removeLast(); + return lines.join('\n'); +} + ImplParseResult parseImplHeader(const QString& headerPath, const QString& className, const QString& metadataPath, @@ -293,7 +301,7 @@ ImplParseResult parseImplHeader(const QString& headerPath, if (end >= 0) { t = t.left(end); inBlockComment = false; } t.remove(QRegularExpression(R"(^\*+\s?)")); t = t.trimmed(); - if (!t.isEmpty()) pendingDoc.append(t); + pendingDoc.append(t); break; } @@ -337,7 +345,7 @@ ImplParseResult parseImplHeader(const QString& headerPath, QString text = line.mid(3); if (text.startsWith('<')) text = text.mid(1); // ///< trailing form text = text.trimmed(); - if (!text.isEmpty()) pendingDoc.append(text); + pendingDoc.append(text); break; } if (line.startsWith("/**") || line.startsWith("/*!")) { @@ -347,7 +355,7 @@ ImplParseResult parseImplHeader(const QString& headerPath, else inBlockComment = true; text.remove(QRegularExpression(R"(^\*+\s?)")); text = text.trimmed(); - if (!text.isEmpty()) pendingDoc.append(text); + pendingDoc.append(text); break; } if (line.startsWith("//") || line.startsWith("/*") || line.startsWith("*")) { @@ -405,7 +413,7 @@ ImplParseResult parseImplHeader(const QString& headerPath, QString decl = line.left(line.size() - 1).trimmed(); MethodDecl md; if (parseMethodLine(decl, md)) { - md.description = pendingDoc.join(' ').trimmed(); + md.description = joinDocLines(pendingDoc); result.module.methods.append(md); } } diff --git a/cpp-generator/experimental/lidl_gen_provider.cpp b/cpp-generator/experimental/lidl_gen_provider.cpp index ea80062..8bc8608 100644 --- a/cpp-generator/experimental/lidl_gen_provider.cpp +++ b/cpp-generator/experimental/lidl_gen_provider.cpp @@ -493,6 +493,7 @@ QString lidlMakeProviderDispatch(const ModuleDecl& module) QString escDesc = md.description; escDesc.replace('\\', "\\\\"); escDesc.replace('"', "\\\""); + escDesc.replace('\n', "\\n"); s << " obj[\"description\"] = QStringLiteral(\"" << escDesc << "\");\n"; } diff --git a/cpp-generator/legacy/generator_lib.cpp b/cpp-generator/legacy/generator_lib.cpp index cd9483b..2e6c251 100644 --- a/cpp-generator/legacy/generator_lib.cpp +++ b/cpp-generator/legacy/generator_lib.cpp @@ -680,6 +680,16 @@ QString makeSource(const QString& moduleName, const QString& className, const QS return c; } +// Join accumulated doc-comment lines into a description, preserving the +// original line breaks. Leading/trailing blank lines are dropped; interior +// blank lines (paragraph breaks) are kept. +static QString joinDocLines(QStringList lines) +{ + while (!lines.isEmpty() && lines.first().trimmed().isEmpty()) lines.removeFirst(); + while (!lines.isEmpty() && lines.last().trimmed().isEmpty()) lines.removeLast(); + return lines.join('\n'); +} + QVector parseProviderHeader(const QString& headerPath, QTextStream& err) { QVector methods; @@ -715,7 +725,7 @@ QVector parseProviderHeader(const QString& headerPath, QTextStream } text.remove(QRegularExpression(R"(^\*+\s?)")); // strip leading '*' text = text.trimmed(); - if (!text.isEmpty()) pendingDoc.append(text); + pendingDoc.append(text); continue; } @@ -729,7 +739,7 @@ QVector parseProviderHeader(const QString& headerPath, QTextStream QString text = line.mid(3); if (text.startsWith('<')) text = text.mid(1); // ///< trailing form text = text.trimmed(); - if (!text.isEmpty()) pendingDoc.append(text); + pendingDoc.append(text); } else if (line.startsWith("/**") || line.startsWith("/*!")) { QString text = line.mid(3); int end = text.indexOf("*/"); @@ -737,7 +747,7 @@ QVector parseProviderHeader(const QString& headerPath, QTextStream else inBlockComment = true; text.remove(QRegularExpression(R"(^\*+\s?)")); text = text.trimmed(); - if (!text.isEmpty()) pendingDoc.append(text); + pendingDoc.append(text); } else if (line.startsWith("//") || line.startsWith("/*") || line.startsWith("*")) { // Non-doc comment: ignore, keep any pending doc comment. } else { @@ -749,7 +759,7 @@ QVector parseProviderHeader(const QString& headerPath, QTextStream ParsedMethod m; m.returnType = normalizeType(match.captured(1)); m.name = match.captured(2); - m.description = pendingDoc.join(' ').trimmed(); + m.description = joinDocLines(pendingDoc); pendingDoc.clear(); QString paramStr = match.captured(3).trimmed(); diff --git a/cpp-generator/legacy/main.cpp b/cpp-generator/legacy/main.cpp index b1b00c3..98c715a 100644 --- a/cpp-generator/legacy/main.cpp +++ b/cpp-generator/legacy/main.cpp @@ -24,6 +24,7 @@ static QString cppStringEscape(const QString& s) QString out = s; out.replace('\\', "\\\\"); out.replace('"', "\\\""); + out.replace('\n', "\\n"); return out; }