Skip to main content

tree_sitter_cpp/
lib.rs

1//! This crate provides C++ language support for the [tree-sitter] parsing library.
2//!
3//! Use [willbooster-tree-sitter] 14.0.0 or later within major version 14. The parser carries the ABI
4//! number of the WillBooster/tree-sitter fork; upstream's runtime rejects it.
5//!
6//! Typically, you will use the [`LANGUAGE`] constant to add this language to a
7//! tree-sitter [`Parser`], and then use the parser to parse some code:
8//!
9//! ```
10//! let code = "#include <iostream>\n\nint main() {\n    std::cout << \"Hello\\n\";\n}\n";
11//! let mut parser = tree_sitter::Parser::new();
12//! let language = tree_sitter_cpp::LANGUAGE;
13//! parser
14//!     .set_language(&language.into())
15//!     .expect("Error loading C++ parser");
16//! let tree = parser.parse(code, None).unwrap();
17//! assert!(!tree.root_node().has_error());
18//! ```
19//!
20//! [`Parser`]: https://docs.rs/willbooster-tree-sitter/14.0.0/tree_sitter/struct.Parser.html
21//! [willbooster-tree-sitter]: https://crates.io/crates/willbooster-tree-sitter
22//! [tree-sitter]: https://tree-sitter.github.io/
23
24use tree_sitter_language::LanguageFn;
25
26unsafe extern "C" {
27    fn tree_sitter_cpp() -> *const ();
28}
29
30/// The tree-sitter [`LanguageFn`] for this grammar.
31pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_cpp) };
32
33/// The content of the [`node-types.json`] file for this grammar.
34///
35/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types
36pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
37
38/// The combined C and C++ syntax-highlighting query, with inherited C patterns first.
39pub const HIGHLIGHTS_QUERY: &str = concat!(
40    include_str!("../../queries/c/highlights.scm"),
41    "\n",
42    include_str!("../../queries/highlights.scm")
43);
44
45/// The query for extracting symbol definitions from C++ source.
46pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm");
47
48/// The query for identifying languages and contents embedded in C++ raw strings.
49pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm");
50
51#[cfg(test)]
52mod tests {
53    use tree_sitter::StreamingIterator;
54
55    #[test]
56    fn test_can_load_grammar() {
57        let mut parser = tree_sitter::Parser::new();
58        parser
59            .set_language(&super::LANGUAGE.into())
60            .expect("Error loading C++ parser");
61    }
62
63    #[test]
64    fn highlights_c_and_cpp_syntax() {
65        let source = "class Example { int value = 1; };\n";
66        for (name, texts) in [
67            ("keyword", vec!["class"]),
68            ("type", vec!["Example", "int"]),
69            ("property", vec!["value"]),
70            ("number", vec!["1"]),
71        ] {
72            assert_eq!(captured_texts(source, super::HIGHLIGHTS_QUERY, name), texts);
73        }
74    }
75
76    #[test]
77    fn extracts_symbol_definitions() {
78        let source = "struct Example { void method() {} };\nvoid function() {}\n";
79        assert_eq!(
80            captured_texts(source, super::TAGS_QUERY, "name"),
81            ["Example", "method", "function"]
82        );
83    }
84
85    #[test]
86    fn extracts_raw_string_injections() {
87        let source =
88            "const char* embedded = R\"sql(SELECT 1)sql\";\nconst char* plain = \"SELECT 2\";\n";
89        assert_eq!(
90            captured_texts(source, super::INJECTIONS_QUERY, "injection.language"),
91            ["sql"]
92        );
93        assert_eq!(
94            captured_texts(source, super::INJECTIONS_QUERY, "injection.content"),
95            ["SELECT 1"]
96        );
97    }
98
99    fn captured_texts(source: &str, query_source: &str, capture_name: &str) -> Vec<String> {
100        let language = super::LANGUAGE.into();
101        let mut parser = tree_sitter::Parser::new();
102        parser.set_language(&language).unwrap();
103        let tree = parser.parse(source, None).unwrap();
104        assert!(!tree.root_node().has_error());
105
106        let query = tree_sitter::Query::new(&language, query_source).unwrap();
107        let name_index = query.capture_index_for_name(capture_name).unwrap();
108        let mut cursor = tree_sitter::QueryCursor::new();
109        let mut captures = cursor.captures(&query, tree.root_node(), source.as_bytes());
110        let mut texts = Vec::new();
111        while let Some((query_match, capture_index)) = captures.next() {
112            let capture = query_match.captures()[*capture_index];
113            if capture.index == name_index {
114                texts.push(
115                    capture
116                        .node
117                        .utf8_text(source.as_bytes())
118                        .unwrap()
119                        .to_owned(),
120                );
121            }
122        }
123        texts
124    }
125}