Skip to content

Development

ry is a Rust workspace of six product crates, described file by file in Structure and as a system in Architecture.

The console embeds R without linking against it at build time. It finds R’s shared library through R_HOME (or by asking R RHOME on the PATH) and loads it at runtime, so the whole workspace builds, and its unit tests run, on a machine with no R at all. Only running the console needs R. On Unix the console hooks in through the ptr_R_ReadConsole globals, and on Windows through the Rstart callback struct. Its end-to-end tests drive the real binary through a pseudo-terminal (a Unix pty or a Windows ConPTY) and skip cleanly where R is missing. Run just repl-e2e locally before you touch the console.

Architecture describes how the analysis works, and Structure maps every source file. The editor extensions live under editors/: code is the VS Code extension, and zed is the Zed extension.

The justfile at the repository root names every day-to-day action, and running just with no arguments lists them. These are the ones you will reach for most:

Terminal window
just gate # the full gate: battery, clippy -D warnings, fmt check
just battery # the workspace test battery, excluding zed_ry
just fixture <group__case> # one focused fixture case
just bless -p semantics --test ... # re-bless fixture expectations
just fuzz-deep # the long-running seeded fuzz pass
just stats <path> # the workspace performance diagnosis
just perf # the corpus-wide perf and memory budgets (needs corpus/)

just fixture runs a case from the test_typing_fixtures target in semantics unless you pass a different target and package as its second and third arguments. Whenever you bless expectations, review the diff before you commit it.

Without just, these are the raw commands:

Terminal window
cargo build # the product crate, the workspace default member
cargo test # the product crate's suites
cargo test --workspace --exclude zed_ry # everything: all six crates
cargo test -p semantics # the analysis core's fixture and fuzz suites
cargo test -p format --test test_format_fixtures

Most behavior is verified by fixture tests: human-readable .test files, each rendered and compared to its expected output. Read Testing for the fixture contract before you add or change one. Two environment variables come up every day:

  • RY_BLESS=1 rewrites the expected #++++ blocks in place from the current output.
  • FIXTURE_FILTER=group__case runs a single fixture case.

Some suites, and every measurement instrument, read a corpus of real-world R code. Fetch it with scripts/fetch-corpus.rs, a single-file script that needs cargo +nightly -Zscript and writes into the gitignored corpus/ directory. The resolved inventory is committed as scripts/corpus-manifest.txt. The performance and memory witness is crates/semantics/tests/test_perf.rs, and Testing documents it.

ry server --debug surfaces internal analysis facts in the editor: every hover gains a “Debug” section showing the Lowering, Naming, and Parsing views of the expression under the cursor. Setting RY_DEBUG=1 in the server’s environment does the same, for setups where changing the server’s arguments is awkward. It is deliberately not a ry.toml key, because the configuration file is a user-facing contract and this switch is a tool for people working on ry itself.

ry debug analysis-stats [path] runs the full analysis pipeline over a workspace, through the same queries the language server uses, and reports where the time and memory go. For each phase (load, parse, lowering and naming, type checking, diagnostics) it prints the wall time and how much the resident set grew. After that come the slowest files by type-checking time, and an incremental probe on representative files that reports keystroke latency, item rechecks and resolve steps per keystroke, and the raw re-parse time as a floor to compare against.

The command forces [check] typing on, since a diagnosis without the type checker measures nothing interesting, and it tells you when your configuration had it off. Build with --release when the absolute numbers matter; a debug build still gives honest ratios.

ry debug ast <file> prints a file’s syntax tree.

Run just docs for a live preview, and cd docs && bun run build for a full build.

The formatter reference at docs/src/content/docs/reference/formatting-rules.md is generated, so do not edit it by hand. Edit crates/format/tests/formatter.template.md instead, and regenerate the page with just format-docs.

  1. Run cd editors/code && bun install to install the npm modules.
  2. Press Ctrl+Shift+B in VS Code to compile the client in watch mode.
  3. Open the Run and Debug view with Ctrl+Shift+D.
  4. Select Launch Client from the drop-down, if it is not already selected.
  5. Press F5 to run the launch configuration.
  6. In the Extension Development Host window that opens, open any .R file.

The hard part of formatting R is comments. A comment can sit between any two tokens: inside an if header, between a call and its argument list, after an operator. A concise “format each field” style drops such a comment without a word, so the formatter walks the concrete children token by token and decides placement at the token level.

The rowan tree adds a twist. A trailing comment attaches inside an expression node, so any decision about where a closing token goes has to look at tokens, never at whole elements. See crates/format/src/format.rs and the formatter fixtures under crates/format/tests/format/.