Skip to content
Merged
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
1 change: 1 addition & 0 deletions .claude/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
settings.local.json
117 changes: 39 additions & 78 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,99 +1,60 @@
# CLAUDE.md
## R package development

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
### Key commands

## About This Project
```
# To run code
Rscript -e "devtools::load_all(); code"

testthat is R's most popular unit testing framework, used by thousands of CRAN packages. It provides functions to make testing R code as fun and addictive as possible, with clear expectations, visual progress indicators, and seamless integration with R package development workflows.
# To run all tests
Rscript -e "devtools::test()"

## Key development commands
# To run tests for R/{name.R}
Rscript -e "devtools::test(filter = '{name}')"

# To document the package
Rscript -e "devtools::document()"

# To check pkgdown documentation
Rscript -e "pkgdown::check_pkgdown()"

# To format code
air format .
```

### Coding

General advice:
* When running R from the console, always run it with `--quiet --vanilla`
* Always run `air format .` after generating code
* Use the base pipe operator (`|>`) not the magrittr pipe (`%>%`)
* Don't use `_$x` or `_$[["x"]]` since dbplyr must work on R 4.1.
* Use `\() ...` for single-line anonymous functions. For all other cases, use `function() {...}`

### Testing

- Use `devtools::test()` to run all tests
- Use `devtools::test_file("tests/testthat/test-filename.R")` to run tests in a specific file
- DO NOT USE `devtools::test_active_file()`
- All testing functions automatically load code; you don't needs to.

- All new code should have an accompanying test.
- Tests for `R/{name}.R` go in `tests/testthat/test-{name}.R`.
- All new code should have an accompanying test.
- If there are existing tests, place new tests next to similar existing tests.
- Strive to keep your tests minimal with few comments.

### Documentation

- Run `devtools::document()` after changing any roxygen2 docs.
- Every user facing function should be exported and have roxygen2 documentation.
- Whenever you add a new documentation file, make sure to also add the topic name to `_pkgdown.yml`.
- Run `pkgdown::check_pkgdown()` to check that all topics are included in the reference index.
- Use sentence case for all headings

## Core Architecture

### Main Components

1. **Core Testing Functions** (`R/test-that.R`, `R/test-package.R`):
- `test_that()` - The fundamental testing function
- `test_local()`, `test_package()`, `test_check()` - Different ways to run test suites

2. **Expectations** (`R/expect-*.R`):
- Modular expectation functions (equality, conditions, types, etc.)
- Each expectation type has its own file following the pattern `expect-[type].R`

3. **Reporters** (`R/reporter*.R`):
- Different output formats for test results
- Object-oriented design with base `Reporter` class
- Includes check, debug, progress, summary, JUnit, TAP formats

4. **Snapshot Testing** (`R/snapshot*.R`):
- Value snapshots, file snapshots, output snapshots
- Automatic management and comparison of expected outputs

5. **Parallel Testing** (`R/parallel*.R`):
- Multi-core test execution
- Configuration via `Config/testthat/parallel: true` in DESCRIPTION

6. **Mocking** (`R/mock*.R`, `R/mock2*.R`):
- Function mocking capabilities
- Both legacy (`mock.R`) and modern (`mock2*.R`) implementations

### Key Design Patterns

- **Editions**: testthat has different "editions" with varying behavior, controlled by `Config/testthat/edition`
- **Reporters**: Extensible reporting system using R6 classes
- **Lazy Evaluation**: Expectations use substitute() and lazy evaluation for better error messages
- **C++ Integration**: Core functionality implemented in C++ for performance

### File Organization

- `R/` - All R source code, organized by functionality
- `src/` - C++ source code and Makevars
- `inst/include/testthat/` - C++ headers for other packages to use
- `tests/testthat/` - Package's own comprehensive test suite
- `vignettes/` - Documentation on testing concepts and workflows
- Every user-facing function should be exported and have roxygen2 documentation.
- Wrap roxygen comments at 80 characters.
- Internal functions should not have roxygen documentation.
- Whenever you add a new documentation topic, also add the topic to `_pkgdown.yml`.
- Use `pkgdown::check_pkgdown()` to check that all topics are included in the reference index.

### Important Configuration
### Writing

The package uses several DESCRIPTION fields for configuration:
- `Config/testthat/edition: 3` - Sets testthat edition
- `Config/testthat/parallel: true` - Enables parallel testing
- `Config/testthat/start-first` - Tests to run first in parallel mode
- Use sentence case for headings.
- Use US English.

### C++ Testing Infrastructure
### Proofreading

testthat provides C++ testing capabilities via Catch framework:
- Headers in `inst/include/testthat/`
- Test runner infrastructure in `src/test-runner.cpp`
- Integration with R's testing system
If the user asks you to proofread a file, act as an expert proofreader and editor with a deep understanding of clear, engaging, and well-structured writing.

### Snapshot Testing Workflow
Work paragraph by paragraph, always starting by making a TODO list that includes individual items for each top-level heading.

- Snapshots stored in `tests/testthat/_snaps/`
- Different snapshot types: values, files, output
- Version-specific snapshots for different R versions
- Use `testthat::snapshot_accept()` to update snapshots
Fix spelling, grammar, and other minor problems without asking the user. Label any unclear, confusing, or ambiguous sentences with a FIXME comment.

This codebase prioritizes backward compatibility, comprehensive testing, and clear, descriptive error messages to help R developers write better tests.
Only report what you have changed.
16 changes: 12 additions & 4 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",

