Skip to content

Preserve nested fenced blocks (#262)#275

Merged
leynos merged 15 commits intomainfrom
fix-nested-fence-handling-1ujtgo
Apr 26, 2026
Merged

Preserve nested fenced blocks (#262)#275
leynos merged 15 commits intomainfrom
fix-nested-fence-handling-1ujtgo

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Apr 23, 2026

Summary

Changes

  • Introduces docs/execplans/issue-262-nested_code_block-handling.md containing the full execution plan:
    • Purpose, references, constraints, tolerances, risks, progress, decisions, plan stages (A–E), concrete steps, and acceptance criteria.
    • Rationale for reusing shared fence-state semantics (FenceTracker) and aligning preprocessing with wrapping behavior.
    • Regression targets and testing strategy across fences tests, CLI, and fence_tracker tests.

Rationale

  • The plan responds to issue mdtablefix doesn't correctly handle nested code blocks #262 by ensuring that four-backtick or four-tilde outer fences correctly normalize only the outer delimiters while preserving nested triple-backtick content.
  • It leverages existing fence-tracking logic to avoid introducing a separate nested-fence state machine and to keep wrapping, fences, and specifier attachment consistent.

Plan of work (high level)

  • Stage A: add failing regression examples for nested fences (outer four-backtick/outer tilde with inner triple-backticks) and ensure CLI regression tests cover --fences behavior.
  • Stage B: implement line-by-line compression with a FenceTracker to compress only real outer delimiters and preserve nested content.
  • Stage C: update attach_orphan_specifiers to respect active fence state and avoid attaching inside fences.
  • Stage D: extend FenceTracker-based tests with explicit nested-fence scenarios.
  • Stage E: validate against tests and update architecture/docs if needed.

Test plan

  • Align with existing targets mentioned in the ExecPlan:
    • tests/fences.rs (unit/integration for fence handling)
    • tests/cli.rs (end-to-end CLI behavior with --fences)
    • src/wrap/tests/fence_tracker.rs (shared fence-tracker semantics)
  • Ensure new coverage lives in active top-level test targets (not in non-Cargo-runner files).

Acceptance criteria

  • compress_fences compresses only actual outer delimiters; nested fence-like content remains unchanged.
  • attach_orphan_specifiers does not attach lines inside an open fenced block.
  • FenceTracker tests include explicit outer-four-backticks and outer-tilde scenarios.
  • cargo test --test fences, cargo test --test cli, make check-fmt, make lint, and make test pass.
  • If architecture docs change, related gates (fmt/markdown/nixie) pass as well.

Progress

  • Add ExecPlan doc for nested fence handling
  • Implement core changes in src/fences.rs and related modules (to be done)
  • Extend tests and run full gates (to be done)

Notes

  • This PR focuses on documentation that guides and aligns future implementation work. It does not change public APIs. It serves as a single source of truth for the nested-fence handling approach and testing strategy.

◳ Generated by DevBoxer


ℹ️ Tag @devboxerhub to ask questions and address PR feedback

📎 Task: https://www.devboxer.com/task/0d20afc3-1dba-45f8-bb0d-c8948f1c1c76

📝 Closes #262

Summary by Sourcery

Make fence normalization and orphan specifier attachment preserve nested fenced blocks while aligning behavior with the shared FenceTracker semantics.

Bug Fixes:

  • Ensure compress_fences only compresses safe outer fence delimiters and preserves nested fence-like content, including mixed backtick and tilde scenarios.
  • Prevent attach_orphan_specifiers from attaching language specifiers to fences that reside inside an open fenced block.

Enhancements:

  • Align fences::compress_fences and attach_orphan_specifiers with FenceTracker-based fence state tracking for consistent behavior across preprocessing and wrapping.
  • Improve attach_specifier_to_fence so it retains the original fence marker family when attaching language specifiers.
  • Document the updated fence normalization behavior and shared FenceTracker semantics in the architecture docs.
  • Add an execution plan documenting the nested fenced block handling approach and validation strategy.

Documentation:

  • Add a detailed ExecPlan for nested code block handling and update architecture documentation to describe the new FenceTracker-aligned fence normalization behavior.

Tests:

  • Add unit tests for compress_fences covering nested backtick and tilde fences and preservation of inner literal fences.
  • Add tests ensuring orphan specifiers are not attached inside outer fences and that preserved outer fences retain width when specifiers are attached.
  • Extend FenceTracker tests to cover nested same-marker and mixed-marker fence scenarios that govern the new normalization behavior.
  • Add a CLI regression test to verify --fences preserves nested fenced code blocks end to end.

Add a comprehensive execution plan document issue-262-nested-code-block-handling.md outlining the approach to fix nested fenced code block normalization. This living document details the purpose, context, constraints, risks, and concrete steps to reuse FenceTracker semantics to preserve nested fenced blocks during fence normalization, preventing inner triple-backtick fences from being altered inside outer fences.

This addition aids future implementation, testing, and validation efforts for issue #262.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

This PR implements Issue #262 by preserving nested fenced code blocks during fence normalisation and aligning preprocessing with the shared FenceTracker semantics. It includes a new ExecPlan document and related code, tests and docs updates.

ExecPlan

  • docs/execplans/issue-262-nested-code-block-handling.md: new, complete ExecPlan describing purpose, constraints, risks, tolerances, decisions, plan stages (A–E), concrete steps, acceptance criteria and validation commands (including targeted test invocations).

Core implementation

  • src/fences.rs: compress_fences reworked to buffer matched fenced blocks and use FenceTracker-aware logic to conditionally rewrite outer delimiters only when safe; preserves outer delimiter family/width when shortening would make an inner fence structural. attach_orphan_specifiers and attach_specifier_to_fence now respect fence state (gate attachment inside active fenced regions and preserve original marker style).
  • Reuses existing FenceTracker from src/wrap/fence.rs instead of introducing a bespoke nested-fence state machine.

Tests

  • tests/fences.rs: parameterised unit/regression tests for same-marker and mixed-marker nested fences and orphan-specifier behaviour.
  • tests/cli_fences.rs: CLI regression tests exercising --fences end-to-end.
  • src/wrap/tests/fence_tracker.rs: parameterised tests validating FenceTracker behaviour with nested scenarios.

Documentation

  • Users: README.md and docs/users-guide.md updated with a "Fence normalisation" section describing conditional compression and orphan-specifier rules with Before/After examples.
  • Developers: docs/developers-guide.md and docs/architecture.md updated to document compress_fences, attach_orphan_specifiers and reuse of FenceTracker.
  • New ExecPlan (see above) links design to implementation and tests.
  • Minor reflow/formatting edits elsewhere documented in the commit set.

Outcome and verification

  • Addresses objectives from Issue #262: nested fences are detected and preserved; compress_fences respects structural boundaries; attach_orphan_specifiers is fence-aware; FenceTracker semantics are shared across preprocessing and wrapping; regression and integration tests cover the behaviour.
  • Reviewer and automated checks noted two initial documentation checks failed but those documentation edits were added on the branch and reported resolved; local verification of the remaining gates is recommended. Suggested local validation: run the targeted tests and checks listed in the ExecPlan (for example: cargo test --test fences, cargo test --test cli_fences, make markdownlint, make nixie, make check-fmt, make lint).

Files of primary interest

  • docs/execplans/issue-262-nested-code-block-handling.md
  • src/fences.rs
  • src/wrap/fence.rs
  • tests/fences.rs
  • tests/cli_fences.rs
  • src/wrap/tests/fence_tracker.rs
  • docs/users-guide.md
  • docs/developers-guide.md
  • README.md

Walkthrough

Handle nested fenced code blocks using a FenceTracker-driven normalisation. Buffer outer fenced blocks in compress_fences and compress outer delimiters only when structurally safe; skip orphan-specifier attachment while inside active fenced regions. Update tests and docs to reflect the new fenced-block-aware behaviour.

Changes

Cohort / File(s) Summary
Core Fence Processing
src/fences.rs
Replace stateless per-line fence normalisation with a FenceTracker-driven buffered algorithm in compress_fences; conditionally preserve or compress outer marker runs to avoid turning literal inner fences into structural ones. Gate attach_orphan_specifiers with FenceTracker and preserve original marker style when attaching specifiers.
FenceTracker Tests
src/wrap/tests/fence_tracker.rs
Add parameterised tests exercising nested marker handling and verifying tracker in_fence state across outer/inner start/end scenarios.
Unit & CLI Regression Tests
tests/fences.rs, tests/cli_fences.rs
Add tests validating nested-fence preservation, outer-fence compression safety cases, and that orphan-specifier attachment does not run inside active fenced blocks; add CLI tests for --fences edge cases.
Architecture & Developer Docs
docs/architecture.md, docs/developers-guide.md
Document FenceTracker-driven semantics: conditional outer delimiter compression, indentation propagation rules for attach_orphan_specifiers, and reuse of src/wrap/fence.rs. Reformat figure caption text without changing meaning.
User Documentation & README
docs/users-guide.md, README.md
Update --fences CLI option docs to describe conditional normalisation: compress outer fences only when safe; otherwise preserve original delimiter width. Add Before/After examples and developer notes.
Execution Plan
docs/execplans/issue-262-nested-code-block-handling.md
Add full ExecPlan for issue #262 outlining required nested-fence-safe normalisation, involved modules/tests, constraints, risk mitigations, implementation progress and acceptance criteria.
Miscellaneous Documentation Edits
docs/rust-doctest-dry-guide.md, docs/rust-testing-with-rstest-fixtures.md, docs/execplans/replace-bespoke-wrapping-with-textwrap-and-unicode-width.md, docs/execplans/yaml-frontmatter.md, AGENTS.md, CHANGELOG.md
Reflow and rewrap prose, standardise code-fence language identifiers, and tidy dependency/versioning bullets; no behavioural changes.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Normaliser as compress_fences
    participant Tracker as FenceTracker
    participant Attachor as attach_orphan_specifiers

    Client->>Normaliser: submit lines with nested fences
    activate Normaliser
    Normaliser->>Tracker: open/inspect fence state
    activate Tracker
    Tracker-->>Normaliser: in_fence=true/false
    deactivate Tracker

    Normaliser->>Tracker: observe inner fence-like line
    activate Tracker
    Tracker-->>Normaliser: still in_fence=true
    deactivate Tracker

    alt inner fence would become structural if outer compressed
        Normaliser->>Normaliser: preserve outer delimiter width
    else safe to compress
        Normaliser->>Normaliser: compress outer delimiters to three backticks
    end

    Normaliser->>Tracker: observe outer closing fence
    activate Tracker
    Tracker-->>Normaliser: in_fence=false
    deactivate Tracker
    deactivate Normaliser

    Normaliser-->>Client: return normalised lines

    Client->>Attachor: pass lines for orphan-specifier attachment
    activate Attachor
    Attachor->>Tracker: query in_fence?
    activate Tracker
    Tracker-->>Attachor: true/false
    deactivate Tracker

    alt not in fence and specifier candidate found
        Attachor->>Attachor: attach specifier to next unlabeled fence
    else in fence
        Attachor->>Attachor: skip attachment, preserve lines
    end
    deactivate Attachor

    Attachor-->>Client: final output lines
Loading

Poem

🧩 Track the fences, mark the lines,
Keep inner ticks where daylight shines.
Compress the outer when it's safe,
Leave nested literals in their place.
Orphans wait until they're free — inside, do not intrude!


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (2 errors, 4 warnings)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error Tests lack coverage of the FenceTracker guard that prevents attaching specifier-like lines inside open fenced blocks. Add regression test placing blank line and language-specifier inside outer fence (4+ backticks), followed by inner fence (3 backticks) without specifier, ensuring specifier remains unattached.
Unit Architecture ❌ Error Implementation violates Unit Architecture principles by hiding fallibility behind unwrap_or patterns, conflating state mutation with queries, and leaving critical invariants untype-enforced. Refactor to expose fallibility explicitly using Result types, separate FenceTracker::observe() into distinct command and query operations via a FenceState enum, type-encode the pending_block/tracker invariant, and add architectural tests verifying state invariants.
Developer Documentation ⚠️ Warning Developer documentation properly updated with fence normalisation module section, architecture document reflects design changes, and ExecPlan tracks completion. However, CLI acronym lacks expansion on first use at line 89, violating established coding guidelines. Expand CLI to command-line interface (CLI) on first mention in ExecPlan line 89 to comply with acronym expansion guidelines.
Testing (Property / Proof) ⚠️ Warning PR introduces multiple invariants (marker types, lengths, fence states, nesting, transitions) but relies on discrete manual parametrised unit tests rather than property-based or bounded model checking approaches. Add property-based testing with proptest or formal verification with Kani to systematically validate invariants across arbitrary marker types, lengths, nesting orderings, and state transitions.
Testing (Compile-Time / Ui) ⚠️ Warning CLI fence tests use hardcoded string assertions instead of snapshot testing, reducing test maintainability for text-based Markdown output. Implement snapshot testing with insta library to capture expected Markdown outputs, improving test clarity and reducing brittleness from formatting changes.
Observability ⚠️ Warning The fence-normalisation algorithm lacks observability: no logging, metrics, or diagnostic output exist to explain why delimiters are compressed or preserved, making it impossible to diagnose silent modifications without inspecting source code. Implement a --debug flag or MDTABLEFIX_DEBUG=1 environment variable that emits diagnostic messages to stderr at key decision points, documenting behaviour in CLI help and developer guides.
✅ Passed checks (12 passed)
Check name Status Explanation
Title check ✅ Passed The title directly references the primary objective from linked issue #262 (preserve nested fenced blocks) and includes the issue number.
Description check ✅ Passed The description is substantially related to the changeset, detailing the ExecPlan, implementation roadmap, and acceptance criteria for nested fence preservation.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #262: compress_fences preserves nested fences using FenceTracker, attach_orphan_specifiers respects fence state, and comprehensive tests cover nested scenarios.
Out of Scope Changes check ✅ Passed All changes directly support the nested fence preservation objective: source implementation (src/fences.rs), tests (tests/fences.rs, tests/cli_fences.rs, src/wrap/tests/fence_tracker.rs), and aligned documentation updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
User-Facing Documentation ✅ Passed User-facing documentation adequately covers the changed --fences functionality with safe compression behaviour, explicit preservation rules, and comprehensive examples.
Module-Level Documentation ✅ Passed The src/fences.rs module includes comprehensive module-level documentation and full public API documentation with examples, summaries, and explanations. Neither function requires # Panics or # Errors sections.
Testing (Unit) ✅ Passed Pull request includes comprehensive unit, integration, and end-to-end tests covering fence normalisation with 48 parameterised cases, realistic Markdown input, and public API verification across appropriate functional boundaries.
Domain Architecture ✅ Passed Fence normalisation domain logic remains properly segregated from transport, CLI, and infrastructure concerns. Module expresses Markdown structural invariants through domain-shaped public functions with private implementation details.
Security And Privacy ✅ Passed Pull request implements fence normalisation for Markdown code blocks using safe string processing with no security, privacy, or injection risks identified.
Performance And Resource Use ✅ Passed Changes preserve linear O(n) single-pass algorithms with constant FenceTracker state, necessary string cloning, and pre-allocated buffers; no nested loops, quadratic algorithms, unbounded accumulation, or resource growth introduced.
Concurrency And State ✅ Passed The concurrency and state assessment is not applicable to this PR. The changes introduce no shared mutable state, async execution, locks, transactions, spawned tasks, or re-entrant code.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-nested-fence-handling-1ujtgo

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 23, 2026

Reviewer's Guide

Implements nested-fence-aware normalization for mdtablefix by reworking compress_fences and attach_orphan_specifiers to use FenceTracker semantics, adding targeted regressions (including CLI) for nested backtick/tilde fences, extending FenceTracker tests, updating architecture docs, and adding an ExecPlan documenting the approach and rollout for issue #262.

Sequence diagram for nested-fence-aware compress_fences and attach_orphan_specifiers

sequenceDiagram
    participant CLI
    participant Process as process_stream_inner
    participant Fences as fences::module
    participant Compress as compress_fences
    participant Attach as attach_orphan_specifiers
    participant Tracker as FenceTracker
    participant IsFence as is_fence

    CLI->>Process: run with --fences input_lines
    Process->>Fences: preprocess lines

    Note over Fences: Fence compression phase
    Fences->>Compress: compress_fences(lines)
    Compress->>Tracker: new()

    loop for each line index i
        Compress->>Tracker: in_fence()
        alt outside_fence
            Compress->>IsFence: is_fence(lines[i])
            alt no_matching_block
                Compress-->>Process: compressed_fence_line or original line
            else start_of_matched_block
                Compress->>Tracker: observe(lines[i])
                Compress->>Tracker: in_fence()
                Compress->>Compress: analyze_opening(lines, i)
                Compress->>Compress: opening_rewrite(...)
                Compress-->>Process: rewrite_fence_line(opening_line, rewrite)
                Note over Tracker: tracker now in_fence for outer block
            end
        else inside_fence
            Compress->>Tracker: observe(lines[i])
            alt not_a_delimiter
                Compress-->>Process: preserve line as literal
            else closing_delimiter
                Compress->>Compress: rewrite_fence_line(closing_line, active_rewrite)
                Compress-->>Process: rewritten closing line
                Note over Tracker: fence state closed
            end
        end
    end

    Note over Fences: Orphan specifier attachment phase
    Fences->>Attach: attach_orphan_specifiers(compressed_lines)
    Attach->>Tracker: new()

    loop for each line j
        Attach->>Tracker: in_fence()
        alt inside_fence
            Attach->>Tracker: observe(line_j)
            Attach-->>Process: push line_j unchanged
        else outside_fence
            Attach->>Attach: normalize_specifier(line_j)
            alt is_orphan_specifier and previous_blank
                Attach->>Attach: orphan_specifier_target_without_language(lines, j+1)
                alt found_target_fence
                    Attach->>Attach: attach_specifier_to_fence(target_line, spec, indent)
                    Attach-->>Process: push updated fence_line
                    Attach->>Tracker: observe(target_line)
                else no_target
                    Attach-->>Process: push line_j unchanged
                    Attach->>Tracker: observe(line_j)
                end
            else normal_line
                Attach-->>Process: push line_j unchanged
                Attach->>Tracker: observe(line_j)
            end
        end
    end

    Process-->>CLI: formatted output lines
Loading

Class diagram for updated fence normalization and specifier attachment

classDiagram
    class FenceRewrite {
        <<enum>>
        +Compress
        +PreserveDelimiters
        +PreserveAll
    }

    class OpeningAnalysis {
        +FenceRewrite rewrite
    }

    class FencesModule {
        +compress_fences(lines: &[String]) Vec~String~
        +attach_orphan_specifiers(lines: &[String]) Vec~String~
        -normalize_specifier(line: &str) (String, String)
        -marker_char(marker: &str) Option~char~
        -fence_line_with_marker(line: &str, marker: &str) Option~String~
        -compressed_fence_line(line: &str) Option~String~
        -preserved_fence_line(line: &str) Option~String~
        -interior_fence_requires_preserved_delimiters(opening_marker: &str, line: &str) bool
        -analyze_opening(lines: &[String], opening_index: usize) Option~OpeningAnalysis~
        -opening_rewrite(lines: &[String], index: usize, has_conflicting_interior_fence: bool) FenceRewrite
        -rewrite_fence_line(line: &str, rewrite: FenceRewrite) String
        -attach_specifier_to_fence(fence_line: &str, specifier: &str, spec_indent: &str) String
        -orphan_specifier_target(lines: &[String], start: usize) Option~usize~
        -orphan_specifier_target_without_language(lines: &[String], start: usize) Option~usize~
    }

    class FenceTracker {
        +new() FenceTracker
        +observe(line: &str) bool
        +in_fence() bool
    }

    class IsFenceFn {
        +is_fence(line: &str) Option~(String, String, String)~
    }

    FencesModule --> FenceRewrite : uses
    FencesModule --> OpeningAnalysis : constructs
    FencesModule --> FenceTracker : holds and calls
    FencesModule --> IsFenceFn : calls is_fence

    OpeningAnalysis --> FenceRewrite : contains
Loading

File-Level Changes

Change Details Files
Make compress_fences fence-state-aware so it only compresses safe outer delimiters and preserves nested fence-like content.
  • Replace stateless per-line regex-based compression with a loop that tracks fence state using FenceTracker and is_fence.
  • Introduce FenceRewrite and OpeningAnalysis helpers plus marker/line utilities to decide how each opening/closing fence should be rewritten.
  • Analyze each potential opening fence to find a matching close and detect conflicting interior fences; only compress when shortening cannot make inner literal fences look structural.
  • Ensure lines inside an open fence and closing delimiters that would conflict with inner literal fences are preserved as written, while unmatched legacy cases still get simple line-based compression.
src/fences.rs
Ensure orphan language specifiers are not attached inside active fenced blocks and preserve the original fence marker family when attaching.
  • Change attach_specifier_to_fence to reuse the existing fence marker from the target line instead of always writing backticks.
  • Make attach_orphan_specifiers maintain a FenceTracker while scanning and skip all attachment logic while inside a fence.
  • When attaching to a future fence line, observe it with the tracker so subsequent lines stay consistent with fence state.
src/fences.rs
Add regressions to verify nested fence preservation and orphan specifier behavior for both backtick and tilde markers, and wire a CLI-level regression.
  • Add tests that nested fences (same marker and mixed marker families) are preserved, and that only outer fences may be compressed when safe.
  • Add tests that orphan specifiers are not attached inside outer fences, and that attaching a specifier does not shorten a preserved outer fence.
  • Add a CLI regression that checks mdtablefix --fences preserves a four-backtick outer block containing a triple-backtick fenced example verbatim.
tests/fences.rs
tests/cli_fences.rs
Document the shared FenceTracker-based semantics for fence normalization and record the execution plan for nested fence handling.
  • Update architecture.md to describe that compress_fences/attach_orphan_specifiers now share FenceTracker semantics with wrapping and only compress outer delimiters when safe for nested content.
  • Add an ExecPlan document capturing constraints, risks, staged plan, concrete steps, testing strategy, and retrospective for issue mdtablefix doesn't correctly handle nested code blocks #262 and nested fenced blocks.
  • Extend FenceTracker tests to lock in nested and mixed-marker behavior that compress_fences now relies on.
docs/architecture.md
docs/execplans/issue-262-nested-code-block-handling.md
src/wrap/tests/fence_tracker.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#262 Update fence normalization logic so that compress_fences correctly handles nested fenced blocks (e.g., outer four-or-more backticks or tildes containing inner triple-backtick or triple-tilde blocks), preserving inner fences and not corrupting the outer block structure.
#262 Ensure the behavior for nested fenced blocks is covered by automated tests, including unit tests and CLI-level tests for --fences.
#262 Document the updated nested-fence handling behavior in project documentation/architecture notes.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

leynos and others added 3 commits April 24, 2026 00:38
Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
…s present

Prevent compressing outer fence delimiters below the length of nested same-marker
literal fences to avoid premature termination of the outer block. Specifically,
four-character outer backtick fences wrapping inner triple-backtick fences remain
four backticks wide, and similarly for tilde fences (e.g., `~~~~` wrapping `~~~`).

This fix ensures nested literal content inside fenced blocks is preserved
unchanged and stops fence compression from corrupting document structure. It also
updates `compress_fences` to track fence state and conditionally apply compression
only when safe.

Additional improvements include updated regression tests validating the new
behaviour and enhanced documentation clarifying fence compression rules and risks.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Make `compress_fences` use shared `FenceTracker` semantics for matched
fenced blocks, so literal nested fence examples remain unchanged and unsafe
outer delimiters keep their original marker width or family.

Make orphan specifier attachment fence-aware so language-looking content
inside an outer fence is not attached to an inner literal fence. Document the
new normalization rule and keep the ExecPlan current with the delivered
behaviour and validation evidence.
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 24, 2026

@coderabbitai Have the following failed checks now been resolved?

If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures.

Assessment against linked issues

Issue Objective Addressed Explanation
#262 Update mdtablefix's fence-handling logic so that nested fenced code blocks are correctly tracked and formatted (e.g., triple backticks inside outer fences of four+ backticks or triple tildes are preserved and not mis-normalized). The PR only adds a documentation file (an ExecPlan) describing how to implement correct nested fence handling in the future. It does not modify any Rust source files or the existing fence-handling logic, so the actual bug in mdtablefix remains unfixed.
#262 Add or update tests (and any necessary documentation) to assert the correct behavior for nested fenced blocks so regressions are prevented. Although the new ExecPlan document outlines future test additions and acceptance criteria, this PR does not add or modify any test files. No new regression tests for nested fenced blocks are introduced, and existing tests are unchanged.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me inspect the codebase to craft a precise agent prompt.


🏁 Script executed:

#!/bin/bash
# Find relevant source files
fd -e rs | sort

echo "---FENCES.RS---"
cat src/fences.rs 2>/dev/null || echo "NOT FOUND"

echo "---FENCE TRACKER---"
fd fence_tracker -e rs --exec cat {}

echo "---TEST FILES---"
fd -e rs --search-path tests --exec cat {}

echo "---EXECPLAN DOC---"
cat docs/execplans/issue-262-nested_code_block-handling.md 2>/dev/null || echo "NOT FOUND"

Length of output: 151244


@leynos: Based on examining the actual branch code, the situation is significantly different from what the PR description states. The implementation appears to be complete on this branch — the PR description is stale. Here is what the branch actually contains:


src/fences.rs — Full implementation present

The file contains:

  • A FenceRewrite enum (Compress, PreserveDelimiters, PreserveAll)
  • analyze_opening — scans ahead using FenceTracker to detect conflicting inner fences
  • interior_fence_requires_preserved_delimiters — detects same-family inner fences
  • compress_fences — drives FenceTracker line-by-line, applies the correct FenceRewrite per block
  • attach_orphan_specifiers — tracks fence state and skips attachment inside open blocks

tests/fences.rs — Regression tests present

Including:

  • preserves_same_marker_nested_backtick_fences
  • preserves_same_marker_nested_tilde_fences
  • preserves_backtick_fences_nested_inside_tilde_fences
  • preserves_tilde_content_inside_backtick_fences
  • does_not_attach_orphan_specifier_inside_outer_fence
  • attaches_orphan_specifier_without_shortening_preserved_outer_fence

tests/cli_fences.rs — End-to-end CLI test present

  • test_cli_fences_preserves_nested_backtick_block

src/wrap/tests/fence_tracker.rsFenceTracker scenarios present

  • fence_tracker_keeps_outer_backticks_open_for_inner_triple_backticks
  • fence_tracker_keeps_longer_tildes_open_for_inner_triple_tildes
  • fence_tracker_ignores_other_marker_family_inside_outer_fence

Both objectives from issue #262 are structurally addressed. The PR description checkbox list is out of date. Update the PR description to reflect the implementation and test work that is already on the branch, and verify that cargo test --test fences, cargo test --test cli, make check-fmt, make lint, and make test all pass locally. If they do, the linked issue checks should be satisfied.

@leynos leynos changed the title Add ExecPlan for preserving nested fenced blocks (#262) Preserve nested fenced blocks (#262) Apr 24, 2026
@leynos leynos marked this pull request as ready for review April 24, 2026 18:17
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 5 issues, and left some high level feedback:

  • The new compress_fences implementation calls analyze_opening (which rescans from the current line forward with a fresh FenceTracker) for each line while not in a fence; consider restructuring to avoid this repeated forward scan (e.g., a single pass with state about the current block) to keep behavior linear on large inputs.
  • src/fences.rs now mixes FENCE_RE, wrap::is_fence, and FenceTracker to interpret fence lines; it may be worth consolidating on a single parsing/normalization entry point (or at least documenting the intended division of responsibility) to reduce the risk of subtle divergence between preprocessing and wrapping semantics.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `compress_fences` implementation calls `analyze_opening` (which rescans from the current line forward with a fresh `FenceTracker`) for each line while not in a fence; consider restructuring to avoid this repeated forward scan (e.g., a single pass with state about the current block) to keep behavior linear on large inputs.
- `src/fences.rs` now mixes `FENCE_RE`, `wrap::is_fence`, and `FenceTracker` to interpret fence lines; it may be worth consolidating on a single parsing/normalization entry point (or at least documenting the intended division of responsibility) to reduce the risk of subtle divergence between preprocessing and wrapping semantics.

## Individual Comments

### Comment 1
<location path="src/fences.rs" line_range="111-120" />
<code_context>
+    marker_ch == opening_ch || marker_ch == '`'
+}
+
+fn analyze_opening(lines: &[String], opening_index: usize) -> Option<OpeningAnalysis> {
+    let (_indent, opening_marker, _info) = is_fence(&lines[opening_index])?;
+
+    let mut tracker = FenceTracker::new();
+    if !tracker.observe(&lines[opening_index]) {
+        return None;
+    }
+
+    let mut has_conflicting_interior_fence = false;
+    for line in lines.iter().skip(opening_index + 1) {
+        let was_in_fence = tracker.in_fence();
+        let observed_fence = tracker.observe(line);
+        if observed_fence && was_in_fence && !tracker.in_fence() {
+            return Some(OpeningAnalysis {
+                rewrite: opening_rewrite(lines, opening_index, has_conflicting_interior_fence),
+            });
+        }
+        if was_in_fence && interior_fence_requires_preserved_delimiters(opening_marker, line) {
+            has_conflicting_interior_fence = true;
+        }
+    }
+
+    None
+}
+
</code_context>
<issue_to_address>
**suggestion (performance):** analyze_opening is re-run from the current index on every outer line, which can make compress_fences O(n²) on large inputs.

Because `compress_fences` calls `analyze_opening` for every line where `!tracker.in_fence()`, and `analyze_opening` scans forward from `opening_index + 1` to the closing fence (or EOF), the same ranges can be re-walked many times, yielding quadratic behavior on large inputs with many long fences. Consider moving this analysis into the main pass (e.g., track `has_conflicting_interior_fence` in `FenceTracker` or alongside it so each line is visited once) or caching results per `opening_index`.

Suggested implementation:

```rust

```

To fully implement the performance fix (avoid re-scanning ranges for each line and make the algorithm O(n)), you will also need to:

1. Move the “interior fence” analysis into the main `compress_fences` pass:
   - In `compress_fences`, track for the *current open fence*:
     - The `opening_index` (line index of the opening fence).
     - The `opening_marker` (backticks/tilde string from `is_fence`).
     - A `has_conflicting_interior_fence: bool` flag.
   - When you detect an opening fence (where `FenceTracker` transitions from `!in_fence` to `in_fence`), initialize:
     - `opening_index` to the current line index.
     - `opening_marker` from `is_fence(&lines[opening_index])`.
     - `has_conflicting_interior_fence = false`.
   - For each subsequent line while `tracker.in_fence()` is `true`, call `interior_fence_requires_preserved_delimiters(opening_marker, line)`, and if it returns `true`, set `has_conflicting_interior_fence = true`.

2. When the main `FenceTracker` detects the closing fence (transition from `in_fence == true` to `false`):
   - Use the stored `opening_index` and `has_conflicting_interior_fence` to compute the `OpeningAnalysis`:
     - Call `opening_rewrite(lines, opening_index, has_conflicting_interior_fence)` at that point.
   - Apply the rewrite logic directly in `compress_fences` instead of calling `analyze_opening`.

3. Remove all remaining calls to `analyze_opening` (they will now be replaced by the logic described above in the main loop).
   - If the current code uses `OpeningAnalysis` as a separate step, inline its usage into `compress_fences` when you detect the closing fence.

4. If `OpeningAnalysis` is now only used internally within `compress_fences` and doesn’t need to be a separate abstraction, you may also consider simplifying or removing that type to reduce complexity.
</issue_to_address>

### Comment 2
<location path="tests/fences.rs" line_range="29-38" />
<code_context>
     assert!(tracker.in_fence());
 }

+#[test]
+fn fence_tracker_keeps_outer_backticks_open_for_inner_triple_backticks() {
+    let mut tracker = FenceTracker::new();
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding a regression where an orphan specifier appears deeper inside the fenced content, not just immediately after the outer opener.

The new test confirms we don’t attach an orphan specifier between the outer fence and an inner fenced block. To fully cover this behavior, please also add a case where the orphan-like line appears after the inner opening fence (e.g., the `"Rust"` line in your example) and assert that `attach_orphan_specifiers(&compress_fences(&input))` returns `input` unchanged. That will verify we never attach specifiers anywhere inside an active (possibly nested) fence.
</issue_to_address>

### Comment 3
<location path="src/wrap/tests/fence_tracker.rs" line_range="44-53" />
<code_context>
     assert!(tracker.in_fence());
 }

+#[test]
+fn fence_tracker_keeps_outer_backticks_open_for_inner_triple_backticks() {
+    let mut tracker = FenceTracker::new();
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding the symmetric case with outer tildes and inner backticks to fully document cross-family behavior.

The new test covers outer backticks with inner tildes. To keep the behavior symmetric and guard against regressions, please also add the mirror case with outer tildes and inner backticks, e.g.:

```rust
#[test]
fn fence_tracker_ignores_backticks_inside_outer_tilde_fence() {
    let mut tracker = FenceTracker::default();
    assert!(tracker.observe("~~~~"));
    assert!(tracker.in_fence());
    assert!(tracker.observe("```"));
    assert!(tracker.in_fence());
    assert!(tracker.observe("~~~~"));
    assert!(!tracker.in_fence());
}
```
</issue_to_address>

### Comment 4
<location path="tests/cli_fences.rs" line_range="7-8" />
<code_context>
     assert!(tracker.in_fence());
 }

+#[test]
+fn fence_tracker_keeps_outer_backticks_open_for_inner_triple_backticks() {
+    let mut tracker = FenceTracker::new();
</code_context>
<issue_to_address>
**suggestion (testing):** Extend CLI regression tests to cover tilde-based outer fences and a case where the outer fence is compressed while preserving nested content.

To parallel the ExecPlan tests and strengthen end-to-end coverage, consider also adding:

1. A tilde-based variant (e.g., `~~~~markdown` with an inner ``` block) to verify CLI behavior matches `compress_fences` unit tests.
2. A case where the outer tilde fence is actually compressed (e.g., `~~~~markdown` → ```markdown) while still preserving the inner literal fence.

This will confirm the CLI flow enforces both compression and nested-fence preservation, not just the non-rewrite path.
</issue_to_address>

### Comment 5
<location path="src/fences.rs" line_range="71" />
<code_context>
+    PreserveAll,
+}
+
+struct OpeningAnalysis {
+    rewrite: FenceRewrite,
+}
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying the fence analysis and rewrite helpers by inlining `OpeningAnalysis` into `analyze_opening` and consolidating the marker-handling functions into a single core helper while preserving behavior.

You can trim a fair bit of complexity here without changing behavior by:

### 1. Removing the extra `OpeningAnalysis` wrapper and simplifying `analyze_opening`

`OpeningAnalysis` only wraps a `FenceRewrite`, and `analyze_opening` constructs a new `FenceTracker` for a second pass. You can both:

* return `Option<FenceRewrite>` directly, and  
* avoid creating a new `FenceTracker` by using a minimal local state (fence depth + closing index) when looking ahead.

For example:

```rust
fn analyze_opening(lines: &[String], opening_index: usize) -> Option<FenceRewrite> {
    let (_indent, opening_marker, _info) = is_fence(&lines[opening_index])?;

    let mut depth = 1;
    let mut has_conflicting_interior_fence = false;

    for line in &lines[opening_index + 1..] {
        if let Some((_indent, marker, _info)) = is_fence(line) {
            if depth == 1 && interior_fence_requires_preserved_delimiters(opening_marker, line) {
                has_conflicting_interior_fence = true;
            }
            // naive depth tracking: same marker opens, same marker closes
            if marker == opening_marker {
                depth -= 1;
                if depth == 0 {
                    return Some(opening_rewrite(
                        lines,
                        opening_index,
                        has_conflicting_interior_fence,
                    ));
                }
            } else {
                depth += 1;
            }
        }
    }

    None
}
```

Then call it directly from `compress_fences`:

```rust
if !tracker.in_fence() {
    if let Some(rewrite) = analyze_opening(lines, index) {
        let _ = tracker.observe(line);
        active_rewrite = Some(rewrite);
        out.push(rewrite_fence_line(line, rewrite));
    } else {
        out.push(compressed_fence_line(line).unwrap_or_else(|| line.clone()));
    }
    continue;
}
```

And drop `OpeningAnalysis` entirely:

```rust
// remove:
struct OpeningAnalysis {
    rewrite: FenceRewrite,
}
```

This keeps all behavior (including detection of conflicting interior fences) but removes the nested `FenceTracker` and one layer of indirection.

---

### 2. Flattening the fence rewriting helpers

`fence_line_with_marker`, `compressed_fence_line`, and `preserved_fence_line` are a small chain around the same regex. You can make the core behavior visible with a single helper and a simple enum:

```rust
enum MarkerStrategy<'a> {
    Compressed,          // always ``` 
    PreserveDelimiter,   // original marker
}

