Repository: zkat/miette
Pull request #399 (2024-08-07 00:00:38)

docs: Fix some broken links and missing backticks.


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 01b3a71..7678079 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,7 +24,7 @@ supports-hyperlinks = { version = "3.0.0", optional = true }
 supports-color = { version = "3.0.0", optional = true }
 supports-unicode = { version = "3.0.0", optional = true }
 backtrace = { version = "0.3.69", optional = true }
-terminal_size = { version = "0.3.0", optional = true }
+terminal_size = { version = "0.4.0", optional = true }
 backtrace-ext = { version = "0.2.1", optional = true }
 serde = { version = "1.0.196", features = ["derive"], optional = true }
 syntect = { version = "5.1.0", optional = true }
@@ -46,24 +46,24 @@ strip-ansi-escapes = "0.2.0"
 
 [features]
 default = ["derive"]
-derive = ["miette-derive"]
+derive = ["dep:miette-derive"]
 no-format-args-capture = []
 fancy-base = [
-    "owo-colors",
-    "textwrap",
+    "dep:owo-colors",
+    "dep:textwrap",
 ]
 fancy-no-syscall = [
     "fancy-base",
 ]
 fancy-no-backtrace = [
     "fancy-base",
-    "terminal_size",
-    "supports-hyperlinks",
-    "supports-color",
-    "supports-unicode",
+    "dep:terminal_size",
+    "dep:supports-hyperlinks",
+    "dep:supports-color",
+    "dep:supports-unicode",
 ]
-fancy = ["fancy-no-backtrace", "backtrace", "backtrace-ext"]
-syntect-highlighter = ["fancy-no-backtrace", "syntect"]
+fancy = ["fancy-no-backtrace", "dep:backtrace", "dep:backtrace-ext"]
+syntect-highlighter = ["fancy-no-backtrace", "dep:syntect"]
 
 [workspace]
 members = ["miette-derive"]
diff --git a/README.md b/README.md
index 33022f8..646047c 100644
--- a/README.md
+++ b/README.md
@@ -673,12 +673,12 @@ automatically use the [`syntect`] crate to highlight the `#[source_code]`
 field of your [`Diagnostic`].
 
 Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
-* [language()](SpanContents::language) - Provides the name of the language
+* [`language()`](SpanContents::language) - Provides the name of the language
   as a string. For example `"Rust"` will indicate Rust syntax highlighting.
   You can set the language of the [`SpanContents`] produced by a
   [`NamedSource`] via the [`with_language`](NamedSource::with_language)
   method.
-* [name()](SpanContents::name) - In the absence of an explicitly set
+* [`name()`](SpanContents::name) - In the absence of an explicitly set
   language, the name is assumed to contain a file name or file path.
   The highlighter will check for a file extension at the end of the name and
   try to guess the syntax from that.
diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs
index 94e4128..cbb4aee 100644
--- a/src/eyreish/error.rs
+++ b/src/eyreish/error.rs
@@ -425,6 +425,14 @@ impl Report {
         }
         .into()
     }
+
+    /// Construct a [`Report`] directly from an error-like type
+    pub fn from_err<E>(err: E) -> Self
+    where
+        E: std::error::Error + Send + Sync + 'static,
+    {
+        super::DiagnosticError(Box::new(err)).into()
+    }
 }
 
 impl<E> From<E> for Report
diff --git a/src/eyreish/into_diagnostic.rs b/src/eyreish/into_diagnostic.rs
index 6480013..f862ac6 100644
--- a/src/eyreish/into_diagnostic.rs
+++ b/src/eyreish/into_diagnostic.rs
@@ -6,7 +6,7 @@ use crate::{Diagnostic, Report};
 /// Errors. This is intended to be paired with [`IntoDiagnostic`].
 #[derive(Debug, Error)]
 #[error(transparent)]
