Skip to main content

dprint_plugin_jsonc/parser/
context.rs

1use std::collections::HashSet;
2use jsonc_parser::ast::*;
3use jsonc_parser::common::{Ranged};
4use jsonc_parser::CommentMap;
5use super::super::configuration::Configuration;
6use super::token_finder::TokenFinder;
7
8pub struct Context<'a> {
9    pub config: &'a Configuration,
10    pub text: &'a str,
11    pub handled_comments: HashSet<usize>,
12    pub parent_stack: Vec<Node<'a>>,
13    pub current_node: Option<Node<'a>>,
14    pub comments: &'a CommentMap,
15    pub token_finder: TokenFinder<'a>,
16}
17
18impl<'a> Context<'a> {
19    pub fn has_handled_comment(&self, comment: &Comment) -> bool {
20        self.handled_comments.contains(&comment.start())
21    }
22
23    pub fn mark_comment_handled(&mut self, comment: &Comment) {
24        self.handled_comments.insert(comment.start());
25    }
26
27    pub fn start_line_with_comments(&mut self, node: &dyn Ranged) -> usize {
28        // The start position with comments is the next non-whitespace position
29        // after the previous token's trailing comments. The trailing comments
30        // are similar to the Roslyn definition where it's any comments on the
31        // same line or a single multi-line block comment that begins on the trailing line.
32        let start = node.start();
33        if let Some(leading_comments) = self.comments.get(&start) {
34            if let Some(previous_token) = self.token_finder.get_previous_token(node) {
35                let previous_end_line = previous_token.end_line();
36                let mut past_trailing_comments = false;
37                for comment in leading_comments.iter() {
38                    let comment_start_line = comment.start_line();
39                    if !past_trailing_comments && comment_start_line <= previous_end_line {
40                        let comment_end_line = comment.end_line();
41                        if comment_end_line > previous_end_line {
42                            past_trailing_comments = true;
43                        }
44                    } else {
45                        return comment_start_line;
46                    }
47                }
48
49                node.start_line()
50            } else {
51                leading_comments.iter().next().unwrap().start_line()
52            }
53        } else {
54            node.start_line()
55        }
56    }
57
58    pub fn end_line_with_comments(&mut self, node: &dyn Ranged) -> usize {
59        // start searching from after the trailing comma if it exists
60        let (search_end, previous_end_line) = self.token_finder
61            .get_next_token_if_comma(node).map(|x| (x.end(), x.end_line()))
62            .unwrap_or((node.end(), node.end_line()));
63
64        if let Some(trailing_comments) = self.comments.get(&search_end) {
65            for comment in trailing_comments.iter() {
66                // optimization
67                if comment.kind() == CommentKind::Line { break; }
68
69                let comment_start_line = comment.start_line();
70                if comment_start_line <= previous_end_line {
71                    let comment_end_line = comment.end_line();
72                    if comment_end_line > previous_end_line {
73                        return comment_end_line; // should only include the first multi-line comment block
74                    }
75                } else {
76                    break;
77                }
78            }
79        }
80
81        previous_end_line
82    }
83
84    #[cfg(debug_assertions)]
85    pub fn assert_text(&self, start_pos: usize, end_pos: usize, expected_text: &str) {
86        let actual_text = &self.text[start_pos..end_pos];
87        if actual_text != expected_text {
88            panic!("Expected text `{}`, but found `{}`", expected_text, actual_text)
89        }
90    }
91}