fn rewrite_marker(line: &str, strategy: MarkerStrategy<'_>) -> Option<String> {
    let cap = FENCE_RE.captures(line)?;
    let indent = cap.get(1).map_or("", |m| m.as_str());
    let orig_marker = cap.get(2).map_or("", |m| m.as_str());
    let lang = cap.get(3).map_or("", |m| m.as_str());

    if is_null_lang(lang) {
        let marker = match strategy {
            MarkerStrategy::Compressed => "```",
            MarkerStrategy::PreserveDelimiter => orig_marker,
        };
        Some(format!("{indent}{marker}"))
    } else {
        let marker = match strategy {
            MarkerStrategy::Compressed => "```",
            MarkerStrategy::PreserveDelimiter => orig_marker,
        };
        Some(format!("{indent}{marker}{lang}"))
    }
}
```

Then:

```rust
fn compressed_fence_line(line: &str) -> Option<String> {
    rewrite_marker(line, MarkerStrategy::Compressed)
}

fn preserved_fence_line(line: &str) -> Option<String> {
    rewrite_marker(line, MarkerStrategy::PreserveDelimiter)
}
```

This keeps the same public helpers but removes the extra hop through `fence_line_with_marker` and makes the “indent + marker + lang” logic sit in one obvious place.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/fences.rs Outdated
Comment thread tests/fences.rs Outdated
Comment thread src/wrap/tests/fence_tracker.rs Outdated
Comment thread tests/cli_fences.rs
Comment thread src/fences.rs Outdated
@coderabbitai coderabbitai Bot added the Issue label Apr 24, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/architecture.md`:
- Around line 31-35: Replace the ambiguous phrase "The latter keeps indentation
from the language line when the fence lacks it." with an explicit reference to
the attach_orphan_specifiers function: state that attach_orphan_specifiers is
responsible for preserving/propagating indentation from the language line when a
fence lacks it, so the sentence clearly attributes that behavior to
attach_orphan_specifiers rather than to outer-delimiter compression.

