Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link to files, not topics #1109

Merged
merged 24 commits into from
Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
15ca334
Link to files, not topics #1: external links
gaborcsardi Jun 11, 2020
ce56cdd
Link to files, for internal links as well
gaborcsardi Jun 11, 2020
501d8a5
Link to files, in re-exports as well.
gaborcsardi Jun 11, 2020
b533014
Use dev rmarkdown, to work around a bug in the CRAN version
gaborcsardi Jun 12, 2020
adb089d
Do not link to file, if not needed
gaborcsardi Jun 12, 2020
a86e14c
Link to files refactoring
gaborcsardi Jun 12, 2020
66f00cd
Add test to auto-linking to file
gaborcsardi Jun 12, 2020
683941f
Fix qualified links to the dev package
gaborcsardi Jun 13, 2020
fe7dda7
Add dev docs for linking to file names
gaborcsardi Jun 13, 2020
873f8cc
Do not warn for unknown links
gaborcsardi Jun 13, 2020
8819f9a
Link to file for cross-package qualified links
gaborcsardi Jun 13, 2020
1e063d9
NEWS entry for linking to files
gaborcsardi Jun 13, 2020
b30da61
Add tests for unknown files/topics in re-exports
gaborcsardi Jun 14, 2020
e8dece5
Fix link in note of @inheritDotParams
gaborcsardi Jun 14, 2020
be0fd82
Fix links in inherited docs
gaborcsardi Jun 14, 2020
1c2f7e6
Apply suggestions from code review
gaborcsardi Jun 18, 2020
2db56ed
Link-to-file: fix comment
gaborcsardi Jun 18, 2020
51ecc2d
Fix vectorization in inherited links
gaborcsardi Jun 18, 2020
5cd97dc
Refactor linked file finding code
gaborcsardi Jun 18, 2020
59bdd0f
Don't need dev rmarkdown any more
gaborcsardi Jun 25, 2020
e7de910
Close a dangling connection
gaborcsardi Jun 25, 2020
9947c05
Refactor link-to-file helpers
gaborcsardi Jun 25, 2020
3dc1b0e
Do not qualify links to the current package
gaborcsardi Jun 25, 2020
e54d06c
merge origin/master
hadley Jun 26, 2020
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
1 change: 1 addition & 0 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ jobs:
run: |
remotes::install_deps(dependencies = TRUE)
remotes::install_cran("rcmdcheck")
remotes::install_github("rstudio/rmarkdown")
gaborcsardi marked this conversation as resolved.
Show resolved Hide resolved
shell: Rscript {0}

- name: Check
Expand Down
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ VignetteBuilder:
knitr
Encoding: UTF-8
Roxygen: list(markdown = TRUE, load = "installed")
RoxygenNote: 7.1.0
RoxygenNote: 7.1.0.9000
Language: en-GB
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# roxygen2 (development version)

* When processing cross package markdown links (e.g. `[pkg::fun()]`),
roxygen2 now looks up the file it needs to link to, instead of linking to
the topic, to avoid "Non-file package-anchored links" `R CMD check` warnings.

# roxygen2 7.1.0

