---
title: "Working with srcrefs"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Working with srcrefs}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)

local({
  source_hook <- knitr::knit_hooks$get("source")
  knitr::knit_hooks$set(source = function(x, options) {
    old_opts <- options(width = 120L)
    on.exit(options(old_opts))

    source_hook(x, options)
  })
})
```

```{r setup}
library(covtracer)
```

```{r, include = FALSE}
library(withr)
library(covr)

withr::with_temp_libpaths({
  options(keep.source = TRUE, keep.source.pkg = TRUE, covr.record_tests = TRUE)
  examplepkg_source_path <- system.file("examplepkg", package = "covtracer")
  install.packages(
    examplepkg_source_path,
    type = "source",
    repos = NULL,
    quiet = TRUE,
    INSTALL_opts = c("--with-keep.source", "--install-tests")
  )
  examplepkg_cov <- covr::package_coverage(examplepkg_source_path)
  examplepkg_ns <- getNamespace("examplepkg")
})
```

# What are `srcref` objects?

```{r}
print(examplepkg_ns$hypotenuse)
```

`srcref`s are a base R data type that is used frequently for working with
package code. When you install a package using the `--with-keep.source` flag,
data about the package's source code representation is bound to the objects that
the namespace attaches or loads. In short, `srcref`s are simple data structures
which store the file path of the source code and information about where to find
the relevant bits in that file including line and character columns of the start
and end of the source code.

> For extensive details, refer to `?getSrcref` and `?srcref`.

Lets see it in action:

```{r}
getSrcref(covtracer::test_trace_df)
```

```{r}
# get line and column ranges (for details see ?srcref)
as.numeric(getSrcref(covtracer::test_trace_df))
```

```{r}
getSrcFilename(covtracer::test_trace_df)
```

# Extracting relevant traceability `srcref`s

Instead of working with these objects directly, there are a few helper functions
for making these objects easier to extract. For tracing coverage paths, there
are three important classes of `srcref`s:

1. Package namespace object `srcref`s
1. Test code `srcref`s
1. Coverage trace `srcref`s

## Setup

Before we begin, we'll get a package test coverage object and store the package
namespace. We take extra precaution to use a temporary library for the sake of
example, but this is only necessary when we want to avoid installing the package
into our working library.

```{r, eval = FALSE}
library(withr)
library(covr)

withr::with_temp_libpaths({
  options(keep.source = TRUE, keep.source.pkg = TRUE, covr.record_tests = TRUE)
  examplepkg_source_path <- system.file("examplepkg", package = "covtracer")
  install.packages(
    examplepkg_source_path,
    type = "source",
    repos = NULL,
    INSTALL_opts = c("--with-keep.source", "--install-tests")
  )
  examplepkg_cov <- covr::package_coverage(examplepkg_source_path)
  examplepkg_ns <- getNamespace("examplepkg")
})
```

## Functions for extracting `srcref`s

There are a few functions for teasing out this information succinctly. These
include `pkg`, `trace`, and `test` flavors for `*_srcefs` and `*_srcrefs_df`
families of functions (eg, `pkg_srcrefs_df()`). `*_srcrefs()` functions return a
more primitive `list` objects. Because these can be a bit cumbersome to read
through, `*_srcrefs_df()` alternatives are provided for improved introspection
and readability.

> `data.frame` results contain a `srcref` column, where each element is a
> `srcref` object. Even though this appears as a succinct text, it contains all
> the `srcref` data.

### Extracting package namespace object `srcref`s

Getting a `list` of `srcref`s

```{r, include = FALSE}
pkg_srcrefs(examplepkg_ns)[1] # for brevity, only showing first srcref
```

```{r, eval = FALSE}
pkg_srcrefs(examplepkg_ns)["test_description.character"]
```

Viewing results as a `data.frame`

```{r}
head(pkg_srcrefs_df(examplepkg_ns))
```

Extracing individual `srcref`s from the resulting `data.frame`

```{r}
df <- pkg_srcrefs_df(examplepkg_ns)
df$srcref[[1L]]
```

### Extracting test `srcref`s

Similarly, we can extract test `srcrefs` using equivalent functions for tests.
However, to get test traces, we must first run the package coverage, which
records exactly which tests were actually run by the test suite. Starting from
coverage omits any skipped tests or unevaluated test lines, only presenting test
code that is actually run.

Note that the original source files will no longer exist, as `covr` will install
the package into a temporary location for testing. Because of this, test
"srcrefs" are actually `call` objects with a `with_pseudo_srcref`, allowing them
to be treated like a `srcref`s for consistency.

```{r}
examplepkg_test_srcrefs <- test_srcrefs(examplepkg_cov)
```

Despite not having a valid `srcfile`, we can still use all of our favorite
`srcref` functions because of the `with_pseudo_scref` subclass:

```{r, eval = FALSE}
getSrcFilename(examplepkg_test_srcrefs[[1]])
```

```{r, echo = FALSE}
# execute this code instead, which avoids a vignette error on R devel
getSrcFilename(examplepkg_test_srcrefs[1][[1]])
```

And finally, there is a corresponding `*_df` function to make this information
easier to see at a glance:

```{r}
head(examplepkg_test_srcrefs)
```

### Extracting trace `srcref`s

The final piece of the puzzle is the coverage traces. These are the simplest to
find, since `covr` stores this information with every coverage object. Even
without any helper functions, you can find this information by indexing into a
coverage object to explore for yourself.

```{r}
examplepkg_cov[[1]]$srcref
```

Nevertheless, we provide simple alternatives for restructuring this data into
something more consistent with the rest of the pacakge.

```{r}
examplepkg_trace_srcrefs <- trace_srcrefs(examplepkg_cov)
examplepkg_trace_srcrefs[1]
```

And just like the other functions in the family, this too comes with a `*_df`
companion function.

```{r}
head(trace_srcrefs_df(examplepkg_cov))
```

# Summary

With all of this information, we can match related code blocks to one another to
retrospectively evaluate the relationship between package code and tests.
