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