From f0e2e86ae2cde73bcf82fbd62396c6849365273e Mon Sep 17 00:00:00 2001 From: Isaac Corbrey Date: Sat, 13 Jun 2026 09:34:41 -0500 Subject: [PATCH] jujutsu: Fix rustfmt fix tool --- modules/home/jujutsu/default.nix | 17 ++++- modules/home/jujutsu/rustfmt-wrapper.sh | 86 +++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100755 modules/home/jujutsu/rustfmt-wrapper.sh diff --git a/modules/home/jujutsu/default.nix b/modules/home/jujutsu/default.nix index 69c4966..edb043f 100644 --- a/modules/home/jujutsu/default.nix +++ b/modules/home/jujutsu/default.nix @@ -177,9 +177,24 @@ ]; } (lib.mkIf config.modules.home.rust.enable { + # `jj fix` pipes content via stdin, leaving rustfmt unable to walk + # upward to find Cargo.toml and read the workspace's edition. The + # wrapper takes `$root`/`$path` from jj's substitution, resolves + # the file's enclosing crate (handling workspace inheritance via + # `[workspace.package].edition`), and invokes rustfmt with + # `--edition` set correctly per file. + home.file.".config/jj/scripts/rustfmt-wrapper.sh" = { + source = ./rustfmt-wrapper.sh; + executable = true; + }; + programs.jujutsu.settings = { fix.tools.rustfmt = { - command = ["rustfmt" "--emit" "stdout"]; + command = [ + "${config.home.homeDirectory}/.config/jj/scripts/rustfmt-wrapper.sh" + "$root" + "$path" + ]; patterns = ["glob:'**/*.rs'"]; }; }; diff --git a/modules/home/jujutsu/rustfmt-wrapper.sh b/modules/home/jujutsu/rustfmt-wrapper.sh new file mode 100755 index 0000000..02c13a2 --- /dev/null +++ b/modules/home/jujutsu/rustfmt-wrapper.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# rustfmt-wrapper: invoke rustfmt with the correct Rust edition for the +# file being formatted. +# +# `jj fix` pipes file content via stdin; without a file path, rustfmt +# can't walk upward to find Cargo.toml on its own, so it defaults to +# Rust 2015 and rejects every `async fn` in a modern crate. This +# wrapper takes the workspace root and repo-relative path as arguments +# (jj's `$root` and `$path` substitutions), walks upward from the +# resolved file to find the nearest Cargo.toml with an edition +# declaration, and invokes rustfmt with `--edition` explicit. +# +# Falls back to edition 2021 if no Cargo.toml is found or the edition +# can't be parsed — 2021 is the most common modern edition and is a +# strict subset of 2024, so misclassification just means slightly +# older formatting rules, never a syntax-rejection. +# +# Workspace inheritance is handled: a member crate that uses +# `edition.workspace = true` causes the walk to continue upward +# until the workspace root's `[workspace.package].edition` is found. + +set -euo pipefail + +workspace_root="${1:-}" +repo_path="${2:-}" +fallback_edition="2021" + +# Extract a TOML string value for `key` inside [`section`] of `manifest`. +# Looks for `key = "value"` on its own line within the named section. +# Doesn't handle inline tables or multiline strings — neither of which +# Cargo's edition field uses in practice. +read_toml_string() { + local manifest="$1" + local section="$2" + local key="$3" + awk -v want_section="[${section}]" -v key="${key}" ' + /^\[/ { current = $0; next } + current == want_section { + if (match($0, "^[[:space:]]*" key "[[:space:]]*=[[:space:]]*\"")) { + sub(/^[^"]*"/, "") + sub(/".*$/, "") + print + exit + } + } + ' "$manifest" +} + +find_edition() { + local start_dir="$1" + local dir="$start_dir" + + while [[ -n "$dir" && "$dir" != "/" ]]; do + local cargo="${dir}/Cargo.toml" + if [[ -f "$cargo" ]]; then + # [package].edition: the common per-crate case. + local found + found="$(read_toml_string "$cargo" "package" "edition")" + if [[ -n "$found" ]]; then + printf '%s\n' "$found" + return 0 + fi + # [workspace.package].edition: the inherited-from-workspace case. + found="$(read_toml_string "$cargo" "workspace.package" "edition")" + if [[ -n "$found" ]]; then + printf '%s\n' "$found" + return 0 + fi + fi + dir="$(dirname "$dir")" + done + + return 1 +} + +edition="$fallback_edition" + +if [[ -n "$workspace_root" && -n "$repo_path" ]]; then + abs_path="${workspace_root}/${repo_path}" + start_dir="$(dirname "$abs_path")" + if detected="$(find_edition "$start_dir" 2>/dev/null)"; then + edition="$detected" + fi +fi + +exec rustfmt --emit stdout --edition "$edition" -- 2.51.2