auto_lsp_core/
errors.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 std::{collections::HashMap, path::PathBuf, str::Utf8Error};
20
21use ariadne::{ColorGenerator, Fmt, Label, ReportBuilder, Source};
22use lsp_types::Url;
23use thiserror::Error;
24
25/// Error type coming from either tree-sitter or ast parsing.
26///
27/// This error is only produced by auto-lsp.
28///
29/// [`ParseError`] can be converted to [`lsp_types::Diagnostic`] to be sent to the client.
30///
31/// An ariadne report can be generated from [`ParseError`] using the `to_label` method.
32#[derive(Error, Clone, Debug, PartialEq, Eq)]
33pub enum ParseError {
34    #[error("{error:?}")]
35    LexerError {
36        range: lsp_types::Range,
37        #[source]
38        error: LexerError,
39    },
40    #[error("{error:?}")]
41    AstError {
42        range: lsp_types::Range,
43        #[source]
44        error: AstError,
45    },
46}
47
48impl From<ParseError> for lsp_types::Diagnostic {
49    fn from(error: ParseError) -> Self {
50        (&error).into()
51    }
52}
53
54impl From<&ParseError> for lsp_types::Diagnostic {
55    fn from(error: &ParseError) -> Self {
56        let (range, message) = match error {
57            ParseError::AstError { range, error } => (*range, error.to_string()),
58            ParseError::LexerError { range, error } => (*range, error.to_string()),
59        };
60        lsp_types::Diagnostic {
61            range,
62            severity: Some(lsp_types::DiagnosticSeverity::ERROR),
63            message,
64            code: Some(lsp_types::NumberOrString::String("AUTO_LSP".into())),
65            ..Default::default()
66        }
67    }
68}
69
70impl ParseError {
71    /// Creates a label for the error using ariadne.
72    pub fn to_label(
73        &self,
74        source: &Source<&str>,
75        colors: &mut ColorGenerator,
76        report: &mut ReportBuilder<'_, std::ops::Range<usize>>,
77    ) {
78        let range = match self {
79            ParseError::LexerError { range, .. } => range,
80            ParseError::AstError { range, .. } => range,
81        };
82        let start_line = source.line(range.start.line as usize).unwrap().offset();
83        let end_line = source.line(range.end.line as usize).unwrap().offset();
84        let start = start_line + range.start.character as usize;
85        let end = end_line + range.end.character as usize;
86        let curr_color = colors.next();
87
88        report.add_label(
89            Label::new(start..end)
90                .with_message(format!("{}", self.to_string().fg(curr_color)))
91                .with_color(curr_color),
92        );
93    }
94}
95
96/// Error type for AST parsing.
97#[derive(Error, Clone, Debug, PartialEq, Eq)]
98pub enum AstError {
99    #[error("Unexpected {symbol:?} in {parent_name:?}")]
100    UnexpectedSymbol {
101        range: tree_sitter::Range,
102        symbol: &'static str,
103        parent_name: &'static str,
104    },
105}
106
107impl From<AstError> for ParseError {
108    fn from(error: AstError) -> Self {
109        let range = match &error {
110            AstError::UnexpectedSymbol { range, .. } => lsp_types::Range {
111                start: lsp_types::Position {
112                    line: range.start_point.row as u32,
113                    character: range.start_point.column as u32,
114                },
115                end: lsp_types::Position {
116                    line: range.end_point.row as u32,
117                    character: range.end_point.column as u32,
118                },
119            },
120        };
121        Self::AstError { range, error }
122    }
123}
124
125/// Error type for tree-sitter.
126///
127/// Can either be a syntax error or a missing symbol error.
128#[derive(Error, Clone, Debug, PartialEq, Eq)]
129pub enum LexerError {
130    #[error("{error:?}")]
131    Missing {
132        range: lsp_types::Range,
133        error: String,
134    },
135    #[error("{error:?}")]
136    Syntax {
137        range: lsp_types::Range,
138        error: String,
139    },
140}
141
142impl From<LexerError> for ParseError {
143    fn from(error: LexerError) -> Self {
144        let range = match &error {
145            LexerError::Missing { range, .. } => *range,
146            LexerError::Syntax { range, .. } => *range,
147        };
148        Self::LexerError { range, error }
149    }
150}
151
152/// Main accumulator for parse errors
153///
154/// This is meant to be used in salsa queries to accumulate parse errors.
155#[derive(Debug)]
156#[salsa::accumulator]
157pub struct ParseErrorAccumulator(pub ParseError);
158
159impl ParseErrorAccumulator {
160    pub fn to_label(
161        &self,
162        source: &Source<&str>,
163        colors: &mut ColorGenerator,
164        report: &mut ReportBuilder<'_, std::ops::Range<usize>>,
165    ) {
166        self.0.to_label(source, colors, report);
167    }
168}
169
170impl From<&ParseErrorAccumulator> for lsp_types::Diagnostic {
171    fn from(error: &ParseErrorAccumulator) -> Self {
172        Self::from(&error.0)
173    }
174}
175
176impl From<&ParseError> for ParseErrorAccumulator {
177    fn from(diagnostic: &ParseError) -> Self {
178        Self(diagnostic.clone())
179    }
180}
181
182impl From<ParseError> for ParseErrorAccumulator {
183    fn from(diagnostic: ParseError) -> Self {
184        Self(diagnostic)
185    }
186}
187
188impl From<&ParseErrorAccumulator> for ParseError {
189    fn from(diagnostic: &ParseErrorAccumulator) -> Self {
190        diagnostic.0.clone()
191    }
192}
193
194impl From<LexerError> for ParseErrorAccumulator {
195    fn from(error: LexerError) -> Self {
196        Self(error.into())
197    }
198}
199
200impl From<AstError> for ParseErrorAccumulator {
201    fn from(error: AstError) -> Self {
202        Self(ParseError::from(error))
203    }
204}
205
206/// Error type for position errors.
207///
208/// Only emitted by methods of the [`crate::document::Document`] struct.
209#[derive(Error, Clone, Debug, PartialEq, Eq)]
210pub enum PositionError {
211    #[error("Failed to find position of offset {offset:?}, max line length is {length:?}")]
212    LineOutOfBound { offset: usize, length: usize },
213    #[error("Failed to get position of offset {offset:?}")]
214    WrongPosition { offset: usize },
215    #[error("Failed to get range of {range:?}: {position_error:?}")]
216    WrongRange {
217        range: std::ops::Range<usize>,
218        #[source]
219        position_error: Box<PositionError>,
220    },
221    #[error("Failed to get text in {range:?}")]
222    WrongTextRange { range: std::ops::Range<usize> },
223    #[error("Failed to get text in {range:?}: Encountered UTF-8 error {utf8_error:?}")]
224    UTF8Error {
225        range: std::ops::Range<usize>,
226        utf8_error: Utf8Error,
227    },
228}
229
230/// Error type produced by the runtime - aka the server -.
231#[derive(Error, Clone, Debug, PartialEq, Eq)]
232pub enum RuntimeError {
233    #[error("Document error in {uri:?}: {error:?}")]
234    DocumentError {
235        uri: Url,
236        #[source]
237        error: DocumentError,
238    },
239    #[error("Missing initialization options from client")]
240    MissingOptions,
241    #[error("Missing perFileParser object from initialization options")]
242    MissingPerFileParser,
243    #[error(transparent)]
244    DataBaseError(#[from] DataBaseError),
245    #[error(transparent)]
246    FileSystemError(#[from] FileSystemError),
247    #[error(transparent)]
248    ExtensionError(#[from] ExtensionError),
249}
250
251impl From<(&Url, DocumentError)> for RuntimeError {
252    fn from((uri, error): (&Url, DocumentError)) -> Self {
253        RuntimeError::DocumentError {
254            uri: uri.clone(),
255            error,
256        }
257    }
258}
259
260impl From<(&Url, TreeSitterError)> for RuntimeError {
261    fn from((uri, error): (&Url, TreeSitterError)) -> Self {
262        RuntimeError::DocumentError {
263            uri: uri.clone(),
264            error: DocumentError::TreeSitter(error),
265        }
266    }
267}
268
269impl From<(&Url, TexterError)> for RuntimeError {
270    fn from((uri, error): (&Url, TexterError)) -> Self {
271        RuntimeError::DocumentError {
272            uri: uri.clone(),
273            error: DocumentError::Texter(error),
274        }
275    }
276}
277
278/// Error types produced by the server when performing file system operations.
279#[derive(Error, Clone, Debug, PartialEq, Eq)]
280pub enum FileSystemError {
281    #[cfg(windows)]
282    #[error("Invalid host '{host:?}' for file path: {path:?}")]
283    FileUrlHost { host: String, path: Url },
284    #[error("Failed to convert url {path:?} to file path")]
285    FileUrlToFilePath { path: Url },
286    #[error("Failed to convert file path {path:?} to url")]
287    FilePathToUrl { path: PathBuf },
288    #[error("Failed to get extension of file {path:?}")]
289    FileExtension { path: Url },
290    #[error("Failed to open file {path:?}: {error:?}")]
291    FileOpen { path: Url, error: String },
292    #[error("Failed to read file {path:?}: {error:?}")]
293    FileRead { path: Url, error: String },
294    #[error(transparent)]
295    ExtensionError(#[from] ExtensionError),
296}
297
298/// Error type for file extensions and parsers associated with them.
299#[derive(Error, Clone, Debug, PartialEq, Eq)]
300pub enum ExtensionError {
301    #[error("Unknown file extension {extension:?}, available extensions are: {available:?}")]
302    UnknownExtension {
303        extension: String,
304        available: HashMap<String, String>,
305    },
306    #[error("No parser found for extension {extension:?}, available parsers are: {available:?}")]
307    UnknownParser {
308        extension: String,
309        available: Vec<&'static str>,
310    },
311}
312
313/// Error type triggered by the database.
314#[derive(Error, Clone, Debug, PartialEq, Eq)]
315pub enum DataBaseError {
316    #[error("Failed to get file {uri:?}")]
317    FileNotFound { uri: Url },
318    #[error("File {uri:?} already exists")]
319    FileAlreadyExists { uri: Url },
320    #[error("Document error in {uri:?}: {error:?}")]
321    DocumentError {
322        uri: Url,
323        #[source]
324        error: DocumentError,
325    },
326}
327
328impl From<(&Url, DocumentError)> for DataBaseError {
329    fn from((uri, error): (&Url, DocumentError)) -> Self {
330        DataBaseError::DocumentError {
331            uri: uri.clone(),
332            error,
333        }
334    }
335}
336
337impl From<(&Url, TreeSitterError)> for DataBaseError {
338    fn from((uri, error): (&Url, TreeSitterError)) -> Self {
339        DataBaseError::DocumentError {
340            uri: uri.clone(),
341            error: DocumentError::TreeSitter(error),
342        }
343    }
344}
345
346/// Error type for document handling
347///
348/// Produced by an error coming from either tree-sitter or texter.
349#[derive(Error, Clone, Debug, PartialEq, Eq)]
350pub enum DocumentError {
351    #[error(transparent)]
352    TreeSitter(#[from] TreeSitterError),
353    #[error(transparent)]
354    Texter(#[from] TexterError),
355}
356
357#[derive(Error, Clone, Debug, PartialEq, Eq)]
358pub enum TreeSitterError {
359    #[error("Tree sitter failed to parse tree")]
360    TreeSitterParser,
361}
362
363#[derive(Error, Clone, Debug, PartialEq, Eq)]
364pub enum TexterError {
365    #[error("Texter failed to handle document")]
366    TexterError(#[from] texter::error::Error),
367}