diff --git a/src/cross_references.jl b/src/cross_references.jl
index 35389d515d..1dfaa286b8 100644
--- a/src/cross_references.jl
+++ b/src/cross_references.jl
@@ -228,9 +228,15 @@ function xref_unresolved(node)
occursin(XREF_REGEX, node.element.destination)
end
-
function Selectors.matcher(::Type{XRefResolvers.Header}, node, slug, meta, page, doc, errors)
- return (xref_unresolved(node) && anchor_exists(doc.internal.headers, slug))
+ xref_unresolved(node) || return false
+ dest = xrefname(node.element.destination)
+
+ if isempty(dest)
+ return first(linkcontent(node)) ∈ (:text, :complex)
+ else
+ return !startswith(dest, "#") && occursin(HEADER_REGEX, dest)
+ end
end
function Selectors.runner(::Type{XRefResolvers.Header}, node, slug, meta, page, doc, errors)
@@ -248,7 +254,15 @@ end
function Selectors.matcher(::Type{XRefResolvers.Docs}, node, slug, meta, page, doc, errors)
- return xref_unresolved(node)
+ xref_unresolved(node) || return false
+
+ dest = xrefname(node.element.destination)
+
+ if isempty(dest)
+ return first(linkcontent(node)) == :code
+ else
+ return !startswith(dest, "#")
+ end
end
function Selectors.runner(::Type{XRefResolvers.Docs}, node, slug, meta, page, doc, errors)
@@ -285,15 +299,7 @@ function xref(node::MarkdownAST.Node, meta, page, doc)
slug = xrefname(link.destination)
@assert !isnothing(slug)
if isempty(slug)
- # obtain a slug from the link text
- if length(node.children) == 1 && isa(first(node.children).element, MarkdownAST.Code)
- slug = first(node.children).element.code
- else
- # TODO: remove this hack (replace with mdflatten?)
- md = _link_node_as_md(node)
- text = strip(sprint(Markdown.plain, Markdown.Paragraph(md.content[1].content[1].text)))
- slug = Documenter.slugify(text)
- end
+ slug = Documenter.slugify(last(linkcontent(node)))
else
# explicit slugs that are enclosed in quotes must be further sluggified
stringmatch = match(r"\"(.+)\"", slug)
@@ -341,6 +347,22 @@ function xrefname(link_url::AbstractString)
return isnothing(m[1]) ? "" : strip(m[1])
end
+function linkcontent(node::MarkdownAST.Node)
+ isa(node.element, MarkdownAST.Link) || return nothing
+
+ if length(node.children) == 1
+ child = first(node.children).element
+ if isa(child, MarkdownAST.Code)
+ return (:code, child.code)
+ elseif isa(child, MarkdownAST.Text)
+ return (:text, child.text)
+ end
+ end
+
+ text = MDFlatten.mdflatten(node)
+ return (:complex, text)
+end
+
"""Regular expression for an `@ref` link url.
This is used by the [`XRefResolvers.XRefResolverPipeline`](@ref), respectively
@@ -350,6 +372,10 @@ pipeline.
"""
const XREF_REGEX = r"^\s*@ref(\s.*)?$"
+"""Regular expression for a slug
+"""
+const HEADER_REGEX = r"^\".+\"$"
+
# Cross referencing headers.
# --------------------------
@@ -357,21 +383,23 @@ const XREF_REGEX = r"^\s*@ref(\s.*)?$"
function namedxref(node::MarkdownAST.Node, slug, meta, page, doc, errors)
@assert node.element isa MarkdownAST.Link
headers = doc.internal.headers
- @assert anchor_exists(headers, slug)
- # Add the link to list of local uncheck links.
- doc.internal.locallinks[node.element] = node.element.destination
- # Error checking: `slug` should exist and be unique.
- # TODO: handle non-unique slugs.
- if anchor_isunique(headers, slug)
- # Replace the `@ref` url with a path to the referenced header.
- anchor = Documenter.anchor(headers, slug)
- pagekey = relpath(anchor.file, doc.user.build)
- page = doc.blueprint.pages[pagekey]
- node.element = Documenter.PageLink(page, anchor_label(anchor))
+ if anchor_exists(headers, slug)
+ # Add the link to list of local uncheck links.
+ doc.internal.locallinks[node.element] = node.element.destination
+ # Error checking: `slug` should exist and be unique.
+ # TODO: handle non-unique slugs.
+ if anchor_isunique(headers, slug)
+ # Replace the `@ref` url with a path to the referenced header.
+ anchor = Documenter.anchor(headers, slug)
+ pagekey = relpath(anchor.file, doc.user.build)
+ page = doc.blueprint.pages[pagekey]
+ node.element = Documenter.PageLink(page, anchor_label(anchor))
+ else
+ push!(errors, "Header with slug '$slug' is not unique in $(Documenter.locrepr(page.source)).")
+ end
else
- push!(errors, "Header with slug '$slug' is not unique in $(Documenter.locrepr(page.source)).")
+ push!(errors, "Header with slug '$slug' in $(Documenter.locrepr(page.source)) does not exist.")
end
- return
end
# Cross referencing docstrings.
diff --git a/test/docsxref/make.jl b/test/docsxref/make.jl
index 1d31f6f134..0e2bce8620 100644
--- a/test/docsxref/make.jl
+++ b/test/docsxref/make.jl
@@ -52,23 +52,66 @@ end
captured = IOCapture.capture() do
makedocs(; kwargs...)
end
+
+ output = replace(captured.output,
+ "\\src\\index" => "/src/index",
+ "\\src\\page" => "/src/page")
+
@test isnothing(captured.value)
- @test contains(
- replace(captured.output, "\\src\\index" => "/src/index"),
+ @test contains(output,
"""
┌ Warning: Cannot resolve @ref for md"[`AbstractSelector`](@ref)" in docsxref/src/index.md.
│ - No docstring found in doc for binding `Main.DocsReferencingMain.AbstractSelector`.
│ - Fallback resolution in Main for `AbstractSelector` -> `Documenter.Selectors.AbstractSelector` is only allowed for fully qualified names
"""
)
- @test contains(
- replace(captured.output, "\\src\\page" => "/src/page"),
+ @test contains(output,
"""
┌ Warning: Cannot resolve @ref for md"[`DocsReferencingMain.f`](@ref)" in docsxref/src/page.md.
│ - Exception trying to find docref for `DocsReferencingMain.f`: unable to get the binding for `DocsReferencingMain.f` in module Documenter.Selectors
│ - Fallback resolution in Main for `DocsReferencingMain.f` -> `Main.DocsReferencingMain.f` is only allowed for fully qualified names
"""
)
+
+ @test contains(output,
+ """
+ ┌ Warning: Cannot resolve @ref for md"[header](@ref)" in docsxref/src/index.md.
+ │ - Header with slug 'header' in docsxref/src/index.md does not exist.
+ """
+ )
+ @test contains(output,
+ """
+ ┌ Warning: Cannot resolve @ref for md"[header link](@ref \\\"header\\\")" in docsxref/src/index.md.
+ │ - Header with slug 'header' in docsxref/src/index.md does not exist.
+ """
+ )
+
+ @test contains(output,
+ """
+ ┌ Warning: Cannot resolve @ref for md"[Multiple words](@ref)" in docsxref/src/index.md.
+ │ - Header with slug 'Multiple-words' in docsxref/src/index.md does not exist.
+ """
+ )
+ @test contains(output,
+ """
+ ┌ Warning: Cannot resolve @ref for md"[header link](@ref \\\"Multiple words\\\")" in docsxref/src/index.md.
+ │ - Header with slug 'Multiple-words' in docsxref/src/index.md does not exist.
+ """
+ )
+
+ @test contains(output,
+ """
+ ┌ Warning: Cannot resolve @ref for md"[`foobar`](@ref)" in docsxref/src/index.md.
+ │ - No docstring found in doc for binding `Main.foobar`.
+ """
+ )
+ @test contains(output,
+ """
+ ┌ Warning: Cannot resolve @ref for md"[docstring link](@ref Main.foobar)" in docsxref/src/index.md.
+ │ - No docstring found in doc for binding `Main.foobar`.
+ """
+ )
+
index_html = joinpath(dirname(@__FILE__), "build", "index.html")
@test isfile(index_html)
if isfile(index_html)
@@ -77,6 +120,15 @@ end
@test contains(html, "Documenter.Selectors.AbstractSelector")
@test contains(html, "Main.AbstractSelector")
@test contains(html, "AbstractSelector")
+
+ @test contains(html, "API")
+ @test contains(html, "header link")
+ @test contains(html, "Two words")
+ @test contains(html, "header link")
+ @test contains(html, "#12345")
+ @test contains(html, "issue link")
+ @test contains(html, "DocsReferencingMain.g")
+ @test contains(html, "docstring link")
end
page_html = joinpath(dirname(@__FILE__), "build", "page.html")
@test isfile(page_html)
@@ -88,6 +140,7 @@ end
@test contains(html, "Documenter.hide")
end
+
end
end
diff --git a/test/docsxref/src/index.md b/test/docsxref/src/index.md
index e119cc2279..54154e963d 100644
--- a/test/docsxref/src/index.md
+++ b/test/docsxref/src/index.md
@@ -2,6 +2,27 @@
On the *page* (unlike in the `g` docstring) can link directly to [`AbstractSelector`](@ref) because the `CurrentModule` is `Main`.
+Implicit link to a header (single word): [API](@ref).
+Explicit link to a header (single word): [header link](@ref "API").
+
+Implicit link to a header (multiple words): [Two words](@ref).
+Explicit link to a header (multiple words): [header link](@ref "Two words").
+
+Implicit link to a non-existent header (single word): [header](@ref).
+Explicit link to a non-existent header (single word): [header link](@ref "header").
+
+Implicit link to a non-existent header (multiple words): [Multiple words](@ref).
+Explicit link to a non-existent header (multiple words): [header link](@ref "Multiple words").
+
+Implicit link to an issue: [#12345](@ref).
+Explicit link to an issue: [issue link](@ref #12345).
+
+Implicit link to a docstring: [`DocsReferencingMain.g`](@ref).
+Explicit link to a docstring: [docstring link](@ref DocsReferencingMain.g).
+
+Implicit link to a non-existent docstring: [`foobar`](@ref).
+Explicit link to a non-existent docstring: [docstring link](@ref Main.foobar).
+
## API
```@docs
@@ -13,3 +34,7 @@ DocsReferencingMain.g
Documenter.Selectors.AbstractSelector
Documenter.hide
```
+
+## Two words
+
+Something