# Checking and formatting

`ry check` finds the mistakes R would only show you when a line runs, without running anything and
without any configuration. Run it in the project directory:

```text
$ ry check
unresolved

  ! I could not resolve `ratee` in this package, its imports, or builtins. Did you mean `rate`?
   --[R/discount.R:2:11]
 1 | apply_discount <- function(price, rate) {
 2 |   price * ratee
   |           ^^^^^
 3 | }

1 problem in 1 file
```

It reads every `.R` file in the project, plus the R chunks of `.Rmd`, `.qmd`, and `.Rnw` documents.
Your editor shows the same findings as you type ([Editors](/guides/editors.md)). This page covers what
`ry check` finds with no configuration; type errors are opt-in and have
[their own section](/type-checking/overview.md).

## Names

A misspelled name is the most common finding, and R reports it only when execution reaches it. ry
resolves every name by R's scoping rules, so the typo is reported where you wrote it, as above.

Files in a package's `R/` share one namespace, a script is read from top to bottom, and `library()`
calls, `DESCRIPTION` dependencies, and `NAMESPACE` imports bring names into scope. The same analysis
reports an assignment that nothing reads, a top-level name defined twice in a package (R silently
keeps one of them), `pkg::name` where the package does not export `name`, and a `NAMESPACE` that
imports or exports a name that does not exist.

Every file outside `R/` is analyzed on its own, seeing its own definitions plus the package's.
A script also sees the top-level definitions of every file it `source()`s by a literal path, and of
the files those source in turn. The path is tried relative to the sourcing file's directory, then
relative to the project root, where R usually runs. A sourced file reports no unused assignments,
because the script that sources it is what reads them. A computed path such as
`source(file.path(dir, "utils.R"))` is not followed, so its names stay unresolved.

Files directly under `tests/testthat/` see the package's definitions and the `helper-*.R` and
`setup-*.R` files' definitions, but add nothing to the package, so a variable a test assigns never
changes what a name means under `R/`. Each test file's own bindings stay in that file, as they do
when testthat runs it.

A `.Rmd`, `.qmd`, or `.Rnw` document is read as one script made of its R chunks in order, so a chunk
sees what earlier chunks define, and an inline `` `r expr` `` sees everything defined above it.
Chunks in other languages are treated as prose.

