1use std::path::Path;
2
3use crate::comment_parsers;
4use comment_parsers::{Go, JavaDoc, JsDoc, Lua, Solidity, Unit};
5use harper_core::Token;
6use harper_core::parsers::{self, MarkdownOptions, Parser};
7use harper_core::spell::MutableDictionary;
8use tree_sitter::Node;
9
10use crate::masker::CommentMasker;
11
12pub struct CommentParser {
13 inner: parsers::Mask<CommentMasker, Box<dyn Parser>>,
14}
15
16impl CommentParser {
17 pub fn create_ident_dict(&self, source: &[char]) -> Option<MutableDictionary> {
18 self.inner.masker.create_ident_dict(source)
19 }
20
21 pub fn new_from_language_id(
22 language_id: &str,
23 markdown_options: MarkdownOptions,
24 ) -> Option<Self> {
25 let language = match language_id {
26 "c" => tree_sitter_c::LANGUAGE,
27 "clojure" => tree_sitter_clojure::LANGUAGE,
28 "cmake" => tree_sitter_cmake::LANGUAGE,
29 "cpp" => tree_sitter_cpp::LANGUAGE,
30 "csharp" => tree_sitter_c_sharp::LANGUAGE,
31 "dart" => harper_tree_sitter_dart::LANGUAGE,
32 "gleam" => tree_sitter_gleam::LANGUAGE,
33 "go" => tree_sitter_go::LANGUAGE,
34 "groovy" => tree_sitter_groovy::LANGUAGE,
35 "haskell" => tree_sitter_haskell::LANGUAGE,
36 "daml" => tree_sitter_haskell::LANGUAGE,
37 "java" => tree_sitter_java::LANGUAGE,
38 "javascript" => tree_sitter_javascript::LANGUAGE,
39 "javascriptreact" => tree_sitter_typescript::LANGUAGE_TSX,
40 "kotlin" => tree_sitter_kotlin_ng::LANGUAGE,
41 "lua" => tree_sitter_lua::LANGUAGE,
42 "nix" => tree_sitter_nix::LANGUAGE,
43 "php" => tree_sitter_php::LANGUAGE_PHP,
44 "powershell" => tree_sitter_powershell::LANGUAGE,
45 "ruby" => tree_sitter_ruby::LANGUAGE,
46 "rust" => tree_sitter_rust::LANGUAGE,
47 "scala" => tree_sitter_scala::LANGUAGE,
48 "shellscript" => tree_sitter_bash::LANGUAGE,
49 "solidity" => tree_sitter_solidity::LANGUAGE,
50 "swift" => tree_sitter_swift::LANGUAGE,
51 "toml" => tree_sitter_toml_ng::LANGUAGE,
52 "typescript" => tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
53 "typescriptreact" => tree_sitter_typescript::LANGUAGE_TSX,
54 "zig" => tree_sitter_zig::LANGUAGE,
55 _ => return None,
56 };
57
58 let comment_parser: Box<dyn Parser> = match language_id {
59 "go" => Box::new(Go::new_markdown(markdown_options)),
60 "java" => Box::new(JavaDoc::default()),
61 "javascript" | "javascriptreact" | "typescript" | "typescriptreact" => {
62 Box::new(JsDoc::new_markdown(markdown_options))
63 }
64 "lua" => Box::new(Lua::new_markdown(markdown_options)),
65 "solidity" => Box::new(Solidity::new_markdown(markdown_options)),
66 _ => Box::new(Unit::new_markdown(markdown_options)),
67 };
68
69 Some(Self {
70 inner: parsers::Mask::new(
71 CommentMasker::new(language.into(), Self::node_condition),
72 comment_parser,
73 ),
74 })
75 }
76
77 pub fn new_from_filename(filename: &Path, markdown_options: MarkdownOptions) -> Option<Self> {
79 Self::new_from_language_id(Self::filename_to_filetype(filename)?, markdown_options)
80 }
81
82 fn filename_to_filetype(path: &Path) -> Option<&'static str> {
88 Some(match path.extension()?.to_str()? {
89 "c" => "c",
90 "bb" | "cljc" | "cljd" | "clj" | "cljs" => "clojure",
91 "cmake" => "cmake",
92 "cpp" | "h" => "cpp",
93 "cs" => "csharp",
94 "dart" => "dart",
95 "gleam" => "gleam",
96 "go" => "go",
97 "groovy" | "gradle" => "groovy",
98 "hs" => "haskell",
99 "daml" => "daml",
100 "java" => "java",
101 "js" => "javascript",
102 "jsx" => "javascriptreact",
103 "kt" | "kts" => "kotlin",
104 "lua" => "lua",
105 "nix" => "nix",
106 "php" => "php",
107 "ps1" | "psd1" | "psm1" => "powershell",
108 "rb" => "ruby",
109 "rs" => "rust",
110 "sbt" | "sc" | "scala" | "mill" => "scala",
111 "bash" | "sh" => "shellscript",
112 "sol" => "solidity",
113 "swift" => "swift",
114 "toml" => "toml",
115 "ts" => "typescript",
116 "tsx" => "typescriptreact",
117 "zig" => "zig",
118 _ => return None,
119 })
120 }
121
122 fn node_condition(n: &Node) -> bool {
123 n.kind().contains("comment")
124 }
125}
126
127impl Parser for CommentParser {
128 fn parse(&self, source: &[char]) -> Vec<Token> {
129 self.inner.parse(source)
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::CommentParser;
136 use harper_core::parsers::{MarkdownOptions, StrParser};
137
138 #[test]
139 fn hang() {
140 use std::sync::mpsc::channel;
141 use std::thread;
142 use std::time::Duration;
143
144 let (tx, rx) = channel::<()>();
145
146 let handle = thread::spawn(move || {
147 let opts = MarkdownOptions::default();
148 let parser = CommentParser::new_from_language_id("java", opts).unwrap();
149 let _res = parser.parse_str("//{@j");
150 tx.send(()).expect("send failed");
151 });
152
153 rx.recv_timeout(Duration::from_secs(10)).expect("timed out");
154 handle.join().expect("failed to join");
155 }
156}