Skip to main content

normalize_languages/
asm.rs

1//! Assembly language support.
2
3use crate::external_packages::ResolvedPackage;
4use crate::{Export, Import, Language, Symbol, SymbolKind, Visibility, VisibilityMechanism};
5use std::path::{Path, PathBuf};
6use tree_sitter::Node;
7
8/// Assembly language support.
9pub struct Asm;
10
11impl Language for Asm {
12    fn name(&self) -> &'static str {
13        "Assembly"
14    }
15    fn extensions(&self) -> &'static [&'static str] {
16        &["asm", "s", "S"]
17    }
18    fn grammar_name(&self) -> &'static str {
19        "asm"
20    }
21
22    fn has_symbols(&self) -> bool {
23        true
24    }
25
26    fn container_kinds(&self) -> &'static [&'static str] {
27        &[]
28    }
29
30    fn function_kinds(&self) -> &'static [&'static str] {
31        &["label"]
32    }
33
34    fn type_kinds(&self) -> &'static [&'static str] {
35        &[]
36    }
37
38    fn import_kinds(&self) -> &'static [&'static str] {
39        &[] // asm grammar doesn't have preprocessor includes
40    }
41
42    fn public_symbol_kinds(&self) -> &'static [&'static str] {
43        &["label"]
44    }
45
46    fn visibility_mechanism(&self) -> VisibilityMechanism {
47        VisibilityMechanism::AllPublic
48    }
49
50    fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
51        if node.kind() != "label" {
52            return Vec::new();
53        }
54
55        if let Some(name) = self.node_name(node, content) {
56            return vec![Export {
57                name: name.to_string(),
58                kind: SymbolKind::Function,
59                line: node.start_position().row + 1,
60            }];
61        }
62
63        Vec::new()
64    }
65
66    fn scope_creating_kinds(&self) -> &'static [&'static str] {
67        &["label"]
68    }
69
70    fn control_flow_kinds(&self) -> &'static [&'static str] {
71        &[] // Assembly control flow is via jumps
72    }
73
74    fn complexity_nodes(&self) -> &'static [&'static str] {
75        &[]
76    }
77
78    fn nesting_nodes(&self) -> &'static [&'static str] {
79        &[]
80    }
81
82    fn signature_suffix(&self) -> &'static str {
83        ""
84    }
85
86    fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
87        if node.kind() != "label" {
88            return None;
89        }
90
91        let name = self.node_name(node, content)?;
92        let text = &content[node.byte_range()];
93
94        Some(Symbol {
95            name: name.to_string(),
96            kind: SymbolKind::Function,
97            signature: text.trim().to_string(),
98            docstring: None,
99            attributes: Vec::new(),
100            start_line: node.start_position().row + 1,
101            end_line: node.end_position().row + 1,
102            visibility: Visibility::Public,
103            children: Vec::new(),
104            is_interface_impl: false,
105            implements: Vec::new(),
106        })
107    }
108
109    fn extract_container(&self, _node: &Node, _content: &str) -> Option<Symbol> {
110        None
111    }
112    fn extract_type(&self, _node: &Node, _content: &str) -> Option<Symbol> {
113        None
114    }
115    fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
116        None
117    }
118
119    fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
120        Vec::new()
121    }
122
123    fn extract_imports(&self, _node: &Node, _content: &str) -> Vec<Import> {
124        Vec::new() // asm grammar doesn't have imports
125    }
126
127    fn format_import(&self, _import: &Import, _names: Option<&[&str]>) -> String {
128        // Assembly has no standard import mechanism
129        String::new()
130    }
131
132    fn is_public(&self, _node: &Node, _content: &str) -> bool {
133        true
134    }
135    fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
136        Visibility::Public
137    }
138
139    fn is_test_symbol(&self, _symbol: &crate::Symbol) -> bool {
140        false
141    }
142
143    fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
144        None
145    }
146
147    fn container_body<'a>(&self, _node: &'a Node<'a>) -> Option<Node<'a>> {
148        None
149    }
150    fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
151        false
152    }
153
154    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
155        // Labels end with ':'
156        let text = &content[node.byte_range()];
157        let name = text.trim().trim_end_matches(':');
158        if !name.is_empty() { Some(name) } else { None }
159    }
160
161    fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
162        let ext = path.extension()?.to_str()?;
163        if !["asm", "s", "S"].contains(&ext) {
164            return None;
165        }
166        let stem = path.file_stem()?.to_str()?;
167        Some(stem.to_string())
168    }
169
170    fn module_name_to_paths(&self, module: &str) -> Vec<String> {
171        vec![
172            format!("{}.asm", module),
173            format!("{}.s", module),
174            format!("{}.S", module),
175        ]
176    }
177
178    fn lang_key(&self) -> &'static str {
179        "asm"
180    }
181
182    fn is_stdlib_import(&self, _: &str, _: &Path) -> bool {
183        false
184    }
185    fn find_stdlib(&self, _project_root: &Path) -> Option<PathBuf> {
186        None
187    }
188    fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
189        None
190    }
191    fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
192        None
193    }
194    fn get_version(&self, _: &Path) -> Option<String> {
195        None
196    }
197    fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
198        None
199    }
200    fn indexable_extensions(&self) -> &'static [&'static str] {
201        &["asm", "s", "S"]
202    }
203    fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
204        Vec::new()
205    }
206
207    fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
208        use crate::traits::{has_extension, skip_dotfiles};
209        if skip_dotfiles(name) {
210            return true;
211        }
212        !is_dir && !has_extension(name, self.indexable_extensions())
213    }
214
215    fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
216        Vec::new()
217    }
218
219    fn package_module_name(&self, entry_name: &str) -> String {
220        entry_name
221            .strip_suffix(".asm")
222            .or_else(|| entry_name.strip_suffix(".s"))
223            .or_else(|| entry_name.strip_suffix(".S"))
224            .unwrap_or(entry_name)
225            .to_string()
226    }
227
228    fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
229        if path.is_file() {
230            Some(path.to_path_buf())
231        } else {
232            None
233        }
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use crate::validate_unused_kinds_audit;
241
242    #[test]
243    fn unused_node_kinds_audit() {
244        #[rustfmt::skip]
245        let documented_unused: &[&str] = &[
246            // Asm instructions are too granular for symbol extraction
247            "instruction",
248            // Comments
249            "block_comment",
250        ];
251        validate_unused_kinds_audit(&Asm, documented_unused)
252            .expect("Assembly unused node kinds audit failed");
253    }
254}