1use crate::component::extract_embedded_content;
4use crate::external_packages::ResolvedPackage;
5use crate::{Export, Import, Language, Symbol, SymbolKind, Visibility, VisibilityMechanism};
6use std::path::{Path, PathBuf};
7use tree_sitter::Node;
8
9pub struct Vue;
11
12impl Language for Vue {
13 fn name(&self) -> &'static str {
14 "Vue"
15 }
16 fn extensions(&self) -> &'static [&'static str] {
17 &["vue"]
18 }
19 fn grammar_name(&self) -> &'static str {
20 "vue"
21 }
22
23 fn has_symbols(&self) -> bool {
24 true
25 }
26
27 fn container_kinds(&self) -> &'static [&'static str] {
28 &["script_element", "template_element", "style_element"]
29 }
30 fn function_kinds(&self) -> &'static [&'static str] {
31 &[] }
33 fn type_kinds(&self) -> &'static [&'static str] {
34 &[]
35 }
36 fn import_kinds(&self) -> &'static [&'static str] {
37 &[] }
39
40 fn public_symbol_kinds(&self) -> &'static [&'static str] {
41 &[] }
43
44 fn visibility_mechanism(&self) -> VisibilityMechanism {
45 VisibilityMechanism::ExplicitExport
46 }
47
48 fn scope_creating_kinds(&self) -> &'static [&'static str] {
49 &["element"] }
51
52 fn control_flow_kinds(&self) -> &'static [&'static str] {
53 &["directive_attribute"] }
55
56 fn complexity_nodes(&self) -> &'static [&'static str] {
57 &["directive_attribute", "interpolation"]
58 }
59
60 fn nesting_nodes(&self) -> &'static [&'static str] {
61 &["element", "template_element", "script_element"]
62 }
63
64 fn signature_suffix(&self) -> &'static str {
65 ""
66 }
67
68 fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
69 let name = self.node_name(node, content)?;
70 Some(Symbol {
71 name: name.to_string(),
72 kind: SymbolKind::Function,
73 signature: format!("function {}", name),
74 docstring: None,
75 attributes: Vec::new(),
76 start_line: node.start_position().row + 1,
77 end_line: node.end_position().row + 1,
78 visibility: Visibility::Public,
79 children: Vec::new(),
80 is_interface_impl: false,
81 implements: Vec::new(),
82 })
83 }
84
85 fn extract_container(&self, _node: &Node, _content: &str) -> Option<Symbol> {
86 None
87 }
88
89 fn extract_type(&self, _node: &Node, _content: &str) -> Option<Symbol> {
90 None
91 }
92 fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
93 None
94 }
95
96 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
97 Vec::new()
98 }
99 fn extract_imports(&self, _node: &Node, _content: &str) -> Vec<Import> {
100 Vec::new()
101 }
102
103 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
104 let names_to_use: Vec<&str> = names
106 .map(|n| n.to_vec())
107 .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
108 if names_to_use.is_empty() {
109 format!("import '{}';", import.module)
110 } else {
111 format!(
112 "import {{ {} }} from '{}';",
113 names_to_use.join(", "),
114 import.module
115 )
116 }
117 }
118 fn extract_public_symbols(&self, _node: &Node, _content: &str) -> Vec<Export> {
119 Vec::new()
120 }
121
122 fn is_public(&self, _node: &Node, _content: &str) -> bool {
123 true
124 }
125 fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
126 Visibility::Public
127 }
128
129 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
130 {
131 let name = symbol.name.as_str();
132 match symbol.kind {
133 crate::SymbolKind::Function | crate::SymbolKind::Method => {
134 name.starts_with("test_")
135 || name.starts_with("Test")
136 || name == "describe"
137 || name == "it"
138 || name == "test"
139 }
140 crate::SymbolKind::Module => {
141 name == "tests" || name == "test" || name == "__tests__"
142 }
143 _ => false,
144 }
145 }
146 }
147
148 fn embedded_content(&self, node: &Node, content: &str) -> Option<crate::EmbeddedBlock> {
149 extract_embedded_content(node, content)
150 }
151
152 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
153 node.child_by_field_name("body")
154 }
155 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
156 false
157 }
158
159 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
160 let name_node = node.child_by_field_name("name")?;
161 Some(&content[name_node.byte_range()])
162 }
163
164 fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
165 if path.extension()?.to_str()? != "vue" {
166 return None;
167 }
168 Some(path.to_string_lossy().to_string())
169 }
170 fn module_name_to_paths(&self, module: &str) -> Vec<String> {
171 vec![format!("{}.vue", module)]
172 }
173
174 fn lang_key(&self) -> &'static str {
175 "vue"
176 }
177 fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
178 None
179 }
180 fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
181 None
182 }
183 fn is_stdlib_import(&self, _: &str, _: &Path) -> bool {
184 false
185 }
186 fn get_version(&self, _: &Path) -> Option<String> {
187 None
188 }
189 fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
190 None
191 }
192 fn indexable_extensions(&self) -> &'static [&'static str] {
193 &["vue"]
194 }
195 fn find_stdlib(&self, _: &Path) -> Option<PathBuf> {
196 None
197 }
198 fn package_module_name(&self, name: &str) -> String {
199 name.strip_suffix(".vue").unwrap_or(name).to_string()
200 }
201 fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
202 Vec::new()
203 }
204 fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
205 Vec::new()
206 }
207 fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
208 if path.is_file() {
209 Some(path.to_path_buf())
210 } else {
211 None
212 }
213 }
214
215 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
216 use crate::traits::{has_extension, skip_dotfiles};
217 if skip_dotfiles(name) {
218 return true;
219 }
220 if is_dir && name == "node_modules" {
221 return true;
222 }
223 !is_dir && !has_extension(name, self.indexable_extensions())
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::validate_unused_kinds_audit;
231
232 #[test]
233 fn unused_node_kinds_audit() {
234 #[rustfmt::skip]
235 let documented_unused: &[&str] = &[
236 "directive_modifier", "directive_modifiers", "doctype",
237 ];
238
239 validate_unused_kinds_audit(&Vue, documented_unused)
240 .expect("Vue unused node kinds audit failed");
241 }
242}