use crate::languages::{LanguageHandler, get_handler};
use crate::rules::preservation::PreservationRule;
use tree_sitter::Node;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommentInfo {
pub start_byte: usize,
pub end_byte: usize,
pub start_row: usize,
pub end_row: usize,
pub node_type: &'static str,
pub should_preserve: bool,
pub is_documentation: bool,
}
impl CommentInfo {
#[must_use]
pub fn new(node: Node) -> Self {
Self {
start_byte: node.start_byte(),
end_byte: node.end_byte(),
start_row: node.start_position().row,
end_row: node.end_position().row,
node_type: node.kind(),
should_preserve: false,
is_documentation: false,
}
}
#[must_use]
pub const fn with_documentation(mut self, is_documentation: bool) -> Self {
self.is_documentation = is_documentation;
self
}
#[must_use]
pub const fn with_preservation(mut self, should_preserve: bool) -> Self {
self.should_preserve = should_preserve;
self
}
#[inline]
pub fn content<'a>(&self, source: &'a str) -> &'a str {
&source[self.start_byte..self.end_byte]
}
}
pub struct CommentVisitor<'a> {
source: &'a str,
preservation_rules: &'a [PreservationRule],
comments: Vec<CommentInfo>,
comment_node_types: &'a [String],
doc_comment_node_types: &'a [String],
language_handler: Box<dyn LanguageHandler>,
}
impl<'a> CommentVisitor<'a> {
#[must_use]
pub fn new_with_language(
source: &'a str,
preservation_rules: &'a [PreservationRule],
comment_node_types: &'a [String],
doc_comment_node_types: &'a [String],
language_name: &str,
) -> Self {
let language_handler = get_handler(language_name);
Self {
source,
preservation_rules,
comments: Vec::with_capacity(32),
comment_node_types,
doc_comment_node_types,
language_handler,
}
}
pub fn visit_node(&mut self, node: Node) {
self.visit_node_recursive(node, None);
}
fn visit_node_recursive(&mut self, node: Node, parent: Option<Node>) {
if self.is_comment_node(&node, parent) {
let mut comment_info = CommentInfo::new(node);
if let Some(is_doc) =
self.language_handler
.is_documentation_comment(&node, parent, self.source)
{
comment_info = comment_info.with_documentation(is_doc);
}
let forced_preserve = self
.language_handler
.should_preserve_comment(&node, parent, self.source)
.unwrap_or(false);
let content = comment_info.content(self.source);
let should_preserve =
forced_preserve || self.should_preserve_comment(&comment_info, content);
let comment_with_preservation = comment_info.with_preservation(should_preserve);
self.comments.push(comment_with_preservation);
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.visit_node_recursive(child, Some(node));
}
}
#[must_use]
pub fn get_comments_to_remove(&self) -> Vec<&CommentInfo> {
self.comments
.iter()
.filter(|comment| !comment.should_preserve)
.collect()
}
fn is_comment_node(&self, node: &Node, parent: Option<Node>) -> bool {
let kind = node.kind();
if self
.comment_node_types
.iter()
.any(|node_type| node_type == kind)
{
return true;
}
if self
.doc_comment_node_types
.iter()
.any(|node_type| node_type == kind)
{
if let Some(is_doc) =
self.language_handler
.is_documentation_comment(node, parent, self.source)
{
return is_doc;
}
return true;
}
false
}
fn should_preserve_comment(&self, comment: &CommentInfo, content: &str) -> bool {
for rule in self.preservation_rules {
if rule.matches(comment, content) {
return true;
}
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rules::preservation::PreservationRule;
fn create_mock_comment(node_type: &'static str) -> CommentInfo {
CommentInfo {
start_byte: 0,
end_byte: 0,
start_row: 0,
end_row: 0,
node_type,
should_preserve: false,
is_documentation: false,
}
}
#[test]
fn test_comment_info_creation() {
let comment = create_mock_comment("line_comment");
assert_eq!(comment.node_type, "line_comment");
assert!(!comment.should_preserve);
}
#[test]
fn test_comment_preservation() {
let comment = create_mock_comment("line_comment");
let preserved_comment = comment.with_preservation(true);
assert!(preserved_comment.should_preserve);
}
#[test]
fn test_visitor_creation() {
let source = "// Test\nfn main() {}";
let rules = vec![PreservationRule::pattern("TODO")];
let comment_types = vec!["comment".to_string(), "line_comment".to_string()];
let doc_types = vec!["doc_comment".to_string()];
let visitor =
CommentVisitor::new_with_language(source, &rules, &comment_types, &doc_types, "test");
assert_eq!(visitor.source, source);
assert_eq!(visitor.comments.len(), 0);
}
#[test]
fn test_get_comments_to_remove() {
let source = "// Test";
let rules = vec![PreservationRule::pattern("TODO")];
let comment_types = vec!["comment".to_string(), "line_comment".to_string()];
let doc_types = vec!["doc_comment".to_string()];
let mut visitor =
CommentVisitor::new_with_language(source, &rules, &comment_types, &doc_types, "test");
visitor
.comments
.push(create_mock_comment("line_comment").with_preservation(true));
visitor
.comments
.push(create_mock_comment("line_comment").with_preservation(false));
let to_remove = visitor.get_comments_to_remove();
assert_eq!(to_remove.len(), 1);
assert!(!to_remove[0].should_preserve);
}
}