Skip to content

Roadmap Phase 3-8: unify config resolution and cache per-call waste - #121

Merged
sorafujitani merged 4 commits into
mainfrom
feat/config-unification
Jul 18, 2026
Merged

Roadmap Phase 3-8: unify config resolution and cache per-call waste#121
sorafujitani merged 4 commits into
mainfrom
feat/config-unification

Conversation

@sorafujitani

Copy link
Copy Markdown
Owner

Phase 8 of the ruby-prism migration plan (#110 Phase 3): one config system, and the per-call waste that the fork model used to hide is gone.

What changes for users

--config PATH actually works now. It was silently ignored by the formatter: the Ruby side only used it for file selection while Rust independently re-discovered config on every call. Now:

  • rfmt --config custom.yml --write file.rb applies the file's formatting settings (verified end-to-end with a distinctive indent_width)
  • an explicitly passed config that is missing or invalid fails loudly with exit 1, once — before, a Thor bug (exit_on_failure? undefined) meant even error paths exited 0
  • rfmt config now prints the effective settings the Rust formatter actually resolved (via a new resolved_config_yaml FFI), instead of a Ruby-side approximation
  • discovery (.rfmt.yml etc.) is unchanged for well-formed files; a broken discovered file now warns and falls back to defaults instead of failing every format (deliberate: an LSP mid-edit of .rfmt.yml should not make formatting impossible). Explicit-path errors stay loud — the asymmetry is documented and tested

Long-lived processes pick up config edits: discovery results are cached keyed by cwd + found-file mtime; editing .rfmt.yml between two in-process formats changes the output (tested). Known limit (in code): a config newly created in a parent directory is picked up on the next cache invalidation, not instantly.

Performance

Config::discover() was a cwd→root→home filesystem walk on every Rfmt.format call, and Formatter::new rebuilt the 23-rule registry each time. With the discovery cache (walk → one stat batch), an explicit-path cache, and a OnceLock registry:

ms/file (in-process, lib corpus)
before this PR 0.357
after 0.190

Cumulative since the migration started: 4.28 → 0.19 ms/file (~22x).

Design notes

  • FFI: two fixed-arity functions (format_code/1, format_code_with_config/2) instead of magnus variadic parsing; config crosses the boundary as a PATH so YAML parsing stays in one place
  • Rfmt.format(source, config_path: nil) keeps the positional call compatible
  • Configuration (Ruby) shrinks to file-selection/cache concerns; the three discovery implementations now share one search order (full dedup left out of scope, noted in the plan)

Verification

  • rspec 166/0 (new: explicit-path honored / missing / invalid, mtime invalidation, CLI e2e); corpus check 46/46
  • cargo test 145 (new cache tests: mtime reload, file-vanished re-walk, not-found caching with cwd-creation detection, explicit-path error semantics); clippy/fmt/rubocop clean
  • Real CLI transcript in the PR discussion above; re-verified independently (indent honored, exit 1 on missing config)
  • Benchmark: three runs, 0.190–0.207 ms/file

- cache explicit --config loads by canonical path + mtime
- rfmt config prints the effective Rust-resolved configuration
- fail fast once on a broken --config instead of per-file errors
- align Rfmt::Config.find search order with the Rust side
- ConfigError follows the message-only Display convention
@sorafujitani
sorafujitani merged commit 97c3332 into main Jul 18, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant