phase 15: link health + TOC/links/orphans
New diagnostics module hosts the `nuwiki.link` source: walks every
`WikiLinkNode` and emits a diagnostic per broken target. Severity is
gated by `cfg.link_severity` (off | hint | warn | error). Wired into
`collect_diagnostics` so didOpen/didChange publish link diagnostics
alongside parse errors.
Classification (`BrokenLinkKind`):
- `Wiki` target missing from the workspace index → MissingPage.
- `Wiki`/`AnchorOnly` target with an anchor that matches neither a
heading nor a `:tag:` on the resolved page → MissingAnchor.
- `file:` / `local:` target whose resolved path isn't on disk →
MissingFile. Absolute paths are used as-is; relative paths resolve
against the source URI's parent directory, falling back to the wiki
root.
- Interwiki/Diary/Raw/external — never diagnosed.
`classify_link` works off a `&WikiLinkNode`; `classify_outgoing` does
the same job from a cached `IndexedPage.outgoing` entry so the
workspace-wide checker doesn't need to re-parse every page.
New commands:
- `nuwiki.toc.generate` — generate `= Contents =` heading + nested
list of `[[#anchor|Title]]` entries from current headings. Replaces
any existing h1 "Contents" + its immediate following list
case-insensitively; inserts at line 0 otherwise. Skips the TOC's
own heading so re-generation is idempotent.
- `nuwiki.links.generate` — equivalent of `:VimwikiGenerateLinks`.
Flat alphabetical list under `= Generated Links =`, excludes the
current page.
- `nuwiki.workspace.checkLinks` — returns `Vec<BrokenLinkEntry>` with
`{ uri, range, kind, message }` per broken link across the wiki.
Open documents use live text for range conversion; closed pages
fall back to the stored span coords. Accepts an optional `uri` to
pick a specific wiki; defaults to the first registered one.
- `nuwiki.workspace.findOrphans` — returns `Vec<{ uri, name }>` for
every indexed page with no incoming links. Sorted alphabetically.
Pure ops (`commands::ops`):
- `build_toc_text(items, heading_name)` — formats the TOC body with
2-space indent per level, anchors via `index::slugify`.
- `build_links_text(pages, heading_name, exclude)` — formats the flat
list.
- `find_section_range(ast, heading_name)` — locates an existing h1
section (heading + immediate following list) by case-insensitive
title match.
- `toc_edit` / `links_edit` — full edit producers.
- `collect_workspace_broken_links` / `find_orphans`.
Plumbing:
- `collect_diagnostics` gains a `uri: Option<&Url>` parameter so
link-health can resolve `file:` / `local:` relatives. `ast_diagnostics`
back-compat wrapper preserved.
- `Backend::default_wiki` is no longer `#[allow(dead_code)]`.
Tests: 31 new in `phase15_link_health.rs` covering severity mapping,
classification on every kind/anchor case, TOC nesting, links exclusion,
section-range case-insensitivity, idempotent re-generation, orphan
detection, and the COMMANDS list. The Phase 11 stub test was rewritten
into a real "no index = no diagnostics" assertion. All 274 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
|
||||
pub mod commands;
|
||||
pub mod config;
|
||||
pub mod diagnostics;
|
||||
pub mod edits;
|
||||
pub mod index;
|
||||
pub mod nav;
|
||||
@@ -141,9 +142,8 @@ impl Backend {
|
||||
wikis.iter().find(|w| w.id == id).cloned()
|
||||
}
|
||||
|
||||
/// Scaffolded for Phase 12+ commands that operate on the default wiki
|
||||
/// without a URI in hand (workspace-level operations).
|
||||
#[allow(dead_code)]
|
||||
/// Used by Phase 15 workspace-level commands (`checkLinks`, `findOrphans`)
|
||||
/// when no URI is supplied — they fall back to the first registered wiki.
|
||||
fn default_wiki(&self) -> Option<Wiki> {
|
||||
let wikis = self.wikis.read().ok()?;
|
||||
wikis.first().cloned()
|
||||
@@ -236,13 +236,14 @@ impl Backend {
|
||||
collect_diagnostics(
|
||||
&ast,
|
||||
&text,
|
||||
Some(&uri),
|
||||
Some(&idx_guard),
|
||||
page_name.as_deref(),
|
||||
&cfg.diagnostic,
|
||||
utf8,
|
||||
)
|
||||
} else {
|
||||
collect_diagnostics(&ast, &text, None, None, &cfg.diagnostic, utf8)
|
||||
collect_diagnostics(&ast, &text, Some(&uri), None, None, &cfg.diagnostic, utf8)
|
||||
};
|
||||
diags
|
||||
};
|
||||
@@ -786,7 +787,15 @@ fn utf16_column(text: &str, line: u32, byte_col: u32) -> u32 {
|
||||
/// should use `collect_diagnostics` so they can opt into link-health
|
||||
/// (Phase 15) and other sources.
|
||||
pub fn ast_diagnostics(ast: &DocumentNode, text: &str, utf8: bool) -> Vec<Diagnostic> {
|
||||
collect_diagnostics(ast, text, None, None, &DiagnosticConfig::default(), utf8)
|
||||
collect_diagnostics(
|
||||
ast,
|
||||
text,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&DiagnosticConfig::default(),
|
||||
utf8,
|
||||
)
|
||||
}
|
||||
|
||||
/// Composable diagnostic collector (P11 §12.2.4).
|
||||
@@ -794,13 +803,12 @@ pub fn ast_diagnostics(ast: &DocumentNode, text: &str, utf8: bool) -> Vec<Diagno
|
||||
/// Sources, each gated by `cfg`:
|
||||
///
|
||||
/// 1. Parse errors (always on).
|
||||
/// 2. Broken-link warnings — wired up in Phase 15. For Phase 11 the
|
||||
/// `index` / `page_name` / `cfg.link_severity` arguments are
|
||||
/// accepted but the source is a stub returning no diagnostics, so
|
||||
/// handlers can already pass them in without further refactor.
|
||||
/// 2. Broken-link warnings (Phase 15) — emitted when an index, source
|
||||
/// page name, and `link_severity != Off` are all available.
|
||||
pub fn collect_diagnostics(
|
||||
ast: &DocumentNode,
|
||||
text: &str,
|
||||
uri: Option<&Url>,
|
||||
index: Option<&WorkspaceIndex>,
|
||||
page_name: Option<&str>,
|
||||
cfg: &DiagnosticConfig,
|
||||
@@ -808,8 +816,17 @@ pub fn collect_diagnostics(
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut out = Vec::new();
|
||||
walk_blocks_for_errors(&ast.children, text, utf8, &mut out);
|
||||
// Phase 15 will implement link-health here.
|
||||
let _ = (index, page_name, cfg);
|
||||
if let (Some(idx), Some(name)) = (index, page_name) {
|
||||
out.extend(diagnostics::link_health(
|
||||
ast,
|
||||
text,
|
||||
uri,
|
||||
idx,
|
||||
name,
|
||||
cfg.link_severity,
|
||||
utf8,
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user