Skip to content

Commit cf8fca9

Browse files
committed
Merge remote-tracking branch 'origin/main' into feature/brand-yml
2 parents deab3fd + ad18adc commit cf8fca9

22 files changed

Lines changed: 2059 additions & 146 deletions

.beads/issues.jsonl

Lines changed: 2 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# bd-c05x6 — Plumb SourceInfo into Q-13-4 body-link "missing document" warnings
2+
3+
**Status**: Implementation complete. Awaiting user review before commit.
4+
5+
**Issue**: bd-c05x6 (P3, task)
6+
**Parent (discovered-from)**: bd-hjv5o (item #1 in its checklist)
7+
**Related precedent**: bd-qor9a (nav-surface SourceInfo plumbing) — done.
8+
**Related**: bd-8d6rk (structured Q-13-* diagnostic shape) — done.
9+
10+
## Reproducer
11+
12+
Running `cargo run --bin q2 -- render docs/` on this repo's docs site
13+
today prints (excerpt):
14+
15+
```
16+
Warning [Q-13-4]: Body link references missing document
17+
'authoring/markdown/markdown-basics.qmd' is not in the project index.
18+
ℹ Check the spelling, or confirm the target file is included in the render set.
19+
```
20+
21+
There is no `at file:row:col` line — the user has to grep the source
22+
tree to find which `.qmd` file holds the broken link. Compare with the
23+
sidebar / navbar / footer Q-13-* diagnostics, which (after bd-qor9a)
24+
already render an `at …` line pointing at the offending YAML scalar.
25+
26+
The desired output looks like:
27+
28+
```
29+
Warning [Q-13-4]: Body link references missing document
30+
at docs/authoring/markdown/index.qmd:42:8
31+
'authoring/markdown/markdown-basics.qmd' is not in the project index.
32+
ℹ Check the spelling, or confirm the target file is included in the render set.
33+
```
34+
35+
(or, when source context is wired and the file is on disk, an
36+
Ariadne-rendered source excerpt — same machinery as the nav surfaces).
37+
38+
## Why this is a one-line fix in spirit
39+
40+
Three pieces of infrastructure are already in place:
41+
42+
1. **The parser populates `Link.target_source.url`**. In
43+
`crates/pampa/src/pandoc/treesitter_utils/span_link_helpers.rs:109`,
44+
the inline-link constructor stores the URL range as
45+
`SourceInfo::Original` whenever the URL string is non-empty.
46+
2. **The helper accepts an optional location.**
47+
`resolve_doc_relative_href` (in
48+
`crates/quarto-core/src/transforms/navigation_href.rs:287`) takes
49+
`location: Option<SourceInfo>` and, when `Some`, threads it through
50+
`DiagnosticMessageBuilder::with_location` inside
51+
`missing_document_warning`.
52+
3. **The render driver already prints location.**
53+
`print_render_diagnostics`
54+
(`crates/quarto/src/commands/render.rs:704`) calls
55+
`diagnostic.to_text(Some(&result.render_output.source_context))`,
56+
which renders either an `at file:row:col` line or an Ariadne source
57+
excerpt when the diagnostic has location info.
58+
59+
The only missing wire is at the body-link callsite:
60+
61+
```rust
62+
// crates/quarto-core/src/transforms/link_rewrite.rs:219-227
63+
let new_url = resolve_doc_relative_href(
64+
&link.target.0,
65+
self.source,
66+
self.resolver,
67+
Some(self.index),
68+
None, // <-- this is the bug; should be link.target_source.url.clone()
69+
self.diagnostics,
70+
);
71+
```
72+
73+
bd-hjv5o item #1 already calls this out as a "lightweight follow-up"
74+
to bd-qor9a — no type changes, no plumbing through new fields, no
75+
RenderContext changes. The `Link` node carries the source info into
76+
the visitor; we just have to use it.
77+
78+
## Decisions
79+
80+
**D1: Use `target_source.url`, not `link.source_info`.**
81+
`target_source.url` is the precise byte range of the URL text inside
82+
the parens of `[text](url.qmd)`. `link.source_info` covers the entire
83+
`[text](url)` span, which is correct in a fallback sense but less
84+
useful — the diagnostic should point at the URL, not the link's
85+
display text. Both are populated by the same parser code path
86+
(`span_link_helpers.rs`), so neither is strictly less reliable than
87+
the other.
88+
89+
When `target_source.url` is `None` (URL was empty string — rare for
90+
this codepath since the helper short-circuits on non-`.qmd` paths
91+
before any diagnostic fires), we just pass `None` through; the
92+
diagnostic loses its location but stays correct, exactly matching the
93+
status quo.
94+
95+
**D2: No fallback to `link.source_info`.** If `target_source.url` is
96+
`None`, the diagnostic stays location-less rather than falling back
97+
to a less-precise span. Reasons:
98+
99+
- The only paths that reach the `.qmd`-shaped miss branch have a
100+
non-empty URL, which means the parser would have populated
101+
`target_source.url`. The only way it ends up `None` here is if a
102+
Lua filter (or some other AST-mutating transform) constructed a
103+
fresh `Link` programmatically without setting `target_source.url`.
104+
In that case, `link.source_info` is also likely to be
105+
`SourceInfo::default()` or some filter-provenance value that wouldn't
106+
resolve cleanly either.
107+
- Keeps the code change to one cloned `Option` field; no conditional
108+
logic.
109+
110+
**D3: No changes to the resolution helper's signature.** The
111+
`location: Option<SourceInfo>` parameter on `resolve_doc_relative_href`
112+
has been forward-looking since it was added; this issue is the one
113+
that finally populates it. The helper, the diagnostic builder, and
114+
the renderer don't need any changes — just the one callsite.
115+
116+
**D4: Test shape mirrors the bd-qor9a nav-surface tests.** The
117+
existing `link_rewrite_diagnostic_uses_body_link_label`
118+
(`crates/quarto-core/src/transforms/link_rewrite.rs:637`) is the
119+
right place to extend the unit-test coverage. We assert
120+
`d.location.is_some()` after the rewrite, and we extend
121+
`link_inline()` in the test helpers to accept (or construct from a
122+
default) a synthetic `TargetSourceInfo { url: Some(SourceInfo::…), … }`
123+
so the test exercises the populated path.
124+
125+
The integration test in `crates/quarto-core/tests/link_rewriting_pipeline.rs`
126+
(lines 340 and 385) already builds Pandoc ASTs from real qmd source,
127+
so its `Link::target_source.url` will be populated by the parser. We
128+
add a third assertion (alongside the existing code + path checks)
129+
that each Q-13-4 diagnostic carries a non-`None` location pointing at
130+
the right file.
131+
132+
## TDD checklist
133+
134+
- [x] **Phase 0.1**: New unit test
135+
`link_rewrite_diagnostic_carries_source_location` (alongside
136+
the existing `link_rewrite_diagnostic_uses_body_link_label`)
137+
stamps a `SourceInfo` into `Link.target_source.url` and asserts
138+
that the emitted Q-13-4 carries it. Initial run **failed** with
139+
`left: None, right: Some(Original { … })`, confirming the
140+
`None`-passed status quo.
141+
- [x] **Phase 0.2**: Extended the two Q-13-4 assertions in
142+
`crates/quarto-core/tests/link_rewriting_pipeline.rs`
143+
(`pipeline_body_link_broken_qmd_emits_diagnostic` and
144+
`pipeline_body_link_unresolvable_in_website_warns`) with
145+
`q_13_4.unwrap().location.is_some()`. Both **failed** with
146+
`location: None` before the fix.
147+
148+
## Implementation checklist
149+
150+
- [x] **Phase 1.1**: Changed the `None` at
151+
`crates/quarto-core/src/transforms/link_rewrite.rs:224` to
152+
`link.target_source.url.clone()`.
153+
- [x] **Phase 1.2**: Re-ran the unit + integration tests; all three
154+
previously-failing tests now pass. Full
155+
`cargo nextest run -p quarto-core` clean (2060 passed, 33
156+
skipped).
157+
- [x] **Phase 1.3**: Updated the doc-comments on
158+
`missing_document_warning`, `resolve_href_for_html`, and
159+
`resolve_doc_relative_href` in `navigation_href.rs` so they
160+
reflect the post-bd-c05x6 reality — body-link callsite now
161+
passes the URL's `SourceInfo`; the "forward-looking, callers
162+
pass None" caveats are gone.
163+
164+
## End-to-end verification
165+
166+
- [x] **E1**: `q2 render` on `docs/` (the user's reproducer) now
167+
emits the Q-13-4 warnings *with* source location:
168+
169+
```
170+
Warning: [Q-13-4] Body link references missing document
171+
╭─[ docs/authoring/markdown/index.qmd:89:23 ]
172+
173+
89 │ * [Markdown Basics](./markdown-basics.qmd)
174+
│ ─────────┬──────────
175+
│ ╰── 'authoring/markdown/markdown-basics.qmd'
176+
│ is not in the project index.
177+
```
178+
179+
Both Q-13-4 warnings in the docs/ render carry an Ariadne
180+
excerpt pointing at the offending `.qmd:line:col`. Verified
181+
with `target/debug/q2 render /…/docs` after `cargo build --bin q2`.
182+
- [x] **E2**: Skipped as a separate fixture — the two integration
183+
tests in `link_rewriting_pipeline.rs` already exercise the
184+
Q-13-4 path through `ProjectPipeline::run` against fixture qmds,
185+
and both now assert `location.is_some()`. A fresh fixture would
186+
duplicate that coverage without adding signal.
187+
- [x] **E3**: `cargo xtask verify --skip-hub-build` — all 12 steps
188+
green (build + lint + workspace tests + WASM + q2-preview-spa
189+
bundle).
190+
191+
## What's deliberately out of scope
192+
193+
- **Other Q-13-* surfaces**: bd-qor9a already covered
194+
Q-13-1 / Q-13-2 / Q-13-3 / Q-13-7.
195+
- **Image link diagnostics**: images don't lookup-check against the
196+
index (Q1 parity decision, `link_rewrite.rs:229-234`).
197+
- **`Image::target.0`**: same as above — images point at static
198+
resources, not project documents.
199+
- **Diagnostic *content* changes**: the warning text stays unchanged;
200+
we only add a location.
201+
- **Source info on links constructed by Lua filters**: those won't
202+
have `target_source.url` populated, so the diagnostic remains
203+
location-less for filter-introduced links. That's consistent with
204+
how filter-introduced provenance is handled everywhere else.
205+
206+
## Future work (not this issue)
207+
208+
- The unit-test `link_inline()` helper currently produces
209+
`TargetSourceInfo::empty()`. We may want a sibling
210+
`link_inline_with_source(url, text, url_range)` so future tests can
211+
assert location-aware diagnostics without each test constructing
212+
the source info inline.
213+
- Once this lands, bd-hjv5o item #1 can be checked off; items #2#5
214+
(AutoSpec paths, listing contents paths, format.html.css /
215+
bibliography in frontmatter, crossref absolute-file refs) still
216+
need separate scoping.

0 commit comments

Comments
 (0)