1pub fn normalize_path(path: &str) -> String {
2 path.replace('\\', "/").to_lowercase()
3}
4
5pub fn file_name(path: &str) -> &str {
6 path.rsplit('/').next().unwrap_or(path)
7}
8
9pub fn starts_with_segment(normalized: &str, segment: &str) -> bool {
10 normalized.split('/').next() == Some(segment)
11}
12
13pub fn contains_any(path: &str, needles: &[&str]) -> bool {
14 needles.iter().any(|needle| path.contains(needle))
15}
16
17pub fn is_example_path(normalized: &str) -> bool {
18 contains_any(normalized, &["/examples/", "/example/"])
19 || starts_with_segment(normalized, "examples")
20}
21
22pub fn is_fixture_path(normalized: &str) -> bool {
23 contains_any(normalized, &["/fixtures/", "/gold_fixtures/", "/fixture/"])
24 || starts_with_segment(normalized, "fixtures")
25 || starts_with_segment(normalized, "gold_fixtures")
26}
27
28pub fn is_script_path(normalized: &str) -> bool {
29 contains_any(normalized, &["/scripts/", "/script/"])
30 || starts_with_segment(normalized, "scripts")
31}
32
33pub fn is_test_path(normalized: &str, name: &str) -> bool {
34 contains_any(
35 normalized,
36 &[
37 "/tests/",
38 "/commontest/",
39 "/androidtest/",
40 "/iostest/",
41 "/jvmtest/",
42 ],
43 ) || starts_with_segment(normalized, "tests")
44 || name.ends_with("_test.rs")
45 || name.ends_with("Test.kt")
46 || name.ends_with("Tests.kt")
47 || name.ends_with("Test.java")
48 || name.ends_with("Tests.java")
49 || name.ends_with(".test.ts")
50 || name.ends_with(".test.js")
51 || name.ends_with(".spec.ts")
52 || name.ends_with(".spec.js")
53 || name.ends_with("_spec.rs")
54}
55
56pub fn is_docs_path(normalized: &str) -> bool {
57 contains_any(normalized, &["/docs/", "/doc/"]) || starts_with_segment(normalized, "docs")
58}
59
60pub fn is_generated_path(normalized: &str, name: &str) -> bool {
61 contains_any(normalized, &["/generated/", "/gen/"])
62 || starts_with_segment(normalized, "generated")
63 || name.contains(".generated.")
64 || name.starts_with("generated.")
65 || name == "generated"
66 || name.ends_with(".pyi")
67}
68
69pub fn is_entrypoint_path(normalized: &str, name: &str) -> bool {
70 matches!(
71 name,
72 "main.rs" | "main.py" | "main.ts" | "main.tsx" | "main.jsx" | "main.js" | "main.go"
73 ) || normalized.ends_with("/bin/main.rs")
74 || normalized.ends_with("/bin/main.py")
75 || normalized.ends_with("/bin/main.ts")
76 || normalized.ends_with("/bin/main.tsx")
77 || normalized.ends_with("/bin/main.jsx")
78 || normalized.ends_with("/bin/main.js")
79 || normalized.ends_with("/bin/main.go")
80 || normalized.ends_with("/__main__.py")
81 || normalized.ends_with("/__main__.rs")
82 || normalized.ends_with("/__main__.ts")
83 || normalized.ends_with("/__main__.tsx")
84 || normalized.ends_with("/__main__.jsx")
85 || normalized.ends_with("/__main__.js")
86 || is_serverless_function_entrypoint(normalized, name)
87}
88
89pub fn is_serverless_function_entrypoint(normalized: &str, name: &str) -> bool {
90 matches!(
91 name,
92 "index.ts"
93 | "index.tsx"
94 | "index.js"
95 | "index.jsx"
96 | "index.py"
97 | "index.rs"
98 | "index.go"
99 | "index.kt"
100 ) && contains_any(
101 normalized,
102 &[
103 "supabase/functions/",
104 "netlify/functions/",
105 "vercel/functions/",
106 ],
107 )
108}
109
110pub fn is_cli_path(name: &str) -> bool {
111 matches!(name, "cli.rs" | "cli.py" | "cli.ts" | "cli.js" | "cli.go")
112 || name.ends_with("_cli.rs")
113 || name.ends_with("_cli.py")
114 || name.ends_with("_cli.ts")
115 || name.ends_with("_cli.js")
116 || name.ends_with("_cli.go")
117}
118
119pub fn is_adapter_integration_path(normalized: &str, name: &str) -> bool {
120 contains_any(
121 normalized,
122 &[
123 "/adapter/",
124 "/adapters/",
125 "/integration/",
126 "/integrations/",
127 "/bridge/",
128 "/bridges/",
129 "/gateway/",
130 "/gateways/",
131 "/client/",
132 "/clients/",
133 "/transport/",
134 "/http/",
135 "/api/",
136 "/grpc/",
137 "/rpc/",
138 "/connector/",
139 "/connectors/",
140 ],
141 ) || contains_any(
142 name,
143 &[
144 "adapter",
145 "bridge",
146 "gateway",
147 "client",
148 "transport",
149 "integration",
150 "http",
151 "api",
152 "grpc",
153 "rpc",
154 "connector",
155 ],
156 )
157}
158
159#[cfg(test)]
160mod tests {
161 use super::{is_entrypoint_path, is_generated_path};
162
163 #[test]
164 fn reacts_main_entrypoints_are_treated_as_entrypoints() {
165 assert!(is_entrypoint_path("ui/src/main.tsx", "main.tsx"));
166 assert!(is_entrypoint_path("ui/src/main.jsx", "main.jsx"));
167 assert!(!is_entrypoint_path("ui/src/index.tsx", "index.tsx"));
168 }
169
170 #[test]
171 fn python_stub_files_are_treated_as_generated() {
172 assert!(is_generated_path(
173 "src/typedpkg/__init__.pyi",
174 "__init__.pyi"
175 ));
176 assert!(is_generated_path("src/typedpkg/api.pyi", "api.pyi"));
177 }
178}