1use std::cell::RefCell;
8
9use tree_sitter::Parser;
10
11use crate::error::Error;
12use crate::language::LangId;
13
14thread_local! {
15 static PARSERS: RefCell<[Option<Parser>; LangId::COUNT]> = const { RefCell::new([const { None }; LangId::COUNT]) };
16}
17
18fn get_or_init_parser(
19 parsers: &mut [Option<Parser>; LangId::COUNT],
20 lang: LangId,
21) -> Result<&mut Parser, Error> {
22 let idx = lang as usize;
23 if parsers[idx].is_none() {
24 let mut parser = Parser::new();
25 let grammar = crate::language::grammar_for(lang);
26 parser
27 .set_language(&grammar)
28 .map_err(|e| Error::Config(format!("failed to set language: {e}")))?;
29 parsers[idx] = Some(parser);
30 }
31 parsers[idx]
32 .as_mut()
33 .ok_or_else(|| Error::Config("parser slot was not initialized".into()))
34}
35
36pub(crate) fn parse_tree(lang: LangId, source: &[u8]) -> Result<tree_sitter::Tree, Error> {
37 PARSERS.with(|cache| {
38 let parsers = &mut *cache.borrow_mut();
39 let parser = get_or_init_parser(parsers, lang)?;
40 parser.parse(source, None).ok_or_else(|| Error::Parse {
41 path: Default::default(),
42 message: "parser returned no tree".into(),
43 })
44 })
45}
46
47pub(crate) fn error_ratio(tree: &tree_sitter::Tree, source: &[u8]) -> f64 {
48 if source.is_empty() {
49 return 0.0;
50 }
51 let root = tree.root_node();
52 if !root.has_error() {
53 return 0.0;
54 }
55 let mut total = 0u32;
56 let mut errors = 0u32;
57 count_nodes(&root, &mut total, &mut errors);
58 if total == 0 {
59 return 0.0;
60 }
61 errors as f64 / total as f64
62}
63
64pub fn ast_node_count(tree: &tree_sitter::Tree) -> usize {
74 let mut cursor = tree.walk();
75 let mut total = 0u32;
76 let mut reached_root = false;
77 while !reached_root {
78 if cursor.node().is_named() {
79 total += 1;
80 }
81 if cursor.goto_first_child() {
82 continue;
83 }
84 if cursor.goto_next_sibling() {
85 continue;
86 }
87 let mut retracing = true;
88 while retracing {
89 if !cursor.goto_parent() {
90 reached_root = true;
91 break;
92 }
93 if cursor.goto_next_sibling() {
94 retracing = false;
95 }
96 }
97 }
98 total as usize
99}
100
101fn count_nodes(node: &tree_sitter::Node, total: &mut u32, errors: &mut u32) {
102 let mut cursor = node.walk();
103 let mut reached_root = false;
104 while !reached_root {
105 let n = cursor.node();
106 *total += 1;
107 if n.is_error() || n.is_missing() {
108 *errors += 1;
109 }
110 if cursor.goto_first_child() {
111 continue;
112 }
113 if cursor.goto_next_sibling() {
114 continue;
115 }
116 let mut retracing = true;
117 while retracing {
118 if !cursor.goto_parent() {
119 reached_root = true;
120 break;
121 }
122 if cursor.goto_next_sibling() {
123 retracing = false;
124 }
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::language::LangId;
133
134 #[test]
135 fn parse_tree_valid_python() {
136 let tree = parse_tree(LangId::Python, b"def hello(): pass").unwrap();
137 assert!(!tree.root_node().has_error());
138 assert_eq!(tree.root_node().kind(), "module");
139 }
140
141 #[test]
142 fn parse_tree_switches_languages() {
143 let python = parse_tree(LangId::Python, b"def hello(): pass").unwrap();
144 assert_eq!(python.root_node().kind(), "module");
145
146 let javascript = parse_tree(LangId::JavaScript, b"function hello() {}").unwrap();
147 assert_eq!(javascript.root_node().kind(), "program");
148 }
149
150 #[test]
151 fn error_ratio_valid_source() {
152 let tree = parse_tree(LangId::Python, b"def hello(): pass").unwrap();
153 let ratio = error_ratio(&tree, b"def hello(): pass");
154 assert!(ratio < 0.1);
155 }
156
157 #[test]
158 fn error_ratio_malformed() {
159 let tree = parse_tree(LangId::Python, b"def broken(").unwrap();
160 let ratio = error_ratio(&tree, b"def broken(");
161 assert!(ratio > 0.0);
162 }
163
164 #[test]
165 fn error_ratio_empty_source() {
166 let tree = parse_tree(LangId::Python, b"").unwrap();
167 let ratio = error_ratio(&tree, b"");
168 assert_eq!(ratio, 0.0);
169 }
170}