In `@docs/execplans/issue-262-nested-code-block-handling.md`:
- Around line 291-303: Update the ExecPlan text in Stage A so that follow-up
work points to tests/cli_fences.rs (where the CLI regression was implemented)
instead of tests/cli.rs; locate the reference to "tests/cli.rs" in the Stage A
paragraph (search for the phrase or the Stage A header) and replace it with
"tests/cli_fences.rs" to keep the plan consistent with the implemented CLI
regression.
- Around line 176-178: The paragraph has an inline code span for the fence token
that was split across lines, breaking the backtick sequence (e.g. the outer
fence marker like ```` and inner marker like ```); fix it by rejoining the split
backtick span so the entire inline code span (the full sequence of backticks)
appears intact on one line. Ensure the wrapping logic treats inline code spans
(`…`) as atomic: move the whole backtick span to the next line rather than
splitting it, so the rewritten fence token remains a single unbroken code span.

In `@src/fences.rs`:
- Around line 161-166: The doc comment for compress_fences is stale: it claims
“Lines that do not start with backtick fences are returned unchanged,” which is
incorrect because safe tilde fences can still be rewritten to backticks; update
the comment for compress_fences in src/fences.rs to describe behavior accurately
by saying non-fence lines (or lines that are not fence delimiters) are returned
unchanged, and clarify that safe tilde/other fence delimiters may be rewritten
to three backticks when shortened—adjust the surrounding sentences to reflect
that only non-fence content is unchanged while compatible fence formats may be
normalized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c6f0ed34-10a6-4271-95b9-5052afd56842

📥 Commits

Reviewing files that changed from the base of the PR and between 2de1134 and 2acdf8b.

📒 Files selected for processing (6)
  • docs/architecture.md
  • docs/execplans/issue-262-nested-code-block-handling.md
  • src/fences.rs
  • src/wrap/tests/fence_tracker.rs
  • tests/cli_fences.rs
  • tests/fences.rs

Comment thread docs/architecture.md Outdated
Comment thread docs/execplans/issue-262-nested-code-block-handling.md Outdated
Comment thread docs/execplans/issue-262-nested-code-block-handling.md
Comment thread src/fences.rs Outdated
leynos added 2 commits April 24, 2026 20:52
Replace the repeated forward scan in `compress_fences` with a single-pass
pending-block implementation. The current open block now carries the marker
and conflict state needed to decide whether its delimiters can be rewritten.

Consolidate marker rewriting behind one helper and document how preprocessing
uses `FENCE_RE` for normalization while relying on `FenceTracker` for shared
structural fence semantics.

Add regressions for deeper orphan-like content inside fences, symmetric
outer-tilde tracker behaviour, and CLI nested-fence variants.
Clarify that `attach_orphan_specifiers` is responsible for carrying language
line indentation onto unlabeled fences. Update the ExecPlan to point follow-up
CLI coverage at `tests/cli_fences.rs` and keep inline fence marker code spans
intact after wrapping.

Adjust the `compress_fences` Rustdoc so it describes non-fence lines as
unchanged while allowing safe backtick and tilde delimiter normalization.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/issue-262-nested-code-block-handling.md`:
- Around line 384-394: The docs currently direct testers to run the CLI test
target `cargo test --test cli` but the regression and ExecPlan point to the
specific test file `tests/cli_fences.rs`; update the example commands and
acceptance criteria to run the focused test (`cargo test --test cli_fences` or
target the `cli_fences.rs` test file) and mention `tests/cli_fences.rs`
explicitly so the documented validation path matches the implemented
nested-fence CLI tests; adjust the two occurrences noted (around the shown block
and lines ~435-436) so all references and example commands use the `cli_fences`
test target.

In `@src/fences.rs`:
- Around line 183-197: The code fails to enter fenced-state tracking for
openings recognized by wrap::is_fence when FENCE_RE is narrower; change the
control flow so you call is_fence(line) first and, if it returns Some, call
tracker.observe(line) and create the PendingFenceBlock (using opening_marker
from is_fence) before any FENCE_RE-based short-circuit; otherwise fall back to
the existing compressed_fence_line/continue path. Update the logic around
tracker.in_fence(), FENCE_RE.captures(line), is_fence, tracker.observe,
pending_block and PendingFenceBlock accordingly, and add a regression test that
opens a fence with spaced info (e.g., "``` rust") containing inner fence-like
lines to confirm inner fence-like lines are not rewritten as top-level fences.

In `@src/wrap/tests/fence_tracker.rs`:
- Around line 44-55: Multiple nearly identical tests for nested fence behavior
should be consolidated into a single parameterized rstest: replace the
individual test functions (e.g.,
fence_tracker_keeps_outer_backticks_open_for_inner_triple_backticks and the
other duplicate nested-fence tests) with one #[rstest] function that takes
parameters for outer marker, inner marker, and expected final in_fence state and
add #[case(...)] rows for backticks vs tildes and outer vs inner positions;
inside the test call FenceTracker::new(), then call tracker.observe(...) in the
same sequence used by the originals (observe outer start, assert in_fence,
observe inner start, assert in_fence, observe inner end, assert in_fence,
observe outer end, assert final in_fence) using the passed markers and assert
via tracker.in_fence() after each observe so all variants are covered by cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 00628922-3998-4c62-b399-5fb3755293f2

📥 Commits

Reviewing files that changed from the base of the PR and between 2acdf8b and bf72490.

📒 Files selected for processing (5)
  • docs/execplans/issue-262-nested-code-block-handling.md
  • src/fences.rs
  • src/wrap/tests/fence_tracker.rs
  • tests/cli_fences.rs
  • tests/fences.rs

Comment thread docs/execplans/issue-262-nested-code-block-handling.md
Comment thread src/fences.rs
Comment thread src/wrap/tests/fence_tracker.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/fences.rs (1)

185-194: ⚠️ Potential issue | 🟠 Major

Track structural openings before applying the rewrite regex.

Call is_fence(line) first and enter FenceTracker for every structural opening, even when FENCE_RE cannot normalise that delimiter. As written, an opening such as ``` rust falls through the early FENCE_RE guard, leaves the tracker outside the block, and lets later literal inner fence lines be rewritten as top-level fences. Add a regression for a spaced-info outer fence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fences.rs` around lines 185 - 194, The guard using FENCE_RE before
updating the FenceTracker causes structural openings to be missed (e.g., "```
rust" with spacing) so move the is_fence(line) check ahead of the FENCE_RE
early-return: call is_fence(line) first, and when it returns Some((_indent,
opening_marker, _info)) always call tracker.observe(line) / enter the
fence-tracking branch so the tracker knows we are inside a fence even if
FENCE_RE.captures(line) is None; keep the compressed_fence_line fallback for
non-structural lines, and add a regression test that exercises a spaced-info
outer fence like "``` rust" to prevent future regressions.
docs/execplans/issue-262-nested-code-block-handling.md (1)

388-398: ⚠️ Potential issue | 🟡 Minor

Point the documented CLI validation path at tests/cli_fences.rs.

Replace cargo test --test cli ... here with the cli_fences target. The nested-fence regression is documented elsewhere in this plan as living in tests/cli_fences.rs, so the current completion path can pass without exercising the issue-specific CLI coverage.

Triage: [type:docstyle]

As per coding guidelines, "Ensure that any API or behavioural changes are reflected in the documentation in docs/."

Also applies to: 439-440

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/execplans/issue-262-nested-code-block-handling.md` around lines 388 -
398, Update the documented CLI validation commands to target the specific test
binary that covers the nested-fence regression: replace each occurrence of
"cargo test --test cli" with "cargo test --test cli_fences" so the CLI
validation runs the tests in tests/cli_fences.rs (including the nested-fence
case); ensure both the earlier command and the other occurrences referenced
(including the pair noted at the end of the file) are updated so the documented
steps actually exercise the nested-fence regression.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docs/execplans/issue-262-nested-code-block-handling.md`:
- Around line 388-398: Update the documented CLI validation commands to target
the specific test binary that covers the nested-fence regression: replace each
occurrence of "cargo test --test cli" with "cargo test --test cli_fences" so the
CLI validation runs the tests in tests/cli_fences.rs (including the nested-fence
case); ensure both the earlier command and the other occurrences referenced
(including the pair noted at the end of the file) are updated so the documented
steps actually exercise the nested-fence regression.

In `@src/fences.rs`:
- Around line 185-194: The guard using FENCE_RE before updating the FenceTracker
causes structural openings to be missed (e.g., "``` rust" with spacing) so move
the is_fence(line) check ahead of the FENCE_RE early-return: call is_fence(line)
first, and when it returns Some((_indent, opening_marker, _info)) always call
tracker.observe(line) / enter the fence-tracking branch so the tracker knows we
are inside a fence even if FENCE_RE.captures(line) is None; keep the
compressed_fence_line fallback for non-structural lines, and add a regression
test that exercises a spaced-info outer fence like "``` rust" to prevent future
regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cac20dd3-2d96-4918-b73a-2dfbbd1c337c

📥 Commits

Reviewing files that changed from the base of the PR and between bf72490 and 42ac95e.

📒 Files selected for processing (3)
  • docs/architecture.md
  • docs/execplans/issue-262-nested-code-block-handling.md
  • src/fences.rs

@leynos

This comment was marked as resolved.

Use `wrap::is_fence` before the narrower rewrite regex so spaced info
strings still enter fenced-state tracking. This prevents nested
fence-like content inside those blocks from being treated as top-level
fences.

Add the focused regression, consolidate nested `FenceTracker` cases into
an `rstest` table, and update the ExecPlan validation path to name the
`cli_fences` target. Include Markdown normalization produced by the
required formatting gate.
@coderabbitai

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@AGENTS.md`:
- Around line 241-242: Reflow the sentence so the parenthesis and the backticked
tilde remain together (keep the phrase "Tilde requirements (`~`)" intact) by
adjusting line breaks/wrapping so the opening parenthesis is adjacent to the
backticked tilde; after editing, run make fmt and make markdownlint to validate
formatting.
- Around line 234-235: Reflow the sentence that mentions Cargo.toml so the
parenthetical example stays intact (e.g., `(e.g., `some-crate = "1.2.3"`)`) or
move the line break to occur before the opening parenthesis; remove the stray
leading space at the start of the line containing the example, ensure the
caret/SemVer example `some-crate = "1.2.3"` remains adjacent to `(e.g.,` for
readability, and run `make fmt` to normalize Markdown formatting.

In `@docs/execplans/issue-262-nested-code-block-handling.md`:
- Around line 3-5: Reflow the preamble paragraph so no line exceeds 80
characters by inserting breaks between the comma-separated section names; keep
the inline code formatting for `Constraints`, `Tolerances`, `Risks`, `Progress`,
`Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective`,
producing multiple wrapped lines that read naturally and preserve punctuation
and backticks.
- Around line 456-457: In the sentence within the docs change describing
attach_orphan_specifiers, insert a comma before the coordinating conjunction
"so" so the line reads "...active fenced blocks, so specifier-like lines inside
an outer fence remain content." This update touches the text around the
identifier attach_orphan_specifiers in the affected paragraph; add the comma to
join the two independent clauses correctly.
- Around line 91-93: The bullet point in the
docs/execplans/issue-262-nested-code-block-handling.md file exceeds the
80-column wrap rule; rewrap the sentence that begins with "`tests/cli_fences.rs`
for additional" so no line is longer than 80 characters, breaking the
continuation into multiple wrapped lines while preserving the Markdown bullet,
link text [`tests/cli_fences.rs`](../../tests/cli_fences.rs), and the original
meaning (e.g., wrap after the link or before "because" so the rest of the clause
continues on the next indented line).
- Around line 239-242: In the docs sentence referencing compress_fences and
wrap::is_fence, change the British-spelling "recognised" to the Oxford-preferred
"-ize" form "recognized"; update the phrase so it reads that openings are
"recognized by wrap::is_fence" (leave references to compress_fences, FENCE_RE,
FenceTracker, rstest and tests/cli_fences.rs unchanged).
- Around line 335-338: Reflow the paragraph containing the sentence starting
with "one case that shows `attach_orphan_specifiers`..." so no line exceeds 80
characters; wrap the prose into multiple lines at word boundaries to keep each
line ≤80 columns and preserve the backticked identifier
`attach_orphan_specifiers` and the reference to `tests/cli_fences.rs` and
`--fences` intact.

In `@docs/release-process.md`:
- Around line 10-12: Rewrap the paragraph mentioning the MSRV so the opening
parenthesis doesn't sit alone at line end: move the parenthetical `rust-version
= "1.89"` (or the opening paren) onto the same line as its content so the
sentence reads like "This Minimum Supported Rust Version (MSRV) is also declared
in `Cargo.toml` (`rust-version = \"1.89\`). The `build-test` job in
`.github/workflows/ci.yml` uses this toolchain to guard against regressions."
Adjust the line breaks around "MSRV", "`Cargo.toml`", and the parenthesis to
keep the delimiter with its content for improved readability.

In `@docs/rust-doctest-dry-guide.md`:
- Around line 203-204: The footnote marker `[^9]` was split across lines causing
it to render as text; edit the paragraph containing "scaffolding can be hidden,
presenting the user with only the relevant code." and join the split marker so
the entire `[^9]` appears on the same line immediately after the sentence (no
spaces or line breaks inside the `[^9]` token) to restore proper Markdown
footnote rendering.
- Around line 107-109: Update all doctest code fences in the document to include
an explicit Rust language specifier by changing bare fenced blocks to
triple-backtick fences with "rust" (e.g., ```rust) so examples match the
guidance; ensure every occurrence of bare fences or "Rust"-cased examples is
converted to ```rust and that any inconsistent tilded fences (~~~) are
standardized to the triple-backtick format; review paragraphs discussing
rustdoc/doctest to confirm the examples now obey the `rust`-specifier rule.

In `@docs/rust-testing-with-rstest-fixtures.md`:
- Around line 28-29: The footnote marker is currently attached to the start of
the example paragraph; move the GitHub-flavoured Markdown footnote marker `[^1]`
from the beginning of the sentence that starts on Line 29 to the end of the
preceding claim sentence ("Fixtures address this by encapsulating these
dependencies and their setup logic.") and then remove the leading reference
token from the example sentence so it begins normally ("For instance, if
multiple tests require a logged-in user object or a..."); ensure the footnote
remains `[^1]` and that the example sentence starts without any leading footnote
token.
- Around line 771-772: The footnote marker has been split across two lines (`[`
on one line and `^4]` on the next) which can break GFM parsing; fix by making
the footnote token contiguous (replace the line break so the text reads "needs
to be `.await`ed explicitly within the test body or by using `#[awt][^4]`" or
move `[^4]` directly after the clause) so `[^4]` is a single token on one line,
then run markdownlint to confirm no syntax warnings.

In `@src/fences.rs`:
- Around line 184-223: The code currently treats any is_fence opener as
potentially rewritable and may compress only the closing delimiter, breaking
mismatched fences; fix by recording whether the opener is rewritable when
creating PendingFenceBlock (e.g., set a flag like opening_rewritable =
FENCE_RE.is_match(&opening_marker) in the PendingFenceBlock alongside
opening_marker/lines), then when you finish observing a fence pair (before
calling flush_matched_block), only call flush_matched_block if both the opener’s
opening_rewritable flag and the closing delimiter are rewritable; otherwise push
the original buffered block.lines unchanged (preserving both outer delimiters).
Ensure you use the existing symbols is_fence, PendingFenceBlock, opening_marker,
tracker.observe, interior_fence_requires_preserved_delimiters, and
flush_matched_block to locate where to add the flag and the conditional flush,
and add a regression for a four-backtick spaced-info opener.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: df45a1e8-d687-40f7-bf4d-44937b00f564

📥 Commits

Reviewing files that changed from the base of the PR and between 42ac95e and acd58b7.

📒 Files selected for processing (12)
  • AGENTS.md
  • CHANGELOG.md
  • docs/architecture.md
  • docs/execplans/issue-262-nested-code-block-handling.md
  • docs/execplans/replace-bespoke-wrapping-with-textwrap-and-unicode-width.md
  • docs/execplans/yaml-frontmatter.md
  • docs/release-process.md
  • docs/rust-doctest-dry-guide.md
  • docs/rust-testing-with-rstest-fixtures.md
  • src/fences.rs
  • src/wrap/tests/fence_tracker.rs
  • tests/fences.rs

Comment thread AGENTS.md Outdated
Comment thread AGENTS.md Outdated
Comment thread docs/execplans/issue-262-nested-code-block-handling.md Outdated
Comment thread docs/execplans/issue-262-nested-code-block-handling.md Outdated
Comment thread docs/execplans/issue-262-nested-code-block-handling.md Outdated
Comment thread docs/rust-doctest-dry-guide.md
Comment thread docs/rust-doctest-dry-guide.md Outdated
Comment thread docs/rust-testing-with-rstest-fixtures.md Outdated
Comment thread docs/rust-testing-with-rstest-fixtures.md Outdated
Comment thread src/fences.rs Outdated
leynos added 2 commits April 26, 2026 19:47
Update user-facing and developer documentation for the `--fences`
behaviour introduced by the nested-fence handling work. Describe when
outer delimiters are compressed, when their width is preserved, and how
orphan language specifiers are skipped inside active fenced blocks.
Avoid rewriting only one side of a matched fenced block when either outer
delimiter is outside the supported normalization regex. This keeps spaced
info-string fences intact while preserving existing legacy handling for
unmatched blocks.

Also address review comments in Markdown documentation by keeping inline
examples, tilde markers, and footnote references contiguous after formatting.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/users-guide.md`:
- Around line 60-63: Edit the sentence starting "Outer delimiters are compressed
only when doing so is structurally safe..." and insert a comma before "so" in
the clause "the outer delimiter width is kept so the inner content does not
become a structural close", changing it to "the outer delimiter width is kept,
so the inner content does not become a structural close" to correctly punctuate
the compound sentence.

In `@README.md`:
- Line 64: Update the sentence "Use `--fences` to normalize code block
delimiters." by changing the American spelling "normalize" to the British Oxford
spelling "normalise" so it reads "Use `--fences` to normalise code block
delimiters." Ensure this exact phrase in README.md is replaced to comply with
en-GB-oxendict.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d8161584-767d-43c7-be60-a52dae90bd72

📥 Commits

Reviewing files that changed from the base of the PR and between acd58b7 and ed87a0b.

📒 Files selected for processing (3)
  • README.md
  • docs/developers-guide.md
  • docs/users-guide.md

Comment thread docs/users-guide.md Outdated
Comment thread README.md
Add the missing comma in the user guide's `--fences` preservation rule so the compound sentence reads cleanly.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Tip

For best results, initiate chat on the files or code changes.

@leynos: Both previously-failing checks appear to be resolved on this branch. Here is the status based on the updated files:

Check Status Evidence
User-Facing Documentation ✅ Resolved README.md now describes conditional normalisation semantics (outer fences compressed only when safe, preserved when same-marker nesting conflicts). docs/users-guide.md gains a new ## Fence normalisation section with a before/after example and descriptions of the preservation and orphan-specifier rules.
Developer Documentation ✅ Resolved docs/developers-guide.md gains a new ## Fence normalisation module section documenting compress_fences and attach_orphan_specifiers with FenceTracker-driven semantics noted and a cross-reference to docs/architecture.md. The ExecPlan Progress section was already fully checked off in the previous iteration.

No further work should be required for these two checks. Verify that make markdownlint and make nixie both exit cleanly to confirm no Markdown lint regressions were introduced by the new prose.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@AGENTS.md`:
- Around line 234-235: Reflow the sentence containing "caret requirements" so
the opening parenthesis and example opener remain adjacent to their referent:
move "(e.g.," so the phrase reads "caret requirements (e.g.," on the same line
and place the rest of the example on the following line; after editing the
AGENTS.md sentence, run make fmt and make markdownlint to validate formatting.

In `@docs/developers-guide.md`:
- Around line 89-99: The docs currently understate compress_fences behaviour;
update the compress_fences entry to explicitly document the
backtick-inside-tilde case: state that when an inner backtick-fenced block would
become structural after normalization, compress_fences preserves the outer
tilde-fence width and marker family (i.e., it protects a tilde outer fence from
being converted when an inner backtick fence would be structural), and replace
or augment the phrase "same-marker inner fences" to include this cross-marker
(backtick-inside-tilde) exception; also add a short note in the
FenceTracker/attach_orphan_specifiers description that the scanner logic
intentionally skips attachment in such nested cases so readers can correlate the
behaviour with the tests.

In `@docs/execplans/issue-262-nested-code-block-handling.md`:
- Around line 201-247: Add a final Progress item under the existing "Progress"
list noting that the user guide and developer guide were updated and that
documentation gates (e.g. markdownlint and any doc build/validation commands
run) were executed, and set the ExecPlan Status to "COMPLETE"; update the prose
in this document's header/Status field accordingly and ensure the new checklist
entry mirrors the format of prior timestamps and checks so it appears as a
completed item in the roadmap.
- Around line 20-40: Update the example nested code-blocks so the outer fence
uses four backticks in both the "observable success case" and the corrected
output; specifically change the outer triple-backtick fences shown in the diffed
sample to four backticks so the inner ```rust``` block remains literal, and
likewise ensure the prose that follows refers to preserving outer four-backtick
fences (and analogous tilde fences like `~~~~`/`~~~`) to match the corrected
example.

In `@docs/release-process.md`:
- Around line 11-12: The inline code span `.github/workflows/ci.yml` in the
paragraph containing (`rust-version = "1.89"`) is currently split across lines;
rewrap that paragraph so the entire code span stays intact on the same line as
the preceding sentence, producing: "(`rust-version = \"1.89\"`). The
`build-test` job in `.github/workflows/ci.yml`" on one line and place the
remainder "uses this toolchain to guard against regressions." on the following
line, preserving the 80-column limit and treating the code span as an
unbreakable unit.

In `@docs/rust-testing-with-rstest-fixtures.md`:
- Around line 1279-1281: Rewrap the paragraph containing the phrase shown in the
diff so the compound term stays intact by replacing the split "`rstest`" and
"-generated" with a single "`rstest`-generated" token; locate the sentence
starting with "wrappers to ensure that logging is correctly set up before each
`rstest`" and merge the broken line breaks so it reads "...before each
`rstest`-generated test case runs, making it easier to get consistent log output
from tests." Ensure only whitespace/line breaks are changed, preserving the
original wording.
- Line 1288: The phrase "can be used in conjunction with `rstest`" is wordy — in
the sentence containing that exact string replace it with a concise alternative
such as "can be used with `rstest`" or "can be used alongside `rstest`" to
simplify the wording while preserving meaning; update the sentence in
docs/rust-testing-with-rstest-fixtures.md where the phrase appears.

In `@docs/users-guide.md`:
- Around line 60-68: Update the documentation for the mixed-marker preservation
rule to state that --fences preserves an outer tilde-fenced block when a literal
inner backtick fence would become structural after normalization (not just when
the inner and outer use the “same marker family”); edit the paragraph that
currently reads "same marker family" and expand it to explicitly mention the
tilde-outer / backtick-inner case and that any outer fence is kept when
normalization would otherwise turn an inner literal fence into a structural
close; also ensure the orphan-specifier sentence remains unchanged and that
examples or test references (if any) mention this mixed-marker preservation
behaviour for --fences.

In `@src/fences.rs`:
- Around line 167-174: Update the doc comment for compress_fences to state the
guarantee only applies to matched fenced blocks and explicitly mention the
unmatched-block fallback behavior used by flush_unmatched_block; reference the
compress_fences function and flush_unmatched_block helper so readers know that
when input ends with an unclosed fence the code falls back to stateless per-line
rewriting and fence-like lines inside an unclosed block may be rewritten rather
than preserved.

In `@tests/fences.rs`:
- Around line 29-72: These tests duplicate the same pattern and should be
collapsed into an rstest parameterized case: replace the multiple #[test]
functions (preserves_same_marker_nested_backtick_fences,
preserves_same_marker_nested_tilde_fences,
preserves_backtick_fences_nested_inside_tilde_fences,
preserves_tilde_content_inside_backtick_fences,
preserves_inner_fences_inside_spaced_info_fence,
preserves_four_backtick_spaced_info_outer_fence) with a single #[rstest] that
supplies tuples of input (built with lines_vec!) and expected output, then call
compress_fences(&input) and assert_eq!(out, expected) for each case; keep using
the same helpers (lines_vec!, compress_fences) and preserve the case where
expected equals input versus the single case with a different expected vector.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4bbac326-c72b-4c04-8e4d-41363ea965e0

📥 Commits

Reviewing files that changed from the base of the PR and between ed87a0b and 98bf38d.

📒 Files selected for processing (9)
  • AGENTS.md
  • docs/developers-guide.md
  • docs/execplans/issue-262-nested-code-block-handling.md
  • docs/release-process.md
  • docs/rust-doctest-dry-guide.md
  • docs/rust-testing-with-rstest-fixtures.md
  • docs/users-guide.md
  • src/fences.rs
  • tests/fences.rs

Comment thread AGENTS.md Outdated
Comment thread docs/developers-guide.md Outdated
Comment thread docs/execplans/issue-262-nested-code-block-handling.md
Comment thread docs/execplans/issue-262-nested-code-block-handling.md
Comment thread docs/release-process.md Outdated
Comment thread docs/rust-testing-with-rstest-fixtures.md Outdated
Comment thread docs/rust-testing-with-rstest-fixtures.md Outdated
Comment thread docs/users-guide.md Outdated
Comment thread src/fences.rs Outdated
Comment thread tests/fences.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
docs/users-guide.md (1)

60-63: ⚠️ Potential issue | 🟡 Minor

Insert comma before "so" and document mixed-marker preservation.

Line 63 joins two independent clauses with "so" but lacks a comma. Additionally, the phrase "same marker family" does not reflect the implemented behaviour: from src/fences.rs:112-123, preservation occurs when marker_ch == opening_ch || marker_ch == ''`, meaning a tilde-fenced outer block with a backtick-fenced interior also triggers preservation, not just same-marker scenarios.

Triage: [type:grammar] [type:docstyle]

📝 Proposed fix
 Outer delimiters are compressed only when doing so is structurally safe. If a
-four-or-more-backtick fence, or a longer tilde fence, wraps literal inner
-fence-like content from the same marker family, the outer delimiter width is
-kept, so the inner content does not become a structural close after formatting.
+four-or-more-backtick fence wraps literal inner backtick content, or a longer
+tilde fence wraps literal inner backtick or tilde content, the outer delimiter
+width is kept, so the inner content does not become a structural close after
+formatting.

As per coding guidelines, "Use a comma before 'so' if it connects two independent clauses" and "Ensure that any API or behavioural changes are reflected in the documentation."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/users-guide.md` around lines 60 - 63, Add a comma before "so" in the
sentence to separate the two independent clauses, and update the phrase "same
marker family" to accurately describe the implemented rule in src/fences.rs (see
the logic around marker_ch and opening_ch in lines ~112-123) by stating that
preservation also occurs when marker_ch == opening_ch or when marker_ch == '`'
(i.e., a backtick inner fence inside a tilde outer fence is preserved), ensuring
the docs reflect mixed-marker preservation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/users-guide.md`:
- Around line 70-84: Add a second example showing successful compression: insert
a short block after the existing fence-preservation example that demonstrates a
4-backtick outer fence whose interior contains no conflicting 3-backtick fence,
then show the "After running `mdtablefix --fences`" result where the outer fence
is compressed to three backticks; reference the existing demonstration of
delimiter preservation and use the same narrative phrasing ("Before:" and "After
running `mdtablefix --fences`:") so readers see both compression and
preservation behaviors for `mdtablefix --fences`.
- Line 54: The heading "Fence normalisation" uses -ise spelling; change it to
en-GB-Oxford style with -ize by renaming the heading to "Fence normalization"
(update the heading text in docs/users-guide.md where the string "Fence
normalisation" appears).
- Around line 56-58: Replace the British -ise spelling "normalise" with the
required en-GB-oxendict -ize form "normalize" in the sentence that starts "Pass
`--fences` to normalise fenced code block delimiters..." in docs/users-guide.md
so the documentation follows the project's -ize spelling guideline.

---

Duplicate comments:
In `@docs/users-guide.md`:
- Around line 60-63: Add a comma before "so" in the sentence to separate the two
independent clauses, and update the phrase "same marker family" to accurately
describe the implemented rule in src/fences.rs (see the logic around marker_ch
and opening_ch in lines ~112-123) by stating that preservation also occurs when
marker_ch == opening_ch or when marker_ch == '`' (i.e., a backtick inner fence
inside a tilde outer fence is preserved), ensuring the docs reflect mixed-marker
preservation behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bed254d2-1a5e-4fbb-8dd0-973e1cd53712

📥 Commits

Reviewing files that changed from the base of the PR and between 98bf38d and 6d8f43f.

📒 Files selected for processing (1)
  • docs/users-guide.md

Comment thread docs/users-guide.md Outdated
Comment thread docs/users-guide.md Outdated
Comment thread docs/users-guide.md
leynos added 2 commits April 26, 2026 20:59
Clarify that `compress_fences` protects tilde outer fences when an inner
backtick fence would become structural after normalization. Document the
unmatched-block fallback and keep the ExecPlan examples aligned with the
four-backtick preservation case.

Collapse repeated nested-fence tests into one parameterized `rstest` table and
clean up the requested Markdown wrapping issues.
Use Oxford -ize spelling in the fence section, make the mixed-marker
preservation rule explicit, and add a matching successful compression example
so `--fences` documents both safe rewrite and preservation outcomes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
tests/fences.rs (1)

87-109: 🛠️ Refactor suggestion | 🟠 Major

Collapse these orphan-specifier regressions into one rstest matrix.

Fold these three tests into one parameterized case. The bodies are identical apart
from the fixtures, and the same table-driven pattern is already in use from
Line 29.

Triage: [type:docstyle]

♻️ Proposed refactor
-#[test]
-fn does_not_attach_orphan_specifier_inside_outer_fence() {
-    let input = lines_vec!["````markdown", "Rust", "```", "fn main() {}", "```", "````"];
-    let out = attach_orphan_specifiers(&compress_fences(&input));
-    assert_eq!(out, input);
-}
-
-#[test]
-fn does_not_attach_orphan_specifier_deeper_inside_outer_fence() {
-    let input = lines_vec!["````markdown", "```", "Rust", "fn main() {}", "```", "````",];
-    let out = attach_orphan_specifiers(&compress_fences(&input));
-    assert_eq!(out, input);
-}
-
-#[test]
-fn attaches_orphan_specifier_without_shortening_preserved_outer_fence() {
-    let input = lines_vec!["Rust", "````", "```", "fn main() {}", "```", "````"];
-    let out = attach_orphan_specifiers(&compress_fences(&input));
-    assert_eq!(
-        out,
-        lines_vec!["````rust", "```", "fn main() {}", "```", "````"]
-    );
-}
+#[rstest]
+#[case(
+    lines_vec!["````markdown", "Rust", "```", "fn main() {}", "```", "````"],
+    lines_vec!["````markdown", "Rust", "```", "fn main() {}", "```", "````"]
+)]
+#[case(
+    lines_vec!["````markdown", "```", "Rust", "fn main() {}", "```", "````"],
+    lines_vec!["````markdown", "```", "Rust", "fn main() {}", "```", "````"]
+)]
+#[case(
+    lines_vec!["Rust", "````", "```", "fn main() {}", "```", "````"],
+    lines_vec!["````rust", "```", "fn main() {}", "```", "````"]
+)]
+fn handles_orphan_specifiers_around_outer_fences(
+    #[case] input: Vec<String>,
+    #[case] expected: Vec<String>,
+) {
+    let out = attach_orphan_specifiers(&compress_fences(&input));
+    assert_eq!(out, expected);
+}