One gap matters from the start: after `library()` of a package ry knows nothing about, any bare name
might be one of its exports, so ry stops reporting unresolved names in the whole project.
[Package stubs](/type-checking/unknown.md#package-stubs) shows how to turn the check back on.

Inside data-masking functions such as dplyr's `filter()` and `mutate()`, `data.table`'s `[`, and
base `with()`, `subset()`, and `transform()`, a bare name refers to a column, so this code reports
nothing:

```r
library(dplyr)

by_region <- function(sales) {
  sales |>
    filter(amount > 0) |>
    mutate(net = amount * (1 - fee))
}
```

## Syntax errors

R's parser stops at the first error and names the token it choked on, so for this code it says
`unexpected symbol` and points at `subtitle`. ry names what is missing and keeps analyzing the rest
of the file:

```r
config <- list(
  title = "Revenue"
  subtitle = "by quarter"
)
```

```text
syntax-error

  x missing `,` between these arguments
   --[R/config.R:2:20]
 1 | config <- list(
 2 |   title = "Revenue"
   |                    ^
```

## Lints

Eleven lints are on by default. Three of them catch a habit that causes bugs, and here they are in a
script, with the `unused` finding for each assignment left out:

```r
total = 2L
flag <- T
x <- c(1, 2, )
```

```text
assignment-operator

  ! Use <-, not =, for assignment

boolean-shorthand

  ! Use TRUE, not T, for Boolean values

trailing-comma

  ! Unexpected comma after last argument
```

`T` is an ordinary variable that any code can overwrite. The trailing comma passes an empty
argument, which breaks `c(1, 2, )` when it runs but is harmless to a function that never evaluates
it, so it is a warning. The fourth, `object-system`, warns on each S4, R6 or
Reference Class definition, because [records and tags](/type-checking/records-and-tags.md) model data
with every field checked.

Five more catch code that reads right but does something else: `x == NA` is always `NA`, `&` in
an `if` condition evaluates both sides, `1:length(x)` counts down to zero when `x` is empty,
`class(x) == "data.frame"` misses a tibble, and `length(x == 0)` measures the comparison instead of
comparing the length. Each fix changes what the code does, which is the point,
so it is unsafe and runs only with `--unsafe-fixes`. The last two, `unreachable-code` and
`sprintf-arguments`, flag code after a `return()` and a `sprintf()` format that wants more or fewer
values than it gets. Style lints, such as `redundant-return`, `single-quotes` and `paste-sep`, are off until
you turn them on. [Diagnostic codes](/reference/diagnostic-codes.md) lists every code with its
reason, and [configuration](/reference/configuration.md) shows how to turn each one on or off.

## Fixing findings

A finding with an obvious rewrite says so under its snippet. `ry check --fix` applies the safe ones
to your files, which are the lints above, and then reports what is left:

```text
$ ry check --fix
unresolved

  ! I could not resolve `totl` in this package, its imports, or builtins. Did you mean `total`?
   --[R/rates.R:4:7]
 3 | x <- c(1, 2)
 4 | print(totl)
   |       ^^^^
  help: Change to `total` (`ry check --fix --unsafe-fixes` applies it)

applied 3 fixes
1 problem in 1 file
1 fixable with `ry check --fix --unsafe-fixes`
```

A spelling suggestion is a guess, so it needs `--unsafe-fixes` as well. Your editor offers the same
fixes as quick fixes, and [Fixes](/reference/diagnostic-codes.md#fixes) lists which findings have one.

## Silencing one finding

Every finding carries a code, and naming it in a comment silences it:

```r
y = 3L  # ry: allow(assignment-operator)
```

Type findings work the same way, as in `# ry: allow(type-mismatch)`.

A suppression covers its own line and the line below, so it can sit at the end of the line or above
it. To turn a check off everywhere, set it in [`ry.toml`](/reference/configuration.md).

lintr's comments work too, so code written for lintr keeps its suppressions. `# nolint` silences
every finding on its line except a syntax error, `# nolint start` and `# nolint end` do the same for
the lines between them, and either can name lintr linters, as in `# nolint: seq_linter.`. A named
linter silences the ry code that reports the same thing, such as `length-sequence` for
`seq_linter`, and a linter ry has no counterpart for silences nothing. A `.lintr` file's `linters` field carries
over too, as [configuration](/reference/configuration.md#a-lintr-lintr-file) describes.

## Formatting

`ry fmt` gives every file in the project the same spacing, indentation and braces, so a review shows
what changed instead of whose style it is. It formats files in place:

```r
x<-c(1,2,3)
if(x>1){y<-2}
for (i in 1:3) print(i)
```

```text
$ ry fmt
1 file reformatted, 0 files left unchanged
```

```r
x <- c(1, 2, 3)
if (x > 1) { y <- 2 }
for (i in 1:3) {
  print(i)
}
```

Every loop body, and every `if` or function body that spans lines, gets braces, so a line added
later cannot fall outside the block it belongs to.

`ry fmt` never reflows code to a column limit, because a formatter that does rewrites lines you did
not touch and buries your change in layout churn. Your line breaks decide the layout instead: a call
written on one line stays on one line, and a call you broke across lines gets one argument per line:

```r
report <- summarise(sales, total = sum(amount), n = n())
report <- summarise(sales,
  total = sum(amount), n = n())
```

```r
report <- summarise(sales, total = sum(amount), n = n())
report <- summarise(
  sales,
  total = sum(amount),
  n = n()
)
```

`ry fmt --range 2-3 R/report.R` formats only those lines (a line `3`, or a span with columns
`2:1-3:10`, works too). A statement is the smallest unit, so a range that reaches into a multi-line
call reformats the whole call. Editors use the same operation for "Format Selection" and for
format-on-type. The only settings are indent width and line endings, in
[`ry.toml`](/reference/configuration.md), and [Formatting rules](/reference/formatting-rules.md) has every
rule with examples.

## In CI

`ry check` exits with status 1 when it finds anything, and `ry fmt --check` does when a file is not
formatted, listing each one:

```text
$ ry fmt --check
Would reformat: ./R/report.R
1 file would be reformatted, 0 files already formatted
```

So a CI job needs only those two commands. Pin the ry version, because a newer release can report
findings an older one did not. `ry check --output json` gives machine-readable output, as the
[command reference](/reference/cli.md#json-output) describes.
