dprint_plugin_json/generation/
context.rs

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