As per coding guidelines, "Replace duplicated Rust tests with #[rstest(...)] parameterized cases."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/fences.rs` around lines 87 - 109, Replace the three near-duplicate
tests with a single parameterized rstest: remove
does_not_attach_orphan_specifier_inside_outer_fence,
does_not_attach_orphan_specifier_deeper_inside_outer_fence, and
attaches_orphan_specifier_without_shortening_preserved_outer_fence and add one
function (e.g., handles_orphan_specifiers_around_outer_fences) annotated with
#[rstest] and multiple #[case(...)] entries; each case should pass an input
Vec<String> and the expected Vec<String>, call
attach_orphan_specifiers(&compress_fences(&input)) and assert_eq!(out, expected)
to preserve existing behavior; use the existing lines_vec! fixtures and ensure
rstest is imported/available.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/developers-guide.md`:
- Around line 85-96: Update the section titled "Fence normalisation module" to
use Oxford -ize spelling: change the heading to "Fence normalization module" and
replace all occurrences of "normalisation" -> "normalization" and "normalising"
-> "normalizing" within the paragraph that documents compress_fences and
FenceTracker (the prose mentioning `src/fences.rs`, `compress_fences(lines:
&[String]) -> Vec<String>`, and `FenceTracker`); keep content semantics
identical aside from spelling changes. Ensure the updated text still references
the same functions/behaviour (e.g., same-marker and cross-marker cases) and that
backticks/tilde descriptions are unchanged except for the -ize spelling.

