bash.rs
1 use crate::languages::LanguageSupport; 2 use std::sync::OnceLock; 3 use tracing::error; 4 use tree_sitter::{Language, Query}; 5 6 pub struct Bash; 7 8 static REFERENCE_QUERY: OnceLock<Query> = OnceLock::new(); 9 static BINDING_QUERY: OnceLock<Query> = OnceLock::new(); 10 static IMPORT_QUERY: OnceLock<Query> = OnceLock::new(); 11 static COMPLETION_QUERY: OnceLock<Query> = OnceLock::new(); 12 static REASSIGNMENT_QUERY: OnceLock<Query> = OnceLock::new(); 13 static IDENTIFIER_QUERY: OnceLock<Query> = OnceLock::new(); 14 static EXPORT_QUERY: OnceLock<Query> = OnceLock::new(); 15 static ASSIGNMENT_QUERY: OnceLock<Query> = OnceLock::new(); 16 static DESTRUCTURE_QUERY: OnceLock<Query> = OnceLock::new(); 17 static SCOPE_QUERY: OnceLock<Query> = OnceLock::new(); 18 19 fn compile_query(grammar: &Language, source: &str, query_name: &str) -> Query { 20 match Query::new(grammar, source) { 21 Ok(query) => query, 22 Err(e) => { 23 error!( 24 language = "bash", 25 query = query_name, 26 error = %e, 27 "Failed to compile query, failing fast" 28 ); 29 panic!("Failed to compile query '{}': {}", query_name, e) 30 } 31 } 32 } 33 34 impl LanguageSupport for Bash { 35 fn id(&self) -> &'static str { 36 "bash" 37 } 38 39 fn extensions(&self) -> &'static [&'static str] { 40 &[ 41 "sh", 42 "bash", 43 "zsh", 44 "zshrc", 45 "bashrc", 46 "bash_profile", 47 "zprofile", 48 ] 49 } 50 51 fn language_ids(&self) -> &'static [&'static str] { 52 &["shellscript", "bash", "sh", "zsh"] 53 } 54 55 fn grammar(&self) -> Language { 56 tree_sitter_bash::LANGUAGE.into() 57 } 58 59 fn reference_query(&self) -> &Query { 60 REFERENCE_QUERY.get_or_init(|| { 61 compile_query( 62 &self.grammar(), 63 include_str!("../../queries/bash/references.scm"), 64 "references", 65 ) 66 }) 67 } 68 69 fn binding_query(&self) -> Option<&Query> { 70 Some(BINDING_QUERY.get_or_init(|| { 71 compile_query( 72 &self.grammar(), 73 include_str!("../../queries/bash/bindings.scm"), 74 "bindings", 75 ) 76 })) 77 } 78 79 fn import_query(&self) -> Option<&Query> { 80 Some(IMPORT_QUERY.get_or_init(|| { 81 compile_query( 82 &self.grammar(), 83 include_str!("../../queries/bash/imports.scm"), 84 "imports", 85 ) 86 })) 87 } 88 89 fn completion_query(&self) -> Option<&Query> { 90 Some(COMPLETION_QUERY.get_or_init(|| { 91 compile_query( 92 &self.grammar(), 93 include_str!("../../queries/bash/completion.scm"), 94 "completion", 95 ) 96 })) 97 } 98 99 fn reassignment_query(&self) -> Option<&Query> { 100 Some(REASSIGNMENT_QUERY.get_or_init(|| { 101 compile_query( 102 &self.grammar(), 103 include_str!("../../queries/bash/reassignments.scm"), 104 "reassignments", 105 ) 106 })) 107 } 108 109 fn identifier_query(&self) -> Option<&Query> { 110 Some(IDENTIFIER_QUERY.get_or_init(|| { 111 compile_query( 112 &self.grammar(), 113 include_str!("../../queries/bash/identifiers.scm"), 114 "identifiers", 115 ) 116 })) 117 } 118 119 fn export_query(&self) -> Option<&Query> { 120 Some(EXPORT_QUERY.get_or_init(|| { 121 compile_query( 122 &self.grammar(), 123 include_str!("../../queries/bash/exports.scm"), 124 "exports", 125 ) 126 })) 127 } 128 129 fn assignment_query(&self) -> Option<&Query> { 130 Some(ASSIGNMENT_QUERY.get_or_init(|| { 131 compile_query( 132 &self.grammar(), 133 include_str!("../../queries/bash/assignments.scm"), 134 "assignments", 135 ) 136 })) 137 } 138 139 fn destructure_query(&self) -> Option<&Query> { 140 Some(DESTRUCTURE_QUERY.get_or_init(|| { 141 compile_query( 142 &self.grammar(), 143 include_str!("../../queries/bash/destructures.scm"), 144 "destructures", 145 ) 146 })) 147 } 148 149 fn scope_query(&self) -> Option<&Query> { 150 Some(SCOPE_QUERY.get_or_init(|| { 151 compile_query( 152 &self.grammar(), 153 include_str!("../../queries/bash/scopes.scm"), 154 "scopes", 155 ) 156 })) 157 } 158 159 fn completion_trigger_characters(&self) -> &'static [&'static str] { 160 &["$", "${"] 161 } 162 163 fn is_standard_env_object(&self, _name: &str) -> bool { 164 // In bash, all variable expansions ($VAR, ${VAR}) are env var access 165 true 166 } 167 168 fn comment_node_kinds(&self) -> &'static [&'static str] { 169 &["comment"] 170 } 171 172 fn is_scope_node(&self, node: tree_sitter::Node) -> bool { 173 matches!( 174 node.kind(), 175 "function_definition" 176 | "compound_statement" 177 | "subshell" 178 | "for_statement" 179 | "while_statement" 180 | "if_statement" 181 | "case_statement" 182 ) 183 } 184 } 185 186 #[cfg(test)] 187 mod tests { 188 use super::*; 189 190 fn get_bash() -> Bash { 191 Bash 192 } 193 194 #[test] 195 fn test_id() { 196 assert_eq!(get_bash().id(), "bash"); 197 } 198 199 #[test] 200 fn test_extensions() { 201 let exts = get_bash().extensions(); 202 assert!(exts.contains(&"sh")); 203 assert!(exts.contains(&"bash")); 204 assert!(exts.contains(&"zsh")); 205 } 206 207 #[test] 208 fn test_language_ids() { 209 let ids = get_bash().language_ids(); 210 assert!(ids.contains(&"bash")); 211 assert!(ids.contains(&"shellscript")); 212 } 213 214 #[test] 215 fn test_grammar_compiles() { 216 let bash = get_bash(); 217 let _grammar = bash.grammar(); 218 } 219 220 #[test] 221 fn test_reference_query_compiles() { 222 let bash = get_bash(); 223 let _query = bash.reference_query(); 224 } 225 226 #[test] 227 fn test_binding_query_compiles() { 228 let bash = get_bash(); 229 assert!(bash.binding_query().is_some()); 230 } 231 232 #[test] 233 fn test_import_query_compiles() { 234 let bash = get_bash(); 235 assert!(bash.import_query().is_some()); 236 } 237 238 #[test] 239 fn test_completion_query_compiles() { 240 let bash = get_bash(); 241 assert!(bash.completion_query().is_some()); 242 } 243 244 #[test] 245 fn test_reassignment_query_compiles() { 246 let bash = get_bash(); 247 assert!(bash.reassignment_query().is_some()); 248 } 249 250 #[test] 251 fn test_identifier_query_compiles() { 252 let bash = get_bash(); 253 assert!(bash.identifier_query().is_some()); 254 } 255 256 #[test] 257 fn test_export_query_compiles() { 258 let bash = get_bash(); 259 assert!(bash.export_query().is_some()); 260 } 261 262 #[test] 263 fn test_assignment_query_compiles() { 264 let bash = get_bash(); 265 assert!(bash.assignment_query().is_some()); 266 } 267 268 #[test] 269 fn test_scope_query_compiles() { 270 let bash = get_bash(); 271 assert!(bash.scope_query().is_some()); 272 } 273 274 #[test] 275 fn test_destructure_query_compiles() { 276 let bash = get_bash(); 277 assert!(bash.destructure_query().is_some()); 278 } 279 }