From 25ffbb02aa151b3f795b409b42f7d5b221a84036 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 18:48:18 +0000 Subject: [PATCH] A block this parser has never seen must cost it nothing The row pattern accepted `` blocks and bare text and nothing else. A future block with any other tag - a ``, a `` - would have made every row unmatchable, and that failure does not read as a parse error over here: it reads as "there are no samples for that". Twice now a NEW KIND of block has arrived from the sample repositories (the `@docs` links, then the `@summary` sentence), and the second one is what this change follows. A block is now `
` plus anything, tagged or not, and its TAG decides what it is: no tag is the sentence, `` is the small type (keywords, then the docs links), and anything else is carried through and ignored. The same generalisation went into abap2UI5/docs' link-samples parser, which reads the same rows and failed the same way this morning. The shape is maintained in three repositories and read in two - it is a contract between five programs, and the two readers must be the forgiving end of it. 41 tests, including a row carrying a block between the two the parser reads --- lib/examples.mjs | 24 +++++++++++++++--------- test/unit.test.mjs | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/examples.mjs b/lib/examples.mjs index 09aedcf..754e0ab 100644 --- a/lib/examples.mjs +++ b/lib/examples.mjs @@ -104,23 +104,29 @@ export function catalogueFiles() { *
text the summary sentence (normal type) *
text the search terms, then the docs links */ -const ROW = /^\|\s*(?:\*\*(?[^*]+)\*\*\s*(?:—|--)\s*)?(?<sub>[^|<]*?)\s*(?<blocks>(?:<br>(?:<sub>[^<]*<\/sub>|[^<]*))*)\s*\|\s*\[`(?<cls>[A-Z0-9_]+)`\]\((?<path>[^)]+)\)\s*\|/; +const ROW = /^\|\s*(?:\*\*(?<title>[^*]+)\*\*\s*(?:—|--)\s*)?(?<sub>[^|<]*?)\s*(?<blocks>(?:<br>(?:<[a-z]+>[^<]*<\/[a-z]+>|[^<]*))*)\s*\|\s*\[`(?<cls>[A-Z0-9_]+)`\]\((?<path>[^)]+)\)\s*\|/; -/** The blocks under a row title, in order, as `{ small, text }`. */ +/* The blocks under a row title, in order, as `{ tag, text }`. + * + * A block wrapped in a tag nobody here knows is kept as a block with that tag + * and then ignored by both readers below - which is the whole reason the row + * pattern accepts any tag rather than `<sub>` alone. Twice now a new KIND of + * block has made every row unmatchable over here, and that failure reads as + * "there are no samples for that" rather than as a parse error. */ function blocksOf(blocks) { - return [...(blocks || '').matchAll(/<br>(?:<sub>([^<]*)<\/sub>|([^<]*))/g)] + return [...(blocks || '').matchAll(/<br>(?:<([a-z]+)>([^<]*)<\/[a-z]+>|([^<]*))/g)] .map((m) => (m[1] !== undefined - ? { small: true, text: m[1].trim() } - : { small: false, text: (m[2] || '').trim() })) + ? { tag: m[1], text: (m[2] || '').trim() } + : { tag: '', text: (m[3] || '').trim() })) .filter((b) => b.text); } -/** The sentence: the first block in NORMAL type. */ -const summaryOf = (blocks) => (blocksOf(blocks).find((b) => !b.small)?.text || ''); +/** The sentence: the first block in NORMAL type (no tag at all). */ +const summaryOf = (blocks) => (blocksOf(blocks).find((b) => !b.tag)?.text || ''); -/** The keywords: the first SMALL block that is not the `docs:` one. */ +/** The keywords: the first `<sub>` block that is not the `docs:` one. */ const keywordsOf = (blocks) => - (blocksOf(blocks).find((b) => b.small && !b.text.startsWith('docs:'))?.text || ''); + (blocksOf(blocks).find((b) => b.tag === 'sub' && !b.text.startsWith('docs:'))?.text || ''); /** src/01 is the supported set; src/00 is experimental (97) or a test app (98). */ const areaOf = (repo, p) => { diff --git a/test/unit.test.mjs b/test/unit.test.mjs index be0fa92..62a2b93 100644 --- a/test/unit.test.mjs +++ b/test/unit.test.mjs @@ -429,6 +429,27 @@ test('a row without a summary still parses', () => { assert.equal(e.keywords, 'f4 search help suggestion input'); }); +/* The row shape is maintained in three OTHER repositories, and a new kind of + * block has twice made every row unmatchable here. A block whose tag this + * parser has never seen must cost it nothing - the sample still has to be + * findable, because "no rows parsed" reads as "there are no samples for that" + * rather than as a parse error. */ +test('a row survives a block this parser has never seen', () => { + const md = [ + '## Basics', + '', + '| Sample | Class |', + '|---|---|', + '| **Future** — Something<br>The sentence.<br><span>a block from a later generator</span><br><sub>terms here</sub> | [`Z2UI5_CL_SMP_APP_999`](src/01/z2ui5_cl_smp_app_999.clas.abap) |', + ].join('\n'); + const [e] = parseExamples(md); + assert.ok(e, 'the row with an unknown block was dropped'); + assert.equal(e.cls, 'Z2UI5_CL_SMP_APP_999'); + // and the two blocks this parser DOES read are still told apart correctly + assert.equal(e.summary, 'The sentence.'); + assert.equal(e.keywords, 'terms here'); +}); + test('every catalogue names its repository and its env var when it is missing', () => { assert.deepEqual(CATALOGUES.map((c) => c.repo), ['samples', 'samples-controls', 'samples-stack']); for (const c of CATALOGUES) {