In `@docs/execplans/issue-262-nested-code-block-handling.md`:
- Around line 67-71: Update the paragraph to reflect the shipped behaviour:
describe that the fence preprocessing lives in src/fences.rs and that two
functions are involved—compress_fences(lines) (which performs conditional
outer-fence normalization, not an unconditional rewrite to exactly three
backticks) and attach_orphan_specifiers(lines) (which rewrites a lone language
line such as `Rust` onto the following unlabelled fence); also correct spelling
to "unlabelled" and remove any wording that claims unconditional normalization
to three backticks or implies behavior that is not implemented.

---

Duplicate comments:
In `@tests/fences.rs`:
- Around line 87-109: Replace the three near-duplicate tests with a single
parameterized rstest: remove
does_not_attach_orphan_specifier_inside_outer_fence,
does_not_attach_orphan_specifier_deeper_inside_outer_fence, and
attaches_orphan_specifier_without_shortening_preserved_outer_fence and add one
function (e.g., handles_orphan_specifiers_around_outer_fences) annotated with
#[rstest] and multiple #[case(...)] entries; each case should pass an input
Vec<String> and the expected Vec<String>, call
attach_orphan_specifiers(&compress_fences(&input)) and assert_eq!(out, expected)
to preserve existing behavior; use the existing lines_vec! fixtures and ensure
rstest is imported/available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2466d525-a6c5-446b-bce3-113bd51b2b5b

