Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions apps/oxfmt/src/cli/walk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ impl Walk {
// - Ignore files: root = parent directory of the ignore file
// - `.ignorePatterns`: root = parent directory of `.oxfmtrc.json`
// - Exclude paths (`!` prefix): root = cwd
//
// NOTE: Git ignore files are handled by `WalkBuilder` itself
let mut matchers: Vec<Gitignore> = vec![];

// 1. Handle ignore files (`.gitignore`, `.prettierignore`, or `--ignore-path`)
// 1. Handle formatter ignore files (`.prettierignore`, or `--ignore-path`)
// Patterns are relative to the ignore file location
for ignore_path in &load_ignore_paths(cwd, ignore_paths)? {
let (gitignore, err) = Gitignore::new(ignore_path);
Expand Down Expand Up @@ -118,10 +120,12 @@ impl Walk {
}

//
// Filter paths by ignores
// Filter paths by formatter ignores
//
// NOTE: Base paths passed to `WalkBuilder` are not filtered by `filter_entry()`,
// so we need to filter them here before passing to the walker.
// This is needed for cases like `husky`, may specify ignored paths as staged files.
// NOTE: Git ignored paths are not filtered here.
let target_paths: Vec<_> = target_paths
.into_iter()
.filter(|path| !is_ignored(&matchers, path, path.is_dir()))
Expand Down Expand Up @@ -183,12 +187,20 @@ impl Walk {
.follow_links(false)
// Include hidden files and directories except those we explicitly skip above
.hidden(false)
// Do not respect `.gitignore` automatically, we handle it manually
// Do not respect `.ignore` file
.ignore(false)
// Do not search upward
// NOTE: Prettier only searches current working directory
.parents(false)
// Also do not respect globals
.git_global(false)
.git_ignore(false)
// But respect downward nested `.gitignore` files
// NOTE: Prettier does not: https://github.com/prettier/prettier/issues/4081
.git_ignore(true)
// Also do not respect `.git/info/exclude`
.git_exclude(false)
// Git is not required
.require_git(false)
.build_parallel();
Ok(Some(Self { inner }))
}
Expand Down Expand Up @@ -238,8 +250,7 @@ fn load_ignore_paths(cwd: &Path, ignore_paths: &[PathBuf]) -> Result<Vec<PathBuf

// Else, search for default ignore files in cwd
// These are optional, do not error if not found
Ok([".gitignore", ".prettierignore"]
.into_iter()
Ok(std::iter::once(".prettierignore")
.filter_map(|file_name| {
let path = cwd.join(file_name);
path.exists().then_some(path)
Expand Down
38 changes: 38 additions & 0 deletions apps/oxfmt/test/__snapshots__/gitignore.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`gitignore > should respect .gitignore in subdirectory 1`] = `
"--------------------
arguments: --check
working directory: fixtures/gitignore
exit code: 1
--- STDOUT ---------
Checking formatting...

another/another.js (<variable>ms)
root.js (<variable>ms)
subdir/sub.js (<variable>ms)

Format issues found in above 3 files. Run without \`--check\` to fix.
Finished in <variable>ms on 3 files using 1 threads.
--- STDERR ---------

--------------------"
`;

exports[`gitignore > should respect root .gitignore 1`] = `
"--------------------
arguments: --check
working directory: fixtures/gitignore
exit code: 1
--- STDOUT ---------
Checking formatting...

another/another.js (<variable>ms)
root.js (<variable>ms)

Format issues found in above 2 files. Run without \`--check\` to fix.
Finished in <variable>ms on 2 files using 1 threads.
--- STDERR ---------

--------------------"
`;
1 change: 1 addition & 0 deletions apps/oxfmt/test/fixtures/gitignore/another/another.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const v=5
1 change: 1 addition & 0 deletions apps/oxfmt/test/fixtures/gitignore/ignored-by-root.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const y=2
1 change: 1 addition & 0 deletions apps/oxfmt/test/fixtures/gitignore/root.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const x=1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const w=4
1 change: 1 addition & 0 deletions apps/oxfmt/test/fixtures/gitignore/subdir/sub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const z=3
39 changes: 39 additions & 0 deletions apps/oxfmt/test/gitignore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { writeFile, rm } from "node:fs/promises";
import { runAndSnapshot } from "./utils";

const fixturesDir = join(__dirname, "fixtures");

// NOTE: These tests modify `.gitignore` files in the fixtures directory.
// If we commit `.gitignore`, required test fixtures are also ignored by git!

describe("gitignore", () => {
const cwd = join(fixturesDir, "gitignore");
const rootGitignore = join(cwd, ".gitignore");

it("should respect root .gitignore", async () => {
try {
await writeFile(rootGitignore, ["ignored-by-root.js", "subdir/"].join("\n"));

const snapshot = await runAndSnapshot(cwd, [["--check"]]);
expect(snapshot).toMatchSnapshot();
} finally {
await rm(rootGitignore, { force: true });
}
});

it("should respect .gitignore in subdirectory", async () => {
const subdirGitignore = join(cwd, "subdir", ".gitignore");
try {
await writeFile(rootGitignore, ["ignored-by-root.js"].join("\n"));
await writeFile(subdirGitignore, "ignored-by-subdir.js\n");

const snapshot = await runAndSnapshot(cwd, [["--check"]]);
expect(snapshot).toMatchSnapshot();
} finally {
await rm(rootGitignore, { force: true });
await rm(subdirGitignore, { force: true });
}
});
});
Loading