Skip to content

Commit 9e7a257

Browse files
committed
feat(oxfmt): Support --with-node-modules option
1 parent 6d5f7b2 commit 9e7a257

File tree

10 files changed

+137
-52
lines changed

10 files changed

+137
-52
lines changed

apps/oxfmt/src/command.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ pub struct FormatCommand {
2727
#[bpaf(external)]
2828
pub basic_options: BasicOptions,
2929

30+
#[bpaf(external)]
31+
pub ignore_options: IgnoreOptions,
32+
3033
#[bpaf(external)]
3134
pub misc_options: MiscOptions,
3235

@@ -60,6 +63,14 @@ pub struct BasicOptions {
6063
pub config: Option<PathBuf>,
6164
}
6265

66+
/// Ignore Options
67+
#[derive(Debug, Clone, Bpaf)]
68+
pub struct IgnoreOptions {
69+
/// Format code in node_modules directory (disabled by default)
70+
#[bpaf(switch, hide_usage)]
71+
pub with_node_modules: bool,
72+
}
73+
6374
/// Miscellaneous
6475
#[derive(Debug, Clone, Bpaf)]
6576
pub struct MiscOptions {

apps/oxfmt/src/format.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ impl FormatRunner {
4444
let start_time = Instant::now();
4545

4646
let cwd = self.cwd;
47-
let FormatCommand { paths, output_options, basic_options, misc_options } = self.options;
47+
let FormatCommand { paths, output_options, basic_options, ignore_options, misc_options } =
48+
self.options;
4849

4950
// Find and load config
5051
// NOTE: Currently, we only load single config file.
@@ -76,7 +77,7 @@ impl FormatRunner {
7677
.flatten();
7778

7879
// TODO: Support ignoring files
79-
let walker = Walk::new(&target_paths, override_builder);
80+
let walker = Walk::new(&target_paths, override_builder, ignore_options.with_node_modules);
8081

8182
// Get the receiver for streaming entries
8283
let rx_entry = walker.stream_entries();

apps/oxfmt/src/walk.rs

Lines changed: 75 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,54 @@ use oxc_span::SourceType;
77

88
pub struct Walk {
99
inner: ignore::WalkParallel,
10+
with_node_modules: bool,
11+
}
12+
13+
impl Walk {
14+
/// Will not canonicalize paths.
15+
/// # Panics
16+
pub fn new(
17+
paths: &[PathBuf],
18+
override_builder: Option<Override>,
19+
with_node_modules: bool,
20+
) -> Self {
21+
let mut inner = ignore::WalkBuilder::new(
22+
paths
23+
.iter()
24+
.next()
25+
.expect("Expected paths parameter to Walk::new() to contain at least one path."),
26+
);
27+
28+
if let Some(paths) = paths.get(1..) {
29+
for path in paths {
30+
inner.add(path);
31+
}
32+
}
33+
34+
if let Some(override_builder) = override_builder {
35+
inner.overrides(override_builder);
36+
}
37+
38+
// Do not follow symlinks like Prettier does.
39+
// See https://github.com/prettier/prettier/pull/14627
40+
let inner = inner.hidden(false).ignore(false).git_global(false).build_parallel();
41+
Self { inner, with_node_modules }
42+
}
43+
44+
/// Stream entries through a channel as they are discovered
45+
pub fn stream_entries(self) -> mpsc::Receiver<WalkEntry> {
46+
let (sender, receiver) = mpsc::channel::<WalkEntry>();
47+
let with_node_modules = self.with_node_modules;
48+
49+
// Spawn the walk operation in a separate thread
50+
rayon::spawn(move || {
51+
let mut builder = WalkBuilder { sender, with_node_modules };
52+
self.inner.visit(&mut builder);
53+
// Channel will be closed when builder is dropped
54+
});
55+
56+
receiver
57+
}
1058
}
1159

1260
pub struct WalkEntry {
@@ -16,16 +64,36 @@ pub struct WalkEntry {
1664

1765
struct WalkBuilder {
1866
sender: mpsc::Sender<WalkEntry>,
67+
with_node_modules: bool,
1968
}
2069

2170
impl<'s> ignore::ParallelVisitorBuilder<'s> for WalkBuilder {
2271
fn build(&mut self) -> Box<dyn ignore::ParallelVisitor + 's> {
23-
Box::new(WalkVisitor { sender: self.sender.clone() })
72+
Box::new(WalkVisitor {
73+
sender: self.sender.clone(),
74+
with_node_modules: self.with_node_modules,
75+
})
2476
}
2577
}
2678

2779
struct WalkVisitor {
2880
sender: mpsc::Sender<WalkEntry>,
81+
with_node_modules: bool,
82+
}
83+
84+
impl WalkVisitor {
85+
// We are setting `.hidden(false)` on the `WalkBuilder`,
86+
// it means we want to include hidden files and directories.
87+
// However, we (and also Prettier) still skip traversing certain directories.
88+
// https://prettier.io/docs/ignore#ignoring-files-prettierignore
89+
fn is_skipped_dir(&self, dir_name: &OsStr) -> bool {
90+
dir_name == ".git"
91+
|| dir_name == ".jj"
92+
|| dir_name == ".sl"
93+
|| dir_name == ".svn"
94+
|| dir_name == ".hg"
95+
|| (!self.with_node_modules && dir_name == "node_modules")
96+
}
2997
}
3098

3199
impl ignore::ParallelVisitor for WalkVisitor {
@@ -37,15 +105,13 @@ impl ignore::ParallelVisitor for WalkVisitor {
37105
};
38106

39107
if file_type.is_dir() {
40-
// We are setting `.hidden(false)` on the `WalkBuilder` below,
41-
// it means we want to include hidden files and directories.
42-
// However, we (and also Prettier) still skip traversing VCS directories.
43-
// https://prettier.io/docs/ignore#ignoring-files-prettierignore
44-
let dir_name = entry.file_name();
45-
if matches!(dir_name.to_str(), Some(".git" | ".jj" | ".sl" | ".svn" | ".hg")) {
108+
if self.is_skipped_dir(entry.file_name()) {
46109
return ignore::WalkState::Skip;
47110
}
48-
} else if let Some(source_type) = get_supported_source_type(entry.path()) {
111+
return ignore::WalkState::Continue;
112+
}
113+
114+
if let Some(source_type) = get_supported_source_type(entry.path()) {
49115
let walk_entry =
50116
WalkEntry { path: entry.path().as_os_str().into(), source_type };
51117
// Send each entry immediately through the channel
@@ -54,51 +120,10 @@ impl ignore::ParallelVisitor for WalkVisitor {
54120
return ignore::WalkState::Quit;
55121
}
56122
}
123+
57124
ignore::WalkState::Continue
58125
}
59126
Err(_err) => ignore::WalkState::Skip,
60127
}
61128
}
62129
}
63-
64-
impl Walk {
65-
/// Will not canonicalize paths.
66-
/// # Panics
67-
pub fn new(paths: &[PathBuf], override_builder: Option<Override>) -> Self {
68-
let mut inner = ignore::WalkBuilder::new(
69-
paths
70-
.iter()
71-
.next()
72-
.expect("Expected paths parameter to Walk::new() to contain at least one path."),
73-
);
74-
75-
if let Some(paths) = paths.get(1..) {
76-
for path in paths {
77-
inner.add(path);
78-
}
79-
}
80-
81-
if let Some(override_builder) = override_builder {
82-
inner.overrides(override_builder);
83-
}
84-
85-
// Do not follow symlinks like Prettier does.
86-
// See https://github.com/prettier/prettier/pull/14627
87-
let inner = inner.hidden(false).ignore(false).git_global(false).build_parallel();
88-
Self { inner }
89-
}
90-
91-
/// Stream entries through a channel as they are discovered
92-
pub fn stream_entries(self) -> mpsc::Receiver<WalkEntry> {
93-
let (sender, receiver) = mpsc::channel::<WalkEntry>();
94-
95-
// Spawn the walk operation in a separate thread
96-
rayon::spawn(move || {
97-
let mut builder = WalkBuilder { sender };
98-
self.inner.visit(&mut builder);
99-
// Channel will be closed when builder is dropped
100-
});
101-
102-
receiver
103-
}
104-
}

apps/oxfmt/tests/fixtures/node_modules_dirs/node_modules/test.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/oxfmt/tests/fixtures/node_modules_dirs/regular_dir/node_modules/test.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const bar = 2;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const foo = 1;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// This file should be ignored
2+
const x = 1;

apps/oxfmt/tests/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ fn vcs_dirs_ignored() {
8686
.test_and_snapshot_multiple(&[&["--check"]]);
8787
}
8888

89+
#[test]
90+
fn node_modules_ignored() {
91+
// Test that `node_modules` directories are ignored by default
92+
// but can be included with `--with-node-modules` flag
93+
Tester::new()
94+
.with_cwd(PathBuf::from("tests/fixtures/node_modules_dirs"))
95+
.test_and_snapshot_multiple(&[&["--check"], &["--check", "--with-node-modules"]]);
96+
}
97+
8998
#[test]
9099
fn exclude_nested_paths() {
91100
// Test that nested path exclusion works correctly
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
source: apps/oxfmt/tests/tester.rs
3+
assertion_line: 102
4+
---
5+
##########
6+
arguments: --check
7+
working directory: tests/fixtures/node_modules_dirs
8+
----------
9+
Checking formatting...
10+
regular_dir/test.js (<variable>ms)
11+
root.js (<variable>ms)
12+
13+
Format issues found in above 2 files. Run without `--check` to fix.
14+
Finished in <variable>ms on 2 files using 1 threads.
15+
----------
16+
CLI result: FormatMismatch
17+
----------
18+
19+
##########
20+
arguments: --check --with-node-modules
21+
working directory: tests/fixtures/node_modules_dirs
22+
----------
23+
Checking formatting...
24+
node_modules/test.js (<variable>ms)
25+
regular_dir/node_modules/test.js (<variable>ms)
26+
regular_dir/test.js (<variable>ms)
27+
root.js (<variable>ms)
28+
29+
Format issues found in above 4 files. Run without `--check` to fix.
30+
Finished in <variable>ms on 4 files using 1 threads.
31+
----------
32+
CLI result: FormatMismatch
33+
----------

0 commit comments

Comments
 (0)