📥 Commits

Reviewing files that changed from the base of the PR and between 6d8f43f and 88ebede.

📒 Files selected for processing (7)
  • AGENTS.md
  • docs/developers-guide.md
  • docs/execplans/issue-262-nested-code-block-handling.md
  • docs/rust-testing-with-rstest-fixtures.md
  • docs/users-guide.md
  • src/fences.rs
  • tests/fences.rs

Comment thread docs/developers-guide.md Outdated
Comment thread docs/execplans/issue-262-nested-code-block-handling.md Outdated
Use Oxford spelling in the developer fence documentation and update the
ExecPlan wording so it describes conditional fence normalization rather than an
unconditional rewrite.

Replace the repeated orphan-specifier nested-fence tests with one parameterized
`rstest` table that preserves the same inputs and expected output.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/issue-262-nested-code-block-handling.md`:
- Around line 334-339: The Stage A marker-classification sentence incorrectly
groups both examples as "same-marker cases"; update the wording in the Stage A
paragraph so it explicitly distinguishes the two shapes: refer to the
four-backtick-outer / triple-backtick-inner example as a same-marker case (both
use backticks, different lengths) and refer to the triple-tilde-outer /
triple-backtick-inner example as a mixed-marker case (tilde outer, backtick
inner), and adjust the following sentence about preservation to state that outer
four-character fences must remain four characters and that tilde-outer cases
must preserve tilde width.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 04d04307-3770-40f1-affb-3decdcfef2e1

📥 Commits

Reviewing files that changed from the base of the PR and between 88ebede and e955b6c.

📒 Files selected for processing (3)
  • docs/developers-guide.md
  • docs/execplans/issue-262-nested-code-block-handling.md
  • tests/fences.rs

Comment thread docs/execplans/issue-262-nested-code-block-handling.md Outdated
Distinguish the same-marker and mixed-marker nested fence examples in the
ExecPlan so the regression description matches the implemented preservation
rules.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/issue-262-nested-code-block-handling.md`:
- Around line 454-456: Replace the ambiguous single-line command sentence that
currently lists `cargo test --test fences`, `cargo test --test cli_fences` and
references `tests/cli_fences.rs` with an explicit parallel bullet/list of
commands (one command per line) and a clear note of coverage: list `cargo test
--test fences`, `cargo test --test cli_fences` (annotating that `cli_fences`
runs tests in tests/cli_fences.rs), `make check-fmt`, `make lint`, `make test`,
and include the tracker target used elsewhere in the plan (e.g., the same
tracker identifier referenced previously) so the acceptance criteria explicitly
states each command and the tracker coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b5a9b458-184e-458d-a67a-d5f967887d59

