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-20 15:25:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rumdl (Old)
 and      /work/SRC/openSUSE:Factory/.rumdl.new.1966 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rumdl"

Wed May 20 15:25:36 2026 rev:68 rq:1354149 version:0.1.95

Changes:
--------
--- /work/SRC/openSUSE:Factory/rumdl/rumdl.changes      2026-05-18 
17:50:34.682654285 +0200
+++ /work/SRC/openSUSE:Factory/.rumdl.new.1966/rumdl.changes    2026-05-20 
15:26:52.043939781 +0200
@@ -1,0 +2,13 @@
+Wed May 20 05:08:48 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.1.95:
+  * Added
+    - md010: add code_blocks config option, consistent code-block
+      handling (#630) (b98ca52)
+    - md010: add code_blocks config option (default false)
+      (2c95e17)
+  * Fixed
+    - md010: treat fenced and indented code blocks consistently
+      (#630) (435df34)
+
+-------------------------------------------------------------------

Old:
----
  rumdl-0.1.94.obscpio

New:
----
  rumdl-0.1.95.obscpio

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

Other differences:
------------------
++++++ rumdl.spec ++++++
--- /var/tmp/diff_new_pack.RCk5Qs/_old  2026-05-20 15:26:53.604004059 +0200
+++ /var/tmp/diff_new_pack.RCk5Qs/_new  2026-05-20 15:26:53.608004224 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           rumdl
-Version:        0.1.94
+Version:        0.1.95
 Release:        0
 Summary:        Markdown Linter written in Rust
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.RCk5Qs/_old  2026-05-20 15:26:53.644005707 +0200
+++ /var/tmp/diff_new_pack.RCk5Qs/_new  2026-05-20 15:26:53.648005872 +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.1.94</param>
+    <param name="revision">v0.1.95</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.RCk5Qs/_old  2026-05-20 15:26:53.676007025 +0200
+++ /var/tmp/diff_new_pack.RCk5Qs/_new  2026-05-20 15:26:53.680007190 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/rvben/rumdl.git</param>
-              <param 
name="changesrevision">fbb90ac101470d2c249e3bc5d9a8915de477f3e4</param></service></servicedata>
+              <param 
name="changesrevision">b2164bb1b33cbc3b416f686180033b8be7374f37</param></service></servicedata>
 (No newline at EOF)
 

++++++ rumdl-0.1.94.obscpio -> rumdl-0.1.95.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/CHANGELOG.md 
new/rumdl-0.1.95/CHANGELOG.md
--- old/rumdl-0.1.94/CHANGELOG.md       2026-05-18 12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/CHANGELOG.md       2026-05-19 22:15:04.000000000 +0200
@@ -37,6 +37,18 @@
 
 
 
+
+## [0.1.95](https://github.com/rvben/rumdl/compare/v0.1.94...v0.1.95) - 
2026-05-19
+
+### Added
+
+- **md010**: add code_blocks config option, consistent code-block handling 
(#630) 
([b98ca52](https://github.com/rvben/rumdl/commit/b98ca52b73cecd923c03ce98f505a5afefe20435))
+- **md010**: add code_blocks config option (default false) 
([2c95e17](https://github.com/rvben/rumdl/commit/2c95e1786a1aaf63af8c767f57b469c146982ae7))
+
+### Fixed
+
+- **md010**: treat fenced and indented code blocks consistently (#630) 
([435df34](https://github.com/rvben/rumdl/commit/435df34db542402a2e2f07db12308391bf41a032))
+
 ## [0.1.94](https://github.com/rvben/rumdl/compare/v0.1.93...v0.1.94) - 
2026-05-18
 
 ### Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/Cargo.lock new/rumdl-0.1.95/Cargo.lock
--- old/rumdl-0.1.94/Cargo.lock 2026-05-18 12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/Cargo.lock 2026-05-19 22:15:04.000000000 +0200
@@ -2274,7 +2274,7 @@
 
 [[package]]
 name = "rumdl"
-version = "0.1.94"
+version = "0.1.95"
 dependencies = [
  "anyhow",
  "assert_cmd",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/Cargo.toml new/rumdl-0.1.95/Cargo.toml
--- old/rumdl-0.1.94/Cargo.toml 2026-05-18 12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/Cargo.toml 2026-05-19 22:15:04.000000000 +0200
@@ -1,6 +1,6 @@
 [package]
 name = "rumdl"
-version = "0.1.94"
+version = "0.1.95"
 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.94/README.md new/rumdl-0.1.95/README.md
--- old/rumdl-0.1.94/README.md  2026-05-18 12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/README.md  2026-05-19 22:15:04.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.1.94
+    rev: v0.1.95
     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.1.94
+    rev: v0.1.95
     hooks:
       - id: rumdl
         args: [--no-exclude]  # Disable all exclude patterns
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/docs/global-settings.md 
new/rumdl-0.1.95/docs/global-settings.md
--- old/rumdl-0.1.94/docs/global-settings.md    2026-05-18 12:11:42.000000000 
+0200
+++ new/rumdl-0.1.95/docs/global-settings.md    2026-05-19 22:15:04.000000000 
+0200
@@ -1342,7 +1342,7 @@
 
 ```yaml
 - repo: https://github.com/rvben/rumdl-pre-commit
-  rev: v0.1.94
+  rev: v0.1.95
   hooks:
     - id: rumdl
       args: [--config=.rumdl.toml]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/docs/markdownlint-comparison.md 
new/rumdl-0.1.95/docs/markdownlint-comparison.md
--- old/rumdl-0.1.94/docs/markdownlint-comparison.md    2026-05-18 
12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/docs/markdownlint-comparison.md    2026-05-19 
22:15:04.000000000 +0200
@@ -189,6 +189,24 @@
 
 markdownlint does not have built-in flavor support; users must configure 
individual rules manually.
 
+### 7. Rule-Specific Default Differences
+
+A few rules ship safer defaults than markdownlint. These are opt-out, not 
removed - set the documented option to recover markdownlint-exact behavior.
+
+**MD010 (Hard tabs) - `code_blocks`:**
+
+- **markdownlint**: defaults `code_blocks: true`, flagging and rewriting tabs 
inside fenced *and* indented code blocks.
+- **rumdl**: defaults `code-blocks = false`, skipping tabs inside both fenced 
and indented code blocks.
+- **Rationale**: tabs are syntactically required in Makefiles and conventional 
in `gofmt`-formatted Go. markdownlint's default silently corrupts such snippets 
on auto-fix. Skipping code blocks by default is strictly safer.
+- **markdownlint parity**: set `code-blocks = true` to flag tabs everywhere, 
including code blocks.
+
+```toml
+[MD010]
+code-blocks = true  # markdownlint-exact behavior
+```
+
+**Reference:** [rumdl Issue #630](https://github.com/rvben/rumdl/issues/630) - 
inconsistent tab handling between fenced and indented code blocks.
+
 ## Configuration Compatibility
 
 ### Markdownlint Config Auto-Detection
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/docs/md010.md 
new/rumdl-0.1.95/docs/md010.md
--- old/rumdl-0.1.94/docs/md010.md      2026-05-18 12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/docs/md010.md      2026-05-19 22:15:04.000000000 +0200
@@ -63,12 +63,24 @@
 
 ```toml
 [MD010]
-spaces-per-tab = 4  # Number of spaces to replace each tab with (default: 4)
+spaces-per-tab = 4    # Number of spaces to replace each tab with (default: 4)
+code-blocks = false   # Skip tabs inside code blocks (default: false)
 ```
 
 ### Configuration options explained
 
-- `spaces-per-tab`: How many spaces to use when replacing each tab character
+- `spaces-per-tab`: How many spaces to use when replacing each tab character.
+- `code-blocks`: When `false` (default), hard tabs inside fenced and indented
+  code blocks are skipped - tabs are often required there (Makefiles, Go) and
+  rewriting them would corrupt examples. Set to `true` for markdownlint-parity
+  behavior that flags tabs everywhere, including code blocks.
+
+> **Behavior change:** Earlier versions skipped tabs in fenced code blocks
+> while still flagging indented ones. MD010 now treats both code-block types
+> consistently and, by default, does not flag tabs in either. Note that a
+> document whose content starts with a tab-indented line is a CommonMark
+> indented code block, so leading tabs there are no longer flagged by default.
+> Set `code-blocks = true` to flag tabs in all code blocks.
 
 ## Automatic fixes
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/npm/cli-darwin-arm64/package.json 
new/rumdl-0.1.95/npm/cli-darwin-arm64/package.json
--- old/rumdl-0.1.94/npm/cli-darwin-arm64/package.json  2026-05-18 
12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/npm/cli-darwin-arm64/package.json  2026-05-19 
22:15:04.000000000 +0200
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-darwin-arm64",
-  "version": "0.1.94",
+  "version": "0.1.95",
   "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.94/npm/cli-darwin-x64/package.json 
new/rumdl-0.1.95/npm/cli-darwin-x64/package.json
--- old/rumdl-0.1.94/npm/cli-darwin-x64/package.json    2026-05-18 
12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/npm/cli-darwin-x64/package.json    2026-05-19 
22:15:04.000000000 +0200
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-darwin-x64",
-  "version": "0.1.94",
+  "version": "0.1.95",
   "description": "rumdl binary for macOS x64 (Intel)",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/npm/cli-linux-arm64/package.json 
new/rumdl-0.1.95/npm/cli-linux-arm64/package.json
--- old/rumdl-0.1.94/npm/cli-linux-arm64/package.json   2026-05-18 
12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/npm/cli-linux-arm64/package.json   2026-05-19 
22:15:04.000000000 +0200
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-linux-arm64",
-  "version": "0.1.94",
+  "version": "0.1.95",
   "description": "rumdl binary for Linux ARM64 (glibc)",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/npm/cli-linux-arm64-musl/package.json 
new/rumdl-0.1.95/npm/cli-linux-arm64-musl/package.json
--- old/rumdl-0.1.94/npm/cli-linux-arm64-musl/package.json      2026-05-18 
12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/npm/cli-linux-arm64-musl/package.json      2026-05-19 
22:15:04.000000000 +0200
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-linux-arm64-musl",
-  "version": "0.1.94",
+  "version": "0.1.95",
   "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.94/npm/cli-linux-x64/package.json 
new/rumdl-0.1.95/npm/cli-linux-x64/package.json
--- old/rumdl-0.1.94/npm/cli-linux-x64/package.json     2026-05-18 
12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/npm/cli-linux-x64/package.json     2026-05-19 
22:15:04.000000000 +0200
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-linux-x64",
-  "version": "0.1.94",
+  "version": "0.1.95",
   "description": "rumdl binary for Linux x64 (glibc)",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/npm/cli-linux-x64-musl/package.json 
new/rumdl-0.1.95/npm/cli-linux-x64-musl/package.json
--- old/rumdl-0.1.94/npm/cli-linux-x64-musl/package.json        2026-05-18 
12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/npm/cli-linux-x64-musl/package.json        2026-05-19 
22:15:04.000000000 +0200
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-linux-x64-musl",
-  "version": "0.1.94",
+  "version": "0.1.95",
   "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.94/npm/cli-win32-x64/package.json 
new/rumdl-0.1.95/npm/cli-win32-x64/package.json
--- old/rumdl-0.1.94/npm/cli-win32-x64/package.json     2026-05-18 
12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/npm/cli-win32-x64/package.json     2026-05-19 
22:15:04.000000000 +0200
@@ -1,6 +1,6 @@
 {
   "name": "@rumdl/cli-win32-x64",
-  "version": "0.1.94",
+  "version": "0.1.95",
   "description": "rumdl binary for Windows x64",
   "license": "MIT",
   "repository": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/npm/rumdl/package.json 
new/rumdl-0.1.95/npm/rumdl/package.json
--- old/rumdl-0.1.94/npm/rumdl/package.json     2026-05-18 12:11:42.000000000 
+0200
+++ new/rumdl-0.1.95/npm/rumdl/package.json     2026-05-19 22:15:04.000000000 
+0200
@@ -1,6 +1,6 @@
 {
   "name": "rumdl",
-  "version": "0.1.94",
+  "version": "0.1.95",
   "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.94",
-    "@rumdl/cli-darwin-arm64": "0.1.94",
-    "@rumdl/cli-linux-x64": "0.1.94",
-    "@rumdl/cli-linux-arm64": "0.1.94",
-    "@rumdl/cli-linux-x64-musl": "0.1.94",
-    "@rumdl/cli-linux-arm64-musl": "0.1.94",
-    "@rumdl/cli-win32-x64": "0.1.94"
+    "@rumdl/cli-darwin-x64": "0.1.95",
+    "@rumdl/cli-darwin-arm64": "0.1.95",
+    "@rumdl/cli-linux-x64": "0.1.95",
+    "@rumdl/cli-linux-arm64": "0.1.95",
+    "@rumdl/cli-linux-x64-musl": "0.1.95",
+    "@rumdl/cli-linux-arm64-musl": "0.1.95",
+    "@rumdl/cli-win32-x64": "0.1.95"
   }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/src/lsp/tests.rs 
new/rumdl-0.1.95/src/lsp/tests.rs
--- old/rumdl-0.1.94/src/lsp/tests.rs   2026-05-18 12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/src/lsp/tests.rs   2026-05-19 22:15:04.000000000 +0200
@@ -138,8 +138,10 @@
     let server = create_test_server();
 
     let uri = Url::parse("file:///test.md").unwrap();
-    // Line 2 and 3 have hard tabs (MD010, fixable), range only covers line 0
-    let text = "# Test\n\n\tThis is a test\n\tWith tabs\n";
+    // Lines 2 and 3 (0-indexed LSP line) have hard tabs (MD010, fixable), 
range only covers line 0.
+    // Tabs are placed mid-line in paragraph text (not at column 0 after a 
blank line,
+    // which would be an indented code block skipped by default with 
code_blocks=false).
+    let text = "# Test\n\nThis is\ta test\nWith\ttabs\n";
 
     // Range that doesn't cover the violations (line 0 only)
     let range = Range {
@@ -4446,10 +4448,12 @@
     let server = create_test_server();
 
     let uri = Url::parse("file:///test.md").unwrap();
-    // Line 0: "# Title"       -- no fixable issues here
-    // Line 1: ""              -- blank line
-    // Line 2: "\tTabbed text" -- hard tab (MD010, fixable)
-    let text = "# Title\n\n\tTabbed text\n";
+    // Line 0: "# Title"             -- no fixable issues here
+    // Line 1: ""                    -- blank line
+    // Line 2: "Tabbed\ttext"        -- hard tab (MD010, fixable), mid-line in 
paragraph
+    // Tab placed mid-line to keep it out of an indented code block position
+    // (column-0 tab after blank line would be skipped with code_blocks=false).
+    let text = "# Title\n\nTabbed\ttext\n";
 
     // Narrow range: only line 0 (where Zed cursor might be)
     let narrow_range = Range {
@@ -4496,10 +4500,12 @@
     let server = create_test_server();
 
     let uri = Url::parse("file:///test.md").unwrap();
-    // Two fixable issues on different lines (hard tabs → MD010):
-    // Line 2: "\tFirst issue"
-    // Line 4: "\tSecond issue"
-    let text = "# Title\n\n\tFirst issue\n\n\tSecond issue\n";
+    // Two fixable issues on different lines (hard tabs -> MD010).
+    // Tabs placed mid-line in paragraph text to avoid being treated as
+    // indented code blocks (column-0 tab after blank line is skipped with 
code_blocks=false).
+    // Line 2 (0-indexed LSP line): "First\tissue"
+    // Line 3 (0-indexed LSP line): "Second\tissue"
+    let text = "# Title\n\nFirst\tissue\nSecond\tissue\n";
 
     // Range covering only line 2 (first issue)
     let partial_range = Range {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/rumdl-0.1.94/src/rules/md010_no_hard_tabs/md010_config.rs 
new/rumdl-0.1.95/src/rules/md010_no_hard_tabs/md010_config.rs
--- old/rumdl-0.1.94/src/rules/md010_no_hard_tabs/md010_config.rs       
2026-05-18 12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/src/rules/md010_no_hard_tabs/md010_config.rs       
2026-05-19 22:15:04.000000000 +0200
@@ -9,16 +9,26 @@
     /// Number of spaces per tab (default: 4)
     #[serde(default = "default_spaces_per_tab", alias = "spaces_per_tab")]
     pub spaces_per_tab: PositiveUsize,
+
+    /// Check for hard tabs inside code blocks (default: false).
+    /// When false, tabs inside fenced and indented code blocks are skipped.
+    #[serde(default = "default_code_blocks", alias = "code_blocks")]
+    pub code_blocks: bool,
 }
 
 fn default_spaces_per_tab() -> PositiveUsize {
     PositiveUsize::from_const(4)
 }
 
+fn default_code_blocks() -> bool {
+    false
+}
+
 impl Default for MD010Config {
     fn default() -> Self {
         Self {
             spaces_per_tab: default_spaces_per_tab(),
+            code_blocks: default_code_blocks(),
         }
     }
 }
@@ -66,4 +76,23 @@
         let err = result.unwrap_err().to_string();
         assert!(err.contains("must be at least 1") || err.contains("got 0"));
     }
+
+    #[test]
+    fn test_code_blocks_defaults_false() {
+        let config = MD010Config::default();
+        assert!(!config.code_blocks, "MD010 defaults to skipping code blocks");
+    }
+
+    #[test]
+    fn test_code_blocks_kebab_case() {
+        let config: MD010Config = toml::from_str("code-blocks = 
true\n").unwrap();
+        assert!(config.code_blocks);
+        assert_eq!(config.spaces_per_tab.get(), 4);
+    }
+
+    #[test]
+    fn test_code_blocks_snake_case_alias() {
+        let config: MD010Config = toml::from_str("code_blocks = 
true\n").unwrap();
+        assert!(config.code_blocks);
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/src/rules/md010_no_hard_tabs.rs 
new/rumdl-0.1.95/src/rules/md010_no_hard_tabs.rs
--- old/rumdl-0.1.94/src/rules/md010_no_hard_tabs.rs    2026-05-18 
12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/src/rules/md010_no_hard_tabs.rs    2026-05-19 
22:15:04.000000000 +0200
@@ -5,8 +5,8 @@
 /// See [docs/md010.md](../../docs/md010.md) for full documentation, 
configuration, and examples.
 use crate::utils::range_utils::calculate_match_range;
 
-mod md010_config;
-use md010_config::MD010Config;
+pub mod md010_config;
+pub use md010_config::MD010Config;
 
 /// Rule MD010: Hard tabs
 #[derive(Clone, Default)]
@@ -19,6 +19,7 @@
         Self {
             config: MD010Config {
                 spaces_per_tab: 
crate::types::PositiveUsize::from_const(spaces_per_tab),
+                code_blocks: false,
             },
         }
     }
@@ -27,52 +28,6 @@
         Self { config }
     }
 
-    /// Detect which lines are inside fenced code blocks (``` or ~~~).
-    /// Only fenced code blocks are skipped — indented code blocks (4+ spaces 
/ tab)
-    /// are NOT skipped because the tabs themselves are what MD010 should flag.
-    fn find_fenced_code_block_lines(lines: &[&str]) -> Vec<bool> {
-        let mut in_fenced_block = false;
-        let mut fence_char: Option<char> = None;
-        let mut fence_len: usize = 0;
-        let mut result = vec![false; lines.len()];
-
-        for (i, line) in lines.iter().enumerate() {
-            let trimmed = line.trim_start();
-
-            if !in_fenced_block {
-                // Check for opening fence (3+ backticks or tildes)
-                let first_char = trimmed.chars().next();
-                if matches!(first_char, Some('`') | Some('~')) {
-                    let fc = first_char.unwrap();
-                    let count = trimmed.chars().take_while(|&c| c == 
fc).count();
-                    if count >= 3 {
-                        in_fenced_block = true;
-                        fence_char = Some(fc);
-                        fence_len = count;
-                        result[i] = true;
-                    }
-                }
-            } else {
-                result[i] = true;
-                // Check for closing fence (must match opening fence char and 
be >= opening length)
-                if let Some(fc) = fence_char {
-                    let first = trimmed.chars().next();
-                    if first == Some(fc) {
-                        let count = trimmed.chars().take_while(|&c| c == 
fc).count();
-                        // Closing fence must be at least as long as opening, 
with nothing else on the line
-                        if count >= fence_len && 
trimmed[count..].trim().is_empty() {
-                            in_fenced_block = false;
-                            fence_char = None;
-                            fence_len = 0;
-                        }
-                    }
-                }
-            }
-        }
-
-        result
-    }
-
     fn count_leading_tabs(line: &str) -> usize {
         let mut count = 0;
         for c in line.chars() {
@@ -135,13 +90,12 @@
         let mut warnings = Vec::new();
         let lines = ctx.raw_lines();
 
-        // Track fenced code blocks separately — we skip FENCED blocks but NOT
-        // indented code blocks (since tab indentation IS what MD010 should 
flag)
-        let fenced_lines = Self::find_fenced_code_block_lines(lines);
+        // When `code_blocks` is false (the default), skip tabs inside ANY 
code block -
+        // fenced and indented alike - using the shared spec-compliant flag.
+        let skip_code_blocks = !self.config.code_blocks;
 
         for (line_num, &line) in lines.iter().enumerate() {
-            // Skip fenced code blocks (code has its own formatting rules)
-            if fenced_lines[line_num] {
+            if skip_code_blocks && ctx.line_info(line_num + 
1).is_some_and(|info| info.in_code_block) {
                 continue;
             }
 
@@ -298,43 +252,101 @@
     }
 
     #[test]
-    fn test_leading_tabs() {
-        let rule = MD010NoHardTabs::default();
+    fn test_leading_tabs_skipped_in_indented_code_by_default() {
+        // Both lines start with a tab at column 0: parsed as an indented code 
block.
+        // Default code_blocks=false skips tabs in indented code blocks.
         let content = "\tIndented line\n\t\tDouble indented";
         let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
-        let result = rule.check(&ctx).unwrap();
-        assert_eq!(result.len(), 2);
-        assert_eq!(result[0].line, 1);
-        assert_eq!(result[0].message, "Found leading tab, use 4 spaces 
instead");
-        assert_eq!(result[1].line, 2);
-        assert_eq!(result[1].message, "Found 2 leading tabs, use 8 spaces 
instead");
+
+        let rule_off = MD010NoHardTabs::default();
+        let result_off = rule_off.check(&ctx).unwrap();
+        assert!(
+            result_off.is_empty(),
+            "indented code block skipped by default, got {result_off:?}"
+        );
+        assert_eq!(
+            rule_off.fix(&ctx).unwrap(),
+            "\tIndented line\n\t\tDouble indented",
+            "fix must preserve indented code block content"
+        );
+
+        // code_blocks=true: tabs inside indented code blocks are flagged.
+        let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+            spaces_per_tab: crate::types::PositiveUsize::from_const(4),
+            code_blocks: true,
+        });
+        let result_on = rule_on.check(&ctx).unwrap();
+        assert_eq!(result_on.len(), 2, "got {result_on:?}");
+        assert_eq!(result_on[0].line, 1);
+        assert_eq!(result_on[0].message, "Found leading tab, use 4 spaces 
instead");
+        assert_eq!(result_on[1].line, 2);
+        assert_eq!(result_on[1].message, "Found 2 leading tabs, use 8 spaces 
instead");
+        assert_eq!(rule_on.fix(&ctx).unwrap(), "    Indented line\n        
Double indented");
     }
 
     #[test]
     fn test_fix_tabs() {
-        let rule = MD010NoHardTabs::default();
+        // Line 1 starts with a tab at column 0 -> indented code block, 
skipped by default.
+        // Line 2 has a mid-line tab (alignment) -> flagged and fixed.
         let content = "\tIndented\nNormal\tline\nNo tabs";
         let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
-        let fixed = rule.fix(&ctx).unwrap();
-        assert_eq!(fixed, "    Indented\nNormal    line\nNo tabs");
+
+        let rule_off = MD010NoHardTabs::default();
+        let warnings_off = rule_off.check(&ctx).unwrap();
+        assert_eq!(warnings_off.len(), 1, "got {warnings_off:?}");
+        assert_eq!(warnings_off[0].line, 2);
+        assert_eq!(warnings_off[0].message, "Found tab for alignment, use 
spaces instead");
+        assert_eq!(
+            rule_off.fix(&ctx).unwrap(),
+            "\tIndented\nNormal    line\nNo tabs",
+            "indented code block line preserved; alignment tab fixed"
+        );
+
+        // code_blocks=true: line 1 is also flagged.
+        let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+            spaces_per_tab: crate::types::PositiveUsize::from_const(4),
+            code_blocks: true,
+        });
+        let warnings_on = rule_on.check(&ctx).unwrap();
+        assert_eq!(warnings_on.len(), 2, "got {warnings_on:?}");
+        assert_eq!(warnings_on[0].line, 1);
+        assert_eq!(warnings_on[1].line, 2);
+        assert_eq!(rule_on.fix(&ctx).unwrap(), "    Indented\nNormal    
line\nNo tabs");
     }
 
     #[test]
     fn test_custom_spaces_per_tab() {
-        let rule = MD010NoHardTabs::new(4);
+        // Single tab at column 0 -> indented code block, skipped by default.
         let content = "\tIndented";
         let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
-        let fixed = rule.fix(&ctx).unwrap();
-        assert_eq!(fixed, "    Indented");
+
+        let rule_off = MD010NoHardTabs::new(4);
+        assert!(
+            rule_off.check(&ctx).unwrap().is_empty(),
+            "indented code block skipped by default"
+        );
+        assert_eq!(
+            rule_off.fix(&ctx).unwrap(),
+            "\tIndented",
+            "indented code block preserved by default"
+        );
+
+        // code_blocks=true: tab is flagged and fixed.
+        let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+            spaces_per_tab: crate::types::PositiveUsize::from_const(4),
+            code_blocks: true,
+        });
+        assert_eq!(rule_on.check(&ctx).unwrap().len(), 1);
+        assert_eq!(rule_on.fix(&ctx).unwrap(), "    Indented");
     }
 
     #[test]
-    fn test_code_blocks_always_ignored() {
+    fn test_fenced_code_block_tabs_skipped_by_default() {
         let rule = MD010NoHardTabs::default();
         let content = "Normal\tline\n```\nCode\twith\ttab\n```\nAnother\tline";
         let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
         let result = rule.check(&ctx).unwrap();
-        // Should only flag tabs outside code blocks - code has its own 
formatting rules
+        // By default (code_blocks=false) tabs inside code blocks are skipped
         assert_eq!(result.len(), 2);
         assert_eq!(result[0].line, 1);
         assert_eq!(result[1].line, 5);
@@ -344,12 +356,12 @@
     }
 
     #[test]
-    fn test_code_blocks_never_checked() {
+    fn test_fenced_only_content_skipped_by_default() {
         let rule = MD010NoHardTabs::default();
         let content = "```\nCode\twith\ttab\n```";
         let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
         let result = rule.check(&ctx).unwrap();
-        // Should never flag tabs in code blocks - code has its own formatting 
rules
+        // By default (code_blocks=false) tabs in fenced code blocks are 
skipped
         // (e.g., Makefiles require tabs, Go uses tabs by convention)
         assert_eq!(result.len(), 0);
     }
@@ -390,11 +402,32 @@
 
     #[test]
     fn test_mixed_tabs_and_spaces() {
-        let rule = MD010NoHardTabs::default();
+        // " \t..." (space then tab) and "\t ..." (tab then space): both 
parsed as
+        // indented code blocks by the shared spec-compliant flag.
+        // Default code_blocks=false skips them.
         let content = " \tMixed indentation\n\t Mixed again";
         let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
-        let result = rule.check(&ctx).unwrap();
-        assert_eq!(result.len(), 2);
+
+        let rule_off = MD010NoHardTabs::default();
+        let result_off = rule_off.check(&ctx).unwrap();
+        assert!(
+            result_off.is_empty(),
+            "indented code block lines skipped, got {result_off:?}"
+        );
+        assert_eq!(
+            rule_off.fix(&ctx).unwrap(),
+            " \tMixed indentation\n\t Mixed again",
+            "content preserved unchanged"
+        );
+
+        // code_blocks=true: both lines flagged.
+        let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+            spaces_per_tab: crate::types::PositiveUsize::from_const(4),
+            code_blocks: true,
+        });
+        let result_on = rule_on.check(&ctx).unwrap();
+        assert_eq!(result_on.len(), 2, "got {result_on:?}");
+        assert_eq!(rule_on.fix(&ctx).unwrap(), "     Mixed indentation\n     
Mixed again");
     }
 
     #[test]
@@ -447,20 +480,42 @@
 
     #[test]
     fn test_from_config() {
-        // Test that custom config values are properly loaded
-        let custom_spaces = 8;
-        let rule = MD010NoHardTabs::new(custom_spaces);
-        let content = "\tTab";
-        let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
-        let fixed = rule.fix(&ctx).unwrap();
-        assert_eq!(fixed, "        Tab");
+        // "\tTab" at column 0 -> indented code block, skipped by default 
(code_blocks=false).
+        let content_plain = "\tTab";
+        let ctx_plain = LintContext::new(content_plain, 
crate::config::MarkdownFlavor::Standard, None);
+        let rule_8_off = MD010NoHardTabs::new(8); // spaces_per_tab=8, 
code_blocks=false
+        assert!(
+            rule_8_off.check(&ctx_plain).unwrap().is_empty(),
+            "indented code block skipped"
+        );
+        assert_eq!(
+            rule_8_off.fix(&ctx_plain).unwrap(),
+            "\tTab",
+            "content preserved unchanged"
+        );
 
-        // Code blocks are always ignored
-        let content_with_code = "```\n\tTab in code\n```";
-        let ctx = LintContext::new(content_with_code, 
crate::config::MarkdownFlavor::Standard, None);
-        let result = rule.check(&ctx).unwrap();
-        // Tabs in code blocks are never flagged
-        assert!(result.is_empty());
+        // code_blocks=true: the tab is flagged and replaced with 8 spaces.
+        let rule_8_on = MD010NoHardTabs::from_config_struct(MD010Config {
+            spaces_per_tab: crate::types::PositiveUsize::from_const(8),
+            code_blocks: true,
+        });
+        assert_eq!(rule_8_on.check(&ctx_plain).unwrap().len(), 1);
+        assert_eq!(rule_8_on.fix(&ctx_plain).unwrap(), "        Tab");
+
+        // Fenced code block: tab skipped by default.
+        let content_fenced = "```\n\tTab in code\n```";
+        let ctx_fenced = LintContext::new(content_fenced, 
crate::config::MarkdownFlavor::Standard, None);
+        assert!(
+            rule_8_off.check(&ctx_fenced).unwrap().is_empty(),
+            "fenced code block skipped"
+        );
+        assert_eq!(rule_8_off.fix(&ctx_fenced).unwrap(), "```\n\tTab in 
code\n```");
+
+        // code_blocks=true: tab inside fence is flagged.
+        let result_on = rule_8_on.check(&ctx_fenced).unwrap();
+        assert_eq!(result_on.len(), 1, "got {result_on:?}");
+        assert_eq!(result_on[0].line, 2);
+        assert_eq!(rule_8_on.fix(&ctx_fenced).unwrap(), "```\n        Tab in 
code\n```");
     }
 
     #[test]
@@ -503,14 +558,14 @@
     }
 
     #[test]
-    fn test_code_blocks_always_preserved_in_fix() {
+    fn test_fenced_code_block_tabs_preserved_in_fix_by_default() {
         let rule = MD010NoHardTabs::default();
 
         let content = 
"Text\twith\ttab\n```makefile\ntarget:\n\tcommand\n\tanother\n```\nMore\ttabs";
         let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
         let fixed = rule.fix(&ctx).unwrap();
 
-        // Tabs in code blocks are preserved - code has its own formatting 
rules
+        // By default (code_blocks=false) tabs in fenced code blocks are 
preserved
         // (e.g., Makefiles require tabs, Go uses tabs by convention)
         let expected = "Text    with    
tab\n```makefile\ntarget:\n\tcommand\n\tanother\n```\nMore    tabs";
         assert_eq!(fixed, expected);
@@ -554,23 +609,40 @@
     }
 
     #[test]
-    fn test_indented_code_block_tabs_flagged() {
-        let rule = MD010NoHardTabs::default();
-        // Tabs in indented code blocks are flagged because the tab IS the 
problem
-        // (unlike fenced code blocks where tabs are part of the code 
formatting)
+    fn test_indented_code_block_tabs_skipped_by_default() {
+        // "    code\twith\ttab" is indented with 4 spaces -> indented code 
block.
+        // Default code_blocks=false skips it; only the tab on the normal line 
is flagged.
         let content = "    code\twith\ttab\n\nNormal\ttext";
         let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
-        let result = rule.check(&ctx).unwrap();
+
+        let rule_off = MD010NoHardTabs::default();
+        let result_off = rule_off.check(&ctx).unwrap();
         assert_eq!(
-            result.len(),
+            result_off.len(),
+            1,
+            "expected 1 warning (only normal-text tab), got {}: {:?}",
+            result_off.len(),
+            result_off
+        );
+        assert_eq!(result_off[0].line, 3);
+        assert_eq!(result_off[0].message, "Found tab for alignment, use spaces 
instead");
+
+        // code_blocks=true: all 3 tabs flagged (2 on line 1, 1 on line 3).
+        let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+            spaces_per_tab: crate::types::PositiveUsize::from_const(4),
+            code_blocks: true,
+        });
+        let result_on = rule_on.check(&ctx).unwrap();
+        assert_eq!(
+            result_on.len(),
             3,
-            "Expected 3 warnings but got {}: {:?}",
-            result.len(),
-            result
+            "expected 3 warnings with code_blocks=true, got {}: {:?}",
+            result_on.len(),
+            result_on
         );
-        assert_eq!(result[0].line, 1);
-        assert_eq!(result[1].line, 1);
-        assert_eq!(result[2].line, 3);
+        assert_eq!(result_on[0].line, 1);
+        assert_eq!(result_on[1].line, 1);
+        assert_eq!(result_on[2].line, 3);
     }
 
     #[test]
@@ -601,11 +673,98 @@
 
     #[test]
     fn test_fix_indented_code_block_tabs_replaced() {
-        let rule = MD010NoHardTabs::default();
+        // Default code_blocks=false: indented code block tabs preserved, 
normal-text tab fixed.
         let content = "    code\twith\ttab\n\nNormal\ttext";
         let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
-        let fixed = rule.fix(&ctx).unwrap();
-        // All tabs replaced, including those in indented code blocks
-        assert_eq!(fixed, "    code    with    tab\n\nNormal    text");
+
+        let rule_off = MD010NoHardTabs::default();
+        assert_eq!(
+            rule_off.fix(&ctx).unwrap(),
+            "    code\twith\ttab\n\nNormal    text",
+            "indented code block preserved; only normal-text tab fixed"
+        );
+
+        // code_blocks=true: all tabs replaced including those in the indented 
code block.
+        let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+            spaces_per_tab: crate::types::PositiveUsize::from_const(4),
+            code_blocks: true,
+        });
+        assert_eq!(
+            rule_on.fix(&ctx).unwrap(),
+            "    code    with    tab\n\nNormal    text",
+            "all tabs replaced with code_blocks=true"
+        );
+    }
+
+    #[test]
+    fn test_issue_630_default_skips_both_code_blocks() {
+        // Default code_blocks = false: tabs skipped in BOTH block types.
+        let rule = MD010NoHardTabs::default();
+        let content = "Foo bar\n\n    for range 100 {\n    \tfoo()\n    
}\n\nThis is a fenced\n\n```\nfor range 100 {\n\tfoo()\n}\n```\n";
+        let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
+        let result = rule.check(&ctx).unwrap();
+        assert!(result.is_empty(), "both code blocks skipped, got {result:?}");
+    }
+
+    #[test]
+    fn test_issue_630_code_blocks_true_flags_both() {
+        // code_blocks = true: tabs flagged in BOTH block types.
+        let rule = MD010NoHardTabs::from_config_struct(MD010Config {
+            spaces_per_tab: crate::types::PositiveUsize::from_const(4),
+            code_blocks: true,
+        });
+        let content = "Foo bar\n\n    for range 100 {\n    \tfoo()\n    
}\n\nThis is a fenced\n\n```\nfor range 100 {\n\tfoo()\n}\n```\n";
+        let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
+        let result = rule.check(&ctx).unwrap();
+        // Line 4 "    \tfoo()": one alignment tab group inside the indented 
block.
+        // Line 11 "\tfoo()": one leading tab group inside the fenced block.
+        assert_eq!(result.len(), 2, "got {result:?}");
+        assert_eq!(result[0].line, 4);
+        assert_eq!(result[1].line, 11);
+    }
+
+    #[test]
+    fn test_code_blocks_toggle_fenced() {
+        let content = "Normal\tline\n```\nCode\twith\ttab\n```\nAnother\tline";
+
+        // Default false: only the two tab groups outside the fence.
+        let off = MD010NoHardTabs::default();
+        let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
+        let r_off = off.check(&ctx).unwrap();
+        assert_eq!(r_off.len(), 2, "got {r_off:?}");
+        assert_eq!(r_off[0].line, 1);
+        assert_eq!(r_off[1].line, 5);
+        assert_eq!(
+            off.fix(&ctx).unwrap(),
+            "Normal    line\n```\nCode\twith\ttab\n```\nAnother    line"
+        );
+
+        // true: also the two groups on the fenced content line.
+        let on = MD010NoHardTabs::from_config_struct(MD010Config {
+            spaces_per_tab: crate::types::PositiveUsize::from_const(4),
+            code_blocks: true,
+        });
+        let r_on = on.check(&ctx).unwrap();
+        assert_eq!(r_on.len(), 4, "got {r_on:?}");
+        assert_eq!(r_on[0].line, 1);
+        assert_eq!(r_on[1].line, 3);
+        assert_eq!(r_on[2].line, 3);
+        assert_eq!(r_on[3].line, 5);
+        assert_eq!(
+            on.fix(&ctx).unwrap(),
+            "Normal    line\n```\nCode    with    tab\n```\nAnother    line"
+        );
+    }
+
+    #[test]
+    fn test_code_blocks_toggle_makefile_fence_preserved_by_default() {
+        let content = 
"Text\twith\ttab\n```makefile\ntarget:\n\tcommand\n```\nMore\ttabs";
+        let off = MD010NoHardTabs::default();
+        let ctx = LintContext::new(content, 
crate::config::MarkdownFlavor::Standard, None);
+        // Default preserves the Makefile recipe tab; only prose tabs fixed.
+        assert_eq!(
+            off.fix(&ctx).unwrap(),
+            "Text    with    tab\n```makefile\ntarget:\n\tcommand\n```\nMore   
 tabs"
+        );
     }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/src/rules/mod.rs 
new/rumdl-0.1.95/src/rules/mod.rs
--- old/rumdl-0.1.94/src/rules/mod.rs   2026-05-18 12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/src/rules/mod.rs   2026-05-19 22:15:04.000000000 +0200
@@ -10,7 +10,7 @@
 mod md005_list_indent;
 pub mod md007_ul_indent;
 mod md009_trailing_spaces;
-mod md010_no_hard_tabs;
+pub mod md010_no_hard_tabs;
 mod md011_no_reversed_links;
 pub mod md013_line_length;
 mod md014_commands_show_output;
@@ -79,7 +79,7 @@
 pub use md005_list_indent::MD005ListIndent;
 pub use md007_ul_indent::MD007ULIndent;
 pub use md009_trailing_spaces::MD009TrailingSpaces;
-pub use md010_no_hard_tabs::MD010NoHardTabs;
+pub use md010_no_hard_tabs::{MD010Config, MD010NoHardTabs};
 pub use md011_no_reversed_links::MD011NoReversedLinks;
 pub use md013_line_length::MD013Config;
 pub use md013_line_length::MD013LineLength;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/tests/formats/mkdocs_extensions_test.rs 
new/rumdl-0.1.95/tests/formats/mkdocs_extensions_test.rs
--- old/rumdl-0.1.94/tests/formats/mkdocs_extensions_test.rs    2026-05-18 
12:11:42.000000000 +0200
+++ new/rumdl-0.1.95/tests/formats/mkdocs_extensions_test.rs    2026-05-19 
22:15:04.000000000 +0200
@@ -832,7 +832,9 @@
 
     #[test]
     fn test_hard_tabs_detected() {
-        let content = "# Test\n\n\tIndented with tab.\n";
+        // Use a tab mid-line in a paragraph (not at column 0 after a blank 
line,
+        // which would be an indented code block skipped by default).
+        let content = "# Test\n\nParagraph with\ta hard tab.\n";
         let warnings = lint_mkdocs(content);
         let md010 = warnings
             .iter()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rumdl-0.1.94/tests/rules/md010_test.rs 
new/rumdl-0.1.95/tests/rules/md010_test.rs
--- old/rumdl-0.1.94/tests/rules/md010_test.rs  2026-05-18 12:11:42.000000000 
+0200
+++ new/rumdl-0.1.95/tests/rules/md010_test.rs  2026-05-19 22:15:04.000000000 
+0200
@@ -1,6 +1,7 @@
 use rumdl_lib::lint_context::LintContext;
 use rumdl_lib::rule::Rule;
-use rumdl_lib::rules::MD010NoHardTabs;
+use rumdl_lib::rules::{MD010Config, MD010NoHardTabs};
+use rumdl_lib::types::PositiveUsize;
 
 #[test]
 fn test_no_hard_tabs() {
@@ -24,15 +25,35 @@
 
 #[test]
 fn test_leading_hard_tabs() {
-    let rule = MD010NoHardTabs::default();
+    // Both lines start with a tab at column 0 -> indented code block.
+    // Default code_blocks=false skips tabs in indented code blocks.
     let content = "\tIndented line\n\t\tDouble indented";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let result = rule.check(&ctx).unwrap();
-    assert_eq!(result.len(), 2); // One warning per line (grouped consecutive 
tabs)
-    assert_eq!(result[0].line, 1);
-    assert_eq!(result[0].message, "Found leading tab, use 4 spaces instead");
-    assert_eq!(result[1].line, 2);
-    assert_eq!(result[1].message, "Found 2 leading tabs, use 8 spaces 
instead");
+
+    let rule_off = MD010NoHardTabs::default();
+    let result_off = rule_off.check(&ctx).unwrap();
+    assert!(
+        result_off.is_empty(),
+        "indented code block skipped by default, got {result_off:?}"
+    );
+    assert_eq!(
+        rule_off.fix(&ctx).unwrap(),
+        "\tIndented line\n\t\tDouble indented",
+        "content preserved unchanged"
+    );
+
+    // code_blocks=true: tabs in indented code blocks are flagged.
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(4),
+        code_blocks: true,
+    });
+    let result_on = rule_on.check(&ctx).unwrap();
+    assert_eq!(result_on.len(), 2, "got {result_on:?}");
+    assert_eq!(result_on[0].line, 1);
+    assert_eq!(result_on[0].message, "Found leading tab, use 4 spaces 
instead");
+    assert_eq!(result_on[1].line, 2);
+    assert_eq!(result_on[1].message, "Found 2 leading tabs, use 8 spaces 
instead");
+    assert_eq!(rule_on.fix(&ctx).unwrap(), "    Indented line\n        Double 
indented");
 }
 
 #[test]
@@ -48,20 +69,36 @@
 
 #[test]
 fn test_empty_line_tabs() {
-    let rule = MD010NoHardTabs::default();
+    // Line 2 "\t\t" is an empty line with tabs -> always flagged (not an 
indented code block).
+    // Line 3 "\tMore text" starts with tab at column 0 -> indented code 
block, skipped by default.
     let content = "Normal line\n\t\t\n\tMore text";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let result = rule.check(&ctx).unwrap();
-    // Tab-indented content is flagged (might be accidental)
-    assert_eq!(result.len(), 2); // Empty line with tabs + tab on line 3
-    assert_eq!(result[0].line, 2);
-    assert_eq!(result[0].message, "Empty line contains 2 tabs");
-    assert_eq!(result[1].line, 3);
-    assert_eq!(result[1].message, "Found leading tab, use 4 spaces instead");
+
+    let rule_off = MD010NoHardTabs::default();
+    let result_off = rule_off.check(&ctx).unwrap();
+    assert_eq!(result_off.len(), 1, "only empty-line tabs flagged, got 
{result_off:?}");
+    assert_eq!(result_off[0].line, 2);
+    assert_eq!(result_off[0].message, "Empty line contains 2 tabs");
+    // Empty-line tabs replaced; indented code block line preserved.
+    assert_eq!(rule_off.fix(&ctx).unwrap(), "Normal line\n        \n\tMore 
text");
+
+    // code_blocks=true: line 3 indented code block tab also flagged.
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(4),
+        code_blocks: true,
+    });
+    let result_on = rule_on.check(&ctx).unwrap();
+    assert_eq!(result_on.len(), 2, "got {result_on:?}");
+    assert_eq!(result_on[0].line, 2);
+    assert_eq!(result_on[0].message, "Empty line contains 2 tabs");
+    assert_eq!(result_on[1].line, 3);
+    assert_eq!(result_on[1].message, "Found leading tab, use 4 spaces 
instead");
+    assert_eq!(rule_on.fix(&ctx).unwrap(), "Normal line\n        \n    More 
text");
 }
 
 #[test]
 fn test_code_blocks_allowed() {
+    // Intentionally mirrors test_code_blocks_not_allowed; do not delete the 
redundancy.
     let rule = MD010NoHardTabs::new(4);
     let content = "Normal line\n```\n\tCode with tab\n\tMore 
code\n```\nNormal\tline";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
@@ -72,41 +109,124 @@
 
 #[test]
 fn test_code_blocks_not_allowed() {
-    let rule = MD010NoHardTabs::default(); // code blocks are always skipped 
now
+    // Fenced code block tabs are skipped by default (flagged when 
code_blocks=true).
     let content = "Normal line\n```\n\tCode with tab\n\tMore 
code\n```\nNormal\tline";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let result = rule.check(&ctx).unwrap();
-    assert_eq!(result.len(), 1); // Only tab outside code block is flagged
-    assert_eq!(result[0].line, 6);
+
+    let rule_off = MD010NoHardTabs::default();
+    let result_off = rule_off.check(&ctx).unwrap();
+    assert_eq!(result_off.len(), 1); // Only tab outside code block is flagged
+    assert_eq!(result_off[0].line, 6);
+
+    // code_blocks=true: tabs inside the fenced block are also flagged.
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(4),
+        code_blocks: true,
+    });
+    let result_on = rule_on.check(&ctx).unwrap();
+    assert_eq!(result_on.len(), 3, "got {result_on:?}");
+    assert_eq!(result_on[0].line, 3);
+    assert_eq!(result_on[0].message, "Found leading tab, use 4 spaces 
instead");
+    assert_eq!(result_on[1].line, 4);
+    assert_eq!(result_on[1].message, "Found leading tab, use 4 spaces 
instead");
+    assert_eq!(result_on[2].line, 6);
+    assert_eq!(result_on[2].message, "Found tab for alignment, use spaces 
instead");
+    assert_eq!(
+        rule_on.fix(&ctx).unwrap(),
+        "Normal line\n```\n    Code with tab\n    More code\n```\nNormal    
line"
+    );
 }
 
 #[test]
 fn test_fix_with_code_blocks() {
-    let rule = MD010NoHardTabs::new(2); // 2 spaces per tab, preserve code 
blocks
+    // Default code_blocks=false: lines 1 and 5 are indented code blocks (tab 
at column 0);
+    // line 3 is inside a fenced code block. All tabs skipped -> content 
preserved as-is.
     let content = "\tIndented line\n```\n\tCode\n```\n\t\tDouble indented";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let fixed = rule.fix(&ctx).unwrap();
-    assert_eq!(fixed, "  Indented line\n```\n\tCode\n```\n    Double 
indented");
+
+    let rule_off = MD010NoHardTabs::new(2);
+    assert!(
+        rule_off.check(&ctx).unwrap().is_empty(),
+        "all tabs in code blocks skipped"
+    );
+    assert_eq!(
+        rule_off.fix(&ctx).unwrap(),
+        "\tIndented line\n```\n\tCode\n```\n\t\tDouble indented",
+        "content preserved unchanged"
+    );
+}
+
+#[test]
+fn test_fix_with_code_blocks_true_variant() {
+    // code_blocks=true: tabs in both fenced and indented code blocks are 
replaced.
+    let content = "\tIndented line\n```\n\tCode\n```\n\t\tDouble indented";
+    let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
+
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(2),
+        code_blocks: true,
+    });
+    let result_on = rule_on.check(&ctx).unwrap();
+    assert_eq!(result_on.len(), 3, "got {result_on:?}");
+    assert_eq!(result_on[0].line, 1);
+    assert_eq!(result_on[1].line, 3);
+    assert_eq!(result_on[2].line, 5);
+    assert_eq!(
+        rule_on.fix(&ctx).unwrap(),
+        "  Indented line\n```\n  Code\n```\n    Double indented"
+    );
 }
 
 #[test]
 fn test_fix_without_code_blocks() {
-    let rule = MD010NoHardTabs::new(2); // 2 spaces per tab, code blocks 
always preserved
+    // Intentionally duplicates test_fix_with_code_blocks content as a 
historical regression
+    // counterpart; do not merge or delete either. The code_blocks=true 
behavior for this
+    // content lives in test_fix_with_code_blocks_true_variant.
     let content = "\tIndented line\n```\n\tCode\n```\n\t\tDouble indented";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let fixed = rule.fix(&ctx).unwrap();
-    assert_eq!(fixed, "  Indented line\n```\n\tCode\n```\n    Double 
indented");
+
+    let rule_off = MD010NoHardTabs::new(2);
+    assert!(
+        rule_off.check(&ctx).unwrap().is_empty(),
+        "all tabs in code blocks skipped"
+    );
+    assert_eq!(
+        rule_off.fix(&ctx).unwrap(),
+        "\tIndented line\n```\n\tCode\n```\n\t\tDouble indented",
+        "content preserved unchanged"
+    );
 }
 
 #[test]
 fn test_mixed_indentation() {
-    let rule = MD010NoHardTabs::default();
+    // "    Spaces" is space-indented (not a tab). "\tTab" and "  \tMixed" 
start with
+    // tab/space-tab and are classified as indented code blocks.
+    // Default code_blocks=false skips indented code blocks.
     let content = "    Spaces\n\tTab\n  \tMixed";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let result = rule.check(&ctx).unwrap();
-    assert_eq!(result.len(), 2);
-    assert_eq!(result[0].line, 2);
-    assert_eq!(result[1].line, 3);
+
+    let rule_off = MD010NoHardTabs::default();
+    let result_off = rule_off.check(&ctx).unwrap();
+    assert!(
+        result_off.is_empty(),
+        "indented code block lines skipped, got {result_off:?}"
+    );
+    assert_eq!(
+        rule_off.fix(&ctx).unwrap(),
+        "    Spaces\n\tTab\n  \tMixed",
+        "content preserved unchanged"
+    );
+
+    // code_blocks=true: tabs on lines 2 and 3 are flagged.
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(4),
+        code_blocks: true,
+    });
+    let result_on = rule_on.check(&ctx).unwrap();
+    assert_eq!(result_on.len(), 2, "got {result_on:?}");
+    assert_eq!(result_on[0].line, 2);
+    assert_eq!(result_on[1].line, 3);
+    assert_eq!(rule_on.fix(&ctx).unwrap(), "    Spaces\n    Tab\n      Mixed");
 }
 
 #[test]
@@ -164,22 +284,45 @@
 
 #[test]
 fn test_md010_tabs_in_indented_code() {
-    let rule = MD010NoHardTabs::new(4);
-
-    // Tab-indented content is flagged as it might be accidental
-    // (even if it looks like an indented code block)
+    // Lines 3-4 start with double-tabs -> indented code block.
+    // Default code_blocks=false skips indented code blocks; line 6 alignment 
tabs fixed.
     let content = "Text\n\n\t\tCode with tabs\n\t\tMore 
code\n\nText\twith\ttab";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let fixed = rule.fix(&ctx).unwrap();
 
-    // Tab-indented content is converted to spaces (8 spaces = 2 tabs * 4 
spaces)
+    let rule_off = MD010NoHardTabs::new(4);
+    let result_off = rule_off.check(&ctx).unwrap();
+    assert_eq!(result_off.len(), 2, "got {result_off:?}");
+    assert_eq!(result_off[0].line, 6);
+    assert_eq!(result_off[1].line, 6);
+    let fixed_off = rule_off.fix(&ctx).unwrap();
     assert!(
-        fixed.contains("        Code with tabs"),
-        "Tabs in tab-indented content should be replaced"
+        fixed_off.contains("\t\tCode with tabs"),
+        "indented code block preserved, got: {fixed_off:?}"
     );
     assert!(
-        fixed.contains("Text    with    tab"),
-        "Tabs outside code should be replaced"
+        fixed_off.contains("Text    with    tab"),
+        "alignment tabs fixed, got: {fixed_off:?}"
+    );
+
+    // code_blocks=true: indented code block tabs also flagged and replaced.
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(4),
+        code_blocks: true,
+    });
+    let result_on = rule_on.check(&ctx).unwrap();
+    assert_eq!(result_on.len(), 4, "got {result_on:?}");
+    assert_eq!(result_on[0].line, 3);
+    assert_eq!(result_on[1].line, 4);
+    assert_eq!(result_on[2].line, 6);
+    assert_eq!(result_on[3].line, 6);
+    let fixed_on = rule_on.fix(&ctx).unwrap();
+    assert!(
+        fixed_on.contains("        Code with tabs"),
+        "indented code tabs replaced (2 tabs * 4 spaces), got: {fixed_on:?}"
+    );
+    assert!(
+        fixed_on.contains("Text    with    tab"),
+        "alignment tabs fixed, got: {fixed_on:?}"
     );
 }
 
@@ -249,38 +392,73 @@
 
 #[test]
 fn test_tab_character_in_different_positions() {
-    let rule = MD010NoHardTabs::default();
-
-    // Test tabs at start, middle, and end
+    // Line 1 "\tStart tab" starts with a tab at column 0 -> indented code 
block, skipped by default.
+    // Lines 2-5 have non-leading or leading tabs on non-code-block lines.
     let content = "\tStart tab\nMiddle\ttab\nEnd tab\t\n\t\tDouble 
start\nMixed \t \t spaces";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let result = rule.check(&ctx).unwrap();
 
-    assert_eq!(result.len(), 6, "Should detect all tabs");
-    assert_eq!(result[0].message, "Found leading tab, use 4 spaces instead");
-    assert_eq!(result[1].message, "Found tab for alignment, use spaces 
instead");
-    assert_eq!(result[2].message, "Found tab for alignment, use spaces 
instead");
-    assert_eq!(result[3].message, "Found 2 leading tabs, use 8 spaces 
instead");
-    assert_eq!(result[4].message, "Found tab for alignment, use spaces 
instead");
-    assert_eq!(result[5].message, "Found tab for alignment, use spaces 
instead");
+    let rule_off = MD010NoHardTabs::default();
+    let result_off = rule_off.check(&ctx).unwrap();
+    assert_eq!(result_off.len(), 5, "got {result_off:?}");
+    assert_eq!(result_off[0].line, 2);
+    assert_eq!(result_off[0].message, "Found tab for alignment, use spaces 
instead");
+    assert_eq!(result_off[1].line, 3);
+    assert_eq!(result_off[1].message, "Found tab for alignment, use spaces 
instead");
+    assert_eq!(result_off[2].line, 4);
+    assert_eq!(result_off[2].message, "Found 2 leading tabs, use 8 spaces 
instead");
+    assert_eq!(result_off[3].line, 5);
+    assert_eq!(result_off[3].message, "Found tab for alignment, use spaces 
instead");
+    assert_eq!(result_off[4].line, 5);
+    assert_eq!(result_off[4].message, "Found tab for alignment, use spaces 
instead");
+    assert_eq!(
+        rule_off.fix(&ctx).unwrap(),
+        "\tStart tab\nMiddle    tab\nEnd tab    \n        Double start\nMixed  
         spaces"
+    );
+
+    // code_blocks=true: line 1 is also flagged.
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(4),
+        code_blocks: true,
+    });
+    let result_on = rule_on.check(&ctx).unwrap();
+    assert_eq!(result_on.len(), 6, "got {result_on:?}");
+    assert_eq!(result_on[0].line, 1);
+    assert_eq!(result_on[0].message, "Found leading tab, use 4 spaces 
instead");
+    assert_eq!(
+        rule_on.fix(&ctx).unwrap(),
+        "    Start tab\nMiddle    tab\nEnd tab    \n        Double 
start\nMixed           spaces"
+    );
 }
 
 #[test]
 fn test_mixed_tabs_and_spaces_detailed() {
-    let rule = MD010NoHardTabs::default();
-
-    // Various mixed indentation patterns
+    // All four lines have tabs mixed with leading spaces -> all classified as 
indented code blocks.
+    // Default code_blocks=false skips all of them.
     let content =
         "  \tTwo spaces then tab\n\t  Tab then two spaces\n \t \t Space tab 
space tab\n\t\t  Two tabs then spaces";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let result = rule.check(&ctx).unwrap();
 
-    assert_eq!(result.len(), 5, "Should detect all tabs");
+    let rule_off = MD010NoHardTabs::default();
+    let result_off = rule_off.check(&ctx).unwrap();
+    assert!(
+        result_off.is_empty(),
+        "all lines are indented code blocks, got {result_off:?}"
+    );
+    assert_eq!(
+        rule_off.fix(&ctx).unwrap(),
+        "  \tTwo spaces then tab\n\t  Tab then two spaces\n \t \t Space tab 
space tab\n\t\t  Two tabs then spaces",
+        "content preserved unchanged"
+    );
 
-    // Fix test
-    let fixed = rule.fix(&ctx).unwrap();
+    // code_blocks=true: 5 tab groups flagged and fixed across all lines.
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(4),
+        code_blocks: true,
+    });
+    let result_on = rule_on.check(&ctx).unwrap();
+    assert_eq!(result_on.len(), 5, "got {result_on:?}");
     assert_eq!(
-        fixed,
+        rule_on.fix(&ctx).unwrap(),
         "      Two spaces then tab\n      Tab then two spaces\n           
Space tab space tab\n          Two tabs then spaces"
     );
 }
@@ -308,20 +486,40 @@
 
 #[test]
 fn test_configuration_spaces_per_tab() {
-    // Test different spaces_per_tab configurations
+    // All lines start with tabs at column 0 -> indented code block.
+    // Default code_blocks=false skips them regardless of spaces_per_tab.
     let content = "\tOne tab\n\t\tTwo tabs\n\t\t\tThree tabs";
-
-    // Test with 2 spaces per tab
-    let rule2 = MD010NoHardTabs::new(2);
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let fixed2 = rule2.fix(&ctx).unwrap();
-    assert_eq!(fixed2, "  One tab\n    Two tabs\n      Three tabs");
 
-    // Test with 8 spaces per tab
-    let rule8 = MD010NoHardTabs::new(8);
-    let fixed8 = rule8.fix(&ctx).unwrap();
+    let rule2_off = MD010NoHardTabs::new(2);
+    assert!(rule2_off.check(&ctx).unwrap().is_empty(), "indented code block 
skipped");
+    assert_eq!(
+        rule2_off.fix(&ctx).unwrap(),
+        "\tOne tab\n\t\tTwo tabs\n\t\t\tThree tabs",
+        "content preserved with spaces_per_tab=2"
+    );
+
+    let rule8_off = MD010NoHardTabs::new(8);
+    assert!(rule8_off.check(&ctx).unwrap().is_empty(), "indented code block 
skipped");
     assert_eq!(
-        fixed8,
+        rule8_off.fix(&ctx).unwrap(),
+        "\tOne tab\n\t\tTwo tabs\n\t\t\tThree tabs",
+        "content preserved with spaces_per_tab=8"
+    );
+
+    // code_blocks=true: spaces_per_tab controls substitution width.
+    let rule2_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(2),
+        code_blocks: true,
+    });
+    assert_eq!(rule2_on.fix(&ctx).unwrap(), "  One tab\n    Two tabs\n      
Three tabs");
+
+    let rule8_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(8),
+        code_blocks: true,
+    });
+    assert_eq!(
+        rule8_on.fix(&ctx).unwrap(),
         "        One tab\n                Two tabs\n                        
Three tabs"
     );
 }
@@ -329,63 +527,122 @@
 #[test]
 fn test_configuration_code_blocks_parameter() {
     let content = "Normal\ttab\n\n```javascript\nfunction\tfoo() 
{\n\treturn\ttrue;\n}\n```\n\nAnother\ttab";
-
-    // Code blocks are always skipped now
-    let rule = MD010NoHardTabs::new(4);
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let result = rule.check(&ctx).unwrap();
-    assert_eq!(result.len(), 2, "Should always skip tabs in code blocks");
-    assert_eq!(result[0].line, 1);
-    assert_eq!(result[1].line, 9);
 
-    // Verify fix behavior
-    let fixed = rule.fix(&ctx).unwrap();
-    assert!(fixed.contains("function\tfoo()"), "Should preserve tabs in code 
blocks");
-    assert!(fixed.contains("Normal    tab"), "Should fix tabs outside code 
blocks");
+    // Default code_blocks=false: only tabs outside the fenced block are 
flagged.
+    let rule_off = MD010NoHardTabs::new(4);
+    let result_off = rule_off.check(&ctx).unwrap();
+    assert_eq!(result_off.len(), 2, "only prose tabs flagged, got 
{result_off:?}");
+    assert_eq!(result_off[0].line, 1);
+    assert_eq!(result_off[1].line, 9);
+    let fixed_off = rule_off.fix(&ctx).unwrap();
+    assert!(fixed_off.contains("function\tfoo()"), "fenced code block 
preserved");
+    assert!(fixed_off.contains("Normal    tab"), "prose tab fixed");
+    assert_eq!(
+        fixed_off,
+        "Normal    tab\n\n```javascript\nfunction\tfoo() 
{\n\treturn\ttrue;\n}\n```\n\nAnother    tab"
+    );
+
+    // code_blocks=true: tabs inside the fenced block are also flagged.
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(4),
+        code_blocks: true,
+    });
+    let result_on = rule_on.check(&ctx).unwrap();
+    assert_eq!(result_on.len(), 5, "got {result_on:?}");
+    assert_eq!(result_on[0].line, 1);
+    assert_eq!(result_on[1].line, 4);
+    assert_eq!(result_on[2].line, 5);
+    assert_eq!(result_on[3].line, 5);
+    assert_eq!(result_on[4].line, 9);
+    assert_eq!(
+        rule_on.fix(&ctx).unwrap(),
+        "Normal    tab\n\n```javascript\nfunction    foo() {\n    return    
true;\n}\n```\n\nAnother    tab"
+    );
 }
 
 #[test]
 fn test_consecutive_vs_separate_tabs() {
-    let rule = MD010NoHardTabs::default();
-
-    // Test grouping of consecutive tabs
+    // Line 1 "\t\t\tThree consecutive" starts with 3 tabs at column 0 -> 
indented code block.
+    // Default code_blocks=false skips it; line 2 alignment tabs are flagged 
(3 separate groups).
     let content = "\t\t\tThree consecutive\nOne\tthen\tanother\t";
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let result = rule.check(&ctx).unwrap();
 
-    assert_eq!(result.len(), 4, "Should have 1 group for consecutive, 3 
separate");
-    assert_eq!(result[0].message, "Found 3 leading tabs, use 12 spaces 
instead");
-    assert_eq!(result[1].message, "Found tab for alignment, use spaces 
instead");
-    assert_eq!(result[2].message, "Found tab for alignment, use spaces 
instead");
-    assert_eq!(result[3].message, "Found tab for alignment, use spaces 
instead");
+    let rule_off = MD010NoHardTabs::default();
+    let result_off = rule_off.check(&ctx).unwrap();
+    assert_eq!(
+        result_off.len(),
+        3,
+        "3 separate alignment tab groups on line 2, got {result_off:?}"
+    );
+    assert_eq!(result_off[0].line, 2);
+    assert_eq!(result_off[0].message, "Found tab for alignment, use spaces 
instead");
+    assert_eq!(result_off[1].line, 2);
+    assert_eq!(result_off[1].message, "Found tab for alignment, use spaces 
instead");
+    assert_eq!(result_off[2].line, 2);
+    assert_eq!(result_off[2].message, "Found tab for alignment, use spaces 
instead");
+    assert_eq!(
+        rule_off.fix(&ctx).unwrap(),
+        "\t\t\tThree consecutive\nOne    then    another    "
+    );
+
+    // code_blocks=true: line 1 consecutive-tab group is also flagged.
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(4),
+        code_blocks: true,
+    });
+    let result_on = rule_on.check(&ctx).unwrap();
+    assert_eq!(result_on.len(), 4, "got {result_on:?}");
+    assert_eq!(result_on[0].line, 1);
+    assert_eq!(result_on[0].message, "Found 3 leading tabs, use 12 spaces 
instead");
+    assert_eq!(
+        rule_on.fix(&ctx).unwrap(),
+        "            Three consecutive\nOne    then    another    "
+    );
 }
 
 #[test]
 fn test_fix_preserves_content_structure() {
-    let rule = MD010NoHardTabs::default();
-
-    // Complex content with various elements
     let content = "# Header\n\n\tIndented paragraph\n\n- List\n\t- 
Nested\n\t\t- Double nested\n\n```\n\tCode block\n```\n\n> Quote\n> \tWith 
tab\n\n| Col1\t| Col2\t|\n|---\t|---\t|\n| Data\t| Data\t|";
-
     let ctx = LintContext::new(content, 
rumdl_lib::config::MarkdownFlavor::Standard, None);
-    let fixed = rule.fix(&ctx).unwrap();
 
-    // Verify structure is preserved
-    assert!(fixed.contains("# Header"), "Headers preserved");
-    // Tab-indented content is converted to spaces (might be accidental)
-    assert!(
-        fixed.contains("    Indented paragraph"),
-        "Tab-indented content converted"
-    );
-    assert!(fixed.contains("    - Nested"), "List indentation converted");
-    assert!(
-        fixed.contains("        - Double nested"),
-        "Double indentation converted"
-    );
-    // Fenced code blocks are preserved
-    assert!(fixed.contains("\tCode block"), "Code block tabs preserved");
-    assert!(fixed.contains(">     With tab"), "Quote tab converted");
-    assert!(fixed.contains("| Col1    | Col2    |"), "Table tabs converted");
+    // Default code_blocks=false: indented paragraph (line 3) and code block 
content (line 10)
+    // are preserved; list item tabs and table/quote tabs outside code blocks 
are fixed.
+    let rule_off = MD010NoHardTabs::default();
+    let fixed_off = rule_off.fix(&ctx).unwrap();
+    assert!(fixed_off.contains("# Header"), "headers preserved");
+    assert!(
+        fixed_off.contains("\tIndented paragraph"),
+        "indented code block preserved, got: {fixed_off:?}"
+    );
+    assert!(fixed_off.contains("    - Nested"), "list indentation converted");
+    assert!(
+        fixed_off.contains("        - Double nested"),
+        "double list indentation converted"
+    );
+    assert!(fixed_off.contains("\tCode block"), "fenced code block tab 
preserved");
+    assert!(fixed_off.contains(">     With tab"), "quote tab converted");
+    assert!(fixed_off.contains("| Col1    | Col2    |"), "table tabs 
converted");
+    assert_eq!(
+        fixed_off,
+        "# Header\n\n\tIndented paragraph\n\n- List\n    - Nested\n        - 
Double nested\n\n```\n\tCode block\n```\n\n> Quote\n>     With tab\n\n| Col1    
| Col2    |\n|---    |---    |\n| Data    | Data    |"
+    );
+
+    // code_blocks=true: indented paragraph and fenced code block tabs are 
also fixed.
+    let rule_on = MD010NoHardTabs::from_config_struct(MD010Config {
+        spaces_per_tab: PositiveUsize::from_const(4),
+        code_blocks: true,
+    });
+    let fixed_on = rule_on.fix(&ctx).unwrap();
+    assert!(
+        fixed_on.contains("    Indented paragraph"),
+        "indented paragraph converted with code_blocks=true, got: {fixed_on:?}"
+    );
+    assert!(fixed_on.contains("    Code block"), "fenced code block tab 
converted");
+    assert_eq!(
+        fixed_on,
+        "# Header\n\n    Indented paragraph\n\n- List\n    - Nested\n        - 
Double nested\n\n```\n    Code block\n```\n\n> Quote\n>     With tab\n\n| Col1  
  | Col2    |\n|---    |---    |\n| Data    | Data    |"
+    );
 }
 
 #[test]

++++++ rumdl.obsinfo ++++++
--- /var/tmp/diff_new_pack.RCk5Qs/_old  2026-05-20 15:26:54.788052834 +0200
+++ /var/tmp/diff_new_pack.RCk5Qs/_new  2026-05-20 15:26:54.808053658 +0200
@@ -1,5 +1,5 @@
 name: rumdl
-version: 0.1.94
-mtime: 1779099102
-commit: fbb90ac101470d2c249e3bc5d9a8915de477f3e4
+version: 0.1.95
+mtime: 1779221704
+commit: b2164bb1b33cbc3b416f686180033b8be7374f37
 

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

Reply via email to