From 4043800ef25c2e731fd79e4451ce1a9e08dde115 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 7 Sep 2026 18:39:13 +0300 Subject: [PATCH] fix(docs-infra): recognise an alert that follows prose in the same paragraph `docs-alert` was the only marked extension in the pipeline without a `start` hook, so marked never cut `inlineText` short at an alert and swallowed any directive that was not at the start of the inline source. Writing the alert on its own line without a blank line before it left the literal text in the body. On https://angular.dev/guide/http/testing two alerts render as boxes and a third shows as `IMPORTANT:` in the paragraph text. Also affects https://angular.dev/errors/NG3003 and the first step of the first app tutorial. The same renderer handles JSDoc, so one API page changes too: https://angular.dev/api/upgrade/static/downgradeModule has three `NOTE:` continuation lines inside bullets that now render as alerts. `docs-video` and `docs-pill` already declare `start` the same way. --- .../pipeline/shared/marked/extensions/docs-alert.mts | 6 ++++++ .../pipeline/shared/marked/test/docs-alert/docs-alert.md | 3 +++ .../shared/marked/test/docs-alert/docs-alert.spec.mts | 8 ++++++++ 3 files changed, 17 insertions(+) diff --git a/adev/shared-docs/pipeline/shared/marked/extensions/docs-alert.mts b/adev/shared-docs/pipeline/shared/marked/extensions/docs-alert.mts index 790db0ced37..15a9e00fe24 100644 --- a/adev/shared-docs/pipeline/shared/marked/extensions/docs-alert.mts +++ b/adev/shared-docs/pipeline/shared/marked/extensions/docs-alert.mts @@ -34,9 +34,15 @@ const tokenMatcher = new RegExp( 's', ); +const tokenStartMatcher = new RegExp(`\n(?:${alertSeverityLevels.join('|')}): `); + export const docsAlertExtension: TokenizerAndRendererExtension = { name: 'docs-alert', level: 'inline', + start(src: string) { + const index = src.match(tokenStartMatcher)?.index; + return index === undefined ? undefined : index + 1; + }, tokenizer(this: TokenizerThis, src: string): DocsAlertToken | undefined { const execMatch = tokenMatcher.exec(src); if (execMatch === null) { diff --git a/adev/shared-docs/pipeline/shared/marked/test/docs-alert/docs-alert.md b/adev/shared-docs/pipeline/shared/marked/test/docs-alert/docs-alert.md index 12a89306cda..2cb8e54ec73 100644 --- a/adev/shared-docs/pipeline/shared/marked/test/docs-alert/docs-alert.md +++ b/adev/shared-docs/pipeline/shared/marked/test/docs-alert/docs-alert.md @@ -17,4 +17,7 @@ IMPORTANT: Use Important for information that's crucial to comprehending the tex HELPFUL: Use Best practice to call out practices that are known to be successful or better than alternatives. +Some prose that runs straight into the alert with no blank line. +TIP: THIS TIP FOLLOWS PROSE ON THE NEXT LINE + NOTE: THIS NOTE WITHOUT A LINE RETURN diff --git a/adev/shared-docs/pipeline/shared/marked/test/docs-alert/docs-alert.spec.mts b/adev/shared-docs/pipeline/shared/marked/test/docs-alert/docs-alert.spec.mts index a67756f62bf..21078157597 100644 --- a/adev/shared-docs/pipeline/shared/marked/test/docs-alert/docs-alert.spec.mts +++ b/adev/shared-docs/pipeline/shared/marked/test/docs-alert/docs-alert.spec.mts @@ -35,6 +35,14 @@ describe('markdown to html', () => { expect(noteEl?.textContent?.trim()).toContain(`This is a multiline note`); }); + it(`should handle an alert that follows prose in the same paragraph`, () => { + const tipEls = markdownDocument.querySelectorAll(`.docs-alert-tip`); + + expect(tipEls[tipEls.length - 1]?.textContent?.trim()).toContain( + `THIS TIP FOLLOWS PROSE ON THE NEXT LINE`, + ); + }); + it(`should handle alerts without a line return`, () => { const noteEl = markdownDocument.querySelector(`.docs-alert-note:last-of-type`);