From f35dc3090448952480319f6bdab740549ad105f7 Mon Sep 17 00:00:00 2001 From: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:42:44 +0530 Subject: [PATCH 1/2] fix(prefilter): use token-boundary matching for test and migration framework detection --- src/scanner/prefilter.rs | 53 +++++++++++++++++++---- tests/unit/prefilter_should_scan_tests.rs | 28 ++++++++++++ 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/scanner/prefilter.rs b/src/scanner/prefilter.rs index d3fab9f..ac644e6 100644 --- a/src/scanner/prefilter.rs +++ b/src/scanner/prefilter.rs @@ -216,7 +216,45 @@ impl PreFilter { } } - /// Simple pattern matching for test frameworks + /// Helper to check if a token or module pattern matches on identifier boundaries + fn matches_token_or_module(text: &str, pattern: &str) -> bool { + if pattern.contains('.') + || pattern.contains('@') + || pattern.contains('/') + || pattern.contains('-') + { + return text.contains(pattern); + } + + let mut search_idx = 0; + while let Some(pos) = text[search_idx..].find(pattern) { + let abs_pos = search_idx + pos; + let before_ok = if abs_pos == 0 { + true + } else { + let prev_char = text[..abs_pos].chars().next_back().unwrap(); + !prev_char.is_alphabetic() + }; + + let end_pos = abs_pos + pattern.len(); + let after_ok = if end_pos >= text.len() { + true + } else { + let next_char = text[end_pos..].chars().next().unwrap(); + !next_char.is_alphabetic() + }; + + if before_ok && after_ok { + return true; + } + + search_idx = abs_pos + pattern.len(); + } + + false + } + + /// Pattern matching for test frameworks on token boundaries fn has_test_patterns(&self, imports_text: &str) -> bool { let test_patterns = [ // Universal test indicators @@ -240,25 +278,24 @@ impl PreFilter { "org.testng", ]; - test_patterns.iter().any(|pattern| imports_text.contains(pattern)) + test_patterns.iter().any(|pattern| Self::matches_token_or_module(imports_text, pattern)) } - /// Simple pattern matching for migration frameworks + /// Pattern matching for migration frameworks on token boundaries fn has_migration_patterns(&self, imports_text: &str) -> bool { let migration_patterns = [ - "migration", - "migrations", - "migrate", + "django.db.migrations", "alembic", "flyway", "liquibase", - "django.db.migrations", "sequelize", "knex", "typeorm", ]; - migration_patterns.iter().any(|pattern| imports_text.contains(pattern)) + migration_patterns + .iter() + .any(|pattern| Self::matches_token_or_module(imports_text, pattern)) } pub fn filter_files( diff --git a/tests/unit/prefilter_should_scan_tests.rs b/tests/unit/prefilter_should_scan_tests.rs index 39abb06..0c4d308 100644 --- a/tests/unit/prefilter_should_scan_tests.rs +++ b/tests/unit/prefilter_should_scan_tests.rs @@ -99,4 +99,32 @@ mod prefilter_should_scan_tests { let filter = PreFilter::new(&empty_rules(), "python"); assert!(filter.should_scan_file(path.to_str().unwrap())); } + + #[test] + fn production_file_with_attestation_or_contest_import_is_scanned() { + let dir = TempDir::new().unwrap(); + let path = dir.path().join("auth_service.py"); + fs::write( + &path, + "from myapp.attestation import verify_attestation\nfrom contest_service import handle_contest\nfrom services.latest_events import get_latest\nfrom fastest_cache import cache\n\ndef login(req):\n return verify_attestation(req)\n", + ) + .unwrap(); + + let filter = PreFilter::new(&empty_rules(), "python"); + assert!(filter.should_scan_file(path.to_str().unwrap())); + } + + #[test] + fn production_file_with_migrate_user_import_is_scanned() { + let dir = TempDir::new().unwrap(); + let path = dir.path().join("account_service.py"); + fs::write( + &path, + "from accounts.user_migrator import migrate_user_account\n\ndef run(user):\n migrate_user_account(user)\n", + ) + .unwrap(); + + let filter = PreFilter::new(&empty_rules(), "python"); + assert!(filter.should_scan_file(path.to_str().unwrap())); + } } From 3a40a33a00e47ba65048d0cffa40d0500a93ce43 Mon Sep 17 00:00:00 2001 From: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:02:00 +0530 Subject: [PATCH 2/2] fix(prefilter): treat [A-Za-z0-9_] as non-boundary and refine framework patterns --- src/scanner/prefilter.rs | 14 ++++++++++-- tests/unit/prefilter_should_scan_tests.rs | 27 ++++++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/scanner/prefilter.rs b/src/scanner/prefilter.rs index ac644e6..2fbc7ca 100644 --- a/src/scanner/prefilter.rs +++ b/src/scanner/prefilter.rs @@ -217,6 +217,10 @@ impl PreFilter { } /// Helper to check if a token or module pattern matches on identifier boundaries + fn is_identifier_character(c: char) -> bool { + c.is_alphanumeric() || c == '_' + } + fn matches_token_or_module(text: &str, pattern: &str) -> bool { if pattern.contains('.') || pattern.contains('@') @@ -233,7 +237,7 @@ impl PreFilter { true } else { let prev_char = text[..abs_pos].chars().next_back().unwrap(); - !prev_char.is_alphabetic() + !Self::is_identifier_character(prev_char) }; let end_pos = abs_pos + pattern.len(); @@ -241,7 +245,7 @@ impl PreFilter { true } else { let next_char = text[end_pos..].chars().next().unwrap(); - !next_char.is_alphabetic() + !Self::is_identifier_character(next_char) }; if before_ok && after_ok { @@ -259,6 +263,9 @@ impl PreFilter { let test_patterns = [ // Universal test indicators "test", + "testing", + "testify", + "vitest", "mock", "spec", "jest", @@ -273,6 +280,7 @@ impl PreFilter { "django.test", "flask.testing", "@testing-library", + "github.com/stretchr/testify", "org.junit", "org.mockito", "org.testng", @@ -284,6 +292,8 @@ impl PreFilter { /// Pattern matching for migration frameworks on token boundaries fn has_migration_patterns(&self, imports_text: &str) -> bool { let migration_patterns = [ + "migration", + "migrations", "django.db.migrations", "alembic", "flyway", diff --git a/tests/unit/prefilter_should_scan_tests.rs b/tests/unit/prefilter_should_scan_tests.rs index 0c4d308..f3483b1 100644 --- a/tests/unit/prefilter_should_scan_tests.rs +++ b/tests/unit/prefilter_should_scan_tests.rs @@ -120,11 +120,36 @@ mod prefilter_should_scan_tests { let path = dir.path().join("account_service.py"); fs::write( &path, - "from accounts.user_migrator import migrate_user_account\n\ndef run(user):\n migrate_user_account(user)\n", + "from accounts.user_migration import user_migration_step\n\ndef run(user):\n user_migration_step(user)\n", ) .unwrap(); let filter = PreFilter::new(&empty_rules(), "python"); assert!(filter.should_scan_file(path.to_str().unwrap())); } + + #[test] + fn file_with_testing_or_vitest_import_is_skipped() { + let dir = TempDir::new().unwrap(); + let path = dir.path().join("service.ts"); + fs::write(&path, "import { describe, it } from 'vitest';\n\ndescribe('test', () => {});\n") + .unwrap(); + + let filter = PreFilter::new(&empty_rules(), "typescript"); + assert!(!filter.should_scan_file(path.to_str().unwrap())); + } + + #[test] + fn django_migration_file_with_migrations_import_is_skipped() { + let dir = TempDir::new().unwrap(); + let path = dir.path().join("0001_initial.py"); + fs::write( + &path, + "from django.db import migrations, models\n\nclass Migration(migrations.Migration):\n dependencies = []\n", + ) + .unwrap(); + + let filter = PreFilter::new(&empty_rules(), "python"); + assert!(!filter.should_scan_file(path.to_str().unwrap())); + } }