fluidattacks_blends/
language.rs1use std::path::Path;
5
6use tree_sitter::Language as TreeSitterLanguage;
7
8pub use blends_domain::Language;
9
10pub trait LanguageExt {
11 #[must_use]
12 fn from_path(path: &Path) -> Option<Language>;
13 #[must_use]
14 fn tree_sitter(self) -> TreeSitterLanguage;
15 #[cfg(test)]
16 fn node_types(self) -> Option<&'static str>;
17}
18
19impl LanguageExt for Language {
20 fn from_path(path: &Path) -> Option<Language> {
21 path.extension()
22 .and_then(|extension| extension.to_str())
23 .map(str::to_ascii_lowercase)
24 .as_deref()
25 .and_then(Self::from_extension)
26 }
27
28 fn tree_sitter(self) -> TreeSitterLanguage {
29 match self {
30 Self::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
31 Self::Elixir => tree_sitter_elixir::LANGUAGE.into(),
32 Self::Go => tree_sitter_go::LANGUAGE.into(),
33 Self::Hcl => tree_sitter_hcl::LANGUAGE.into(),
34 Self::Java => tree_sitter_java::LANGUAGE.into(),
35 Self::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
36 Self::Json => tree_sitter_json::LANGUAGE.into(),
37 Self::Kotlin => tree_sitter_kotlin_ng::LANGUAGE.into(),
38 Self::Php => tree_sitter_php::LANGUAGE_PHP.into(),
39 Self::Ruby => tree_sitter_ruby::LANGUAGE.into(),
40 Self::Rust => tree_sitter_rust::LANGUAGE.into(),
41 Self::Scala => tree_sitter_scala::LANGUAGE.into(),
42 Self::Swift => tree_sitter_swift::LANGUAGE.into(),
43 Self::TypeScript => tree_sitter_typescript::LANGUAGE_TSX.into(),
44 Self::Python => tree_sitter_python::LANGUAGE.into(),
45 Self::Yaml => tree_sitter_yaml::LANGUAGE.into(),
46 }
47 }
48
49 #[cfg(test)]
50 fn node_types(self) -> Option<&'static str> {
51 match self {
52 Self::CSharp => Some(tree_sitter_c_sharp::NODE_TYPES),
53 Self::Elixir => Some(tree_sitter_elixir::NODE_TYPES),
54 Self::Go => Some(tree_sitter_go::NODE_TYPES),
55 Self::Hcl => Some(tree_sitter_hcl::NODE_TYPES),
56 Self::Java => Some(tree_sitter_java::NODE_TYPES),
57 Self::JavaScript => Some(tree_sitter_javascript::NODE_TYPES),
58 Self::Json => Some(tree_sitter_json::NODE_TYPES),
59 Self::Kotlin => Some(tree_sitter_kotlin_ng::NODE_TYPES),
60 Self::Php => Some(tree_sitter_php::PHP_NODE_TYPES),
61 Self::Ruby => Some(tree_sitter_ruby::NODE_TYPES),
62 Self::Rust => Some(tree_sitter_rust::NODE_TYPES),
63 Self::Scala => Some(tree_sitter_scala::NODE_TYPES),
64 Self::Swift => Some(tree_sitter_swift::NODE_TYPES),
65 Self::TypeScript => Some(tree_sitter_typescript::TSX_NODE_TYPES),
66 Self::Python => Some(tree_sitter_python::NODE_TYPES),
67 Self::Yaml => Some(tree_sitter_yaml::NODE_TYPES),
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::{Language, LanguageExt};
75 use std::path::Path;
76 use test_case::test_case;
77
78 #[test_case("cs" => Some(Language::CSharp))]
79 #[test_case("ex" => Some(Language::Elixir))]
80 #[test_case("exs" => Some(Language::Elixir))]
81 #[test_case("go" => Some(Language::Go))]
82 #[test_case("hcl" => Some(Language::Hcl))]
83 #[test_case("tf" => Some(Language::Hcl))]
84 #[test_case("java" => Some(Language::Java))]
85 #[test_case("js" => Some(Language::JavaScript))]
86 #[test_case("jsx" => Some(Language::JavaScript))]
87 #[test_case("json" => Some(Language::Json))]
88 #[test_case("kt" => Some(Language::Kotlin))]
89 #[test_case("ktm" => Some(Language::Kotlin))]
90 #[test_case("kts" => Some(Language::Kotlin))]
91 #[test_case("php" => Some(Language::Php))]
92 #[test_case("py" => Some(Language::Python))]
93 #[test_case("rb" => Some(Language::Ruby))]
94 #[test_case("rs" => Some(Language::Rust))]
95 #[test_case("scala" => Some(Language::Scala))]
96 #[test_case("swift" => Some(Language::Swift))]
97 #[test_case("ts" => Some(Language::TypeScript))]
98 #[test_case("tsx" => Some(Language::TypeScript))]
99 #[test_case("yaml" => Some(Language::Yaml))]
100 #[test_case("yml" => Some(Language::Yaml))]
101 #[test_case("unknown" => None)]
102 fn detects_by_extension(extension: &str) -> Option<Language> {
103 Language::from_extension(extension)
104 }
105
106 #[test_case("services/user/Api.java" => Some(Language::Java))]
107 #[test_case("integration/TestSuite.JAVA" => Some(Language::Java) ; "uppercase extension")]
108 #[test_case("infra/main.tf" => Some(Language::Hcl))]
109 #[test_case("config/vars.hcl" => Some(Language::Hcl))]
110 #[test_case("lib/app/router.ex" => Some(Language::Elixir))]
111 #[test_case("cmd/main.go" => Some(Language::Go))]
112 #[test_case("test/app/router_test.exs" => Some(Language::Elixir))]
113 #[test_case("Controllers/HouseController.cs" => Some(Language::CSharp))]
114 #[test_case("Models/User.CS" => Some(Language::CSharp) ; "uppercase cs extension")]
115 #[test_case("src/app/index.js" => Some(Language::JavaScript))]
116 #[test_case("src/app/index.JS" => Some(Language::JavaScript) ; "uppercase js extension")]
117 #[test_case("src/app/component.jsx" => Some(Language::JavaScript))]
118 #[test_case("src/app/app.ts" => Some(Language::TypeScript))]
119 #[test_case("src/app/component.tsx" => Some(Language::TypeScript))]
120 #[test_case("src/views/Home.swift" => Some(Language::Swift))]
121 #[test_case("pkg/module.py" => Some(Language::Python))]
122 #[test_case("scripts/build.PY" => Some(Language::Python) ; "uppercase py extension")]
123 #[test_case("config/settings.json" => Some(Language::Json))]
124 #[test_case("app/models/user.rb" => Some(Language::Ruby))]
125 #[test_case("src/main/scala/App.scala" => Some(Language::Scala))]
126 #[test_case("app/Main.kt" => Some(Language::Kotlin))]
127 #[test_case("build.gradle.kts" => Some(Language::Kotlin))]
128 #[test_case("deploy/values.yaml" => Some(Language::Yaml))]
129 #[test_case("ci/pipeline.yml" => Some(Language::Yaml))]
130 #[test_case("deploy/values.YAML" => Some(Language::Yaml) ; "uppercase yaml extension")]
131 #[test_case("src/main.rs" => Some(Language::Rust))]
132 #[test_case("src/unknown/randomfile.unknown" => None)]
133 #[test_case("folder/with/no_extension" => None)]
134 fn detects_language_by_path(path: &str) -> Option<Language> {
135 Language::from_path(Path::new(path))
136 }
137}