1use std::borrow::Cow;
2use std::path::Path;
3
4use tree_sitter::Language;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Lang {
9 Swift,
10 TypeScript,
11 Tsx,
12 JavaScript,
14 Python,
15}
16
17impl Lang {
18 pub fn from_path(path: &str) -> Option<Lang> {
19 let ext = Path::new(path).extension()?.to_str()?;
20 match ext {
21 "swift" => Some(Lang::Swift),
22 "ts" | "mts" | "cts" => Some(Lang::TypeScript),
23 "tsx" => Some(Lang::Tsx),
24 "js" | "mjs" | "cjs" | "jsx" => Some(Lang::JavaScript),
25 "py" | "pyi" => Some(Lang::Python),
26 _ => None,
27 }
28 }
29
30 pub fn name(self) -> &'static str {
31 match self {
32 Lang::Swift => "Swift",
33 Lang::TypeScript => "TypeScript",
34 Lang::Tsx => "TSX",
35 Lang::JavaScript => "JavaScript",
36 Lang::Python => "Python",
37 }
38 }
39
40 pub fn grammar(self) -> Language {
41 match self {
42 Lang::Swift => tree_sitter_swift::LANGUAGE.into(),
43 Lang::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
44 Lang::Tsx | Lang::JavaScript => tree_sitter_typescript::LANGUAGE_TSX.into(),
45 Lang::Python => tree_sitter_python::LANGUAGE.into(),
46 }
47 }
48
49 pub fn is_comment(self, kind: &str) -> bool {
50 match self {
51 Lang::Swift => matches!(kind, "comment" | "multiline_comment"),
52 Lang::TypeScript | Lang::Tsx | Lang::JavaScript => {
53 matches!(kind, "comment" | "html_comment")
54 }
55 Lang::Python => kind == "comment",
56 }
57 }
58}
59
60pub fn parsable(lang: Lang, src: &str) -> Cow<'_, str> {
73 if lang != Lang::Swift {
74 return Cow::Borrowed(src);
75 }
76 let mut out = src.as_bytes().to_vec();
77 let mut blank = |range: std::ops::Range<usize>, with: &[u8]| {
78 out[range.clone()].copy_from_slice(&with[..range.len()]);
79 };
80
81 for (at, _) in src.match_indices("()") {
82 let before = src[..at].trim_end();
83 let after = src[at + 2..].trim_start();
84 let value_position = matches!(before.chars().last(), Some('(' | ',' | ':' | '=' | '?'))
85 && !before.ends_with("->")
86 && !["->", "throws", "async"]
87 .iter()
88 .any(|word| after.starts_with(word));
89 if value_position {
90 blank(at..at + 2, b"[]");
91 }
92 }
93 for (at, _) in src.match_indices("await ") {
94 let before = src[..at].trim_end();
95 let opens_condition = ["if", "while", "guard"].iter().any(|keyword| {
96 before.ends_with(keyword)
97 && !before[..before.len() - keyword.len()]
98 .ends_with(|c: char| c.is_alphanumeric() || c == '_')
99 });
100 if opens_condition {
101 blank(at..at + 5, b" ");
102 }
103 }
104 for (at, _) in src.match_indices("nonisolated(unsafe)") {
105 blank(at + 11..at + 19, b" ");
106 }
107
108 if out == src.as_bytes() {
109 return Cow::Borrowed(src);
110 }
111 Cow::Owned(String::from_utf8(out).unwrap_or_else(|_| src.to_string()))
113}