auto_lsp_core/ast/
node.rs

1/*
2This file is part of auto-lsp.
3Copyright (C) 2025 CLAUZEL Adrien
4
5auto-lsp is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program.  If not, see <http://www.gnu.org/licenses/>
17*/
18
19use crate::errors::PositionError;
20use downcast_rs::{impl_downcast, DowncastSync};
21use std::cmp::Ordering;
22use std::sync::Arc;
23use tree_sitter::Node;
24
25/// Trait representing an AST node.
26pub trait AstNode: std::fmt::Debug + Send + Sync + DowncastSync {
27    /// Returns `true` if a given [`tree_sitter::Node`] matches this node type.
28    fn contains(node: &Node) -> bool
29    where
30        Self: Sized;
31
32    /// Returns the inner node as a trait object.
33    ///
34    /// If the node is a struct, returns self.    
35    fn lower(&self) -> &dyn AstNode;
36
37    /// Returns the unique ID of this node.
38    ///
39    /// IDs are assigned when [`TryFrom`] is called and are unique within the tree.
40    fn get_id(&self) -> usize;
41
42    /// Returns the ID of the parent node, if any.
43    fn get_parent_id(&self) -> Option<usize>;
44
45    /// Returns the [`tree_sitter::Range`] of this node.
46    fn get_range(&self) -> &tree_sitter::Range;
47
48    /// Returns the LSP-compatible range of this node.
49    fn get_lsp_range(&self) -> lsp_types::Range {
50        let range = self.get_range();
51        lsp_types::Range {
52            start: lsp_types::Position {
53                line: range.start_point.row as u32,
54                character: range.start_point.column as u32,
55            },
56            end: lsp_types::Position {
57                line: range.end_point.row as u32,
58                character: range.end_point.column as u32,
59            },
60        }
61    }
62
63    /// Returns the start position in LSP format.
64    fn get_start_position(&self) -> lsp_types::Position {
65        let range = self.get_range();
66        lsp_types::Position {
67            line: range.start_point.row as u32,
68            character: range.start_point.column as u32,
69        }
70    }
71
72    /// Returns the end position in LSP format.
73    fn get_end_position(&self) -> lsp_types::Position {
74        let range = self.get_range();
75        lsp_types::Position {
76            line: range.end_point.row as u32,
77            character: range.end_point.column as u32,
78        }
79    }
80
81    /// Returns the UTF-8 text slice corresponding to this node.
82    ///
83    /// Returns:
84    /// - `Ok(&str)` with the node's source text
85    /// - `Err(PositionError::WrongTextRange)` if the range is invalid
86    /// - `Err(PositionError::UTF8Error)` if the byte slice is not valid UTF-8
87    fn get_text<'a>(&self, source_code: &'a [u8]) -> Result<&'a str, PositionError> {
88        let range = self.get_range();
89        let range = range.start_byte..range.end_byte;
90        match source_code.get(range.start..range.end) {
91            Some(text) => match std::str::from_utf8(text) {
92                Ok(text) => Ok(text),
93                Err(utf8_error) => Err(PositionError::UTF8Error { range, utf8_error }),
94            },
95            None => Err(PositionError::WrongTextRange { range }),
96        }
97    }
98
99    /// Retrieves the parent node, if present, from the node list.
100    ///
101    /// The node list must be sorted by ID.
102    fn get_parent<'a>(&'a self, nodes: &'a [Arc<dyn AstNode>]) -> Option<&'a Arc<dyn AstNode>> {
103        match nodes.first() {
104            Some(first) => {
105                assert_eq!(
106                    first.get_id(),
107                    0,
108                    "get_parent called on an unsorted node list"
109                );
110                nodes.get(self.get_parent_id()?)
111            }
112            None => None,
113        }
114    }
115}
116
117impl_downcast!(AstNode);
118
119impl PartialEq for dyn AstNode {
120    fn eq(&self, other: &Self) -> bool {
121        self.get_range().eq(other.get_range()) && self.get_id().eq(&other.get_id())
122    }
123}
124
125impl Eq for dyn AstNode {}
126
127impl PartialOrd for dyn AstNode {
128    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
129        Some(self.get_id().cmp(&other.get_id()))
130    }
131}
132
133impl Ord for dyn AstNode {
134    fn cmp(&self, other: &Self) -> Ordering {
135        self.get_id().cmp(&other.get_id())
136    }
137}