"permissions": {
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"defaultMode": "acceptEdits",
"allow": [
"Bash(find:*)",
"Bash(grep:*)",
"Bash(ls:*)",
"Bash(R:*)",
"Bash(Rscript:*)",
"Bash(rm:*)",
"Bash(air format:*)",
"Edit(**)",
"Bash(air:*)",
"WebFetch(domain:github.com)"
],
"deny": []
"deny": [
"Read(.Renviron)",
"Read(.env)"
]
}
}
156 changes: 156 additions & 0 deletions .claude/skills/tidy-deprecate-function/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
---
name: tidy-deprecate-function
description: Guide for deprecating R functions/arguments. Use when a user asks to deprecate a function or parameter, including adding lifecycle warnings, updating documentation, adding NEWS entries, and updating tests.
---

# Deprecate functions and function arguments

Use this skill when deprecating functions or function parameters in dbplyr.

## Overview

This skill guides you through the complete process of deprecating a function or parameter, ensuring all necessary changes are made consistently:

1. Add deprecation warning using `lifecycle::deprecate_warn()`.
2. Silence deprecation warnings in existing tests.
3. Add lifecycle badge to documentation.
4. Add bullet point to NEWS.md.
5. Create test for deprecation warning.

## Workflow

### Step 1: Determine deprecation version

Read the current version from DESCRIPTION and calculate the deprecation version:

- Current version format: `MAJOR.MINOR.PATCH.9000` (development).
- Deprecation version: Next minor release `MAJOR.(MINOR+1).0`.
- Example: If current version is `2.5.1.9000`, deprecation version is `2.6.0`.

### Step 2: Add `lifecycle::deprecate_warn()` call

Add the deprecation warning to the function:

```r
# For a deprecated function:
function_name <- function(...) {
lifecycle::deprecate_warn("X.Y.0", "function_name()", "replacement_function()")
# rest of function
}

# For a deprecated parameter:
function_name <- function(param1, deprecated_param = deprecated()) {
if (lifecycle::is_present(deprecated_param)) {
lifecycle::deprecate_warn("X.Y.0", "function_name(deprecated_param)")
}
# rest of function
}
```

Key points:

- First argument is the deprecation version string (e.g., "2.6.0").
- Second argument describes what is deprecated (e.g., "function_name(param)").
- Optional third argument suggests replacement.
- Use `lifecycle::is_present()` to check if a deprecated parameter was supplied.

### Step 3: Update tests

Find all existing tests that use the deprecated function or parameter and silence lifecycle warnings. Add at the beginning of test blocks that use the deprecated feature:

```r
test_that("existing test with deprecated feature", {
withr::local_options(lifecycle_verbosity = "quiet")

# existing test code
})
```

Then add a new test to verify the deprecation message in the appropriate test file (usually `tests/testthat/test-{name}.R`):

```r
test_that("function_name(deprecated_param) is deprecated", {
expect_snapshot(. <- function_name(deprecated_param = value))
})
```

You'll need to supply any additional arguments to create a valid call.

Then run the tests and verify they pass.

### Step 4: Update documentation

For function deprecation, add to the description section:

```r
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' This function is deprecated. Please use [replacement_function()] instead.
```

If the documentation does not already contain `@description`, you will need to add it.

For argument deprecation, add to the appropriate `@param` tag:

```r
#' @param deprecated_param `r lifecycle::badge("deprecated")`
```

When deprecating a function or parameter in favor of a replacement, add old/new examples to the `@examples` section to help users migrate. These should relace all existing examples.

```r
#' @examples
#' # Old:
#' old_function(arg1, arg2)
#' # New:
#' replacement_function(arg1, arg2)
#'
#' # Old:
#' x <- "value"
#' old_function("prefix", x, "suffix")
#' # New:
#' replacement_function("prefix {x} suffix")
```

Key points:

- Use "# Old:" and "# New:" comments to clearly show the transition.
- Include 2-3 practical examples covering common use cases.
- Make examples runnable and self-contained.
- Show how the new syntax differs from the old.

Then re-document the package.

### Step 5: Add NEWS entry

Add a bullet point to the top of the "# dbplyr (development version)" section in NEWS.md:

```markdown
# dbplyr (development version)

* `function_name(parameter)` is deprecated and will be removed in a future
version.
* `function_name()` is deprecated. Use `replacement_function()` instead.
```

Place the entry:

- In the lifecycle subsection if it exists, otherwise at the top level under development version.
- Include the replacement if known.
- Keep entries concise and actionable.

## Implementation checklist

When deprecating a function or parameter, ensure you:

- [ ] Read DESCRIPTION to determine deprecation version.
- [ ] Add `lifecycle::deprecate_warn()` call in the function.
- [ ] Add `withr::local_options(lifecycle_verbosity = "quiet")` to existing tests.
- [ ] Create new test for deprecation warning using `expect_snapshot()`.
- [ ] Run tests to verify everything works.
- [ ] Add lifecycle badge to roxygen documentation.
- [ ] Add migration examples to `@examples` section (for function deprecation).
- [ ] Run `devtools::document()` to update documentation.
- [ ] Add bullet point to NEWS.md.
- [ ] Run `air format .` to format code.
2 changes: 1 addition & 1 deletion .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
actions: read

# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1)
# model: "claude-opus-4-1-20250805"
model: "claude-opus-4-5-20251101"

# Optional: Customize the trigger phrase (default: @claude)
# trigger_phrase: "/claude"
Expand Down
Loading