Skip to content

Commit 88bfcce

Browse files
fix: 🐛 correctly handle include patterns in file filtering (#19)
Updates the file filtering logic to properly use OverrideBuilder for include patterns.
1 parent 7c23462 commit 88bfcce

File tree

1 file changed

+153
-15
lines changed

1 file changed

+153
-15
lines changed

src/analyzer.rs

Lines changed: 153 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,22 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
9090
.git_ignore(!args.no_ignore)
9191
.ignore(!args.no_ignore);
9292

93+
let mut override_builder = OverrideBuilder::new(path);
94+
95+
// Handle include patterns first (positive patterns)
9396
if let Some(ref includes) = args.include {
9497
for pattern in includes {
95-
builder.add_custom_ignore_filename(pattern);
98+
// Include patterns are positive patterns (no ! prefix)
99+
if let Err(e) = override_builder.add(pattern) {
100+
eprintln!(
101+
"Warning: Invalid include pattern '{}': {}",
102+
pattern, e
103+
);
104+
}
96105
}
97106
}
98107

99-
let mut override_builder = OverrideBuilder::new(path);
108+
// Handle exclude patterns (negative patterns)
100109
if let Some(ref excludes) = args.exclude {
101110
for exclude in excludes {
102111
match exclude {
@@ -177,15 +186,37 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
177186
.map(|m| m.len() <= max_size)
178187
.unwrap_or(false)
179188
{
180-
// Need to check excludes even for single files explicitly passed
189+
// Need to check includes and excludes even for single files explicitly passed
181190
let mut excluded = false;
191+
let mut override_builder =
192+
OverrideBuilder::new(path.parent().unwrap_or(path)); // Base relative to parent
193+
194+
// Handle include patterns first (positive patterns)
195+
if let Some(ref includes) = args.include {
196+
for pattern in includes {
197+
// Include patterns are positive patterns (no ! prefix)
198+
if let Err(e) = override_builder.add(pattern) {
199+
eprintln!(
200+
"Warning: Invalid include pattern '{}': {}",
201+
pattern, e
202+
);
203+
}
204+
}
205+
}
206+
207+
// Handle exclude patterns (negative patterns)
182208
if let Some(ref excludes) = args.exclude {
183-
let mut override_builder =
184-
OverrideBuilder::new(path.parent().unwrap_or(path)); // Base relative to parent
185209
for exclude in excludes {
186210
match exclude {
187211
Exclude::Pattern(pattern) => {
188-
if let Err(e) = override_builder.add(pattern) {
212+
// Add a '!' prefix if it doesn't already have one
213+
// This makes it a negative pattern (exclude)
214+
let exclude_pattern = if !pattern.starts_with('!') {
215+
format!("!{}", pattern)
216+
} else {
217+
pattern.clone()
218+
};
219+
if let Err(e) = override_builder.add(&exclude_pattern) {
189220
eprintln!(
190221
"Warning: Invalid exclude pattern '{}': {}",
191222
pattern, e
@@ -203,10 +234,19 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
203234
if excluded {
204235
continue;
205236
}
206-
let overrides = override_builder.build()?;
207-
if overrides.matched(path, false).is_ignore() {
208-
excluded = true;
209-
}
237+
}
238+
239+
let overrides = override_builder.build()?;
240+
let match_result = overrides.matched(path, false);
241+
242+
// If there are include patterns, the file must match at least one include pattern
243+
// and not be excluded by any exclude pattern
244+
if args.include.is_some() {
245+
// With include patterns: file must be whitelisted (matched by include) and not ignored (excluded)
246+
excluded = !match_result.is_whitelist() || match_result.is_ignore();
247+
} else {
248+
// Without include patterns: file is excluded only if it matches an exclude pattern
249+
excluded = match_result.is_ignore();
210250
}
211251

212252
if !excluded {
@@ -398,18 +438,36 @@ mod tests {
398438

399439
// Test excluding all Rust files
400440
cli.exclude = Some(vec![Exclude::Pattern("**/*.rs".to_string())]);
401-
let _ = process_directory(&cli)?;
441+
let entries = process_entries(&cli)?;
442+
402443
// Verify no .rs files were processed
403-
// This would need to be adapted based on how you want to verify the results
444+
for entry in &entries {
445+
assert!(
446+
entry.path.extension().and_then(|ext| ext.to_str()) != Some("rs"),
447+
"Found .rs file that should have been excluded: {:?}",
448+
entry.path
449+
);
450+
}
404451

405452
// Test excluding specific directories
406453
cli.exclude = Some(vec![
407454
Exclude::Pattern("**/node_modules/**".to_string()),
408455
Exclude::Pattern("**/target/**".to_string()),
409456
Exclude::Pattern("**/.git/**".to_string()),
410457
]);
411-
let _ = process_directory(&cli)?;
458+
let entries = process_entries(&cli)?;
459+
412460
// Verify excluded directories were not processed
461+
for entry in &entries {
462+
let path_str = entry.path.to_string_lossy();
463+
assert!(
464+
!path_str.contains("node_modules") &&
465+
!path_str.contains("target") &&
466+
!path_str.contains(".git"),
467+
"Found file from excluded directory: {:?}",
468+
entry.path
469+
);
470+
}
413471

414472
Ok(())
415473
}
@@ -421,13 +479,93 @@ mod tests {
421479

422480
// Test including only Rust files
423481
cli.include = Some(vec!["**/*.rs".to_string()]);
424-
let _ = process_directory(&cli)?;
482+
let entries = process_entries(&cli)?;
483+
425484
// Verify only .rs files were processed
485+
assert!(!entries.is_empty(), "Should have found some .rs files");
486+
for entry in &entries {
487+
assert!(
488+
entry.path.extension().and_then(|ext| ext.to_str()) == Some("rs"),
489+
"Found non-.rs file: {:?}",
490+
entry.path
491+
);
492+
}
493+
494+
// Should find 4 .rs files: main.rs, lib.rs, test.rs, code.rs
495+
assert_eq!(entries.len(), 4, "Should find exactly 4 .rs files");
426496

427497
// Test including multiple patterns
428498
cli.include = Some(vec!["**/*.rs".to_string(), "**/*.py".to_string()]);
429-
let _ = process_directory(&cli)?;
499+
let entries = process_entries(&cli)?;
500+
430501
// Verify only .rs and .py files were processed
502+
assert!(!entries.is_empty(), "Should have found some .rs and .py files");
503+
for entry in &entries {
504+
let ext = entry.path.extension().and_then(|ext| ext.to_str());
505+
assert!(
506+
ext == Some("rs") || ext == Some("py"),
507+
"Found file with unexpected extension: {:?}",
508+
entry.path
509+
);
510+
}
511+
512+
// Should find 4 .rs files + 1 .py file = 5 total
513+
assert_eq!(entries.len(), 5, "Should find exactly 5 .rs and .py files");
514+
515+
Ok(())
516+
}
517+
518+
#[test]
519+
fn test_process_directory_with_includes_and_excludes() -> Result<()> {
520+
let (dir, _files) = setup_test_directory()?;
521+
let mut cli = create_test_cli(dir.path());
522+
523+
// Test including only Rust files but excluding specific ones
524+
cli.include = Some(vec!["**/*.rs".to_string()]);
525+
cli.exclude = Some(vec![Exclude::Pattern("**/test.rs".to_string())]);
526+
let entries = process_entries(&cli)?;
527+
528+
// Verify only .rs files were processed, but test.rs was excluded
529+
assert!(!entries.is_empty(), "Should have found some .rs files");
530+
for entry in &entries {
531+
assert!(
532+
entry.path.extension().and_then(|ext| ext.to_str()) == Some("rs"),
533+
"Found non-.rs file: {:?}",
534+
entry.path
535+
);
536+
assert!(
537+
!entry.path.to_string_lossy().contains("test.rs"),
538+
"Found excluded test.rs file: {:?}",
539+
entry.path
540+
);
541+
}
542+
543+
// Should find 3 .rs files (main.rs, lib.rs, code.rs) but not test.rs
544+
assert_eq!(entries.len(), 3, "Should find exactly 3 .rs files (excluding test.rs)");
545+
546+
// Test including multiple file types but excluding a directory
547+
cli.include = Some(vec!["**/*.rs".to_string(), "**/*.py".to_string()]);
548+
cli.exclude = Some(vec![Exclude::Pattern("**/nested/**".to_string())]);
549+
let entries = process_entries(&cli)?;
550+
551+
// Verify only .rs and .py files were processed, but nested directory was excluded
552+
assert!(!entries.is_empty(), "Should have found some .rs and .py files");
553+
for entry in &entries {
554+
let ext = entry.path.extension().and_then(|ext| ext.to_str());
555+
assert!(
556+
ext == Some("rs") || ext == Some("py"),
557+
"Found file with unexpected extension: {:?}",
558+
entry.path
559+
);
560+
assert!(
561+
!entry.path.to_string_lossy().contains("nested"),
562+
"Found file from excluded nested directory: {:?}",
563+
entry.path
564+
);
565+
}
566+
567+
// Should find 3 .rs files (main.rs, lib.rs, test.rs) but not code.rs or script.py from nested
568+
assert_eq!(entries.len(), 3, "Should find exactly 3 files (excluding nested directory)");
431569

432570
Ok(())
433571
}

0 commit comments

Comments
 (0)