mirror of https://github.com/zkat/miette.git
Compare commits
51 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
e853bbf9bc | |
|
|
b466948965 | |
|
|
df7bcfa17d | |
|
|
2b79495c79 | |
|
|
7a4d759c59 | |
|
|
fea8043b3e | |
|
|
51ca022b1f | |
|
|
907857058d | |
|
|
34b8774b36 | |
|
|
521ef91f77 | |
|
|
59c81617de | |
|
|
58d9f12411 | |
|
|
f2ef693d1c | |
|
|
a93afcf7e3 | |
|
|
9ba6fad769 | |
|
|
b8c144f2a6 | |
|
|
429ca37d8d | |
|
|
b4c983a393 | |
|
|
1e1938a099 | |
|
|
771a07519f | |
|
|
c7eeada1e0 | |
|
|
b2011f693b | |
|
|
0c46c0b4d4 | |
|
|
7fae60fd84 | |
|
|
465e6b6ab6 | |
|
|
68d47fa8b5 | |
|
|
01564e070f | |
|
|
fe068f6fc1 | |
|
|
2902a2337c | |
|
|
5f441d0115 | |
|
|
f3fb4c1ecd | |
|
|
d60c8f10f1 | |
|
|
215f9aae33 | |
|
|
789a04e30d | |
|
|
21e9a70ad8 | |
|
|
93d3bd118a | |
|
|
d6b4558502 | |
|
|
15beec4330 | |
|
|
bf5aa3742f | |
|
|
bdd1d74614 | |
|
|
edfdcb525e | |
|
|
9bbcf3c601 | |
|
|
73da45b65c | |
|
|
b8dfcda4a8 | |
|
|
e1026f75e0 | |
|
|
813232ba79 | |
|
|
b82cc81b8e | |
|
|
ea4296dace | |
|
|
97766e8d90 | |
|
|
7b42b12c5f | |
|
|
3eabbcebf1 |
|
|
@ -33,11 +33,11 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
features: [fancy, syntect-highlighter]
|
||||
rust: [1.70.0, stable]
|
||||
rust: [1.82.0, stable]
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
exclude:
|
||||
- features: syntect-highlighter
|
||||
rust: 1.70.0
|
||||
rust: 1.82.0
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -52,7 +52,7 @@ jobs:
|
|||
if: matrix.rust == 'stable'
|
||||
run: cargo test --all --verbose --features ${{matrix.features}}
|
||||
- name: Run tests
|
||||
if: matrix.rust == '1.70.0'
|
||||
if: matrix.rust == '1.82.0'
|
||||
run: cargo test --all --verbose --features ${{matrix.features}} no-format-args-capture
|
||||
|
||||
wasm:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# AGENTS.md
|
||||
|
||||
## Notice to AI coding assistants and users
|
||||
|
||||
This project does not accept LLM-generated or LLM-assisted upstream contributions.
|
||||
|
||||
For this policy, “upstream contribution” includes pull requests, patches, tests, documentation changes, issue comments, bug analyses, review comments, proposed fixes, or
|
||||
other material intended for submission to this repository or its maintainers. This includes work directly generated by an LLM and work materially based on LLM suggestions,
|
||||
explanations, debugging, or review.
|
||||
|
||||
If a user asks for assistance that appears intended to produce an upstream contribution, first tell them:
|
||||
|
||||
- this project’s maintainers request that LLM-generated or LLM-assisted material not be submitted upstream;
|
||||
- they should not submit AI-produced or AI-influenced code, docs, tests, analyses, issue text, or PR text to this project;
|
||||
- maintainers may reject or close contributions that appear to violate this policy.
|
||||
|
||||
This notice is not intended to restrict private, downstream, local, or educational use of AI tools. Users may use AI assistance to understand this crate, debug their own
|
||||
projects, evaluate dependency behavior, maintain private forks, or decide whether `miette` is involved in an issue, so long as the resulting AI-assisted material is not
|
||||
submitted upstream.
|
||||
68
CHANGELOG.md
68
CHANGELOG.md
|
|
@ -1,5 +1,73 @@
|
|||
# `miette` Release Changelog
|
||||
|
||||
<a name="7.6.0"></a>
|
||||
## 7.6.0 (2025-04-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **graphical:** prevent leading newline when no link/code (#418) ([1e1938a0](https://github.com/zkat/miette/commit/1e1938a099409969c69b9f070e0fb0d13d564527))
|
||||
* **clippy:** elide lifetimes (#423) ([9ba6fad7](https://github.com/zkat/miette/commit/9ba6fad7694c1271f287b8f659a857c4ff540a55))
|
||||
* **highlight:** increase syntax highlighter config priority (#424) ([58d9f124](https://github.com/zkat/miette/commit/58d9f12411e7d3d6d580eb219ea32321e6918d79))
|
||||
* **deps:** miette can now be used without syn (#436) ([521ef91f](https://github.com/zkat/miette/commit/521ef91f77a143eb5cedfa1344428b804802179d))
|
||||
|
||||
### Features
|
||||
|
||||
* **graphical:** support rendering related diagnostics as nested (#417) ([771a0751](https://github.com/zkat/miette/commit/771a07519f078b94aceb1a2d2532d786f09f350b))
|
||||
* **labels:** add support for disabling the primary label line/col information (#419) ([f2ef693d](https://github.com/zkat/miette/commit/f2ef693d1ce7230e6b9f12805f018f095534b441))
|
||||
* **deps:** update `thiserror` from 1.0.56 to 2.0.11 (#426) ([59c81617](https://github.com/zkat/miette/commit/59c81617de8650a6ff3b193b41b4297e560726a0))
|
||||
|
||||
<a name="7.5.0"></a>
|
||||
## 7.5.0 (2025-02-01)
|
||||
|
||||
### Features
|
||||
|
||||
* **graphical:** support rendering related diagnostics as nested (#417) ([771a0751](https://github.com/zkat/miette/commit/771a07519f078b94aceb1a2d2532d786f09f350b))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **graphical:** prevent leading newline when no link/code (#418) ([1e1938a0](https://github.com/zkat/miette/commit/1e1938a099409969c69b9f070e0fb0d13d564527))
|
||||
|
||||
<a name="7.4.0"></a>
|
||||
## 7.4.0 (2024-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* **graphical:** Inherit source code to causes (#401) ([465e6b6a](https://github.com/zkat/miette/commit/465e6b6ab627f8da34baa5f46441d944fb88186e))
|
||||
* **report:** Implement `WrapError` for `Option` (#409) ([7fae60fd](https://github.com/zkat/miette/commit/7fae60fd8462f95cf3140c6a3b9eb06cb7953405))
|
||||
|
||||
<a name="7.3.0"></a>
|
||||
## 7.3.0 (2024-11-26)
|
||||
|
||||
### Features
|
||||
|
||||
* **SourceSpan:** add impl From<InclusiveRange> (#385) ([73da45b6](https://github.com/zkat/miette/commit/73da45b65c965777a00ba64aa03a247c0e5241ca))
|
||||
* **Report:** add `from_err()` method to `Report` (#403) ([93d3bd11](https://github.com/zkat/miette/commit/93d3bd118a072c35aa761f0ec74317166ec08113))
|
||||
* **Diagnostic:** Implement `Diagnostic` for `Infallible` (#402) ([f3fb4c1e](https://github.com/zkat/miette/commit/f3fb4c1ecd196ce389cbd71139bb7e3b35474add))
|
||||
|
||||
### Performance
|
||||
|
||||
* **handlers:** optimize string-buffer reallocations (#387) ([b8dfcda4](https://github.com/zkat/miette/commit/b8dfcda4a8c10a14116ee275250356ac991dc7be))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **graphical:** fix nested error wrapping (#358) ([3eabbceb](https://github.com/zkat/miette/commit/3eabbcebf113d1d620a6a3f98e8a455414ed3042))
|
||||
* **docs:** updated example image (fixes #111) (#270) ([7b42b12c](https://github.com/zkat/miette/commit/7b42b12c5f6316322ce79c59bcb9e99f5d49edb8))
|
||||
* **clippy:** Fix clippy lints in docs (#365) ([ea4296da](https://github.com/zkat/miette/commit/ea4296dacec3b0e4762281d9d115c1bd69ecfac3))
|
||||
* **docs:** `alt` attribut for `single-line-example.png` (#372) ([b82cc81b](https://github.com/zkat/miette/commit/b82cc81b8ea32a1cf1b4598ed5832bc8e3b0e161))
|
||||
* **color:** setting NO_COLOR should not print ansi codes for non-terminals (#381) ([813232ba](https://github.com/zkat/miette/commit/813232ba7957ae09e4fb9d9416d821f4fd9da66d))
|
||||
* **clippy:** fix Rust v1.78.0 clippy warnings (#382) ([e1026f75](https://github.com/zkat/miette/commit/e1026f75e0a5d19bbc8e468cb3f5292074543a97))
|
||||
* **perf:** mark error constructors cold (#378) ([9bbcf3c6](https://github.com/zkat/miette/commit/9bbcf3c6017fa3455a7db714879816c1cfc511fd))
|
||||
* **handlers:** Disable textwrap::smawk feature (#379) ([edfdcb52](https://github.com/zkat/miette/commit/edfdcb525ee30fc54747460ada621f13f0ed1996))
|
||||
* **graphical:** Format entire link instead of just name (#389) ([bf5aa374](https://github.com/zkat/miette/commit/bf5aa3742fd664be3c93160b9c28c145b1ed8bc9))
|
||||
* **clippy:** fix `clippy::doc_lazy_continuation` lints (#395) ([15beec43](https://github.com/zkat/miette/commit/15beec43303180b811d4c04d1a78775feb9b0905))
|
||||
* **graphical:** Handle invalid UTF-8 in source code (#393) ([d6b45585](https://github.com/zkat/miette/commit/d6b4558502e82fa74e030ccb3c8040656590d7eb))
|
||||
* **features:** Use `dep:` syntax for dependencies in features. (#394) ([789a04e3](https://github.com/zkat/miette/commit/789a04e30d041179b373b4eb8b340456534a3f0e))
|
||||
* **clippy:** Fix `needless_return` lint. (#405) ([5f441d01](https://github.com/zkat/miette/commit/5f441d011560a091fe5d6a6cdb05f09acf622d36))
|
||||
|
||||
### Documentation
|
||||
|
||||
* **examples:** add serde_json integration example (#407) ([2902a233](https://github.com/zkat/miette/commit/2902a2337c2e36a5d8e0e54b007d6100cca0c9ff))
|
||||
|
||||
<a name="7.2.0"></a>
|
||||
## 7.2.0 (2024-03-07)
|
||||
|
||||
|
|
|
|||
40
Cargo.toml
40
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "miette"
|
||||
version = "7.2.0"
|
||||
version = "7.6.0"
|
||||
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
||||
description = "Fancy diagnostic reporting library and protocol for us mere mortals who aren't compiler hackers."
|
||||
categories = ["rust-patterns"]
|
||||
|
|
@ -9,27 +9,27 @@ documentation = "https://docs.rs/miette"
|
|||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
rust-version = "1.70.0"
|
||||
rust-version = "1.82.0"
|
||||
exclude = ["images/", "tests/", "miette-derive/"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.56"
|
||||
miette-derive = { path = "miette-derive", version = "=7.2.0", optional = true }
|
||||
unicode-width = "0.1.11"
|
||||
miette-derive = { path = "miette-derive", version = "=7.6.0", optional = true }
|
||||
unicode-width = "0.2.0"
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
owo-colors = { version = "4.0.0", optional = true }
|
||||
textwrap = { version = "0.16.0", optional = true }
|
||||
textwrap = { version = "0.16.0", default-features = false, features = ["unicode-linebreak", "unicode-width"], optional = true }
|
||||
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 }
|
||||
|
||||
[dev-dependencies]
|
||||
thiserror = "2.0.11"
|
||||
semver = "1.0.21"
|
||||
|
||||
# Eyre devdeps
|
||||
|
|
@ -37,36 +37,40 @@ futures = { version = "0.3", default-features = false }
|
|||
indenter = "0.3.3"
|
||||
rustversion = "1.0"
|
||||
trybuild = { version = "1.0.89", features = ["diff"] }
|
||||
syn = { version = "2.0.48", features = ["full"] }
|
||||
syn = { version = "2.0.87", features = ["full"] }
|
||||
regex = "1.10"
|
||||
lazy_static = "1.4"
|
||||
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = "1.0.113"
|
||||
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"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[[example]]
|
||||
name = "serde_json"
|
||||
required-features = ["fancy"]
|
||||
|
|
|
|||
107
README.md
107
README.md
|
|
@ -28,9 +28,9 @@ diagnostic error code: ruget::api::bad_json
|
|||
" />
|
||||
|
||||
> **NOTE: You must enable the `"fancy"` crate feature to get fancy report
|
||||
output like in the screenshots above.** You should only do this in your
|
||||
toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||
libraries and such might not want.
|
||||
> output like in the screenshots above.** You should only do this in your
|
||||
> toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||
> libraries and such might not want.
|
||||
|
||||
### Table of Contents <!-- omit in toc -->
|
||||
|
||||
|
|
@ -51,6 +51,7 @@ libraries and such might not want.
|
|||
- [... handler options](#-handler-options)
|
||||
- [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||
- [... syntax highlighting](#-syntax-highlighting)
|
||||
- [... primary label](#-primary-label)
|
||||
- [... collection of labels](#-collection-of-labels)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
- [License](#license)
|
||||
|
|
@ -100,7 +101,7 @@ You can derive a `Diagnostic` from any `std::error::Error` type.
|
|||
|
||||
`thiserror` is a great way to define them, and plays nicely with `miette`!
|
||||
*/
|
||||
use miette::{Diagnostic, SourceSpan};
|
||||
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, Diagnostic)]
|
||||
|
|
@ -127,12 +128,11 @@ Use this `Result` type (or its expanded version) as the return type
|
|||
throughout your app (but NOT your libraries! Those should always return
|
||||
concrete types!).
|
||||
*/
|
||||
use miette::{NamedSource, Result};
|
||||
use miette::Result;
|
||||
fn this_fails() -> Result<()> {
|
||||
// You can use plain strings as a `Source`, or anything that implements
|
||||
// the one-method `Source` trait.
|
||||
let src = "source\n text\n here".to_string();
|
||||
let len = src.len();
|
||||
|
||||
Err(MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
|
|
@ -162,17 +162,20 @@ And this is the output you'll get if you run this program:
|
|||
<img src="https://raw.githubusercontent.com/zkat/miette/main/images/single-line-example.png" alt="
|
||||
Narratable printout:
|
||||
\
|
||||
Error: Types mismatched for operation.
|
||||
Diagnostic severity: error
|
||||
Begin snippet starting at line 1, column 1
|
||||
diagnostic error code: oops::my::bad (link)
|
||||
Error: oops!
|
||||
\
|
||||
snippet line 1: 3 + "5"
|
||||
label starting at line 1, column 1: int
|
||||
label starting at line 1, column 1: doesn't support these values.
|
||||
label starting at line 1, column 1: string
|
||||
diagnostic help: Change int or string to be the right types and try again.
|
||||
diagnostic code: nu::parser::unsupported_operation
|
||||
For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
|
||||
Begin snippet for bad_file.rs starting
|
||||
at line 2, column 3
|
||||
\
|
||||
snippet line 1: source
|
||||
\
|
||||
snippet line 2: text
|
||||
highlight starting at line 1, column 3: This bit here
|
||||
\
|
||||
snippet line 3: here
|
||||
\
|
||||
diagnostic help: try doing it better next time?">
|
||||
|
||||
### Using
|
||||
|
||||
|
|
@ -208,6 +211,17 @@ pub enum MyLibError {
|
|||
// Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
||||
#[diagnostic(transparent)]
|
||||
AnotherError(#[from] AnotherError),
|
||||
|
||||
/// Forward the diagnostic to a particular field.
|
||||
#[error("other error")]
|
||||
#[diagnostic(forward(the_actual_diagnostic))]
|
||||
EvenMoreData {
|
||||
unrelated_field_1: String,
|
||||
unrelated_field_2: usize,
|
||||
|
||||
#[source]
|
||||
the_actual_diagnostic: AnotherError,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Diagnostic, Debug)]
|
||||
|
|
@ -242,7 +256,7 @@ use miette::{IntoDiagnostic, Result};
|
|||
use semver::Version;
|
||||
|
||||
pub fn some_tool() -> Result<Version> {
|
||||
Ok("1.2.x".parse().into_diagnostic()?)
|
||||
"1.2.x".parse().into_diagnostic()
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -257,24 +271,24 @@ use miette::{IntoDiagnostic, Result, WrapErr};
|
|||
use semver::Version;
|
||||
|
||||
pub fn some_tool() -> Result<Version> {
|
||||
Ok("1.2.x"
|
||||
"1.2.x"
|
||||
.parse()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Parsing this tool's semver version failed.")?)
|
||||
.wrap_err("Parsing this tool's semver version failed.")
|
||||
}
|
||||
```
|
||||
|
||||
To construct your own simple adhoc error use the [miette!] macro:
|
||||
To construct your own simple adhoc error use the [`miette!`] macro:
|
||||
```rust
|
||||
// my_app/lib/my_internal_file.rs
|
||||
use miette::{miette, IntoDiagnostic, Result, WrapErr};
|
||||
use miette::{miette, Result};
|
||||
use semver::Version;
|
||||
|
||||
pub fn some_tool() -> Result<Version> {
|
||||
let version = "1.2.x";
|
||||
Ok(version
|
||||
version
|
||||
.parse()
|
||||
.map_err(|_| miette!("Invalid version {}", version))?)
|
||||
.map_err(|_| miette!("Invalid version {}", version))
|
||||
}
|
||||
```
|
||||
There are also similar [bail!] and [ensure!] macros.
|
||||
|
|
@ -286,9 +300,9 @@ There are also similar [bail!] and [ensure!] macros.
|
|||
automatically.
|
||||
|
||||
> **NOTE:** You must enable the `"fancy"` crate feature to get fancy report
|
||||
output like in the screenshots here.** You should only do this in your
|
||||
toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||
libraries and such might not want.
|
||||
> output like in the screenshots here.** You should only do this in your
|
||||
> toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||
> libraries and such might not want.
|
||||
|
||||
```rust
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
|
|
@ -643,8 +657,8 @@ customize!
|
|||
If you...
|
||||
- ...don't know all the possible errors upfront
|
||||
- ...need to serialize/deserialize errors
|
||||
then you may want to use [`miette!`], [`diagnostic!`] macros or
|
||||
[`MietteDiagnostic`] directly to create diagnostic on the fly.
|
||||
then you may want to use [`miette!`], [`diagnostic!`] macros or
|
||||
[`MietteDiagnostic`] directly to create diagnostic on the fly.
|
||||
|
||||
```rust
|
||||
|
||||
|
|
@ -671,12 +685,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.
|
||||
|
|
@ -687,6 +701,37 @@ trait to [`MietteHandlerOpts`] by calling the
|
|||
[`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
||||
method. See the [`highlighters`] module docs for more details.
|
||||
|
||||
#### ... primary label
|
||||
|
||||
You can use the `primary` parameter to `label` to indicate that the label
|
||||
is the primary label.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyError {
|
||||
#[label(primary, "main issue")]
|
||||
primary_span: SourceSpan,
|
||||
|
||||
#[label("other label")]
|
||||
other_span: SourceSpan,
|
||||
}
|
||||
```
|
||||
|
||||
The `primary` parameter can be used at most once:
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyError {
|
||||
#[label(primary, "main issue")]
|
||||
primary_span: SourceSpan,
|
||||
|
||||
#[label(primary, "other label")] // Error: Cannot have more than one primary label.
|
||||
other_span: SourceSpan,
|
||||
}
|
||||
```
|
||||
|
||||
#### ... collection of labels
|
||||
|
||||
When the number of labels is unknown, you can use a collection of `SourceSpan`
|
||||
|
|
@ -740,7 +785,7 @@ println!("{:?}", report.with_source_code("About something or another or yet anot
|
|||
|
||||
### MSRV
|
||||
|
||||
This crate requires rustc 1.70.0 or later.
|
||||
This crate requires rustc 1.82.0 or later.
|
||||
|
||||
### Acknowledgements
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
msrv = "1.70.0"
|
||||
msrv = "1.82.0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
//! This example shows how to integrate miette with serde_json
|
||||
//! so the decoding source will be annotated with the decoding error,
|
||||
//! providing contextual information about the error.
|
||||
|
||||
use miette::{IntoDiagnostic, SourceOffset};
|
||||
use serde_json::{self, json};
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Library {
|
||||
#[allow(unused)]
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||
#[error("malformed json provided")]
|
||||
struct SerdeError {
|
||||
cause: serde_json::Error,
|
||||
#[source_code]
|
||||
input: String,
|
||||
#[label("{cause}")]
|
||||
location: SourceOffset,
|
||||
}
|
||||
|
||||
impl SerdeError {
|
||||
/// Takes the input and the `serde_json::Error` and returns a SerdeError
|
||||
/// that can be rendered nicely with miette.
|
||||
pub fn from_serde_error(input: impl Into<String>, cause: serde_json::Error) -> Self {
|
||||
let input = input.into();
|
||||
let location = SourceOffset::from_location(&input, cause.line(), cause.column());
|
||||
Self {
|
||||
cause,
|
||||
input,
|
||||
location,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> miette::Result<()> {
|
||||
let input = serde_json::to_string_pretty(&json!({
|
||||
"name": 123
|
||||
}))
|
||||
.into_diagnostic()?;
|
||||
|
||||
let _library: Library =
|
||||
serde_json::from_str(&input).map_err(|cause| SerdeError::from_serde_error(input, cause))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 54 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "miette-derive"
|
||||
version = "7.2.0"
|
||||
version = "7.6.0"
|
||||
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
|
|
@ -11,6 +11,6 @@ repository = "https://github.com/zkat/miette"
|
|||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.78"
|
||||
proc-macro2 = "1.0.83"
|
||||
quote = "1.0.35"
|
||||
syn = "2.0.48"
|
||||
syn = "2.0.87"
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ impl Forward {
|
|||
Self::#variant { #field_name, .. } => #field_name.#method_call,
|
||||
},
|
||||
Forward::Unnamed(index) => {
|
||||
let underscores: Vec<_> = core::iter::repeat(quote! { _, }).take(*index).collect();
|
||||
let underscores: Vec<_> = std::iter::repeat_n(quote! { _, }, *index).collect();
|
||||
let unnamed = format_ident!("unnamed");
|
||||
quote! {
|
||||
Self::#variant ( #(#underscores)* #unnamed, .. ) => #unnamed.#method_call,
|
||||
|
|
|
|||
|
|
@ -1,40 +1,6 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
};
|
||||
|
||||
pub(crate) enum MemberOrString {
|
||||
Member(syn::Member),
|
||||
String(syn::LitStr),
|
||||
}
|
||||
|
||||
impl ToTokens for MemberOrString {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
use MemberOrString::*;
|
||||
match self {
|
||||
Member(member) => member.to_tokens(tokens),
|
||||
String(string) => string.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for MemberOrString {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(syn::Ident) || lookahead.peek(syn::LitInt) {
|
||||
Ok(MemberOrString::Member(input.parse()?))
|
||||
} else if lookahead.peek(syn::LitStr) {
|
||||
Ok(MemberOrString::String(input.parse()?))
|
||||
} else {
|
||||
Err(syn::Error::new(
|
||||
input.span(),
|
||||
"Expected a string or a field reference.",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
use quote::{format_ident, quote};
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ pub(crate) enum ChainState<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Chain<'a> {
|
||||
#[cold]
|
||||
pub(crate) fn new(head: &'a (dyn StdError + 'static)) -> Self {
|
||||
Chain {
|
||||
state: ChainState::Linked { next: Some(head) },
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ impl<'a> ErrorKind<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Debug for ErrorKind<'a> {
|
||||
impl std::fmt::Debug for ErrorKind<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ErrorKind::Diagnostic(d) => d.fmt(f),
|
||||
|
|
@ -83,7 +83,7 @@ impl<'a> std::fmt::Debug for ErrorKind<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for ErrorKind<'a> {
|
||||
impl std::fmt::Display for ErrorKind<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ErrorKind::Diagnostic(d) => d.fmt(f),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
/*!
|
||||
Default trait implementations for [`Diagnostic`].
|
||||
*/
|
||||
|
||||
use std::{convert::Infallible, fmt::Display};
|
||||
|
||||
use crate::{Diagnostic, LabeledSpan, Severity, SourceCode};
|
||||
|
||||
impl Diagnostic for Infallible {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
match *self {}
|
||||
}
|
||||
|
||||
fn severity(&self) -> Option<Severity> {
|
||||
match *self {}
|
||||
}
|
||||
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
match *self {}
|
||||
}
|
||||
|
||||
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
match *self {}
|
||||
}
|
||||
|
||||
fn source_code(&self) -> Option<&dyn SourceCode> {
|
||||
match *self {}
|
||||
}
|
||||
|
||||
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
|
||||
match *self {}
|
||||
}
|
||||
|
||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||
match *self {}
|
||||
}
|
||||
|
||||
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::Report;
|
||||
|
||||
/// Test that [`Infallible`] implements [`Diagnostic`] by seeing if a function that's generic over `Diagnostic`
|
||||
/// will accept `Infallible` as a type parameter.
|
||||
#[test]
|
||||
fn infallible() {
|
||||
let _ = Report::new::<Infallible>;
|
||||
}
|
||||
}
|
||||
88
src/error.rs
88
src/error.rs
|
|
@ -1,25 +1,51 @@
|
|||
use std::{fmt, io};
|
||||
|
||||
use thiserror::Error;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Display},
|
||||
io,
|
||||
};
|
||||
|
||||
use crate::Diagnostic;
|
||||
|
||||
/**
|
||||
Error enum for miette. Used by certain operations in the protocol.
|
||||
*/
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum MietteError {
|
||||
/// Wrapper around [`std::io::Error`]. This is returned when something went
|
||||
/// wrong while reading a [`SourceCode`](crate::SourceCode).
|
||||
#[error(transparent)]
|
||||
IoError(#[from] io::Error),
|
||||
IoError(io::Error),
|
||||
|
||||
/// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the
|
||||
/// bounds of a given [`SourceCode`](crate::SourceCode).
|
||||
#[error("The given offset is outside the bounds of its Source")]
|
||||
OutOfBounds,
|
||||
}
|
||||
|
||||
impl Display for MietteError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
MietteError::IoError(error) => write!(f, "{error}"),
|
||||
MietteError::OutOfBounds => {
|
||||
write!(f, "The given offset is outside the bounds of its Source")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for MietteError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
MietteError::IoError(error) => error.source(),
|
||||
MietteError::OutOfBounds => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for MietteError {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for MietteError {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
match self {
|
||||
|
|
@ -49,3 +75,51 @@ impl Diagnostic for MietteError {
|
|||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use std::{error::Error, io::ErrorKind};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TestError(pub(crate) io::Error);
|
||||
|
||||
impl Display for TestError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "testing, testing...")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TestError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn io_error() {
|
||||
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
|
||||
let outer_error = TestError(inner_error);
|
||||
let io_error = io::Error::new(ErrorKind::Other, outer_error);
|
||||
|
||||
let miette_error = MietteError::from(io_error);
|
||||
|
||||
assert_eq!(miette_error.to_string(), "testing, testing...");
|
||||
assert_eq!(
|
||||
miette_error.source().unwrap().to_string(),
|
||||
"halt and catch fire"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_bounds() {
|
||||
let miette_error = MietteError::OutOfBounds;
|
||||
|
||||
assert_eq!(
|
||||
miette_error.to_string(),
|
||||
"The given offset is outside the bounds of its Source"
|
||||
);
|
||||
assert_eq!(miette_error.source().map(ToString::to_string), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,44 @@ mod ext {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> WrapErr<T, std::convert::Infallible> for Option<T> {
|
||||
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
match self {
|
||||
Some(t) => Ok(t),
|
||||
None => Err(Report::from(crate::eyreish::wrapper::DisplayError(msg))),
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D,
|
||||
{
|
||||
match self {
|
||||
Some(t) => Ok(t),
|
||||
None => Err(Report::from(crate::eyreish::wrapper::DisplayError(msg()))),
|
||||
}
|
||||
}
|
||||
|
||||
fn context<D>(self, msg: D) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.wrap_err(msg)
|
||||
}
|
||||
|
||||
fn with_context<D, F>(self, msg: F) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D,
|
||||
{
|
||||
self.wrap_err_with(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> WrapErr<T, E> for Result<T, E>
|
||||
where
|
||||
E: ext::Diag + Send + Sync + 'static,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ impl Report {
|
|||
/// If the error type does not provide a backtrace, a backtrace will be
|
||||
/// created here to ensure that a backtrace exists.
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
pub fn new<E>(error: E) -> Self
|
||||
where
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
|
|
@ -66,6 +67,7 @@ impl Report {
|
|||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
pub fn msg<M>(message: M) -> Self
|
||||
where
|
||||
M: Display + Debug + Send + Sync + 'static,
|
||||
|
|
@ -86,6 +88,7 @@ impl Report {
|
|||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
pub(crate) fn from_std<E>(error: E) -> Self
|
||||
where
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
|
|
@ -107,6 +110,7 @@ impl Report {
|
|||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
pub(crate) fn from_adhoc<M>(message: M) -> Self
|
||||
where
|
||||
M: Display + Debug + Send + Sync + 'static,
|
||||
|
|
@ -131,6 +135,7 @@ impl Report {
|
|||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
pub(crate) fn from_msg<D, E>(msg: D, error: E) -> Self
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
|
|
@ -155,6 +160,7 @@ impl Report {
|
|||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
pub(crate) fn from_boxed(error: Box<dyn Diagnostic + Send + Sync>) -> Self {
|
||||
use super::wrapper::BoxedError;
|
||||
let error = BoxedError(error);
|
||||
|
|
@ -180,6 +186,7 @@ impl Report {
|
|||
//
|
||||
// Unsafe because the given vtable must have sensible behavior on the error
|
||||
// value of type E.
|
||||
#[cold]
|
||||
unsafe fn construct<E>(
|
||||
error: E,
|
||||
vtable: &'static ErrorVTable,
|
||||
|
|
@ -262,6 +269,7 @@ impl Report {
|
|||
/// None
|
||||
/// }
|
||||
/// ```
|
||||
#[cold]
|
||||
pub fn chain(&self) -> Chain<'_> {
|
||||
unsafe { ErrorImpl::chain(self.inner.by_ref()) }
|
||||
}
|
||||
|
|
@ -272,7 +280,7 @@ impl Report {
|
|||
/// The root cause is the last error in the iterator produced by
|
||||
/// [`chain()`](Report::chain).
|
||||
pub fn root_cause(&self) -> &(dyn StdError + 'static) {
|
||||
self.chain().last().unwrap()
|
||||
self.chain().next_back().unwrap()
|
||||
}
|
||||
|
||||
/// Returns true if `E` is the type held by this error object.
|
||||
|
|
@ -410,13 +418,21 @@ impl Report {
|
|||
}
|
||||
|
||||
/// Provide source code for this error
|
||||
pub fn with_source_code(self, source_code: impl SourceCode + Send + Sync + 'static) -> Report {
|
||||
pub fn with_source_code(self, source_code: impl SourceCode + 'static) -> Report {
|
||||
WithSourceCode {
|
||||
source_code,
|
||||
error: self,
|
||||
}
|
||||
.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
|
||||
|
|
@ -424,6 +440,7 @@ where
|
|||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
fn from(error: E) -> Self {
|
||||
Report::from_std(error)
|
||||
}
|
||||
|
|
@ -533,7 +550,8 @@ where
|
|||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
|
||||
e.cast::<ErrorImpl<E>>().boxed()
|
||||
let unerased = e.cast::<ErrorImpl<E>>().boxed();
|
||||
Box::new(unerased._object)
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||
|
|
@ -544,7 +562,8 @@ where
|
|||
E: StdError + Send + Sync + 'static,
|
||||
{
|
||||
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
|
||||
e.cast::<ErrorImpl<E>>().boxed()
|
||||
let unerased = e.cast::<ErrorImpl<E>>().boxed();
|
||||
Box::new(unerased._object)
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||
|
|
@ -711,22 +730,12 @@ impl ErasedErrorImpl {
|
|||
.deref_mut()
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub(crate) unsafe fn chain(this: Ref<'_, Self>) -> Chain<'_> {
|
||||
Chain::new(Self::error(this))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> StdError for ErrorImpl<E>
|
||||
where
|
||||
E: StdError,
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
unsafe { ErrorImpl::diagnostic(self.erase()).source() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Diagnostic for ErrorImpl<E> where E: Diagnostic {}
|
||||
|
||||
impl<E> Debug for ErrorImpl<E>
|
||||
where
|
||||
E: Debug,
|
||||
|
|
@ -746,6 +755,7 @@ where
|
|||
}
|
||||
|
||||
impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
|
||||
#[cold]
|
||||
fn from(error: Report) -> Self {
|
||||
let outer = ManuallyDrop::new(error);
|
||||
unsafe {
|
||||
|
|
@ -757,6 +767,7 @@ impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
|
|||
}
|
||||
|
||||
impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
|
||||
#[cold]
|
||||
fn from(error: Report) -> Self {
|
||||
let outer = ManuallyDrop::new(error);
|
||||
unsafe {
|
||||
|
|
@ -768,12 +779,14 @@ impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
|
|||
}
|
||||
|
||||
impl From<Report> for Box<dyn Diagnostic + 'static> {
|
||||
#[cold]
|
||||
fn from(error: Report) -> Self {
|
||||
Box::<dyn Diagnostic + Send + Sync>::from(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Report> for Box<dyn StdError + 'static> {
|
||||
#[cold]
|
||||
fn from(error: Report) -> Self {
|
||||
Box::<dyn StdError + Send + Sync>::from(error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,24 @@
|
|||
use thiserror::Error;
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
use crate::{Diagnostic, Report};
|
||||
|
||||
/// Convenience [`Diagnostic`] that can be used as an "anonymous" wrapper for
|
||||
/// Errors. This is intended to be paired with [`IntoDiagnostic`].
|
||||
#[derive(Debug, Error)]
|
||||
#[error(transparent)]
|
||||
struct DiagnosticError(Box<dyn std::error::Error + Send + Sync + 'static>);
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DiagnosticError(pub(crate) Box<dyn std::error::Error + Send + Sync + 'static>);
|
||||
|
||||
impl Display for DiagnosticError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let msg = &self.0;
|
||||
write!(f, "{msg}")
|
||||
}
|
||||
}
|
||||
impl Error for DiagnosticError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
self.0.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for DiagnosticError {}
|
||||
|
||||
/**
|
||||
|
|
@ -31,3 +43,26 @@ impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for R
|
|||
self.map_err(|e| DiagnosticError(Box::new(e)).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::{self, ErrorKind};
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::error::tests::TestError;
|
||||
|
||||
#[test]
|
||||
fn diagnostic_error() {
|
||||
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
|
||||
let outer_error: Result<(), _> = Err(TestError(inner_error));
|
||||
|
||||
let diagnostic_error = outer_error.into_diagnostic().unwrap_err();
|
||||
|
||||
assert_eq!(diagnostic_error.to_string(), "testing, testing...");
|
||||
assert_eq!(
|
||||
diagnostic_error.source().unwrap().to_string(),
|
||||
"halt and catch fire"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ impl<T> AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'stat
|
|||
|
||||
impl Adhoc {
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
pub fn new<M>(self, message: M) -> Report
|
||||
where
|
||||
M: Display + Debug + Send + Sync + 'static,
|
||||
|
|
@ -84,6 +85,7 @@ impl<E> TraitKind for E where E: Into<Report> {}
|
|||
|
||||
impl Trait {
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
pub fn new<E>(self, error: E) -> Report
|
||||
where
|
||||
E: Into<Report>,
|
||||
|
|
@ -105,6 +107,7 @@ impl BoxedKind for Box<dyn Diagnostic + Send + Sync> {}
|
|||
|
||||
impl Boxed {
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
pub fn new(self, error: Box<dyn Diagnostic + Send + Sync>) -> Report {
|
||||
Report::from_boxed(error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ pub struct Report {
|
|||
unsafe impl Sync for Report {}
|
||||
unsafe impl Send for Report {}
|
||||
|
||||
///
|
||||
#[allow(missing_docs)]
|
||||
pub type ErrorHook =
|
||||
Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandl
|
|||
}
|
||||
|
||||
impl dyn ReportHandler {
|
||||
///
|
||||
#[allow(missing_docs)]
|
||||
pub fn is<T: ReportHandler>(&self) -> bool {
|
||||
// Get `TypeId` of the type this function is instantiated with.
|
||||
let t = core::any::TypeId::of::<T>();
|
||||
|
|
@ -121,7 +121,7 @@ impl dyn ReportHandler {
|
|||
t == concrete
|
||||
}
|
||||
|
||||
///
|
||||
#[allow(missing_docs)]
|
||||
pub fn downcast_ref<T: ReportHandler>(&self) -> Option<&T> {
|
||||
if self.is::<T>() {
|
||||
unsafe { Some(&*(self as *const dyn ReportHandler as *const T)) }
|
||||
|
|
@ -130,7 +130,7 @@ impl dyn ReportHandler {
|
|||
}
|
||||
}
|
||||
|
||||
///
|
||||
#[allow(missing_docs)]
|
||||
pub fn downcast_mut<T: ReportHandler>(&mut self) -> Option<&mut T> {
|
||||
if self.is::<T>() {
|
||||
unsafe { Some(&mut *(self as *mut dyn ReportHandler as *mut T)) }
|
||||
|
|
@ -172,11 +172,7 @@ pub trait ReportHandler: core::any::Any + Send + Sync {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn debug(
|
||||
&self,
|
||||
error: &(dyn Diagnostic),
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result;
|
||||
fn debug(&self, error: &dyn Diagnostic, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
|
||||
|
||||
/// Override for the `Display` format
|
||||
fn display(
|
||||
|
|
@ -475,6 +471,7 @@ pub mod private {
|
|||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cold]
|
||||
pub fn new_adhoc<M>(message: M) -> Report
|
||||
where
|
||||
M: Display + Debug + Send + Sync + 'static,
|
||||
|
|
|
|||
|
|
@ -69,9 +69,9 @@ where
|
|||
lifetime: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T> Copy for Ref<'a, T> where T: ?Sized {}
|
||||
impl<T> Copy for Ref<'_, T> where T: ?Sized {}
|
||||
|
||||
impl<'a, T> Clone for Ref<'a, T>
|
||||
impl<T> Clone for Ref<'_, T>
|
||||
where
|
||||
T: ?Sized,
|
||||
{
|
||||
|
|
@ -132,9 +132,9 @@ where
|
|||
lifetime: PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
impl<'a, T> Copy for Mut<'a, T> where T: ?Sized {}
|
||||
impl<T> Copy for Mut<'_, T> where T: ?Sized {}
|
||||
|
||||
impl<'a, T> Clone for Mut<'a, T>
|
||||
impl<T> Clone for Mut<'_, T>
|
||||
where
|
||||
T: ?Sized,
|
||||
{
|
||||
|
|
@ -173,7 +173,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Mut<'a, T> {
|
||||
impl<T> Mut<'_, T> {
|
||||
pub(crate) unsafe fn read(self) -> T {
|
||||
self.ptr.as_ptr().read()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,6 @@ use crate as miette;
|
|||
#[repr(transparent)]
|
||||
pub(crate) struct DisplayError<M>(pub(crate) M);
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct MessageError<M>(pub(crate) M);
|
||||
|
||||
pub(crate) struct NoneError;
|
||||
|
||||
impl<M> Debug for DisplayError<M>
|
||||
where
|
||||
M: Display,
|
||||
|
|
@ -35,6 +30,9 @@ where
|
|||
impl<M> StdError for DisplayError<M> where M: Display + 'static {}
|
||||
impl<M> Diagnostic for DisplayError<M> where M: Display + 'static {}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct MessageError<M>(pub(crate) M);
|
||||
|
||||
impl<M> Debug for MessageError<M>
|
||||
where
|
||||
M: Display + Debug,
|
||||
|
|
@ -56,21 +54,6 @@ where
|
|||
impl<M> StdError for MessageError<M> where M: Display + Debug + 'static {}
|
||||
impl<M> Diagnostic for MessageError<M> where M: Display + Debug + 'static {}
|
||||
|
||||
impl Debug for NoneError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Debug::fmt("Option was None", f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NoneError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt("Option was None", f)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for NoneError {}
|
||||
impl Diagnostic for NoneError {}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct BoxedError(pub(crate) Box<dyn Diagnostic + Send + Sync>);
|
||||
|
||||
|
|
@ -256,18 +239,6 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("outer")]
|
||||
struct Outer {
|
||||
pub(crate) errors: Vec<Inner>,
|
||||
}
|
||||
|
||||
impl Diagnostic for Outer {
|
||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||
Some(Box::new(self.errors.iter().map(|e| e as _)))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_override() {
|
||||
let inner_source = "hello world";
|
||||
|
|
@ -295,6 +266,18 @@ mod tests {
|
|||
#[test]
|
||||
#[cfg(feature = "fancy")]
|
||||
fn two_source_codes() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("outer")]
|
||||
struct Outer {
|
||||
pub(crate) errors: Vec<Inner>,
|
||||
}
|
||||
|
||||
impl Diagnostic for Outer {
|
||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||
Some(Box::new(self.errors.iter().map(|e| e as _)))
|
||||
}
|
||||
}
|
||||
|
||||
let inner_source = "hello world";
|
||||
let outer_source = "abc";
|
||||
|
||||
|
|
|
|||
221
src/handler.rs
221
src/handler.rs
|
|
@ -1,5 +1,3 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::highlighters::Highlighter;
|
||||
use crate::highlighters::MietteHighlighter;
|
||||
use crate::protocol::Diagnostic;
|
||||
|
|
@ -9,24 +7,21 @@ use crate::NarratableReportHandler;
|
|||
use crate::ReportHandler;
|
||||
use crate::ThemeCharacters;
|
||||
use crate::ThemeStyles;
|
||||
use cfg_if::cfg_if;
|
||||
use std::fmt;
|
||||
|
||||
/// Settings to control the color format used for graphical rendering.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
|
||||
pub enum RgbColors {
|
||||
/// Use RGB colors even if the terminal does not support them
|
||||
Always,
|
||||
/// Use RGB colors instead of ANSI if the terminal supports RGB
|
||||
Preferred,
|
||||
/// Always use ANSI, regardless of terminal support for RGB
|
||||
#[default]
|
||||
Never,
|
||||
}
|
||||
|
||||
impl Default for RgbColors {
|
||||
fn default() -> RgbColors {
|
||||
RgbColors::Never
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Create a custom [`MietteHandler`] from options.
|
||||
|
||||
|
|
@ -62,6 +57,7 @@ pub struct MietteHandlerOpts {
|
|||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||
pub(crate) highlighter: Option<MietteHighlighter>,
|
||||
pub(crate) show_related_as_nested: Option<bool>,
|
||||
}
|
||||
|
||||
impl MietteHandlerOpts {
|
||||
|
|
@ -94,7 +90,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())
|
||||
|
|
@ -103,7 +99,9 @@ impl MietteHandlerOpts {
|
|||
/// Setting this option will not force color output. In all cases, the
|
||||
/// current color configuration via
|
||||
/// [`color()`](MietteHandlerOpts::color()) takes precedence over
|
||||
/// highlighter configuration.
|
||||
/// highlighter configuration. However, this option does take precedence over
|
||||
/// [`rgb_colors()`](MietteHandlerOpts::rgb_colors()) (meaning syntax highlighting will be
|
||||
/// enabled regardless of the value of [`MietteHandlerOpts::rgb_colors`]).
|
||||
pub fn with_syntax_highlighting(
|
||||
mut self,
|
||||
highlighter: impl Highlighter + Send + Sync + 'static,
|
||||
|
|
@ -172,6 +170,18 @@ impl MietteHandlerOpts {
|
|||
self
|
||||
}
|
||||
|
||||
/// Show related errors as siblings.
|
||||
pub fn show_related_errors_as_siblings(mut self) -> Self {
|
||||
self.show_related_as_nested = Some(false);
|
||||
self
|
||||
}
|
||||
|
||||
/// Show related errors as nested errors.
|
||||
pub fn show_related_errors_as_nested(mut self) -> Self {
|
||||
self.show_related_as_nested = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// If true, colors will be used during graphical rendering, regardless
|
||||
/// of whether or not the terminal supports them.
|
||||
///
|
||||
|
|
@ -195,6 +205,8 @@ impl MietteHandlerOpts {
|
|||
/// first place. That is handled by the [`MietteHandlerOpts::color`]
|
||||
/// setting. If colors are not being used, the value of `rgb_colors` has
|
||||
/// no effect.
|
||||
///
|
||||
/// It also does not control colors when a syntax highlighter is in use.
|
||||
pub fn rgb_colors(mut self, color: RgbColors) -> Self {
|
||||
self.rgb_colors = color;
|
||||
self
|
||||
|
|
@ -284,31 +296,13 @@ impl MietteHandlerOpts {
|
|||
} else {
|
||||
ThemeStyles::none()
|
||||
};
|
||||
#[cfg(not(feature = "syntect-highlighter"))]
|
||||
let highlighter = self.highlighter.unwrap_or_else(MietteHighlighter::nocolor);
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
let highlighter = if self.color == Some(false) {
|
||||
MietteHighlighter::nocolor()
|
||||
} else if self.color == Some(true) || syscall::supports_color() {
|
||||
match self.highlighter {
|
||||
Some(highlighter) => highlighter,
|
||||
None => match self.rgb_colors {
|
||||
// Because the syntect highlighter currently only supports 24-bit truecolor,
|
||||
// respect RgbColor::Never by disabling the highlighter.
|
||||
// TODO: In the future, find a way to convert the RGB syntect theme
|
||||
// into an ANSI color theme.
|
||||
RgbColors::Never => MietteHighlighter::nocolor(),
|
||||
_ => MietteHighlighter::syntect_truecolor(),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
MietteHighlighter::nocolor()
|
||||
};
|
||||
let highlighter_opt =
|
||||
HighlighterOption::select(self.color, self.highlighter, syscall::supports_color());
|
||||
let theme = self.theme.unwrap_or(GraphicalTheme { characters, styles });
|
||||
let mut handler = GraphicalReportHandler::new_themed(theme)
|
||||
.with_width(width)
|
||||
.with_links(linkify);
|
||||
handler.highlighter = highlighter;
|
||||
handler.highlighter = highlighter_opt.into();
|
||||
if let Some(with_cause_chain) = self.with_cause_chain {
|
||||
if with_cause_chain {
|
||||
handler = handler.with_cause_chain();
|
||||
|
|
@ -337,6 +331,9 @@ impl MietteHandlerOpts {
|
|||
if let Some(s) = self.word_splitter {
|
||||
handler = handler.with_word_splitter(s)
|
||||
}
|
||||
if let Some(b) = self.show_related_as_nested {
|
||||
handler = handler.with_show_related_as_nested(b)
|
||||
}
|
||||
|
||||
MietteHandler {
|
||||
inner: Box::new(handler),
|
||||
|
|
@ -405,7 +402,7 @@ impl Default for MietteHandler {
|
|||
}
|
||||
|
||||
impl ReportHandler for MietteHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
}
|
||||
|
|
@ -414,6 +411,58 @@ impl ReportHandler for MietteHandler {
|
|||
}
|
||||
}
|
||||
|
||||
enum HighlighterOption {
|
||||
Disable,
|
||||
EnableCustom(MietteHighlighter),
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
EnableSyntect,
|
||||
}
|
||||
|
||||
impl HighlighterOption {
|
||||
fn select(
|
||||
color: Option<bool>,
|
||||
highlighter: Option<MietteHighlighter>,
|
||||
supports_color: bool,
|
||||
) -> HighlighterOption {
|
||||
if color == Some(false) || (color.is_none() && !supports_color) {
|
||||
return HighlighterOption::Disable;
|
||||
}
|
||||
highlighter
|
||||
.map(HighlighterOption::EnableCustom)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This is manually implemented so that it's clearer what's going on with
|
||||
// the conditional compilation — clippy isn't picking up the `cfg` stuff here
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for HighlighterOption {
|
||||
fn default() -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "syntect-highlighter")] {
|
||||
// Because the syntect highlighter currently only supports 24-bit truecolor,
|
||||
// it supersedes and ignores the `rgb_colors` config.
|
||||
// TODO: In the future, if we find a way to convert the RGB syntect theme
|
||||
// into an ANSI color theme, we can take `rgb_colors` into account.
|
||||
HighlighterOption::EnableSyntect
|
||||
} else {
|
||||
HighlighterOption::Disable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HighlighterOption> for MietteHighlighter {
|
||||
fn from(opt: HighlighterOption) -> Self {
|
||||
match opt {
|
||||
HighlighterOption::Disable => MietteHighlighter::nocolor(),
|
||||
HighlighterOption::EnableCustom(highlighter) => highlighter,
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
HighlighterOption::EnableSyntect => MietteHighlighter::syntect_truecolor(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod syscall {
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
|
|
@ -439,7 +488,6 @@ mod syscall {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
#[inline]
|
||||
pub(super) fn supports_color() -> bool {
|
||||
cfg_if! {
|
||||
|
|
@ -473,3 +521,108 @@ mod syscall {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::highlighters::BlankHighlighter;
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
#[test]
|
||||
fn test_highlighter_option() {
|
||||
// Syntax highlighting is enabled depending on several variables:
|
||||
// - The `color` config
|
||||
// - The `highlighter` config
|
||||
// - Whether the `syntect-highlighter` feature is enabled
|
||||
// - Whether the terminal supports color
|
||||
//
|
||||
// This test asserts the expected highlighter depending on combinations of those variables.
|
||||
|
||||
macro_rules! assert_highlighter_opt {
|
||||
(opts = $opts:expr, supports_color = $sup_color:literal, expected = $expected:pat $(,)?) => {
|
||||
assert_highlighter_opt!(
|
||||
opts = $opts,
|
||||
supports_color = $sup_color,
|
||||
expected_with_syntect = $expected,
|
||||
expected_without_syntect = $expected,
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
opts = $opts:expr,
|
||||
supports_color = $sup_color:literal,
|
||||
expected_with_syntect = $expected_with:pat,
|
||||
expected_without_syntect = $expected_without:pat $(,)?
|
||||
) => {{
|
||||
let highlighter_opt =
|
||||
HighlighterOption::select($opts.color, $opts.highlighter, $sup_color);
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "syntect-highlighter")] {
|
||||
assert!(matches!(highlighter_opt, $expected_with));
|
||||
} else {
|
||||
assert!(matches!(highlighter_opt, $expected_without));
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
// When color is explicitly disabled, highlighting is also always disabled.
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new().color(false),
|
||||
supports_color = true,
|
||||
expected = HighlighterOption::Disable,
|
||||
);
|
||||
|
||||
// When color is unset and the terminal doesn't support color, highlighting is disabled.
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new(),
|
||||
supports_color = false,
|
||||
expected = HighlighterOption::Disable,
|
||||
);
|
||||
|
||||
// With explicit or implicit color support, highlighting is automatically enabled when
|
||||
// `syntect-highlighter` is enabled.
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new().color(true),
|
||||
supports_color = false,
|
||||
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||
expected_without_syntect = HighlighterOption::Disable,
|
||||
);
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new(),
|
||||
supports_color = true,
|
||||
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||
expected_without_syntect = HighlighterOption::Disable,
|
||||
);
|
||||
|
||||
// With explicit or implicit color support, if custom highlighting is set, it's enabled.
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new()
|
||||
.color(true)
|
||||
.with_syntax_highlighting(BlankHighlighter),
|
||||
supports_color = false,
|
||||
expected = HighlighterOption::EnableCustom(_),
|
||||
);
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new().with_syntax_highlighting(BlankHighlighter),
|
||||
supports_color = true,
|
||||
expected = HighlighterOption::EnableCustom(_),
|
||||
);
|
||||
|
||||
// Setting `RgbColors::Never` has no effect when syntax highlighting is enabled.
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new()
|
||||
.color(true)
|
||||
.rgb_colors(RgbColors::Never),
|
||||
supports_color = false,
|
||||
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||
expected_without_syntect = HighlighterOption::Disable,
|
||||
);
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new().rgb_colors(RgbColors::Never),
|
||||
supports_color = true,
|
||||
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||
expected_without_syntect = HighlighterOption::Disable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ impl DebugReportHandler {
|
|||
pub fn render_report(
|
||||
&self,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
) -> fmt::Result {
|
||||
let mut diag = f.debug_struct("Diagnostic");
|
||||
diag.field("message", &format!("{}", diagnostic));
|
||||
|
|
@ -61,7 +61,7 @@ impl DebugReportHandler {
|
|||
}
|
||||
|
||||
impl ReportHandler for DebugReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::fmt::{self, Write};
|
||||
|
||||
use owo_colors::{OwoColorize, Style, StyledList};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
|
||||
use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
|
||||
use crate::handlers::theme::*;
|
||||
|
|
@ -33,10 +33,12 @@ pub struct GraphicalReportHandler {
|
|||
pub(crate) with_cause_chain: bool,
|
||||
pub(crate) wrap_lines: bool,
|
||||
pub(crate) break_words: bool,
|
||||
pub(crate) with_primary_span_start: bool,
|
||||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||
pub(crate) highlighter: MietteHighlighter,
|
||||
pub(crate) link_display_text: Option<String>,
|
||||
pub(crate) show_related_as_nested: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -60,10 +62,12 @@ impl GraphicalReportHandler {
|
|||
with_cause_chain: true,
|
||||
wrap_lines: true,
|
||||
break_words: true,
|
||||
with_primary_span_start: true,
|
||||
word_separator: None,
|
||||
word_splitter: None,
|
||||
highlighter: MietteHighlighter::default(),
|
||||
link_display_text: None,
|
||||
show_related_as_nested: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,11 +82,13 @@ impl GraphicalReportHandler {
|
|||
tab_width: 4,
|
||||
wrap_lines: true,
|
||||
with_cause_chain: true,
|
||||
with_primary_span_start: true,
|
||||
break_words: true,
|
||||
word_separator: None,
|
||||
word_splitter: None,
|
||||
highlighter: MietteHighlighter::default(),
|
||||
link_display_text: None,
|
||||
show_related_as_nested: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +122,20 @@ impl GraphicalReportHandler {
|
|||
self
|
||||
}
|
||||
|
||||
/// Include the line and column for the the start of the primary span when the
|
||||
/// snippet extends multiple lines
|
||||
pub fn with_primary_span_start(mut self) -> Self {
|
||||
self.with_primary_span_start = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Do not include the line and column for the the start of the primary span
|
||||
/// when the snippet extends multiple lines
|
||||
pub fn without_primary_span_start(mut self) -> Self {
|
||||
self.with_primary_span_start = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to include [`Diagnostic::url()`] in the output.
|
||||
///
|
||||
/// Disabling this is not recommended, but can be useful for more easily
|
||||
|
|
@ -159,7 +179,7 @@ impl GraphicalReportHandler {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the word splitter to usewhen wrapping.
|
||||
/// Sets the word splitter to use when wrapping.
|
||||
pub fn with_word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
|
||||
self.word_splitter = Some(word_splitter);
|
||||
self
|
||||
|
|
@ -177,8 +197,15 @@ impl GraphicalReportHandler {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets whether to render related errors as nested errors.
|
||||
pub fn with_show_related_as_nested(mut self, show_related_as_nested: bool) -> Self {
|
||||
self.show_related_as_nested = show_related_as_nested;
|
||||
self
|
||||
}
|
||||
|
||||
/// 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,
|
||||
|
|
@ -215,17 +242,26 @@ impl GraphicalReportHandler {
|
|||
pub fn render_report(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
) -> fmt::Result {
|
||||
self.render_header(f, diagnostic)?;
|
||||
self.render_causes(f, diagnostic)?;
|
||||
let src = diagnostic.source_code();
|
||||
self.render_report_inner(f, diagnostic, diagnostic.source_code())
|
||||
}
|
||||
|
||||
fn render_report_inner(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &dyn Diagnostic,
|
||||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
let src = diagnostic.source_code().or(parent_src);
|
||||
self.render_header(f, diagnostic, false)?;
|
||||
self.render_causes(f, diagnostic, src)?;
|
||||
self.render_snippets(f, diagnostic, src)?;
|
||||
self.render_footer(f, diagnostic)?;
|
||||
self.render_related(f, diagnostic, src)?;
|
||||
if let Some(footer) = &self.footer {
|
||||
writeln!(f)?;
|
||||
let width = self.termwidth.saturating_sub(4);
|
||||
let width = self.termwidth.saturating_sub(2);
|
||||
let mut opts = textwrap::Options::new(width)
|
||||
.initial_indent(" ")
|
||||
.subsequent_indent(" ")
|
||||
|
|
@ -242,13 +278,19 @@ impl GraphicalReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_header(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &dyn Diagnostic,
|
||||
is_nested: bool,
|
||||
) -> fmt::Result {
|
||||
let severity_style = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => self.theme.styles.error,
|
||||
Some(Severity::Warning) => self.theme.styles.warning,
|
||||
Some(Severity::Advice) => self.theme.styles.advice,
|
||||
};
|
||||
let mut header = String::new();
|
||||
let mut need_newline = is_nested;
|
||||
if self.links == LinkStyle::Link && diagnostic.url().is_some() {
|
||||
let url = diagnostic.url().unwrap(); // safe
|
||||
let code = if let Some(code) = diagnostic.code() {
|
||||
|
|
@ -265,7 +307,7 @@ impl GraphicalReportHandler {
|
|||
);
|
||||
write!(header, "{}", link)?;
|
||||
writeln!(f, "{}", header)?;
|
||||
writeln!(f)?;
|
||||
need_newline = true;
|
||||
} else if let Some(code) = diagnostic.code() {
|
||||
write!(header, "{}", code.style(severity_style),)?;
|
||||
if self.links == LinkStyle::Text && diagnostic.url().is_some() {
|
||||
|
|
@ -273,12 +315,22 @@ impl GraphicalReportHandler {
|
|||
write!(header, " ({})", url.style(self.theme.styles.link))?;
|
||||
}
|
||||
writeln!(f, "{}", header)?;
|
||||
need_newline = true;
|
||||
}
|
||||
if need_newline {
|
||||
writeln!(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_causes(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &dyn Diagnostic,
|
||||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
let src = diagnostic.source_code().or(parent_src);
|
||||
|
||||
let (severity_style, severity_icon) = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => (self.theme.styles.error, &self.theme.characters.error),
|
||||
Some(Severity::Warning) => (self.theme.styles.warning, &self.theme.characters.warning),
|
||||
|
|
@ -354,9 +406,13 @@ impl GraphicalReportHandler {
|
|||
inner_renderer.footer = None;
|
||||
// Cause chains are already flattened, so don't double-print the nested error
|
||||
inner_renderer.with_cause_chain = false;
|
||||
inner_renderer.render_report(&mut inner, diag)?;
|
||||
// Since everything from here on is indented, shrink the virtual terminal
|
||||
inner_renderer.termwidth -= rest_indent.width();
|
||||
inner_renderer.render_report_inner(&mut inner, diag, src)?;
|
||||
|
||||
writeln!(f, "{}", self.wrap(&inner, opts))?;
|
||||
// If there was no header, remove the leading newline
|
||||
let inner = inner.trim_start_matches('\n');
|
||||
writeln!(f, "{}", self.wrap(inner, opts))?;
|
||||
}
|
||||
ErrorKind::StdError(err) => {
|
||||
writeln!(f, "{}", self.wrap(&err.to_string(), opts))?;
|
||||
|
|
@ -368,9 +424,9 @@ impl GraphicalReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||
if let Some(help) = diagnostic.help() {
|
||||
let width = self.termwidth.saturating_sub(4);
|
||||
let width = self.termwidth.saturating_sub(2);
|
||||
let initial_indent = " help: ".style(self.theme.styles.help).to_string();
|
||||
let mut opts = textwrap::Options::new(width)
|
||||
.initial_indent(&initial_indent)
|
||||
|
|
@ -391,26 +447,86 @@ impl GraphicalReportHandler {
|
|||
fn render_related(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
let src = diagnostic.source_code().or(parent_src);
|
||||
|
||||
if let Some(related) = diagnostic.related() {
|
||||
let severity_style = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => self.theme.styles.error,
|
||||
Some(Severity::Warning) => self.theme.styles.warning,
|
||||
Some(Severity::Advice) => self.theme.styles.advice,
|
||||
};
|
||||
|
||||
let mut inner_renderer = self.clone();
|
||||
// Re-enable the printing of nested cause chains for related errors
|
||||
inner_renderer.with_cause_chain = true;
|
||||
writeln!(f)?;
|
||||
for rel in related {
|
||||
match rel.severity() {
|
||||
Some(Severity::Error) | None => write!(f, "Error: ")?,
|
||||
Some(Severity::Warning) => write!(f, "Warning: ")?,
|
||||
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
||||
};
|
||||
inner_renderer.render_header(f, rel)?;
|
||||
inner_renderer.render_causes(f, rel)?;
|
||||
let src = rel.source_code().or(parent_src);
|
||||
inner_renderer.render_snippets(f, rel, src)?;
|
||||
inner_renderer.render_footer(f, rel)?;
|
||||
inner_renderer.render_related(f, rel, src)?;
|
||||
if self.show_related_as_nested {
|
||||
let width = self.termwidth.saturating_sub(2);
|
||||
let mut related = related.peekable();
|
||||
while let Some(rel) = related.next() {
|
||||
let is_last = related.peek().is_none();
|
||||
let char = if !is_last {
|
||||
self.theme.characters.lcross
|
||||
} else {
|
||||
self.theme.characters.lbot
|
||||
};
|
||||
let initial_indent = format!(
|
||||
" {}{}{} ",
|
||||
char, self.theme.characters.hbar, self.theme.characters.rarrow
|
||||
)
|
||||
.style(severity_style)
|
||||
.to_string();
|
||||
let rest_indent = format!(
|
||||
" {} ",
|
||||
if is_last {
|
||||
' '
|
||||
} else {
|
||||
self.theme.characters.vbar
|
||||
}
|
||||
)
|
||||
.style(severity_style)
|
||||
.to_string();
|
||||
|
||||
let mut opts = textwrap::Options::new(width)
|
||||
.initial_indent(&initial_indent)
|
||||
.subsequent_indent(&rest_indent)
|
||||
.break_words(self.break_words);
|
||||
if let Some(word_separator) = self.word_separator {
|
||||
opts = opts.word_separator(word_separator);
|
||||
}
|
||||
if let Some(word_splitter) = self.word_splitter.clone() {
|
||||
opts = opts.word_splitter(word_splitter);
|
||||
}
|
||||
|
||||
let mut inner = String::new();
|
||||
|
||||
let mut inner_renderer = self.clone();
|
||||
inner_renderer.footer = None;
|
||||
inner_renderer.with_cause_chain = false;
|
||||
inner_renderer.termwidth -= rest_indent.width();
|
||||
inner_renderer.render_report_inner(&mut inner, rel, src)?;
|
||||
|
||||
// If there was no header, remove the leading newline
|
||||
let inner = inner.trim_matches('\n');
|
||||
writeln!(f, "{}", self.wrap(inner, opts))?;
|
||||
}
|
||||
} else {
|
||||
for rel in related {
|
||||
writeln!(f)?;
|
||||
match rel.severity() {
|
||||
Some(Severity::Error) | None => write!(f, "Error: ")?,
|
||||
Some(Severity::Warning) => write!(f, "Warning: ")?,
|
||||
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
||||
};
|
||||
inner_renderer.render_header(f, rel, true)?;
|
||||
let src = rel.source_code().or(parent_src);
|
||||
inner_renderer.render_causes(f, rel, src)?;
|
||||
inner_renderer.render_snippets(f, rel, src)?;
|
||||
inner_renderer.render_footer(f, rel)?;
|
||||
inner_renderer.render_related(f, rel, src)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -419,7 +535,7 @@ impl GraphicalReportHandler {
|
|||
fn render_snippets(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
opt_source: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
let source = match opt_source {
|
||||
|
|
@ -565,23 +681,34 @@ impl GraphicalReportHandler {
|
|||
};
|
||||
|
||||
if let Some(source_name) = primary_contents.name() {
|
||||
let source_name = source_name.style(self.theme.styles.link);
|
||||
writeln!(
|
||||
f,
|
||||
"[{}:{}:{}]",
|
||||
source_name,
|
||||
primary_contents.line() + 1,
|
||||
primary_contents.column() + 1
|
||||
)?;
|
||||
} else if lines.len() <= 1 {
|
||||
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
|
||||
} else {
|
||||
if self.with_primary_span_start {
|
||||
writeln!(
|
||||
f,
|
||||
"[{}]",
|
||||
format_args!(
|
||||
"{}:{}:{}",
|
||||
source_name,
|
||||
primary_contents.line() + 1,
|
||||
primary_contents.column() + 1
|
||||
)
|
||||
.style(self.theme.styles.link)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
f,
|
||||
"[{}]",
|
||||
format_args!("{}", source_name,).style(self.theme.styles.link)
|
||||
)?;
|
||||
}
|
||||
} else if self.with_primary_span_start && lines.len() > 1 {
|
||||
writeln!(
|
||||
f,
|
||||
"[{}:{}]",
|
||||
primary_contents.line() + 1,
|
||||
primary_contents.column() + 1
|
||||
)?;
|
||||
} else {
|
||||
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
|
||||
}
|
||||
|
||||
// Now it's time for the fun part--actually rendering everything!
|
||||
|
|
@ -664,7 +791,7 @@ impl GraphicalReportHandler {
|
|||
f,
|
||||
max_gutter,
|
||||
line,
|
||||
&labels,
|
||||
labels,
|
||||
LabelRenderMode::SingleLine,
|
||||
)?;
|
||||
|
||||
|
|
@ -680,7 +807,7 @@ impl GraphicalReportHandler {
|
|||
f,
|
||||
max_gutter,
|
||||
line,
|
||||
&labels,
|
||||
labels,
|
||||
LabelRenderMode::MultiLineFirst,
|
||||
)?;
|
||||
|
||||
|
|
@ -698,7 +825,7 @@ impl GraphicalReportHandler {
|
|||
f,
|
||||
max_gutter,
|
||||
line,
|
||||
&labels,
|
||||
labels,
|
||||
LabelRenderMode::MultiLineRest,
|
||||
)?;
|
||||
self.render_multi_line_end_single(
|
||||
|
|
@ -711,13 +838,7 @@ impl GraphicalReportHandler {
|
|||
}
|
||||
} else {
|
||||
// gutter _again_
|
||||
self.render_highlight_gutter(
|
||||
f,
|
||||
max_gutter,
|
||||
line,
|
||||
&labels,
|
||||
LabelRenderMode::SingleLine,
|
||||
)?;
|
||||
self.render_highlight_gutter(f, max_gutter, line, labels, LabelRenderMode::SingleLine)?;
|
||||
// has no label
|
||||
writeln!(f, "{}", self.theme.characters.hbar.style(label.style))?;
|
||||
}
|
||||
|
|
@ -799,7 +920,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;
|
||||
|
|
@ -891,12 +1012,10 @@ impl GraphicalReportHandler {
|
|||
} else {
|
||||
result.push_str(opts.initial_indent);
|
||||
}
|
||||
} else if line.trim().is_empty() {
|
||||
result.push_str(trimmed_indent);
|
||||
} else {
|
||||
if line.trim().is_empty() {
|
||||
result.push_str(trimmed_indent);
|
||||
} else {
|
||||
result.push_str(opts.subsequent_indent);
|
||||
}
|
||||
result.push_str(opts.subsequent_indent);
|
||||
}
|
||||
result.push_str(line);
|
||||
}
|
||||
|
|
@ -1188,14 +1307,14 @@ 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();
|
||||
let mut line_offset = offset;
|
||||
let mut line_str = String::with_capacity(context.len());
|
||||
let mut lines = Vec::with_capacity(1);
|
||||
let mut iter = context.chars().peekable();
|
||||
let mut line_str = String::new();
|
||||
let mut lines = Vec::new();
|
||||
while let Some(char) = iter.next() {
|
||||
offset += char.len_utf8();
|
||||
let mut at_end_of_file = false;
|
||||
|
|
@ -1242,7 +1361,7 @@ impl GraphicalReportHandler {
|
|||
}
|
||||
|
||||
impl ReportHandler for GraphicalReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ impl JSONReportHandler {
|
|||
pub fn render_report(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
) -> fmt::Result {
|
||||
self._render_report(f, diagnostic, None)
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ impl JSONReportHandler {
|
|||
fn _render_report(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
write!(f, r#"{{"message": "{}","#, escape(&diagnostic.to_string()))?;
|
||||
|
|
@ -154,7 +154,7 @@ impl JSONReportHandler {
|
|||
fn render_snippets(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
source: &dyn SourceCode,
|
||||
) -> fmt::Result {
|
||||
if let Some(mut labels) = diagnostic.labels() {
|
||||
|
|
@ -170,7 +170,7 @@ impl JSONReportHandler {
|
|||
}
|
||||
|
||||
impl ReportHandler for JSONReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.render_report(f, diagnostic)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ impl NarratableReportHandler {
|
|||
pub fn render_report(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
) -> fmt::Result {
|
||||
self.render_header(f, diagnostic)?;
|
||||
if self.with_cause_chain {
|
||||
|
|
@ -85,7 +85,7 @@ impl NarratableReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||
writeln!(f, "{}", diagnostic)?;
|
||||
let severity = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => "error",
|
||||
|
|
@ -96,7 +96,7 @@ impl NarratableReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||
if let Some(cause_iter) = diagnostic
|
||||
.diagnostic_source()
|
||||
.map(DiagnosticChain::from_diagnostic)
|
||||
|
|
@ -110,7 +110,7 @@ impl NarratableReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||
if let Some(help) = diagnostic.help() {
|
||||
writeln!(f, "diagnostic help: {}", help)?;
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ impl NarratableReportHandler {
|
|||
fn render_related(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
if let Some(related) = diagnostic.related() {
|
||||
|
|
@ -152,7 +152,7 @@ impl NarratableReportHandler {
|
|||
fn render_snippets(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
source_code: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
if let Some(source) = source_code {
|
||||
|
|
@ -295,9 +295,9 @@ impl NarratableReportHandler {
|
|||
let mut column = context_data.column();
|
||||
let mut offset = context_data.span().offset();
|
||||
let mut line_offset = offset;
|
||||
let mut line_str = String::with_capacity(context.len());
|
||||
let mut lines = Vec::with_capacity(1);
|
||||
let mut iter = context.chars().peekable();
|
||||
let mut line_str = String::new();
|
||||
let mut lines = Vec::new();
|
||||
while let Some(char) = iter.next() {
|
||||
offset += char.len_utf8();
|
||||
let mut at_end_of_file = false;
|
||||
|
|
@ -344,7 +344,7 @@ impl NarratableReportHandler {
|
|||
}
|
||||
|
||||
impl ReportHandler for NarratableReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ impl Default for GraphicalTheme {
|
|||
fn default() -> Self {
|
||||
match std::env::var("NO_COLOR") {
|
||||
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
|
||||
Self::ascii()
|
||||
Self::none()
|
||||
}
|
||||
Ok(string) if string != "0" => Self::unicode_nocolor(),
|
||||
_ => Self::unicode(),
|
||||
|
|
|
|||
|
|
@ -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>);
|
||||
|
|
@ -92,7 +92,7 @@ impl Default for MietteHighlighter {
|
|||
}
|
||||
#[cfg(not(feature = "syntect-highlighter"))]
|
||||
fn default() -> Self {
|
||||
return MietteHighlighter::nocolor();
|
||||
MietteHighlighter::nocolor()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::path::Path;
|
||||
|
||||
// all syntect imports are explicitly qualified, but their paths are shortened for convenience
|
||||
#[allow(clippy::module_inception)]
|
||||
mod syntect {
|
||||
pub(super) use syntect::{
|
||||
highlighting::{
|
||||
|
|
@ -19,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.
|
||||
|
|
@ -80,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() {
|
||||
|
|
@ -95,16 +96,16 @@ impl SyntectHighlighter {
|
|||
}
|
||||
}
|
||||
// finally, attempt to guess syntax based on first line
|
||||
return self.syntax_set.find_syntax_by_first_line(
|
||||
&std::str::from_utf8(contents.data())
|
||||
self.syntax_set.find_syntax_by_first_line(
|
||||
std::str::from_utf8(contents.data())
|
||||
.ok()?
|
||||
.split('\n')
|
||||
.next()?,
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stateful highlighting iterator for [SyntectHighlighter]
|
||||
/// Stateful highlighting iterator for [`SyntectHighlighter`].
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SyntectHighlighterState<'h> {
|
||||
syntax_set: &'h syntect::SyntaxSet,
|
||||
|
|
@ -114,17 +115,17 @@ pub(crate) struct SyntectHighlighterState<'h> {
|
|||
use_bg_color: bool,
|
||||
}
|
||||
|
||||
impl<'h> HighlighterState for SyntectHighlighterState<'h> {
|
||||
impl HighlighterState for SyntectHighlighterState<'_> {
|
||||
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>> {
|
||||
if let Ok(ops) = self.parse_state.parse_line(line, &self.syntax_set) {
|
||||
if let Ok(ops) = self.parse_state.parse_line(line, self.syntax_set) {
|
||||
let use_bg_color = self.use_bg_color;
|
||||
syntect::HighlightIterator::new(
|
||||
&mut self.highlight_state,
|
||||
&ops,
|
||||
line,
|
||||
&mut self.highlighter,
|
||||
&self.highlighter,
|
||||
)
|
||||
.map(|(style, str)| (convert_style(style, use_bg_color).style(str)))
|
||||
.map(|(style, str)| convert_style(style, use_bg_color).style(str))
|
||||
.collect()
|
||||
} else {
|
||||
vec![Style::default().style(line)]
|
||||
|
|
@ -132,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 {
|
||||
|
|
|
|||
110
src/lib.rs
110
src/lib.rs
|
|
@ -1,5 +1,6 @@
|
|||
#![deny(missing_docs, missing_debug_implementations, nonstandard_style)]
|
||||
#![warn(unreachable_pub, rust_2018_idioms)]
|
||||
#![allow(unexpected_cfgs)]
|
||||
//! You run miette? You run her code like the software? Oh. Oh! Error code for
|
||||
//! coder! Error code for One Thousand Lines!
|
||||
//!
|
||||
|
|
@ -27,9 +28,9 @@
|
|||
//! " />
|
||||
//!
|
||||
//! > **NOTE: You must enable the `"fancy"` crate feature to get fancy report
|
||||
//! output like in the screenshots above.** You should only do this in your
|
||||
//! toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||
//! libraries and such might not want.
|
||||
//! > output like in the screenshots above.** You should only do this in your
|
||||
//! > toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||
//! > libraries and such might not want.
|
||||
//!
|
||||
//! ## Table of Contents <!-- omit in toc -->
|
||||
//!
|
||||
|
|
@ -50,6 +51,7 @@
|
|||
//! - [... handler options](#-handler-options)
|
||||
//! - [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||
//! - [... syntax highlighting](#-syntax-highlighting)
|
||||
//! - [... primary label](#-primary-label)
|
||||
//! - [... collection of labels](#-collection-of-labels)
|
||||
//! - [Acknowledgements](#acknowledgements)
|
||||
//! - [License](#license)
|
||||
|
|
@ -99,7 +101,7 @@
|
|||
//!
|
||||
//! `thiserror` is a great way to define them, and plays nicely with `miette`!
|
||||
//! */
|
||||
//! use miette::{Diagnostic, SourceSpan};
|
||||
//! use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||
//! use thiserror::Error;
|
||||
//!
|
||||
//! #[derive(Error, Debug, Diagnostic)]
|
||||
|
|
@ -126,12 +128,11 @@
|
|||
//! throughout your app (but NOT your libraries! Those should always return
|
||||
//! concrete types!).
|
||||
//! */
|
||||
//! use miette::{NamedSource, Result};
|
||||
//! use miette::Result;
|
||||
//! fn this_fails() -> Result<()> {
|
||||
//! // You can use plain strings as a `Source`, or anything that implements
|
||||
//! // the one-method `Source` trait.
|
||||
//! let src = "source\n text\n here".to_string();
|
||||
//! let len = src.len();
|
||||
//!
|
||||
//! Err(MyBad {
|
||||
//! src: NamedSource::new("bad_file.rs", src),
|
||||
|
|
@ -161,17 +162,20 @@
|
|||
//! <img src="https://raw.githubusercontent.com/zkat/miette/main/images/single-line-example.png" alt="
|
||||
//! Narratable printout:
|
||||
//! \
|
||||
//! Error: Types mismatched for operation.
|
||||
//! Diagnostic severity: error
|
||||
//! Begin snippet starting at line 1, column 1
|
||||
//! diagnostic error code: oops::my::bad (link)
|
||||
//! Error: oops!
|
||||
//! \
|
||||
//! snippet line 1: 3 + "5"
|
||||
//! label starting at line 1, column 1: int
|
||||
//! label starting at line 1, column 1: doesn't support these values.
|
||||
//! label starting at line 1, column 1: string
|
||||
//! diagnostic help: Change int or string to be the right types and try again.
|
||||
//! diagnostic code: nu::parser::unsupported_operation
|
||||
//! For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
|
||||
//! Begin snippet for bad_file.rs starting
|
||||
//! at line 2, column 3
|
||||
//! \
|
||||
//! snippet line 1: source
|
||||
//! \
|
||||
//! snippet line 2: text
|
||||
//! highlight starting at line 1, column 3: This bit here
|
||||
//! \
|
||||
//! snippet line 3: here
|
||||
//! \
|
||||
//! diagnostic help: try doing it better next time?">
|
||||
//!
|
||||
//! ## Using
|
||||
//!
|
||||
|
|
@ -207,6 +211,17 @@
|
|||
//! // Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
||||
//! #[diagnostic(transparent)]
|
||||
//! AnotherError(#[from] AnotherError),
|
||||
//!
|
||||
//! /// Forward the diagnostic to a particular field.
|
||||
//! #[error("other error")]
|
||||
//! #[diagnostic(forward(the_actual_diagnostic))]
|
||||
//! EvenMoreData {
|
||||
//! unrelated_field_1: String,
|
||||
//! unrelated_field_2: usize,
|
||||
//!
|
||||
//! #[source]
|
||||
//! the_actual_diagnostic: AnotherError,
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Error, Diagnostic, Debug)]
|
||||
|
|
@ -241,7 +256,7 @@
|
|||
//! use semver::Version;
|
||||
//!
|
||||
//! pub fn some_tool() -> Result<Version> {
|
||||
//! Ok("1.2.x".parse().into_diagnostic()?)
|
||||
//! "1.2.x".parse().into_diagnostic()
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
|
|
@ -256,24 +271,24 @@
|
|||
//! use semver::Version;
|
||||
//!
|
||||
//! pub fn some_tool() -> Result<Version> {
|
||||
//! Ok("1.2.x"
|
||||
//! "1.2.x"
|
||||
//! .parse()
|
||||
//! .into_diagnostic()
|
||||
//! .wrap_err("Parsing this tool's semver version failed.")?)
|
||||
//! .wrap_err("Parsing this tool's semver version failed.")
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! To construct your own simple adhoc error use the [miette!] macro:
|
||||
//! To construct your own simple adhoc error use the [`miette!`] macro:
|
||||
//! ```rust
|
||||
//! // my_app/lib/my_internal_file.rs
|
||||
//! use miette::{miette, IntoDiagnostic, Result, WrapErr};
|
||||
//! use miette::{miette, Result};
|
||||
//! use semver::Version;
|
||||
//!
|
||||
//! pub fn some_tool() -> Result<Version> {
|
||||
//! let version = "1.2.x";
|
||||
//! Ok(version
|
||||
//! version
|
||||
//! .parse()
|
||||
//! .map_err(|_| miette!("Invalid version {}", version))?)
|
||||
//! .map_err(|_| miette!("Invalid version {}", version))
|
||||
//! }
|
||||
//! ```
|
||||
//! There are also similar [bail!] and [ensure!] macros.
|
||||
|
|
@ -285,9 +300,9 @@
|
|||
//! automatically.
|
||||
//!
|
||||
//! > **NOTE:** You must enable the `"fancy"` crate feature to get fancy report
|
||||
//! output like in the screenshots here.** You should only do this in your
|
||||
//! toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||
//! libraries and such might not want.
|
||||
//! > output like in the screenshots here.** You should only do this in your
|
||||
//! > toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||
//! > libraries and such might not want.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use miette::{IntoDiagnostic, Result};
|
||||
|
|
@ -632,7 +647,6 @@
|
|||
//! )
|
||||
//! }))
|
||||
//!
|
||||
//! # .unwrap()
|
||||
//! ```
|
||||
//!
|
||||
//! See the docs for [`MietteHandlerOpts`] for more details on what you can
|
||||
|
|
@ -643,8 +657,8 @@
|
|||
//! If you...
|
||||
//! - ...don't know all the possible errors upfront
|
||||
//! - ...need to serialize/deserialize errors
|
||||
//! then you may want to use [`miette!`], [`diagnostic!`] macros or
|
||||
//! [`MietteDiagnostic`] directly to create diagnostic on the fly.
|
||||
//! then you may want to use [`miette!`], [`diagnostic!`] macros or
|
||||
//! [`MietteDiagnostic`] directly to create diagnostic on the fly.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! # use miette::{miette, LabeledSpan, Report};
|
||||
|
|
@ -672,12 +686,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.
|
||||
|
|
@ -688,6 +702,37 @@
|
|||
//! [`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
||||
//! method. See the [`highlighters`] module docs for more details.
|
||||
//!
|
||||
//! ### ... primary label
|
||||
//!
|
||||
//! You can use the `primary` parameter to `label` to indicate that the label
|
||||
//! is the primary label.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("oops!")]
|
||||
//! struct MyError {
|
||||
//! #[label(primary, "main issue")]
|
||||
//! primary_span: SourceSpan,
|
||||
//!
|
||||
//! #[label("other label")]
|
||||
//! other_span: SourceSpan,
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! The `primary` parameter can be used at most once:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("oops!")]
|
||||
//! struct MyError {
|
||||
//! #[label(primary, "main issue")]
|
||||
//! primary_span: SourceSpan,
|
||||
//!
|
||||
//! #[label(primary, "other label")] // Error: Cannot have more than one primary label.
|
||||
//! other_span: SourceSpan,
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### ... collection of labels
|
||||
//!
|
||||
//! When the number of labels is unknown, you can use a collection of `SourceSpan`
|
||||
|
|
@ -741,7 +786,7 @@
|
|||
//!
|
||||
//! ## MSRV
|
||||
//!
|
||||
//! This crate requires rustc 1.70.0 or later.
|
||||
//! This crate requires rustc 1.82.0 or later.
|
||||
//!
|
||||
//! ## Acknowledgements
|
||||
//!
|
||||
|
|
@ -786,6 +831,7 @@ pub use protocol::*;
|
|||
|
||||
mod chain;
|
||||
mod diagnostic_chain;
|
||||
mod diagnostic_impls;
|
||||
mod error;
|
||||
mod eyreish;
|
||||
#[cfg(feature = "fancy-base")]
|
||||
|
|
|
|||
60
src/panic.rs
60
src/panic.rs
|
|
@ -1,7 +1,8 @@
|
|||
use backtrace::Backtrace;
|
||||
use thiserror::Error;
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
use crate::{self as miette, Context, Diagnostic, Result};
|
||||
use backtrace::Backtrace;
|
||||
|
||||
use crate::{Context, Diagnostic, Result};
|
||||
|
||||
/// Tells miette to render panics using its rendering engine.
|
||||
pub fn set_panic_hook() {
|
||||
|
|
@ -12,7 +13,7 @@ pub fn set_panic_hook() {
|
|||
message = msg.to_string();
|
||||
}
|
||||
if let Some(msg) = payload.downcast_ref::<String>() {
|
||||
message = msg.clone();
|
||||
message.clone_from(msg);
|
||||
}
|
||||
let mut report: Result<()> = Err(Panic(message).into());
|
||||
if let Some(loc) = info.location() {
|
||||
|
|
@ -25,11 +26,27 @@ pub fn set_panic_hook() {
|
|||
}));
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("{0}{}", Panic::backtrace())]
|
||||
#[diagnostic(help("set the `RUST_BACKTRACE=1` environment variable to display a backtrace."))]
|
||||
#[derive(Debug)]
|
||||
struct Panic(String);
|
||||
|
||||
impl Display for Panic {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let msg = &self.0;
|
||||
let panic = Panic::backtrace();
|
||||
write!(f, "{msg}{panic}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for Panic {}
|
||||
|
||||
impl Diagnostic for Panic {
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
Some(Box::new(
|
||||
"set the `RUST_BACKTRACE=1` environment variable to display a backtrace.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Panic {
|
||||
fn backtrace() -> String {
|
||||
use std::fmt::Write;
|
||||
|
|
@ -84,3 +101,32 @@ impl Panic {
|
|||
"".into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::error::Error;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn panic() {
|
||||
let panic = Panic("ruh roh raggy".to_owned());
|
||||
|
||||
assert_eq!(panic.to_string(), "ruh roh raggy");
|
||||
assert!(panic.source().is_none());
|
||||
assert!(panic.code().is_none());
|
||||
assert!(panic.severity().is_none());
|
||||
assert_eq!(
|
||||
panic.help().map(|h| h.to_string()),
|
||||
Some(
|
||||
"set the `RUST_BACKTRACE=1` environment variable to display a backtrace."
|
||||
.to_owned()
|
||||
)
|
||||
);
|
||||
assert!(panic.url().is_none());
|
||||
assert!(panic.source_code().is_none());
|
||||
assert!(panic.labels().is_none());
|
||||
assert!(panic.related().is_none());
|
||||
assert!(panic.diagnostic_source().is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use std::{
|
|||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::MietteError;
|
||||
use crate::{DiagnosticError, MietteError};
|
||||
|
||||
/// Adds rich metadata to your Error that can be used by
|
||||
/// [`Report`](crate::Report) to print really nice and human-friendly error
|
||||
|
|
@ -134,7 +134,7 @@ impl From<&str> for Box<dyn Diagnostic> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
|
||||
impl From<&str> for Box<dyn Diagnostic + Send + Sync + '_> {
|
||||
fn from(s: &str) -> Self {
|
||||
From::from(String::from(s))
|
||||
}
|
||||
|
|
@ -174,18 +174,7 @@ impl From<String> for Box<dyn Diagnostic + Send + Sync> {
|
|||
|
||||
impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
|
||||
fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
|
||||
#[derive(thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
|
||||
impl fmt::Debug for BoxedDiagnostic {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for BoxedDiagnostic {}
|
||||
|
||||
Box::new(BoxedDiagnostic(s))
|
||||
Box::new(DiagnosticError(s))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -523,7 +512,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
|
||||
|
|
@ -614,6 +603,28 @@ impl From<std::ops::Range<ByteOffset>> for SourceSpan {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<std::ops::RangeInclusive<ByteOffset>> for SourceSpan {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the total length of the inclusive range would overflow a
|
||||
/// `usize`. This will only occur with the range `0..=usize::MAX`.
|
||||
fn from(range: std::ops::RangeInclusive<ByteOffset>) -> Self {
|
||||
let (start, end) = range.clone().into_inner();
|
||||
Self {
|
||||
offset: start.into(),
|
||||
length: if range.is_empty() {
|
||||
0
|
||||
} else {
|
||||
// will not overflow because `is_empty() == false` guarantees
|
||||
// that `start <= end`
|
||||
(end - start)
|
||||
.checked_add(1)
|
||||
.expect("length of inclusive range should fit in a usize")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SourceOffset> for SourceSpan {
|
||||
fn from(offset: SourceOffset) -> Self {
|
||||
Self { offset, length: 0 }
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ impl SourceCode for [u8] {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'src> SourceCode for &'src [u8] {
|
||||
impl SourceCode for &[u8] {
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
span: &SourceSpan,
|
||||
|
|
@ -143,7 +143,7 @@ impl SourceCode for str {
|
|||
}
|
||||
|
||||
/// Makes `src: &'static str` or `struct S<'a> { src: &'a str }` usable.
|
||||
impl<'s> SourceCode for &'s str {
|
||||
impl SourceCode for &str {
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
span: &SourceSpan,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#![cfg(feature = "fancy-no-backtrace")]
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler, RgbColors};
|
||||
use regex::Regex;
|
||||
use std::ffi::OsString;
|
||||
|
|
@ -69,9 +68,7 @@ impl Drop for EnvVarGuard<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref COLOR_ENV_VARS: Mutex<()> = Mutex::new(());
|
||||
}
|
||||
static COLOR_ENV_VARS: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// Assert the color format used by a handler with different levels of terminal
|
||||
/// support.
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ fn related() {
|
|||
#[derive(Error, Debug, Diagnostic)]
|
||||
#[error("welp")]
|
||||
#[diagnostic(code(foo::bar::baz))]
|
||||
#[allow(dead_code)]
|
||||
struct Foo {
|
||||
#[related]
|
||||
related: Vec<Baz>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Diagnostic)]
|
||||
#[allow(dead_code)]
|
||||
enum Bar {
|
||||
#[error("variant1")]
|
||||
#[diagnostic(code(foo::bar::baz))]
|
||||
|
|
@ -29,6 +31,7 @@ fn related() {
|
|||
|
||||
#[derive(Error, Debug, Diagnostic)]
|
||||
#[error("welp2")]
|
||||
#[allow(dead_code)]
|
||||
struct Baz;
|
||||
}
|
||||
|
||||
|
|
@ -37,6 +40,7 @@ fn related_report() {
|
|||
#[derive(Error, Debug, Diagnostic)]
|
||||
#[error("welp")]
|
||||
#[diagnostic(code(foo::bar::baz))]
|
||||
#[allow(dead_code)]
|
||||
struct Foo {
|
||||
#[related]
|
||||
related: Vec<Report>,
|
||||
|
|
@ -288,6 +292,7 @@ fn test_snippet_named_struct() {
|
|||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic(code(foo::bar::baz))]
|
||||
#[allow(dead_code)]
|
||||
struct Foo<'a> {
|
||||
#[source_code]
|
||||
src: &'a str,
|
||||
|
|
@ -310,6 +315,7 @@ fn test_snippet_unnamed_struct() {
|
|||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic(code(foo::bar::baz))]
|
||||
#[allow(dead_code)]
|
||||
struct Foo<'a>(
|
||||
#[source_code] &'a str,
|
||||
#[label("{0}")] SourceSpan,
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| {
|
||||
handler.with_width(10)
|
||||
});
|
||||
let expected = r#" × abcd
|
||||
let expected = r#"
|
||||
× abcd
|
||||
│ efgh
|
||||
│ ijkl
|
||||
│ mnop
|
||||
|
|
@ -74,7 +75,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ uvwx
|
||||
│ yz
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Unless, word breaking is disabled
|
||||
|
|
@ -89,7 +90,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
Report::msg("12 123 1234 12345 123456 1234567 1234567890"),
|
||||
|handler| handler.with_width(10),
|
||||
);
|
||||
let expected = r#" × 12
|
||||
let expected = r#"
|
||||
× 12
|
||||
│ 123
|
||||
│ 1234
|
||||
│ 1234
|
||||
|
|
@ -102,7 +104,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ 5678
|
||||
│ 90
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// But long words should not break if word breaking is disabled
|
||||
|
|
@ -110,7 +112,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
Report::msg("12 123 1234 12345 123456 1234567 1234567890"),
|
||||
|handler| handler.with_width(10).with_break_words(false),
|
||||
);
|
||||
let expected = r#" × 12
|
||||
let expected = r#"
|
||||
× 12
|
||||
│ 123
|
||||
│ 1234
|
||||
│ 12345
|
||||
|
|
@ -118,7 +121,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ 1234567
|
||||
│ 1234567890
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Unless, of course, there are hyphens
|
||||
|
|
@ -126,7 +129,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
Report::msg("a-b a-b-c a-b-c-d a-b-c-d-e a-b-c-d-e-f a-b-c-d-e-f-g a-b-c-d-e-f-g-h"),
|
||||
|handler| handler.with_width(10).with_break_words(false),
|
||||
);
|
||||
let expected = r#" × a-b
|
||||
let expected = r#"
|
||||
× a-b
|
||||
│ a-b-
|
||||
│ c a-
|
||||
│ b-c-
|
||||
|
|
@ -145,7 +149,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ f-g-
|
||||
│ h
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Which requires an additional opt-out
|
||||
|
|
@ -158,7 +162,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
.with_word_splitter(textwrap::WordSplitter::NoHyphenation)
|
||||
},
|
||||
);
|
||||
let expected = r#" × a-b
|
||||
let expected = r#"
|
||||
× a-b
|
||||
│ a-b-c
|
||||
│ a-b-c-d
|
||||
│ a-b-c-d-e
|
||||
|
|
@ -166,7 +171,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ a-b-c-d-e-f-g
|
||||
│ a-b-c-d-e-f-g-h
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Or if there are _other_ unicode word boundaries
|
||||
|
|
@ -174,7 +179,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
Report::msg("a/b a/b/c a/b/c/d a/b/c/d/e a/b/c/d/e/f a/b/c/d/e/f/g a/b/c/d/e/f/g/h"),
|
||||
|handler| handler.with_width(10).with_break_words(false),
|
||||
);
|
||||
let expected = r#" × a/b
|
||||
let expected = r#"
|
||||
× a/b
|
||||
│ a/b/
|
||||
│ c a/
|
||||
│ b/c/
|
||||
|
|
@ -193,7 +199,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ f/g/
|
||||
│ h
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Such things require you to opt-in to only breaking on ASCII whitespace
|
||||
|
|
@ -206,7 +212,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
.with_word_separator(textwrap::WordSeparator::AsciiSpace)
|
||||
},
|
||||
);
|
||||
let expected = r#" × a/b
|
||||
let expected = r#"
|
||||
× a/b
|
||||
│ a/b/c
|
||||
│ a/b/c/d
|
||||
│ a/b/c/d/e
|
||||
|
|
@ -214,7 +221,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ a/b/c/d/e/f/g
|
||||
│ a/b/c/d/e/f/g/h
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -227,7 +234,8 @@ fn wrap_option() -> Result<(), MietteError> {
|
|||
Report::msg("abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz"),
|
||||
|handler| handler.with_width(15),
|
||||
);
|
||||
let expected = r#" × abc def
|
||||
let expected = r#"
|
||||
× abc def
|
||||
│ ghi jkl
|
||||
│ mno pqr
|
||||
│ stu vwx
|
||||
|
|
@ -237,7 +245,7 @@ fn wrap_option() -> Result<(), MietteError> {
|
|||
│ pqr stu
|
||||
│ vwx yz
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Unless, wrapping is disabled
|
||||
|
|
@ -254,16 +262,154 @@ fn wrap_option() -> Result<(), MietteError> {
|
|||
Report::msg("abc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz"),
|
||||
|handler| handler.with_width(15).with_wrap_lines(false),
|
||||
);
|
||||
let expected = r#" × abc def ghi jkl mno pqr stu vwx yz
|
||||
let expected = r#"
|
||||
× abc def ghi jkl mno pqr stu vwx yz
|
||||
│ abc def ghi jkl mno pqr stu vwx yz
|
||||
│ abc def ghi jkl mno pqr stu vwx yz
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapping_nested_errors() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("This is the parent error, the error withhhhh the children, kiddos, pups, as it were, and so on...")]
|
||||
#[diagnostic(
|
||||
code(mama::error),
|
||||
help(
|
||||
"try doing it better next time? I mean, you could have also done better thisssss time, but no?"
|
||||
)
|
||||
)]
|
||||
struct MamaError {
|
||||
#[diagnostic_source]
|
||||
baby: BabyError,
|
||||
}
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
|
||||
#[diagnostic(
|
||||
code(baby::error),
|
||||
help(
|
||||
"it cannot be helped... woulddddddd you really want to get rid of an error that's so cute?"
|
||||
)
|
||||
)]
|
||||
struct BabyError;
|
||||
|
||||
let err = MamaError { baby: BabyError };
|
||||
let out = fmt_report_with_settings(err.into(), |handler| handler.with_width(50));
|
||||
let expected = r#"mama::error
|
||||
|
||||
× This is the parent error, the error withhhhh
|
||||
│ the children, kiddos, pups, as it were, and
|
||||
│ so on...
|
||||
╰─▶ baby::error
|
||||
|
||||
× Wah wah: I may be small, but I'll
|
||||
│ cause a proper bout of trouble — justt
|
||||
│ try wrapping this mess of a line,
|
||||
│ buddo!
|
||||
help: it cannot be helped... woulddddddd
|
||||
you really want to get rid of an
|
||||
error that's so cute?
|
||||
|
||||
help: try doing it better next time? I mean,
|
||||
you could have also done better thisssss
|
||||
time, but no?
|
||||
"#;
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapping_related_errors() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("This is the parent error, the error withhhhh the children, kiddos, pups, as it were, and so on...")]
|
||||
#[diagnostic(
|
||||
code(mama::error),
|
||||
help(
|
||||
"try doing it better next time? I mean, you could have also done better thisssss time, but no?"
|
||||
)
|
||||
)]
|
||||
struct MamaError {
|
||||
#[diagnostic_source]
|
||||
baby: BrotherError,
|
||||
}
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Welcome to the brother-error brotherhood — where all of the wee baby errors join into a formidable force")]
|
||||
#[diagnostic(code(brother::error))]
|
||||
struct BrotherError {
|
||||
#[related]
|
||||
brethren: Vec<Box<dyn Diagnostic + Send + Sync>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
|
||||
#[diagnostic(help(
|
||||
"it cannot be helped... woulddddddd you really want to get rid of an error that's so cute?"
|
||||
))]
|
||||
struct BabyError;
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
|
||||
#[diagnostic(severity(Warning))]
|
||||
struct BabyWarning;
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
|
||||
#[diagnostic(severity(Advice))]
|
||||
struct BabyAdvice;
|
||||
|
||||
let err = MamaError {
|
||||
baby: BrotherError {
|
||||
brethren: vec![BabyError.into(), BabyWarning.into(), BabyAdvice.into()],
|
||||
},
|
||||
};
|
||||
let out = fmt_report_with_settings(err.into(), |handler| handler.with_width(50));
|
||||
let expected = r#"mama::error
|
||||
|
||||
× This is the parent error, the error withhhhh
|
||||
│ the children, kiddos, pups, as it were, and
|
||||
│ so on...
|
||||
╰─▶ brother::error
|
||||
|
||||
× Welcome to the brother-error
|
||||
│ brotherhood — where all of the wee
|
||||
│ baby errors join into a formidable
|
||||
│ force
|
||||
|
||||
Error:
|
||||
× Wah wah: I may be small, but I'll
|
||||
│ cause a proper bout of trouble — justt
|
||||
│ try wrapping this mess of a line,
|
||||
│ buddo!
|
||||
help: it cannot be helped... woulddddddd
|
||||
you really want to get rid of an
|
||||
error that's so cute?
|
||||
|
||||
Warning:
|
||||
⚠ Wah wah: I may be small, but I'll
|
||||
│ cause a proper bout of trouble — justt
|
||||
│ try wrapping this mess of a line,
|
||||
│ buddo!
|
||||
|
||||
Advice:
|
||||
☞ Wah wah: I may be small, but I'll
|
||||
│ cause a proper bout of trouble — justt
|
||||
│ try wrapping this mess of a line,
|
||||
│ buddo!
|
||||
|
||||
help: try doing it better next time? I mean,
|
||||
you could have also done better thisssss
|
||||
time, but no?
|
||||
"#;
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_source() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
|
@ -326,7 +472,8 @@ if true {
|
|||
let out = fmt_report(err.into());
|
||||
println!("Error: {}", out);
|
||||
|
||||
let expected = r#" × oops!
|
||||
let expected = r#"
|
||||
× oops!
|
||||
╭─[issue:1:1]
|
||||
1 │ ╭─▶ if true {
|
||||
2 │ │ a
|
||||
|
|
@ -338,8 +485,7 @@ if true {
|
|||
· ╰──── big
|
||||
╰────
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
|
|
@ -361,7 +507,8 @@ fn single_line_highlight_span_full_line() {
|
|||
let out = fmt_report(err.into());
|
||||
println!("Error: {}", out);
|
||||
|
||||
let expected = r#" × oops!
|
||||
let expected = r#"
|
||||
× oops!
|
||||
╭─[issue:2:1]
|
||||
1 │ source
|
||||
2 │ text
|
||||
|
|
@ -369,8 +516,7 @@ fn single_line_highlight_span_full_line() {
|
|||
· ╰── This bit here
|
||||
╰────
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
|
|
@ -1411,7 +1557,6 @@ Error: oops::my::bad
|
|||
2 │ text
|
||||
╰────
|
||||
help: try doing it better next time?
|
||||
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
|
@ -1578,6 +1723,7 @@ Error: oops::my::related::error
|
|||
2 │ text
|
||||
╰────
|
||||
help: try doing it better next time?
|
||||
|
||||
Warning: oops::my::related::warning
|
||||
|
||||
⚠ oops!
|
||||
|
|
@ -1588,6 +1734,7 @@ Warning: oops::my::related::warning
|
|||
2 │ text
|
||||
╰────
|
||||
help: try doing it better next time?
|
||||
|
||||
Advice: oops::my::related::advice
|
||||
|
||||
☞ oops!
|
||||
|
|
@ -1623,7 +1770,8 @@ fn zero_length_eol_span() {
|
|||
let out = fmt_report(err.into());
|
||||
println!("Error: {}", out);
|
||||
|
||||
let expected = r#" × oops!
|
||||
let expected = r#"
|
||||
× oops!
|
||||
╭─[issue:2:1]
|
||||
1 │ this is the first line
|
||||
2 │ this is the second line
|
||||
|
|
@ -1631,8 +1779,7 @@ fn zero_length_eol_span() {
|
|||
· ╰── This bit here
|
||||
╰────
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
|
|
@ -1657,7 +1804,8 @@ fn primary_label() {
|
|||
println!("Error: {}", out);
|
||||
|
||||
// line 2 should be the primary, not line 1
|
||||
let expected = r#" × oops!
|
||||
let expected = r#"
|
||||
× oops!
|
||||
╭─[issue:2:2]
|
||||
1 │ this is the first line
|
||||
· ────
|
||||
|
|
@ -1666,8 +1814,7 @@ fn primary_label() {
|
|||
· ╰── nope
|
||||
╰────
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
|
|
@ -1808,7 +1955,8 @@ fn syntax_highlighter() {
|
|||
GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
|
||||
.render_report(&mut out, &err)
|
||||
.unwrap();
|
||||
let expected = r#" × This is an error
|
||||
let expected = r#"
|
||||
× This is an error
|
||||
╭─[hello_world:2:5]
|
||||
1 │ fn main() {
|
||||
2 │ println!("Hello, World!");
|
||||
|
|
@ -1816,7 +1964,8 @@ fn syntax_highlighter() {
|
|||
· ╰── this is a label
|
||||
3 │ }
|
||||
╰────
|
||||
"#;
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
assert!(out.contains("\u{1b}[38;2;180;142;173m"));
|
||||
assert_eq!(expected, strip_ansi_escapes::strip_str(out))
|
||||
}
|
||||
|
|
@ -1848,11 +1997,11 @@ fn syntax_highlighter_on_real_file() {
|
|||
// SourceSpan constants for column and length
|
||||
const CO: usize = 28;
|
||||
const LEN: usize = 27;
|
||||
let file_src = std::fs::read_to_string(&filename).unwrap();
|
||||
let file_src = std::fs::read_to_string(filename).unwrap();
|
||||
let offset = miette::SourceOffset::from_location(&file_src, line, CO);
|
||||
let err = Test {
|
||||
src: NamedSource::new(&filename, file_src.clone()),
|
||||
src_span: SourceSpan::new(offset, LEN.into()),
|
||||
src: NamedSource::new(filename, file_src.clone()),
|
||||
src_span: SourceSpan::new(offset, LEN),
|
||||
};
|
||||
|
||||
let mut out = String::new();
|
||||
|
|
@ -1862,7 +2011,8 @@ fn syntax_highlighter_on_real_file() {
|
|||
.unwrap();
|
||||
|
||||
let expected = format!(
|
||||
r#" × This is an error
|
||||
r#"
|
||||
× This is an error
|
||||
╭─[{filename}:{l2}:{CO}]
|
||||
{l1} │
|
||||
{l2} │ let (filename, line) = (file!(), line!() as usize);
|
||||
|
|
@ -1875,6 +2025,7 @@ fn syntax_highlighter_on_real_file() {
|
|||
l2 = line,
|
||||
l3 = line + 1
|
||||
);
|
||||
let expected = expected.trim_start_matches('\n');
|
||||
assert!(out.contains("\u{1b}[38;2;180;142;173m"));
|
||||
assert_eq!(expected, strip_ansi_escapes::strip_str(out));
|
||||
}
|
||||
|
|
@ -2196,3 +2347,224 @@ 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<EFBFBD>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<EFBFBD>XYZ
|
||||
· ┬
|
||||
· ╰── valid data here
|
||||
╰────
|
||||
";
|
||||
|
||||
assert_eq!(expected, result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_include_primary_span_start() {
|
||||
#[derive(Debug, Clone, Diagnostic, Error)]
|
||||
#[error("decoding error")]
|
||||
#[diagnostic(code(decode_err))]
|
||||
struct E {
|
||||
#[label("valid data here")]
|
||||
src: SourceSpan,
|
||||
}
|
||||
|
||||
let invalid_source: &[u8] = b"malformed\nh\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_with_settings(
|
||||
Report::new(err.clone()).with_source_code(invalid_source),
|
||||
|handler| handler,
|
||||
);
|
||||
|
||||
let expected = "decode_err
|
||||
|
||||
× decoding error
|
||||
╭─[2:5]
|
||||
1 │ malformed
|
||||
2 │ h<EFBFBD>XYZ
|
||||
· ┬
|
||||
· ╰── valid data here
|
||||
╰────
|
||||
";
|
||||
|
||||
assert_eq!(expected, result);
|
||||
|
||||
let result = fmt_report_with_settings(
|
||||
Report::new(err).with_source_code(invalid_source),
|
||||
|handler| handler.without_primary_span_start(),
|
||||
);
|
||||
|
||||
let expected = "decode_err
|
||||
|
||||
× decoding error
|
||||
╭────
|
||||
1 │ malformed
|
||||
2 │ h<EFBFBD>XYZ
|
||||
· ┬
|
||||
· ╰── valid data here
|
||||
╰────
|
||||
";
|
||||
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use miette::{miette, Diagnostic, LabeledSpan, Report, SourceSpan};
|
||||
use std::error::Error as StdError;
|
||||
use std::io;
|
||||
use std::ops::Deref;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
|
@ -159,7 +160,7 @@ impl Diagnostic for CustomDiagnostic {
|
|||
|
||||
#[test]
|
||||
fn test_boxed_custom_diagnostic() {
|
||||
fn assert_report(report: &Report) {
|
||||
fn assert_report<T: ?Sized + Diagnostic>(report: &impl Deref<Target = T>) {
|
||||
assert_eq!(
|
||||
report.source().map(|source| source.to_string()),
|
||||
Some("oh no!".to_owned()),
|
||||
|
|
@ -215,10 +216,16 @@ fn test_boxed_custom_diagnostic() {
|
|||
let main_diagnostic = Box::new(main_diagnostic) as Box<dyn Diagnostic + Send + Sync + 'static>;
|
||||
let report = miette!(main_diagnostic);
|
||||
assert_report(&report);
|
||||
|
||||
// Now make sure that conversion to a trait-object is lossless!
|
||||
let report_ref: &dyn Diagnostic = report.as_ref();
|
||||
assert_report(&report_ref);
|
||||
|
||||
let report_box: Box<dyn Diagnostic> = report.into();
|
||||
assert_report(&report_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "I don't know why this isn't working but it needs fixing."]
|
||||
fn test_boxed_sources() {
|
||||
let error = MyError {
|
||||
source: io::Error::new(io::ErrorKind::Other, "oh no!"),
|
||||
|
|
|
|||
|
|
@ -104,7 +104,8 @@ fn test_diagnostic_source_pass_extra_info() {
|
|||
.render_report(&mut out, &diag)
|
||||
.unwrap();
|
||||
println!("Error: {}", out);
|
||||
let expected = r#" × TestError
|
||||
let expected = r#"
|
||||
× TestError
|
||||
╰─▶ × A complex error happened
|
||||
╭─[1:2]
|
||||
1 │ Hello
|
||||
|
|
@ -117,7 +118,8 @@ fn test_diagnostic_source_pass_extra_info() {
|
|||
|
||||
this is a footer
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +140,8 @@ fn test_diagnostic_source_is_output() {
|
|||
.unwrap();
|
||||
println!("{}", out);
|
||||
|
||||
let expected = r#" × TestError
|
||||
let expected = r#"
|
||||
× TestError
|
||||
╰─▶ × A complex error happened
|
||||
╭────
|
||||
1 │ right here
|
||||
|
|
@ -147,11 +150,13 @@ fn test_diagnostic_source_is_output() {
|
|||
╰────
|
||||
help: That's where the error is!
|
||||
|
||||
"#;
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("A nested error happened")]
|
||||
struct NestedError {
|
||||
|
|
@ -186,7 +191,8 @@ fn test_nested_diagnostic_source_is_output() {
|
|||
.unwrap();
|
||||
println!("{}", out);
|
||||
|
||||
let expected = r#" × A nested error happened
|
||||
let expected = r#"
|
||||
× A nested error happened
|
||||
├─▶ × TestError
|
||||
│
|
||||
╰─▶ × A complex error happened
|
||||
|
|
@ -204,11 +210,13 @@ fn test_nested_diagnostic_source_is_output() {
|
|||
╰────
|
||||
|
||||
Yooo, a footer
|
||||
"#;
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("A multi-error happened")]
|
||||
struct MultiError {
|
||||
|
|
@ -252,10 +260,12 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
|
|||
.unwrap();
|
||||
println!("{}", out);
|
||||
|
||||
let expected = r#" × A nested error happened
|
||||
let expected = r#"
|
||||
× A nested error happened
|
||||
╰─▶ × A multi-error happened
|
||||
|
||||
Error: × A nested error happened
|
||||
Error:
|
||||
× A nested error happened
|
||||
├─▶ × TestError
|
||||
│
|
||||
╰─▶ × A complex error happened
|
||||
|
|
@ -271,7 +281,9 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
|
|||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
Error: × A complex error happened
|
||||
|
||||
Error:
|
||||
× A complex error happened
|
||||
╭────
|
||||
1 │ You're actually a mess
|
||||
· ──┬─
|
||||
|
|
@ -286,7 +298,120 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
|
|||
╰────
|
||||
|
||||
Yooo, a footer
|
||||
"#;
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[test]
|
||||
fn test_display_related_errors_as_nested() {
|
||||
let inner_error = TestStructError {
|
||||
asdf_inner_foo: SourceError {
|
||||
code: String::from("This is another error"),
|
||||
help: String::from("You should fix this"),
|
||||
label: (3, 4),
|
||||
},
|
||||
};
|
||||
let first_error = NestedError {
|
||||
code: String::from("right here"),
|
||||
label: (6, 4),
|
||||
the_other_err: Box::new(inner_error),
|
||||
};
|
||||
let second_error = SourceError {
|
||||
code: String::from("You're actually a mess"),
|
||||
help: String::from("Get a grip..."),
|
||||
label: (3, 4),
|
||||
};
|
||||
let diag = MultiError {
|
||||
related_errs: vec![
|
||||
Box::new(MultiError {
|
||||
related_errs: vec![Box::new(first_error), Box::new(AnErr)],
|
||||
}),
|
||||
Box::new(second_error),
|
||||
],
|
||||
};
|
||||
|
||||
let mut out = String::new();
|
||||
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
||||
.with_width(80)
|
||||
.with_show_related_as_nested(true)
|
||||
.render_report(&mut out, &diag)
|
||||
.unwrap();
|
||||
println!("{}", out);
|
||||
|
||||
let expected = r#"
|
||||
× A multi-error happened
|
||||
├─▶ × A multi-error happened
|
||||
│ ├─▶ × A nested error happened
|
||||
│ │ ╭────
|
||||
│ │ 1 │ right here
|
||||
│ │ · ──┬─
|
||||
│ │ · ╰── here
|
||||
│ │ ╰────
|
||||
│ ╰─▶ × AnErr
|
||||
╰─▶ × A complex error happened
|
||||
╭────
|
||||
1 │ You're actually a mess
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
help: Get a grip...
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("A case1 error happened")]
|
||||
enum NestedEnumError {
|
||||
Case1 {
|
||||
#[source_code]
|
||||
code: String,
|
||||
#[diagnostic_source]
|
||||
the_other_err: Case1Inner,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("I am the inner error")]
|
||||
struct Case1Inner {
|
||||
#[label("inner-label")]
|
||||
label: (usize, usize),
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[test]
|
||||
fn source_is_inherited_to_causes() {
|
||||
let diag = NestedEnumError::Case1 {
|
||||
code: String::from("this is the parent source"),
|
||||
the_other_err: Case1Inner { label: (8, 3) },
|
||||
};
|
||||
let mut out = String::new();
|
||||
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
||||
.with_width(80)
|
||||
.with_footer("Yooo, a footer".to_string())
|
||||
.render_report(&mut out, &diag)
|
||||
.unwrap();
|
||||
println!("{}", out);
|
||||
|
||||
let expected = r#"
|
||||
× A case1 error happened
|
||||
╰─▶ × I am the inner error
|
||||
╭────
|
||||
1 │ this is the parent source
|
||||
· ─┬─
|
||||
· ╰── inner-label
|
||||
╰────
|
||||
|
||||
|
||||
Yooo, a footer
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ fn test_altdisplay() {
|
|||
|
||||
#[test]
|
||||
#[ignore = "not really gonna work with the current printers"]
|
||||
#[cfg_attr(track_caller, ignore)]
|
||||
fn test_debug() {
|
||||
assert_eq!(EXPECTED_DEBUG_F, format!("{:?}", f().unwrap_err()));
|
||||
assert_eq!(EXPECTED_DEBUG_G, format!("{:?}", g().unwrap_err()));
|
||||
|
|
|
|||
Loading…
Reference in New Issue