diff --git a/apps/oxfmt/src/cli/walk.rs b/apps/oxfmt/src/cli/walk.rs index 9dc91994955b6..e12a2cced823d 100644 --- a/apps/oxfmt/src/cli/walk.rs +++ b/apps/oxfmt/src/cli/walk.rs @@ -5,7 +5,7 @@ use std::{ use ignore::gitignore::{Gitignore, GitignoreBuilder}; -use crate::core::FormatFileStrategy; +use crate::core::{FormatFileStrategy, utils::normalize_relative_path}; pub struct Walk { inner: ignore::WalkParallel, @@ -232,6 +232,11 @@ impl Walk { fn is_ignored(matchers: &[Gitignore], path: &Path, is_dir: bool, check_ancestors: bool) -> bool { for matcher in matchers { let matched = if check_ancestors { + // `matched_path_or_any_parents()` panics if path is not under matcher's root. + // Skip this matcher if the path is outside its scope. + if !path.starts_with(matcher.path()) { + continue; + } matcher.matched_path_or_any_parents(path, is_dir) } else { matcher.matched(path, is_dir) @@ -248,7 +253,7 @@ fn load_ignore_paths(cwd: &Path, ignore_paths: &[PathBuf]) -> Result) -> Option { // If `--config` is explicitly specified, use that path if let Some(config_path) = config_path { - return Some(if config_path.is_absolute() { - config_path.to_path_buf() - } else { - cwd.join(config_path) - }); + return Some(utils::normalize_relative_path(cwd, config_path)); } // If `--config` is not specified, search the nearest config file from cwd upwards diff --git a/apps/oxfmt/src/core/utils.rs b/apps/oxfmt/src/core/utils.rs index 464eeb13a3afb..8fc04d0f2bcd4 100644 --- a/apps/oxfmt/src/core/utils.rs +++ b/apps/oxfmt/src/core/utils.rs @@ -1,4 +1,8 @@ -use std::{fs, io, io::Write, path::Path}; +use std::{ + fs, io, + io::Write, + path::{Path, PathBuf}, +}; /// To debug `oxc_formatter`: /// `OXC_LOG=oxc_formatter oxfmt` @@ -52,3 +56,13 @@ pub fn print_and_flush(writer: &mut dyn Write, message: &str) { writer.write_all(message.as_bytes()).or_else(check_for_writer_error).unwrap(); writer.flush().unwrap(); } + +/// Normalize a relative path by stripping `./` prefix and joining with `cwd`. +/// This ensures consistent path format and avoids issues with relative paths. +pub fn normalize_relative_path(cwd: &Path, path: &Path) -> PathBuf { + if path.is_absolute() { + return path.to_path_buf(); + } + + if let Ok(stripped) = path.strip_prefix("./") { cwd.join(stripped) } else { cwd.join(path) } +} diff --git a/apps/oxfmt/test/cli/config_in_subdirectory/__snapshots__/config_in_subdirectory.test.ts.snap b/apps/oxfmt/test/cli/config_in_subdirectory/__snapshots__/config_in_subdirectory.test.ts.snap new file mode 100644 index 0000000000000..ade7d57e8e860 --- /dev/null +++ b/apps/oxfmt/test/cli/config_in_subdirectory/__snapshots__/config_in_subdirectory.test.ts.snap @@ -0,0 +1,31 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`config_in_subdirectory > should not panic when config with \`ignorePatterns\` is in subdirectory 1`] = ` +"-------------------- +arguments: --check -c ./subdir/.oxfmtrc.json ./subdir +working directory: config_in_subdirectory/fixtures +exit code: 0 +--- STDOUT --------- +Checking formatting... + +All matched files use the correct format. +Finished in ms on 1 files using 1 threads. +--- STDERR --------- + +--------------------" +`; + +exports[`config_in_subdirectory > should not panic when target path is absolute and outside matcher root 1`] = ` +"-------------------- +arguments: --check -c ./subdir/.oxfmtrc.json /outside/file.js +working directory: config_in_subdirectory/fixtures +exit code: 0 +--- STDOUT --------- +Checking formatting... + +All matched files use the correct format. +Finished in ms on 1 files using 1 threads. +--- STDERR --------- + +--------------------" +`; diff --git a/apps/oxfmt/test/cli/config_in_subdirectory/config_in_subdirectory.test.ts b/apps/oxfmt/test/cli/config_in_subdirectory/config_in_subdirectory.test.ts new file mode 100644 index 0000000000000..9ed18b467e3da --- /dev/null +++ b/apps/oxfmt/test/cli/config_in_subdirectory/config_in_subdirectory.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { join } from "node:path"; +import { runAndSnapshot } from "../utils"; + +const fixturesDir = join(import.meta.dirname, "fixtures"); + +describe("config_in_subdirectory", () => { + it("should not panic when config with `ignorePatterns` is in subdirectory", async () => { + // Simulates running from project root: + // ``` + // oxfmt -c ./subdir/.oxfmtrc.json ./subdir + // ``` + // Where the config has `ignorePatterns`, causing matcher root to be `./subdir/` + const snapshot = await runAndSnapshot(fixturesDir, [ + ["--check", "-c", "./subdir/.oxfmtrc.json", "./subdir"], + ]); + expect(snapshot).toMatchSnapshot(); + }); + + it("should not panic when target path is absolute and outside matcher root", async () => { + // fixtures/ + // subdir/ <- matcher root (has `.oxfmtrc.json` with `ignorePatterns`) + // outside/file.js <- target path (outside of matcher root) + const snapshot = await runAndSnapshot(fixturesDir, [ + // Target path is outside of matcher root - should not panic + ["--check", "-c", "./subdir/.oxfmtrc.json", join(fixturesDir, "outside", "file.js")], + ]); + expect(snapshot).toMatchSnapshot(); + }); +}); diff --git a/apps/oxfmt/test/cli/config_in_subdirectory/fixtures/outside/file.js b/apps/oxfmt/test/cli/config_in_subdirectory/fixtures/outside/file.js new file mode 100644 index 0000000000000..943c458c79e20 --- /dev/null +++ b/apps/oxfmt/test/cli/config_in_subdirectory/fixtures/outside/file.js @@ -0,0 +1 @@ +const x = 1; diff --git a/apps/oxfmt/test/cli/config_in_subdirectory/fixtures/subdir/.oxfmtrc.json b/apps/oxfmt/test/cli/config_in_subdirectory/fixtures/subdir/.oxfmtrc.json new file mode 100644 index 0000000000000..875cb792f3ef7 --- /dev/null +++ b/apps/oxfmt/test/cli/config_in_subdirectory/fixtures/subdir/.oxfmtrc.json @@ -0,0 +1,3 @@ +{ + "ignorePatterns": ["ignored/"] +} diff --git a/apps/oxfmt/test/cli/config_in_subdirectory/fixtures/subdir/ignored/broken.js b/apps/oxfmt/test/cli/config_in_subdirectory/fixtures/subdir/ignored/broken.js new file mode 100644 index 0000000000000..9071bf5852ca1 --- /dev/null +++ b/apps/oxfmt/test/cli/config_in_subdirectory/fixtures/subdir/ignored/broken.js @@ -0,0 +1 @@ +class X {