From 2ca2e9d441012401078540ceb7a87af506a802ef Mon Sep 17 00:00:00 2001 From: leynos Date: Mon, 8 Jun 2026 20:11:31 +0200 Subject: [PATCH 1/2] Reformat Markdown documents Apply the repository Markdown formatter to existing documentation so the checked-in files match the current house style. Keep the change scoped to formatter output and leave document content unchanged. --- AGENTS.md | 4 +- CHANGELOG.md | 9 +- docs/architecture.md | 20 +- docs/developers-guide.md | 285 +++++++++--------- docs/execplans/cli-matrix-testing.md | 4 +- .../issue-262-nested-code-block-handling.md | 4 +- ...rapping-with-textwrap-and-unicode-width.md | 4 +- docs/execplans/yaml-frontmatter.md | 4 +- docs/rust-doctest-dry-guide.md | 24 +- docs/rust-testing-with-rstest-fixtures.md | 18 +- docs/users-guide.md | 33 +- 11 files changed, 200 insertions(+), 209 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c1972dad..1972ec59 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -237,7 +237,7 @@ project: - **Mandate caret requirements for all dependencies.** All crate versions specified in `Cargo.toml` must use SemVer-compatible caret requirements (e.g., - `some-crate = "1.2.3"`). This is Cargo's default and allows for safe, + `some-crate = "1.2.3"`). This is Cargo's default and allows for safe, non-breaking updates to minor and patch versions while preventing breaking changes from new major versions. This approach is critical for ensuring build stability and reproducibility. @@ -274,7 +274,7 @@ project: - Use `tracing` for logging and diagnostics. Prefer structured `tracing::{trace, debug, info, warn, error}` events and spans over `println!`, - `eprintln!`, or direct `log` macros. Add fields for identifiers, state, and + `eprintln!`, or direct `log` macros. Add fields for identifiers, state, and error context so downstream subscribers can filter and correlate events without parsing message text. - Use `#[tracing::instrument]` or explicit spans around request handling, diff --git a/CHANGELOG.md b/CHANGELOG.md index 931374f3..b71e8889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,16 +30,15 @@ wrapping instead of stranding punctuation at line ends. ( [#293](https://github.com/leynos/mdtablefix/issues/293)) - Keep inflectional affixes (`s`, `'s`, `ed`, `ing`) and hyphenated compounds - attached to the preceding inline code span during paragraph reflow. - A suffix that directly follows a closing backtick fence is absorbed into the - code token, so the two never end up on separate lines after wrapping. + attached to the preceding inline code span during paragraph reflow. A suffix + that directly follows a closing backtick fence is absorbed into the code + token, so the two never end up on separate lines after wrapping. ([`#300`](https://github.com/leynos/mdtablefix/issues/300)) - Preserve inline GFM footnote references as unbreakable tokens when wrapping Markdown paragraphs. ([#277](https://github.com/leynos/mdtablefix/issues/277)) - Preserve link reference definitions verbatim when `--wrap` is used, so labels, URLs, and optional titles are never collapsed into prose or split - across lines. - ([`#292`](https://github.com/leynos/mdtablefix/issues/292)) + across lines. ([`#292`](https://github.com/leynos/mdtablefix/issues/292)) - Normalize whitespace-only artefacts during wrapping by rebalancing atomic tails. - Preserve trailing spaces on the final line when wrapping Markdown, retaining diff --git a/docs/architecture.md b/docs/architecture.md index e7c7554f..39b4855e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -386,15 +386,15 @@ module handles filesystem operations, delegating the text processing to It keeps the current indent, emits wrapped or verbatim lines into the caller's output buffer, and leaves inline fitting to the wrapping helpers. -`HtmlTableState` buffers candidate HTML table lines until the surrounding -table closes. Its depth counter tracks nested `` blocks, so only the -outermost table is converted at once, while incomplete input can still be -flushed back verbatim. +`HtmlTableState` buffers candidate HTML table lines until the surrounding table +closes. Its depth counter tracks nested `
` blocks, so only the outermost +table is converted at once, while incomplete input can still be flushed back +verbatim. The `footnotes::renumber::definitions` submodule owns definition scanning and rewriting. `DefinitionScanState` coordinates the number mapping, collects -already-parsed definitions, and stages numeric candidates for later -conversion without cluttering the top-level renumber flow. +already-parsed definitions, and stages numeric candidates for later conversion +without cluttering the top-level renumber flow. `ListState` tracks the active indentation stack and per-indent counters for ordered list renumbering. It resets on headings and thematic breaks, and it @@ -463,7 +463,7 @@ flowchart TD Figure: `wrap_text` control flow. The wrapper classifies each incoming line, passes fenced blocks, tables, headings, directives, and indented code through unchanged, flushes paragraphs on blanks, routes prose and prefixed lines through - `ParagraphWriter`, computes visible widths with `unicode-width`, and delegates +`ParagraphWriter`, computes visible widths with `unicode-width`, and delegates inline line fitting to `textwrap` before reconstructing the emitted Markdown lines. @@ -594,6 +594,6 @@ absorbed into the code token during tokenization by `scan_code_suffix_end` in `src/wrap/tokenize/scanning.rs`. The combined code-and-suffix token is then classified as atomic by `has_inline_code_structure` in `src/wrap/inline/fragment.rs`, so wrapping treats the full string — for example, -`` `VarGuard`s ``, `` `class`'s ``, `` `fetch`ed ``, or `` `run`ning `` — as an -unbreakable unit. No line break is inserted between the closing backtick and the -following letters. +`` `VarGuard`s ``, `` `class`'s ``, `` `fetch`ed ``, or `` `run`ning `` — as +an unbreakable unit. No line break is inserted between the closing backtick and +the following letters. diff --git a/docs/developers-guide.md b/docs/developers-guide.md index 2ed137c0..8e5d2c66 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -142,10 +142,9 @@ The rationale for the staged table reflow pipeline is recorded in parse, width-calculation, or separator-handling flow so implementation changes stay aligned with the documented design constraints. -The rationale for treating date-like prose sequences as atomic inline -fragments is recorded in `docs/adrs/0003-date-sequences-as-inline-fragments.md`. -Refer to that ADR before adding new date forms or changing the span-grouping -boundary. +The rationale for treating date-like prose sequences as atomic inline fragments +is recorded in `docs/adrs/0003-date-sequences-as-inline-fragments.md`. Refer to +that ADR before adding new date forms or changing the span-grouping boundary. ## Wrap module architecture @@ -173,34 +172,33 @@ The wrapping pipeline for `--wrap` is: lines for ambiguity-preserving passthrough, and `synthetic_join_spaces` stores byte offsets for spaces inserted by continuation joining so only formatter-created code-span edge spaces are trimmed later. Subsequent source - lines are routed through - `handle_pending_continuation` (in `src/wrap.rs`) instead of the normal - wrapping path. `handle_pending_continuation` classifies the line and - delegates each soft-wrapped continuation chunk to `apply_continuation_chunk` - in `src/wrap/continuation.rs`, the module that owns the join/update/dispatch - state machine. Each continuation is joined onto `pending_prefix.rest` via - `join_pending_continuation`, which inserts a space unless the continuation - begins with the exact matching closing fence (detected by - `continuation_begins_with_closing_fence`). Blockquote continuations are only - joined when their prefix exactly matches the pending prefix. After joining, - `apply_continuation_chunk` consults `update_span_state` to drive a - `SpanStateUpdate` (`StillOpen`, `ClosedAndReopened`, or `Flush`); when the - same chunk both closes the pre-existing span and opens a new one, the helper - emits the closed prefix segment and keeps the new span pending rather than - inventing a closing fence. `ParagraphState::drain_pending_prefix` takes that - pending segment and clears the regular paragraph buffers before final - emission. `PendingPrefix::used_prefix` tracks whether the original prefix has - already been emitted, and - `pending_prefix_for_next_segment` uses it to give the first split segment the - original prefix and later split segments the continuation indent. If the - opener is at or near the end of its source line, `PendingPrefix` marks - subsequent continuations as verbatim, so joining does not create leading or - trailing spaces inside the code span. When the projected join would exceed - the available content width, the pending line and continuation are emitted - verbatim rather than joined into a Markdownlint-invalid overlong line. When - the scanner reports no open span and no close/reopen boundary exists, - `flush_paragraph` emits the buffered segment atomically using - `append_wrapped_with_prefix_width`. The exception is + lines are routed through `handle_pending_continuation` (in `src/wrap.rs`) + instead of the normal wrapping path. `handle_pending_continuation` + classifies the line and delegates each soft-wrapped continuation chunk to + `apply_continuation_chunk` in `src/wrap/continuation.rs`, the module that + owns the join/update/dispatch state machine. Each continuation is joined onto + `pending_prefix.rest` via `join_pending_continuation`, which inserts a + space unless the continuation begins with the exact matching closing fence + (detected by `continuation_begins_with_closing_fence`). Blockquote + continuations are only joined when their prefix exactly matches the pending + prefix. After joining, `apply_continuation_chunk` consults + `update_span_state` to drive a `SpanStateUpdate` (`StillOpen`, + `ClosedAndReopened`, or `Flush`); when the same chunk both closes the + pre-existing span and opens a new one, the helper emits the closed prefix + segment and keeps the new span pending rather than inventing a closing fence. + `ParagraphState::drain_pending_prefix` takes that pending segment and + clears the regular paragraph buffers before final emission. + `PendingPrefix::used_prefix` tracks whether the original prefix has already + been emitted, and `pending_prefix_for_next_segment` uses it to give the + first split segment the original prefix and later split segments the + continuation indent. If the opener is at or near the end of its source line, + `PendingPrefix` marks subsequent continuations as verbatim, so joining does + not create leading or trailing spaces inside the code span. When the + projected join would exceed the available content width, the pending line + and continuation are emitted verbatim rather than joined into a + Markdownlint-invalid overlong line. When the scanner reports no open span + and no close/reopen boundary exists, `flush_paragraph` emits the buffered + segment atomically using `append_wrapped_with_prefix_width`. The exception is `ContinuationMode::VerbatimFlush`: when the scanner sees a closing fence immediately followed by a word character, `flush_paragraph` emits `pending.original_lines` verbatim instead of rewrapping the buffer. When @@ -213,8 +211,7 @@ The wrapping pipeline for `--wrap` is: `VerbatimFlush` preserves `pending.original_lines` for ambiguous close and reopen sequences. The `code_span_trim` module contains `trim_code_span_edge_spaces`, which matches code spans by exact fence length - and removes only spaces whose byte offsets appear in - `synthetic_join_spaces`. + and removes only spaces whose byte offsets appear in `synthetic_join_spaces`. 3. **Fragment construction and line fitting.** `wrap_preserving_code` in `src/wrap/inline.rs` tokenizes prose with `tokenize::segment_inline`, groups @@ -225,27 +222,26 @@ The wrapping pipeline for `--wrap` is: in `src/wrap/inline/span_helpers.rs` extend grouped spans over trailing punctuation, couple adjacent footnote references, and merge chained inline code or link tokens. `determine_token_span` forward-couples opening - punctuation tokens (`(`, `[`, and CJK openers) and hyphen-prefix tokens - to the next inline code span or Markdown link so wrapping never leaves a - lone opener or prefix at the end of a line. - `try_couple_inline_link_after_opener` applies the same rule to - parenthesized inline citation links such as `([1](url))`, grouping the - opener and link as one `SpanKind::Link` so adjacent citations like - `([1](url))([2](url2))` do not split at the boundary. At the tokeniser - level, `segment_inline` also stops trailing-punctuation and plain-text - scans at an unescaped `([` boundary via `scan_trailing_punctuation_end` and - `scan_plain_text_end`, both using `starts_inline_citation`, so the citation - opener `(` is emitted as its own token instead of being swallowed into the - preceding token's punctuation cluster. That boundary gives - `determine_token_span` and `try_couple_inline_link_after_opener` a clean - opener token to couple with the following inline link, making the full - `([n](url))` span atomic, while escaped sequences such as `\([` bypass the - early exit and remain plain text. Trailing punctuation after those atomic - spans is grouped in the same pass, and GFM footnote references that - immediately follow inline code or links (including opener-coupled spans) - stay attached to the preceding punctuation cluster. - Date-component predicates are applied by `try_match_date_sequence` in - `span_helpers.rs` before `determine_token_span` performs the standard + punctuation tokens (`(`, `[`, and CJK openers) and hyphen-prefix tokens to + the next inline code span or Markdown link so wrapping never leaves a lone + opener or prefix at the end of a line. `try_couple_inline_link_after_opener` + applies the same rule to parenthesized inline citation links such as + `([1](url))`, grouping the opener and link as one `SpanKind::Link` so + adjacent citations like `([1](url))([2](url2))` do not split at the + boundary. At the tokeniser level, `segment_inline` also stops + trailing-punctuation and plain-text scans at an unescaped `([` boundary via + `scan_trailing_punctuation_end` and `scan_plain_text_end`, both using + `starts_inline_citation`, so the citation opener `(` is emitted as its own + token instead of being swallowed into the preceding token's punctuation + cluster. That boundary gives `determine_token_span` and + `try_couple_inline_link_after_opener` a clean opener token to couple with + the following inline link, making the full `([n](url))` span atomic, while + escaped sequences such as `\([` bypass the early exit and remain plain text. + Trailing punctuation after those atomic spans is grouped in the same pass, + and GFM footnote references that immediately follow inline code or links + (including opener-coupled spans) stay attached to the preceding punctuation + cluster. Date-component predicates are applied by `try_match_date_sequence` + in `span_helpers.rs` before `determine_token_span` performs the standard punctuation and link grouping pass. 4. **Post-processing and rendering.** The `postprocess` module applies @@ -393,25 +389,25 @@ when a footnote marker has been promoted or grouped with preceding punctuation. overflow the target width. Opening punctuation that immediately precedes an inline code span or link is grouped with that span during token grouping so the opener is not left on the previous line. Trailing punctuation after those - spans follows the same grouping rules. GFM footnote references that immediately - follow inline code or link spans without intervening whitespace are coupled - to the preceding punctuation cluster, so the marker is not wrapped onto the - next line alone. Inflectional affixes (`s`, `'s`, `ed`, + spans follows the same grouping rules. GFM footnote references that + immediately follow inline code or link spans without intervening whitespace + are coupled to the preceding punctuation cluster, so the marker is not + wrapped onto the next line alone. Inflectional affixes (`s`, `'s`, `ed`, `ing`) and hyphenated compounds that immediately follow a closed backtick fence are absorbed into the code token by `scan_code_suffix_end` in - `src/wrap/tokenize/scanning.rs`; the combined token is recognized as atomic - by `has_inline_code_structure` in `src/wrap/inline/fragment.rs`, so wrapping + `src/wrap/tokenize/scanning.rs`; the combined token is recognized as atomic by + `has_inline_code_structure` in `src/wrap/inline/fragment.rs`, so wrapping treats the full string as one unit. Leading-hyphen compounds — a token that - ends with a hyphen and contains at least one alphabetic character (for - example `pre-`, `LLM-`, `(API-`) — are coupled forward to the next inline - code span during span grouping by the `ends_with_hyphen_prefix` predicate in + ends with a hyphen and contains at least one alphabetic character (for example + `pre-`, `LLM-`, `(API-`) — are coupled forward to the next inline code span + during span grouping by the `ends_with_hyphen_prefix` predicate in `src/wrap/inline/predicates.rs`, applied in `determine_token_span` in `src/wrap/inline.rs`. The coupling mirrors the existing opening-punctuation pattern, so compounds such as `` pre-`LLMPort` `` and `` (API-`Foo`) `` remain atomic during wrapping. Internal hyphen chains (e.g. - `state-of-the-art-`) are accepted by design; bare dash runs such as `-` - or `---` are rejected. Unicode alphabetic characters (e.g. `pré-`, - `字-`) are intentionally supported. + `state-of-the-art-`) are accepted by design; bare dash runs such as `-` or + `---` are rejected. Unicode alphabetic characters (e.g. `pré-`, `字-`) are + intentionally supported. - **Hard breaks.** Trailing two-space hard breaks must survive on the emitted line where they occur. - **Verbatim blocks.** Fenced code blocks must pass through unchanged, along @@ -427,21 +423,21 @@ when a footnote marker has been promoted or grouped with preceding punctuation. content. - **Closing fence detection.** Backslash escape checks apply only while detecting opening backtick fences in ordinary Markdown text. Once a code span - is open, backslashes in the span content are literal bytes and must not make a - matching closing fence invisible. All tokenizer entry points that close code - spans use `position_after_close` so they also reject candidate closers + is open, backslashes in the span content are literal bytes and must not make + a matching closing fence invisible. All tokenizer entry points that close + code spans use `position_after_close` so they also reject candidate closers embedded in a longer backtick run. - **Width-aware inline-code carries.** `merge_whitespace_only_lines` receives the active wrap width from `wrap_preserving_code`. Before carrying a previous inline-code tail across a single-space wrap artefact, it must compute the - projected destination line width and skip the carry when that projection would - exceed the configured width. + projected destination line width and skip the carry when that projection + would exceed the configured width. - **`WRAP_COLS` public constant.** `mdtablefix::process::WRAP_COLS` is - exported as `pub` so that integration tests can reference the production - wrap width instead of hard-coding `80`. When writing tests that depend on - the column boundary (for example, wrap-boundary edge-case tests), import - and use `WRAP_COLS` as the single source of truth. Do not duplicate the - literal value `80` in test code. + exported as `pub` so that integration tests can reference the production wrap + width instead of hard-coding `80`. When writing tests that depend on the + column boundary (for example, wrap-boundary edge-case tests), import and use + `WRAP_COLS` as the single source of truth. Do not duplicate the literal value + `80` in test code. Refer to `docs/adrs/0002-textwrap-wrapping-engine.md` for the rationale behind replacing `LineBuffer` with `textwrap`. @@ -466,21 +462,21 @@ from library code. ### Field naming -Use the stable structured field names `token`, `kind`, `start`, `end`, -`width`, `truncated`, `reason`, and `is_image`. +Use the stable structured field names `token`, `kind`, `start`, `end`, `width`, +`truncated`, `reason`, and `is_image`. Table: Structured field names emitted by tracing instrumentation. -| Field | Type | Used in | Meaning | -| --- | --- | --- | --- | -| `token` | `%str` | fragment, link, footnote events | The text slice that was classified or parsed | -| `kind` | `?FragmentKind` | `fragment classified` | The computed fragment classification | -| `start` | `usize` | span events | Byte offset where the span begins | -| `end` | `usize` | span events | Byte offset where the span ends (exclusive) | -| `width` | `usize` | span events | Display-column width of the span | -| `truncated` | `bool` | `fragment classified` | Whether `token` was shortened to <= 80 bytes | -| `reason` | `&str` | `footnote end not found` | Diagnostic tag: `"prefix_mismatch"` or `"unterminated_bracket"` | -| `is_image` | `bool` | `link or image parsed` | `true` when the link token is an image literal (`![]()`) | +| Field | Type | Used in | Meaning | +| ----------- | --------------- | ------------------------------- | --------------------------------------------------------------- | +| `token` | `%str` | fragment, link, footnote events | The text slice that was classified or parsed | +| `kind` | `?FragmentKind` | `fragment classified` | The computed fragment classification | +| `start` | `usize` | span events | Byte offset where the span begins | +| `end` | `usize` | span events | Byte offset where the span ends (exclusive) | +| `width` | `usize` | span events | Display-column width of the span | +| `truncated` | `bool` | `fragment classified` | Whether `token` was shortened to <= 80 bytes | +| `reason` | `&str` | `footnote end not found` | Diagnostic tag: `"prefix_mismatch"` or `"unterminated_bracket"` | +| `is_image` | `bool` | `link or image parsed` | `true` when the link token is an image literal (`![]()`) | For example: @@ -502,8 +498,7 @@ identifiers, or other sensitive values. When enabling DEBUG or TRACE logging from this library in a production environment, configure the subscriber to redact or drop the `token` field -before writing to any persistent sink. For example, with -`tracing-subscriber`: +before writing to any persistent sink. For example, with `tracing-subscriber`: ```rust use tracing_subscriber::fmt::format::FmtSpan; @@ -531,19 +526,19 @@ points. Table: Instrumented functions and their logging levels and fields. -| Function | Level | Fields | -| --- | --- | --- | -| `looks_like_footnote_ref` | trace | `token` (in), return value (out) | -| `ends_with_footnote_ref` | trace | `token` (in), return value (out) | -| `ends_with_hyphen_prefix` | trace | `token` (in), return value (out) | -| `is_month_name` | trace | `token` (in), return value (out) | -| `is_ordinal_day` | trace | `token` (in), return value (out) | -| `is_numeric_day` | trace | `token` (in), return value (out) | -| `is_year` | trace | `token` (in), return value (out) | -| `try_match_date_sequence` | trace, debug | `start` (in), `skip(tokens)`, return value (out); matched date pattern | -| `date_token_span` | trace | `start` (in), `skip(tokens)`, return value (out); over-width date fallback remains behaviour-only | -| `parse_link_or_image` | debug | `idx` (in), `skip(text)`, return value (out) | -| `find_footnote_end` | trace | `idx` (in), `skip(text)`, return value (out) | +| Function | Level | Fields | +| ------------------------- | ------------ | ------------------------------------------------------------------------------------------------- | +| `looks_like_footnote_ref` | trace | `token` (in), return value (out) | +| `ends_with_footnote_ref` | trace | `token` (in), return value (out) | +| `ends_with_hyphen_prefix` | trace | `token` (in), return value (out) | +| `is_month_name` | trace | `token` (in), return value (out) | +| `is_ordinal_day` | trace | `token` (in), return value (out) | +| `is_numeric_day` | trace | `token` (in), return value (out) | +| `is_year` | trace | `token` (in), return value (out) | +| `try_match_date_sequence` | trace, debug | `start` (in), `skip(tokens)`, return value (out); matched date pattern | +| `date_token_span` | trace | `start` (in), `skip(tokens)`, return value (out); over-width date fallback remains behaviour-only | +| `parse_link_or_image` | debug | `idx` (in), `skip(text)`, return value (out) | +| `find_footnote_end` | trace | `idx` (in), `skip(text)`, return value (out) | ## Fences module @@ -682,29 +677,28 @@ functions remain focused on traversal. ### `HtmlTableState` (`src/html.rs`) `HtmlTableState` buffers the lines belonging to an HTML `
` -block and tracks the current nesting depth. `in_html()` returns `true` -whenever the buffer is non-empty, so the caller knows a table is still being -accumulated. `push_html_line` appends the supplied line, increments `depth` -once for every `` start tag found on the trimmed line, and decrements -it once for every `
` end tag on the same trimmed line. When `depth` -returns to zero, the buffered lines are converted by `table_lines_to_markdown` -and the buffer is cleared. `flush_raw` exists for the fenced-block escape -path: it emits the buffered lines verbatim without conversion, so raw HTML -inside a fenced code block is preserved unchanged. +block and tracks the current nesting depth. `in_html()` returns `true` whenever +the buffer is non-empty, so the caller knows a table is still being accumulated. +`push_html_line` appends the supplied line, increments `depth` once for every +`` start tag found on the trimmed line, and decrements it once for every +`
` end tag on the same trimmed line. When `depth` returns to zero, the +buffered lines are converted by `table_lines_to_markdown` and the buffer is +cleared. `flush_raw` exists for the fenced-block escape path: it emits the +buffered lines verbatim without conversion, so raw HTML inside a fenced code +block is preserved unchanged. ### `DefinitionScanState` (`src/footnotes/renumber/definitions.rs`) -`DefinitionScanState` accumulates the footnote-definition rewrite plan -during a single scan over the input. It borrows the shared `(original → -new)` mapping and the `next_number` counter so renumbering decisions stay -consistent with explicit reference rewrites. Explicit `[^n]:` headers are -appended to `definitions` as soon as they are encountered, producing a -`DefinitionLine` per header in scan order. Ordered-list items that look like -candidate footnote definitions are buffered as `NumericCandidate` entries -during the scan and finalized at the end via `finalize_numeric_candidates`, -which drains the buffer in reverse, so the assigned numbers reflect -bottom-up ordering rather than the order in which the candidates were -discovered. +`DefinitionScanState` accumulates the footnote-definition rewrite plan during a +single scan over the input. It borrows the shared `(original → new)` mapping +and the `next_number` counter so renumbering decisions stay consistent with +explicit reference rewrites. Explicit `[^n]:` headers are appended to +`definitions` as soon as they are encountered, producing a `DefinitionLine` per +header in scan order. Ordered-list items that look like candidate footnote +definitions are buffered as `NumericCandidate` entries during the scan and +finalized at the end via `finalize_numeric_candidates`, which drains the buffer +in reverse, so the assigned numbers reflect bottom-up ordering rather than the +order in which the candidates were discovered. ### `ListState` (`src/lists.rs`) @@ -713,10 +707,10 @@ ordered-list renumbering. `next_number(indent)` first prunes indent levels deeper than `indent` (their counters disappear so a future deeper level restarts at 1), pushes `indent` onto the stack if it is new, and returns the next sequential number for that level — incrementing the counter, so the next -call at the same indent receives the following integer. `reset()` clears -both the stack and the counter map; the renumbering pass invokes it when a -heading or thematic break is encountered, so the next list starts numbering -from 1 again. +call at the same indent receives the following integer. `reset()` clears both +the stack and the counter map; the renumbering pass invokes it when a heading +or thematic break is encountered, so the next list starts numbering from 1 +again. ## Test infrastructure @@ -726,12 +720,12 @@ Integration-test helpers are organized under `tests/support/`: Table: Integration-test support modules and their purposes. -| Module | Purpose | -| --- | --- | -| `cli_args.rs` | `run_cli_with_args` — invokes the binary with argument-only tests | -| `cli_stdin.rs` | `run_cli_with_stdin` — invokes the binary feeding stdin | -| `fixtures.rs` | Shared rstest fixtures (e.g. `broken_table`) | -| `wrap_assertions.rs` | Higher-level assertions for wrapping output | +| Module | Purpose | +| -------------------- | ----------------------------------------------------------------- | +| `cli_args.rs` | `run_cli_with_args` — invokes the binary with argument-only tests | +| `cli_stdin.rs` | `run_cli_with_stdin` — invokes the binary feeding stdin | +| `fixtures.rs` | Shared rstest fixtures (e.g. `broken_table`) | +| `wrap_assertions.rs` | Higher-level assertions for wrapping output | Each integration-test file declares the modules it needs via explicit `#[path = "support/…"]` attributes, keeping inter-test coupling minimal. @@ -743,20 +737,20 @@ integration-test crates: Table: Macros for building `Vec` from literals and file lines. -| Macro | Purpose | -| --- | --- | -| `lines_vec![…]` | Builds a `Vec` from string-like values. | -| `include_lines!("path")` | Builds a `Vec` from file lines. | +| Macro | Purpose | +| ------------------------ | ----------------------------------------------- | +| `lines_vec![…]` | Builds a `Vec` from string-like values. | +| `include_lines!("path")` | Builds a `Vec` from file lines. | `lines_vec![…]` reduces boilerplate when constructing fixture inputs. `include_lines!("path")` uses `include_str!` at compile time and returns one `String` per line of the referenced file. Both macros are exported rather than kept private because Rust's macro scoping -rules require `#[macro_export]` for macros to be visible across integration-test -binary crates. The `#[expect(unused_macros)]` suppressions that previously -guarded them were replaced by the export attribute when it became clear that -multiple test binaries depend on them. +rules require `#[macro_export]` for macros to be visible across +integration-test binary crates. The `#[expect(unused_macros)]` suppressions +that previously guarded them were replaced by the export attribute when it +became clear that multiple test binaries depend on them. ### `test-macros` crate @@ -771,8 +765,7 @@ attribute applies to code that the compiler has not yet expanded, making `#[expect]` semantically unusable at that site. This is a known consequence of the `rstest` fixture expansion and is not a lint-integrity violation. -Apply it to any fixture function whose single-expression body triggers the -lint: +Apply it to any fixture function whose single-expression body triggers the lint: ```rust #[test_macros::allow_fixture_expansion_lints] @@ -785,7 +778,7 @@ pub fn broken_table() -> Vec { … } `format_breaks` in [src/breaks.rs](../src/breaks.rs) returns `Vec>` so unchanged lines can be forwarded without allocating. Lines that do not match a thematic break are emitted as `Cow::Borrowed` slices -into the input `&[String]`. Synthesized thematic-break lines are also emitted -as `Cow::Borrowed`, pointing to the shared `LazyLock` static +into the input `&[String]`. Synthesized thematic-break lines are also emitted as +`Cow::Borrowed`, pointing to the shared `LazyLock` static `THEMATIC_BREAK_LINE`. Callers that need owned `String` values must call `.into_owned()` on each item. diff --git a/docs/execplans/cli-matrix-testing.md b/docs/execplans/cli-matrix-testing.md index 5e72cec9..e62f06a1 100644 --- a/docs/execplans/cli-matrix-testing.md +++ b/docs/execplans/cli-matrix-testing.md @@ -253,8 +253,8 @@ the `insta` dev dependency. `tests/data/cli-matrix/` contains four `.dat` fixtures, and `tests/cli_matrix.rs` plus `tests/cli_matrix/support.rs` define the base catalogue, expansion checks, command runner, and `insta` snapshots. -The harness now verifies case identifier uniqueness, `.dat` fixture use, -stdout and `--in-place` expansion, wrapped and unwrapped expansion, per-flag +The harness now verifies case identifier uniqueness, `.dat` fixture use, stdout +and `--in-place` expansion, wrapped and unwrapped expansion, per-flag enabled/disabled coverage, and pairwise coverage for the seven non-wrap transform flags. The snapshot test executes 32 physical command runs and asserts that file-to-stdout output matches the corresponding `--in-place` diff --git a/docs/execplans/issue-262-nested-code-block-handling.md b/docs/execplans/issue-262-nested-code-block-handling.md index e493401f..ea50100c 100644 --- a/docs/execplans/issue-262-nested-code-block-handling.md +++ b/docs/execplans/issue-262-nested-code-block-handling.md @@ -344,8 +344,8 @@ containing a shorter literal `~~~` block that must remain unchanged because it does not close the outer fence. Add one case that shows `attach_orphan_specifiers` must not attach a specifier-like line when it appears inside an already open outer fence. Add a CLI regression in -`tests/cli_fences.rs` that exercises `--fences` on one of these documents so the -user-visible behaviour is covered end to end. +`tests/cli_fences.rs` that exercises `--fences` on one of these documents so +the user-visible behaviour is covered end to end. Stage B is the implementation pass in `src/fences.rs`. Refactor `compress_fences` from a stateless `map` into a line-by-line loop that keeps a diff --git a/docs/execplans/replace-bespoke-wrapping-with-textwrap-and-unicode-width.md b/docs/execplans/replace-bespoke-wrapping-with-textwrap-and-unicode-width.md index 0549b935..97f1719b 100644 --- a/docs/execplans/replace-bespoke-wrapping-with-textwrap-and-unicode-width.md +++ b/docs/execplans/replace-bespoke-wrapping-with-textwrap-and-unicode-width.md @@ -1,7 +1,7 @@ # Replace bespoke wrapping with `textwrap` and `unicode-width` This ExecPlan (execution plan) is a living document. The sections `Constraints`, - `Tolerances`, `Risks`, `Progress`, `Surprises & Discoveries`, `Decision Log`, +`Tolerances`, `Risks`, `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds. Status: COMPLETED @@ -180,7 +180,7 @@ the plan prefers a smaller, safer first delivery and a follow-up issue. plain fragments can invalidate `wrap_first_fit`'s width guarantee if it moves a fragment without rechecking the destination line width. Evidence: `rebalance_atomic_tails` on 2026-04-23 could turn `a four` / `five` into `a` / - `four five` at width `6`. Impact: any heuristic that mutates fitted lines + `four five` at width `6`. Impact: any heuristic that mutates fitted lines after wrapping must be width-aware, or it can regress downstream layout assumptions. diff --git a/docs/execplans/yaml-frontmatter.md b/docs/execplans/yaml-frontmatter.md index 0c932723..5ecf65d4 100644 --- a/docs/execplans/yaml-frontmatter.md +++ b/docs/execplans/yaml-frontmatter.md @@ -1,7 +1,7 @@ # Preserve leading YAML frontmatter while formatting Markdown This ExecPlan (execution plan) is a living document. The sections `Constraints`, - `Tolerances`, `Risks`, `Progress`, `Surprises & Discoveries`, `Decision Log`, +`Tolerances`, `Risks`, `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds. Status: DELIVERED @@ -181,7 +181,7 @@ Stage D adds regression coverage. Put detector-specific unit tests in new test module. Add at least one behavioural CLI test in `tests/cli.rs` covering a document with leading frontmatter plus a paragraph or table body. The CLI test should enable `--breaks` and one ordinary formatting option such as - `--wrap` so it proves both preservation and continued formatting. +`--wrap` so it proves both preservation and continued formatting. Stage E updates the docs. Add a short YAML frontmatter note and example to `README.md` so users know the block is preserved. Update `docs/architecture.md` diff --git a/docs/rust-doctest-dry-guide.md b/docs/rust-doctest-dry-guide.md index 8dc134dc..b145c67b 100644 --- a/docs/rust-doctest-dry-guide.md +++ b/docs/rust-doctest-dry-guide.md @@ -14,8 +14,8 @@ the power and the inherent limitations of doctests. ### 1.1 The "Separate Crate" Paradigm At its heart, `rustdoc` treats each documentation test not as a snippet of code -running within the library's own context, but as an entirely separate, -temporary crate.[^1] When a developer executes +running within the library's own context, but as an entirely separate, temporary +crate.[^1] When a developer executes `cargo test --doc`, `rustdoc` initiates a multi-stage process for every code block found in the documentation comments[^3]: @@ -40,8 +40,8 @@ block found in the documentation comments[^3]: 4. **Execution and Verification**: Finally, if compilation succeeds, the resulting executable is run. The test is considered to have passed if the - program runs to completion without panicking. The executable is then - deleted. [^2] + program runs to completion without panicking. The executable is then deleted. + [^2] The significance of this model cannot be overstated. It effectively transforms every doctest into a true integration test.[^6] The test code does not have @@ -105,7 +105,7 @@ Doctests reside within documentation comments. Rust recognizes two types: `lib.rs` or `mod.rs` to provide crate- or module-level documentation.[^8] Within these comments, a code block is denoted by triple back-ticks (```). While - `rustdoc` defaults to Rust syntax, explicitly add the `rust` language +`rustdoc` defaults to Rust syntax, explicitly add the `rust` language specifier for clarity.[^3] A doctest "passes" when it compiles and runs without panicking. To assert specific outcomes, use the standard macros `assert!`, `assert_eq!`, and `assert_ne!`.[^3] @@ -272,8 +272,8 @@ table provides a comparative reference for the most common doctest attributes. - `edition20xx`: This attribute allows an example to be tested against a specific Rust edition. This is important for crates that support multiple - editions and need to demonstrate edition-specific features or migration - paths. [^4] + editions and need to demonstrate edition-specific features or migration paths. + [^4] ## The DRY Principle in Doctests: Managing Shared and Complex Logic @@ -403,7 +403,7 @@ pub struct UnixSocket; ``` This `any` directive ensures the struct is compiled either when the target OS is - `unix` OR when `rustdoc` is running. This correctly makes the item visible in +`unix` OR when `rustdoc` is running. This correctly makes the item visible in the generated HTML. However, it is crucial to understand that this **does not** make the doctest for `UnixSocket` pass on non-Unix platforms. @@ -577,8 +577,8 @@ real-world challenges when working with doctests. `#[test]` function in a temporary file or test module. This allows the developer to leverage the full power of the IDE. Once the code is working - correctly, it can be copied into the doc comment, and the necessary - formatting (`///`, `#`, etc.) can be applied.[^15] + correctly, it can be copied into the doc comment, and the necessary formatting + (`///`, `#`, etc.) can be applied.[^15] ## Conclusion and Recommendations @@ -643,7 +643,7 @@ July 15, 2025, [^11]: Compile_fail doc test ignored in cfg(test) - help - The Rust Programming Language Forum, accessed on July 15, 2025, - accessed on July 15, 2025, +accessed on July 15, 2025, [^12]: quote_doctest - Rust - [Docs.rs](http://Docs.rs), accessed on July 15, 2025, @@ -652,7 +652,7 @@ Language Forum, accessed on July 15, 2025, [^14]: rust - How can I conditionally execute a module-level doctest based …, accessed on July 15, 2025, - have doctests?, accessed on July 15, 2025, +have doctests?, accessed on July 15, 2025, [^15]: How do you write your doc tests? : r/rust - Reddit, accessed on July 15, 2025, diff --git a/docs/rust-testing-with-rstest-fixtures.md b/docs/rust-testing-with-rstest-fixtures.md index 090d839b..b807d1d5 100644 --- a/docs/rust-testing-with-rstest-fixtures.md +++ b/docs/rust-testing-with-rstest-fixtures.md @@ -1283,15 +1283,15 @@ wrappers to ensure that logging is correctly set up before each The `test-with` crate allows for conditional execution of tests based on various runtime conditions, such as the presence of environment variables, the -existence of specific files or folders, or the availability of network -services. [^22] It can be used with `rstest`. For example, an `rstest` test -could be further annotated with `test-with` attributes to ensure it only runs -if a particular database configuration file exists or if a dependent web -service is reachable. The order of macros is important: `rstest` should -typically generate the test cases first, and then `test-with` can apply its -conditional execution logic to these generated tests.[^22] This allows `rstest` -to focus on test structure and data provision, while `test-with` provides an -orthogonal layer of control over test execution conditions. +existence of specific files or folders, or the availability of network services. +[^22] It can be used with `rstest`. For example, an `rstest` test could be +further annotated with `test-with` attributes to ensure it only runs if a +particular database configuration file exists or if a dependent web service is +reachable. The order of macros is important: `rstest` should typically generate +the test cases first, and then `test-with` can apply its conditional execution +logic to these generated tests.[^22] This allows `rstest` to focus on test +structure and data provision, while `test-with` provides an orthogonal layer of +control over test execution conditions. ## XI. Conclusion and Further Resources diff --git a/docs/users-guide.md b/docs/users-guide.md index 637bc6de..63fa0856 100644 --- a/docs/users-guide.md +++ b/docs/users-guide.md @@ -39,11 +39,11 @@ footnote references (`[^label]`) are treated as unbreakable units. A span is never split across lines; it moves as a whole to the next line when it would otherwise exceed the target width. -Common English prose dates, such as `25th December 2025`, `19 March 2018`, -and `July 4, 2008`, are also treated as unbreakable inline fragments. This -applies to ordinal-day, numeric-day, and month-name-first forms with full or -abbreviated month names. If a date is wider than the configured wrap width, -the existing long-token fallback behaviour applies. +Common English prose dates, such as `25th December 2025`, `19 March 2018`, and +`July 4, 2008`, are also treated as unbreakable inline fragments. This applies +to ordinal-day, numeric-day, and month-name-first forms with full or +abbreviated month names. If a date is wider than the configured wrap width, the +existing long-token fallback behaviour applies. Parenthesized inline citations such as `pattern([1](url))` are also treated as unbreakable units, keeping the citation link and its surrounding parentheses @@ -63,9 +63,9 @@ checklist item from being reformatted as several independent checklist entries. If joining a split inline code span would exceed the configured wrap width, `mdtablefix` preserves the existing multi-line shape instead of emitting an -overlong line. Ambiguous close-and-reopen patterns are also preserved -verbatim, so the formatter does not introduce Markdownlint MD038 spacing -violations or change the intended code-span boundaries. +overlong line. Ambiguous close-and-reopen patterns are also preserved verbatim, +so the formatter does not introduce Markdownlint MD038 spacing violations or +change the intended code-span boundaries. When `--wrap` is combined with `--renumber`, ordered list item boundaries are preserved even when a list item contains a long inline code span. The wrapper @@ -141,9 +141,9 @@ wrapped line, so hard-break semantics are not lost after reformatting. Lines that consist entirely of whitespace — spaces, tabs, or any mixture — are normalized to empty strings during wrapping. Such lines act as paragraph -boundaries and are never passed through with their original whitespace -content, so the output uses a single uniform separator between paragraphs -regardless of the input's incidental indentation. +boundaries and are never passed through with their original whitespace content, +so the output uses a single uniform separator between paragraphs regardless of +the input's incidental indentation. When computing the indentation width for continuation lines in prefixed contexts (blockquotes, lists, and footnote definitions), `mdtablefix` measures @@ -155,13 +155,12 @@ CJK punctuation. ## HTML table conversion `mdtablefix` converts `…
` blocks that span multiple lines and -carry leading indentation into Markdown pipe tables. The leading indentation -is preserved on every emitted row, so the converted table sits at the same +carry leading indentation into Markdown pipe tables. The leading indentation is +preserved on every emitted row, so the converted table sits at the same indentation level as the original HTML. Surrounding non-table lines at that -same indentation level are passed through unchanged. Nested `` tags -are tracked by depth, so the buffered structure is converted only once the -outermost `
` is reached and never split into two separate -conversions. +same indentation level are passed through unchanged. Nested `` tags are +tracked by depth, so the buffered structure is converted only once the outermost +`
` is reached and never split into two separate conversions. ## Fence normalization From 1665b2b10bfa3565ec196d5808ed68affc5c7a9c Mon Sep 17 00:00:00 2001 From: leynos Date: Mon, 8 Jun 2026 20:19:35 +0200 Subject: [PATCH 2/2] Attach inline footnote references Remove formatter-inserted spacing between sentence punctuation and inline GFM footnote references in the Rust documentation guides. Keep footnote definition lines untouched so reference lists retain their Markdown structure. --- docs/rust-doctest-dry-guide.md | 8 ++++---- docs/rust-testing-with-rstest-fixtures.md | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/rust-doctest-dry-guide.md b/docs/rust-doctest-dry-guide.md index b145c67b..29846851 100644 --- a/docs/rust-doctest-dry-guide.md +++ b/docs/rust-doctest-dry-guide.md @@ -40,8 +40,8 @@ block found in the documentation comments[^3]: 4. **Execution and Verification**: Finally, if compilation succeeds, the resulting executable is run. The test is considered to have passed if the - program runs to completion without panicking. The executable is then deleted. - [^2] + program runs to completion without panicking. The executable is then + deleted.[^2] The significance of this model cannot be overstated. It effectively transforms every doctest into a true integration test.[^6] The test code does not have @@ -272,8 +272,8 @@ table provides a comparative reference for the most common doctest attributes. - `edition20xx`: This attribute allows an example to be tested against a specific Rust edition. This is important for crates that support multiple - editions and need to demonstrate edition-specific features or migration paths. - [^4] + editions and need to demonstrate edition-specific features or migration + paths.[^4] ## The DRY Principle in Doctests: Managing Shared and Complex Logic diff --git a/docs/rust-testing-with-rstest-fixtures.md b/docs/rust-testing-with-rstest-fixtures.md index b807d1d5..37d8fd2f 100644 --- a/docs/rust-testing-with-rstest-fixtures.md +++ b/docs/rust-testing-with-rstest-fixtures.md @@ -1283,15 +1283,15 @@ wrappers to ensure that logging is correctly set up before each The `test-with` crate allows for conditional execution of tests based on various runtime conditions, such as the presence of environment variables, the -existence of specific files or folders, or the availability of network services. -[^22] It can be used with `rstest`. For example, an `rstest` test could be -further annotated with `test-with` attributes to ensure it only runs if a -particular database configuration file exists or if a dependent web service is -reachable. The order of macros is important: `rstest` should typically generate -the test cases first, and then `test-with` can apply its conditional execution -logic to these generated tests.[^22] This allows `rstest` to focus on test -structure and data provision, while `test-with` provides an orthogonal layer of -control over test execution conditions. +existence of specific files or folders, or the availability of network +services.[^22] It can be used with `rstest`. For example, an `rstest` test +could be further annotated with `test-with` attributes to ensure it only runs +if a particular database configuration file exists or if a dependent web +service is reachable. The order of macros is important: `rstest` should +typically generate the test cases first, and then `test-with` can apply its +conditional execution logic to these generated tests.[^22] This allows `rstest` +to focus on test structure and data provision, while `test-with` provides an +orthogonal layer of control over test execution conditions. ## XI. Conclusion and Further Resources