1 /*!
2 This module defines the core of the miette protocol: a series of types and
3 traits that you can implement to get access to miette's (and related library's)
4 full reporting and such features.
5 */
6 use std::{
7     fmt::{self, Display},
8     fs,
9     panic::Location,
10 };
11 
12 #[cfg(feature = "serde")]
13 use serde::{Deserialize, Serialize};
14 
15 use crate::MietteError;
16 
17 /// Adds rich metadata to your Error that can be used by
18 /// [`Report`](crate::Report) to print really nice and human-friendly error
19 /// messages.
20 pub trait Diagnostic: std::error::Error {
21     /// Unique diagnostic code that can be used to look up more information
22     /// about this `Diagnostic`. Ideally also globally unique, and documented
23     /// in the toplevel crate's documentation for easy searching. Rust path
24     /// format (`foo::bar::baz`) is recommended, but more classic codes like
25     /// `E0123` or enums will work just fine.
code<'a>(&'a self) -> Option<Box<dyn Display + 'a>>26     fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
27         None
28     }
29 
30     /// Diagnostic severity. This may be used by
31     /// [`ReportHandler`](crate::ReportHandler)s to change the display format
32     /// of this diagnostic.
33     ///
34     /// If `None`, reporters should treat this as [`Severity::Error`].
severity(&self) -> Option<Severity>35     fn severity(&self) -> Option<Severity> {
36         None
37     }
38 
39     /// Additional help text related to this `Diagnostic`. Do you have any
40     /// advice for the poor soul who's just run into this issue?
help<'a>(&'a self) -> Option<Box<dyn Display + 'a>>41     fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
42         None
43     }
44 
45     /// URL to visit for a more detailed explanation/help about this
46     /// `Diagnostic`.
url<'a>(&'a self) -> Option<Box<dyn Display + 'a>>47     fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
48         None
49     }
50 
51     /// Source code to apply this `Diagnostic`'s [`Diagnostic::labels`] to.
source_code(&self) -> Option<&dyn SourceCode>52     fn source_code(&self) -> Option<&dyn SourceCode> {
53         None
54     }
55 
56     /// Labels to apply to this `Diagnostic`'s [`Diagnostic::source_code`]
labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>>57     fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
58         None
59     }
60 
61     /// Additional related `Diagnostic`s.
related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>>62     fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
63         None
64     }
65 
66     /// The cause of the error.
diagnostic_source(&self) -> Option<&dyn Diagnostic>67     fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
68         None
69     }
70 }
71 
72 macro_rules! box_impls {
73     ($($box_type:ty),*) => {
74         $(
75             impl std::error::Error for $box_type {
76                 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
77                     (**self).source()
78                 }
79 
80                 fn cause(&self) -> Option<&dyn std::error::Error> {
81                     self.source()
82                 }
83             }
84         )*
85     }
86 }
87 
88 box_impls! {
89     Box<dyn Diagnostic>,
90     Box<dyn Diagnostic + Send>,
91     Box<dyn Diagnostic + Send + Sync>
92 }
93 
94 impl<T: Diagnostic + Send + Sync + 'static> From<T>
95     for Box<dyn Diagnostic + Send + Sync + 'static>
96 {
from(diag: T) -> Self97     fn from(diag: T) -> Self {
98         Box::new(diag)
99     }
100 }
101 
102 impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + Send + 'static> {
from(diag: T) -> Self103     fn from(diag: T) -> Self {
104         Box::<dyn Diagnostic + Send + Sync>::from(diag)
105     }
106 }
107 
108 impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + 'static> {
from(diag: T) -> Self109     fn from(diag: T) -> Self {
110         Box::<dyn Diagnostic + Send + Sync>::from(diag)
111     }
112 }
113 
114 impl From<&str> for Box<dyn Diagnostic> {
from(s: &str) -> Self115     fn from(s: &str) -> Self {
116         From::from(String::from(s))
117     }
118 }
119 
120 impl<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
from(s: &str) -> Self121     fn from(s: &str) -> Self {
122         From::from(String::from(s))
123     }
124 }
125 
126 impl From<String> for Box<dyn Diagnostic> {
from(s: String) -> Self127     fn from(s: String) -> Self {
128         let err1: Box<dyn Diagnostic + Send + Sync> = From::from(s);
129         let err2: Box<dyn Diagnostic> = err1;
130         err2
131     }
132 }
133 
134 impl From<String> for Box<dyn Diagnostic + Send + Sync> {
from(s: String) -> Self135     fn from(s: String) -> Self {
136         struct StringError(String);
137 
138         impl std::error::Error for StringError {}
139         impl Diagnostic for StringError {}
140 
141         impl Display for StringError {
142             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143                 Display::fmt(&self.0, f)
144             }
145         }
146 
147         // Purposefully skip printing "StringError(..)"
148         impl fmt::Debug for StringError {
149             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150                 fmt::Debug::fmt(&self.0, f)
151             }
152         }
153 
154         Box::new(StringError(s))
155     }
156 }
157 
158 impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
from(s: Box<dyn std::error::Error + Send + Sync>) -> Self159     fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
160         #[derive(thiserror::Error)]
161         #[error(transparent)]
162         struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
163         impl fmt::Debug for BoxedDiagnostic {
164             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165                 fmt::Debug::fmt(&self.0, f)
166             }
167         }
168 
169         impl Diagnostic for BoxedDiagnostic {}
170 
171         Box::new(BoxedDiagnostic(s))
172     }
173 }
174 
175 /**
176 [`Diagnostic`] severity. Intended to be used by
177 [`ReportHandler`](crate::ReportHandler)s to change the way different
178 [`Diagnostic`]s are displayed. Defaults to [`Severity::Error`].
179 */
180 #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
181 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
182 pub enum Severity {
183     /// Just some help. Here's how you could be doing it better.
184     Advice,
185     /// Warning. Please take note.
186     Warning,
187     /// Critical failure. The program cannot continue.
188     /// This is the default severity, if you don't specify another one.
189     Error,
190 }
191 
192 impl Default for Severity {
default() -> Self193     fn default() -> Self {
194         Severity::Error
195     }
196 }
197 
198 #[cfg(feature = "serde")]
199 #[test]
test_serialize_severity()200 fn test_serialize_severity() {
201     use serde_json::json;
202 
203     assert_eq!(json!(Severity::Advice), json!("Advice"));
204     assert_eq!(json!(Severity::Warning), json!("Warning"));
205     assert_eq!(json!(Severity::Error), json!("Error"));
206 }
207 
208 #[cfg(feature = "serde")]
209 #[test]
test_deserialize_severity()210 fn test_deserialize_severity() {
211     use serde_json::json;
212 
213     let severity: Severity = serde_json::from_value(json!("Advice")).unwrap();
214     assert_eq!(severity, Severity::Advice);
215 
216     let severity: Severity = serde_json::from_value(json!("Warning")).unwrap();
217     assert_eq!(severity, Severity::Warning);
218 
219     let severity: Severity = serde_json::from_value(json!("Error")).unwrap();
220     assert_eq!(severity, Severity::Error);
221 }
222 
223 /**
224 Represents readable source code of some sort.
225 
226 This trait is able to support simple `SourceCode` types like [`String`]s, as
227 well as more involved types like indexes into centralized `SourceMap`-like
228 types, file handles, and even network streams.
229 
230 If you can read it, you can source it, and it's not necessary to read the
231 whole thing--meaning you should be able to support `SourceCode`s which are
232 gigabytes or larger in size.
233 */
234 pub trait SourceCode: Send + Sync {
235     /// Read the bytes for a specific span from this SourceCode, keeping a
236     /// certain number of lines before and after the span as context.
read_span<'a>( &'a self, span: &SourceSpan, context_lines_before: usize, context_lines_after: usize, ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>237     fn read_span<'a>(
238         &'a self,
239         span: &SourceSpan,
240         context_lines_before: usize,
241         context_lines_after: usize,
242     ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>;
243 }
244 
245 /// A labeled [`SourceSpan`].
246 #[derive(Debug, Clone, PartialEq, Eq)]
247 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
248 pub struct LabeledSpan {
249     #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
250     label: Option<String>,
251     span: SourceSpan,
252 }
253 
254 impl LabeledSpan {
255     /// Makes a new labeled span.
new(label: Option<String>, offset: ByteOffset, len: usize) -> Self256     pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
257         Self {
258             label,
259             span: SourceSpan::new(SourceOffset(offset), SourceOffset(len)),
260         }
261     }
262 
263     /// Makes a new labeled span using an existing span.
new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self264     pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
265         Self {
266             label,
267             span: span.into(),
268         }
269     }
270 
271     /// Makes a new label at specified span
272     ///
273     /// # Examples
274     /// ```
275     /// use miette::LabeledSpan;
276     ///
277     /// let source = "Cpp is the best";
278     /// let label = LabeledSpan::at(0..3, "should be Rust");
279     /// assert_eq!(
280     ///     label,
281     ///     LabeledSpan::new(Some("should be Rust".to_string()), 0, 3)
282     /// )
283     /// ```
at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self284     pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
285         Self::new_with_span(Some(label.into()), span)
286     }
287 
288     /// Makes a new label that points at a specific offset.
289     ///
290     /// # Examples
291     /// ```
292     /// use miette::LabeledSpan;
293     ///
294     /// let source = "(2 + 2";
295     /// let label = LabeledSpan::at_offset(4, "expected a closing parenthesis");
296     /// assert_eq!(
297     ///     label,
298     ///     LabeledSpan::new(Some("expected a closing parenthesis".to_string()), 4, 0)
299     /// )
300     /// ```
at_offset(offset: ByteOffset, label: impl Into<String>) -> Self301     pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
302         Self::new(Some(label.into()), offset, 0)
303     }
304 
305     /// Makes a new label without text, that underlines a specific span.
306     ///
307     /// # Examples
308     /// ```
309     /// use miette::LabeledSpan;
310     ///
311     /// let source = "You have an eror here";
312     /// let label = LabeledSpan::underline(12..16);
313     /// assert_eq!(label, LabeledSpan::new(None, 12, 4))
314     /// ```
underline(span: impl Into<SourceSpan>) -> Self315     pub fn underline(span: impl Into<SourceSpan>) -> Self {
316         Self::new_with_span(None, span)
317     }
318 
319     /// Gets the (optional) label string for this `LabeledSpan`.
label(&self) -> Option<&str>320     pub fn label(&self) -> Option<&str> {
321         self.label.as_deref()
322     }
323 
324     /// Returns a reference to the inner [`SourceSpan`].
inner(&self) -> &SourceSpan325     pub const fn inner(&self) -> &SourceSpan {
326         &self.span
327     }
328 
329     /// Returns the 0-based starting byte offset.
offset(&self) -> usize330     pub const fn offset(&self) -> usize {
331         self.span.offset()
332     }
333 
334     /// Returns the number of bytes this `LabeledSpan` spans.
len(&self) -> usize335     pub const fn len(&self) -> usize {
336         self.span.len()
337     }
338 
339     /// True if this `LabeledSpan` is empty.
is_empty(&self) -> bool340     pub const fn is_empty(&self) -> bool {
341         self.span.is_empty()
342     }
343 }
344 
345 #[cfg(feature = "serde")]
346 #[test]
test_serialize_labeled_span()347 fn test_serialize_labeled_span() {
348     use serde_json::json;
349 
350     assert_eq!(
351         json!(LabeledSpan::new(None, 0, 0)),
352         json!({
353             "span": { "offset": 0, "length": 0 }
354         })
355     );
356 
357     assert_eq!(
358         json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
359         json!({
360             "label": "label",
361             "span": { "offset": 0, "length": 0 }
362         })
363     )
364 }
365 
366 #[cfg(feature = "serde")]
367 #[test]
test_deserialize_labeled_span()368 fn test_deserialize_labeled_span() {
369     use serde_json::json;
370 
371     let span: LabeledSpan = serde_json::from_value(json!({
372         "label": null,
373         "span": { "offset": 0, "length": 0 }
374     }))
375     .unwrap();
376     assert_eq!(span, LabeledSpan::new(None, 0, 0));
377 
378     let span: LabeledSpan = serde_json::from_value(json!({
379         "span": { "offset": 0, "length": 0 }
380     }))
381     .unwrap();
382     assert_eq!(span, LabeledSpan::new(None, 0, 0));
383 
384     let span: LabeledSpan = serde_json::from_value(json!({
385         "label": "label",
386         "span": { "offset": 0, "length": 0 }
387     }))
388     .unwrap();
389     assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0))
390 }
391 
392 /**
393 Contents of a [`SourceCode`] covered by [`SourceSpan`].
394 
395 Includes line and column information to optimize highlight calculations.
396 */
397 pub trait SpanContents<'a> {
398     /// Reference to the data inside the associated span, in bytes.
data(&self) -> &'a [u8]399     fn data(&self) -> &'a [u8];
400     /// [`SourceSpan`] representing the span covered by this `SpanContents`.
span(&self) -> &SourceSpan401     fn span(&self) -> &SourceSpan;
402     /// An optional (file?) name for the container of this `SpanContents`.
name(&self) -> Option<&str>403     fn name(&self) -> Option<&str> {
404         None
405     }
406     /// The 0-indexed line in the associated [`SourceCode`] where the data
407     /// begins.
line(&self) -> usize408     fn line(&self) -> usize;
409     /// The 0-indexed column in the associated [`SourceCode`] where the data
410     /// begins, relative to `line`.
column(&self) -> usize411     fn column(&self) -> usize;
412     /// Total number of lines covered by this `SpanContents`.
line_count(&self) -> usize413     fn line_count(&self) -> usize;
414 }
415 
416 /**
417 Basic implementation of the [`SpanContents`] trait, for convenience.
418 */
419 #[derive(Clone, Debug)]
420 pub struct MietteSpanContents<'a> {
421     // Data from a [`SourceCode`], in bytes.
422     data: &'a [u8],
423     // span actually covered by this SpanContents.
424     span: SourceSpan,
425     // The 0-indexed line where the associated [`SourceSpan`] _starts_.
426     line: usize,
427     // The 0-indexed column where the associated [`SourceSpan`] _starts_.
428     column: usize,
429     // Number of line in this snippet.
430     line_count: usize,
431     // Optional filename
432     name: Option<String>,
433 }
434 
435 impl<'a> MietteSpanContents<'a> {
436     /// Make a new [`MietteSpanContents`] object.
new( data: &'a [u8], span: SourceSpan, line: usize, column: usize, line_count: usize, ) -> MietteSpanContents<'a>437     pub const fn new(
438         data: &'a [u8],
439         span: SourceSpan,
440         line: usize,
441         column: usize,
442         line_count: usize,
443     ) -> MietteSpanContents<'a> {
444         MietteSpanContents {
445             data,
446             span,
447             line,
448             column,
449             line_count,
450             name: None,
451         }
452     }
453 
454     /// Make a new [`MietteSpanContents`] object, with a name for its 'file'.
new_named( name: String, data: &'a [u8], span: SourceSpan, line: usize, column: usize, line_count: usize, ) -> MietteSpanContents<'a>455     pub const fn new_named(
456         name: String,
457         data: &'a [u8],
458         span: SourceSpan,
459         line: usize,
460         column: usize,
461         line_count: usize,
462     ) -> MietteSpanContents<'a> {
463         MietteSpanContents {
464             data,
465             span,
466             line,
467             column,
468             line_count,
469             name: Some(name),
470         }
471     }
472 }
473 
474 impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
data(&self) -> &'a [u8]475     fn data(&self) -> &'a [u8] {
476         self.data
477     }
span(&self) -> &SourceSpan478     fn span(&self) -> &SourceSpan {
479         &self.span
480     }
line(&self) -> usize481     fn line(&self) -> usize {
482         self.line
483     }
column(&self) -> usize484     fn column(&self) -> usize {
485         self.column
486     }
line_count(&self) -> usize487     fn line_count(&self) -> usize {
488         self.line_count
489     }
name(&self) -> Option<&str>490     fn name(&self) -> Option<&str> {
491         self.name.as_deref()
492     }
493 }
494 
495 /// Span within a [`SourceCode`]
496 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
497 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
498 pub struct SourceSpan {
499     /// The start of the span.
500     offset: SourceOffset,
501     /// The total length of the span
502     length: usize,
503 }
504 
505 impl SourceSpan {
506     /// Create a new [`SourceSpan`].
new(start: SourceOffset, length: SourceOffset) -> Self507     pub const fn new(start: SourceOffset, length: SourceOffset) -> Self {
508         Self {
509             offset: start,
510             length: length.offset(),
511         }
512     }
513 
514     /// The absolute offset, in bytes, from the beginning of a [`SourceCode`].
offset(&self) -> usize515     pub const fn offset(&self) -> usize {
516         self.offset.offset()
517     }
518 
519     /// Total length of the [`SourceSpan`], in bytes.
len(&self) -> usize520     pub const fn len(&self) -> usize {
521         self.length
522     }
523 
524     /// Whether this [`SourceSpan`] has a length of zero. It may still be useful
525     /// to point to a specific point.
is_empty(&self) -> bool526     pub const fn is_empty(&self) -> bool {
527         self.length == 0
528     }
529 }
530 
531 impl From<(ByteOffset, usize)> for SourceSpan {
from((start, len): (ByteOffset, usize)) -> Self532     fn from((start, len): (ByteOffset, usize)) -> Self {
533         Self {
534             offset: start.into(),
535             length: len,
536         }
537     }
538 }
539 
540 impl From<(SourceOffset, SourceOffset)> for SourceSpan {
from((start, len): (SourceOffset, SourceOffset)) -> Self541     fn from((start, len): (SourceOffset, SourceOffset)) -> Self {
542         Self::new(start, len)
543     }
544 }
545 
546 impl From<std::ops::Range<ByteOffset>> for SourceSpan {
from(range: std::ops::Range<ByteOffset>) -> Self547     fn from(range: std::ops::Range<ByteOffset>) -> Self {
548         Self {
549             offset: range.start.into(),
550             length: range.len(),
551         }
552     }
553 }
554 
555 impl From<SourceOffset> for SourceSpan {
from(offset: SourceOffset) -> Self556     fn from(offset: SourceOffset) -> Self {
557         Self { offset, length: 0 }
558     }
559 }
560 
561 impl From<ByteOffset> for SourceSpan {
from(offset: ByteOffset) -> Self562     fn from(offset: ByteOffset) -> Self {
563         Self {
564             offset: offset.into(),
565             length: 0,
566         }
567     }
568 }
569 
570 #[cfg(feature = "serde")]
571 #[test]
test_serialize_source_span()572 fn test_serialize_source_span() {
573     use serde_json::json;
574 
575     assert_eq!(
576         json!(SourceSpan::from(0)),
577         json!({ "offset": 0, "length": 0})
578     )
579 }
580 
581 #[cfg(feature = "serde")]
582 #[test]
test_deserialize_source_span()583 fn test_deserialize_source_span() {
584     use serde_json::json;
585 
586     let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
587     assert_eq!(span, SourceSpan::from(0))
588 }
589 
590 /**
591 "Raw" type for the byte offset from the beginning of a [`SourceCode`].
592 */
593 pub type ByteOffset = usize;
594 
595 /**
596 Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`]
597 */
598 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
599 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
600 pub struct SourceOffset(ByteOffset);
601 
602 impl SourceOffset {
603     /// Actual byte offset.
offset(&self) -> ByteOffset604     pub const fn offset(&self) -> ByteOffset {
605         self.0
606     }
607 
608     /// Little utility to help convert 1-based line/column locations into
609     /// miette-compatible Spans
610     ///
611     /// This function is infallible: Giving an out-of-range line/column pair
612     /// will return the offset of the last byte in the source.
from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self613     pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
614         let mut line = 0usize;
615         let mut col = 0usize;
616         let mut offset = 0usize;
617         for char in source.as_ref().chars() {
618             if line + 1 >= loc_line && col + 1 >= loc_col {
619                 break;
620             }
621             if char == '\n' {
622                 col = 0;
623                 line += 1;
624             } else {
625                 col += 1;
626             }
627             offset += char.len_utf8();
628         }
629 
630         SourceOffset(offset)
631     }
632 
633     /// Returns an offset for the _file_ location of wherever this function is
634     /// called. If you want to get _that_ caller's location, mark this
635     /// function's caller with `#[track_caller]` (and so on and so forth).
636     ///
637     /// Returns both the filename that was given and the offset of the caller
638     /// as a [`SourceOffset`].
639     ///
640     /// Keep in mind that this fill only work if the file your Rust source
641     /// file was compiled from is actually available at that location. If
642     /// you're shipping binaries for your application, you'll want to ignore
643     /// the Err case or otherwise report it.
644     #[track_caller]
from_current_location() -> Result<(String, Self), MietteError>645     pub fn from_current_location() -> Result<(String, Self), MietteError> {
646         let loc = Location::caller();
647         Ok((
648             loc.file().into(),
649             fs::read_to_string(loc.file())
650                 .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?,
651         ))
652     }
653 }
654 
655 impl From<ByteOffset> for SourceOffset {
from(bytes: ByteOffset) -> Self656     fn from(bytes: ByteOffset) -> Self {
657         SourceOffset(bytes)
658     }
659 }
660 
661 #[test]
test_source_offset_from_location()662 fn test_source_offset_from_location() {
663     let source = "f\n\noo\r\nbar";
664 
665     assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
666     assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
667     assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
668     assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
669     assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
670     assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
671     assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
672     assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
673     assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
674     assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
675     assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
676 
677     // Out-of-range
678     assert_eq!(
679         SourceOffset::from_location(source, 5, 1).offset(),
680         source.len()
681     );
682 }
683 
684 #[cfg(feature = "serde")]
685 #[test]
test_serialize_source_offset()686 fn test_serialize_source_offset() {
687     use serde_json::json;
688 
689     assert_eq!(json!(SourceOffset::from(0)), 0)
690 }
691 
692 #[cfg(feature = "serde")]
693 #[test]
test_deserialize_source_offset()694 fn test_deserialize_source_offset() {
695     let offset: SourceOffset = serde_json::from_str("0").unwrap();
696     assert_eq!(offset, SourceOffset::from(0))
697 }
698