dprint-plugin-json 0.21.3

JSON formatter for dprint.
Documentation
use super::super::configuration::Configuration;
use super::token_finder::TokenFinder;
use jsonc_parser::CommentMap;
use jsonc_parser::ast::*;
use jsonc_parser::common::Ranged;
use std::collections::HashSet;
use text_lines::TextLines;

pub struct Context<'a, 'b> {
  pub config: &'b Configuration,
  pub text: &'b str,
  pub text_info: TextLines,
  pub is_jsonc: bool,
  pub handled_comments: HashSet<usize>,
  pub parent_stack: Vec<Node<'a, 'a>>,
  pub current_node: Option<Node<'a, 'a>>,
  pub comments: &'b CommentMap<'a>,
  pub token_finder: TokenFinder<'a>,
}

impl<'a, 'b> Context<'a, 'b> {
  pub fn has_handled_comment(&self, comment: &Comment) -> bool {
    self.handled_comments.contains(&comment.start())
  }

  pub fn mark_comment_handled(&mut self, comment: &Comment) {
    self.handled_comments.insert(comment.start());
  }

  pub fn start_line_with_comments(&mut self, node: &dyn Ranged) -> usize {
    // The start position with comments is the next non-whitespace position
    // after the previous token's trailing comments. The trailing comments
    // are similar to the Roslyn definition where it's any comments on the
    // same line or a single multi-line block comment that begins on the trailing line.
    let start = node.start();
    if let Some(leading_comments) = self.comments.get(&start) {
      if let Some(previous_token) = self.token_finder.get_previous_token(node) {
        let previous_end_line = self.text_info.line_index(previous_token.end());
        let mut past_trailing_comments = false;
        for comment in leading_comments.iter() {
          let comment_start_line = self.text_info.line_index(comment.start());
          if !past_trailing_comments && comment_start_line <= previous_end_line {
            let comment_end_line = self.text_info.line_index(comment.end());
            if comment_end_line > previous_end_line {
              past_trailing_comments = true;
            }
          } else {
            return comment_start_line;
          }
        }

        self.text_info.line_index(node.start())
      } else {
        self
          .text_info
          .line_index(leading_comments.iter().next().unwrap().start())
      }
    } else {
      self.text_info.line_index(node.start())
    }
  }

  pub fn end_line_with_comments(&mut self, node: &dyn Ranged) -> usize {
    // start searching from after the trailing comma if it exists
    let (search_end, previous_end_line) = self
      .token_finder
      .get_next_token_if_comma(node)
      .map(|x| (x.end(), self.text_info.line_index(x.end())))
      .unwrap_or((node.end(), self.text_info.line_index(node.end())));

    if let Some(trailing_comments) = self.comments.get(&search_end) {
      for comment in trailing_comments.iter() {
        // optimization
        if comment.kind() == CommentKind::Line {
          break;
        }

        let comment_start_line = self.text_info.line_index(comment.start());
        if comment_start_line <= previous_end_line {
          let comment_end_line = self.text_info.line_index(comment.end());
          if comment_end_line > previous_end_line {
            return comment_end_line; // should only include the first multi-line comment block
          }
        } else {
          break;
        }
      }
    }

    previous_end_line
  }

  #[cfg(debug_assertions)]
  pub fn assert_text(&self, start_pos: usize, end_pos: usize, expected_text: &str) {
    let actual_text = &self.text[start_pos..end_pos];
    if actual_text != expected_text {
      panic!("Expected text `{}`, but found `{}`", expected_text, actual_text)
    }
  }
}