-struct DiagnosticError(Box<dyn std::error::Error + Send + Sync + 'static>);
+pub(crate) struct DiagnosticError(pub(crate) Box<dyn std::error::Error + Send + Sync + 'static>);
 impl Diagnostic for DiagnosticError {}
 
 /**
diff --git a/src/handler.rs b/src/handler.rs
index efbee86..10d8f65 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -89,7 +89,7 @@ impl MietteHandlerOpts {
     /// Syntax highlighting is disabled by default unless the
     /// `syntect-highlighter` feature is enabled. Call this method
     /// to override the default and use a custom highlighter
-    /// implmentation instead.
+    /// implementation instead.
     ///
     /// Use
     /// [`without_syntax_highlighting()`](MietteHandlerOpts::without_syntax_highlighting())
diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs
index 87fb354..8c4a9a5 100644
--- a/src/handlers/graphical.rs
+++ b/src/handlers/graphical.rs
@@ -178,7 +178,8 @@ impl GraphicalReportHandler {
     }
 
     /// Enable syntax highlighting for source code snippets, using the given
-    /// [`Highlighter`]. See the [crate::highlighters] crate for more details.
+    /// [`Highlighter`]. See the [highlighters](crate::highlighters) crate
+    /// for more details.
     pub fn with_syntax_highlighting(
         mut self,
         highlighter: impl Highlighter + Send + Sync + 'static,
@@ -799,7 +800,7 @@ impl GraphicalReportHandler {
             return Ok(());
         }
 
-        // keeps track of how many colums wide the gutter is
+        // keeps track of how many columns wide the gutter is
         // important for ansi since simply measuring the size of the final string
         // gives the wrong result when the string contains ansi codes.
         let mut gutter_cols = 0;
@@ -1186,7 +1187,7 @@ impl GraphicalReportHandler {
         let context_data = source
             .read_span(context_span, self.context_lines, self.context_lines)
             .map_err(|_| fmt::Error)?;
-        let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
+        let context = String::from_utf8_lossy(context_data.data());
         let mut line = context_data.line();
         let mut column = context_data.column();
         let mut offset = context_data.span().offset();
diff --git a/src/highlighters/mod.rs b/src/highlighters/mod.rs
index d605c1c..75e7e7f 100644
--- a/src/highlighters/mod.rs
+++ b/src/highlighters/mod.rs
@@ -26,16 +26,16 @@ mod syntect;
 
 /// A syntax highlighter for highlighting miette [`SourceCode`](crate::SourceCode) snippets.
 pub trait Highlighter {
-    ///  Creates a new [HighlighterState] to begin parsing and highlighting
-    /// a [SpanContents].
+    ///  Creates a new [`HighlighterState`] to begin parsing and highlighting
+    /// a [`SpanContents`].
     ///
-    /// The [GraphicalReportHandler](crate::GraphicalReportHandler) will call
-    /// this method at the start of rendering a [SpanContents].
+    /// The [`GraphicalReportHandler`](crate::GraphicalReportHandler) will call
+    /// this method at the start of rendering a [`SpanContents`].
     ///
-    /// The [SpanContents] is provided as input only so that the [Highlighter]
+    /// The [`SpanContents`] is provided as input only so that the [`Highlighter`]
     /// can detect language syntax and make other initialization decisions prior
     /// to highlighting, but it is not intended that the Highlighter begin
-    /// highlighting at this point. The returned [HighlighterState] is
+    /// highlighting at this point. The returned [`HighlighterState`] is
     /// responsible for the actual rendering.
     fn start_highlighter_state<'h>(
         &'h self,
@@ -46,12 +46,12 @@ pub trait Highlighter {
 /// A stateful highlighter that incrementally highlights lines of a particular
 /// source code.
 ///
-/// The [GraphicalReportHandler](crate::GraphicalReportHandler)
+/// The [`GraphicalReportHandler`](crate::GraphicalReportHandler)
 /// will create a highlighter state by calling
-/// [start_highlighter_state](Highlighter::start_highlighter_state) at the
+/// [`start_highlighter_state`](Highlighter::start_highlighter_state) at the
 /// start of rendering, then it will iteratively call
-/// [highlight_line](HighlighterState::highlight_line) to render individual
-/// highlighted lines. This allows [Highlighter] implementations to maintain
+/// [`highlight_line`](HighlighterState::highlight_line) to render individual
+/// highlighted lines. This allows [`Highlighter`] implementations to maintain
 /// mutable parsing and highlighting state.
 pub trait HighlighterState {
     /// Highlight an individual line from the source code by returning a vector of [Styled]
@@ -59,9 +59,9 @@ pub trait HighlighterState {
     fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>>;
 }
 
-/// Arcified trait object for Highlighter. Used internally by [GraphicalReportHandler]
+/// Arcified trait object for Highlighter. Used internally by [`GraphicalReportHandler`]
 ///
-/// Wrapping the trait object in this way allows us to implement Debug and Clone.
+/// Wrapping the trait object in this way allows us to implement `Debug` and `Clone`.
 #[derive(Clone)]
 #[repr(transparent)]
 pub(crate) struct MietteHighlighter(Arc<dyn Highlighter + Send + Sync>);
diff --git a/src/highlighters/syntect.rs b/src/highlighters/syntect.rs
index 69ed015..538124c 100644
--- a/src/highlighters/syntect.rs
+++ b/src/highlighters/syntect.rs
@@ -20,7 +20,7 @@ use crate::{
 
 use super::BlankHighlighterState;
 
-/// Highlights miette [SourceCode] with the [syntect](https://docs.rs/syntect/latest/syntect/) highlighting crate.
+/// Highlights miette [`SpanContents`] with the [syntect](https://docs.rs/syntect/latest/syntect/) highlighting crate.
 ///
 /// Currently only 24-bit truecolor output is supported due to syntect themes
 /// representing color as RGBA.
@@ -81,7 +81,7 @@ impl SyntectHighlighter {
         )
     }
 
-    /// Determine syntect SyntaxReference to use for given SourceCode
+    /// Determine syntect [`SyntaxReference`] to use for given [`SpanContents`].
     fn detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference> {
         // use language if given
         if let Some(language) = contents.language() {
@@ -105,7 +105,7 @@ impl SyntectHighlighter {
     }
 }
 
-/// Stateful highlighting iterator for [SyntectHighlighter]
+/// Stateful highlighting iterator for [`SyntectHighlighter`].
 #[derive(Debug)]
 pub(crate) struct SyntectHighlighterState<'h> {
     syntax_set: &'h syntect::SyntaxSet,
@@ -133,7 +133,7 @@ impl<'h> HighlighterState for SyntectHighlighterState<'h> {
     }
 }
 
-/// Convert syntect [syntect::Style] into owo_colors [Style] */
+/// Convert syntect [`syntect::Style`] into `owo_colors` [`Style`]
 #[inline]
 fn convert_style(syntect_style: syntect::Style, use_bg_color: bool) -> Style {
     if use_bg_color {
diff --git a/src/lib.rs b/src/lib.rs
index a42f4fa..295dfcb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -674,12 +674,12 @@
 //! field of your [`Diagnostic`].
 //!
 //! Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
-//! * [language()](SpanContents::language) - Provides the name of the language
+//! * [`language()`](SpanContents::language) - Provides the name of the language
 //!   as a string. For example `"Rust"` will indicate Rust syntax highlighting.
 //!   You can set the language of the [`SpanContents`] produced by a
 //!   [`NamedSource`] via the [`with_language`](NamedSource::with_language)
 //!   method.
-//! * [name()](SpanContents::name) - In the absence of an explicitly set
+//! * [`name()`](SpanContents::name) - In the absence of an explicitly set
 //!   language, the name is assumed to contain a file name or file path.
 //!   The highlighter will check for a file extension at the end of the name and
 //!   try to guess the syntax from that.
diff --git a/src/protocol.rs b/src/protocol.rs
index d3ee9ca..7f09d4d 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -523,7 +523,7 @@ impl<'a> MietteSpanContents<'a> {
         }
     }
 
-    /// Sets the [`language`](SourceCode::language) for syntax highlighting.
+    /// Sets the [`language`](SpanContents::language) for syntax highlighting.
     pub fn with_language(mut self, language: impl Into<String>) -> Self {
         self.language = Some(language.into());
         self


Test patch:
diff --git a/tests/graphical.rs b/tests/graphical.rs
index 7221a70..3176812 100644
--- a/tests/graphical.rs
+++ b/tests/graphical.rs
@@ -2349,3 +2349,164 @@ Error: oops::my::inner
     assert_eq!(expected, &out);
     Ok(())
 }
+
+#[test]
+fn after_unicode_width() -> Result<(), MietteError> {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("pointing")]
+    #[diagnostic(code(pointing_at))]
+    struct E {
+        #[label("something of interest")]
+        src: SourceSpan,
+    }
+
+    let unicode_source = "höööt!";
+
+    // make err pointing at the t
+    let (t_index, _) = unicode_source
+        .bytes()
+        .enumerate()
+        .find(|&(_, x)| x == b't')
+        .unwrap();
+
+    let err = E {
+        src: SourceSpan::from((t_index, 1)),
+    };
+
+    let result = fmt_report(Report::new(err).with_source_code(String::from(unicode_source)));
+
+    let expected = "pointing_at
+
+  × pointing
+   ╭────
+ 1 │ höööt!
+   ·     ┬
+   ·     ╰── something of interest
+   ╰────
+";
+
+    assert_eq!(expected, result);
+
+    Ok(())
+}
+
+#[test]
+fn at_unicode_width() -> Result<(), MietteError> {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("pointing")]
+    #[diagnostic(code(pointing_at))]
+    struct E {
+        #[label("something of interest")]
+        src: SourceSpan,
+    }
+
+    let unicode_source = "höööt!";
+
+    // make err pointing at an ö
+    let err = E {
+        src: SourceSpan::from((1, "ö".len())),
+    };
+
+    // we want to make sure the pointer is one char wide, not 2
+    assert!(err.src.len() == 2);
+
+    let result = fmt_report(Report::new(err).with_source_code(String::from(unicode_source)));
+
+    let expected = "pointing_at
+
+  × pointing
+   ╭────
+ 1 │ höööt!
+   ·  ┬
+   ·  ╰── something of interest
+   ╰────
+";
+
+    assert_eq!(expected, result);
+
+    Ok(())
+}
+
+#[test]
+fn at_invalid_unicode() -> Result<(), MietteError> {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("decoding error")]
+    #[diagnostic(code(decode_err))]
+    struct E {
+        #[label("invalid data here")]
+        src: SourceSpan,
+    }
+
+    // 3 bytes here are mapped to 1 char when replaced by replacement character
+    // this tests that the line pointing from the label is the correct length when rendered
+    // - it should be 1 char wide not 3 chars
+    let invalid_source: &[u8] = b"malformed h\xf0\x93\x8aXYZ";
+
+    // invalid utf8 lint only available from 1.72
+    #[allow(unknown_lints, invalid_from_utf8)]
+    let utf8_err = std::str::from_utf8(invalid_source).unwrap_err();
+
+    // make err pointing at the invalid part
+    let err = E {
+        src: SourceSpan::from((utf8_err.valid_up_to(), utf8_err.error_len().unwrap_or(1))),
+    };
+
+    // check that we're testing the thing we think we're testing
+    assert_eq!(err.src.len(), 3);
+
+    let result = fmt_report(Report::new(err).with_source_code(Vec::from(invalid_source)));
+
+    let expected = "decode_err
+
+  × decoding error
+   ╭────
+ 1 │ malformed h�XYZ
+   ·            ┬
+   ·            ╰── invalid data here
+   ╰────
+";
+
+    assert_eq!(expected, result);
+
+    Ok(())
+}
+
+#[test]
+fn after_invalid_unicode() -> Result<(), MietteError> {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("decoding error")]
+    #[diagnostic(code(decode_err))]
+    struct E {
+        #[label("valid data here")]
+        src: SourceSpan,
+    }
+
+    let invalid_source: &[u8] = b"malformed h\xf0\x93\x8aXYZ";
+
+    let (x_index, _) = invalid_source
+        .iter()
+        .enumerate()
+        .find(|&(_, &x)| x == b'X')
+        .unwrap();
+
+    // make err pointing at the X
+    let err = E {
+        src: SourceSpan::from((x_index, 1)),
+    };
+
+    let result = fmt_report(Report::new(err).with_source_code(invalid_source));
+
+    let expected = "decode_err
+
+  × decoding error
+   ╭────
+ 1 │ malformed h�XYZ
+   ·             ┬
+   ·             ╰── valid data here
+   ╰────
+";
+
+    assert_eq!(expected, result);
+
+    Ok(())
+}