use std::path::Path;
use crate::engine::codebase_scan::{self, ExtensionFilter, ScanConfig};
pub(crate) const INDEX_FILES: &[&str] = &[
"mod.rs",
"lib.rs",
"main.rs",
"index.js",
"index.jsx",
"index.ts",
"index.tsx",
"index.mjs",
"__init__.py",
];
pub(crate) fn is_index_file(path: &Path) -> bool {
path.file_name()
.and_then(|n| n.to_str())
.map(|name| INDEX_FILES.contains(&name))
.unwrap_or(false)
}
pub(crate) fn extension_provided_file_extensions() -> Vec<String> {
crate::extension::load_all_extensions()
.unwrap_or_default()
.into_iter()
.flat_map(|m| m.provided_file_extensions().to_vec())
.collect()
}
pub(crate) fn walk_source_files(root: &Path) -> std::io::Result<Vec<std::path::PathBuf>> {
let dynamic_extensions = extension_provided_file_extensions();
let config = ScanConfig {
extensions: ExtensionFilter::Only(dynamic_extensions),
..Default::default()
};
let mut files = codebase_scan::walk_files(root, &config);
files.retain(|f| !is_index_file(f));
Ok(files)
}
pub fn is_test_path(relative_path: &str) -> bool {
let path_lower = relative_path.to_lowercase();
if path_lower.starts_with("tests/")
|| path_lower.starts_with("test/")
|| path_lower.starts_with("__tests__/")
|| path_lower.contains("/tests/")
|| path_lower.contains("/test/")
|| path_lower.contains("/__tests__/")
{
return true;
}
let file_name = relative_path.rsplit('/').next().unwrap_or(relative_path);
if file_name.ends_with("_test.rs") || file_name.ends_with("_tests.rs") {
return true;
}
if file_name.ends_with("Test.php") {
return true;
}
for ext in &[
".test.js",
".test.jsx",
".test.ts",
".test.tsx",
".test.mjs",
".spec.js",
".spec.jsx",
".spec.ts",
".spec.tsx",
".spec.mjs",
] {
if file_name.ends_with(ext) {
return true;
}
}
if file_name.starts_with("test_") && file_name.ends_with(".py") {
return true;
}
false
}
const COMMON_SOURCE_EXTENSIONS: &[&str] = &[
"rs", "php", "js", "ts", "py", "go", "java", "rb", "swift", "kt", "c", "cpp", "h",
];
pub(crate) fn count_unclaimed_source_files(root: &Path) -> usize {
let claimed = extension_provided_file_extensions();
let config = ScanConfig {
extensions: ExtensionFilter::Only(
COMMON_SOURCE_EXTENSIONS
.iter()
.filter(|ext| !claimed.iter().any(|c| c.as_str() == **ext))
.map(|ext| ext.to_string())
.collect(),
),
..Default::default()
};
codebase_scan::walk_files(root, &config).len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_test_path_directory_patterns() {
assert!(is_test_path("tests/core/audit.rs"));
assert!(is_test_path("tests/Unit/FooTest.php"));
assert!(is_test_path("test/helpers.js"));
assert!(is_test_path("src/__tests__/foo.test.ts"));
assert!(is_test_path("inc/Tests/Abilities/FooTest.php"));
assert!(is_test_path("some/deep/path/tests/unit/bar.rs"));
}
#[test]
fn test_is_test_path_filename_patterns() {
assert!(is_test_path("src/core/audit_test.rs"));
assert!(is_test_path("src/core/audit_tests.rs"));
assert!(is_test_path("inc/Abilities/SystemAbilitiesTest.php"));
assert!(is_test_path("src/components/Button.test.tsx"));
assert!(is_test_path("src/utils/parse.spec.ts"));
assert!(is_test_path("lib/test_runner.py"));
}
#[test]
fn test_is_test_path_negative() {
assert!(!is_test_path("src/core/audit.rs"));
assert!(!is_test_path("inc/Abilities/SystemAbilities.php"));
assert!(!is_test_path("src/components/Button.tsx"));
assert!(!is_test_path("src/utils/test_helpers.rs")); assert!(!is_test_path("src/testing/framework.rs")); }
}