Skip to main content

normalize_languages/
devicetree.rs

1//! Device Tree source file 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/// Device Tree language support.
9pub struct DeviceTree;
10
11impl Language for DeviceTree {
12    fn name(&self) -> &'static str {
13        "DeviceTree"
14    }
15    fn extensions(&self) -> &'static [&'static str] {
16        &["dts", "dtsi"]
17    }
18    fn grammar_name(&self) -> &'static str {
19        "devicetree"
20    }
21
22    fn has_symbols(&self) -> bool {
23        true
24    }
25
26    fn container_kinds(&self) -> &'static [&'static str] {
27        &["node"]
28    }
29
30    fn function_kinds(&self) -> &'static [&'static str] {
31        &[]
32    }
33    fn type_kinds(&self) -> &'static [&'static str] {
34        &[]
35    }
36
37    fn import_kinds(&self) -> &'static [&'static str] {
38        &["preproc_include"]
39    }
40
41    fn public_symbol_kinds(&self) -> &'static [&'static str] {
42        &["node", "property"]
43    }
44
45    fn visibility_mechanism(&self) -> VisibilityMechanism {
46        VisibilityMechanism::AllPublic
47    }
48
49    fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
50        let kind = match node.kind() {
51            "node" => SymbolKind::Module,
52            "property" => SymbolKind::Variable,
53            _ => return Vec::new(),
54        };
55
56        if let Some(name) = self.node_name(node, content) {
57            return vec![Export {
58                name: name.to_string(),
59                kind,
60                line: node.start_position().row + 1,
61            }];
62        }
63        Vec::new()
64    }
65
66    fn scope_creating_kinds(&self) -> &'static [&'static str] {
67        &["node"]
68    }
69
70    fn control_flow_kinds(&self) -> &'static [&'static str] {
71        &[]
72    }
73    fn complexity_nodes(&self) -> &'static [&'static str] {
74        &[]
75    }
76    fn nesting_nodes(&self) -> &'static [&'static str] {
77        &["node"]
78    }
79
80    fn signature_suffix(&self) -> &'static str {
81        ""
82    }
83
84    fn extract_function(
85        &self,
86        _node: &Node,
87        _content: &str,
88        _in_container: bool,
89    ) -> Option<Symbol> {
90        None
91    }
92
93    fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
94        if node.kind() != "node" {
95            return None;
96        }
97
98        let name = self.node_name(node, content)?;
99        let text = &content[node.byte_range()];
100        let first_line = text.lines().next().unwrap_or(text);
101
102        Some(Symbol {
103            name: name.to_string(),
104            kind: SymbolKind::Module,
105            signature: first_line.trim().to_string(),
106            docstring: None,
107            attributes: Vec::new(),
108            start_line: node.start_position().row + 1,
109            end_line: node.end_position().row + 1,
110            visibility: Visibility::Public,
111            children: Vec::new(),
112            is_interface_impl: false,
113            implements: Vec::new(),
114        })
115    }
116
117    fn extract_type(&self, _node: &Node, _content: &str) -> Option<Symbol> {
118        None
119    }
120    fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
121        None
122    }
123
124    fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
125        Vec::new()
126    }
127
128    fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
129        if node.kind() != "preproc_include" {
130            return Vec::new();
131        }
132
133        let text = &content[node.byte_range()];
134        let module = text
135            .split('"')
136            .nth(1)
137            .or_else(|| text.split('<').nth(1).and_then(|s| s.split('>').next()))
138            .map(|s| s.to_string());
139
140        if let Some(module) = module {
141            return vec![Import {
142                module,
143                names: Vec::new(),
144                alias: None,
145                is_wildcard: false,
146                is_relative: text.contains('"'),
147                line: node.start_position().row + 1,
148            }];
149        }
150        Vec::new()
151    }
152
153    fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
154        // Device Tree: /include/ "file.dtsi"
155        format!("/include/ \"{}\"", import.module)
156    }
157
158    fn is_public(&self, _node: &Node, _content: &str) -> bool {
159        true
160    }
161    fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
162        Visibility::Public
163    }
164
165    fn is_test_symbol(&self, _symbol: &crate::Symbol) -> bool {
166        false
167    }
168
169    fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
170        None
171    }
172
173    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
174        node.child_by_field_name("body")
175    }
176
177    fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
178        false
179    }
180
181    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
182        node.child_by_field_name("name")
183            .map(|n| &content[n.byte_range()])
184    }
185
186    fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
187        let ext = path.extension()?.to_str()?;
188        if !["dts", "dtsi"].contains(&ext) {
189            return None;
190        }
191        let stem = path.file_stem()?.to_str()?;
192        Some(stem.to_string())
193    }
194
195    fn module_name_to_paths(&self, module: &str) -> Vec<String> {
196        vec![format!("{}.dts", module), format!("{}.dtsi", module)]
197    }
198
199    fn lang_key(&self) -> &'static str {
200        "devicetree"
201    }
202
203    fn is_stdlib_import(&self, _: &str, _: &Path) -> bool {
204        false
205    }
206    fn find_stdlib(&self, _: &Path) -> Option<PathBuf> {
207        None
208    }
209    fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
210        None
211    }
212    fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
213        None
214    }
215    fn get_version(&self, _: &Path) -> Option<String> {
216        None
217    }
218    fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
219        None
220    }
221    fn indexable_extensions(&self) -> &'static [&'static str] {
222        &["dts", "dtsi"]
223    }
224    fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
225        Vec::new()
226    }
227
228    fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
229        use crate::traits::{has_extension, skip_dotfiles};
230        if skip_dotfiles(name) {
231            return true;
232        }
233        !is_dir && !has_extension(name, self.indexable_extensions())
234    }
235
236    fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
237        Vec::new()
238    }
239
240    fn package_module_name(&self, entry_name: &str) -> String {
241        entry_name
242            .strip_suffix(".dts")
243            .or_else(|| entry_name.strip_suffix(".dtsi"))
244            .unwrap_or(entry_name)
245            .to_string()
246    }
247
248    fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
249        if path.is_file() {
250            Some(path.to_path_buf())
251        } else {
252            None
253        }
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use crate::validate_unused_kinds_audit;
261
262    #[test]
263    fn unused_node_kinds_audit() {
264        #[rustfmt::skip]
265        let documented_unused: &[&str] = &[
266            // Preprocessor
267            "preproc_if", "preproc_ifdef", "preproc_else", "preproc_elif",
268            "preproc_elifdef", "preproc_function_def",
269            // Expressions
270            "unary_expression", "binary_expression", "conditional_expression",
271            "parenthesized_expression", "call_expression",
272            // Other
273            "identifier", "omit_if_no_ref",
274        ];
275        validate_unused_kinds_audit(&DeviceTree, documented_unused)
276            .expect("DeviceTree unused node kinds audit failed");
277    }
278}