normalize_languages/
svelte.rs1use crate::component::extract_embedded_content;
4use crate::{ContainerBody, Import, Language, LanguageEmbedded, LanguageSymbols, Visibility};
5use tree_sitter::Node;
6
7pub struct Svelte;
9
10impl Language for Svelte {
11 fn name(&self) -> &'static str {
12 "Svelte"
13 }
14 fn extensions(&self) -> &'static [&'static str] {
15 &["svelte"]
16 }
17 fn grammar_name(&self) -> &'static str {
18 "svelte"
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() != "import_statement" {
27 return Vec::new();
28 }
29
30 let text = &content[node.byte_range()];
31 let line = node.start_position().row + 1;
32
33 if let Some(from_idx) = text.find(" from ") {
35 let rest = &text[from_idx + 6..];
36 if let Some(start) = rest.find('"').or_else(|| rest.find('\'')) {
37 let Some(quote) = rest[start..].chars().next() else {
39 return Vec::new();
40 };
41 let inner = &rest[start + 1..];
42 if let Some(end) = inner.find(quote) {
43 let module = inner[..end].to_string();
44 return vec![Import {
45 module: module.clone(),
46 names: Vec::new(),
47 alias: None,
48 is_wildcard: text.contains(" * "),
49 is_relative: module.starts_with('.'),
50 line,
51 }];
52 }
53 }
54 }
55
56 Vec::new()
57 }
58
59 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
60 let names_to_use: Vec<&str> = names
62 .map(|n| n.to_vec())
63 .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
64 if names_to_use.is_empty() {
65 format!("import '{}';", import.module)
66 } else {
67 format!(
68 "import {{ {} }} from '{}';",
69 names_to_use.join(", "),
70 import.module
71 )
72 }
73 }
74
75 fn get_visibility(&self, node: &Node, _content: &str) -> Visibility {
76 let mut cursor = node.walk();
77 for child in node.children(&mut cursor) {
78 if child.kind() == "export" {
79 return Visibility::Public;
80 }
81 }
82 Visibility::Private
83 }
84
85 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
86 let name = symbol.name.as_str();
87 match symbol.kind {
88 crate::SymbolKind::Function | crate::SymbolKind::Method => {
89 name.starts_with("test_")
90 || name.starts_with("Test")
91 || name == "describe"
92 || name == "it"
93 || name == "test"
94 }
95 crate::SymbolKind::Module => name == "tests" || name == "test" || name == "__tests__",
96 _ => false,
97 }
98 }
99
100 fn as_embedded(&self) -> Option<&dyn LanguageEmbedded> {
101 Some(self)
102 }
103
104 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
105 let mut cursor = node.walk();
107 node.children(&mut cursor)
108 .find(|&child| child.kind() == "raw_text")
109 }
110
111 fn analyze_container_body(
112 &self,
113 body_node: &Node,
114 content: &str,
115 inner_indent: &str,
116 ) -> Option<ContainerBody> {
117 crate::body::analyze_end_body(body_node, content, inner_indent)
119 }
120
121 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
122 node.child_by_field_name("name")
123 .or_else(|| node.child_by_field_name("function"))
124 .map(|n| &content[n.byte_range()])
125 }
126}
127
128impl LanguageSymbols for Svelte {}
129
130impl LanguageEmbedded for Svelte {
131 fn embedded_content(&self, node: &Node, content: &str) -> Option<crate::EmbeddedBlock> {
132 extract_embedded_content(node, content)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::validate_unused_kinds_audit;
140
141 #[test]
142 fn unused_node_kinds_audit() {
143 #[rustfmt::skip]
145 let documented_unused: &[&str] = &[
146 "await_end", "await_start", "block_end_tag", "block_start_tag",
147 "block_tag", "catch_block", "catch_start", "doctype", "else_block",
148 "else_if_start", "else_start", "expression", "expression_tag",
149 "if_end", "if_start", "key_statement", "snippet_statement", "then_block",
150 "await_statement", "each_statement", "else_if_block", "if_statement",
152 ];
153 validate_unused_kinds_audit(&Svelte, documented_unused)
154 .expect("Svelte unused node kinds audit failed");
155 }
156}