## New features
Expand Down
20 changes: 13 additions & 7 deletions R/markdown-link.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@
#' spaces between the closing and opening bracket in the `[text][ref]`
#' form.
#'
#' Starting from R 4.0.2-ish, explicit cross-package links to topics are not
#' allowed, so for each such linked topic, we look up the linked file.
#'
#' These are the link references we add:
#' ```
#' MARKDOWN LINK TEXT CODE RD
#' -------- --------- ---- --
#' [fun()] fun() T \\link[=fun]{fun()}
#' [obj] obj F \\link{obj}
#' [pkg::fun()] pkg::fun() T \\link[pkg:fun]{pkg::fun()}
#' [pkg::obj] pkg::obj F \\link[pkg:obj]{pkg::obj}
#' [pkg::fun()] pkg::fun() T \\link[pkg:file]{pkg::fun()}
#' [pkg::obj] pkg::obj F \\link[pkg:file]{pkg::obj}
#' [text][fun()] text F \\link[=fun]{text}
#' [text][obj] text F \\link[=obj]{text}
#' [text][pkg::fun()] text F \\link[pkg:fun]{text}
#' [text][pkg::obj] text F \\link[pkg:obj]{text}
#' [text][pkg::fun()] text F \\link[pkg:file]{text}
#' [text][pkg::obj] text F \\link[pkg:file]{text}
#' [s4-class] s4 F \\linkS4class{s4}
#' [pkg::s4-class] pkg::s4 F \\link[pkg:s4-class]{pkg::s4}
#' [pkg::s4-class] pkg::s4 F \\link[pkg:file]{pkg::s4}
#' ```
#'
#' The reference links will always look like `R:ref` for `[ref]` and
Expand Down Expand Up @@ -111,6 +114,8 @@ parse_link <- function(destination, contents, state) {
## `obj` is fun or obj (fun is without parens)
## `s4` is TRUE if we link to an S4 class (i.e. have -class suffix)
## `noclass` is fun with -class removed
## `file` is the file name of the linked topic, if known. Otherwise random
gaborcsardi marked this conversation as resolved.
Show resolved Hide resolved
## id to fill in later.

is_code <- is_code || (grepl("[(][)]$", destination) && ! has_link_text)
pkg <- str_match(destination, "^(.*)::")[1,2]
Expand All @@ -121,6 +126,7 @@ parse_link <- function(destination, contents, state) {
obj <- sub("[(][)]$", "", fun)
s4 <- str_detect(destination, "-class$")
noclass <- str_match(fun, "^(.*)-class$")[1,2]
file <- find_topic_filename(pkg, obj, state$tag)

## To understand this, look at the RD column of the table above
if (!has_link_text) {
Expand All @@ -130,7 +136,7 @@ parse_link <- function(destination, contents, state) {
if (is_fun || ! is.na(pkg)) "[",
if (is_fun && is.na(pkg)) "=",
if (! is.na(pkg)) paste0(pkg, ":"),
if (is_fun || ! is.na(pkg)) paste0(obj, "]"),
if (is_fun || ! is.na(pkg)) paste0(if (is.na(pkg)) obj else file, "]"),
"{",
if (!is.na(pkg)) paste0(pkg, "::"),
if (s4) noclass else fun,
Expand All @@ -146,7 +152,7 @@ parse_link <- function(destination, contents, state) {
if (is_code) "\\code{",
"\\link[",
if (is.na(pkg)) "=" else paste0(pkg, ":"),
obj,
if (is.na(pkg)) obj else file,
"]{"
),
contents,
Expand Down
23 changes: 22 additions & 1 deletion R/object-import.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ merge.rd_section_reexport <- function(x, y, ...) {
format.rd_section_reexport <- function(x, ...) {
pkgs <- split(x$value$fun, x$value$pkg)
pkg_links <- map2(names(pkgs), pkgs, function(pkg, funs) {
links <- paste0("\\code{\\link[", pkg, "]{", escape(sort(funs)), "}}",
funs <- sort(funs)
files <- vapply(funs, find_topic_in_package_reexp, character(1), pkg = pkg)
links <- paste0(
"\\code{\\link[", pkg,
ifelse(files == funs, "", paste0(":", files)),
hadley marked this conversation as resolved.
Show resolved Hide resolved
"]{", escape(funs), "}}",
collapse = ", ")
paste0("\\item{", pkg, "}{", links, "}")
})
Expand All @@ -34,3 +39,19 @@ format.rd_section_reexport <- function(x, ...) {
"\n}}\n"
)
}

find_topic_in_package_reexp <- function(pkg, topic) {
path <- tryCatch(
gaborcsardi marked this conversation as resolved.
Show resolved Hide resolved
find_topic_in_package(pkg, topic),
error = function(err) {
roxy_warning("Unavailable package in re-export: ", pkg, "::", topic)
topic
}
)
if (is.na(path)) {
roxy_warning("Unavailable topic in re-export: ", pkg, "::", topic)
topic
} else {
path
gaborcsardi marked this conversation as resolved.
Show resolved Hide resolved
}
}
63 changes: 63 additions & 0 deletions R/rd-find-link-files.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

#' Find the Rd file of a topic
#'
#' @param pkg Package to search in, or `NA` if no package was specified.
#' If the same as the dev package, then we treat it as `NA`.
#' @param topic Topic to search for. This is the escaped, so it is `"\%\%"` and
#' not `"%%"`.
#' @param tag The roxy tag object that contains the link. We use this for
#' better warnings, that include the file name and line number (of the tag).
#' @return String. File name to link to.
#'
#' @details
#' If `pkg` is `NA` or the package being documented, we'll just leave the
#' topic alone.
#'
#' If `pkg` is not `NA` and not the package being documented (the _dev_
#' package), then we need to be able to find the Rd file. If we can't, that's
#' a warning and the link is left untouched. This typically happens when the
#' linked package is not installed or cannot be loaded.
#'
#' @noRd

find_topic_filename <- function(pkg, topic, tag) {
gaborcsardi marked this conversation as resolved.
Show resolved Hide resolved
tag <- tag %||% list(file = NA, line = NA)
if (is.na(pkg) || identical(roxy_meta_get("current_package"), pkg)) {
topic
} else {
path <- tryCatch(
find_topic_in_package(pkg, topic),
error = function(err) {
roxy_tag_warning(tag, "Link to unavailable package: ", pkg, ". ", err$message)
topic
}
)
if (is.na(path)) {
roxy_tag_warning(tag, "Link to unknown topic: ", topic, " in package ", pkg)
topic
} else {
path
}
}
}

#' Find a help topic in a package
#'
#' This is used by both `find_topic_filename()` and
#' `format.rd_section_reexport()` that creates the re-exports page. The error
#' messages are different for the two, so errors are not handled here.
#'
#' @param pkg Package name. This cannot be `NA`.
#' @inheritParams find_topic_filename
#' @return File name if the topic was found, `NA` if the package could be
#' searched, but the topic was not found. Errors if the package cannot be
#' searched. (Because it is not installed or cannot be loaded, etc.)
#'
#' @noRd

find_topic_in_package <- function(pkg, topic) {
# This is needed because we have the escaped text here, and parse_Rd will
# un-escape it properly.
raw_topic <- str_trim(tools::parse_Rd(textConnection(topic))[[1]][1])
gaborcsardi marked this conversation as resolved.
Show resolved Hide resolved
basename(utils::help((raw_topic), (pkg))[1])
}
8 changes: 7 additions & 1 deletion R/rd-inherit.R
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,13 @@ inherit_dot_params <- function(topic, topics, env) {
# Build the Rd
# (1) Link to function(s) that was inherited from
src <- inheritors$source
dest <- ifelse(has_colons(src), gsub("::", ":", src), paste0("=", src))
if (has_colons(src)) {
target <- str_split_fixed(src, "::", n = 2)
file <- find_topic_in_package(target[1], target[2])
dest <- paste0(target[1], ":", file)
} else {
dest <- paste0("=", src)
}
from <- paste0("\\code{\\link[", dest, "]{", src, "}}", collapse = ", ")

# (2) Show each inherited argument
Expand Down
24 changes: 23 additions & 1 deletion R/utils-rd.R
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,14 @@ tweak_links <- function(x, package) {
topic <- substr(opt, 2, nchar(opt))

if (has_topic(topic, package)) {
attr(x, "Rd_option") <- structure(paste0(package, ":", topic), Rd_tag = "TEXT")
file <- find_topic_in_package(package, topic)
attr(x, "Rd_option") <- structure(paste0(package, ":", file), Rd_tag = "TEXT")
}
} else if (grepl(":", opt)) {
# need to fix the link to point to a file
target <- str_split_fixed(opt, ":", n = 2)
file <- find_topic_in_inherited_link(target[1], target[2])
attr(x, "Rd_option") <- structure(paste0(target[1], ":", file), Rd_tag = "TEXT")
}
}
} else if (length(x) > 0) {
Expand All @@ -100,6 +106,22 @@ tweak_links <- function(x, package) {
x
}

find_topic_in_inherited_link <- function(pkg, topic) {
path <- tryCatch(
find_topic_in_package(pkg, topic),
error = function(err) {
roxy_warning("Unavailable package in inherited link: ", pkg, "::", topic)
topic
}
)
if (is.na(path)) {
roxy_warning("Unavailable topic in inherited link: ", pkg, "::", topic)
topic
} else {
path
}
}

# helpers -----------------------------------------------------------------

parse_rd <- function(x) {
Expand Down
Binary file modified man/figures/test-figure-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions man/markdown_pass1.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 19 additions & 13 deletions tests/testthat/test-markdown-link.R
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ test_that("% in links are escaped", {
expect_equal(markdown("[x][%%]"), "\\link[=\\%\\%]{x}")
expect_equal(markdown("[%][x]"), "\\link[=x]{\\%}")
expect_equal(markdown("[%%]"), "\\link{\\%\\%}")
expect_equal(markdown("[foo::%%]"), "\\link[foo:\\%\\%]{foo::\\%\\%}")
expect_equal(markdown("[base::%%]"), "\\link[base:Arithmetic]{base::\\%\\%}")
})

test_that("commonmark picks up the various link references", {
Expand Down Expand Up @@ -94,16 +94,19 @@ test_that("short and sweet links work", {
foo <- function() {}")[[1]]
expect_equivalent_rd(out1, out2)

out1 <- roc_proc_text(rd_roclet(), "
expect_warning(
out1 <- roc_proc_text(rd_roclet(), "
#' Title
#'
#' See [pkg::function()], [pkg::object].
#' See [11pkg::function()], [11pkg::object].
hadley marked this conversation as resolved.
Show resolved Hide resolved
#' @md
foo <- function() {}")[[1]]
foo <- function() {}")[[1]],
"Link to unavailable package"
)
out2 <- roc_proc_text(rd_roclet(), "
#' Title
#'
#' See \\code{\\link[pkg:function]{pkg::function()}}, \\link[pkg:object]{pkg::object}.
#' See \\code{\\link[11pkg:function]{11pkg::function()}}, \\link[11pkg:object]{11pkg::object}.
foo <- function() {}")[[1]]
expect_equivalent_rd(out1, out2)

Expand All @@ -120,16 +123,19 @@ test_that("short and sweet links work", {
foo <- function() {}")[[1]]
expect_equivalent_rd(out1, out2)

out1 <- roc_proc_text(rd_roclet(), "
expect_warning(
out1 <- roc_proc_text(rd_roclet(), "
#' Title
#'
#' Description, see [name words][pkg::bar].
#' Description, see [name words][stringr::bar111].
#' @md
foo <- function() {}")[[1]]
foo <- function() {}")[[1]],
"Link to unknown topic: bar111 in package stringr"
)
out2 <- roc_proc_text(rd_roclet(), "
#' Title
#'
#' Description, see \\link[pkg:bar]{name words}.
#' Description, see \\link[stringr:bar111]{name words}.
foo <- function() {}")[[1]]
expect_equivalent_rd(out1, out2)

Expand Down Expand Up @@ -243,7 +249,7 @@ test_that("another markdown link bug is fixed", {

test_that("markdown code as link text is rendered as code", {

out1 <- roc_proc_text(rd_roclet(), "
suppressWarnings(out1 <- roc_proc_text(rd_roclet(), "
#' Title
#'
#' Description, see [`name`][dest],
Expand All @@ -253,7 +259,7 @@ test_that("markdown code as link text is rendered as code", {
#' [`terms`][terms.object],
#' [`abc`][abc-class].
#' @md
foo <- function() {}")[[1]]
foo <- function() {}")[[1]])
out2 <- roc_proc_text(rd_roclet(), "
#' Title
#'
Expand Down Expand Up @@ -366,12 +372,12 @@ test_that("links to S4 classes are OK", {
foo <- function() {}")[[1]]
expect_equivalent_rd(out1, out2)

out1 <- roc_proc_text(rd_roclet(), "
suppressWarnings(out1 <- roc_proc_text(rd_roclet(), "
#' Title
#'
#' Description, see [pkg::linktos4-class] as well.
#' @md
foo <- function() {}")[[1]]
foo <- function() {}")[[1]])
out2 <- roc_proc_text(rd_roclet(), "
#' Title
#'
Expand Down
11 changes: 11 additions & 0 deletions tests/testthat/test-object-import.R
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,14 @@ test_that("can't set description and re-export", {

expect_length(out, 0)
})

test_that("warnings for unknown packages and objects", {
expect_warning(
format(rd_section_reexport("11papaya", "fun")),
"Unavailable package in re-export"
)
expect_warning(
format(rd_section_reexport("stringr", "12345543221")),
"Unavailable topic in re-export"
)
})
2 changes: 1 addition & 1 deletion tests/testthat/testNamespace/DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ Author: Hadley <hadley@rstudio.com>
Maintainer: Hadley <hadley@rstudio.com>
Encoding: UTF-8
Version: 0.1
RoxygenNote: 7.0.2.9000
RoxygenNote: 7.1.0.9000