Skip to main content

sniff/
roles_paths.rs

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}