use std::fmt::Debug;
use std::iter::Peekable;
use ruff_formatter::{SourceCode, SourceCodeSlice};
use ruff_python_ast::{AnyNodeRef, Identifier};
use ruff_python_ast::{Mod, Stmt};
#[allow(clippy::wildcard_imports)]
use ruff_python_ast::visitor::source_order::*;
use ruff_python_trivia::{CommentLinePosition, CommentRanges};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::comments::node_key::NodeRefEqualityKey;
use crate::comments::placement::place_comment;
use crate::comments::{CommentsMap, SourceComment};
pub(crate) fn collect_comments<'a>(
root: &'a Mod,
source_code: SourceCode<'a>,
comment_ranges: &'a CommentRanges,
) -> Vec<DecoratedComment<'a>> {
let mut collector = CommentsVecBuilder::default();
CommentsVisitor::new(source_code, comment_ranges, &mut collector).visit(AnyNodeRef::from(root));
collector.comments
}
pub(super) struct CommentsVisitor<'a, 'builder> {
builder: &'builder mut (dyn PushComment<'a> + 'a),
source_code: SourceCode<'a>,
parents: Vec<AnyNodeRef<'a>>,
preceding_node: Option<AnyNodeRef<'a>>,
comment_ranges: Peekable<std::slice::Iter<'a, TextRange>>,
}
impl<'a, 'builder> CommentsVisitor<'a, 'builder> {
pub(super) fn new(
source_code: SourceCode<'a>,
comment_ranges: &'a CommentRanges,
builder: &'builder mut (dyn PushComment<'a> + 'a),
) -> Self {
Self {
builder,
source_code,
parents: Vec::new(),
preceding_node: None,
comment_ranges: comment_ranges.iter().peekable(),
}
}
pub(super) fn visit(mut self, root: AnyNodeRef<'a>) {
if self.enter_node(root).is_traverse() {
root.visit_source_order(&mut self);
}
self.leave_node(root);
}
fn can_skip(&mut self, node_end: TextSize) -> bool {
self.comment_ranges
.peek()
.is_none_or(|next_comment| next_comment.start() >= node_end)
}
}
impl<'ast> SourceOrderVisitor<'ast> for CommentsVisitor<'ast, '_> {
fn enter_node(&mut self, node: AnyNodeRef<'ast>) -> TraversalSignal {
let node_range = node.range();
let enclosing_node = self.parents.last().copied().unwrap_or(node);
while let Some(comment_range) = self.comment_ranges.peek().copied() {
if comment_range.end() > node_range.start() {
break;
}
let comment = DecoratedComment {
enclosing: enclosing_node,
preceding: self.preceding_node,
following: Some(node),
parent: self.parents.iter().rev().nth(1).copied(),
line_position: CommentLinePosition::for_range(
*comment_range,
self.source_code.as_str(),
),
slice: self.source_code.slice(*comment_range),
};
self.builder.push_comment(comment);
self.comment_ranges.next();
}
self.preceding_node = None;
self.parents.push(node);
if self.can_skip(node_range.end()) {
TraversalSignal::Skip
} else {
TraversalSignal::Traverse
}
}
fn leave_node(&mut self, node: AnyNodeRef<'ast>) {
self.parents.pop();
let node_end = node.end();
let is_root = self.parents.is_empty();
while let Some(comment_range) = self.comment_ranges.peek().copied() {
if comment_range.start() >= node_end && !is_root {
break;
}
let comment = DecoratedComment {
enclosing: node,
parent: self.parents.last().copied(),
preceding: self.preceding_node,
following: None,
line_position: CommentLinePosition::for_range(
*comment_range,
self.source_code.as_str(),
),
slice: self.source_code.slice(*comment_range),
};
self.builder.push_comment(comment);
self.comment_ranges.next();
}
self.preceding_node = Some(node);
}
fn visit_body(&mut self, body: &'ast [Stmt]) {
match body {
[] => {
}
[only] => self.visit_stmt(only),
[first, .., last] => {
if self.can_skip(last.end()) {
self.visit_stmt(first);
self.preceding_node = Some(last.into());
} else {
walk_body(self, body);
}
}
}
}
fn visit_identifier(&mut self, _identifier: &'ast Identifier) {
}
}
#[derive(Debug, Clone)]
pub(crate) struct DecoratedComment<'a> {
enclosing: AnyNodeRef<'a>,
preceding: Option<AnyNodeRef<'a>>,
following: Option<AnyNodeRef<'a>>,
parent: Option<AnyNodeRef<'a>>,
line_position: CommentLinePosition,
slice: SourceCodeSlice,
}
impl<'a> DecoratedComment<'a> {
pub(crate) fn enclosing_node(&self) -> AnyNodeRef<'a> {
self.enclosing
}
pub(super) fn enclosing_parent(&self) -> Option<AnyNodeRef<'a>> {
self.parent
}
pub(crate) fn preceding_node(&self) -> Option<AnyNodeRef<'a>> {
self.preceding
}
pub(crate) fn following_node(&self) -> Option<AnyNodeRef<'a>> {
self.following
}
pub(super) fn line_position(&self) -> CommentLinePosition {
self.line_position
}
pub(crate) fn slice(&self) -> &SourceCodeSlice {
&self.slice
}
}
impl Ranged for DecoratedComment<'_> {
#[inline]
fn range(&self) -> TextRange {
self.slice.range()
}
}
impl From<DecoratedComment<'_>> for SourceComment {
fn from(decorated: DecoratedComment) -> Self {
Self::new(decorated.slice, decorated.line_position)
}
}
#[derive(Debug)]
pub(super) enum CommentPlacement<'a> {
Leading {
node: AnyNodeRef<'a>,
comment: SourceComment,
},
Trailing {
node: AnyNodeRef<'a>,
comment: SourceComment,
},
Dangling {
node: AnyNodeRef<'a>,
comment: SourceComment,
},
Default(DecoratedComment<'a>),
}
impl<'a> CommentPlacement<'a> {
#[inline]
pub(super) fn leading(node: impl Into<AnyNodeRef<'a>>, comment: DecoratedComment) -> Self {
Self::Leading {
node: node.into(),
comment: comment.into(),
}
}
pub(super) fn dangling(node: impl Into<AnyNodeRef<'a>>, comment: DecoratedComment) -> Self {
Self::Dangling {
node: node.into(),
comment: comment.into(),
}
}
#[inline]
pub(super) fn trailing(node: impl Into<AnyNodeRef<'a>>, comment: DecoratedComment) -> Self {
Self::Trailing {
node: node.into(),
comment: comment.into(),
}
}
pub(super) fn or_else<F: FnOnce(DecoratedComment<'a>) -> Self>(self, f: F) -> Self {
match self {
Self::Default(comment) => f(comment),
_ => self,
}
}
}
pub(super) trait PushComment<'a> {
fn push_comment(&mut self, placement: DecoratedComment<'a>);
}
#[derive(Debug, Default)]
struct CommentsVecBuilder<'a> {
comments: Vec<DecoratedComment<'a>>,
}
impl<'a> PushComment<'a> for CommentsVecBuilder<'a> {
fn push_comment(&mut self, placement: DecoratedComment<'a>) {
self.comments.push(placement);
}
}
pub(super) struct CommentsMapBuilder<'a> {
comments: CommentsMap<'a>,
comment_ranges: &'a CommentRanges,
source: &'a str,
}
impl<'a> PushComment<'a> for CommentsMapBuilder<'a> {
fn push_comment(&mut self, placement: DecoratedComment<'a>) {
let placement = place_comment(placement, self.comment_ranges, self.source);
match placement {
CommentPlacement::Leading { node, comment } => {
self.push_leading_comment(node, comment);
}
CommentPlacement::Trailing { node, comment } => {
self.push_trailing_comment(node, comment);
}
CommentPlacement::Dangling { node, comment } => {
self.push_dangling_comment(node, comment);
}
CommentPlacement::Default(comment) => {
match comment.line_position() {
CommentLinePosition::EndOfLine => {
match (comment.preceding_node(), comment.following_node()) {
(Some(preceding), Some(_)) => {
self.push_trailing_comment(preceding, comment);
}
(Some(preceding), None) => {
self.push_trailing_comment(preceding, comment);
}
(None, Some(following)) => {
self.push_leading_comment(following, comment);
}
(None, None) => {
self.push_dangling_comment(comment.enclosing_node(), comment);
}
}
}
CommentLinePosition::OwnLine => {
match (comment.preceding_node(), comment.following_node()) {
(_, Some(following)) => {
self.push_leading_comment(following, comment);
}
(Some(preceding), None) => {
self.push_trailing_comment(preceding, comment);
}
(None, None) => {
self.push_dangling_comment(comment.enclosing_node(), comment);
}
}
}
}
}
}
}
}
impl<'a> CommentsMapBuilder<'a> {
pub(crate) fn new(source: &'a str, comment_ranges: &'a CommentRanges) -> Self {
Self {
comments: CommentsMap::default(),
comment_ranges,
source,
}
}
pub(crate) fn finish(self) -> CommentsMap<'a> {
self.comments
}
fn push_leading_comment(&mut self, node: AnyNodeRef<'a>, comment: impl Into<SourceComment>) {
self.comments
.push_leading(NodeRefEqualityKey::from_ref(node), comment.into());
}
fn push_dangling_comment(&mut self, node: AnyNodeRef<'a>, comment: impl Into<SourceComment>) {
self.comments
.push_dangling(NodeRefEqualityKey::from_ref(node), comment.into());
}
fn push_trailing_comment(&mut self, node: AnyNodeRef<'a>, comment: impl Into<SourceComment>) {
self.comments
.push_trailing(NodeRefEqualityKey::from_ref(node), comment.into());
}
}