1use std::path::Path;
2
3pub fn score_file(path: &Path, base_path: &Path) -> u32 {
14 let file_name = path
15 .file_name()
16 .map(|f| f.to_string_lossy().to_lowercase())
17 .unwrap_or_default();
18
19 let relative = path.strip_prefix(base_path).unwrap_or(path);
20 let depth = relative.components().count().saturating_sub(1); let path_str = relative.to_string_lossy().to_lowercase();
23
24 if is_fixture(&path_str) {
26 5
27 } else if is_test(&path_str, &file_name) {
28 30
29 } else if is_readme(&file_name) {
30 100
31 } else if is_entry_point(&file_name) {
32 90
33 } else if is_config(&file_name) {
34 80
35 } else {
36 let score = 70u32.saturating_sub((depth as u32) * 10);
38 score.max(10)
39 }
40}
41
42fn is_readme(file_name: &str) -> bool {
43 file_name.starts_with("readme")
44}
45
46fn is_entry_point(file_name: &str) -> bool {
47 let stem = file_name.split('.').next().unwrap_or("");
48 matches!(stem, "main" | "index" | "app" | "lib" | "mod")
49}
50
51fn is_config(file_name: &str) -> bool {
52 let stem = file_name.split('.').next().unwrap_or("");
53 matches!(
54 stem,
55 "config"
56 | "settings"
57 | "package"
58 | "cargo"
59 | "tsconfig"
60 | "webpack"
61 | "vite"
62 | "eslint"
63 | "prettier"
64 | "jest"
65 | "pyproject"
66 | "setup"
67 | "makefile"
68 | "dockerfile"
69 | "docker-compose"
70 | "go"
71 ) || file_name.ends_with(".toml")
72 || file_name.ends_with(".yaml")
73 || file_name.ends_with(".yml")
74 || file_name.ends_with(".json") && !file_name.contains("test")
75}
76
77fn is_test(path_str: &str, file_name: &str) -> bool {
78 path_str.contains("test")
79 || path_str.contains("spec")
80 || file_name.contains("test")
81 || file_name.contains("spec")
82}
83
84fn is_fixture(path_str: &str) -> bool {
85 path_str.contains("fixture")
86 || path_str.contains("testdata")
87 || path_str.contains("test_data")
88 || path_str.contains("__snapshots__")
89 || path_str.contains("generated")
90 || path_str.contains("vendor")
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use std::path::PathBuf;
97
98 fn score(path: &str) -> u32 {
99 score_file(Path::new(path), Path::new("/project"))
100 }
101
102 #[test]
103 fn test_readme_highest() {
104 assert_eq!(score("/project/README.md"), 100);
105 assert_eq!(score("/project/readme.txt"), 100);
106 }
107
108 #[test]
109 fn test_entry_points() {
110 assert_eq!(score("/project/src/main.rs"), 90);
111 assert_eq!(score("/project/src/index.ts"), 90);
112 assert_eq!(score("/project/src/lib.rs"), 90);
113 }
114
115 #[test]
116 fn test_config_files() {
117 assert_eq!(score("/project/Cargo.toml"), 80);
118 assert_eq!(score("/project/package.json"), 80);
119 }
120
121 #[test]
122 fn test_source_with_depth_penalty() {
123 assert_eq!(score("/project/foo.rs"), 70);
125 assert_eq!(score("/project/src/foo.rs"), 60);
127 assert_eq!(score("/project/src/utils/foo.rs"), 50);
129 assert_eq!(score("/project/a/b/c/d/e/f/foo.rs"), 10);
131 }
132
133 #[test]
134 fn test_tests_scored_low() {
135 assert_eq!(score("/project/tests/unit_test.rs"), 30);
136 assert_eq!(score("/project/src/foo_test.go"), 30);
137 }
138
139 #[test]
140 fn test_fixtures_lowest() {
141 assert_eq!(score("/project/tests/fixtures/data.json"), 5);
142 assert_eq!(score("/project/testdata/input.txt"), 5);
143 }
144
145 #[test]
146 fn test_readme_in_subdirectory() {
147 assert_eq!(score("/project/tests/fixtures/README.md"), 5);
149 }
150
151 #[test]
152 fn test_sorting_order() {
153 let base = PathBuf::from("/project");
154 let mut files = [
155 PathBuf::from("/project/tests/fixture/data.json"),
156 PathBuf::from("/project/src/utils.rs"),
157 PathBuf::from("/project/README.md"),
158 PathBuf::from("/project/src/main.rs"),
159 PathBuf::from("/project/Cargo.toml"),
160 PathBuf::from("/project/tests/test_foo.rs"),
161 ];
162
163 files.sort_by(|a, b| {
164 let sa = score_file(a, &base);
165 let sb = score_file(b, &base);
166 sb.cmp(&sa).then_with(|| a.cmp(b))
167 });
168
169 let names: Vec<&str> = files
170 .iter()
171 .map(|p| p.file_name().unwrap().to_str().unwrap())
172 .collect();
173 assert_eq!(names[0], "README.md");
174 assert_eq!(names[1], "main.rs");
175 assert_eq!(names[2], "Cargo.toml");
176 }
177}