Commit2a7d24ebRecorded31 Jul 2026Repositorysigil-markdown

sigil-markdown 0.9.4: stop publishing HTML comments

Message

A <!-- ... --> block fell through to the paragraph case and rendered as escaped visible text. That published RELEASES.md's authoring notes onto usesigil.org, from a comment whose own first line read "this comment is not published".

Block-level comments, single and multi-line, are now consumed and emitted as nothing. Per CommonMark an unterminated comment runs to end of document; that is asserted by a test so it is deliberate rather than a surprise. Inline comments are still passed through - this parser has no inline raw-HTML handling at all.

32/32, up from 27. Sabotage-tested: neutering the detector fails exactly the three comment tests, and the two CONTROL tests (prose with angle brackets, a document with no comment) stay green, so they would catch a stripper that deleted too much.

Changed
 CHANGELOG.md           |  6 ++++++
 package.sgl            |  2 +-
 src/sigil/markdown.sgl | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 test/test-markdown.sgl | 34 +++++++++++++++++++++++++++++++++-
 4 files changed, 86 insertions(+), 2 deletions(-)
Diff
CHANGELOG.mdmodified
@@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file.
7
The format is based on [Keep a Changelog](https://keepachangelog.com/),
8
and this project adheres to [Semantic Versioning](https://semver.org/).
9
+10
## [0.9.4] - 2026-07-31
+11
+12
### Fixed
+13
+14
- HTML comments are no longer rendered. A `<!-- ... -->` block previously fell through to the paragraph case and appeared as escaped, visible text, so authoring notes intended for source readers were published. Both single-line and multi-line comments are now consumed and emitted as nothing. Per CommonMark, an unterminated comment runs to the end of the document; that behaviour is deliberate and covered by a test. Inline comments inside a paragraph are still passed through, since this parser has no inline raw-HTML handling.
+15
16
## [0.9.3] - 2026-07-30
17
18
### Fixed
package.sglmodified
@@ -5,7 +5,7 @@
5
6
(package
7
name: "sigil-markdown"
8
version: "0.9.3"
+8
version: "0.9.4"
9
sigil: "^0.17"
10
description: "Markdown and frontmatter parser"
11
url: "https://codeberg.org/sigil/sigil-markdown"
src/sigil/markdown.sglmodified
@@ -98,6 +98,24 @@
98
(loop start (+ i 1) lines))))))
99
100
;; Check if line is a horizontal rule (---, ***, ___)
+101
;; Does LINE open an HTML comment block? CommonMark HTML block type 2.
+102
;; Leading spaces are allowed, as with the other block starts.
+103
(define (html-comment-start? line)
+104
(let ((trimmed (string-trim line)))
+105
(and (>= (string-length trimmed) 4)
+106
(string=? (substring trimmed 0 4) "<!--"))))
+107
+108
;; Does LINE contain the comment terminator? Per CommonMark the block ends
+109
;; on the line containing `-->`, INCLUDING the opening line, so a one-line
+110
;; `<!-- x -->` is a complete block.
+111
(define (html-comment-end? line)
+112
(let ((len (string-length line)))
+113
(let loop ((i 0))
+114
(cond
+115
((> (+ i 3) len) #f)
+116
((string=? (substring line i (+ i 3)) "-->") #t)
+117
(else (loop (+ i 1)))))))
+118
119
(define (horizontal-rule? line)
120
(let ((trimmed (string-trim line)))
121
(and (>= (string-length trimmed) 3)
@@ -386,6 +404,34 @@
404
((string-blank? line)
405
(loop rest blocks))
406
+407
;; HTML comment block - consumed and emitted as nothing.
+408
;;
+409
;; Must be tested BEFORE the paragraph fallthrough, which is
+410
;; what previously rendered `<!-- ... -->` as escaped visible
+411
;; text. That shipped authoring notes onto usesigil.org's
+412
;; Releases page, from a comment whose own first line read
+413
;; "this comment is not published".
+414
;;
+415
;; Per CommonMark, an UNTERMINATED comment runs to end of
+416
;; document. That is the spec and other tools agree, so we
+417
;; match it rather than inventing a safer-looking rule that
+418
;; would render differently elsewhere. It does mean a stray
+419
;; `<!--` silently eats the remainder; the alternative is
+420
;; content that renders one way here and another way in every
+421
;; other renderer, which is worse.
+422
;;
+423
;; Scope: BLOCK-level comments only. An inline `<!-- -->` in
+424
;; the middle of a paragraph is still passed through as text,
+425
;; because this parser has no inline raw-HTML handling at all.
+426
((html-comment-start? line)
+427
(if (html-comment-end? line)
+428
(loop rest blocks)
+429
(let skip-loop ((rest rest))
+430
(cond
+431
((null? rest) (reverse blocks))
+432
((html-comment-end? (car rest)) (loop (cdr rest) blocks))
+433
(else (skip-loop (cdr rest)))))))
+434
435
;; ATX Header
436
((parse-atx-header line)
437
=> (lambda (header)
test/test-markdown.sglmodified
@@ -264,4 +264,36 @@
264
(inner (car (filter pair? (cdr top))))
265
(deeper (car (filter pair? (cdr (car (sxml-content inner)))))))
266
(assert-equal 'ul (sxml-tag inner))
267
(assert-equal 'ul (sxml-tag deeper)))))
+267
(assert-equal 'ul (sxml-tag deeper))))
+268
+269
;; HTML comments. Before 0.9.4 these fell through to the paragraph case and
+270
;; rendered as escaped VISIBLE TEXT — which published a RELEASES.md authoring
+271
;; note onto usesigil.org, from a comment whose own first line read "this
+272
;; comment is not published".
+273
;;
+274
;; The two CONTROL tests below are the point: it is easy to write a stripper
+275
;; that also eats ordinary prose containing angle brackets, and a suite that
+276
;; only checked "the comment is gone" would pass for a parser that deleted
+277
;; far too much.
+278
+279
(test "multi-line HTML comment is not rendered"
+280
(let ((doc (markdown->sxml "# T\n\n<!--\n hidden note\n-->\n\nVisible.\n")))
+281
(assert-equal '(document (h1 "T") (p "Visible.")) doc)))
+282
+283
(test "single-line HTML comment is not rendered"
+284
(let ((doc (markdown->sxml "# T\n\n<!-- hidden -->\n\nVisible.\n")))
+285
(assert-equal '(document (h1 "T") (p "Visible.")) doc)))
+286
+287
(test "CONTROL: prose containing angle brackets survives"
+288
(let ((doc (markdown->sxml "# T\n\na < b and c > d\n")))
+289
(assert-equal '(document (h1 "T") (p "a < b and c > d")) doc)))
+290
+291
(test "CONTROL: a document with no comment is unchanged"
+292
(let ((doc (markdown->sxml "# T\n\nVisible only.\n")))
+293
(assert-equal '(document (h1 "T") (p "Visible only.")) doc)))
+294
+295
;; CommonMark: an unterminated comment runs to end of document. Asserted so
+296
;; the behaviour is deliberate and discoverable rather than a surprise.
+297
(test "unterminated comment consumes the rest, per CommonMark"
+298
(let ((doc (markdown->sxml "# T\n\n<!-- oops\n\nSwallowed.\n")))
+299
(assert-equal '(document (h1 "T")) doc))))