📥 Commits

Reviewing files that changed from the base of the PR and between e955b6c and 0d7c86e.

📒 Files selected for processing (1)
  • docs/execplans/issue-262-nested-code-block-handling.md

Comment thread docs/execplans/issue-262-nested-code-block-handling.md Outdated
Expand the ExecPlan acceptance criteria so each validation command is listed
separately, including the `cli_fences` integration target and tracker coverage.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/issue-262-nested-code-block-handling.md`:
- Around line 89-93: Replace the first occurrence of the acronym "CLI" in the
paragraph that lists `tests/cli.rs` and `tests/cli_fences.rs` with its expanded
form "command-line interface (CLI)" and leave subsequent uses as "CLI"; update
the sentence that currently starts with "CLI" so it reads "command-line
interface (CLI) behaviour" (or equivalent) while keeping the existing link text
(`tests/cli.rs`, `tests/cli_fences.rs`) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e4eca565-2b97-4010-b1c6-9f41c4209adf

📥 Commits

Reviewing files that changed from the base of the PR and between 0d7c86e and 78731e5.

📒 Files selected for processing (1)
  • docs/execplans/issue-262-nested-code-block-handling.md

Comment thread docs/execplans/issue-262-nested-code-block-handling.md
@leynos leynos merged commit 23f5a10 into main Apr 26, 2026
3 checks passed
@leynos leynos deleted the fix-nested-fence-handling-1ujtgo branch April 26, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mdtablefix doesn't correctly handle nested code blocks

1 participant