topiary_core/
tree_sitter.rs

1// WASM build doesn't use topiary_tree_sitter_facade::QueryMatch or
2// streaming_iterator::StreamingIterator
3#![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/// Supported visualisation formats
23#[derive(Clone, Copy, Debug)]
24pub enum Visualisation {
25    GraphViz,
26    Json,
27}
28
29/// Refers to a position within the code. Used for error reporting, and for
30/// comparing input with formatted output. The numbers are 1-based, because that
31/// is how editors usually refer to a position. Derived from tree_sitter::Point.
32#[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/// Topiary often needs both the tree-sitter `Query` and the original content
45/// belonging to the file from which the query was parsed. This struct is a simple
46/// convenience wrapper that combines the `Query` with its original string.
47#[derive(Debug)]
48pub struct TopiaryQuery {
49    pub query: Query,
50    pub query_content: String,
51}
52
53impl TopiaryQuery {
54    /// Creates a new `TopiaryQuery` from a tree-sitter language/grammar and the
55    /// contents of the query file.
56    ///
57    /// # Errors
58    ///
59    /// This function will return an error if tree-sitter failed to parse the
60    /// query file.
61    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    /// Calculates the provided position of the Pattern in the query source file
75    /// from the byte offset of the pattern in the query.
76    #[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// Simplified syntactic node struct, for the sake of serialisation.
111#[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
148/// Extension trait for [`Node`] to allow for 1-based display in logs.
149///
150/// (Can't be done as a [`Display`] impl on [`Node`] directly, since that would
151/// run into orphan issues. An alternative that would work is a [`Display`] impl
152/// on a wrapper struct.)
153pub trait NodeExt {
154    /// Produce a textual representation with 1-based row/column indexes.
155    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)]
186// A struct to statically store the public fields of query match results,
187// to avoid running queries twice.
188struct 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            // .node() doesn't provide access to the inner [`tree_sitter`]
205            // object. As a result, we can't get the index out directly, so we
206            // skip it for now.
207            write!(f, "{}", capture.node().display_one_based())?;
208        }
209        write!(f, " ] }}")?;
210        Ok(())
211    }
212}
213
214#[derive(Clone, Debug, PartialEq)]
215// A struct to store the result of a query coverage check
216pub 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    /// Returns an error if coverage is not 100%
235    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
270/// Applies a query to an input content and returns a collection of atoms.
271///
272/// # Errors
273///
274/// This function can return an error if:
275/// - The input content cannot be parsed by the grammar.
276/// - The query content cannot be parsed by the grammar.
277/// - The input exhaustivity check fails.
278/// - A found predicate could not be parsed or is malformed.
279/// - A unknown capture name was encountered in the query.
280pub 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
290/// Applies a query to a tree and returns a collection of atoms.
291///
292/// # Errors
293///
294/// This function can return an error if:
295/// - The query content cannot be parsed by the grammar.
296/// - The input exhaustivity check fails.
297/// - A found predicate could not be parsed or is malformed.
298/// - A unknown capture name was encountered in the query.
299pub 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    // Match queries
308    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)] // This is not a normal iterator
314    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    // Find the ids of all tree-sitter nodes that were identified as a leaf
324    // We want to avoid recursing into them in the collect_leaves function.
325    let specified_leaf_nodes: HashSet<usize> = collect_leaf_ids(&matches, capture_names.clone());
326
327    // The Flattening: collects all terminal nodes of the tree-sitter tree in a Vec
328    let mut atoms = AtomCollection::collect_leaves(&root, source, specified_leaf_nodes)?;
329
330    log::debug!("List of atoms before formatting: {atoms:?}");
331
332    // Memoization of the pattern positions
333    let mut pattern_positions: Vec<Option<Position>> = Vec::new();
334
335    // The web bindings for tree-sitter do not have support for pattern_count, so instead we will resize as needed
336    // Only reallocate if we are actually going to use the vec
337    #[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    // If there are more than one capture per match, it generally means that we
343    // want to use the last capture. For example
344    // (
345    //   (enum_item) @append_hardline .
346    //   (line_comment)? @append_hardline
347    // )
348    // means we want to append a hardline at
349    // the end, but we don't know if we get a line_comment capture or not.
350    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        // NOTE: Only performed if logging is enabled to avoid unnecessary computation of Position
359        if log::log_enabled!(log::Level::Info) {
360            #[cfg(target_arch = "wasm32")]
361            // Resize the pattern_positions vector if we need to store more positions
362            if m.pattern_index >= pattern_positions.len() {
363                pattern_positions.resize(m.pattern_index + 1, None);
364            }
365
366            // Fetch from pattern_positions, otherwise insert
367            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 any capture is a do_nothing, then do nothing.
383        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    // Now apply all atoms in prepend and append to the leaf nodes.
397    atoms.apply_prepends_and_appends();
398
399    Ok(atoms)
400}
401
402/// Represents the code span for a given tree-sitter node
403#[derive(Debug)]
404pub struct NodeSpan {
405    pub(crate) range: Range,
406    // source code contents
407    pub content: Option<String>,
408    // source code location
409    pub location: Option<String>,
410    pub language: &'static str,
411}
412
413impl NodeSpan {
414    /// Creates a new [`Self`] without source text or language
415    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    /// Creates a [`SourceSpan`] from the node's byte range
424    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    /// Adds source text to [`Self`] for adding context to display
433    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    /// Adds span origin name to [`Self`] for adding context to display
443    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
457/// Parses some string into a syntax tree, given a tree-sitter grammar.
458pub 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    // Fail parsing if we don't get a complete syntax tree.
473    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
481// returns first error node encountered
482fn 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
494/// Collects the IDs of all leaf nodes in a set of query matches.
495///
496/// This function takes a slice of `LocalQueryMatch` and a slice of capture names,
497/// and returns a `HashSet` of node IDs that are matched by the "leaf" capture name.
498fn 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
511/// Handles a query predicate and returns a new set of query predicates with the corresponding field updated.
512///
513/// # Arguments
514///
515/// * `predicate` - A reference to a `QueryPredicate` object that represents a predicate in a query pattern.
516/// * `predicates` - A reference to a `QueryPredicates` object that holds the current state of the query predicates.
517///
518/// # Returns
519///
520/// A `FormatterResult` that contains either a new `QueryPredicates` object with the updated field, or a `FormatterError` if the predicate is invalid or missing an argument.
521///
522/// # Errors
523///
524/// This function will return an error if:
525///
526/// * The predicate operator is not one of the supported ones.
527/// * The predicate operator requires an argument but none is provided.
528fn 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
596/// Checks the validity of the query predicates.
597///
598/// This function ensures that the query predicates do not contain more than one
599/// of the following: #single_line_only, #multi_line_only, #single_line_scope_only,
600/// or #multi_line_scope_only. These predicates are incompatible with each other
601/// and would result in an invalid query.
602///
603/// # Arguments
604///
605/// * `predicates` - A reference to a QueryPredicates struct that holds the query predicates.
606///
607/// # Errors
608///
609/// If the query predicates contain more than one incompatible predicate, this function
610/// returns a FormatterError::Query with a descriptive message.
611fn 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"))]
636/// Check if the input tests all patterns in the query, by successively disabling
637/// all patterns. If disabling a pattern does not decrease the number of matches,
638/// then that pattern originally matched nothing in the input.
639pub 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    // Match queries
653    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 there are no queries at all (e.g., when debugging) return early
664    // rather than dividing by zero
665    if pattern_count == 0 {
666        let cover_percentage = 0.0;
667        return Ok(CoverageData {
668            cover_percentage,
669            missing_patterns,
670        });
671    }
672
673    // This particular test avoids a SIGSEGV error that occurs when trying
674    // to count the matches of an empty query (see #481)
675    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            // The TreeSitter API doesn't support splitting a query per pattern subqueries.
694            // We do so manually here by using the `query_content` and `query` fields for the same
695            // `TopiaryQuery` object.
696
697            let start_idx = query.start_byte_for_pattern(i);
698            let end_idx = query.end_byte_for_pattern(i);
699            // SAFETY: the index range provided is returned directly from the inner `Query` object
700            let pattern_content = unsafe { query_content.get_unchecked(start_idx..end_idx) };
701            // All child patterns of a non-empty `Query` object created through `Query::new` are guaranteed
702            // to create their own valid `Query` by referencing their pattern byte range.
703            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}