treefmt icon indicating copy to clipboard operation
treefmt copied to clipboard

Prettier through `--stdin` does no formatting

Open losnappas opened this issue 5 months ago • 6 comments

Describe the bug

I run e.g. treefmt --stdin test.js < test.js, but no changes. Neither for json files. But file does change if I run treefmt test.js.

To Reproduce

Steps to reproduce the behavior:

I am using treefmt-nix, with this:

treefmt = {
  programs = {
    prettier.enable = true;
  };
};
const x = () => {
    console.log( "zxc " );
    };
  1. nix fmt -- --stdin test.js < test.js
  2. Outputs the same content as went in, no changes.
  3. For reference, nix fmt -- --stdin flake.nix < flake.nix does output changes (when needed)
  4. nix fmt results in formatted js file

Expected behavior

It should format the JS through stdin via prettier.

System information

nixos-unstable 01f116e4df6a15f4ccdffb1bcd41096869fb385c treefmt v2.4.0

Additional context

losnappas avatar Oct 24 '25 06:10 losnappas

I just did a quick test using treefmt directly using your sample js and the following config:

[formatter.prettier]
command = "prettier"
options = ["--write"]
includes = [
    "*.js"
]

Here's what I'm seeing:

❯ nix run github:numtide/treefmt/v2.4.0 -- --stdin test.js < test.js 
const x = () => {
  console.log("zxc ");
};
traversed 1 files
emitted 1 files for processing
formatted 1 files (1 changed) in 173ms

Can you provide a standalone reproducer, or is your repo public?

brianmcgee avatar Oct 24 '25 09:10 brianmcgee

https://github.com/losnappas/treefmt-repro

17:08:36{direnv}/tmp/treefmt-test $ nix fmt -- --stdin test.js < test.js
const x = () => {
    console.log( "zxc " );
    };traversed 1 files
emitted 1 files for processing
formatted 1 files (0 changed) in 94ms
17:08:40{direnv}/tmp/treefmt-test $ nix run github:numtide/treefmt/v2.4.0 -- --stdin test.js < test.js 
warning: ignoring the client-specified setting 'trusted-public-keys', because it is a restricted setting and you are not a trusted user
const x = () => {
    console.log( "zxc " );
    };traversed 1 files
emitted 1 files for processing
formatted 1 files (0 changed) in 96ms
$ nix fmt
traversed 6 files
emitted 1 files for processing
formatted 1 files (1 changed) in 147ms
17:09:59{direnv}/tmp/treefmt-test $ cat test.js 
const x = () => {
  console.log("zxc ");
};


$ # and again after reverting file
 $ nix run github:numtide/treefmt/v2.4.0 -- .
warning: ignoring the client-specified setting 'trusted-public-keys', because it is a restricted setting and you are not a trusted user
traversed 6 files
emitted 1 files for processing
formatted 1 files (1 changed) in 149ms
17:10:49{direnv}/tmp/treefmt-test $ cat test.js 
const x = () => {
  console.log("zxc ");
};


17:12:00{direnv}/tmp/treefmt-test $ prettier --stdin-filepath test.js < test.js
const x = () => {
  console.log("zxc ");
};

losnappas avatar Oct 27 '25 15:10 losnappas

@losnappas, thanks for the reproducer. I just checked it out and was able to replicate what you are seeing.

This gives me something to investigate.

brianmcgee avatar Oct 27 '25 19:10 brianmcgee

I've done some quick testing, and here's what I've found so far.

I ejected the wrapped treefmt being used via flake-parts with nix build .# and:

perSystem = { config, self', inputs', pkgs, system, ... }: {
    ....
    packages.default = config.treefmt.build.wrapper;
}

It looks like this:

#!/nix/store/ciarnmsx8lvsrmdbjddpmx0pqjrm8imb-bash-5.3p3/bin/bash
set -euo pipefail
unset PRJ_ROOT
exec /nix/store/xq1681mm2pyi0vidfxd4z0jfdm6psw3h-treefmt-2.4.0/bin/treefmt \
  --config-file=/nix/store/13nidh2ij1am61s8n2nwbi864zf4sbz2-treefmt.toml \
  --tree-root-file=flake.nix \
  "$@"

If I run the command as is from the root of the repo it doesn't work:

 /nix/store/xq1681mm2pyi0vidfxd4z0jfdm6psw3h-treefmt-2.4.0/bin/treefmt \
  --config-file=/nix/store/13nidh2ij1am61s8n2nwbi864zf4sbz2-treefmt.toml \
  --tree-root-file=flake.nix \ 
  --stdin test.js < test.js 
const x = () => {
    console.log( "zxc " );
    };
traversed 1 files
emitted 1 files for processing
formatted 1 files (0 changed) in 167ms

If I omit the --tree-root-file argument it works:

 /nix/store/xq1681mm2pyi0vidfxd4z0jfdm6psw3h-treefmt-2.4.0/bin/treefmt \
  --config-file=/nix/store/13nidh2ij1am61s8n2nwbi864zf4sbz2-treefmt.toml \ 
  --stdin test.js < test.js 
const x = () => {
  console.log("zxc ");
};
traversed 1 files
emitted 1 files for processing
formatted 1 files (1 changed) in 201ms

I now know where to look for this bug. I'll try to find some time in the next few days to dig into it.

brianmcgee avatar Oct 27 '25 19:10 brianmcgee

I found the issue.

https://github.com/numtide/treefmt/pull/624/commits/b68d3286440c818d2452a65d3c503a7245447269 introduced a change to how we create temp files for processing stdin. Previously, we had been making a temporary file local to the file being processed (the path passed in before < for matching purposes), a descendant of the root of the tree.

Now, we have a virtual path for matching, but we generate the temp file in /tmp.

Turns out, after some experimentation, prettier, for good reason, doesn't process files that are not descendants of the directory it's being invoked from. This behaviour likely differs on a per-formatter basis.

I need to consider how best to resolve this issue permanently. Feels like I keep flip-flopping with stdin processing. 🤔

@jfly @zimbatm I welcome your input.

brianmcgee avatar Nov 05 '25 16:11 brianmcgee

Turns out, after some experimentation, prettier, for good reason, doesn't process files that are not descendants of the directory it's being invoked from.

This reminds me of the issue that originally brought me to treefmt. We decided to add this to the spec:

It MUST process the specified files. For example, it MUST NOT ignore files because they are not tracked by a VCS.

(With my pedantic hat on) I'd say that prettier is violating the formatter specification.

This behaviour likely differs on a per-formatter basis

I agree. I don't think there's any behavior that's perfect. Perhaps the old behavior (tempfiles alongside the original files) is more likely to play nicely with existing formatters. IMO, the only correct solution is for us to implement the stdin spec.

jfly avatar Nov 07 '25 16:11 jfly