1use indexmap::IndexMap;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
6
7#[cfg(feature = "artifact-graph")]
8use crate::execution::{ArtifactCommand, ArtifactGraph, Operation};
9use crate::{
10 execution::DefaultPlanes,
11 lsp::IntoDiagnostic,
12 modules::{ModulePath, ModuleSource},
13 source_range::SourceRange,
14 ModuleId,
15};
16
17#[derive(thiserror::Error, Debug)]
19pub enum ExecError {
20 #[error("{0}")]
21 Kcl(#[from] Box<crate::KclErrorWithOutputs>),
22 #[error("Could not connect to engine: {0}")]
23 Connection(#[from] ConnectionError),
24 #[error("PNG snapshot could not be decoded: {0}")]
25 BadPng(String),
26 #[error("Bad export: {0}")]
27 BadExport(String),
28}
29
30impl From<KclErrorWithOutputs> for ExecError {
31 fn from(error: KclErrorWithOutputs) -> Self {
32 ExecError::Kcl(Box::new(error))
33 }
34}
35
36#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
38#[derive(Debug)]
39pub struct ExecErrorWithState {
40 pub error: ExecError,
41 pub exec_state: Option<crate::execution::ExecState>,
42}
43
44impl ExecErrorWithState {
45 #[cfg_attr(target_arch = "wasm32", expect(dead_code))]
46 pub fn new(error: ExecError, exec_state: crate::execution::ExecState) -> Self {
47 Self {
48 error,
49 exec_state: Some(exec_state),
50 }
51 }
52}
53
54impl ExecError {
55 pub fn as_kcl_error(&self) -> Option<&crate::KclError> {
56 let ExecError::Kcl(k) = &self else {
57 return None;
58 };
59 Some(&k.error)
60 }
61}
62
63impl From<ExecError> for ExecErrorWithState {
64 fn from(error: ExecError) -> Self {
65 Self {
66 error,
67 exec_state: None,
68 }
69 }
70}
71
72impl From<ConnectionError> for ExecErrorWithState {
73 fn from(error: ConnectionError) -> Self {
74 Self {
75 error: error.into(),
76 exec_state: None,
77 }
78 }
79}
80
81#[derive(thiserror::Error, Debug)]
83pub enum ConnectionError {
84 #[error("Could not create a Zoo client: {0}")]
85 CouldNotMakeClient(anyhow::Error),
86 #[error("Could not establish connection to engine: {0}")]
87 Establishing(anyhow::Error),
88}
89
90#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq)]
91#[ts(export)]
92#[serde(tag = "kind", rename_all = "snake_case")]
93pub enum KclError {
94 #[error("lexical: {0:?}")]
95 Lexical(KclErrorDetails),
96 #[error("syntax: {0:?}")]
97 Syntax(KclErrorDetails),
98 #[error("semantic: {0:?}")]
99 Semantic(KclErrorDetails),
100 #[error("import cycle: {0:?}")]
101 ImportCycle(KclErrorDetails),
102 #[error("type: {0:?}")]
103 Type(KclErrorDetails),
104 #[error("i/o: {0:?}")]
105 Io(KclErrorDetails),
106 #[error("unexpected: {0:?}")]
107 Unexpected(KclErrorDetails),
108 #[error("value already defined: {0:?}")]
109 ValueAlreadyDefined(KclErrorDetails),
110 #[error("undefined value: {0:?}")]
111 UndefinedValue(KclErrorDetails),
112 #[error("invalid expression: {0:?}")]
113 InvalidExpression(KclErrorDetails),
114 #[error("engine: {0:?}")]
115 Engine(KclErrorDetails),
116 #[error("internal error, please report to KittyCAD team: {0:?}")]
117 Internal(KclErrorDetails),
118}
119
120impl From<KclErrorWithOutputs> for KclError {
121 fn from(error: KclErrorWithOutputs) -> Self {
122 error.error
123 }
124}
125
126#[derive(Error, Debug, Serialize, ts_rs::TS, Clone, PartialEq)]
127#[error("{error}")]
128#[ts(export)]
129#[serde(rename_all = "camelCase")]
130pub struct KclErrorWithOutputs {
131 pub error: KclError,
132 pub non_fatal: Vec<CompilationError>,
133 #[cfg(feature = "artifact-graph")]
134 pub operations: Vec<Operation>,
135 #[cfg(feature = "artifact-graph")]
136 pub artifact_commands: Vec<ArtifactCommand>,
137 #[cfg(feature = "artifact-graph")]
138 pub artifact_graph: ArtifactGraph,
139 pub filenames: IndexMap<ModuleId, ModulePath>,
140 pub source_files: IndexMap<ModuleId, ModuleSource>,
141 pub default_planes: Option<DefaultPlanes>,
142}
143
144impl KclErrorWithOutputs {
145 #[allow(clippy::too_many_arguments)]
146 pub fn new(
147 error: KclError,
148 non_fatal: Vec<CompilationError>,
149 #[cfg(feature = "artifact-graph")] operations: Vec<Operation>,
150 #[cfg(feature = "artifact-graph")] artifact_commands: Vec<ArtifactCommand>,
151 #[cfg(feature = "artifact-graph")] artifact_graph: ArtifactGraph,
152 filenames: IndexMap<ModuleId, ModulePath>,
153 source_files: IndexMap<ModuleId, ModuleSource>,
154 default_planes: Option<DefaultPlanes>,
155 ) -> Self {
156 Self {
157 error,
158 non_fatal,
159 #[cfg(feature = "artifact-graph")]
160 operations,
161 #[cfg(feature = "artifact-graph")]
162 artifact_commands,
163 #[cfg(feature = "artifact-graph")]
164 artifact_graph,
165 filenames,
166 source_files,
167 default_planes,
168 }
169 }
170 pub fn no_outputs(error: KclError) -> Self {
171 Self {
172 error,
173 non_fatal: Default::default(),
174 #[cfg(feature = "artifact-graph")]
175 operations: Default::default(),
176 #[cfg(feature = "artifact-graph")]
177 artifact_commands: Default::default(),
178 #[cfg(feature = "artifact-graph")]
179 artifact_graph: Default::default(),
180 filenames: Default::default(),
181 source_files: Default::default(),
182 default_planes: Default::default(),
183 }
184 }
185 pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> {
186 let mut source_ranges = self.error.source_ranges();
187
188 let first_source_range = source_ranges
190 .pop()
191 .ok_or_else(|| anyhow::anyhow!("No source ranges found"))?;
192
193 let source = self
194 .source_files
195 .get(&first_source_range.module_id())
196 .cloned()
197 .unwrap_or(ModuleSource {
198 source: code.to_string(),
199 path: self
200 .filenames
201 .get(&first_source_range.module_id())
202 .cloned()
203 .unwrap_or(ModulePath::Main),
204 });
205 let filename = source.path.to_string();
206 let kcl_source = source.source.to_string();
207
208 let mut related = Vec::new();
209 for source_range in source_ranges {
210 let module_id = source_range.module_id();
211 let source = self.source_files.get(&module_id).cloned().unwrap_or(ModuleSource {
212 source: code.to_string(),
213 path: self.filenames.get(&module_id).cloned().unwrap_or(ModulePath::Main),
214 });
215 let error = self.error.override_source_ranges(vec![source_range]);
216 let report = Report {
217 error,
218 kcl_source: source.source.to_string(),
219 filename: source.path.to_string(),
220 };
221 related.push(report);
222 }
223
224 Ok(ReportWithOutputs {
225 error: self,
226 kcl_source,
227 filename,
228 related,
229 })
230 }
231}
232
233impl IntoDiagnostic for KclErrorWithOutputs {
234 fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
235 let message = self.error.get_message();
236 let source_ranges = self.error.source_ranges();
237
238 source_ranges
239 .into_iter()
240 .map(|source_range| {
241 let source = self
242 .source_files
243 .get(&source_range.module_id())
244 .cloned()
245 .unwrap_or(ModuleSource {
246 source: code.to_string(),
247 path: self.filenames.get(&source_range.module_id()).unwrap().clone(),
248 });
249 let mut filename = source.path.to_string();
250 if !filename.starts_with("file://") {
251 filename = format!("file:///{}", filename.trim_start_matches("/"));
252 }
253
254 let related_information = if let Ok(uri) = url::Url::parse(&filename) {
255 Some(vec![tower_lsp::lsp_types::DiagnosticRelatedInformation {
256 location: tower_lsp::lsp_types::Location {
257 uri,
258 range: source_range.to_lsp_range(&source.source),
259 },
260 message: message.to_string(),
261 }])
262 } else {
263 None
264 };
265
266 Diagnostic {
267 range: source_range.to_lsp_range(code),
268 severity: Some(self.severity()),
269 code: None,
270 code_description: None,
272 source: Some("kcl".to_string()),
273 related_information,
274 message: message.clone(),
275 tags: None,
276 data: None,
277 }
278 })
279 .collect()
280 }
281
282 fn severity(&self) -> DiagnosticSeverity {
283 DiagnosticSeverity::ERROR
284 }
285}
286
287#[derive(thiserror::Error, Debug)]
288#[error("{}", self.error.error.get_message())]
289pub struct ReportWithOutputs {
290 pub error: KclErrorWithOutputs,
291 pub kcl_source: String,
292 pub filename: String,
293 pub related: Vec<Report>,
294}
295
296impl miette::Diagnostic for ReportWithOutputs {
297 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
298 let family = match self.error.error {
299 KclError::Lexical(_) => "Lexical",
300 KclError::Syntax(_) => "Syntax",
301 KclError::Semantic(_) => "Semantic",
302 KclError::ImportCycle(_) => "ImportCycle",
303 KclError::Type(_) => "Type",
304 KclError::Io(_) => "I/O",
305 KclError::Unexpected(_) => "Unexpected",
306 KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
307 KclError::UndefinedValue(_) => "UndefinedValue",
308 KclError::InvalidExpression(_) => "InvalidExpression",
309 KclError::Engine(_) => "Engine",
310 KclError::Internal(_) => "Internal",
311 };
312 let error_string = format!("KCL {family} error");
313 Some(Box::new(error_string))
314 }
315
316 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
317 Some(&self.kcl_source)
318 }
319
320 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
321 let iter = self
322 .error
323 .error
324 .source_ranges()
325 .clone()
326 .into_iter()
327 .map(miette::SourceSpan::from)
328 .map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
329 Some(Box::new(iter))
330 }
331
332 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
333 let iter = self.related.iter().map(|r| r as &dyn miette::Diagnostic);
334 Some(Box::new(iter))
335 }
336}
337
338#[derive(thiserror::Error, Debug)]
339#[error("{}", self.error.get_message())]
340pub struct Report {
341 pub error: KclError,
342 pub kcl_source: String,
343 pub filename: String,
344}
345
346impl miette::Diagnostic for Report {
347 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
348 let family = match self.error {
349 KclError::Lexical(_) => "Lexical",
350 KclError::Syntax(_) => "Syntax",
351 KclError::Semantic(_) => "Semantic",
352 KclError::ImportCycle(_) => "ImportCycle",
353 KclError::Type(_) => "Type",
354 KclError::Io(_) => "I/O",
355 KclError::Unexpected(_) => "Unexpected",
356 KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
357 KclError::UndefinedValue(_) => "UndefinedValue",
358 KclError::InvalidExpression(_) => "InvalidExpression",
359 KclError::Engine(_) => "Engine",
360 KclError::Internal(_) => "Internal",
361 };
362 let error_string = format!("KCL {family} error");
363 Some(Box::new(error_string))
364 }
365
366 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
367 Some(&self.kcl_source)
368 }
369
370 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
371 let iter = self
372 .error
373 .source_ranges()
374 .clone()
375 .into_iter()
376 .map(miette::SourceSpan::from)
377 .map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
378 Some(Box::new(iter))
379 }
380}
381
382#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
383#[serde(rename_all = "camelCase")]
384#[error("{message}")]
385#[ts(export)]
386pub struct KclErrorDetails {
387 #[label(collection, "Errors")]
388 pub source_ranges: Vec<SourceRange>,
389 pub backtrace: Vec<BacktraceItem>,
390 #[serde(rename = "msg")]
391 pub message: String,
392}
393
394impl KclErrorDetails {
395 pub fn new(message: String, source_ranges: Vec<SourceRange>) -> KclErrorDetails {
396 let backtrace = source_ranges
397 .iter()
398 .map(|s| BacktraceItem {
399 source_range: *s,
400 fn_name: None,
401 })
402 .collect();
403 KclErrorDetails {
404 source_ranges,
405 backtrace,
406 message,
407 }
408 }
409}
410
411impl KclError {
412 pub fn internal(message: String) -> KclError {
413 KclError::Internal(KclErrorDetails {
414 source_ranges: Default::default(),
415 backtrace: Default::default(),
416 message,
417 })
418 }
419
420 pub fn get_message(&self) -> String {
422 format!("{}: {}", self.error_type(), self.message())
423 }
424
425 pub fn error_type(&self) -> &'static str {
426 match self {
427 KclError::Lexical(_) => "lexical",
428 KclError::Syntax(_) => "syntax",
429 KclError::Semantic(_) => "semantic",
430 KclError::ImportCycle(_) => "import cycle",
431 KclError::Type(_) => "type",
432 KclError::Io(_) => "i/o",
433 KclError::Unexpected(_) => "unexpected",
434 KclError::ValueAlreadyDefined(_) => "value already defined",
435 KclError::UndefinedValue(_) => "undefined value",
436 KclError::InvalidExpression(_) => "invalid expression",
437 KclError::Engine(_) => "engine",
438 KclError::Internal(_) => "internal",
439 }
440 }
441
442 pub fn source_ranges(&self) -> Vec<SourceRange> {
443 match &self {
444 KclError::Lexical(e) => e.source_ranges.clone(),
445 KclError::Syntax(e) => e.source_ranges.clone(),
446 KclError::Semantic(e) => e.source_ranges.clone(),
447 KclError::ImportCycle(e) => e.source_ranges.clone(),
448 KclError::Type(e) => e.source_ranges.clone(),
449 KclError::Io(e) => e.source_ranges.clone(),
450 KclError::Unexpected(e) => e.source_ranges.clone(),
451 KclError::ValueAlreadyDefined(e) => e.source_ranges.clone(),
452 KclError::UndefinedValue(e) => e.source_ranges.clone(),
453 KclError::InvalidExpression(e) => e.source_ranges.clone(),
454 KclError::Engine(e) => e.source_ranges.clone(),
455 KclError::Internal(e) => e.source_ranges.clone(),
456 }
457 }
458
459 pub fn message(&self) -> &str {
461 match &self {
462 KclError::Lexical(e) => &e.message,
463 KclError::Syntax(e) => &e.message,
464 KclError::Semantic(e) => &e.message,
465 KclError::ImportCycle(e) => &e.message,
466 KclError::Type(e) => &e.message,
467 KclError::Io(e) => &e.message,
468 KclError::Unexpected(e) => &e.message,
469 KclError::ValueAlreadyDefined(e) => &e.message,
470 KclError::UndefinedValue(e) => &e.message,
471 KclError::InvalidExpression(e) => &e.message,
472 KclError::Engine(e) => &e.message,
473 KclError::Internal(e) => &e.message,
474 }
475 }
476
477 pub fn backtrace(&self) -> Vec<BacktraceItem> {
478 match self {
479 KclError::Lexical(e)
480 | KclError::Syntax(e)
481 | KclError::Semantic(e)
482 | KclError::ImportCycle(e)
483 | KclError::Type(e)
484 | KclError::Io(e)
485 | KclError::Unexpected(e)
486 | KclError::ValueAlreadyDefined(e)
487 | KclError::UndefinedValue(e)
488 | KclError::InvalidExpression(e)
489 | KclError::Engine(e)
490 | KclError::Internal(e) => e.backtrace.clone(),
491 }
492 }
493
494 pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
495 let mut new = self.clone();
496 match &mut new {
497 KclError::Lexical(e)
498 | KclError::Syntax(e)
499 | KclError::Semantic(e)
500 | KclError::ImportCycle(e)
501 | KclError::Type(e)
502 | KclError::Io(e)
503 | KclError::Unexpected(e)
504 | KclError::ValueAlreadyDefined(e)
505 | KclError::UndefinedValue(e)
506 | KclError::InvalidExpression(e)
507 | KclError::Engine(e)
508 | KclError::Internal(e) => {
509 e.backtrace = source_ranges
510 .iter()
511 .map(|s| BacktraceItem {
512 source_range: *s,
513 fn_name: None,
514 })
515 .collect();
516 e.source_ranges = source_ranges;
517 }
518 }
519
520 new
521 }
522
523 pub(crate) fn set_last_backtrace_fn_name(&self, last_fn_name: Option<String>) -> Self {
524 let mut new = self.clone();
525 match &mut new {
526 KclError::Lexical(e)
527 | KclError::Syntax(e)
528 | KclError::Semantic(e)
529 | KclError::ImportCycle(e)
530 | KclError::Type(e)
531 | KclError::Io(e)
532 | KclError::Unexpected(e)
533 | KclError::ValueAlreadyDefined(e)
534 | KclError::UndefinedValue(e)
535 | KclError::InvalidExpression(e)
536 | KclError::Engine(e)
537 | KclError::Internal(e) => {
538 if let Some(item) = e.backtrace.last_mut() {
539 item.fn_name = last_fn_name;
540 }
541 }
542 }
543
544 new
545 }
546
547 pub(crate) fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
548 let mut new = self.clone();
549 match &mut new {
550 KclError::Lexical(e)
551 | KclError::Syntax(e)
552 | KclError::Semantic(e)
553 | KclError::ImportCycle(e)
554 | KclError::Type(e)
555 | KclError::Io(e)
556 | KclError::Unexpected(e)
557 | KclError::ValueAlreadyDefined(e)
558 | KclError::UndefinedValue(e)
559 | KclError::InvalidExpression(e)
560 | KclError::Engine(e)
561 | KclError::Internal(e) => {
562 if let Some(item) = e.backtrace.last_mut() {
563 item.fn_name = last_fn_name;
564 }
565 e.backtrace.push(BacktraceItem {
566 source_range,
567 fn_name: None,
568 });
569 e.source_ranges.push(source_range);
570 }
571 }
572
573 new
574 }
575}
576
577#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS, thiserror::Error, miette::Diagnostic)]
578#[serde(rename_all = "camelCase")]
579#[ts(export)]
580pub struct BacktraceItem {
581 pub source_range: SourceRange,
582 pub fn_name: Option<String>,
583}
584
585impl std::fmt::Display for BacktraceItem {
586 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
587 if let Some(fn_name) = &self.fn_name {
588 write!(f, "{fn_name}: {:?}", self.source_range)
589 } else {
590 write!(f, "(fn): {:?}", self.source_range)
591 }
592 }
593}
594
595impl IntoDiagnostic for KclError {
596 fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
597 let message = self.get_message();
598 let source_ranges = self.source_ranges();
599
600 let module_id = ModuleId::default();
602 let source_ranges = source_ranges
603 .iter()
604 .filter(|r| r.module_id() == module_id)
605 .collect::<Vec<_>>();
606
607 let mut diagnostics = Vec::new();
608 for source_range in &source_ranges {
609 diagnostics.push(Diagnostic {
610 range: source_range.to_lsp_range(code),
611 severity: Some(self.severity()),
612 code: None,
613 code_description: None,
615 source: Some("kcl".to_string()),
616 related_information: None,
617 message: message.clone(),
618 tags: None,
619 data: None,
620 });
621 }
622
623 diagnostics
624 }
625
626 fn severity(&self) -> DiagnosticSeverity {
627 DiagnosticSeverity::ERROR
628 }
629}
630
631impl From<KclError> for String {
634 fn from(error: KclError) -> Self {
635 serde_json::to_string(&error).unwrap()
636 }
637}
638
639impl From<String> for KclError {
640 fn from(error: String) -> Self {
641 serde_json::from_str(&error).unwrap()
642 }
643}
644
645#[cfg(feature = "pyo3")]
646impl From<pyo3::PyErr> for KclError {
647 fn from(error: pyo3::PyErr) -> Self {
648 KclError::Internal(KclErrorDetails {
649 source_ranges: vec![],
650 backtrace: Default::default(),
651 message: error.to_string(),
652 })
653 }
654}
655
656#[cfg(feature = "pyo3")]
657impl From<KclError> for pyo3::PyErr {
658 fn from(error: KclError) -> Self {
659 pyo3::exceptions::PyException::new_err(error.to_string())
660 }
661}
662
663#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS, PartialEq, Eq)]
665#[ts(export)]
666pub struct CompilationError {
667 #[serde(rename = "sourceRange")]
668 pub source_range: SourceRange,
669 pub message: String,
670 pub suggestion: Option<Suggestion>,
671 pub severity: Severity,
672 pub tag: Tag,
673}
674
675impl CompilationError {
676 pub(crate) fn err(source_range: SourceRange, message: impl ToString) -> CompilationError {
677 CompilationError {
678 source_range,
679 message: message.to_string(),
680 suggestion: None,
681 severity: Severity::Error,
682 tag: Tag::None,
683 }
684 }
685
686 pub(crate) fn fatal(source_range: SourceRange, message: impl ToString) -> CompilationError {
687 CompilationError {
688 source_range,
689 message: message.to_string(),
690 suggestion: None,
691 severity: Severity::Fatal,
692 tag: Tag::None,
693 }
694 }
695
696 pub(crate) fn with_suggestion(
697 self,
698 suggestion_title: impl ToString,
699 suggestion_insert: impl ToString,
700 source_range: Option<SourceRange>,
702 tag: Tag,
703 ) -> CompilationError {
704 CompilationError {
705 suggestion: Some(Suggestion {
706 title: suggestion_title.to_string(),
707 insert: suggestion_insert.to_string(),
708 source_range: source_range.unwrap_or(self.source_range),
709 }),
710 tag,
711 ..self
712 }
713 }
714
715 #[cfg(test)]
716 pub fn apply_suggestion(&self, src: &str) -> Option<String> {
717 let suggestion = self.suggestion.as_ref()?;
718 Some(format!(
719 "{}{}{}",
720 &src[0..suggestion.source_range.start()],
721 suggestion.insert,
722 &src[suggestion.source_range.end()..]
723 ))
724 }
725}
726
727impl From<CompilationError> for KclErrorDetails {
728 fn from(err: CompilationError) -> Self {
729 let backtrace = vec![BacktraceItem {
730 source_range: err.source_range,
731 fn_name: None,
732 }];
733 KclErrorDetails {
734 source_ranges: vec![err.source_range],
735 backtrace,
736 message: err.message,
737 }
738 }
739}
740
741#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, ts_rs::TS)]
742#[ts(export)]
743pub enum Severity {
744 Warning,
745 Error,
746 Fatal,
747}
748
749impl Severity {
750 pub fn is_err(self) -> bool {
751 match self {
752 Severity::Warning => false,
753 Severity::Error | Severity::Fatal => true,
754 }
755 }
756}
757
758#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, ts_rs::TS)]
759#[ts(export)]
760pub enum Tag {
761 Deprecated,
762 Unnecessary,
763 None,
764}
765
766#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS, PartialEq, Eq, JsonSchema)]
767#[ts(export)]
768pub struct Suggestion {
769 pub title: String,
770 pub insert: String,
771 pub source_range: SourceRange,
772}
773
774pub type LspSuggestion = (Suggestion, tower_lsp::lsp_types::Range);
775
776impl Suggestion {
777 pub fn to_lsp_edit(&self, code: &str) -> LspSuggestion {
778 let range = self.source_range.to_lsp_range(code);
779 (self.clone(), range)
780 }
781}