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-05-30 22:58:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rumdl (Old) and /work/SRC/openSUSE:Factory/.rumdl.new.1937 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rumdl" Sat May 30 22:58:40 2026 rev:71 rq:1356007 version:0.2.4 Changes: -------- --- /work/SRC/openSUSE:Factory/rumdl/rumdl.changes 2026-05-29 18:14:27.824767901 +0200 +++ /work/SRC/openSUSE:Factory/.rumdl.new.1937/rumdl.changes 2026-05-30 23:01:30.524586075 +0200 @@ -1,0 +2,14 @@ +Sat May 30 06:47:15 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 0.2.4: + * Fixed + - md060: apply aligned-delimiter when a table auto-compacts + past max-width (663f4ba) + - md034: don't flag URL arguments of MyST colon directives + (d55ed20) + - embedded: gate markdown code block formatting behind + code-block-tools opt-in (bd23ad1) + - md046: treat MyST directive body as directive, not indented + code block (060bae2) + +------------------------------------------------------------------- Old: ---- rumdl-0.2.3.obscpio New: ---- rumdl-0.2.4.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rumdl.spec ++++++ --- /var/tmp/diff_new_pack.EWBysp/_old 2026-05-30 23:01:34.532750845 +0200 +++ /var/tmp/diff_new_pack.EWBysp/_new 2026-05-30 23:01:34.536751009 +0200 @@ -17,7 +17,7 @@ Name: rumdl -Version: 0.2.3 +Version: 0.2.4 Release: 0 Summary: Markdown Linter written in Rust License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.EWBysp/_old 2026-05-30 23:01:34.588753147 +0200 +++ /var/tmp/diff_new_pack.EWBysp/_new 2026-05-30 23:01:34.600753640 +0200 @@ -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.2.3</param> + <param name="revision">v0.2.4</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.EWBysp/_old 2026-05-30 23:01:34.636755120 +0200 +++ /var/tmp/diff_new_pack.EWBysp/_new 2026-05-30 23:01:34.640755285 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/rvben/rumdl.git</param> - <param name="changesrevision">a5a0c2d5d6d631393c5ca773872742726d4fa4db</param></service></servicedata> + <param name="changesrevision">a0d57ac276a15c3697a27cfe53f46e7742c341b9</param></service></servicedata> (No newline at EOF) ++++++ rumdl-0.2.3.obscpio -> rumdl-0.2.4.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/CHANGELOG.md new/rumdl-0.2.4/CHANGELOG.md --- old/rumdl-0.2.3/CHANGELOG.md 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/CHANGELOG.md 2026-05-29 21:11:01.000000000 +0200 @@ -43,6 +43,16 @@ + +## [0.2.4](https://github.com/rvben/rumdl/compare/v0.2.3...v0.2.4) - 2026-05-29 + +### Fixed + +- **md060**: apply aligned-delimiter when a table auto-compacts past max-width ([663f4ba](https://github.com/rvben/rumdl/commit/663f4babbb24102bd80924c2d31c9bd7005c61e7)) +- **md034**: don't flag URL arguments of MyST colon directives ([d55ed20](https://github.com/rvben/rumdl/commit/d55ed20eab6a5b7d17f53976af53c019e4d3b0c1)) +- **embedded**: gate markdown code block formatting behind code-block-tools opt-in ([bd23ad1](https://github.com/rvben/rumdl/commit/bd23ad15e02499100f9d76ed24e9aae16b8750b6)) +- **md046**: treat MyST directive body as directive, not indented code block ([060bae2](https://github.com/rvben/rumdl/commit/060bae2292c7e25805abc1d86de709252c607641)) + ## [0.2.3](https://github.com/rvben/rumdl/compare/v0.2.2...v0.2.3) - 2026-05-27 ### Fixed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/Cargo.lock new/rumdl-0.2.4/Cargo.lock --- old/rumdl-0.2.3/Cargo.lock 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/Cargo.lock 2026-05-29 21:11:01.000000000 +0200 @@ -2274,7 +2274,7 @@ [[package]] name = "rumdl" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "assert_cmd", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/Cargo.toml new/rumdl-0.2.4/Cargo.toml --- old/rumdl-0.2.3/Cargo.toml 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/Cargo.toml 2026-05-29 21:11:01.000000000 +0200 @@ -1,6 +1,6 @@ [package] name = "rumdl" -version = "0.2.3" +version = "0.2.4" 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.2.3/README.md new/rumdl-0.2.4/README.md --- old/rumdl-0.2.3/README.md 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/README.md 2026-05-29 21:11:01.000000000 +0200 @@ -206,7 +206,7 @@ mise install rumdl # Use a specific version for the project -mise use [email protected] +mise use [email protected] ``` ### Using Nix (macOS/Linux) @@ -405,7 +405,7 @@ ```yaml repos: - repo: https://github.com/rvben/rumdl-pre-commit - rev: v0.2.3 + rev: v0.2.4 hooks: - id: rumdl # Lint only (fails on issues) - id: rumdl-fmt # Auto-format and fail if issues remain @@ -427,7 +427,7 @@ ```yaml repos: - repo: https://github.com/rvben/rumdl-pre-commit - rev: v0.2.3 + rev: v0.2.4 hooks: - id: rumdl args: [--no-exclude] # Disable all exclude patterns diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/docs/global-settings.md new/rumdl-0.2.4/docs/global-settings.md --- old/rumdl-0.2.3/docs/global-settings.md 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/docs/global-settings.md 2026-05-29 21:11:01.000000000 +0200 @@ -1342,7 +1342,7 @@ ```yaml - repo: https://github.com/rvben/rumdl-pre-commit - rev: v0.2.3 + rev: v0.2.4 hooks: - id: rumdl args: [--config=.rumdl.toml] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/npm/cli-darwin-arm64/package.json new/rumdl-0.2.4/npm/cli-darwin-arm64/package.json --- old/rumdl-0.2.3/npm/cli-darwin-arm64/package.json 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/npm/cli-darwin-arm64/package.json 2026-05-29 21:11:01.000000000 +0200 @@ -1,6 +1,6 @@ { "name": "@rumdl/cli-darwin-arm64", - "version": "0.2.3", + "version": "0.2.4", "description": "rumdl binary for macOS ARM64 (Apple Silicon)", "license": "MIT", "repository": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/npm/cli-darwin-x64/package.json new/rumdl-0.2.4/npm/cli-darwin-x64/package.json --- old/rumdl-0.2.3/npm/cli-darwin-x64/package.json 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/npm/cli-darwin-x64/package.json 2026-05-29 21:11:01.000000000 +0200 @@ -1,6 +1,6 @@ { "name": "@rumdl/cli-darwin-x64", - "version": "0.2.3", + "version": "0.2.4", "description": "rumdl binary for macOS x64 (Intel)", "license": "MIT", "repository": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/npm/cli-linux-arm64/package.json new/rumdl-0.2.4/npm/cli-linux-arm64/package.json --- old/rumdl-0.2.3/npm/cli-linux-arm64/package.json 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/npm/cli-linux-arm64/package.json 2026-05-29 21:11:01.000000000 +0200 @@ -1,6 +1,6 @@ { "name": "@rumdl/cli-linux-arm64", - "version": "0.2.3", + "version": "0.2.4", "description": "rumdl binary for Linux ARM64 (glibc)", "license": "MIT", "repository": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/npm/cli-linux-arm64-musl/package.json new/rumdl-0.2.4/npm/cli-linux-arm64-musl/package.json --- old/rumdl-0.2.3/npm/cli-linux-arm64-musl/package.json 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/npm/cli-linux-arm64-musl/package.json 2026-05-29 21:11:01.000000000 +0200 @@ -1,6 +1,6 @@ { "name": "@rumdl/cli-linux-arm64-musl", - "version": "0.2.3", + "version": "0.2.4", "description": "rumdl binary for Linux ARM64 (musl/Alpine)", "license": "MIT", "repository": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/npm/cli-linux-x64/package.json new/rumdl-0.2.4/npm/cli-linux-x64/package.json --- old/rumdl-0.2.3/npm/cli-linux-x64/package.json 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/npm/cli-linux-x64/package.json 2026-05-29 21:11:01.000000000 +0200 @@ -1,6 +1,6 @@ { "name": "@rumdl/cli-linux-x64", - "version": "0.2.3", + "version": "0.2.4", "description": "rumdl binary for Linux x64 (glibc)", "license": "MIT", "repository": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/npm/cli-linux-x64-musl/package.json new/rumdl-0.2.4/npm/cli-linux-x64-musl/package.json --- old/rumdl-0.2.3/npm/cli-linux-x64-musl/package.json 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/npm/cli-linux-x64-musl/package.json 2026-05-29 21:11:01.000000000 +0200 @@ -1,6 +1,6 @@ { "name": "@rumdl/cli-linux-x64-musl", - "version": "0.2.3", + "version": "0.2.4", "description": "rumdl binary for Linux x64 (musl/Alpine)", "license": "MIT", "repository": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/npm/cli-win32-x64/package.json new/rumdl-0.2.4/npm/cli-win32-x64/package.json --- old/rumdl-0.2.3/npm/cli-win32-x64/package.json 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/npm/cli-win32-x64/package.json 2026-05-29 21:11:01.000000000 +0200 @@ -1,6 +1,6 @@ { "name": "@rumdl/cli-win32-x64", - "version": "0.2.3", + "version": "0.2.4", "description": "rumdl binary for Windows x64", "license": "MIT", "repository": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/npm/rumdl/package.json new/rumdl-0.2.4/npm/rumdl/package.json --- old/rumdl-0.2.3/npm/rumdl/package.json 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/npm/rumdl/package.json 2026-05-29 21:11:01.000000000 +0200 @@ -1,6 +1,6 @@ { "name": "rumdl", - "version": "0.2.3", + "version": "0.2.4", "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.2.3", - "@rumdl/cli-darwin-arm64": "0.2.3", - "@rumdl/cli-linux-x64": "0.2.3", - "@rumdl/cli-linux-arm64": "0.2.3", - "@rumdl/cli-linux-x64-musl": "0.2.3", - "@rumdl/cli-linux-arm64-musl": "0.2.3", - "@rumdl/cli-win32-x64": "0.2.3" + "@rumdl/cli-darwin-x64": "0.2.4", + "@rumdl/cli-darwin-arm64": "0.2.4", + "@rumdl/cli-linux-x64": "0.2.4", + "@rumdl/cli-linux-arm64": "0.2.4", + "@rumdl/cli-linux-x64-musl": "0.2.4", + "@rumdl/cli-linux-arm64-musl": "0.2.4", + "@rumdl/cli-win32-x64": "0.2.4" } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/src/file_processor/processing.rs new/rumdl-0.2.4/src/file_processor/processing.rs --- old/rumdl-0.2.3/src/file_processor/processing.rs 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/src/file_processor/processing.rs 2026-05-29 21:11:01.000000000 +0200 @@ -229,10 +229,15 @@ Some(Path::new(file_path)), ); - // Format embedded markdown blocks (recursive formatting) - // Use filtered_rules to respect per-file-ignores for embedded content - let embedded_formatted = format_embedded_markdown_blocks(&mut content, &filtered_rules, config); - warnings_fixed += embedded_formatted; + // Format embedded markdown blocks (recursive formatting). This is opt-in + // via code-block-tools (`[code-block-tools.languages.markdown] lint = ["rumdl"]`) + // and gated identically to the check path, so `--fix` never rewrites the + // contents of a markdown code block that `check` did not report on. + // filtered_rules respects per-file-ignores for the embedded content. + if should_lint_embedded_markdown(&config.code_block_tools) { + let embedded_formatted = format_embedded_markdown_blocks(&mut content, &filtered_rules, config); + warnings_fixed += embedded_formatted; + } // Format doc comments in Rust files if Path::new(file_path).extension().is_some_and(|ext| ext == "rs") { @@ -304,10 +309,15 @@ Some(Path::new(file_path)), ); - // Format embedded markdown blocks (recursive formatting) - // Use filtered_rules to respect per-file-ignores for embedded content - let embedded_formatted = format_embedded_markdown_blocks(&mut content, &filtered_rules, config); - warnings_fixed += embedded_formatted; + // Format embedded markdown blocks (recursive formatting). This is opt-in + // via code-block-tools (`[code-block-tools.languages.markdown] lint = ["rumdl"]`) + // and gated identically to the check path, so `--fix` never rewrites the + // contents of a markdown code block that `check` did not report on. + // filtered_rules respects per-file-ignores for the embedded content. + if should_lint_embedded_markdown(&config.code_block_tools) { + let embedded_formatted = format_embedded_markdown_blocks(&mut content, &filtered_rules, config); + warnings_fixed += embedded_formatted; + } // Format doc comments in Rust files if Path::new(file_path).extension().is_some_and(|ext| ext == "rs") { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/src/lint_context/flavor_detection.rs new/rumdl-0.2.4/src/lint_context/flavor_detection.rs --- old/rumdl-0.2.3/src/lint_context/flavor_detection.rs 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/src/lint_context/flavor_detection.rs 2026-05-29 21:11:01.000000000 +0200 @@ -1017,7 +1017,7 @@ /// Check if a line is a MyST colon directive opener. /// Pattern: 0-3 leading spaces, 3+ colons, immediately followed by `{name}`. /// Returns the colon count if it's an opener, None otherwise. -fn myst_colon_directive_opener(line: &str) -> Option<usize> { +pub(super) fn myst_colon_directive_opener(line: &str) -> Option<usize> { let spaces = count_leading_spaces(line); if spaces > 3 { return None; @@ -1278,11 +1278,15 @@ } } } else { - // Code-bearing directive: mark opener/closer as directive but keep in_code_block - if end_line_idx > 0 - && let Some(closer_line) = lines.get_mut(end_line_idx - 1) - { - closer_line.in_myst_directive = true; + // Code-bearing directive (e.g. `{eval-rst}`, `{code-block}`): the body is + // opaque code, so keep `in_code_block` set on the body lines. Mark every + // line of the directive (opener, body, closer) as `in_myst_directive` so + // rules that skip directive structure (MD046, MD048) treat the whole block + // as one directive. Marking only the fences left the indented body lines + // looking like a standalone indented code block to those rules. + let body_end = end_line_idx.min(lines.len()); + for line in &mut lines[start_line_idx..body_end] { + line.in_myst_directive = true; } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/src/lint_context/mod.rs new/rumdl-0.2.4/src/lint_context/mod.rs --- old/rumdl-0.2.3/src/lint_context/mod.rs 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/src/lint_context/mod.rs 2026-05-29 21:11:01.000000000 +0200 @@ -984,6 +984,22 @@ Self::binary_search_ranges(&self.myst_comment_ranges, byte_pos) } + /// Check if a line (1-indexed) is a MyST colon-fence directive opener (`:::{name} ...`). + /// + /// The text after `{name}` on an opener is the directive's argument (an opaque + /// path, URL, or label), not markdown prose. Rules that reformat prose should + /// skip these lines. Returns false for non-MyST flavors and for directive body + /// or closer lines. + pub fn is_myst_colon_directive_opener_line(&self, line_num: usize) -> bool { + if !self.flavor.supports_myst_directives() { + return false; + } + self.lines.get(line_num.wrapping_sub(1)).is_some_and(|info| { + info.in_myst_directive + && flavor_detection::myst_colon_directive_opener(info.content(self.content)).is_some() + }) + } + /// Get HTML tags - computed lazily on first access pub fn html_tags(&self) -> Arc<Vec<HtmlTag>> { Arc::clone(self.html_tags_cache.get_or_init(|| { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/src/rules/md034_no_bare_urls.rs new/rumdl-0.2.4/src/rules/md034_no_bare_urls.rs --- old/rumdl-0.2.3/src/rules/md034_no_bare_urls.rs 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/src/rules/md034_no_bare_urls.rs 2026-05-29 21:11:01.000000000 +0200 @@ -497,6 +497,15 @@ .skip_mdx_comments() .skip_obsidian_comments() { + // Skip MyST colon-fence directive openers (`:::{name} <arg>`). The text + // after the directive name is an opaque argument (a URL, path, or label), + // not markdown prose, so a bare URL there must not be wrapped in angle + // brackets. Directive body lines are not openers, so they fall through to + // `check_line` and are linted as usual. + if ctx.is_myst_colon_directive_opener_line(line.line_num) { + continue; + } + let mut line_warnings = self.check_line(line.content, ctx, line.line_num, &code_spans, &mut buffers, line_index); @@ -732,4 +741,105 @@ "URL in backticks after a fenced code block inside MDX must not be flagged: {result:?}" ); } + + /// Issue #642: a URL given as the argument of a MyST colon-fence directive + /// (`:::{name} <url>`) is the directive's opaque argument, not markdown prose, + /// and must not be wrapped in angle brackets. + #[test] + fn test_myst_colon_directive_argument_url_not_flagged() { + use crate::config::MarkdownFlavor; + use crate::lint_context::LintContext; + let rule = MD034NoBareUrls; + let content = "\ +:::{anywidget} https://cdn.jsdelivr.net/npm/[email protected]/dist/repo-review-anywidget.mjs +{ + \"deps\": [\"repo-review~=1.1.0\"] +} +::: +"; + let ctx = LintContext::new(content, MarkdownFlavor::MyST, None); + let result = rule.check(&ctx).unwrap(); + assert!( + result.is_empty(), + "URL argument on a MyST colon directive opener must not be flagged: {result:?}" + ); + } + + /// A nested MyST colon directive opener also carries an opaque argument. + #[test] + fn test_myst_nested_colon_directive_argument_url_not_flagged() { + use crate::config::MarkdownFlavor; + use crate::lint_context::LintContext; + let rule = MD034NoBareUrls; + let content = "\ +::::{grid} +:::{card} https://example.com/card-target +Some caption. +::: +:::: +"; + let ctx = LintContext::new(content, MarkdownFlavor::MyST, None); + let result = rule.check(&ctx).unwrap(); + assert!( + result.is_empty(), + "URL argument on a nested MyST colon directive opener must not be flagged: {result:?}" + ); + } + + /// A bare URL in the *body* of a content directive (e.g. `{note}`) is genuine + /// prose and must still be flagged. The opener exemption must not leak to the body. + #[test] + fn test_myst_directive_body_url_still_flagged() { + use crate::config::MarkdownFlavor; + use crate::lint_context::LintContext; + let rule = MD034NoBareUrls; + let content = "\ +:::{note} +See https://example.com/docs for more details. +::: +"; + let ctx = LintContext::new(content, MarkdownFlavor::MyST, None); + let result = rule.check(&ctx).unwrap(); + assert_eq!( + result.len(), + 1, + "Bare URL in a MyST directive body must still be flagged: {result:?}" + ); + } + + /// An unclosed colon directive (no terminating `:::`) still has its opener + /// argument treated as opaque: the URL must not be flagged. + #[test] + fn test_myst_unclosed_colon_directive_argument_url_not_flagged() { + use crate::config::MarkdownFlavor; + use crate::lint_context::LintContext; + let rule = MD034NoBareUrls; + let content = "\ +:::{anywidget} https://example.com/widget.mjs +Some trailing content with no closing fence. +"; + let ctx = LintContext::new(content, MarkdownFlavor::MyST, None); + let result = rule.check(&ctx).unwrap(); + assert!( + result.is_empty(), + "URL argument on an unclosed MyST colon directive opener must not be flagged: {result:?}" + ); + } + + /// The colon-directive exemption is MyST-specific: under the Standard flavor a + /// `:::{...}` line is ordinary text and a bare URL on it must still be flagged. + #[test] + fn test_colon_directive_url_flagged_in_standard_flavor() { + use crate::config::MarkdownFlavor; + use crate::lint_context::LintContext; + let rule = MD034NoBareUrls; + let content = ":::{anywidget} https://example.com/widget.mjs\n"; + let ctx = LintContext::new(content, MarkdownFlavor::Standard, None); + let result = rule.check(&ctx).unwrap(); + assert_eq!( + result.len(), + 1, + "Under Standard flavor a bare URL on a `:::` line must still be flagged: {result:?}" + ); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/src/rules/md060_table_format/md060_config.rs new/rumdl-0.2.4/src/rules/md060_table_format/md060_config.rs --- old/rumdl-0.2.3/src/rules/md060_table_format/md060_config.rs 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/src/rules/md060_table_format/md060_config.rs 2026-05-29 21:11:01.000000000 +0200 @@ -166,7 +166,9 @@ /// body rows remain compact/tight and are not padded. /// /// No effect under `aligned` / `aligned-no-space` (those styles already - /// align the delimiter row by construction). + /// align the delimiter row by construction), except when a table exceeds + /// `max-width` and auto-compacts: the effective output style is then + /// `compact`, so the delimiter row is aligned to the header column widths. /// /// Mirrors markdownlint MD060's `aligned_delimiter` option; the snake_case /// alias is accepted for cross-tool compatibility. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/src/rules/md060_table_format.rs new/rumdl-0.2.4/src/rules/md060_table_format.rs --- old/rumdl-0.2.3/src/rules/md060_table_format.rs 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/src/rules/md060_table_format.rs 2026-05-29 21:11:01.000000000 +0200 @@ -899,11 +899,27 @@ let calc_aligned_width = 1 + (num_columns * 3) + column_widths.iter().sum::<usize>(); aligned_width = Some(calc_aligned_width); - // Auto-compact: if aligned table exceeds max width, use compact formatting instead + // Auto-compact: if aligned table exceeds max width, use compact formatting instead. + // The effective output style is now `compact`, so honor `aligned-delimiter` + // exactly as the explicit `compact` style does: align the delimiter row's pipes + // to the header column widths while body rows stay compact. if calc_aligned_width > self.effective_max_width() { auto_compacted = true; - for line in &stripped_lines { + let header_widths = if self.config.aligned_delimiter && stripped_lines.len() >= 2 { + let header_cells = Self::parse_table_row_with_flavor(stripped_lines[0], flavor); + Some(Self::header_cell_widths(&header_cells)) + } else { + None + }; + for (row_idx, line) in stripped_lines.iter().enumerate() { let cells = Self::parse_table_row_with_flavor(line, flavor); + if row_idx == 1 + && let Some(widths) = &header_widths + { + // Auto-compact always produces the single-space compact form. + result.push(Self::format_delimiter_aligned_to_header(&cells, widths, true)); + continue; + } result.push(Self::format_table_compact(&cells)); } } else { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/tests/regressions/embedded_markdown_fix_gate_issue_643_test.rs new/rumdl-0.2.4/tests/regressions/embedded_markdown_fix_gate_issue_643_test.rs --- old/rumdl-0.2.3/tests/regressions/embedded_markdown_fix_gate_issue_643_test.rs 1970-01-01 01:00:00.000000000 +0100 +++ new/rumdl-0.2.4/tests/regressions/embedded_markdown_fix_gate_issue_643_test.rs 2026-05-29 21:11:01.000000000 +0200 @@ -0,0 +1,153 @@ +//! Regression tests: `--fix` must not rewrite the contents of a ` ```markdown ` +//! code block unless embedded markdown linting is explicitly opted into via +//! code-block-tools. +//! +//! Embedded markdown linting is documented as opt-in (the special `rumdl` tool in +//! `[code-block-tools.languages.markdown] lint = ["rumdl"]`). The check path +//! honored that gate, but the fix path ran the recursive embedded formatter +//! unconditionally, so `rumdl check --fix` silently rewrote code-block content +//! that `rumdl check` never reported. For MyST directive blocks this mangled the +//! directive (MD046 converting the fence to an indented block, MD040 injecting a +//! `text` language), corrupting content the user wrote verbatim inside a fence. +//! +//! These tests run the real binary end-to-end so they exercise the exact fix +//! coordinator + embedded gating used in production. + +use std::fs; +use std::process::Command; +use tempfile::tempdir; + +/// Run `rumdl check --fix` on a file containing `markdown`, using `config` as the +/// sole configuration source, and return the file contents after the fix. The +/// config is passed via `--config` and the working directory is the temp dir so +/// the project's own config never leaks in. `--no-cache` keeps the result +/// deterministic across repeated runs. +fn fix_with_config(markdown: &str, config: &str) -> String { + let dir = tempdir().unwrap(); + let file_path = dir.path().join("doc.md"); + let config_path = dir.path().join(".rumdl.toml"); + fs::write(&file_path, markdown).unwrap(); + fs::write(&config_path, config).unwrap(); + + let output = Command::new(env!("CARGO_BIN_EXE_rumdl")) + .current_dir(dir.path()) + .arg("check") + .arg("--fix") + .arg("--no-cache") + .arg("--config") + .arg(&config_path) + .arg(&file_path) + .output() + .expect("Failed to execute rumdl"); + + let exit_code = output.status.code().unwrap_or(-1); + assert_ne!( + exit_code, + 2, + "rumdl errored (exit 2)\nstdout: {}\nstderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + + fs::read_to_string(&file_path).unwrap() +} + +/// The exact reproduction from the issue: a ` ````markdown ` block containing a +/// MyST ` ```{eval-rst} ` directive, under MyST flavor with reflow enabled. +/// Without opting into embedded markdown linting, the block must be untouched. +#[test] +fn eval_rst_directive_in_markdown_block_unchanged_without_optin() { + let markdown = "````markdown\n```{eval-rst}\n.. autofunction:: example.refraction.snell\n :noindex:\n :toctree: generated\n```\n````\n"; + + let config = r#" +[global] +flavor = "myst" +disable = ["MD031", "MD057", "MD059"] + +[MD013] +reflow = true +code-blocks = false +tables = false +"#; + + let fixed = fix_with_config(markdown, config); + assert_eq!( + fixed, markdown, + "embedded MyST directive must be left byte-for-byte unchanged when embedded markdown linting is not enabled" + ); +} + +/// A plain ` ```markdown ` block with an obvious fixable issue inside +/// (MD018: no space after `#`). Without opt-in the inner content must not be +/// touched, so users can show intentionally "broken" markdown examples in docs. +#[test] +fn markdown_block_content_unchanged_without_optin() { + let markdown = "# Doc\n\n```markdown\n#Heading without space\n```\n"; + + let config = "[global]\nflavor = \"standard\"\n"; + + let fixed = fix_with_config(markdown, config); + assert_eq!( + fixed, markdown, + "markdown code block content must not be rewritten when embedded markdown linting is not enabled" + ); +} + +/// When embedded markdown linting IS opted into, the recursive formatter runs and +/// fixes the inner content. This proves the gate (not a blanket disable) is the +/// deciding factor, keeping the documented feature working. +#[test] +fn markdown_block_content_fixed_with_optin() { + let markdown = "# Doc\n\n```markdown\n#Heading without space\n```\n"; + + let config = r#" +[global] +flavor = "standard" + +[code-block-tools] +enabled = true + +[code-block-tools.languages.markdown] +lint = ["rumdl"] +"#; + + let fixed = fix_with_config(markdown, config); + assert!( + fixed.contains("# Heading without space"), + "embedded markdown should be formatted when opted in via code-block-tools, got:\n{fixed}" + ); + assert_ne!(fixed, markdown, "opt-in run should have changed the embedded content"); +} + +/// Opt-in must not corrupt a MyST directive. Uses the reporter's full config +/// (MyST flavor + reflow + the same disabled rules) so the embedded sub-lint runs +/// every rule that previously mangled the directive: MD046 converting the fence +/// to an indented block, and MD040 injecting a `text` language. The directive, +/// including its multi-line option body, must survive byte-for-byte. +#[test] +fn eval_rst_directive_preserved_under_myst_with_optin() { + let markdown = "````markdown\n```{eval-rst}\n.. autofunction:: example.refraction.snell\n :noindex:\n :toctree: generated\n```\n````\n"; + + let config = r#" +[global] +flavor = "myst" +disable = ["MD031", "MD057", "MD059"] + +[MD013] +reflow = true +code-blocks = false +tables = false + +[code-block-tools] +enabled = true + +[code-block-tools.languages.markdown] +lint = ["rumdl"] +"#; + + let fixed = fix_with_config(markdown, config); + assert_eq!( + fixed, markdown, + "MyST directive (fence + indented option body) must be preserved even with embedded linting enabled, got:\n{fixed}" + ); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/tests/regressions/mod.rs new/rumdl-0.2.4/tests/regressions/mod.rs --- old/rumdl-0.2.3/tests/regressions/mod.rs 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/tests/regressions/mod.rs 2026-05-29 21:11:01.000000000 +0200 @@ -1,5 +1,6 @@ mod code_block_blockquote_edge_cases; mod consistency_regression_tests; +mod embedded_markdown_fix_gate_issue_643_test; mod escaped_brackets_test; mod final_confidence_assessment; mod html_comments_test; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/tests/rules/md046_test.rs new/rumdl-0.2.4/tests/rules/md046_test.rs --- old/rumdl-0.2.3/tests/rules/md046_test.rs 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/tests/rules/md046_test.rs 2026-05-29 21:11:01.000000000 +0200 @@ -920,3 +920,43 @@ "The inner ``` should be preserved as indented content, got:\n{fixed}" ); } + +#[test] +fn test_myst_eval_rst_directive_body_not_indented_block() { + // A fenced MyST directive (`{eval-rst}`) is parsed as a code block whose body + // is opaque reStructuredText. Its indented option lines (`:noindex:`, + // `:toctree:`) must NOT be counted as a standalone indented code block, so + // under the default consistent style MD046 reports nothing and leaves the + // directive untouched. + let rule = MD046CodeBlockStyle::new(CodeBlockStyle::Consistent); + let content = + "```{eval-rst}\n.. autofunction:: example.refraction.snell\n :noindex:\n :toctree: generated\n```\n"; + let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::MyST, None); + + let warnings = rule.check(&ctx).unwrap(); + assert!( + warnings.is_empty(), + "MD046 must not flag a MyST directive's indented body as an indented code block, got: {warnings:?}" + ); + + let fixed = rule.fix(&ctx).unwrap(); + assert_eq!( + fixed, content, + "MD046 must not rewrite a MyST directive body, got:\n{fixed}" + ); +} + +#[test] +fn test_myst_directive_body_not_indented_block_with_preceding_heading() { + // Same as above but with content before the directive, guarding both the + // first-block and later-block code paths. + let rule = MD046CodeBlockStyle::new(CodeBlockStyle::Consistent); + let content = "# Title\n\n```{eval-rst}\n.. autofunction:: example.refraction.snell\n :noindex:\n :toctree: generated\n```\n"; + let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::MyST, None); + + let warnings = rule.check(&ctx).unwrap(); + assert!( + warnings.is_empty(), + "MD046 must not flag a MyST directive body regardless of position, got: {warnings:?}" + ); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rumdl-0.2.3/tests/rules/md060_test.rs new/rumdl-0.2.4/tests/rules/md060_test.rs --- old/rumdl-0.2.3/tests/rules/md060_test.rs 2026-05-27 21:28:16.000000000 +0200 +++ new/rumdl-0.2.4/tests/rules/md060_test.rs 2026-05-29 21:11:01.000000000 +0200 @@ -3523,6 +3523,80 @@ ); } +fn md060_aligned_config_with_max_width(aligned_delimiter: bool, max_width: usize) -> MD060Config { + MD060Config { + enabled: true, + style: "aligned".to_string(), + max_width: LineLength::from_const(max_width), + column_align: ColumnAlign::Auto, + column_align_header: None, + column_align_body: None, + loose_last_column: false, + aligned_delimiter, + } +} + +#[test] +fn test_md060_aligned_autocompact_honors_aligned_delimiter() { + // #646: when an `aligned` table exceeds max-width it auto-compacts. The effective output + // style is then `compact`, so `aligned-delimiter = true` must align the delimiter row's + // pipes to the header column widths, exactly as `style = "compact"` would. + let config = md060_aligned_config_with_max_width(true, 40); + let rule = MD060TableFormat::from_config_struct(config, default_md013_config(), false); + + let content = "| ID | Description |\n| --- | --- |\n| 1 | A very long description that causes the table to exceed the configured maximum width |\n| 2 | Another long description that keeps the table in compact mode |"; + let ctx = LintContext::new(content, MarkdownFlavor::Standard, None); + + let fixed = rule.fix(&ctx).unwrap(); + // "ID" = 2 cols → 2 dashes; "Description" = 11 cols → 11 dashes. Body rows stay compact. + let expected = "| ID | Description |\n| -- | ----------- |\n| 1 | A very long description that causes the table to exceed the configured maximum width |\n| 2 | Another long description that keeps the table in compact mode |"; + assert_eq!( + fixed, expected, + "auto-compacted aligned table must align the delimiter row to header widths when aligned-delimiter is set" + ); +} + +#[test] +fn test_md060_aligned_autocompact_without_aligned_delimiter_keeps_minimal_dashes() { + // Default (aligned-delimiter = false): auto-compact keeps the minimal compact delimiter. + let config = md060_aligned_config_with_max_width(false, 40); + let rule = MD060TableFormat::from_config_struct(config, default_md013_config(), false); + + let content = "| ID | Description |\n| --- | --- |\n| 1 | A very long description that causes the table to exceed the configured maximum width |\n| 2 | Another long description that keeps the table in compact mode |"; + let ctx = LintContext::new(content, MarkdownFlavor::Standard, None); + + let fixed = rule.fix(&ctx).unwrap(); + let expected = "| ID | Description |\n| --- | --- |\n| 1 | A very long description that causes the table to exceed the configured maximum width |\n| 2 | Another long description that keeps the table in compact mode |"; + assert_eq!( + fixed, expected, + "without aligned-delimiter the auto-compacted delimiter row keeps minimal dashes (no regression)" + ); +} + +#[test] +fn test_md060_aligned_autocompact_aligned_delimiter_idempotent() { + // Running the fix twice on an auto-compacted + aligned-delimiter table must be stable. + let config = md060_aligned_config_with_max_width(true, 40); + let rule = MD060TableFormat::from_config_struct(config, default_md013_config(), false); + + let content = "| ID | Description |\n| --- | --- |\n| 1 | A very long description that causes the table to exceed the configured maximum width |\n| 2 | Another long description that keeps the table in compact mode |"; + let ctx = LintContext::new(content, MarkdownFlavor::Standard, None); + let fixed_once = rule.fix(&ctx).unwrap(); + + let ctx2 = LintContext::new(&fixed_once, MarkdownFlavor::Standard, None); + let fixed_twice = rule.fix(&ctx2).unwrap(); + + assert_eq!( + fixed_once, fixed_twice, + "auto-compact + aligned-delimiter fix must be idempotent" + ); + let warnings = rule.check(&ctx2).unwrap(); + assert!( + warnings.is_empty(), + "Already-correct auto-compacted aligned-delimiter table should produce no warnings, got: {warnings:?}" + ); +} + #[test] fn test_md060_compact_accepts_mdformat_empty_cell() { // mdformat writes empty compact cells as `| |` (single space between pipes). ++++++ rumdl.obsinfo ++++++ --- /var/tmp/diff_new_pack.EWBysp/_old 2026-05-30 23:01:35.928808235 +0200 +++ /var/tmp/diff_new_pack.EWBysp/_new 2026-05-30 23:01:35.932808399 +0200 @@ -1,5 +1,5 @@ name: rumdl -version: 0.2.3 -mtime: 1779910096 -commit: a5a0c2d5d6d631393c5ca773872742726d4fa4db +version: 0.2.4 +mtime: 1780081861 +commit: a0d57ac276a15c3697a27cfe53f46e7742c341b9 ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/rumdl/vendor.tar.zst /work/SRC/openSUSE:Factory/.rumdl.new.1937/vendor.tar.zst differ: char 7722, line 46
