1#![cfg_attr(target_arch = "wasm32", allow(unused_imports))]
4
5use std::{collections::HashSet, fmt::Display};
6
7use miette::{LabeledSpan, Severity, SourceSpan};
8use serde::Serialize;
9
10use topiary_tree_sitter_facade::{
11 Node, Parser, Point, Query, QueryCapture, QueryCursor, QueryMatch, QueryPredicate, Range, Tree,
12};
13
14use streaming_iterator::StreamingIterator;
15
16use crate::{
17 FormatterResult,
18 atom_collection::{AtomCollection, QueryPredicates},
19 error::FormatterError,
20};
21
22#[derive(Clone, Copy, Debug)]
24pub enum Visualisation {
25 GraphViz,
26 Json,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
33pub struct Position {
34 pub row: u32,
35 pub column: u32,
36}
37
38impl Display for Position {
39 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
40 write!(f, "({},{})", self.row, self.column)
41 }
42}
43
44#[derive(Debug)]
48pub struct TopiaryQuery {
49 pub query: Query,
50 pub query_content: String,
51}
52
53impl TopiaryQuery {
54 pub fn new(
62 grammar: &topiary_tree_sitter_facade::Language,
63 query_content: &str,
64 ) -> FormatterResult<TopiaryQuery> {
65 let query = Query::new(grammar, query_content)
66 .map_err(|e| FormatterError::Query("Error parsing query file".into(), Some(e)))?;
67
68 Ok(TopiaryQuery {
69 query,
70 query_content: query_content.to_owned(),
71 })
72 }
73
74 #[cfg(not(target_arch = "wasm32"))]
77 pub fn pattern_position(&self, pattern_index: usize) -> Position {
78 let byte_offset = self.query.start_byte_for_pattern(pattern_index);
79 let (row, column) =
80 self.query_content[..byte_offset]
81 .chars()
82 .fold((0, 0), |(row, column), c| {
83 if c == '\n' {
84 (row + 1, 0)
85 } else {
86 (row, column + 1)
87 }
88 });
89 Position {
90 row: row + 1,
91 column: column + 1,
92 }
93 }
94
95 #[cfg(target_arch = "wasm32")]
96 pub fn pattern_position(&self, _pattern_index: usize) -> Position {
97 unimplemented!()
98 }
99}
100
101impl From<Point> for Position {
102 fn from(point: Point) -> Self {
103 Self {
104 row: point.row() + 1,
105 column: point.column() + 1,
106 }
107 }
108}
109
110#[derive(Serialize)]
112pub struct SyntaxNode {
113 #[serde(skip_serializing)]
114 pub id: usize,
115
116 pub kind: String,
117 pub is_named: bool,
118 is_extra: bool,
119 is_error: bool,
120 is_missing: bool,
121 start: Position,
122 end: Position,
123
124 pub children: Vec<SyntaxNode>,
125}
126
127impl From<Node<'_>> for SyntaxNode {
128 fn from(node: Node) -> Self {
129 let mut walker = node.walk();
130 let children = node.children(&mut walker).map(Self::from).collect();
131
132 Self {
133 id: node.id(),
134
135 kind: node.kind().into(),
136 is_named: node.is_named(),
137 is_extra: node.is_extra(),
138 is_error: node.is_error(),
139 is_missing: node.is_missing(),
140 start: node.start_position().into(),
141 end: node.end_position().into(),
142
143 children,
144 }
145 }
146}
147
148pub trait NodeExt {
154 fn display_one_based(&self) -> String;
156}
157
158impl NodeExt for Node<'_> {
159 fn display_one_based(&self) -> String {
160 format!(
161 "{{Node {:?} {} - {}}}",
162 self.kind(),
163 Position::from(self.start_position()),
164 Position::from(self.end_position()),
165 )
166 }
167}
168
169#[cfg(not(target_arch = "wasm32"))]
170impl NodeExt for tree_sitter::Node<'_> {
171 fn display_one_based(&self) -> String {
172 format!(
173 "{{Node {:?} {} - {}}}",
174 self.kind(),
175 Position::from(<tree_sitter::Point as Into<Point>>::into(
176 self.start_position()
177 )),
178 Position::from(<tree_sitter::Point as Into<Point>>::into(
179 self.end_position()
180 )),
181 )
182 }
183}
184
185#[derive(Debug)]
186struct LocalQueryMatch<'a> {
189 pattern_index: usize,
190 captures: Vec<QueryCapture<'a>>,
191}
192
193impl Display for LocalQueryMatch<'_> {
194 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
195 write!(
196 f,
197 "LocalQueryMatch {{ pattern_index: {}, captures: [ ",
198 self.pattern_index
199 )?;
200 for (index, capture) in self.captures.iter().enumerate() {
201 if index > 0 {
202 write!(f, ", ")?;
203 }
204 write!(f, "{}", capture.node().display_one_based())?;
208 }
209 write!(f, " ] }}")?;
210 Ok(())
211 }
212}
213
214#[derive(Clone, Debug, PartialEq)]
215pub struct CoverageData {
217 pub cover_percentage: f32,
218 pub missing_patterns: Vec<LabeledSpan>,
219}
220
221impl CoverageData {
222 fn status_msg(&self) -> String {
223 match self.cover_percentage {
224 0.0 if self.missing_patterns.is_empty() => "No queries found".into(),
225 1.0 => "All queries are matched".into(),
226 _ => format!("Unmatched queries: {}", self.missing_patterns.len()),
227 }
228 }
229
230 fn full_coverage(&self) -> bool {
231 self.cover_percentage == 1.0
232 }
233
234 pub fn get_result(&self) -> Result<(), FormatterError> {
236 if !self.full_coverage() {
237 return Err(FormatterError::PatternDoesNotMatch);
238 }
239 Ok(())
240 }
241}
242
243impl std::fmt::Display for CoverageData {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 write!(f, "{}", self.status_msg())
246 }
247}
248impl std::error::Error for CoverageData {}
249
250impl miette::Diagnostic for CoverageData {
251 fn severity(&self) -> Option<miette::Severity> {
252 match self.cover_percentage {
253 1.0 => Severity::Advice,
254 0.0 => Severity::Warning,
255 _ => Severity::Error,
256 }
257 .into()
258 }
259 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
260 Some(Box::new(self.missing_patterns.iter().cloned()))
261 }
262
263 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
264 let msg = format!("Query coverage: {:.2}%", self.cover_percentage * 100.0);
265
266 Some(Box::new(msg))
267 }
268}
269
270pub fn apply_query(
281 input_content: &str,
282 query: &TopiaryQuery,
283 grammar: &topiary_tree_sitter_facade::Language,
284 tolerate_parsing_errors: bool,
285) -> FormatterResult<AtomCollection> {
286 let tree = parse(input_content, grammar, tolerate_parsing_errors)?;
287 apply_query_tree(tree, input_content, query)
288}
289
290pub fn apply_query_tree(
300 tree: Tree,
301 input_content: &str,
302 query: &TopiaryQuery,
303) -> FormatterResult<AtomCollection> {
304 let root = tree.root_node();
305 let source = input_content.as_bytes();
306
307 let mut cursor = QueryCursor::new();
309 let mut matches: Vec<LocalQueryMatch> = Vec::new();
310 let capture_names = query.query.capture_names();
311
312 let mut query_matches = query.query.matches(&root, source, &mut cursor);
313 #[allow(clippy::while_let_on_iterator)] while let Some(query_match) = query_matches.next() {
315 let local_captures: Vec<QueryCapture> = query_match.captures().collect();
316
317 matches.push(LocalQueryMatch {
318 pattern_index: query_match.pattern_index(),
319 captures: local_captures,
320 });
321 }
322
323 let specified_leaf_nodes: HashSet<usize> = collect_leaf_ids(&matches, capture_names.clone());
326
327 let mut atoms = AtomCollection::collect_leaves(&root, source, specified_leaf_nodes)?;
329
330 log::debug!("List of atoms before formatting: {atoms:?}");
331
332 let mut pattern_positions: Vec<Option<Position>> = Vec::new();
334
335 #[cfg(not(target_arch = "wasm32"))]
338 if log::log_enabled!(log::Level::Info) {
339 pattern_positions.resize(query.query.pattern_count(), None);
340 }
341
342 for m in matches {
351 let mut predicates = QueryPredicates::default();
352
353 for p in query.query.general_predicates(m.pattern_index) {
354 predicates = handle_predicate(&p, &predicates)?;
355 }
356 check_predicates(&predicates)?;
357
358 if log::log_enabled!(log::Level::Info) {
360 #[cfg(target_arch = "wasm32")]
361 if m.pattern_index >= pattern_positions.len() {
363 pattern_positions.resize(m.pattern_index + 1, None);
364 }
365
366 let pos = pattern_positions[m.pattern_index].unwrap_or_else(|| {
368 let pos = query.pattern_position(m.pattern_index);
369 pattern_positions[m.pattern_index] = Some(pos);
370 pos
371 });
372
373 let query_name_info = if let Some(name) = &predicates.query_name {
374 format!(" of query \"{name}\"")
375 } else {
376 "".into()
377 };
378
379 log::debug!("Processing match{query_name_info}: {m} at location {pos}");
380 }
381
382 if m.captures
384 .iter()
385 .any(|c| c.name(capture_names.as_slice()) == "do_nothing")
386 {
387 continue;
388 }
389
390 for c in m.captures {
391 let name = c.name(capture_names.as_slice());
392 atoms.resolve_capture(&name, &c.node(), &predicates)?;
393 }
394 }
395
396 atoms.apply_prepends_and_appends();
398
399 Ok(atoms)
400}
401
402#[derive(Debug)]
404pub struct NodeSpan {
405 pub(crate) range: Range,
406 pub content: Option<String>,
408 pub location: Option<String>,
410 pub language: &'static str,
411}
412
413impl NodeSpan {
414 pub fn new(node: &Node) -> Self {
416 Self {
417 range: node.range(),
418 content: None,
419 location: None,
420 language: node.language_name().unwrap_or_default(),
421 }
422 }
423 pub fn source_span(&self) -> SourceSpan {
425 (self.range.start_byte() as usize..=self.range.end_byte() as usize).into()
426 }
427
428 pub(crate) fn set_content(&mut self, content: String) {
429 self.content = Some(content);
430 }
431
432 pub fn with_content(mut self, content: String) -> Self {
434 self.set_content(content);
435 self
436 }
437
438 pub(crate) fn set_location(&mut self, location: String) {
439 self.location = Some(location);
440 }
441
442 pub fn with_location(mut self, location: String) -> Self {
444 self.set_location(location);
445 self
446 }
447}
448
449impl std::ops::Deref for NodeSpan {
450 type Target = Range;
451
452 fn deref(&self) -> &Self::Target {
453 &self.range
454 }
455}
456
457pub fn parse(
459 content: &str,
460 grammar: &topiary_tree_sitter_facade::Language,
461 tolerate_parsing_errors: bool,
462) -> FormatterResult<Tree> {
463 let mut parser = Parser::new()?;
464 parser.set_language(grammar).map_err(|_| {
465 FormatterError::Internal("Could not apply Tree-sitter grammar".into(), None)
466 })?;
467
468 let tree = parser
469 .parse(content, None)?
470 .ok_or_else(|| FormatterError::Internal("Could not parse input".into(), None))?;
471
472 if !tolerate_parsing_errors {
474 check_for_error_nodes(&tree.root_node())
475 .map_err(|e| e.with_content(content.to_string()))?;
476 }
477
478 Ok(tree)
479}
480
481fn check_for_error_nodes(node: &Node) -> Result<(), NodeSpan> {
483 if node.is_error() {
484 return Err(NodeSpan::new(node));
485 }
486
487 for child in node.children(&mut node.walk()) {
488 check_for_error_nodes(&child)?;
489 }
490
491 Ok(())
492}
493
494fn collect_leaf_ids(matches: &[LocalQueryMatch], capture_names: Vec<&str>) -> HashSet<usize> {
499 let mut ids = HashSet::new();
500
501 for m in matches {
502 for c in &m.captures {
503 if c.name(capture_names.as_slice()) == "leaf" {
504 ids.insert(c.node().id());
505 }
506 }
507 }
508 ids
509}
510
511fn handle_predicate(
529 predicate: &QueryPredicate,
530 predicates: &QueryPredicates,
531) -> FormatterResult<QueryPredicates> {
532 let operator = &*predicate.operator();
533 if "delimiter!" == operator {
534 let arg =
535 predicate.args().into_iter().next().ok_or_else(|| {
536 FormatterError::Query(format!("{operator} needs an argument"), None)
537 })?;
538 Ok(QueryPredicates {
539 delimiter: Some(arg),
540 ..predicates.clone()
541 })
542 } else if "scope_id!" == operator {
543 let arg =
544 predicate.args().into_iter().next().ok_or_else(|| {
545 FormatterError::Query(format!("{operator} needs an argument"), None)
546 })?;
547 Ok(QueryPredicates {
548 scope_id: Some(arg),
549 ..predicates.clone()
550 })
551 } else if "single_line_only!" == operator {
552 Ok(QueryPredicates {
553 single_line_only: true,
554 ..predicates.clone()
555 })
556 } else if "multi_line_only!" == operator {
557 Ok(QueryPredicates {
558 multi_line_only: true,
559 ..predicates.clone()
560 })
561 } else if "single_line_scope_only!" == operator {
562 let arg =
563 predicate.args().into_iter().next().ok_or_else(|| {
564 FormatterError::Query(format!("{operator} needs an argument"), None)
565 })?;
566 Ok(QueryPredicates {
567 single_line_scope_only: Some(arg),
568 ..predicates.clone()
569 })
570 } else if "multi_line_scope_only!" == operator {
571 let arg =
572 predicate.args().into_iter().next().ok_or_else(|| {
573 FormatterError::Query(format!("{operator} needs an argument"), None)
574 })?;
575 Ok(QueryPredicates {
576 multi_line_scope_only: Some(arg),
577 ..predicates.clone()
578 })
579 } else if "query_name!" == operator {
580 let arg =
581 predicate.args().into_iter().next().ok_or_else(|| {
582 FormatterError::Query(format!("{operator} needs an argument"), None)
583 })?;
584 Ok(QueryPredicates {
585 query_name: Some(arg),
586 ..predicates.clone()
587 })
588 } else {
589 Err(FormatterError::Query(
590 format!("{operator} is an unknown predicate. Maybe you forgot a \"!\"?"),
591 None,
592 ))
593 }
594}
595
596fn check_predicates(predicates: &QueryPredicates) -> FormatterResult<()> {
612 let mut incompatible_predicates = 0;
613 if predicates.single_line_only {
614 incompatible_predicates += 1;
615 }
616 if predicates.multi_line_only {
617 incompatible_predicates += 1;
618 }
619 if predicates.single_line_scope_only.is_some() {
620 incompatible_predicates += 1;
621 }
622 if predicates.multi_line_scope_only.is_some() {
623 incompatible_predicates += 1;
624 }
625 if incompatible_predicates > 1 {
626 Err(FormatterError::Query(
627 "A query can contain at most one #single/multi_line[_scope]_only! predicate".into(),
628 None,
629 ))
630 } else {
631 Ok(())
632 }
633}
634
635#[cfg(not(target_arch = "wasm32"))]
636pub fn check_query_coverage(
640 input_content: &str,
641 original_query: &TopiaryQuery,
642 grammar: &topiary_tree_sitter_facade::Language,
643) -> FormatterResult<CoverageData> {
644 use miette::LabeledSpan;
645 use rayon::iter::{IntoParallelIterator, ParallelIterator};
646
647 let tree = parse(input_content, grammar, false)?;
648 let root = tree.root_node();
649 let source = input_content.as_bytes();
650 let mut missing_patterns = Vec::new();
651
652 let mut cursor = QueryCursor::new();
654 let ref_match_count = original_query
655 .query
656 .matches(&root, source, &mut cursor)
657 .count();
658
659 let pattern_count = original_query.query.pattern_count();
660 let query_content = &original_query.query_content;
661 let query = &original_query.query;
662
663 if pattern_count == 0 {
666 let cover_percentage = 0.0;
667 return Ok(CoverageData {
668 cover_percentage,
669 missing_patterns,
670 });
671 }
672
673 if pattern_count == 1 {
676 let mut cover_percentage = 1.0;
677 if ref_match_count == 0 {
678 missing_patterns.push(LabeledSpan::new_with_span(
679 Some("empty query".into()),
680 SourceSpan::from(0..query_content.len()),
681 ));
682 cover_percentage = 0.0
683 }
684 return Ok(CoverageData {
685 cover_percentage,
686 missing_patterns,
687 });
688 }
689
690 let missing_patterns: Vec<LabeledSpan> = (0..pattern_count)
691 .into_par_iter()
692 .filter_map(|i| {
693 let start_idx = query.start_byte_for_pattern(i);
698 let end_idx = query.end_byte_for_pattern(i);
699 let pattern_content = unsafe { query_content.get_unchecked(start_idx..end_idx) };
701 let pattern_query = Query::new(grammar, pattern_content)
704 .expect("unable to create subquery of valid query, this is a bug");
705
706 let mut cursor = QueryCursor::new();
707 let pattern_has_matches = pattern_query
708 .matches(&root, source, &mut cursor)
709 .next()
710 .is_some();
711 if !pattern_has_matches {
712 let trimmed_end_idx = pattern_content
713 .rmatch_indices('\n')
714 .map(|(i, _)| i)
715 .find_map(|i| {
716 let line = pattern_content[i..].trim_start();
717 let is_pattern_line = !line.is_empty() && !line.starts_with(';');
718 is_pattern_line.then_some(start_idx + i + 2)
719 })
720 .unwrap_or(pattern_content.len());
721 return Some(LabeledSpan::new_with_span(
722 Some("unmatched".into()),
723 SourceSpan::from(start_idx..trimmed_end_idx),
724 ));
725 }
726 None
727 })
728 .collect();
729
730 let ok_patterns = pattern_count - missing_patterns.len();
731 let cover_percentage = ok_patterns as f32 / pattern_count as f32;
732 Ok(CoverageData {
733 cover_percentage,
734 missing_patterns,
735 })
736}
737
738#[cfg(target_arch = "wasm32")]
739pub fn check_query_coverage(
740 _input_content: &str,
741 _original_query: &TopiaryQuery,
742 _grammar: &topiary_tree_sitter_facade::Language,
743) -> FormatterResult<CoverageData> {
744 unimplemented!();
745}