use std::path::Path;
const DEFAULT_EXCLUDE_EXTENSIONS: &[&str] = &[
"resx", "po", "pot", "xlf", "xliff", "strings", "arb", "lproj",
"md", "txt", "rst", "adoc", "textile",
];
const DEFAULT_EXCLUDE_COMPOUND_EXTENSIONS: &[&str] = &[
"pb.go",
"pb.h",
"pb.cc",
"pb.swift",
"g.cs",
"generated.cs",
"d.ts",
"min.js",
"min.css",
];
const DEFAULT_EXCLUDE_PATTERNS: &[&str] = &[
"**/pnpm-lock.yaml",
"**/package-lock.json",
"**/yarn.lock",
"**/Cargo.lock",
"**/Gemfile.lock",
"**/poetry.lock",
"**/composer.lock",
"**/go.sum",
"**/flake.lock",
"**/*.lock",
"**/appsettings*.json",
"**/launchSettings.json",
"**/.env*",
".claude/**",
".cursor/**",
".idea/**",
".vscode/**",
"**/Migrations/*.Designer.cs",
"**/Migrations/*ModelSnapshot.cs",
"**/migrations/*.py",
"db/schema.rb",
"prisma/migrations/**",
"alembic/versions/**",
"**/i18n/**",
"**/l10n/**",
"**/locales/**",
"**/locale/**",
"**/node_modules/**",
"**/vendor/**",
"**/__pycache__/**",
"**/*.egg-info/**",
"**/target/**",
"**/.next/**",
"**/.nuxt/**",
"**/out/**",
"**/gen/**",
"**/generated/**",
"**/.gradle/**",
"**/.mvn/**",
"**/build/**",
"**/*_pb2.py",
];
pub fn is_excluded(
path: &Path,
patterns: &[String],
extensions: &[String],
use_defaults: bool,
) -> bool {
let path_str = path.to_string_lossy();
let path_lower = path_str.to_lowercase();
if use_defaults {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
let ext_lower = ext.to_lowercase();
if DEFAULT_EXCLUDE_EXTENSIONS.iter().any(|&e| e == ext_lower) {
return true;
}
}
if DEFAULT_EXCLUDE_COMPOUND_EXTENSIONS
.iter()
.any(|&e| path_lower.ends_with(&format!(".{}", e)))
{
return true;
}
if DEFAULT_EXCLUDE_PATTERNS
.iter()
.any(|p| glob_match::glob_match(p, &path_str))
{
return true;
}
}
if !extensions.is_empty() {
for ext in extensions {
let ext_lower = ext.trim_start_matches('.').to_lowercase();
if path_lower.ends_with(&format!(".{}", ext_lower)) {
return true;
}
}
}
for pattern in patterns {
if glob_match::glob_match(pattern, &path_str) {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_excluded_matches_default_extensions() {
let p = Path::new("src/Resources/Strings.resx");
assert!(is_excluded(p, &[], &[], true));
assert!(!is_excluded(p, &[], &[], false));
}
#[test]
fn is_excluded_matches_po_files() {
assert!(is_excluded(
Path::new("locale/fr/messages.po"),
&[],
&[],
true
));
assert!(is_excluded(Path::new("lang/en.pot"), &[], &[], true));
assert!(is_excluded(Path::new("i18n/strings.xlf"), &[], &[], true));
}
#[test]
fn is_excluded_matches_user_globs() {
let patterns = vec!["**/i18n/**".to_string()];
assert!(is_excluded(
Path::new("src/assets/i18n/sfk-messages/fr-FR.ts"),
&patterns,
&[],
false
));
assert!(!is_excluded(
Path::new("src/main.rs"),
&patterns,
&[],
false
));
}
#[test]
fn is_excluded_combines_defaults_and_user_patterns() {
let patterns = vec!["**/i18n/**".to_string()];
assert!(is_excluded(Path::new("foo.resx"), &patterns, &[], true));
assert!(is_excluded(
Path::new("src/i18n/en.ts"),
&patterns,
&[],
true
));
assert!(!is_excluded(Path::new("src/main.rs"), &patterns, &[], true));
}
#[test]
fn is_excluded_matches_config_files_by_default() {
assert!(is_excluded(
Path::new("src/server/BusinessHub.API/appsettings.json"),
&[],
&[],
true,
));
assert!(is_excluded(
Path::new("src/server/BusinessHub.API/appsettings.Development.json"),
&[],
&[],
true,
));
assert!(is_excluded(
Path::new("Properties/launchSettings.json"),
&[],
&[],
true,
));
assert!(is_excluded(Path::new("some/path/foo.lock"), &[], &[], true));
assert!(is_excluded(Path::new(".env.production"), &[], &[], true));
assert!(!is_excluded(
Path::new("src/data/schema.json"),
&[],
&[],
true
));
}
#[test]
fn is_excluded_matches_i18n_directories_by_default() {
assert!(is_excluded(
Path::new("src/client/src/assets/i18n/sfk-messages/en-US.ts"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("app/l10n/strings_fr.arb"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("src/locales/en.json"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("config/locale/fr.yml"),
&[],
&[],
true
));
assert!(!is_excluded(Path::new("src/main.ts"), &[], &[], true));
}
#[test]
fn is_excluded_case_insensitive_extension() {
assert!(is_excluded(Path::new("Strings.RESX"), &[], &[], true));
assert!(is_excluded(Path::new("lang.Resx"), &[], &[], true));
}
#[test]
fn is_excluded_matches_documentation_files() {
assert!(is_excluded(Path::new("README.md"), &[], &[], true));
assert!(is_excluded(Path::new("docs/guide.rst"), &[], &[], true));
assert!(is_excluded(Path::new("CHANGELOG.txt"), &[], &[], true));
assert!(is_excluded(Path::new("docs/api.adoc"), &[], &[], true));
assert!(is_excluded(Path::new("notes.textile"), &[], &[], true));
assert!(!is_excluded(Path::new("README.md"), &[], &[], false));
}
#[test]
fn is_excluded_matches_default_lockfiles() {
assert!(is_excluded(Path::new("pnpm-lock.yaml"), &[], &[], true));
assert!(is_excluded(Path::new("package-lock.json"), &[], &[], true));
assert!(is_excluded(Path::new("yarn.lock"), &[], &[], true));
assert!(is_excluded(Path::new("Cargo.lock"), &[], &[], true));
assert!(is_excluded(Path::new("go.sum"), &[], &[], true));
assert!(is_excluded(Path::new("poetry.lock"), &[], &[], true));
assert!(!is_excluded(Path::new("pnpm-lock.yaml"), &[], &[], false));
}
#[test]
fn is_excluded_matches_nested_lockfiles() {
assert!(is_excluded(
Path::new("apps/web/pnpm-lock.yaml"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("packages/ui/package-lock.json"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("services/api/go.sum"),
&[],
&[],
true
));
assert!(is_excluded(Path::new("backend/Cargo.lock"), &[], &[], true));
}
#[test]
fn is_excluded_matches_orm_generated_files() {
assert!(is_excluded(
Path::new("Data/Migrations/20240101_Init.Designer.cs"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("Data/Migrations/AppDbContextModelSnapshot.cs"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("myapp/migrations/0001_initial.py"),
&[],
&[],
true
));
assert!(is_excluded(Path::new("db/schema.rb"), &[], &[], true));
assert!(is_excluded(
Path::new("prisma/migrations/20240101/migration.sql"),
&[],
&[],
true
));
assert!(!is_excluded(
Path::new("src/Models/User.cs"),
&[],
&[],
true
));
}
#[test]
fn is_excluded_matches_default_tooling_dirs() {
assert!(is_excluded(
Path::new(".claude/settings.json"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new(".cursor/rules/my-rule"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new(".idea/workspace.xml"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new(".vscode/settings.json"),
&[],
&[],
true
));
assert!(!is_excluded(
Path::new(".claude/settings.json"),
&[],
&[],
false
));
}
#[test]
fn is_excluded_by_user_extension_simple() {
let exts = vec!["jar".to_string()];
assert!(is_excluded(Path::new("lib/commons.jar"), &[], &exts, false));
assert!(is_excluded(Path::new("deps/app.JAR"), &[], &exts, false));
assert!(!is_excluded(Path::new("src/main.rs"), &[], &exts, false));
}
#[test]
fn is_excluded_by_user_extension_compound() {
let exts = vec!["min.js".to_string()];
assert!(is_excluded(
Path::new("dist/bundle.min.js"),
&[],
&exts,
false
));
assert!(!is_excluded(Path::new("src/app.js"), &[], &exts, false));
}
#[test]
fn is_excluded_extension_leading_dot_normalised() {
let exts = vec![".jar".to_string()];
assert!(is_excluded(Path::new("lib/foo.jar"), &[], &exts, false));
}
#[test]
fn is_excluded_file_without_extension_not_excluded() {
let exts = vec!["jar".to_string()];
assert!(!is_excluded(Path::new("Makefile"), &[], &exts, false));
assert!(!is_excluded(Path::new("Dockerfile"), &[], &exts, false));
assert!(!is_excluded(Path::new("LICENSE"), &[], &exts, false));
}
#[test]
fn is_excluded_dotted_directory_not_confused_with_extension() {
let exts = vec!["2".to_string()];
assert!(!is_excluded(
Path::new("src/v1.2/main.rs"),
&[],
&exts,
false
));
assert!(is_excluded(Path::new("src/v1.2"), &[], &exts, false));
}
#[test]
fn is_excluded_extension_independent_of_defaults() {
let exts = vec!["jar".to_string()];
assert!(is_excluded(Path::new("lib/foo.jar"), &[], &exts, false));
assert!(is_excluded(Path::new("README.md"), &[], &exts, true));
}
#[test]
fn is_excluded_matches_generated_directories() {
assert!(is_excluded(
Path::new("node_modules/lodash/index.js"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("vendor/github.com/foo/bar.go"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("src/__pycache__/utils.cpython-311.pyc"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("myapp.egg-info/PKG-INFO"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("target/debug/build/out/main.rs"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new(".next/server/pages/index.js"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new(".nuxt/components.d.ts"),
&[],
&[],
true
));
assert!(is_excluded(Path::new("out/Release/chrome"), &[], &[], true));
assert!(is_excluded(
Path::new("src/gen/proto/user.go"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("src/generated/api/client.ts"),
&[],
&[],
true
));
assert!(is_excluded(Path::new(".gradle/caches/foo"), &[], &[], true));
assert!(is_excluded(
Path::new(".mvn/wrapper/maven-wrapper.jar"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("build/outputs/apk/debug.apk"),
&[],
&[],
true
));
assert!(is_excluded(Path::new("proto/user_pb2.py"), &[], &[], true));
assert!(!is_excluded(Path::new("dist/published.js"), &[], &[], true));
assert!(!is_excluded(Path::new("src/main.rs"), &[], &[], true));
assert!(!is_excluded(
Path::new("node_modules/foo/bar.js"),
&[],
&[],
false
));
}
#[test]
fn is_excluded_matches_generated_extensions() {
assert!(is_excluded(Path::new("proto/user.pb.go"), &[], &[], true));
assert!(is_excluded(Path::new("proto/user.pb.h"), &[], &[], true));
assert!(is_excluded(Path::new("proto/user.pb.cc"), &[], &[], true));
assert!(is_excluded(
Path::new("proto/user.pb.swift"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("src/Api/Client.g.cs"),
&[],
&[],
true
));
assert!(is_excluded(
Path::new("src/Api/Client.generated.cs"),
&[],
&[],
true
));
assert!(is_excluded(Path::new("types/index.d.ts"), &[], &[], true));
assert!(is_excluded(Path::new("dist/app.min.js"), &[], &[], true));
assert!(is_excluded(
Path::new("dist/styles.min.css"),
&[],
&[],
true
));
assert!(!is_excluded(Path::new("src/main.rs"), &[], &[], true));
assert!(!is_excluded(Path::new("src/user.go"), &[], &[], true));
assert!(!is_excluded(Path::new("src/client.ts"), &[], &[], true));
assert!(!is_excluded(Path::new("proto/user.pb.go"), &[], &[], false));
}
}