1use crate::docstring::extract_preceding_prefix_comments;
4use crate::{Import, Language, LanguageSymbols, Visibility};
5use tree_sitter::Node;
6
7pub struct R;
9
10impl Language for R {
11 fn name(&self) -> &'static str {
12 "R"
13 }
14 fn extensions(&self) -> &'static [&'static str] {
15 &["r", "R", "rmd", "Rmd"]
16 }
17 fn grammar_name(&self) -> &'static str {
18 "r"
19 }
20
21 fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
22 Some(self)
23 }
24
25 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
26 if node.kind() != "call" {
27 return Vec::new();
28 }
29
30 let text = &content[node.byte_range()];
31 if !text.starts_with("library(") && !text.starts_with("require(") {
32 return Vec::new();
33 }
34
35 let inner = text
37 .split('(')
38 .nth(1)
39 .and_then(|s| s.split(')').next())
40 .map(|s| s.trim().trim_matches('"').trim_matches('\'').to_string());
41
42 if let Some(module) = inner {
43 return vec![Import {
44 module,
45 names: Vec::new(),
46 alias: None,
47 is_wildcard: true,
48 is_relative: false,
49 line: node.start_position().row + 1,
50 }];
51 }
52
53 Vec::new()
54 }
55
56 fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
57 format!("library({})", import.module)
59 }
60
61 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
62 if node
63 .child(0)
64 .is_none_or(|n| !content[n.byte_range()].starts_with('.'))
65 {
66 Visibility::Public
67 } else {
68 Visibility::Private
69 }
70 }
71
72 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
73 let name = symbol.name.as_str();
74 match symbol.kind {
75 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
76 crate::SymbolKind::Module => name == "tests" || name == "test",
77 _ => false,
78 }
79 }
80
81 fn test_file_globs(&self) -> &'static [&'static str] {
82 &["**/test-*.R", "**/test_*.R"]
83 }
84
85 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
86 extract_preceding_prefix_comments(node, content, "#'")
88 }
89
90 fn node_name<'a>(&self, _node: &Node, _content: &'a str) -> Option<&'a str> {
91 None
92 }
93}
94
95impl LanguageSymbols for R {}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::validate_unused_kinds_audit;
101
102 #[test]
103 fn unused_node_kinds_audit() {
104 #[rustfmt::skip]
105 let documented_unused: &[&str] = &[
106 "extract_operator", "identifier",
107 "namespace_operator", "parenthesized_expression", "return", "unary_operator",
108 "braced_expression",
110 "if_statement",
111 "while_statement",
112 "function_definition",
113 "repeat_statement",
114 "for_statement",
115 ];
116 validate_unused_kinds_audit(&R, documented_unused)
117 .expect("R unused node kinds audit failed");
118 }
119}