normalize_languages/
css.rs1use crate::{Language, LanguageSymbols};
7use tree_sitter::Node;
8
9pub struct Css;
11
12impl Language for Css {
13 fn name(&self) -> &'static str {
14 "CSS"
15 }
16 fn extensions(&self) -> &'static [&'static str] {
17 &["css"]
18 }
19 fn grammar_name(&self) -> &'static str {
20 "css"
21 }
22
23 fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
24 Some(self)
25 }
26
27 fn refine_kind(
28 &self,
29 node: &Node,
30 _content: &str,
31 tag_kind: crate::SymbolKind,
32 ) -> crate::SymbolKind {
33 match node.kind() {
34 "media_statement" | "supports_statement" | "keyframes_statement" => {
36 crate::SymbolKind::Module
37 }
38 _ => tag_kind,
39 }
40 }
41
42 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
43 match node.kind() {
44 "rule_set" => {
45 let mut cursor = node.walk();
47 for child in node.children(&mut cursor) {
48 if child.kind() == "selectors" {
49 return Some(content[child.byte_range()].trim());
50 }
51 }
52 None
53 }
54 "media_statement" => {
55 extract_at_rule_name(node, content, "@media")
57 }
58 "supports_statement" => extract_at_rule_name(node, content, "@supports"),
59 "keyframes_statement" => {
60 let mut cursor = node.walk();
62 for child in node.children(&mut cursor) {
63 if child.kind() == "keyframes_name" {
64 return Some(content[child.byte_range()].trim());
65 }
66 }
67 None
68 }
69 "declaration" => {
70 let mut cursor = node.walk();
72 for child in node.children(&mut cursor) {
73 if child.kind() == "property_name" {
74 return Some(content[child.byte_range()].trim());
75 }
76 }
77 None
78 }
79 _ => None,
80 }
81 }
82
83 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
84 match node.kind() {
85 "rule_set" | "media_statement" | "supports_statement" | "keyframes_statement" => {
86 let mut cursor = node.walk();
87 for child in node.children(&mut cursor) {
88 if child.kind() == "block" || child.kind() == "keyframe_block_list" {
89 return Some(child);
90 }
91 }
92 None
93 }
94 _ => None,
95 }
96 }
97
98 fn build_signature(&self, node: &Node, content: &str) -> String {
99 if let Some(name) = self.node_name(node, content) {
100 match node.kind() {
101 "rule_set" => format!("{} {{ … }}", name),
102 "media_statement" => format!("@media {} {{ … }}", name),
103 "supports_statement" => format!("@supports {} {{ … }}", name),
104 "keyframes_statement" => format!("@keyframes {} {{ … }}", name),
105 "declaration" => {
106 let mut cursor = node.walk();
108 let mut found_name = false;
109 for child in node.children(&mut cursor) {
110 if child.kind() == "property_name" {
111 found_name = true;
112 } else if found_name && child.kind() != ":" && child.kind() != ";" {
113 let val = content[child.byte_range()].trim();
114 if val.len() > 40 {
115 return format!("{}: {}…", name, &val[..37]);
116 }
117 return format!("{}: {}", name, val);
118 }
119 }
120 name.to_string()
121 }
122 _ => name.to_string(),
123 }
124 } else {
125 content[node.byte_range()]
126 .lines()
127 .next()
128 .unwrap_or("")
129 .trim()
130 .to_string()
131 }
132 }
133}
134
135impl LanguageSymbols for Css {}
136
137fn extract_at_rule_name<'a>(node: &Node, content: &'a str, keyword: &str) -> Option<&'a str> {
139 let full = &content[node.byte_range()];
140 let after_keyword = full.strip_prefix(keyword)?.trim_start();
141 let name = after_keyword.split('{').next()?.trim();
143 if name.is_empty() {
144 return None;
145 }
146 let start = node.start_byte() + full.find(name)?;
148 let end = start + name.len();
149 Some(&content[start..end])
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::validate_unused_kinds_audit;
156
157 #[test]
158 fn unused_node_kinds_audit() {
159 #[rustfmt::skip]
160 let documented_unused: &[&str] = &[
161 "binary_expression", "block", "call_expression", "charset_statement",
162 "class_name", "class_selector",
163 "function_name",
164 "identifier", "import_statement", "important", "important_value",
165 "keyframe_block", "keyframe_block_list",
166 "namespace_statement", "postcss_statement",
167 "pseudo_class_selector", "scope_statement",
168 ];
169 validate_unused_kinds_audit(&Css, documented_unused)
170 .expect("CSS unused node kinds audit failed");
171 }
172}