1use crate::external_packages::ResolvedPackage;
4use crate::{Export, Import, Language, Symbol, SymbolKind, Visibility, VisibilityMechanism};
5use std::path::{Path, PathBuf};
6use tree_sitter::Node;
7
8pub struct Markdown;
10
11impl Language for Markdown {
12 fn name(&self) -> &'static str {
13 "Markdown"
14 }
15 fn extensions(&self) -> &'static [&'static str] {
16 &["md", "markdown"]
17 }
18 fn grammar_name(&self) -> &'static str {
19 "markdown"
20 }
21
22 fn has_symbols(&self) -> bool {
23 true
24 }
25
26 fn container_kinds(&self) -> &'static [&'static str] {
28 &["atx_heading", "setext_heading"]
29 }
30 fn function_kinds(&self) -> &'static [&'static str] {
31 &[]
32 }
33 fn type_kinds(&self) -> &'static [&'static str] {
34 &[]
35 }
36 fn import_kinds(&self) -> &'static [&'static str] {
37 &[]
38 }
39 fn public_symbol_kinds(&self) -> &'static [&'static str] {
40 &[]
41 }
42 fn visibility_mechanism(&self) -> VisibilityMechanism {
43 VisibilityMechanism::NotApplicable
44 }
45 fn scope_creating_kinds(&self) -> &'static [&'static str] {
46 &[]
47 }
48 fn control_flow_kinds(&self) -> &'static [&'static str] {
49 &[]
50 }
51 fn complexity_nodes(&self) -> &'static [&'static str] {
52 &[]
53 }
54 fn nesting_nodes(&self) -> &'static [&'static str] {
55 &[]
56 }
57
58 fn signature_suffix(&self) -> &'static str {
59 ""
60 }
61
62 fn extract_function(
63 &self,
64 _node: &Node,
65 _content: &str,
66 _in_container: bool,
67 ) -> Option<Symbol> {
68 None
69 }
70
71 fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
72 let mut cursor = node.walk();
74 let text = node
75 .children(&mut cursor)
76 .find(|c| c.kind() == "heading_content" || c.kind() == "inline")
77 .map(|c| content[c.byte_range()].trim().to_string())
78 .unwrap_or_default();
79
80 if text.is_empty() {
81 return None;
82 }
83
84 let mut cursor2 = node.walk();
86 let level = node
87 .children(&mut cursor2)
88 .find_map(|c| match c.kind() {
89 "atx_h1_marker" => Some(1),
90 "atx_h2_marker" => Some(2),
91 "atx_h3_marker" => Some(3),
92 "atx_h4_marker" => Some(4),
93 "atx_h5_marker" => Some(5),
94 "atx_h6_marker" => Some(6),
95 _ => None,
96 })
97 .unwrap_or(1);
98
99 Some(Symbol {
100 name: text.clone(),
101 kind: SymbolKind::Heading,
102 signature: format!("{} {}", "#".repeat(level), text),
103 docstring: None,
104 attributes: Vec::new(),
105 start_line: node.start_position().row + 1,
106 end_line: node.end_position().row + 1,
107 visibility: Visibility::Public,
108 children: Vec::new(),
109 is_interface_impl: false,
110 implements: Vec::new(),
111 })
112 }
113
114 fn extract_type(&self, _node: &Node, _content: &str) -> Option<Symbol> {
115 None
116 }
117 fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
118 None
119 }
120
121 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
122 Vec::new()
123 }
124 fn extract_imports(&self, _node: &Node, _content: &str) -> Vec<Import> {
125 Vec::new()
126 }
127
128 fn format_import(&self, _import: &Import, _names: Option<&[&str]>) -> String {
129 String::new()
131 }
132 fn extract_public_symbols(&self, _node: &Node, _content: &str) -> Vec<Export> {
133 Vec::new()
134 }
135
136 fn is_public(&self, _node: &Node, _content: &str) -> bool {
137 true
138 }
139 fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
140 Visibility::Public
141 }
142
143 fn is_test_symbol(&self, _symbol: &crate::Symbol) -> bool {
144 false
145 }
146
147 fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
148 None
149 }
150
151 fn container_body<'a>(&self, _node: &'a Node<'a>) -> Option<Node<'a>> {
152 None
153 }
154 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
155 false
156 }
157 fn node_name<'a>(&self, _node: &Node, _content: &'a str) -> Option<&'a str> {
158 None
159 }
160
161 fn file_path_to_module_name(&self, _: &Path) -> Option<String> {
162 None
163 }
164 fn module_name_to_paths(&self, _: &str) -> Vec<String> {
165 Vec::new()
166 }
167
168 fn lang_key(&self) -> &'static str {
169 ""
170 }
171 fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
172 None
173 }
174 fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
175 None
176 }
177 fn is_stdlib_import(&self, _: &str, _: &Path) -> bool {
178 false
179 }
180 fn get_version(&self, _: &Path) -> Option<String> {
181 None
182 }
183 fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
184 None
185 }
186 fn indexable_extensions(&self) -> &'static [&'static str] {
187 &[]
188 }
189 fn find_stdlib(&self, _: &Path) -> Option<PathBuf> {
190 None
191 }
192 fn package_module_name(&self, name: &str) -> String {
193 name.to_string()
194 }
195 fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
196 Vec::new()
197 }
198 fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
199 Vec::new()
200 }
201 fn find_package_entry(&self, _: &Path) -> Option<PathBuf> {
202 None
203 }
204
205 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
206 use crate::traits::{has_extension, skip_dotfiles};
207 if skip_dotfiles(name) {
208 return true;
209 }
210 !is_dir && !has_extension(name, self.indexable_extensions())
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use crate::validate_unused_kinds_audit;
218
219 #[test]
220 fn unused_node_kinds_audit() {
221 #[rustfmt::skip]
222 let documented_unused: &[&str] = &[
223 "block_continuation", "block_quote", "block_quote_marker",
224 "fenced_code_block", "fenced_code_block_delimiter",
225 "html_block", "indented_code_block", "link_reference_definition",
226 ];
227
228 validate_unused_kinds_audit(&Markdown, documented_unused)
229 .expect("Markdown unused node kinds audit failed");
230 }
231}