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
47#[derive(Debug, Clone, Copy, PartialEq)]
49pub struct TreeMetrics {
50 pub error_ratio: f64,
51 pub node_count: usize,
52}
53
54pub fn tree_metrics(tree: &tree_sitter::Tree, source: &[u8]) -> TreeMetrics {
56 if source.is_empty() {
57 return TreeMetrics {
58 error_ratio: 0.0,
59 node_count: 0,
60 };
61 }
62 let mut cursor = tree.walk();
63 let mut total = 0u32;
64 let mut errors = 0u32;
65 let mut named = 0u32;
66 let mut reached_root = false;
67
68 while !reached_root {
69 let n = cursor.node();
70 total += 1;
71 if n.is_named() {
72 named += 1;
73 }
74 if n.is_error() || n.is_missing() {
75 errors += 1;
76 }
77 if cursor.goto_first_child() {
78 continue;
79 }
80 if cursor.goto_next_sibling() {
81 continue;
82 }
83 let mut retracing = true;
84 while retracing {
85 if !cursor.goto_parent() {
86 reached_root = true;
87 break;
88 }
89 if cursor.goto_next_sibling() {
90 retracing = false;
91 }
92 }
93 }
94
95 let error_ratio = if total == 0 {
96 0.0
97 } else {
98 errors as f64 / total as f64
99 };
100
101 TreeMetrics {
102 error_ratio,
103 node_count: named as usize,
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::language::LangId;
111
112 #[test]
113 fn parse_tree_valid_python() {
114 let tree = parse_tree(LangId::Python, b"def hello(): pass").unwrap();
115 assert!(!tree.root_node().has_error());
116 assert_eq!(tree.root_node().kind(), "module");
117 }
118
119 #[test]
120 fn parse_tree_switches_languages() {
121 let python = parse_tree(LangId::Python, b"def hello(): pass").unwrap();
122 assert_eq!(python.root_node().kind(), "module");
123
124 let javascript = parse_tree(LangId::JavaScript, b"function hello() {}").unwrap();
125 assert_eq!(javascript.root_node().kind(), "program");
126 }
127
128 #[test]
129 fn tree_metrics_valid_source() {
130 let tree = parse_tree(LangId::Python, b"def hello(): pass").unwrap();
131 let metrics = tree_metrics(&tree, b"def hello(): pass");
132 assert!(metrics.error_ratio < 0.1);
133 assert!(metrics.node_count > 0);
134 }
135
136 #[test]
137 fn tree_metrics_malformed() {
138 let tree = parse_tree(LangId::Python, b"def broken(").unwrap();
139 let metrics = tree_metrics(&tree, b"def broken(");
140 assert!(metrics.error_ratio > 0.0);
141 }
142
143 #[test]
144 fn tree_metrics_empty_source() {
145 let tree = parse_tree(LangId::Python, b"").unwrap();
146 let metrics = tree_metrics(&tree, b"");
147 assert_eq!(metrics.error_ratio, 0.0);
148 assert_eq!(metrics.node_count, 0);
149 }
150}