Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 53 additions & 25 deletions src/cross_references.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -350,28 +372,34 @@ pipeline.
"""
const XREF_REGEX = r"^\s*@ref(\s.*)?$"

"""Regular expression for a slug
"""
const HEADER_REGEX = r"^\".+\"$"


# Cross referencing headers.
# --------------------------

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.
Expand Down
61 changes: 57 additions & 4 deletions test/docsxref/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -77,6 +120,15 @@ end
@test contains(html, "<a href=\"index.html#Documenter.Selectors.AbstractSelector\"><code>Documenter.Selectors.AbstractSelector</code></a>")
@test contains(html, "<a href=\"index.html#Documenter.Selectors.AbstractSelector\"><code>Main.AbstractSelector</code></a>")
@test contains(html, "<a href=\"@ref\"><code>AbstractSelector</code></a>")

@test contains(html, "<a href=\"index.html#API\">API</a>")
@test contains(html, "<a href=\"index.html#API\">header link</a>")
@test contains(html, "<a href=\"index.html#Two-words\">Two words</a>")
@test contains(html, "<a href=\"index.html#Two-words\">header link</a>")
@test contains(html, "<a href=\"https://github.com/JuliaDocs/Documenter.jl/issues/12345\">#12345</a>")
@test contains(html, "<a href=\"https://github.com/JuliaDocs/Documenter.jl/issues/12345\">issue link</a>")
@test contains(html, "<a href=\"index.html#Main.DocsReferencingMain.g\"><code>DocsReferencingMain.g</code></a>")
@test contains(html, "<a href=\"index.html#Main.DocsReferencingMain.g\">docstring link</a>")
end
page_html = joinpath(dirname(@__FILE__), "build", "page.html")
@test isfile(page_html)
Expand All @@ -88,6 +140,7 @@ end
@test contains(html, "<a href=\"index.html#Documenter.hide\"><code>Documenter.hide</code></a>")
end


end

end
25 changes: 25 additions & 0 deletions test/docsxref/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -13,3 +34,7 @@ DocsReferencingMain.g
Documenter.Selectors.AbstractSelector
Documenter.hide
```

## Two words

Something
Loading