harper_comments/
comment_parser.rs

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            "go" => tree_sitter_go::LANGUAGE,
33            "haskell" => tree_sitter_haskell::LANGUAGE,
34            "daml" => tree_sitter_haskell::LANGUAGE,
35            "java" => tree_sitter_java::LANGUAGE,
36            "javascript" => tree_sitter_javascript::LANGUAGE,
37            "javascriptreact" => tree_sitter_typescript::LANGUAGE_TSX,
38            "kotlin" => tree_sitter_kotlin_ng::LANGUAGE,
39            "lua" => tree_sitter_lua::LANGUAGE,
40            "nix" => tree_sitter_nix::LANGUAGE,
41            "php" => tree_sitter_php::LANGUAGE_PHP,
42            "ruby" => tree_sitter_ruby::LANGUAGE,
43            "rust" => tree_sitter_rust::LANGUAGE,
44            "scala" => tree_sitter_scala::LANGUAGE,
45            "shellscript" => tree_sitter_bash::LANGUAGE,
46            "solidity" => tree_sitter_solidity::LANGUAGE,
47            "swift" => tree_sitter_swift::LANGUAGE,
48            "toml" => tree_sitter_toml_ng::LANGUAGE,
49            "typescript" => tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
50            "typescriptreact" => tree_sitter_typescript::LANGUAGE_TSX,
51            _ => return None,
52        };
53
54        let comment_parser: Box<dyn Parser> = match language_id {
55            "go" => Box::new(Go::new_markdown(markdown_options)),
56            "java" => Box::new(JavaDoc::default()),
57            "javascript" | "javascriptreact" | "typescript" | "typescriptreact" => {
58                Box::new(JsDoc::new_markdown(markdown_options))
59            }
60            "lua" => Box::new(Lua::new_markdown(markdown_options)),
61            "solidity" => Box::new(Solidity::new_markdown(markdown_options)),
62            _ => Box::new(Unit::new_markdown(markdown_options)),
63        };
64
65        Some(Self {
66            inner: parsers::Mask::new(
67                CommentMasker::new(language.into(), Self::node_condition),
68                comment_parser,
69            ),
70        })
71    }
72
73    /// Infer the programming language from a provided filename.
74    pub fn new_from_filename(filename: &Path, markdown_options: MarkdownOptions) -> Option<Self> {
75        Self::new_from_language_id(Self::filename_to_filetype(filename)?, markdown_options)
76    }
77
78    /// Convert a provided path to a corresponding Language Server Protocol file
79    /// type.
80    ///
81    /// Note to contributors: try to keep this in sync with
82    /// [`Self::new_from_language_id`]
83    fn filename_to_filetype(path: &Path) -> Option<&'static str> {
84        Some(match path.extension()?.to_str()? {
85            "c" => "c",
86            "bb" | "cljc" | "cljd" | "clj" | "cljs" => "clojure",
87            "cmake" => "cmake",
88            "cpp" | "h" => "cpp",
89            "cs" => "csharp",
90            "dart" => "dart",
91            "go" => "go",
92            "hs" => "haskell",
93            "daml" => "daml",
94            "java" => "java",
95            "js" => "javascript",
96            "jsx" => "javascriptreact",
97            "kt" | "kts" => "kotlin",
98            "lua" => "lua",
99            "nix" => "nix",
100            "php" => "php",
101            "rb" => "ruby",
102            "rs" => "rust",
103            "sbt" | "sc" | "scala" | "mill" => "scala",
104            "bash" | "sh" => "shellscript",
105            "sol" => "solidity",
106            "swift" => "swift",
107            "toml" => "toml",
108            "ts" => "typescript",
109            "tsx" => "typescriptreact",
110            _ => return None,
111        })
112    }
113
114    fn node_condition(n: &Node) -> bool {
115        n.kind().contains("comment")
116    }
117}
118
119impl Parser for CommentParser {
120    fn parse(&self, source: &[char]) -> Vec<Token> {
121        self.inner.parse(source)
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::CommentParser;
128    use harper_core::parsers::{MarkdownOptions, StrParser};
129
130    #[test]
131    fn hang() {
132        use std::sync::mpsc::channel;
133        use std::thread;
134        use std::time::Duration;
135
136        let (tx, rx) = channel::<()>();
137
138        let handle = thread::spawn(move || {
139            let opts = MarkdownOptions::default();
140            let parser = CommentParser::new_from_language_id("java", opts).unwrap();
141            let _res = parser.parse_str("//{@j");
142            tx.send(()).expect("send failed");
143        });
144
145        rx.recv_timeout(Duration::from_secs(10)).expect("timed out");
146        handle.join().expect("failed to join");
147    }
148}