From 05dd37a30ab5b6ed78197aa6ef29d07ebafc95a2 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Wed, 15 Jul 2026 07:00:43 +0100 Subject: [PATCH] implements PLN-0095 --- discussion/index.ndjson | 2 +- ...-documentation-text-block-normalization.md | 2 +- .../PbsDocumentationTextBlockNormalizer.java | 72 +++++++++++++++++++ ...sDocumentationTextBlockNormalizerTest.java | 54 ++++++++++++++ 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsDocumentationTextBlockNormalizer.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDocumentationTextBlockNormalizerTest.java diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 84f3d191..92019ccb 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,7 +1,7 @@ {"type":"meta","next_id":{"DSC":39,"AGD":42,"DEC":40,"PLN":103,"LSN":55,"CLSN":1}} {"type":"discussion","id":"DSC-0038","status":"done","ticket":"studio-packer-rgba8888-asset-pipeline","title":"Studio and Packer RGBA8888 Asset Pipeline Alignment","created_at":"2026-05-23","updated_at":"2026-07-14","tags":["studio","packer","assets","glyph-bank","palette","rgba8888","runtime-alignment"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0053","file":"discussion/lessons/DSC-0038-studio-packer-rgba8888-asset-pipeline/LSN-0053-rgba8888-is-the-canonical-studio-packer-palette-contract.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14"}]} {"type":"discussion","id":"DSC-0037","status":"done","ticket":"pbs-autocomplete-parameter-names","title":"PBS autocomplete parameter names for stdlib and method calls","created_at":"2026-05-08","updated_at":"2026-05-14","tags":["compiler-pbs","studio","lsp","autocomplete","signature-help","stdlib"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0052","file":"discussion/lessons/DSC-0037-pbs-autocomplete-parameter-names/LSN-0052-canonical-callable-parameter-names-through-pbs-editor-assistance.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]} -{"type":"discussion","id":"DSC-0036","status":"in_progress","ticket":"pbs-symbol-documentation-and-hover-markdown","title":"Modelo de documentacao de simbolos em PBS e consumo markdown no hover","created_at":"2026-05-08","updated_at":"2026-07-15","tags":["compiler","compiler-pbs","studio","lsp","vscode","editor","hover","documentation","markdown"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-pbs-symbol-documentation-and-hover-markdown.md","status":"accepted","created_at":"2026-05-08","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0039","file":"DEC-0039-pbs-symbol-documentation-with-doc-markdown-text-blocks.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0039"}],"plans":[{"id":"PLN-0092","file":"PLN-0092-specify-pbs-doc-attribute-and-markdown-text-block-syntax.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0093","file":"PLN-0093-implement-pbs-lexer-support-for-documentation-text-blocks.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0094","file":"PLN-0094-extend-pbs-attribute-parser-and-ast-for-doc-markdown-payloads.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0095","file":"PLN-0095-implement-documentation-text-block-normalization.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0096","file":"PLN-0096-validate-doc-attribute-semantics-and-diagnostics.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0097","file":"PLN-0097-attach-doc-metadata-to-pbs-semantic-symbols.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0098","file":"PLN-0098-expose-doc-metadata-through-pbs-editorial-and-lsp-surfaces.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0099","file":"PLN-0099-render-doc-markdown-in-hover-composition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0100","file":"PLN-0100-author-doc-metadata-for-stdlib-sdk-and-interface-declarations.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0101","file":"PLN-0101-protect-runtime-artifacts-from-doc-metadata-lowering.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0102","file":"PLN-0102-add-end-to-end-doc-documentation-conformance-coverage.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0036","status":"in_progress","ticket":"pbs-symbol-documentation-and-hover-markdown","title":"Modelo de documentacao de simbolos em PBS e consumo markdown no hover","created_at":"2026-05-08","updated_at":"2026-07-15","tags":["compiler","compiler-pbs","studio","lsp","vscode","editor","hover","documentation","markdown"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-pbs-symbol-documentation-and-hover-markdown.md","status":"accepted","created_at":"2026-05-08","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0039","file":"DEC-0039-pbs-symbol-documentation-with-doc-markdown-text-blocks.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0039"}],"plans":[{"id":"PLN-0092","file":"PLN-0092-specify-pbs-doc-attribute-and-markdown-text-block-syntax.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0093","file":"PLN-0093-implement-pbs-lexer-support-for-documentation-text-blocks.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0094","file":"PLN-0094-extend-pbs-attribute-parser-and-ast-for-doc-markdown-payloads.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0095","file":"PLN-0095-implement-documentation-text-block-normalization.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0096","file":"PLN-0096-validate-doc-attribute-semantics-and-diagnostics.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0097","file":"PLN-0097-attach-doc-metadata-to-pbs-semantic-symbols.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0098","file":"PLN-0098-expose-doc-metadata-through-pbs-editorial-and-lsp-surfaces.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0099","file":"PLN-0099-render-doc-markdown-in-hover-composition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0100","file":"PLN-0100-author-doc-metadata-for-stdlib-sdk-and-interface-declarations.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0101","file":"PLN-0101-protect-runtime-artifacts-from-doc-metadata-lowering.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]},{"id":"PLN-0102","file":"PLN-0102-add-end-to-end-doc-documentation-conformance-coverage.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0039"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"pbs-lsp-editor-assistance-wave-1","title":"Wave 1 de assistencia editorial via LSP para PBS no VS Code","created_at":"2026-05-08","updated_at":"2026-05-08","tags":["studio","lsp","vscode","compiler","compiler-pbs","editor","completion","hover","signature-help"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0051","file":"discussion/lessons/DSC-0035-pbs-lsp-editor-assistance-wave-1/LSN-0051-compiler-backed-editor-assistance-for-pbs.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"frontend-semantic-host-projection-flexibility","title":"Frontend semantic vocabulary flexibility and declarative host projection","created_at":"2026-05-06","updated_at":"2026-05-07","tags":["compiler","compiler-general","frontend","semantics","vscode","host-projection","lsp"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0048","file":"discussion/lessons/DSC-0034-frontend-semantic-host-projection-flexibility/LSN-0048-frontend-owned-semantic-vocabularies-with-declarative-host-projection.md","status":"done","created_at":"2026-05-07","updated_at":"2026-05-07"}]} {"type":"discussion","id":"DSC-0033","status":"done","ticket":"frontend-visual-theme-spec-and-css-retirement","title":"Frontend visual theme spec and retirement of host-consumed semantic CSS","created_at":"2026-05-06","updated_at":"2026-05-08","tags":["compiler","compiler-general","frontend","presentation","theming","studio","vscode","lsp","pbs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0050","file":"discussion/lessons/DSC-0033-frontend-visual-theme-spec-and-css-retirement/LSN-0050-frontend-owned-visual-themes-with-structured-contract-and-host-adapters.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]} diff --git a/discussion/workflow/plans/PLN-0095-implement-documentation-text-block-normalization.md b/discussion/workflow/plans/PLN-0095-implement-documentation-text-block-normalization.md index fffbc28b..f34da5b6 100644 --- a/discussion/workflow/plans/PLN-0095-implement-documentation-text-block-normalization.md +++ b/discussion/workflow/plans/PLN-0095-implement-documentation-text-block-normalization.md @@ -2,7 +2,7 @@ id: PLN-0095 ticket: pbs-symbol-documentation-and-hover-markdown title: Implement documentation text block normalization -status: open +status: done created: 2026-07-15 ref_decisions: [DEC-0039] tags: [compiler, compiler-pbs, studio, lsp, vscode, editor, hover, documentation, markdown] diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsDocumentationTextBlockNormalizer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsDocumentationTextBlockNormalizer.java new file mode 100644 index 00000000..bcc31abf --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsDocumentationTextBlockNormalizer.java @@ -0,0 +1,72 @@ +package p.studio.compiler.pbs; + +import java.util.ArrayList; +import java.util.List; + +public final class PbsDocumentationTextBlockNormalizer { + private static final String DELIMITER = "\"\"\""; + + private PbsDocumentationTextBlockNormalizer() { + } + + public static String normalizeLexeme(final String lexeme) { + if (lexeme == null || !lexeme.startsWith(DELIMITER) || !lexeme.endsWith(DELIMITER) || lexeme.length() < 6) { + return ""; + } + return normalizeContent(lexeme.substring(DELIMITER.length(), lexeme.length() - DELIMITER.length())); + } + + public static String normalizeContent(final String rawContent) { + final var lines = splitLines(rawContent == null ? "" : rawContent.replace("\r\n", "\n").replace('\r', '\n')); + removeIncidentalBoundaryLines(lines); + final int commonIndent = commonIndent(lines); + if (commonIndent > 0) { + for (int i = 0; i < lines.size(); i++) { + final var line = lines.get(i); + if (line.isBlank()) { + lines.set(i, ""); + } else { + lines.set(i, line.substring(Math.min(commonIndent, leadingWhitespace(line)))); + } + } + } + return String.join("\n", lines); + } + + private static List splitLines(final String text) { + final var lines = new ArrayList(); + final var parts = text.split("\n", -1); + for (final var part : parts) { + lines.add(part); + } + return lines; + } + + private static void removeIncidentalBoundaryLines(final List lines) { + if (!lines.isEmpty() && lines.getFirst().isBlank()) { + lines.removeFirst(); + } + if (!lines.isEmpty() && lines.getLast().isBlank()) { + lines.removeLast(); + } + } + + private static int commonIndent(final List lines) { + var common = Integer.MAX_VALUE; + for (final var line : lines) { + if (line.isBlank()) { + continue; + } + common = Math.min(common, leadingWhitespace(line)); + } + return common == Integer.MAX_VALUE ? 0 : common; + } + + private static int leadingWhitespace(final String line) { + var count = 0; + while (count < line.length() && Character.isWhitespace(line.charAt(count))) { + count++; + } + return count; + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDocumentationTextBlockNormalizerTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDocumentationTextBlockNormalizerTest.java new file mode 100644 index 00000000..fb28990b --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDocumentationTextBlockNormalizerTest.java @@ -0,0 +1,54 @@ +package p.studio.compiler.pbs; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PbsDocumentationTextBlockNormalizerTest { + + @Test + void shouldNormalizeIncidentalBoundaryLinesAndIndentation() { + final var lexeme = "\"\"\"\n" + + " Draws a sprite.\n" + + "\n" + + " - `x`: screen x\n" + + " - `y`: screen y\n" + + " \"\"\""; + + final var normalized = PbsDocumentationTextBlockNormalizer.normalizeLexeme(lexeme); + + assertEquals("Draws a sprite.\n\n- `x`: screen x\n- `y`: screen y", normalized); + } + + @Test + void shouldPreserveInternalLineBreaksAndMarkdownContent() { + final var content = "\n" + + " Paragraph one.\n" + + " \n" + + " `code_span()`\n" + + " Paragraph two.\n"; + + final var normalized = PbsDocumentationTextBlockNormalizer.normalizeContent(content); + + assertEquals("Paragraph one.\n\n`code_span()`\nParagraph two.", normalized); + } + + @Test + void shouldReturnEmptyStringForWhitespaceOnlyPayload() { + final var normalized = PbsDocumentationTextBlockNormalizer.normalizeLexeme("\"\"\"\n \n \t\n\"\"\""); + + assertEquals("", normalized.trim()); + } + + @Test + void shouldNormalizeCarriageReturnLineEndings() { + final var normalized = PbsDocumentationTextBlockNormalizer.normalizeContent("\r\n\tLine one\r\n\tLine two\r\n"); + + assertEquals("Line one\nLine two", normalized); + } + + @Test + void shouldReturnEmptyStringForMalformedLexeme() { + assertEquals("", PbsDocumentationTextBlockNormalizer.normalizeLexeme("\"not a text block\"")); + } +}