diff --git a/.changeset/blue-parts-lie.md b/.changeset/blue-parts-lie.md
new file mode 100644
index 000000000000..2c0168a2cc13
--- /dev/null
+++ b/.changeset/blue-parts-lie.md
@@ -0,0 +1,25 @@
+---
+"@biomejs/biome": patch
+---
+
+Fixed [#8233](https://github.com/biomejs/biome/issues/8233), where Biome CLI in
+stdin mode didn't work correctly when handling files in projects with nested
+configurations. For example, with the following structure,
+`--stdin-file-path=subdirectory/...` would not use the nested configuration in
+`subdirectory/biome.json`:
+
+```
+├── biome.json
+└── subdirectory
+ ├── biome.json
+ └── lib.js
+```
+
+```shell
+biome format --write --stdin-file-path=subdirectory/lib.js < subdirectory/lib.js
+```
+
+Now, the nested configuration is correctly picked up and applied.
+
+In addition, Biome now shows a warning if `--stdin-file-path` is provided but
+that path is ignored and therefore not formatted or fixed.
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index e75e0aa01a98..cfb44be0d8e1 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -101,9 +101,7 @@ jobs:
- name: Build Biome debug binary
run: cargo build --bin biome
- name: Run tests
- run: |
- cd e2e-tests
- sh test-all.sh
+ run: ./e2e-tests/test-all.sh
documentation:
name: Documentation
diff --git a/biome.json b/biome.json
index c52df58e969f..6eaae321f1e0 100644
--- a/biome.json
+++ b/biome.json
@@ -33,6 +33,7 @@
"!**/undefined",
"!**/benchmark/target",
"!**/benches",
+ "!!**/e2e-tests",
"!!**/target",
"!!.cargo"
]
diff --git a/crates/biome_cli/src/execute/mod.rs b/crates/biome_cli/src/execute/mod.rs
index 16c3c1ca0516..6a3e63a19812 100644
--- a/crates/biome_cli/src/execute/mod.rs
+++ b/crates/biome_cli/src/execute/mod.rs
@@ -562,7 +562,15 @@ pub fn execute_mode(
// don't do any traversal if there's some content coming from stdin
if let Some(stdin) = execution.as_stdin_file() {
- let biome_path = BiomePath::new(stdin.as_path());
+ // Biome path must starts_with(the project directory), which will have been
+ // joined to workdir. Otherwise we won't find nested settings.
+ let working_dir = session
+ .app
+ .workspace
+ .fs()
+ .working_directory()
+ .unwrap_or_default();
+ let biome_path = BiomePath::new(working_dir.join(stdin.as_path()));
return std_in::run(
session,
project_key,
diff --git a/crates/biome_cli/src/execute/std_in.rs b/crates/biome_cli/src/execute/std_in.rs
index 6972f7750eee..05baa9201371 100644
--- a/crates/biome_cli/src/execute/std_in.rs
+++ b/crates/biome_cli/src/execute/std_in.rs
@@ -49,6 +49,10 @@ pub(crate) fn run<'a>(
if file_features.is_ignored() {
console.append(markup! {{content}});
+ // Write error last because files may generally be long
+ console.error(markup! {
+ "The content was not formatted because the path `"{biome_path.as_str()}"` is ignored."
+ });
return Ok(());
}
@@ -99,6 +103,7 @@ pub(crate) fn run<'a>(
console.append(markup! {
{content}
});
+ // Write error last because files may generally be long
console.error(markup! {
"The content was not formatted because the formatter is currently disabled."
});
@@ -130,6 +135,10 @@ pub(crate) fn run<'a>(
if file_features.is_ignored() {
console.append(markup! {{content}});
+ // Write error last because files may generally be long
+ console.error(markup! {
+ "The content was not fixed because the path `"{biome_path.as_str()}"` is ignored."
+ });
return Ok(());
}
diff --git a/crates/biome_cli/tests/snapshots/main_commands_check/check_stdin_ignores_unknown_file_path.snap b/crates/biome_cli/tests/snapshots/main_commands_check/check_stdin_ignores_unknown_file_path.snap
index 88585ac947b1..c9f8a1645258 100644
--- a/crates/biome_cli/tests/snapshots/main_commands_check/check_stdin_ignores_unknown_file_path.snap
+++ b/crates/biome_cli/tests/snapshots/main_commands_check/check_stdin_ignores_unknown_file_path.snap
@@ -13,3 +13,7 @@ function f() {var x=1; return{x}} class Foo {}
```block
function f() {var x=1; return{x}} class Foo {}
```
+
+```block
+The content was not fixed because the path `mock.cc` is ignored.
+```
diff --git a/crates/biome_cli/tests/snapshots/main_commands_format/format_stdin_does_not_error_with_ignore_unknown_file_extensions.snap b/crates/biome_cli/tests/snapshots/main_commands_format/format_stdin_does_not_error_with_ignore_unknown_file_extensions.snap
index f61d4a0f1fb3..e33a80856272 100644
--- a/crates/biome_cli/tests/snapshots/main_commands_format/format_stdin_does_not_error_with_ignore_unknown_file_extensions.snap
+++ b/crates/biome_cli/tests/snapshots/main_commands_format/format_stdin_does_not_error_with_ignore_unknown_file_extensions.snap
@@ -13,3 +13,7 @@ function f() {return{}}
```block
function f() {return{}}
```
+
+```block
+The content was not formatted because the path `mock.cc` is ignored.
+```
diff --git a/e2e-tests/relative-path-ignore-file/biome.json b/e2e-tests/relative-path-ignore-file/biome.json
index e84571ba3c6b..1aea52f104c2 100644
--- a/e2e-tests/relative-path-ignore-file/biome.json
+++ b/e2e-tests/relative-path-ignore-file/biome.json
@@ -1,5 +1,5 @@
{
- "root": false,
+ "root": true,
"files": {
"includes": ["**", "!file.js"]
},
diff --git a/e2e-tests/relative-path/biome.json b/e2e-tests/relative-path/biome.json
index a972cb86c08f..5a881e23adc9 100644
--- a/e2e-tests/relative-path/biome.json
+++ b/e2e-tests/relative-path/biome.json
@@ -1,5 +1,5 @@
{
- "root": false,
+ "root": true,
"files": {
"includes": ["**"]
},
diff --git a/e2e-tests/stdin-nested-config/app.js b/e2e-tests/stdin-nested-config/app.js
new file mode 100644
index 000000000000..c6184bf69097
--- /dev/null
+++ b/e2e-tests/stdin-nested-config/app.js
@@ -0,0 +1,5 @@
+let x = 5;
+
+function indent() {
+ return x;
+}
diff --git a/e2e-tests/stdin-nested-config/app.js.formatted b/e2e-tests/stdin-nested-config/app.js.formatted
new file mode 100644
index 000000000000..99f9621d99dc
--- /dev/null
+++ b/e2e-tests/stdin-nested-config/app.js.formatted
@@ -0,0 +1,5 @@
+let x = 5;
+
+function indent() {
+ return x;
+}
diff --git a/e2e-tests/stdin-nested-config/biome.jsonc b/e2e-tests/stdin-nested-config/biome.jsonc
new file mode 100644
index 000000000000..42d507a3a185
--- /dev/null
+++ b/e2e-tests/stdin-nested-config/biome.jsonc
@@ -0,0 +1,16 @@
+{
+ "root": true,
+ "vcs": {
+ "enabled": false
+ },
+ "files": {
+ "ignoreUnknown": false,
+ // Include .js recursively, but not .ts
+ "includes": ["**/biome.jsonc", "**/*.js"]
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "space",
+ "indentWidth": 4
+ }
+}
diff --git a/e2e-tests/stdin-nested-config/donotformat.ts b/e2e-tests/stdin-nested-config/donotformat.ts
new file mode 100644
index 000000000000..c5b0f8cd8a06
--- /dev/null
+++ b/e2e-tests/stdin-nested-config/donotformat.ts
@@ -0,0 +1 @@
+let x = 10;
diff --git a/e2e-tests/stdin-nested-config/subdirectory/biome.jsonc b/e2e-tests/stdin-nested-config/subdirectory/biome.jsonc
new file mode 100644
index 000000000000..c553bc42e339
--- /dev/null
+++ b/e2e-tests/stdin-nested-config/subdirectory/biome.jsonc
@@ -0,0 +1,12 @@
+{
+ "root": false,
+ "extends": "//",
+ "files": {
+ // This should be relative to the location of this config file, so we should not touch root/donotformat.ts
+ "includes": ["*.ts"]
+ },
+ "formatter": {
+ "indentStyle": "space",
+ "indentWidth": 2
+ }
+}
diff --git a/e2e-tests/stdin-nested-config/subdirectory/lib.js b/e2e-tests/stdin-nested-config/subdirectory/lib.js
new file mode 100644
index 000000000000..44fd558cea34
--- /dev/null
+++ b/e2e-tests/stdin-nested-config/subdirectory/lib.js
@@ -0,0 +1,5 @@
+let y = 65;
+
+function indent() {
+ return y;
+}
diff --git a/e2e-tests/stdin-nested-config/subdirectory/lib.js.formatted b/e2e-tests/stdin-nested-config/subdirectory/lib.js.formatted
new file mode 100644
index 000000000000..97fb2c8ca73b
--- /dev/null
+++ b/e2e-tests/stdin-nested-config/subdirectory/lib.js.formatted
@@ -0,0 +1,5 @@
+let y = 65;
+
+function indent() {
+ return y;
+}
diff --git a/e2e-tests/stdin-nested-config/subdirectory/typed.ts b/e2e-tests/stdin-nested-config/subdirectory/typed.ts
new file mode 100644
index 000000000000..ee2ce6d1b806
--- /dev/null
+++ b/e2e-tests/stdin-nested-config/subdirectory/typed.ts
@@ -0,0 +1,5 @@
+let y = 65;
+
+function indent() {
+ return y;
+}
diff --git a/e2e-tests/stdin-nested-config/subdirectory/typed.ts.formatted b/e2e-tests/stdin-nested-config/subdirectory/typed.ts.formatted
new file mode 100644
index 000000000000..97fb2c8ca73b
--- /dev/null
+++ b/e2e-tests/stdin-nested-config/subdirectory/typed.ts.formatted
@@ -0,0 +1,5 @@
+let y = 65;
+
+function indent() {
+ return y;
+}
diff --git a/e2e-tests/stdin-nested-config/test.sh b/e2e-tests/stdin-nested-config/test.sh
new file mode 100755
index 000000000000..d6cb1acd4693
--- /dev/null
+++ b/e2e-tests/stdin-nested-config/test.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+TEMP=$(mktemp)
+trap 'rm -f $TEMP' EXIT
+
+biome() {
+ cargo run --bin biome -- "$@"
+}
+
+STATUS=0
+diff_stdin_filepath() {
+ biome format --stdin-file-path="$1" < "$1" > "$TEMP"
+ if ! git diff --no-index "$1.formatted" "$TEMP"; then
+ STATUS=1
+ fi
+}
+
+diff_stdin_filepath app.js
+diff_stdin_filepath subdirectory/lib.js
+diff_stdin_filepath subdirectory/typed.ts
+
+biome format --stdin-file-path="donotformat.ts" < "donotformat.ts" > "$TEMP"
+if ! git diff --no-index "donotformat.ts" "$TEMP"; then
+ STATUS=1
+fi
+
+exit $STATUS
diff --git a/e2e-tests/test-all.sh b/e2e-tests/test-all.sh
index 3e98bf744360..ef462d693b19 100755
--- a/e2e-tests/test-all.sh
+++ b/e2e-tests/test-all.sh
@@ -1,12 +1,35 @@
-#!/bin/sh
+#!/usr/bin/env bash
+
+# Usage:
+# ./test-all.sh
+# ./test-all.sh stdin # to filter tests by name
set -eu
+# Change to the script's directory
+cd "$(dirname "$0")"
+
+# Glob matcher
+if [[ -z "${1:-}" ]]; then FILTER="*"; else FILTER="*$1*"; fi
+
+redecho() {
+ echo -e "\033[1;31m$1\033[0m"
+}
+
+bail() {
+ redecho "Error: $1"
+ exit 1
+}
+
for x in *; do
if test -d "$x"; then
+ if [[ "$x" != $FILTER ]]; then
+ echo "Skipping $x"
+ continue
+ fi
echo "Testing $x..."
- cd "$x"
- sh test.sh
- cd ..
+ pushd "$x" > /dev/null
+ bash test.sh || bail "Test failed: $x. To re-run: $0 $x"
+ popd > /dev/null
fi
done