@@ -3,7 +3,7 @@ use std::{
33 sync:: mpsc,
44} ;
55
6- use ignore:: { gitignore:: GitignoreBuilder , overrides :: OverrideBuilder } ;
6+ use ignore:: gitignore:: GitignoreBuilder ;
77
88use crate :: core:: FormatFileSource ;
99
@@ -18,38 +18,49 @@ impl Walk {
1818 ignore_paths : & [ PathBuf ] ,
1919 with_node_modules : bool ,
2020 ignore_patterns : & [ String ] ,
21- ) -> Result < Self , String > {
22- let ( target_paths, exclude_patterns) = normalize_paths ( cwd, paths) ;
23-
24- // Add all non-`!` prefixed paths to the walker base
25- let mut inner = ignore:: WalkBuilder :: new (
26- target_paths
27- . first ( )
28- . expect ( "`target_paths` never be empty, should have at least `cwd`" ) ,
29- ) ;
30- if let Some ( paths) = target_paths. get ( 1 ..) {
31- for path in paths {
32- inner. add ( path) ;
21+ ) -> Result < Option < Self > , String > {
22+ //
23+ // Classify and normalize specified paths
24+ //
25+ let mut target_paths = vec ! [ ] ;
26+ let mut exclude_patterns = vec ! [ ] ;
27+ for path in paths {
28+ let path_str = path. to_string_lossy ( ) ;
29+
30+ // Instead of `oxlint`'s `--ignore-pattern=PAT`,
31+ // `oxfmt` supports `!` prefix in paths like Prettier.
32+ if path_str. starts_with ( '!' ) {
33+ exclude_patterns. push ( path_str. to_string ( ) ) ;
34+ continue ;
3335 }
34- }
3536
36- // NOTE: We are using `OverrideBuilder` only for exclusion.
37- // This means there is no way to "re-include" a file once ignored.
37+ // Otherwise, treat as target path
3838
39- // Treat all `!` prefixed patterns as overrides to exclude
40- if !exclude_patterns. is_empty ( ) {
41- let mut builder = OverrideBuilder :: new ( cwd) ;
42- for pattern_str in exclude_patterns {
43- builder
44- . add ( & pattern_str)
45- . map_err ( |_| format ! ( "{pattern_str} is not a valid glob for override." ) ) ?;
39+ if path. is_absolute ( ) {
40+ target_paths. push ( path. clone ( ) ) ;
41+ continue ;
4642 }
47- let overrides = builder. build ( ) . map_err ( |_| "Failed to build overrides" . to_string ( ) ) ?;
48- inner. overrides ( overrides) ;
43+
44+ // NOTE: `.` and cwd behave differently, need to normalize
45+ let path = if path_str == "." {
46+ cwd. to_path_buf ( )
47+ } else if let Some ( stripped) = path_str. strip_prefix ( "./" ) {
48+ cwd. join ( stripped)
49+ } else {
50+ cwd. join ( path)
51+ } ;
52+ target_paths. push ( path) ;
53+ }
54+ // Default to cwd if no target paths are provided
55+ if target_paths. is_empty ( ) {
56+ target_paths. push ( cwd. to_path_buf ( ) ) ;
4957 }
5058
51- // Handle ignore files
59+ //
60+ // Build ignores
61+ //
5262 let mut builder = GitignoreBuilder :: new ( cwd) ;
63+ // Handle ignore files
5364 for ignore_path in & load_ignore_paths ( cwd, ignore_paths) {
5465 if builder. add ( ignore_path) . is_some ( ) {
5566 return Err ( format ! ( "Failed to add ignore file: {}" , ignore_path. display( ) ) ) ;
@@ -58,11 +69,47 @@ impl Walk {
5869 // Handle `config.ignorePatterns`
5970 for pattern in ignore_patterns {
6071 if builder. add_line ( None , pattern) . is_err ( ) {
61- return Err ( format ! ( "Failed to add ignore pattern `{pattern}`" ) ) ;
72+ return Err ( format ! (
73+ "Failed to add ignore pattern `{pattern}` from `.ignorePatterns`"
74+ ) ) ;
75+ }
76+ }
77+ // Handle `!` prefixed paths as ignore patterns too
78+ for pattern in & exclude_patterns {
79+ // Remove the leading `!` because `GitignoreBuilder` uses `!` as negation
80+ let pattern =
81+ pattern. strip_prefix ( '!' ) . expect ( "There should be a `!` prefix, already checked" ) ;
82+ if builder. add_line ( None , pattern) . is_err ( ) {
83+ return Err ( format ! ( "Failed to add ignore pattern `{pattern}` from `!` prefix" ) ) ;
6284 }
6385 }
6486 let ignores = builder. build ( ) . map_err ( |_| "Failed to build ignores" . to_string ( ) ) ?;
6587
88+ //
89+ // Filter paths by ignores
90+ //
91+ // NOTE: Base paths passed to `WalkBuilder` are not filtered by `filter_entry()`,
92+ // so we need to filter them here before passing to the walker.
93+ let target_paths: Vec < _ > = target_paths
94+ . into_iter ( )
95+ . filter ( |path| {
96+ let matched = ignores. matched ( path, path. is_dir ( ) ) ;
97+ !matched. is_ignore ( ) || matched. is_whitelist ( )
98+ } )
99+ . collect ( ) ;
100+
101+ // If no target paths remain after filtering, return `None`.
102+ // Not an error, but nothing to format, leave it to the caller how to handle.
103+ let Some ( first_path) = target_paths. first ( ) else {
104+ return Ok ( None ) ;
105+ } ;
106+
107+ // Add all non-`!` prefixed paths to the walker base
108+ let mut inner = ignore:: WalkBuilder :: new ( first_path) ;
109+ for path in target_paths. iter ( ) . skip ( 1 ) {
110+ inner. add ( path) ;
111+ }
112+
66113 // NOTE: If return `false` here, it will not be `visit()`ed at all
67114 inner. filter_entry ( move |entry| {
68115 let Some ( file_type) = entry. file_type ( ) else {
@@ -115,7 +162,7 @@ impl Walk {
115162 . git_ignore ( false )
116163 . git_exclude ( false )
117164 . build_parallel ( ) ;
118- Ok ( Self { inner } )
165+ Ok ( Some ( Self { inner } ) )
119166 }
120167
121168 /// Stream entries through a channel as they are discovered
@@ -133,49 +180,6 @@ impl Walk {
133180 }
134181}
135182
136- /// Normalize user input paths into `target_paths` and `exclude_patterns`.
137- /// - `target_paths`: Absolute paths to format
138- /// - `exclude_patterns`: Pattern strings to exclude (with `!` prefix)
139- fn normalize_paths ( cwd : & Path , input_paths : & [ PathBuf ] ) -> ( Vec < PathBuf > , Vec < String > ) {
140- let mut target_paths = vec ! [ ] ;
141- let mut exclude_patterns = vec ! [ ] ;
142-
143- for path in input_paths {
144- let path_str = path. to_string_lossy ( ) ;
145-
146- // Instead of `oxlint`'s `--ignore-pattern=PAT`,
147- // `oxfmt` supports `!` prefix in paths like Prettier.
148- if path_str. starts_with ( '!' ) {
149- exclude_patterns. push ( path_str. to_string ( ) ) ;
150- continue ;
151- }
152-
153- // Otherwise, treat as target path
154-
155- if path. is_absolute ( ) {
156- target_paths. push ( path. clone ( ) ) ;
157- continue ;
158- }
159-
160- // NOTE: `.` and cwd behaves differently, need to normalize
161- let path = if path_str == "." {
162- cwd. to_path_buf ( )
163- } else if let Some ( stripped) = path_str. strip_prefix ( "./" ) {
164- cwd. join ( stripped)
165- } else {
166- cwd. join ( path)
167- } ;
168- target_paths. push ( path) ;
169- }
170-
171- // Default to cwd if no `target_paths` are provided
172- if target_paths. is_empty ( ) {
173- target_paths. push ( cwd. into ( ) ) ;
174- }
175-
176- ( target_paths, exclude_patterns)
177- }
178-
179183fn load_ignore_paths ( cwd : & Path , ignore_paths : & [ PathBuf ] ) -> Vec < PathBuf > {
180184 // If specified, just resolves absolute paths
181185 if !ignore_paths. is_empty ( ) {
@@ -187,12 +191,12 @@ fn load_ignore_paths(cwd: &Path, ignore_paths: &[PathBuf]) -> Vec<PathBuf> {
187191
188192 // Else, search for default ignore files in cwd
189193 [ ".gitignore" , ".prettierignore" ]
190- . iter ( )
194+ . into_iter ( )
191195 . filter_map ( |file_name| {
192196 let path = cwd. join ( file_name) ;
193- if path. exists ( ) { Some ( path) } else { None }
197+ path. exists ( ) . then_some ( path)
194198 } )
195- . collect :: < Vec < _ > > ( )
199+ . collect ( )
196200}
197201
198202// ---
0 commit comments