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        // Missing node's symbol name as it appears in the grammar ignoring aliases as a string
135        grammar_name: &'static str,
136    },
137    #[error("{error}")]
138    Syntax {
139        range: lsp_types::Range,
140        error: String,
141        affected: String,
142    },
143}
144
145impl From<LexerError> for ParseError {
146    fn from(error: LexerError) -> Self {
147        let range = match &error {
148            LexerError::Missing { range, .. } => *range,
149            LexerError::Syntax { range, .. } => *range,
150        };
151        Self::LexerError { range, error }
152    }
153}
154
155/// Main accumulator for parse errors
156///
157/// This is meant to be used in salsa queries to accumulate parse errors.
158#[derive(Debug)]
159#[salsa::accumulator]
160pub struct ParseErrorAccumulator(pub ParseError);
161
162impl ParseErrorAccumulator {
163    pub fn to_label(
164        &self,
165        source: &Source<&str>,
166        colors: &mut ColorGenerator,
167        report: &mut ReportBuilder<'_, std::ops::Range<usize>>,
168    ) {
169        self.0.to_label(source, colors, report);
170    }
171}
172
173impl From<&ParseErrorAccumulator> for lsp_types::Diagnostic {
174    fn from(error: &ParseErrorAccumulator) -> Self {
175        Self::from(&error.0)
176    }
177}
178
179impl From<&ParseError> for ParseErrorAccumulator {
180    fn from(diagnostic: &ParseError) -> Self {
181        Self(diagnostic.clone())
182    }
183}
184
185impl From<ParseError> for ParseErrorAccumulator {
186    fn from(diagnostic: ParseError) -> Self {
187        Self(diagnostic)
188    }
189}
190
191impl From<&ParseErrorAccumulator> for ParseError {
192    fn from(diagnostic: &ParseErrorAccumulator) -> Self {
193        diagnostic.0.clone()
194    }
195}
196
197impl From<LexerError> for ParseErrorAccumulator {
198    fn from(error: LexerError) -> Self {
199        Self(error.into())
200    }
201}
202
203impl From<AstError> for ParseErrorAccumulator {
204    fn from(error: AstError) -> Self {
205        Self(ParseError::from(error))
206    }
207}
208
209/// Error type for position errors.
210///
211/// Only emitted by methods of the [`crate::document::Document`] struct.
212#[derive(Error, Clone, Debug, PartialEq, Eq)]
213pub enum PositionError {
214    #[error("Failed to find position of offset {offset}, max line length is {length}")]
215    LineOutOfBound { offset: usize, length: usize },
216    #[error("Failed to get position of offset {offset}")]
217    WrongPosition { offset: usize },
218    #[error("Failed to get range of {range:?}: {position_error}")]
219    WrongRange {
220        range: std::ops::Range<usize>,
221        #[source]
222        position_error: Box<PositionError>,
223    },
224    #[error("Failed to get text in {range:?}")]
225    WrongTextRange { range: std::ops::Range<usize> },
226    #[error("Failed to get text in {range:?}: Encountered UTF-8 error {utf8_error}")]
227    UTF8Error {
228        range: std::ops::Range<usize>,
229        utf8_error: Utf8Error,
230    },
231}
232
233/// Error type produced by the runtime - aka the server -.
234#[derive(Error, Clone, Debug, PartialEq, Eq)]
235pub enum RuntimeError {
236    #[error("Document error in {uri}: {error}")]
237    DocumentError {
238        uri: Url,
239        #[source]
240        error: DocumentError,
241    },
242    #[error("Missing initialization options from client")]
243    MissingOptions,
244    #[error("Missing perFileParser object from initialization options")]
245    MissingPerFileParser,
246    #[error(transparent)]
247    DataBaseError(#[from] DataBaseError),
248    #[error(transparent)]
249    FileSystemError(#[from] FileSystemError),
250    #[error(transparent)]
251    ExtensionError(#[from] ExtensionError),
252}
253
254impl From<(&Url, DocumentError)> for RuntimeError {
255    fn from((uri, error): (&Url, DocumentError)) -> Self {
256        RuntimeError::DocumentError {
257            uri: uri.clone(),
258            error,
259        }
260    }
261}
262
263impl From<(&Url, TreeSitterError)> for RuntimeError {
264    fn from((uri, error): (&Url, TreeSitterError)) -> Self {
265        RuntimeError::DocumentError {
266            uri: uri.clone(),
267            error: DocumentError::TreeSitter(error),
268        }
269    }
270}
271
272impl From<(&Url, TexterError)> for RuntimeError {
273    fn from((uri, error): (&Url, TexterError)) -> Self {
274        RuntimeError::DocumentError {
275            uri: uri.clone(),
276            error: DocumentError::Texter(error),
277        }
278    }
279}
280
281/// Error types produced by the server when performing file system operations.
282#[derive(Error, Clone, Debug, PartialEq, Eq)]
283pub enum FileSystemError {
284    #[cfg(windows)]
285    #[error("Invalid host '{host}' for file path: {path}")]
286    FileUrlHost { host: String, path: Url },
287    #[error("Failed to convert url {path} to file path")]
288    FileUrlToFilePath { path: Url },
289    #[error("Failed to convert file path {path} to url")]
290    FilePathToUrl { path: PathBuf },
291    #[error("Failed to get extension of file {path}")]
292    FileExtension { path: Url },
293    #[error("Failed to open file {path}: {error}")]
294    FileOpen { path: Url, error: String },
295    #[error("Failed to read file {path}: {error}")]
296    FileRead { path: Url, error: String },
297    #[error(transparent)]
298    ExtensionError(#[from] ExtensionError),
299}
300
301/// Error type for file extensions and parsers associated with them.
302#[derive(Error, Clone, Debug, PartialEq, Eq)]
303pub enum ExtensionError {
304    #[error("Unknown file extension {extension}, available extensions are: {available:?}")]
305    UnknownExtension {
306        extension: String,
307        available: HashMap<String, String>,
308    },
309    #[error("No parser found for extension {extension}, available parsers are: {available:?}")]
310    UnknownParser {
311        extension: String,
312        available: Vec<&'static str>,
313    },
314}
315
316/// Error type triggered by the database.
317#[derive(Error, Clone, Debug, PartialEq, Eq)]
318pub enum DataBaseError {
319    #[error("Failed to get file {uri}")]
320    FileNotFound { uri: Url },
321    #[error("File {uri} already exists")]
322    FileAlreadyExists { uri: Url },
323    #[error("Document error in {uri}: {error}")]
324    DocumentError {
325        uri: Url,
326        #[source]
327        error: DocumentError,
328    },
329}
330
331impl From<(&Url, DocumentError)> for DataBaseError {
332    fn from((uri, error): (&Url, DocumentError)) -> Self {
333        DataBaseError::DocumentError {
334            uri: uri.clone(),
335            error,
336        }
337    }
338}
339
340impl From<(&Url, TreeSitterError)> for DataBaseError {
341    fn from((uri, error): (&Url, TreeSitterError)) -> Self {
342        DataBaseError::DocumentError {
343            uri: uri.clone(),
344            error: DocumentError::TreeSitter(error),
345        }
346    }
347}
348
349/// Error type for document handling
350///
351/// Produced by an error coming from either tree-sitter or texter.
352#[derive(Error, Clone, Debug, PartialEq, Eq)]
353pub enum DocumentError {
354    #[error(transparent)]
355    TreeSitter(#[from] TreeSitterError),
356    #[error(transparent)]
357    Texter(#[from] TexterError),
358}
359
360#[derive(Error, Clone, Debug, PartialEq, Eq)]
361pub enum TreeSitterError {
362    #[error("Tree sitter failed to parse tree")]
363    TreeSitterParser,
364}
365
366#[derive(Error, Clone, Debug, PartialEq, Eq)]
367pub enum TexterError {
368    #[error("Texter failed to handle document")]
369    TexterError(#[from] texter::error::Error),
370}