Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package rumdl for openSUSE:Factory checked 
in at 2026-03-19 17:39:39
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rumdl (Old)
 and      /work/SRC/openSUSE:Factory/.rumdl.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rumdl"

Thu Mar 19 17:39:39 2026 rev:46 rq:1341081 version:0.1.54

Changes:
--------
--- /work/SRC/openSUSE:Factory/rumdl/rumdl.changes      2026-03-17 
19:05:37.867490352 +0100
+++ /work/SRC/openSUSE:Factory/.rumdl.new.8177/rumdl.changes    2026-03-19 
17:41:20.643670878 +0100
@@ -1,0 +2,15 @@
+Thu Mar 19 06:13:36 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.1.54:
+  * Fixed
+    - MD013: Lines consisting entirely of inline HTML (e.g., badge
+      links <a href="..."><img .../></a>) are no longer flagged
+      when strict = false (#535)
+      - Two-tier detection: lines where all content is inside HTML
+        tags, and lines that start/end with tags containing URL
+        attributes (href, src, srcset, poster)
+      - HTML-only lines are also treated as paragraph boundaries in
+        reflow mode, preventing them from being merged into
+        adjacent prose
+
+-------------------------------------------------------------------

Old:
----
  rumdl-0.1.53.obscpio

New:
----
  rumdl-0.1.54.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ rumdl.spec ++++++
--- /var/tmp/diff_new_pack.WPwok3/_old  2026-03-19 17:41:23.247778760 +0100
+++ /var/tmp/diff_new_pack.WPwok3/_new  2026-03-19 17:41:23.263779423 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           rumdl
-Version:        0.1.53
+Version:        0.1.54
 Release:        0
 Summary:        Markdown Linter written in Rust
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.WPwok3/_old  2026-03-19 17:41:23.467787874 +0100
+++ /var/tmp/diff_new_pack.WPwok3/_new  2026-03-19 17:41:23.499789200 +0100
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/rvben/rumdl.git</param>
     <param name="scm">git</param>
     <param name="submodules">enable</param>
-    <param name="revision">v0.1.53</param>
+    <param name="revision">v0.1.54</param>
     <param name="match-tag">v*.*.*</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.WPwok3/_old  2026-03-19 17:41:23.635794835 +0100
+++ /var/tmp/diff_new_pack.WPwok3/_new  2026-03-19 17:41:23.659795829 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/rvben/rumdl.git</param>
-              <param 
name="changesrevision">ce2ee387fc15da333da08f395dc578d5eb5e29b7</param></service></servicedata>
+              <param 
name="changesrevision">8eeed34c6f8f3bacc43f59a6a34f3c15dd594b20</param></service></servicedata>
 (No newline at EOF)
 

++++++ rumdl-0.1.53.obscpio -> rumdl-0.1.54.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/CHANGELOG.md 
new/rumdl-0.1.54/CHANGELOG.md
--- old/rumdl-0.1.53/CHANGELOG.md       2026-03-16 16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/CHANGELOG.md       2026-03-18 17:23:42.000000000 +0100
@@ -7,6 +7,14 @@
 
 ## [Unreleased]
 
+## [0.1.54] - 2026-03-18
+
+### Fixed
+
+- **MD013**: Lines consisting entirely of inline HTML (e.g., badge links `<a 
href="..."><img .../></a>`) are no longer flagged when `strict = false` 
([#535](https://github.com/rvben/rumdl/issues/535))
+  - Two-tier detection: lines where all content is inside HTML tags, and lines 
that start/end with tags containing URL attributes (`href`, `src`, `srcset`, 
`poster`)
+  - HTML-only lines are also treated as paragraph boundaries in reflow mode, 
preventing them from being merged into adjacent prose
+
 ## [0.1.53] - 2026-03-16
 
 ### Fixed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/Cargo.lock new/rumdl-0.1.54/Cargo.lock
--- old/rumdl-0.1.53/Cargo.lock 2026-03-16 16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/Cargo.lock 2026-03-18 17:23:42.000000000 +0100
@@ -2247,7 +2247,7 @@
 
 [[package]]
 name = "rumdl"
-version = "0.1.53"
+version = "0.1.54"
 dependencies = [
  "anyhow",
  "assert_cmd",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/Cargo.toml new/rumdl-0.1.54/Cargo.toml
--- old/rumdl-0.1.53/Cargo.toml 2026-03-16 16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/Cargo.toml 2026-03-18 17:23:42.000000000 +0100
@@ -1,6 +1,6 @@
 [package]
 name = "rumdl"
-version = "0.1.53"
+version = "0.1.54"
 edition = "2024"
 rust-version = "1.94.0"
 description = "A fast Markdown linter written in Rust (Ru(st) MarkDown Linter)"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/README.md new/rumdl-0.1.54/README.md
--- old/rumdl-0.1.53/README.md  2026-03-16 16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/README.md  2026-03-18 17:23:42.000000000 +0100
@@ -196,7 +196,7 @@
 mise install rumdl
 
 # Use a specific version for the project
-mise use [email protected]
+mise use [email protected]
 ```
 
 ### Using Nix (macOS/Linux)
@@ -346,7 +346,7 @@
 ```yaml
 repos:
   - repo: https://github.com/rvben/rumdl-pre-commit
-    rev: v0.1.53
+    rev: v0.1.54
     hooks:
       - id: rumdl      # Lint only (fails on issues)
       - id: rumdl-fmt  # Auto-format and fail if issues remain
@@ -368,7 +368,7 @@
 ```yaml
 repos:
   - repo: https://github.com/rvben/rumdl-pre-commit
-    rev: v0.1.53
+    rev: v0.1.54
     hooks:
       - id: rumdl
         args: [--no-exclude]  # Disable all exclude patterns
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/npm/cli-darwin-arm64/package.json 
new/rumdl-0.1.54/npm/cli-darwin-arm64/package.json
--- old/rumdl-0.1.53/npm/cli-darwin-arm64/package.json  2026-03-16 
16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/npm/cli-darwin-arm64/package.json  2026-03-18 
17:23:42.000000000 +0100
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-darwin-arm64",
-  "version": "0.1.53",
+  "version": "0.1.54",
   "description": "rumdl binary for macOS ARM64 (Apple Silicon)",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/npm/cli-darwin-x64/package.json 
new/rumdl-0.1.54/npm/cli-darwin-x64/package.json
--- old/rumdl-0.1.53/npm/cli-darwin-x64/package.json    2026-03-16 
16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/npm/cli-darwin-x64/package.json    2026-03-18 
17:23:42.000000000 +0100
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-darwin-x64",
-  "version": "0.1.53",
+  "version": "0.1.54",
   "description": "rumdl binary for macOS x64 (Intel)",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/npm/cli-linux-arm64/package.json 
new/rumdl-0.1.54/npm/cli-linux-arm64/package.json
--- old/rumdl-0.1.53/npm/cli-linux-arm64/package.json   2026-03-16 
16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/npm/cli-linux-arm64/package.json   2026-03-18 
17:23:42.000000000 +0100
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-linux-arm64",
-  "version": "0.1.53",
+  "version": "0.1.54",
   "description": "rumdl binary for Linux ARM64 (glibc)",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/npm/cli-linux-arm64-musl/package.json 
new/rumdl-0.1.54/npm/cli-linux-arm64-musl/package.json
--- old/rumdl-0.1.53/npm/cli-linux-arm64-musl/package.json      2026-03-16 
16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/npm/cli-linux-arm64-musl/package.json      2026-03-18 
17:23:42.000000000 +0100
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-linux-arm64-musl",
-  "version": "0.1.53",
+  "version": "0.1.54",
   "description": "rumdl binary for Linux ARM64 (musl/Alpine)",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/npm/cli-linux-x64/package.json 
new/rumdl-0.1.54/npm/cli-linux-x64/package.json
--- old/rumdl-0.1.53/npm/cli-linux-x64/package.json     2026-03-16 
16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/npm/cli-linux-x64/package.json     2026-03-18 
17:23:42.000000000 +0100
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-linux-x64",
-  "version": "0.1.53",
+  "version": "0.1.54",
   "description": "rumdl binary for Linux x64 (glibc)",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/npm/cli-linux-x64-musl/package.json 
new/rumdl-0.1.54/npm/cli-linux-x64-musl/package.json
--- old/rumdl-0.1.53/npm/cli-linux-x64-musl/package.json        2026-03-16 
16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/npm/cli-linux-x64-musl/package.json        2026-03-18 
17:23:42.000000000 +0100
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-linux-x64-musl",
-  "version": "0.1.53",
+  "version": "0.1.54",
   "description": "rumdl binary for Linux x64 (musl/Alpine)",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/npm/cli-win32-x64/package.json 
new/rumdl-0.1.54/npm/cli-win32-x64/package.json
--- old/rumdl-0.1.53/npm/cli-win32-x64/package.json     2026-03-16 
16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/npm/cli-win32-x64/package.json     2026-03-18 
17:23:42.000000000 +0100
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-win32-x64",
-  "version": "0.1.53",
+  "version": "0.1.54",
   "description": "rumdl binary for Windows x64",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/npm/rumdl/package.json 
new/rumdl-0.1.54/npm/rumdl/package.json
--- old/rumdl-0.1.53/npm/rumdl/package.json     2026-03-16 16:29:34.000000000 
+0100
+++ new/rumdl-0.1.54/npm/rumdl/package.json     2026-03-18 17:23:42.000000000 
+0100
@@ -1,6 +1,6 @@
 {
   "name": "rumdl",
-  "version": "0.1.53",
+  "version": "0.1.54",
   "description": "A fast Markdown linter written in Rust",
   "license": "MIT",
   "repository": {
@@ -33,12 +33,12 @@
     "node": ">=18.0.0"
   },
   "optionalDependencies": {
-    "@rumdl/cli-darwin-x64": "0.1.53",
-    "@rumdl/cli-darwin-arm64": "0.1.53",
-    "@rumdl/cli-linux-x64": "0.1.53",
-    "@rumdl/cli-linux-arm64": "0.1.53",
-    "@rumdl/cli-linux-x64-musl": "0.1.53",
-    "@rumdl/cli-linux-arm64-musl": "0.1.53",
-    "@rumdl/cli-win32-x64": "0.1.53"
+    "@rumdl/cli-darwin-x64": "0.1.54",
+    "@rumdl/cli-darwin-arm64": "0.1.54",
+    "@rumdl/cli-linux-x64": "0.1.54",
+    "@rumdl/cli-linux-arm64": "0.1.54",
+    "@rumdl/cli-linux-x64-musl": "0.1.54",
+    "@rumdl/cli-linux-arm64-musl": "0.1.54",
+    "@rumdl/cli-win32-x64": "0.1.54"
   }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/src/rules/md013_line_length/helpers.rs 
new/rumdl-0.1.54/src/rules/md013_line_length/helpers.rs
--- old/rumdl-0.1.53/src/rules/md013_line_length/helpers.rs     2026-03-16 
16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/src/rules/md013_line_length/helpers.rs     2026-03-18 
17:23:42.000000000 +0100
@@ -243,6 +243,107 @@
     is_link_with_optional_emphasis(s)
 }
 
+/// Check if a line consists entirely of HTML structure that cannot be
+/// meaningfully shortened. Used to exempt HTML-only lines from MD013 in
+/// non-strict mode.
+///
+/// After stripping blockquote and list markers, a line is exempt if either:
+///
+/// 1. All non-whitespace content is inside `<...>` tags (e.g., badges,
+///    self-closing images, nested tags with no text between them).
+/// 2. The line starts with `<` and ends with `>` AND contains URL-bearing
+///    attributes (`href=`, `src=`, `srcset=`, `poster=`). This handles
+///    `<a href="url">text</a>` — functionally identical to `[text](url)`
+///    which is already exempt as a standalone link.
+///
+/// Handles quoted attribute values that may contain `>` characters.
+///
+/// Examples that return true:
+/// - `<a href="..."><img alt="badge" src="..."/></a>` (all content in tags)
+/// - `<img src="..." alt="..." width="..." height="..."/>` (self-closing)
+/// - `<a href="...">link text</a>` (HTML link, consistent with markdown link 
exemption)
+/// - `<video src="..." poster="..." controls></video>` (media with URL attrs)
+///
+/// Examples that return false:
+/// - `Some text <a href="...">link</a>` (text before tags)
+/// - `<b>very long bold text</b>` (formatting tag without URL attributes)
+/// - `Plain text without any HTML`
+pub(crate) fn is_html_only_line(line: &str) -> bool {
+    let mut s = line.trim_start();
+
+    // Strip blockquote markers
+    while let Some(rest) = s.strip_prefix('>') {
+        s = rest.trim_start();
+    }
+
+    // Strip list markers
+    if is_list_item(s) {
+        let (_, content) = extract_list_marker_and_content(s);
+        return is_html_only_content(&content);
+    }
+
+    is_html_only_content(s)
+}
+
+/// Combined check for HTML-only content.
+fn is_html_only_content(s: &str) -> bool {
+    let s = s.trim();
+    if s.is_empty() || !s.starts_with('<') {
+        return false;
+    }
+
+    // Check 1: All non-whitespace content is inside HTML tags.
+    // Covers badges, self-closing images, nested tags with no text between 
them.
+    if is_content_all_html_tags(s) {
+        return true;
+    }
+
+    // Check 2: Line is entirely wrapped in HTML (starts with <, ends with >)
+    // and contains URL-bearing attributes. This makes <a href="url">text</a>
+    // consistent with the existing [text](url) standalone link exemption.
+    if s.ends_with('>') && (s.contains("href=") || s.contains("src=") || 
s.contains("srcset=") || s.contains("poster="))
+    {
+        return true;
+    }
+
+    false
+}
+
+/// Returns true if all non-whitespace content is inside `<...>` delimiters.
+fn is_content_all_html_tags(s: &str) -> bool {
+    let s = s.trim();
+    if s.is_empty() || !s.starts_with('<') {
+        return false;
+    }
+
+    let mut in_tag = false;
+    let mut quote_char: Option<char> = None;
+    let mut found_complete_tag = false;
+
+    for c in s.chars() {
+        if let Some(q) = quote_char {
+            if c == q {
+                quote_char = None;
+            }
+        } else if in_tag {
+            match c {
+                '"' | '\'' => quote_char = Some(c),
+                '>' => {
+                    in_tag = false;
+                    found_complete_tag = true;
+                }
+                _ => {}
+            }
+        } else if c == '<' {
+            in_tag = true;
+        } else if !c.is_whitespace() {
+            return false;
+        }
+    }
+
+    found_complete_tag
+}
+
 /// Check if content (after stripping list/blockquote markers) is a standalone 
link,
 /// optionally wrapped in emphasis.
 fn is_link_with_optional_emphasis(s: &str) -> bool {
@@ -491,4 +592,165 @@
         // Link followed by text
         assert!(!is_standalone_link_or_image_line("[link](url) extra text"));
     }
+
+    // --- is_html_only_line tests ---
+
+    #[test]
+    fn test_html_only_badge_line() {
+        // The reported case: badge with nested <a> and <img>
+        assert!(is_html_only_line(
+            r#"<a 
href="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook";><img alt="badge" 
src="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook/shield"/></a>"#
+        ));
+    }
+
+    #[test]
+    fn test_html_only_self_closing_tags() {
+        assert!(is_html_only_line(
+            r#"<img src="https://example.com/image.png"; alt="screenshot" 
width="800" height="600"/>"#
+        ));
+        assert!(is_html_only_line(r#"<br/>"#));
+        assert!(is_html_only_line(r#"<hr />"#));
+    }
+
+    #[test]
+    fn test_html_only_multiple_tags() {
+        // Multiple adjacent tags with no text between them
+        assert!(is_html_only_line(r#"<img src="a.png"/><img src="b.png"/>"#));
+        assert!(is_html_only_line(r#"<br/><br/><br/>"#));
+    }
+
+    #[test]
+    fn test_html_only_empty_element() {
+        // Tags with no content between opening and closing
+        assert!(is_html_only_line(r#"<video src="long-url.mp4" 
controls></video>"#));
+        assert!(is_html_only_line(r#"<div></div>"#));
+    }
+
+    #[test]
+    fn test_html_only_with_whitespace_between_tags() {
+        assert!(is_html_only_line(r#"<img src="a.png"/> <img src="b.png"/>"#));
+    }
+
+    #[test]
+    fn test_html_only_quoted_angle_brackets() {
+        // Attribute value containing > should not break parsing
+        assert!(is_html_only_line(r#"<img alt="a > b" src="test.png"/>"#));
+        assert!(is_html_only_line(r#"<img alt='a > b' src="test.png"/>"#));
+    }
+
+    #[test]
+    fn test_html_only_in_blockquote() {
+        assert!(is_html_only_line(r#"> <img src="long-url.png" 
alt="screenshot"/>"#));
+        assert!(is_html_only_line(r#">> <a href="url"><img src="img"/></a>"#));
+    }
+
+    #[test]
+    fn test_html_only_in_list() {
+        assert!(is_html_only_line(r#"- <img src="long-url.png" 
alt="screenshot"/>"#));
+        assert!(is_html_only_line(r#"1. <a href="url"><img src="img"/></a>"#));
+        assert!(is_html_only_line(r#"  - <img src="long-url.png"/>"#));
+    }
+
+    #[test]
+    fn test_html_only_link_with_text_and_url() {
+        // <a href="url">text</a> is functionally identical to [text](url)
+        // which is already exempt — so this should also be exempt
+        assert!(is_html_only_line(
+            r#"<a href="https://example.com/very-long-path";>Click here for 
details</a>"#
+        ));
+        // With target attribute (reason to use HTML over markdown)
+        assert!(is_html_only_line(
+            r#"<a href="https://example.com/very-long-path"; 
target="_blank">Click here for details</a>"#
+        ));
+        // Multiple URL attributes
+        assert!(is_html_only_line(
+            r#"<a href="https://example.com/path";><img 
src="https://example.com/badge.svg"; alt="status"/></a>"#
+        ));
+    }
+
+    #[test]
+    fn test_not_html_only_text_before_tags() {
+        assert!(!is_html_only_line(r#"Click here: <a href="url">link</a>"#));
+        assert!(!is_html_only_line(r#"See <img src="url"/> for details"#));
+    }
+
+    #[test]
+    fn test_not_html_only_text_after_tags() {
+        assert!(!is_html_only_line(r#"<a href="url">link</a> - click above"#));
+        assert!(!is_html_only_line(r#"<img src="url"/> is an image"#));
+    }
+
+    #[test]
+    fn test_not_html_only_formatting_tags_without_urls() {
+        // Formatting tags without URL attributes should NOT be exempt —
+        // the line is long because of text content, not URLs
+        assert!(!is_html_only_line(
+            r#"<b>This is very long bold text that exceeds the line length 
limit</b>"#
+        ));
+        assert!(!is_html_only_line(
+            r#"<p>This is a very long paragraph written in HTML tags for some 
reason</p>"#
+        ));
+        assert!(!is_html_only_line(
+            r#"<span style="color:red">Some styled text that is quite 
long</span>"#
+        ));
+        assert!(!is_html_only_line(
+            r#"<em>Emphasized text that goes on and on and on</em>"#
+        ));
+        // Multiple formatting tags with text between them
+        assert!(!is_html_only_line(r#"<b>bold</b> and <i>italic</i>"#));
+    }
+
+    #[test]
+    fn test_not_html_only_plain_text() {
+        assert!(!is_html_only_line("Just some long text without any HTML"));
+        assert!(!is_html_only_line(""));
+        assert!(!is_html_only_line("   "));
+    }
+
+    #[test]
+    fn test_not_html_only_incomplete_tag() {
+        // Unclosed tag with no complete tag
+        assert!(!is_html_only_line("<unclosed"));
+        // Doesn't end with > (unclosed outer element)
+        assert!(!is_html_only_line(r#"<a href="url">text"#));
+    }
+
+    #[test]
+    fn test_html_only_comment() {
+        // Simple HTML comments (no > inside) are detected as all-inside-tags
+        assert!(is_html_only_line(
+            "<!-- this is a long HTML comment that spans many characters -->"
+        ));
+    }
+
+    #[test]
+    fn test_html_only_media_elements() {
+        assert!(is_html_only_line(
+            r#"<video src="https://example.com/very-long-path/video.mp4"; 
poster="https://example.com/thumb.jpg"; controls></video>"#
+        ));
+        assert!(is_html_only_line(
+            r#"<audio src="https://example.com/very-long-path/audio.mp3"; 
controls></audio>"#
+        ));
+        assert!(is_html_only_line(
+            r#"<source srcset="https://example.com/image-large.webp"; 
media="(min-width: 800px)"/>"#
+        ));
+        assert!(is_html_only_line(
+            r#"<picture><source srcset="large.webp"/><img 
src="fallback.png"/></picture>"#
+        ));
+    }
+
+    #[test]
+    fn test_html_only_in_list_with_url_text() {
+        // List item containing an HTML link with text — should be exempt
+        assert!(is_html_only_line(
+            r#"- <a href="https://example.com/very-long-path";>documentation 
link</a>"#
+        ));
+    }
+
+    #[test]
+    fn test_html_only_in_blockquote_with_url_text() {
+        assert!(is_html_only_line(
+            r#"> <a href="https://example.com/very-long-path";>documentation 
link</a>"#
+        ));
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/src/rules/md013_line_length/mod.rs 
new/rumdl-0.1.54/src/rules/md013_line_length/mod.rs
--- old/rumdl-0.1.53/src/rules/md013_line_length/mod.rs 2026-03-16 
16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/src/rules/md013_line_length/mod.rs 2026-03-18 
17:23:42.000000000 +0100
@@ -22,8 +22,8 @@
 pub mod md013_config;
 use crate::utils::is_template_directive_only;
 use helpers::{
-    extract_list_marker_and_content, has_hard_break, is_github_alert_marker, 
is_horizontal_rule, is_list_item,
-    is_standalone_link_or_image_line, split_into_segments, 
trim_preserving_hard_break,
+    extract_list_marker_and_content, has_hard_break, is_github_alert_marker, 
is_horizontal_rule, is_html_only_line,
+    is_list_item, is_standalone_link_or_image_line, split_into_segments, 
trim_preserving_hard_break,
 };
 pub use md013_config::MD013Config;
 use md013_config::{LengthMode, ReflowMode};
@@ -348,6 +348,13 @@
                     continue;
                 }
 
+                // Lines consisting entirely of HTML tags are exempt.
+                // Badge lines, images with attributes, and similar inline HTML
+                // are long due to URLs in attributes and can't be 
meaningfully shortened.
+                if is_html_only_line(line) {
+                    continue;
+                }
+
                 // Skip setext heading underlines
                 if !line.trim().is_empty() && line.trim().chars().all(|c| c == 
'=' || c == '-') {
                     continue;
@@ -596,6 +603,7 @@
             || is_standalone_attr_list(content)
             || is_snippet_block_delimiter(content)
             || is_github_alert_marker(trimmed)
+            || is_html_only_line(content)
     }
 
     fn generate_blockquote_paragraph_fix(
@@ -900,6 +908,7 @@
                 || is_template_directive_only(lines[i])
                 || is_link_ref_def
                 || ctx.line_info(line_num).is_some_and(|info| 
info.is_div_marker)
+                || is_html_only_line(lines[i])
             {
                 i += 1;
                 continue;
@@ -1111,6 +1120,14 @@
                         continue;
                     }
 
+                    // HTML-only lines inside footnotes are not reflowable
+                    if is_html_only_line(next_trimmed) {
+                        
fn_lines.push(FnLineType::Verbatim(strip_fn_indent(next), indent));
+                        last_consumed = i;
+                        i += 1;
+                        continue;
+                    }
+
                     // Regular prose content
                     
fn_lines.push(FnLineType::Content(next_trimmed.to_string()));
                     last_consumed = i;
@@ -1329,11 +1346,12 @@
                         break;
                     }
 
-                    // Skip list items, code blocks, headings within containers
+                    // Skip list items, code blocks, headings, HTML-only lines 
within containers
                     if is_list_item(line.trim())
                         || line.trim().starts_with("```")
                         || line.trim().starts_with("~~~")
                         || line.trim().starts_with('#')
+                        || is_html_only_line(line)
                     {
                         break;
                     }
@@ -2151,6 +2169,10 @@
                     if !config.strict && 
is_standalone_link_or_image_line(raw_line) {
                         return true;
                     }
+                    // HTML-only lines: exempt when not strict
+                    if !config.strict && is_html_only_line(raw_line) {
+                        return true;
+                    }
                     false
                 };
 
@@ -2872,6 +2894,7 @@
                     || is_standalone_attr_list(next_line)
                     || is_snippet_block_delimiter(next_line)
                     || ctx.line_info(next_line_num).is_some_and(|info| 
info.is_div_marker)
+                    || is_html_only_line(next_line)
                 {
                     break;
                 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.53/src/rules/md013_line_length/tests.rs 
new/rumdl-0.1.54/src/rules/md013_line_length/tests.rs
--- old/rumdl-0.1.53/src/rules/md013_line_length/tests.rs       2026-03-16 
16:29:34.000000000 +0100
+++ new/rumdl-0.1.54/src/rules/md013_line_length/tests.rs       2026-03-18 
17:23:42.000000000 +0100
@@ -6204,3 +6204,237 @@
         "Regular paragraph after blockquote should still warn when 
blockquotes=false"
     );
 }
+
+// --- HTML-only line exemption tests (issue #535) ---
+
+#[test]
+fn test_html_only_badge_line_exempt() {
+    let rule = MD013LineLength::new(80, false, false, false, false);
+    let content = r#"# Demo
+
+<a href="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook";><img 
alt="badge" 
src="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook/shield"/></a>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "HTML-only badge line should be exempt in non-strict mode, got: 
{result:?}"
+    );
+}
+
+#[test]
+fn test_html_only_img_with_attributes_exempt() {
+    let rule = MD013LineLength::new(80, false, false, false, false);
+    let content = r#"<img 
src="https://example.com/very-long-path/to/image.png"; alt="screenshot of the 
application" width="1286" height="185"/>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "HTML-only img tag should be exempt in non-strict mode, got: 
{result:?}"
+    );
+}
+
+#[test]
+fn test_html_only_multiple_badges_exempt() {
+    let rule = MD013LineLength::new(80, false, false, false, false);
+    let content = r#"<a href="https://example.com/first";><img 
src="https://img.shields.io/badge/first-blue"/></a> <a 
href="https://example.com/second";><img 
src="https://img.shields.io/badge/second-green"/></a>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "Multiple HTML badge tags on one line should be exempt, got: 
{result:?}"
+    );
+}
+
+#[test]
+fn test_html_only_not_exempt_in_strict_mode() {
+    let rule = MD013LineLength::new(80, false, false, false, true);
+    let content = r#"<a 
href="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook";><img alt="badge" 
src="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook/shield"/></a>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        !result.is_empty(),
+        "HTML-only lines should NOT be exempt in strict mode"
+    );
+}
+
+#[test]
+fn test_html_with_text_outside_tags_not_exempt() {
+    let rule = MD013LineLength::new(80, false, false, false, false);
+    let content = r#"Check out this badge: <a 
href="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook";><img alt="badge" 
src="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook/shield"/></a>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        !result.is_empty(),
+        "Lines with text outside HTML tags should still be flagged"
+    );
+}
+
+#[test]
+fn test_html_link_with_text_exempt_like_markdown_link() {
+    // <a href="url">text</a> is functionally identical to [text](url)
+    // which is already exempt — HTML links should be exempt too
+    let rule = MD013LineLength::new(30, false, false, false, false);
+    let content = r#"<a href="https://example.com/very-long-path";>Click here 
for more information about this topic and read the docs</a>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "HTML link with text should be exempt (consistent with markdown link 
exemption), got: {result:?}"
+    );
+}
+
+#[test]
+fn test_html_formatting_tags_without_urls_not_exempt() {
+    // <b>, <p>, <em> etc. without URL attributes should still be flagged
+    let rule = MD013LineLength::new(30, false, false, false, false);
+    let content = r#"<b>This is very long bold text that definitely exceeds 
the thirty char limit easily</b>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        !result.is_empty(),
+        "Formatting tags without URL attributes should still be flagged"
+    );
+}
+
+#[test]
+fn test_html_link_with_target_blank_exempt() {
+    // HTML used because markdown can't do target="_blank"
+    let rule = MD013LineLength::new(80, false, false, false, false);
+    let content =
+        r#"<a href="https://example.com/very-long-path/to/documentation/page"; 
target="_blank">Documentation</a>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "HTML link with target=_blank should be exempt, got: {result:?}"
+    );
+}
+
+#[test]
+fn test_html_only_in_list_exempt() {
+    let rule = MD013LineLength::new(80, false, false, false, false);
+    let content = r#"- <a 
href="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook";><img alt="badge" 
src="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook/shield"/></a>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "HTML-only line in list item should be exempt, got: {result:?}"
+    );
+}
+
+#[test]
+fn test_html_only_in_blockquote_exempt() {
+    let rule = MD013LineLength::new(80, false, false, false, false);
+    let content = r#"> <a 
href="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook";><img alt="badge" 
src="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook/shield"/></a>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "HTML-only line in blockquote should be exempt, got: {result:?}"
+    );
+}
+
+#[test]
+fn test_html_only_media_elements_exempt() {
+    let rule = MD013LineLength::new(80, false, false, false, false);
+    let content = r#"<video 
src="https://example.com/very-long-path/to/video.mp4"; 
poster="https://example.com/very-long-path/thumb.jpg"; controls></video>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "HTML-only media element should be exempt, got: {result:?}"
+    );
+}
+
+#[test]
+fn test_html_only_with_quoted_angle_brackets_exempt() {
+    let rule = MD013LineLength::new(80, false, false, false, false);
+    let content = r#"<img alt="comparison: value_a > value_b shows the 
difference clearly in this long alt text" 
src="https://example.com/image.png"/>"#;
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "HTML tag with > in quoted attribute should be exempt, got: {result:?}"
+    );
+}
+
+#[test]
+fn test_html_only_reflow_mode_not_merged_into_paragraph() {
+    // With reflow enabled, HTML-only lines should NOT be merged into adjacent 
paragraphs.
+    // This was the actual bug: the reflow path didn't recognize HTML-only 
lines as
+    // paragraph boundaries, causing them to be absorbed and flagged.
+    let config = MD013Config {
+        line_length: crate::types::LineLength::from_const(80),
+        reflow: true,
+        ..Default::default()
+    };
+    let rule = MD013LineLength::from_config_struct(config);
+
+    let content = "Some paragraph text.\n\n<a 
href=\"https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook\";><img 
alt=\"badge\" 
src=\"https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook/shield\"/></a>";
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "HTML-only line should not generate warnings in reflow mode, got: 
{result:?}"
+    );
+}
+
+#[test]
+fn test_html_only_reflow_mode_preserves_html_line() {
+    // Fix mode should not modify HTML-only lines
+    let config = MD013Config {
+        line_length: crate::types::LineLength::from_const(80),
+        reflow: true,
+        ..Default::default()
+    };
+    let rule = MD013LineLength::from_config_struct(config);
+
+    let content = "Some paragraph text.\n\n<a 
href=\"https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook\";><img 
alt=\"badge\" 
src=\"https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook/shield\"/></a>\n\nMore
 text after.";
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let fixed = rule.fix(&ctx).unwrap();
+    assert!(
+        fixed.contains(r#"<a 
href="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook";><img alt="badge" 
src="https://dotfyle.com/plugins/chrisgrieser/nvim-rulebook/shield"/></a>"#),
+        "Fix should preserve HTML-only line unchanged, got:\n{fixed}"
+    );
+}
+
+#[test]
+fn test_html_link_with_text_reflow_mode_exempt() {
+    // <a href="url">text</a> should also be exempt in reflow mode
+    let config = MD013Config {
+        line_length: crate::types::LineLength::from_const(80),
+        reflow: true,
+        ..Default::default()
+    };
+    let rule = MD013LineLength::from_config_struct(config);
+
+    let content = "<a 
href=\"https://example.com/very-long-path/to/documentation/page\"; 
target=\"_blank\">Click here for documentation</a>";
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let result = rule.check(&ctx).unwrap();
+    assert!(
+        result.is_empty(),
+        "HTML link with text should be exempt in reflow mode, got: {result:?}"
+    );
+}
+
+#[test]
+fn test_html_only_adjacent_to_paragraph_not_absorbed() {
+    // An HTML-only line adjacent to a long paragraph should not be merged
+    // into that paragraph during reflow
+    let config = MD013Config {
+        line_length: crate::types::LineLength::from_const(80),
+        reflow: true,
+        ..Default::default()
+    };
+    let rule = MD013LineLength::from_config_struct(config);
+
+    let content = "This is paragraph text that is quite long and exceeds the 
eighty character limit set for this test case.\n<a 
href=\"https://example.com\";><img src=\"https://example.com/badge.svg\"; 
alt=\"badge\"/></a>";
+    let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
+    let fixed = rule.fix(&ctx).unwrap();
+
+    // The paragraph should be reflowed but the HTML line should be preserved
+    assert!(
+        fixed.contains(r#"<a href="https://example.com";><img 
src="https://example.com/badge.svg"; alt="badge"/></a>"#),
+        "HTML-only line should be preserved during adjacent paragraph reflow, 
got:\n{fixed}"
+    );
+}

++++++ rumdl.obsinfo ++++++
--- /var/tmp/diff_new_pack.WPwok3/_old  2026-03-19 17:41:26.791925586 +0100
+++ /var/tmp/diff_new_pack.WPwok3/_new  2026-03-19 17:41:26.863928569 +0100
@@ -1,5 +1,5 @@
 name: rumdl
-version: 0.1.53
-mtime: 1773674974
-commit: ce2ee387fc15da333da08f395dc578d5eb5e29b7
+version: 0.1.54
+mtime: 1773851022
+commit: 8eeed34c6f8f3bacc43f59a6a34f3c15dd594b20
 

++++++ vendor.tar.zst ++++++
/work/SRC/openSUSE:Factory/rumdl/vendor.tar.zst 
/work/SRC/openSUSE:Factory/.rumdl.new.8177/vendor.tar.zst differ: char 7, line 1

Reply via email to