|
11 | 11 | #' ``` |
12 | 12 | #' MARKDOWN LINK TEXT CODE RD |
13 | 13 | #' -------- --------- ---- -- |
14 | | -#' [fun()] fun() T \\link[=fun]{fun()} |
15 | | -#' [obj] obj F \\link{obj} |
| 14 | +#' [fun()] fun() T \\link[=fun]{fun()} or |
| 15 | +#' \\link[pkg:file]{pkg::fun()} |
| 16 | +#' [obj] obj F \\link{obj} or |
| 17 | +#' \\link[pkg:file]{pkg::obj} |
16 | 18 | #' [pkg::fun()] pkg::fun() T \\link[pkg:file]{pkg::fun()} |
17 | 19 | #' [pkg::obj] pkg::obj F \\link[pkg:file]{pkg::obj} |
18 | | -#' [text][fun()] text F \\link[=fun]{text} |
19 | | -#' [text][obj] text F \\link[=obj]{text} |
| 20 | +#' [text][fun()] text F \\link[=fun]{text} or |
| 21 | +#' \\link[pkg:file]{text} |
| 22 | +#' [text][obj] text F \\link[=obj]{text} or |
| 23 | +#' \\link[pkg:file]{text} |
20 | 24 | #' [text][pkg::fun()] text F \\link[pkg:file]{text} |
21 | 25 | #' [text][pkg::obj] text F \\link[pkg:file]{text} |
22 | | -#' [s4-class] s4 F \\linkS4class{s4} |
| 26 | +#' [s4-class] s4 F \\linkS4class{s4} or |
| 27 | +#' \\link[pkg:file]{s4} |
23 | 28 | #' [pkg::s4-class] pkg::s4 F \\link[pkg:file]{pkg::s4} |
24 | 29 | #' ``` |
25 | 30 | #' |
| 31 | +#' For the links with two RD variants the first version is used for |
| 32 | +#' within-package links, and the second version is used for cross-package |
| 33 | +#' links. |
| 34 | +#' |
26 | 35 | #' The reference links will always look like `R:ref` for `[ref]` and |
27 | 36 | #' `[text][ref]`. These are explicitly tested in `test-rd-markdown-links.R`. |
28 | 37 | #' |
@@ -131,13 +140,15 @@ parse_link <- function(destination, contents, state) { |
131 | 140 | is_code <- is_code || (grepl("[(][)]$", destination) && ! has_link_text) |
132 | 141 | pkg <- str_match(destination, "^(.*)::")[1,2] |
133 | 142 | pkg <- gsub("%", "\\\\%", pkg) |
134 | | - if (!is.na(pkg) && pkg == thispkg) pkg <- NA_character_ |
135 | 143 | fun <- utils::tail(strsplit(destination, "::", fixed = TRUE)[[1]], 1) |
136 | 144 | fun <- gsub("%", "\\\\%", fun) |
137 | 145 | is_fun <- grepl("[(][)]$", fun) |
138 | 146 | obj <- sub("[(][)]$", "", fun) |
139 | 147 | s4 <- str_detect(destination, "-class$") |
140 | 148 | noclass <- str_match(fun, "^(.*)-class$")[1,2] |
| 149 | + |
| 150 | + if (is.na(pkg)) pkg <- resolve_link_package(obj, thispkg, state = state) |
| 151 | + if (!is.na(pkg) && pkg == thispkg) pkg <- NA_character_ |
141 | 152 | file <- find_topic_filename(pkg, obj, state$tag) |
142 | 153 |
|
143 | 154 | ## To understand this, look at the RD column of the table above |
@@ -174,6 +185,103 @@ parse_link <- function(destination, contents, state) { |
174 | 185 | } |
175 | 186 | } |
176 | 187 |
|
| 188 | +resolve_link_package <- function(topic, me = NULL, pkgdir = NULL, state = NULL) { |
| 189 | + me <- me %||% roxy_meta_get("current_package") |
| 190 | + # this is from the roxygen2 tests, should not happen on a real package |
| 191 | + if (is.null(me) || is.na(me) || me == "") return(NA_character_) |
| 192 | + |
| 193 | + # if it is in the current package, then no need for package name, right? |
| 194 | + if (has_topic(topic, me)) return(NA_character_) |
| 195 | + |
| 196 | + # try packages in depends, imports, suggests first, error on name clashes |
| 197 | + pkgs <- local_pkg_deps(pkgdir) |
| 198 | + |
| 199 | + pkg_has_topic <- pkgs[map_lgl(pkgs, has_topic, topic = topic)] |
| 200 | + pkg_has_topic <- map_chr(pkg_has_topic, function(p) { |
| 201 | + p0 <- find_reexport_source(topic, p) |
| 202 | + if (is.na(p0)) p else p0 |
| 203 | + }) |
| 204 | + pkg_has_topic <- unique(pkg_has_topic) |
| 205 | + base <- base_packages() |
| 206 | + if (length(pkg_has_topic) == 0) { |
| 207 | + # fall through to check base packages as well |
| 208 | + } else if (length(pkg_has_topic) == 1) { |
| 209 | + if (pkg_has_topic %in% base) { |
| 210 | + return(NA_character_) |
| 211 | + } else { |
| 212 | + return(pkg_has_topic) |
| 213 | + } |
| 214 | + } else { |
| 215 | + warn_roxy_tag(state$tag, c( |
| 216 | + "Topic {.val {topic}} is available in multiple packages: {.pkg {pkg_has_topic}}", |
| 217 | + i = "Qualify topic explicitly with a package name when linking to it." |
| 218 | + )) |
| 219 | + return(NA_character_) |
| 220 | + } |
| 221 | + |
| 222 | + # try base packages as well, take the first hit, |
| 223 | + # there should not be any name clashes, anyway |
| 224 | + for (bp in base) { |
| 225 | + if (has_topic(topic, bp)) return(NA_character_) |
| 226 | + } |
| 227 | + |
| 228 | + warn_roxy_tag(state$tag, c( |
| 229 | + "Could not resolve link to topic {.val {topic}} in the dependencies or base packages", |
| 230 | + "i" = paste( |
| 231 | + "If you haven't documented {.val {topic}} yet, or just changed its name, this is normal.", |
| 232 | + "Once {.val {topic}} is documented, this warning goes away."), |
| 233 | + "i" = "Make sure that the name of the topic is spelled correctly.", |
| 234 | + "i" = "Always list the linked package as a dependency.", |
| 235 | + "i" = "Alternatively, you can fully qualify the link with a package name." |
| 236 | + )) |
| 237 | + |
| 238 | + NA_character_ |
| 239 | +} |
| 240 | + |
| 241 | +# this is mostly from downlit |
| 242 | +is_exported <- function(name, package) { |
| 243 | + name %in% getNamespaceExports(ns_env(package)) |
| 244 | +} |
| 245 | + |
| 246 | +is_reexported <- function(name, package) { |
| 247 | + if (package == "base") { |
| 248 | + return(FALSE) |
| 249 | + } |
| 250 | + is_imported <- env_has(ns_imports_env(package), name) |
| 251 | + is_imported && is_exported(name, package) |
| 252 | +} |
| 253 | + |
| 254 | +find_reexport_source <- function(topic, package) { |
| 255 | + ns <- ns_env(package) |
| 256 | + if (!env_has(ns, topic, inherit = TRUE)) { |
| 257 | + return(NA_character_) |
| 258 | + } |
| 259 | + |
| 260 | + obj <- env_get(ns, topic, inherit = TRUE) |
| 261 | + if (is.primitive(obj)) { |
| 262 | + # primitive functions all live in base |
| 263 | + "base" |
| 264 | + } else if (is.function(obj)) { |
| 265 | + ## For functions, we can just take their environment. |
| 266 | + ns_env_name(get_env(obj)) |
| 267 | + } else { |
| 268 | + ## For other objects, we need to check the import env of the package, |
| 269 | + ## to see where 'topic' is coming from. The import env has redundant |
| 270 | + ## information. It seems that we just need to find a named list |
| 271 | + ## entry that contains `topic`. |
| 272 | + imp <- getNamespaceImports(ns) |
| 273 | + imp <- imp[names(imp) != ""] |
| 274 | + wpkgs <- vapply(imp, `%in%`, x = topic, FUN.VALUE = logical(1)) |
| 275 | + |
| 276 | + if (!any(wpkgs)) { |
| 277 | + return(NA_character_) |
| 278 | + } |
| 279 | + pkgs <- names(wpkgs)[wpkgs] |
| 280 | + # Take the last match, in case imports have name clashes. |
| 281 | + pkgs[[length(pkgs)]] |
| 282 | + } |
| 283 | +} |
| 284 | + |
177 | 285 | #' Dummy page to test roxygen's markdown formatting |
178 | 286 | #' |
179 | 287 | #' Links are very tricky, so I'll put in some links here: |
|
0 commit comments