mirror of https://github.com/zkat/miette.git
Merge branch 'zkat:main' into main
This commit is contained in:
commit
c5385e1822
|
|
@ -10,14 +10,18 @@ jobs:
|
|||
name: Check fmt & build docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
components: rustfmt
|
||||
override: true
|
||||
- name: Install cargo-readme
|
||||
run: cargo install cargo-readme
|
||||
- name: Check doc consistency
|
||||
shell: bash
|
||||
run: diff -q README.md <(cargo readme)
|
||||
|| { echo "::error::Update lib.rs then use cargo-readme to update README.md"; exit 1; }
|
||||
- name: rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
- name: docs
|
||||
|
|
@ -28,40 +32,53 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
rust: [1.56.0, stable]
|
||||
features: [fancy, syntect-highlighter]
|
||||
rust: [1.70.0, stable]
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
exclude:
|
||||
- features: syntect-highlighter
|
||||
rust: 1.70.0
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
components: clippy
|
||||
override: true
|
||||
- name: Clippy
|
||||
run: cargo clippy --all -- -D warnings
|
||||
- name: Run tests
|
||||
if: matrix.rust == 'stable'
|
||||
run: cargo test --all --verbose --features fancy
|
||||
run: cargo test --all --verbose --features ${{matrix.features}}
|
||||
- name: Run tests
|
||||
if: matrix.rust == '1.56.0'
|
||||
run: cargo test --all --verbose --features fancy no-format-args-capture
|
||||
if: matrix.rust == '1.70.0'
|
||||
run: cargo test --all --verbose --features ${{matrix.features}} no-format-args-capture
|
||||
|
||||
wasm:
|
||||
name: Check Wasm build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: wasm32-unknown-unknown
|
||||
- name: Check wasm target
|
||||
run: cargo check --target wasm32-unknown-unknown --features fancy-no-syscall
|
||||
|
||||
miri:
|
||||
name: Miri
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
components: miri,rust-src
|
||||
override: true
|
||||
- name: Run tests with miri
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
|
||||
|
|
@ -75,13 +92,10 @@ jobs:
|
|||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
- name: Run minimal version build
|
||||
run: cargo build -Z minimal-versions --all-features
|
||||
|
||||
run: cargo build -Z direct-minimal-versions --features fancy,no-format-args-capture
|
||||
|
|
|
|||
104
CHANGELOG.md
104
CHANGELOG.md
|
|
@ -1,5 +1,109 @@
|
|||
# `miette` Release Changelog
|
||||
|
||||
<a name="7.2.0"></a>
|
||||
## 7.2.0 (2024-03-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **wasm:** add feature "fancy-no-syscall" for wasm targets (#349) ([328bf379](https://github.com/zkat/miette/commit/328bf3792213fc0bed94e72a39acb722b65141dd))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **label-collections:** Label collection fixes and cleanup (#343) ([75fea093](https://github.com/zkat/miette/commit/75fea0935e495d0215518c80d32dd820910982e3))
|
||||
* **invalid span:** skip the snippet when read_span fails (#347) ([7d9dfc6e](https://github.com/zkat/miette/commit/7d9dfc6e8e591f9606c3da55bd8465962358b20f))
|
||||
* **redundant-import:** fix a warning and CI failure in nightly (#348) ([6ea86a22](https://github.com/zkat/miette/commit/6ea86a2248854acf88df345814b6c97d31b8b4d9))
|
||||
|
||||
<a name="7.1.0"></a>
|
||||
## 7.1.0 (2024-02-16)
|
||||
|
||||
### Features
|
||||
|
||||
* **derive:** enable more boxed types to be #[diagnostic_source] (#338) ([c2f06f6c](https://github.com/zkat/miette/commit/c2f06f6cca15cbdd083dbff3d46b7729056ac6a4))
|
||||
* **source:** derive common traits for NamedSource, SourceSpan, and SourceOffset (#340) ([6f09250c](https://github.com/zkat/miette/commit/6f09250cca14561f07fba899a8e6d3c0df14230e))
|
||||
* **collection:** add support for collection of labels (#341) ([03060245](https://github.com/zkat/miette/commit/03060245d816a53a33209e6b7e1c3c42948e9962))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tests:** revert test-breaking changes of e5c7ae4 (#339) ([6e829f8c](https://github.com/zkat/miette/commit/6e829f8c0ce2fc7bb2fc4041e6a6072f12db1f71))
|
||||
|
||||
<a name="7.0.0"></a>
|
||||
## 7.0.0 (2024-02-05)
|
||||
|
||||
This is a small breaking release on the heels of 6.0 because I neglected to
|
||||
bump owo-colors. I figured it's a good time to do it, before 6.0 gets more
|
||||
widely used.
|
||||
|
||||
### Features
|
||||
|
||||
* **fancy:** Add option to change the link display text (#335) ([c7144ee5](https://github.com/zkat/miette/commit/c7144ee513bf8f06c5f7d89c45436802994a51fc))
|
||||
* **deps:** bump dependencies ([a4011d17](https://github.com/zkat/miette/commit/a4011d174c40acbba5b0176db7cb71ec5ca0cb49))
|
||||
* **BREAKING CHANGE**: This bumps owo-colors to 4.0, which is a breaking change because we expose its styles as part of the graphical renderer API
|
||||
|
||||
<a name="6.0.1"></a>
|
||||
## 6.0.1 (2024-02-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **graphical:** oops. Fix theme issue ([8b46679c](https://github.com/zkat/miette/commit/8b46679c3647e1455d91b4c68743c619fb3f3eb3))
|
||||
* **fmt:** remove nightly-only fmt flags ([1fa7f524](https://github.com/zkat/miette/commit/1fa7f5241fb91d2e5bad9b0e26bcc7cd5f9011f1))
|
||||
* **highlighter:** ugh, missed another spot ([ab7c066e](https://github.com/zkat/miette/commit/ab7c066e7675d8c7ecb956000d278fc31f3bc6a1))
|
||||
|
||||
<a name="6.0.0"></a>
|
||||
## 6.0.0 (2024-02-04)
|
||||
|
||||
The long-awaited 6.0 release of `miette` is here, with TONS of goodies, not
|
||||
least of which is syntax highlighting support!
|
||||
|
||||
It also comes with a few breaking changes so make sure to check below and
|
||||
update your code as needed!
|
||||
|
||||
### Features
|
||||
|
||||
* **labels:** Add support for primary label in specifying line/col information (#291) ([db0b7e40](https://github.com/zkat/miette/commit/db0b7e403a5ae52ae360991b6508490d8c579886))
|
||||
* **derive:** Allow optional sources in derive (#301) ([88d00e0e](https://github.com/zkat/miette/commit/88d00e0e20bf95e03b8f81dcd5adf38c917e190e))
|
||||
* **derive:** Make `miette-derive` be able to be turned off (#304) ([c7ba5b7e](https://github.com/zkat/miette/commit/c7ba5b7e52e05991cecd3ca925c710bbe49850b9))
|
||||
* **graphical:** Expose additional `textwrap` options (#321) ([fd77257c](https://github.com/zkat/miette/commit/fd77257cee0f5d03aa7dccb4ba8cbaa40c1a88c6))
|
||||
* **graphical:** support rendering labels that contain newlines (#318) ([865d67c8](https://github.com/zkat/miette/commit/865d67c8dda119ddd03ac43be22f4fa272a9f433))
|
||||
* **graphical:** Add `wrap_lines: bool` option allowing wrapping be disabled entirely (#328) ([b0744462](https://github.com/zkat/miette/commit/b0744462adbbfbb6d845f382db36be883c7f3c45))
|
||||
* **graphical:** render disjoint snippets separately for cleaner output (#324) ([19c22143](https://github.com/zkat/miette/commit/19c22143cb544616046784e35c5e78cc5b881289))
|
||||
* **deps:** Bump terminal-size to v0.3.0 (#308) ([c0a298e5](https://github.com/zkat/miette/commit/c0a298e5a8d699acf9fcd61b5d5fa4f6279a47ab))
|
||||
* **BREAKING CHANGE**: This requires an MSRV bump to 1.70.0.
|
||||
* **source-code:** Don't override provided source code (#300) ([0d5c2ce7](https://github.com/zkat/miette/commit/0d5c2ce7536b0ea205346595d8a00d00bfb6cbd2))
|
||||
* **BREAKING CHANGE**: Source code is no longer overridden if it was provided by the diagnostic's own `source_code()` impl.
|
||||
* **source:** use `usize` for length (#265) ([fad0e76a](https://github.com/zkat/miette/commit/fad0e76ad2e19d5cac13cf8324338aca0d623d93))
|
||||
* **BREAKING CHANGE**: This changes `SourceSpan`'s length type to `usize`.
|
||||
* **source:** Allow inner source type of a NamedSource to be borrowed (#254) ([1df3b1a5](https://github.com/zkat/miette/commit/1df3b1a537f2e54cd40ec45f5cd851337a22e95a))
|
||||
* **BREAKING CHANGE**: This makes the `NamedSource` type generic over its `Source` type, instead of boxing it.
|
||||
* **highlighting:** add syntax highlighting support with syntect crate (#313) ([e65d0a78](https://github.com/zkat/miette/commit/e65d0a78cc639653f061a45d8ce35b1a3551ade7))
|
||||
* **deps:** remove is-terminal dep in favor of `std::io::IsTerminal` ([e5c7ae46](https://github.com/zkat/miette/commit/e5c7ae469e40a8bc102e1fca3b8fd4b2ec137696))
|
||||
* **deps:** remove once_cell dep in favor of `std::sync::OnceLock` ([4c48584f](https://github.com/zkat/miette/commit/4c48584f304414c6924bede3308b455cfef60749))
|
||||
* **BREAKING CHANGE**: This requires an MSRV bump to 1.70.0.
|
||||
* **deps:** bump some semver-breaking deps to newer versions ([29d000f2](https://github.com/zkat/miette/commit/29d000f201b259a056867a2876384f97653a6e9e))
|
||||
* **MSRV:** Actually bump the MSRV to 1.70.0 ([ab59a7bc](https://github.com/zkat/miette/commit/ab59a7bc9bceace5761a862ee2ebff3e5943b12f))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **misc:** Improve ci and fix clippy (#290) ([cc81382a](https://github.com/zkat/miette/commit/cc81382a6070dd226a20e4a39518d88e957ac0e1))
|
||||
* **tests:** Fix `cargo test` with default features. (#294) ([1f448e47](https://github.com/zkat/miette/commit/1f448e47751d0f914134b0e9138fdb1a5a95d55c))
|
||||
* **clippy:** Add missing semicolons where nothing is returned. (#293) ([06b34823](https://github.com/zkat/miette/commit/06b348230aaf153b8b050322f05e5d185351d2d1))
|
||||
* **graphical:** Extend error text span to whole code points (#312) ([a8b4ae01](https://github.com/zkat/miette/commit/a8b4ae012aa0cf03b53a18f013c2b3f76c5040e7))
|
||||
* **formatting:** Fix formatting bug when an empty span is not aligned to a char boundary (#314) ([3d6f903d](https://github.com/zkat/miette/commit/3d6f903df0e7c9d0eb9a1fdbbf0028bab5496429))
|
||||
* **docs:** add example to README and docs fixing #96 (#319) ([251d6d59](https://github.com/zkat/miette/commit/251d6d59292397458328ef57fb7957faedafd019))
|
||||
* **graphical:** rendering bug on small spans in large spans (#316) ([7ff4f874](https://github.com/zkat/miette/commit/7ff4f874d693a665af4df40f4e94505013e3e262))
|
||||
* **graphical:** render cause chains for inner errors (#330) ([cb2ae2e1](https://github.com/zkat/miette/commit/cb2ae2e18b446a5e90885faf8a30b5672c307df8))
|
||||
* **handler:** remove the two extra `is_terminal` sys call from `MietteHandlerOpts::build` (#325) ([f1dc89c0](https://github.com/zkat/miette/commit/f1dc89c07640445d224b61ef96c6b25fcdf62dee))
|
||||
|
||||
### Documentation
|
||||
|
||||
* **README:** Move import of `NamedResult` to where it is used (#309) ([d37ada87](https://github.com/zkat/miette/commit/d37ada876a5831d3f47622274e334c9a24aa5d2b))
|
||||
|
||||
<a name="5.10.0"></a>
|
||||
## 5.10.0 (2023-07-16)
|
||||
|
||||
### Features
|
||||
|
||||
* **protocol:** add StdError impl for Box<dyn Diagnostic + Send + Sync> (#273) ([2e3e5c9d](https://github.com/zkat/miette/commit/2e3e5c9d15e234495369e9b47d032644dd5664ad))
|
||||
|
||||
<a name="5.9.0"></a>
|
||||
## 5.9.0 (2023-05-18)
|
||||
|
||||
|
|
|
|||
56
Cargo.toml
56
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "miette"
|
||||
version = "5.9.0"
|
||||
version = "7.2.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,53 +9,61 @@ documentation = "https://docs.rs/miette"
|
|||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
rust-version = "1.56.0"
|
||||
rust-version = "1.70.0"
|
||||
exclude = ["images/", "tests/", "miette-derive/"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.40"
|
||||
miette-derive = { path = "miette-derive", version = "=5.9.0" }
|
||||
once_cell = "1.8.0"
|
||||
unicode-width = "0.1.9"
|
||||
thiserror = "1.0.56"
|
||||
miette-derive = { path = "miette-derive", version = "=7.2.0", optional = true }
|
||||
unicode-width = "0.1.11"
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
owo-colors = { version = "3.0.0", optional = true }
|
||||
is-terminal = { version = "0.4.0", optional = true }
|
||||
textwrap = { version = "0.15.0", optional = true }
|
||||
supports-hyperlinks = { version = "2.0.0", optional = true }
|
||||
supports-color = { version = "2.0.0", optional = true }
|
||||
supports-unicode = { version = "2.0.0", optional = true }
|
||||
backtrace = { version = "0.3.61", optional = true }
|
||||
terminal_size = { version = "0.1.17", optional = true }
|
||||
owo-colors = { version = "4.0.0", optional = true }
|
||||
textwrap = { version = "0.16.0", 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 }
|
||||
backtrace-ext = { version = "0.2.1", optional = true }
|
||||
serde = { version = "1.0.162", features = ["derive"], optional = true }
|
||||
serde = { version = "1.0.196", features = ["derive"], optional = true }
|
||||
syntect = { version = "5.1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
semver = "1.0.4"
|
||||
semver = "1.0.21"
|
||||
|
||||
# Eyre devdeps
|
||||
futures = { version = "0.3", default-features = false }
|
||||
indenter = "0.3.0"
|
||||
indenter = "0.3.3"
|
||||
rustversion = "1.0"
|
||||
trybuild = { version = "1.0.19", features = ["diff"] }
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
regex = "1.5"
|
||||
trybuild = { version = "1.0.89", features = ["diff"] }
|
||||
syn = { version = "2.0.48", features = ["full"] }
|
||||
regex = "1.10"
|
||||
lazy_static = "1.4"
|
||||
|
||||
serde_json = "1.0.64"
|
||||
serde_json = "1.0.113"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["derive"]
|
||||
derive = ["miette-derive"]
|
||||
no-format-args-capture = []
|
||||
fancy-no-backtrace = [
|
||||
fancy-base = [
|
||||
"owo-colors",
|
||||
"is-terminal",
|
||||
"textwrap",
|
||||
]
|
||||
fancy-no-syscall = [
|
||||
"fancy-base",
|
||||
]
|
||||
fancy-no-backtrace = [
|
||||
"fancy-base",
|
||||
"terminal_size",
|
||||
"supports-hyperlinks",
|
||||
"supports-color",
|
||||
"supports-unicode",
|
||||
]
|
||||
fancy = ["fancy-no-backtrace", "backtrace", "backtrace-ext"]
|
||||
syntect-highlighter = ["fancy-no-backtrace", "syntect"]
|
||||
|
||||
[workspace]
|
||||
members = ["miette-derive"]
|
||||
|
|
|
|||
219
README.md
219
README.md
|
|
@ -4,7 +4,7 @@
|
|||
You run miette? You run her code like the software? Oh. Oh! Error code for
|
||||
coder! Error code for One Thousand Lines!
|
||||
|
||||
## About
|
||||
### About
|
||||
|
||||
`miette` is a diagnostic library for Rust. It includes a series of
|
||||
traits/protocols that allow you to hook into its error reporting facilities,
|
||||
|
|
@ -13,18 +13,20 @@ can print out like this (or in any format you like!):
|
|||
|
||||
<img src="https://raw.githubusercontent.com/zkat/miette/main/images/serde_json.png" alt="Hi! miette also includes a screen-reader-oriented diagnostic printer that's enabled in various situations, such as when you use NO_COLOR or CLICOLOR settings, or on CI. This behavior is also fully configurable and customizable. For example, this is what this particular diagnostic will look like when the narrated printer is enabled:
|
||||
\
|
||||
Error: Received some bad JSON from the source. Unable to parse.
|
||||
Caused by: missing field `foo` at line 1 column 1700
|
||||
diagnostic error code: oops::my::bad (link)
|
||||
Error: oops!
|
||||
\
|
||||
Begin snippet for https://api.nuget.org/v3/registration5-gz-semver2/json.net/index.json starting
|
||||
at line 1, column 1659
|
||||
Begin snippet for bad_file.rs starting
|
||||
at line 2, column 3
|
||||
\
|
||||
snippet line 1: gs":["json"],"title":"","version":"1.0.0"},"packageContent":"https://api.nuget.o
|
||||
highlight starting at line 1, column 1699: last parsing location
|
||||
snippet line 1: source
|
||||
\
|
||||
diagnostic help: This is a bug. It might be in ruget, or it might be in the
|
||||
source you're using, but it's definitely a bug and should be reported.
|
||||
diagnostic error code: ruget::api::bad_json
|
||||
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?
|
||||
" />
|
||||
|
||||
> **NOTE: You must enable the `"fancy"` crate feature to get fancy report
|
||||
|
|
@ -32,7 +34,7 @@ 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 -->
|
||||
### Table of Contents <!-- omit in toc -->
|
||||
|
||||
- [About](#about)
|
||||
- [Features](#features)
|
||||
|
|
@ -44,14 +46,18 @@ libraries and such might not want.
|
|||
- [... in `main()`](#-in-main)
|
||||
- [... diagnostic code URLs](#-diagnostic-code-urls)
|
||||
- [... snippets](#-snippets)
|
||||
- [... help text](#-help-text)
|
||||
- [... severity level](#-severity-level)
|
||||
- [... multiple related errors](#-multiple-related-errors)
|
||||
- [... delayed source code](#-delayed-source-code)
|
||||
- [... handler options](#-handler-options)
|
||||
- [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||
- [... syntax highlighting](#-syntax-highlighting)
|
||||
- [... collection of labels](#-collection-of-labels)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
- [License](#license)
|
||||
|
||||
## Features
|
||||
### Features
|
||||
|
||||
- Generic [`Diagnostic`] protocol, compatible (and dependent on)
|
||||
[`std::error::Error`].
|
||||
|
|
@ -76,7 +82,7 @@ the following features:
|
|||
- Cause chain printing
|
||||
- Turns diagnostic codes into links in [supported terminals](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda).
|
||||
|
||||
## Installing
|
||||
### Installing
|
||||
|
||||
```sh
|
||||
$ cargo add miette
|
||||
|
|
@ -88,7 +94,7 @@ If you want to use the fancy printer in all these screenshots:
|
|||
$ cargo add miette --features fancy
|
||||
```
|
||||
|
||||
## Example
|
||||
### Example
|
||||
|
||||
```rust
|
||||
/*
|
||||
|
|
@ -110,7 +116,7 @@ struct MyBad {
|
|||
// The Source that we're gonna be printing snippets out of.
|
||||
// This can be a String if you don't have or care about file names.
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
// Snippets and highlights can be included in the diagnostic!
|
||||
#[label("This bit here")]
|
||||
bad_bit: SourceSpan,
|
||||
|
|
@ -174,9 +180,9 @@ snippet line 3: here
|
|||
diagnostic help: try doing it better next time?
|
||||
">
|
||||
|
||||
## Using
|
||||
### Using
|
||||
|
||||
### ... in libraries
|
||||
#### ... in libraries
|
||||
|
||||
`miette` is _fully compatible_ with library usage. Consumers who don't know
|
||||
about, or don't want, `miette` features can safely use its error types as
|
||||
|
|
@ -191,7 +197,7 @@ the trait directly, just like with `std::error::Error`.
|
|||
|
||||
```rust
|
||||
// lib/error.rs
|
||||
use miette::Diagnostic;
|
||||
use miette::{Diagnostic, SourceSpan};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Diagnostic, Debug)]
|
||||
|
|
@ -203,6 +209,18 @@ pub enum MyLibError {
|
|||
#[error("Oops it blew up")]
|
||||
#[diagnostic(code(my_lib::bad_code))]
|
||||
BadThingHappened,
|
||||
|
||||
#[error(transparent)]
|
||||
// Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
||||
#[diagnostic(transparent)]
|
||||
AnotherError(#[from] AnotherError),
|
||||
}
|
||||
|
||||
#[derive(Error, Diagnostic, Debug)]
|
||||
#[error("another error")]
|
||||
pub struct AnotherError {
|
||||
#[label("here")]
|
||||
pub at: SourceSpan
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -210,7 +228,7 @@ Then, return this error type from all your fallible public APIs. It's a best
|
|||
practice to wrap any "external" error types in your error `enum` instead of
|
||||
using something like [`Report`] in a library.
|
||||
|
||||
### ... in application code
|
||||
#### ... in application code
|
||||
|
||||
Application code tends to work a little differently than libraries. You
|
||||
don't always need or care to define dedicated error wrappers for errors
|
||||
|
|
@ -252,8 +270,7 @@ pub fn some_tool() -> Result<Version> {
|
|||
}
|
||||
```
|
||||
|
||||
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};
|
||||
|
|
@ -266,8 +283,9 @@ pub fn some_tool() -> Result<Version> {
|
|||
.map_err(|_| miette!("Invalid version {}", version))?)
|
||||
}
|
||||
```
|
||||
There are also similar [bail!] and [ensure!] macros.
|
||||
|
||||
### ... in `main()`
|
||||
#### ... in `main()`
|
||||
|
||||
`main()` is just like any other part of your application-internal code. Use
|
||||
`Result` as your return value, and it will pretty-print your diagnostics
|
||||
|
|
@ -297,7 +315,24 @@ enabled:
|
|||
miette = { version = "X.Y.Z", features = ["fancy"] }
|
||||
```
|
||||
|
||||
### ... diagnostic code URLs
|
||||
Another way to display a diagnostic is by printing them using the debug formatter.
|
||||
This is, in fact, what returning diagnostics from main ends up doing.
|
||||
To do it yourself, you can write the following:
|
||||
|
||||
```rust
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use semver::Version;
|
||||
|
||||
fn just_a_random_function() {
|
||||
let version_result: Result<Version> = "1.2.x".parse().into_diagnostic();
|
||||
match version_result {
|
||||
Err(e) => println!("{:?}", e),
|
||||
Ok(version) => println!("{}", version),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### ... diagnostic code URLs
|
||||
|
||||
`miette` supports providing a URL for individual diagnostics. This URL will
|
||||
be displayed as an actual link in supported terminals, like so:
|
||||
|
|
@ -350,7 +385,7 @@ use thiserror::Error;
|
|||
struct MyErr;
|
||||
```
|
||||
|
||||
### ... snippets
|
||||
#### ... snippets
|
||||
|
||||
Along with its general error handling and reporting features, `miette` also
|
||||
includes facilities for adding error spans/annotations/labels to your
|
||||
|
|
@ -434,7 +469,20 @@ let err = Foo {
|
|||
};
|
||||
```
|
||||
|
||||
### ... multiple related errors
|
||||
#### ... severity level
|
||||
`miette` provides a way to set the severity level of a diagnostic.
|
||||
|
||||
```rust
|
||||
use miette::Diagnostic;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic(severity(Warning))]
|
||||
struct Foo;
|
||||
```
|
||||
|
||||
#### ... multiple related errors
|
||||
|
||||
`miette` supports collecting multiple errors into a single diagnostic, and
|
||||
printing them all together nicely.
|
||||
|
|
@ -454,7 +502,7 @@ struct MyError {
|
|||
}
|
||||
```
|
||||
|
||||
### ... delayed source code
|
||||
#### ... delayed source code
|
||||
|
||||
Sometimes it makes sense to add source code to the error message later.
|
||||
One option is to use [`with_source_code()`](Report::with_source_code)
|
||||
|
|
@ -537,7 +585,7 @@ fn main() -> miette::Result<()> {
|
|||
}
|
||||
```
|
||||
|
||||
### ... Diagnostic-based error sources.
|
||||
#### ... Diagnostic-based error sources.
|
||||
|
||||
When one uses the `#[source]` attribute on a field, that usually comes
|
||||
from `thiserror`, and implements a method for
|
||||
|
|
@ -570,7 +618,7 @@ struct MyError {
|
|||
struct OtherError;
|
||||
```
|
||||
|
||||
### ... handler options
|
||||
#### ... handler options
|
||||
|
||||
[`MietteHandler`] is the default handler, and is very customizable. In
|
||||
most cases, you can simply use [`MietteHandlerOpts`] to tweak its behavior
|
||||
|
|
@ -586,15 +634,17 @@ miette::set_hook(Box::new(|_| {
|
|||
.unicode(false)
|
||||
.context_lines(3)
|
||||
.tab_width(4)
|
||||
.break_words(true)
|
||||
.build(),
|
||||
)
|
||||
}))
|
||||
|
||||
```
|
||||
|
||||
See the docs for [`MietteHandlerOpts`] for more details on what you can
|
||||
customize!
|
||||
|
||||
### ... dynamic diagnostics
|
||||
#### ... dynamic diagnostics
|
||||
|
||||
If you...
|
||||
- ...don't know all the possible errors upfront
|
||||
|
|
@ -603,9 +653,10 @@ then you may want to use [`miette!`], [`diagnostic!`] macros or
|
|||
[`MietteDiagnostic`] directly to create diagnostic on the fly.
|
||||
|
||||
```rust
|
||||
|
||||
let source = "2 + 2 * 2 = 8".to_string();
|
||||
let report = miette!(
|
||||
labels = vec[
|
||||
labels = vec![
|
||||
LabeledSpan::at(12..13, "this should be 6"),
|
||||
],
|
||||
help = "'*' has greater precedence than '+'",
|
||||
|
|
@ -614,26 +665,108 @@ let report = miette!(
|
|||
println!("{:?}", report)
|
||||
```
|
||||
|
||||
## Acknowledgements
|
||||
#### ... syntax highlighting
|
||||
|
||||
`miette` can be configured to highlight syntax in source code snippets.
|
||||
|
||||
<!-- TODO: screenshot goes here once default Theme is decided -->
|
||||
|
||||
To use the built-in highlighting functionality, you must enable the
|
||||
`syntect-highlighter` crate feature. When this feature is enabled, `miette` will
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
If you want to use a custom highlighter, you can provide a custom
|
||||
implementation of the [`Highlighter`](highlighters::Highlighter)
|
||||
trait to [`MietteHandlerOpts`] by calling the
|
||||
[`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
||||
method. See the [`highlighters`] module docs for more details.
|
||||
|
||||
#### ... collection of labels
|
||||
|
||||
When the number of labels is unknown, you can use a collection of `SourceSpan`
|
||||
(or any type convertible into `SourceSpan`). For this, add the `collection`
|
||||
parameter to `label` and use any type than can be iterated over for the field.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyError {
|
||||
#[label("main issue")]
|
||||
primary_span: SourceSpan,
|
||||
|
||||
#[label(collection, "related to this")]
|
||||
other_spans: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
let report: miette::Report = MyError {
|
||||
primary_span: (6, 9).into(),
|
||||
other_spans: vec![19..26, 30..41],
|
||||
}.into();
|
||||
|
||||
println!("{:?}", report.with_source_code("About something or another or yet another ...".to_string()));
|
||||
```
|
||||
|
||||
A collection can also be of `LabeledSpan` if you want to have different text
|
||||
for different labels. Labels with no text will use the one from the `label`
|
||||
attribute
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyError {
|
||||
#[label("main issue")]
|
||||
primary_span: SourceSpan,
|
||||
|
||||
#[label(collection, "related to this")]
|
||||
other_spans: Vec<LabeledSpan>, // LabeledSpan
|
||||
}
|
||||
|
||||
let report: miette::Report = MyError {
|
||||
primary_span: (6, 9).into(),
|
||||
other_spans: vec![
|
||||
LabeledSpan::new(None, 19, 7), // Use default text `related to this`
|
||||
LabeledSpan::new(Some("and also this".to_string()), 30, 11), // Use specific text
|
||||
],
|
||||
}.into();
|
||||
|
||||
println!("{:?}", report.with_source_code("About something or another or yet another ...".to_string()));
|
||||
```
|
||||
|
||||
### MSRV
|
||||
|
||||
This crate requires rustc 1.70.0 or later.
|
||||
|
||||
### Acknowledgements
|
||||
|
||||
`miette` was not developed in a void. It owes enormous credit to various
|
||||
other projects and their authors:
|
||||
|
||||
- [`anyhow`](http://crates.io/crates/anyhow) and
|
||||
[`color-eyre`](https://crates.io/crates/color-eyre): these two
|
||||
enormously influential error handling libraries have pushed forward the
|
||||
experience of application-level error handling and error reporting.
|
||||
`miette`'s `Report` type is an attempt at a very very rough version of
|
||||
their `Report` types.
|
||||
- [`thiserror`](https://crates.io/crates/thiserror) for setting the
|
||||
standard for library-level error definitions, and for being the
|
||||
inspiration behind `miette`'s derive macro.
|
||||
- [`anyhow`](http://crates.io/crates/anyhow) and [`color-eyre`](https://crates.io/crates/color-eyre):
|
||||
these two enormously influential error handling libraries have pushed
|
||||
forward the experience of application-level error handling and error
|
||||
reporting. `miette`'s `Report` type is an attempt at a very very rough
|
||||
version of their `Report` types.
|
||||
- [`thiserror`](https://crates.io/crates/thiserror) for setting the standard
|
||||
for library-level error definitions, and for being the inspiration behind
|
||||
`miette`'s derive macro.
|
||||
- `rustc` and [@estebank](https://github.com/estebank) for their
|
||||
state-of-the-art work in compiler diagnostics.
|
||||
- [`ariadne`](https://crates.io/crates/ariadne) for pushing forward how
|
||||
_pretty_ these diagnostics can really look!
|
||||
|
||||
## License
|
||||
### License
|
||||
|
||||
`miette` is released to the Rust community under the [Apache license
|
||||
2.0](./LICENSE).
|
||||
|
|
@ -652,7 +785,7 @@ under the Apache License. Some code is taken from
|
|||
[`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
|
||||
[`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
|
||||
[`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
|
||||
[`ReportHandler`]: https://docs.rs/miette/latest/miette/struct.ReportHandler.html
|
||||
[`ReportHandler`]: https://docs.rs/miette/latest/miette/trait.ReportHandler.html
|
||||
[`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
|
||||
[`SourceCode`]: https://docs.rs/miette/latest/miette/struct.SourceCode.html
|
||||
[`SourceCode`]: https://docs.rs/miette/latest/miette/trait.SourceCode.html
|
||||
[`SourceSpan`]: https://docs.rs/miette/latest/miette/struct.SourceSpan.html
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
[`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
|
||||
[`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
|
||||
[`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
|
||||
[`ReportHandler`]: https://docs.rs/miette/latest/miette/struct.ReportHandler.html
|
||||
[`ReportHandler`]: https://docs.rs/miette/latest/miette/trait.ReportHandler.html
|
||||
[`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
|
||||
[`SourceCode`]: https://docs.rs/miette/latest/miette/struct.SourceCode.html
|
||||
[`SourceCode`]: https://docs.rs/miette/latest/miette/trait.SourceCode.html
|
||||
[`SourceSpan`]: https://docs.rs/miette/latest/miette/struct.SourceSpan.html
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
msrv = "1.56.0"
|
||||
msrv = "1.70.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "miette-derive"
|
||||
version = "5.9.0"
|
||||
version = "7.2.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"
|
||||
quote = "1.0"
|
||||
syn = "2.0.11"
|
||||
proc-macro2 = "1.0.78"
|
||||
quote = "1.0.35"
|
||||
syn = "2.0.48"
|
||||
|
|
|
|||
|
|
@ -16,14 +16,23 @@ use crate::{
|
|||
|
||||
pub struct Labels(Vec<Label>);
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum LabelType {
|
||||
Default,
|
||||
Primary,
|
||||
Collection,
|
||||
}
|
||||
|
||||
struct Label {
|
||||
label: Option<Display>,
|
||||
ty: syn::Type,
|
||||
span: syn::Member,
|
||||
lbl_ty: LabelType,
|
||||
}
|
||||
|
||||
struct LabelAttr {
|
||||
label: Option<Display>,
|
||||
lbl_ty: LabelType,
|
||||
}
|
||||
|
||||
impl Parse for LabelAttr {
|
||||
|
|
@ -40,10 +49,26 @@ impl Parse for LabelAttr {
|
|||
}
|
||||
});
|
||||
let la = input.lookahead1();
|
||||
let label = if la.peek(syn::token::Paren) {
|
||||
// #[label("{}", x)]
|
||||
let (lbl_ty, label) = if la.peek(syn::token::Paren) {
|
||||
// #[label(primary?, "{}", x)]
|
||||
let content;
|
||||
parenthesized!(content in input);
|
||||
|
||||
let attr = match content.parse::<Option<syn::Ident>>()? {
|
||||
Some(ident) if ident == "primary" => {
|
||||
let _ = content.parse::<Token![,]>();
|
||||
LabelType::Primary
|
||||
}
|
||||
Some(ident) if ident == "collection" => {
|
||||
let _ = content.parse::<Token![,]>();
|
||||
LabelType::Collection
|
||||
}
|
||||
Some(_) => {
|
||||
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or either the keyword `primary` or `collection`."));
|
||||
}
|
||||
_ => LabelType::Default,
|
||||
};
|
||||
|
||||
if content.peek(syn::LitStr) {
|
||||
let fmt = content.parse()?;
|
||||
let args = if content.is_empty() {
|
||||
|
|
@ -56,22 +81,27 @@ impl Parse for LabelAttr {
|
|||
args,
|
||||
has_bonus_display: false,
|
||||
};
|
||||
Some(display)
|
||||
(attr, Some(display))
|
||||
} else if !content.is_empty() {
|
||||
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or either the keyword `primary` or `collection`."));
|
||||
} else {
|
||||
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The first argument must be a literal string."));
|
||||
(attr, None)
|
||||
}
|
||||
} else if la.peek(Token![=]) {
|
||||
// #[label = "blabla"]
|
||||
input.parse::<Token![=]>()?;
|
||||
Some(Display {
|
||||
fmt: input.parse()?,
|
||||
args: TokenStream::new(),
|
||||
has_bonus_display: false,
|
||||
})
|
||||
(
|
||||
LabelType::Default,
|
||||
Some(Display {
|
||||
fmt: input.parse()?,
|
||||
args: TokenStream::new(),
|
||||
has_bonus_display: false,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
(LabelType::Default, None)
|
||||
};
|
||||
Ok(LabelAttr { label })
|
||||
Ok(LabelAttr { label, lbl_ty })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,12 +130,25 @@ impl Labels {
|
|||
})
|
||||
};
|
||||
use quote::ToTokens;
|
||||
let LabelAttr { label } =
|
||||
let LabelAttr { label, lbl_ty } =
|
||||
syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
|
||||
|
||||
if lbl_ty == LabelType::Primary
|
||||
&& labels
|
||||
.iter()
|
||||
.any(|l: &Label| l.lbl_ty == LabelType::Primary)
|
||||
{
|
||||
return Err(syn::Error::new(
|
||||
field.span(),
|
||||
"Cannot have more than one primary label.",
|
||||
));
|
||||
}
|
||||
|
||||
labels.push(Label {
|
||||
label,
|
||||
span,
|
||||
ty: field.ty.clone(),
|
||||
lbl_ty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -119,36 +162,81 @@ impl Labels {
|
|||
|
||||
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
||||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
let labels = self.0.iter().map(|highlight| {
|
||||
let Label { span, label, ty } = highlight;
|
||||
let var = quote! { __miette_internal_var };
|
||||
if let Some(display) = label {
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
quote! {
|
||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||
std::option::Option::Some(format!(#fmt #args)),
|
||||
#var.clone(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||
std::option::Option::None,
|
||||
#var.clone(),
|
||||
))
|
||||
}
|
||||
let labels = self.0.iter().filter_map(|highlight| {
|
||||
let Label {
|
||||
span,
|
||||
label,
|
||||
ty,
|
||||
lbl_ty,
|
||||
} = highlight;
|
||||
if *lbl_ty == LabelType::Collection {
|
||||
return None;
|
||||
}
|
||||
let var = quote! { __miette_internal_var };
|
||||
let display = if let Some(display) = label {
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
||||
} else {
|
||||
quote! { std::option::Option::None }
|
||||
};
|
||||
let ctor = if *lbl_ty == LabelType::Primary {
|
||||
quote! { miette::LabeledSpan::new_primary_with_span }
|
||||
} else {
|
||||
quote! { miette::LabeledSpan::new_with_span }
|
||||
};
|
||||
|
||||
Some(quote! {
|
||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||
.map(|#var| #ctor(
|
||||
#display,
|
||||
#var.clone(),
|
||||
))
|
||||
})
|
||||
});
|
||||
let collections_chain = self.0.iter().filter_map(|label| {
|
||||
let Label {
|
||||
span,
|
||||
label,
|
||||
ty: _,
|
||||
lbl_ty,
|
||||
} = label;
|
||||
if *lbl_ty != LabelType::Collection {
|
||||
return None;
|
||||
}
|
||||
let display = if let Some(display) = label {
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
||||
} else {
|
||||
quote! { std::option::Option::None }
|
||||
};
|
||||
Some(quote! {
|
||||
.chain({
|
||||
let display = #display;
|
||||
self.#span.iter().map(move |span| {
|
||||
use miette::macro_helpers::{ToLabelSpanWrapper,ToLabeledSpan};
|
||||
let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone());
|
||||
if display.is_some() && labeled_span.label().is_none() {
|
||||
labeled_span.set_label(display.clone())
|
||||
}
|
||||
Some(labeled_span)
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
Some(quote! {
|
||||
#[allow(unused_variables)]
|
||||
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||
use miette::macro_helpers::ToOption;
|
||||
let Self #display_pat = self;
|
||||
std::option::Option::Some(Box::new(vec![
|
||||
|
||||
let labels_iter = vec![
|
||||
#(#labels),*
|
||||
].into_iter().filter(Option::is_some).map(Option::unwrap)))
|
||||
]
|
||||
.into_iter()
|
||||
#(#collections_chain)*;
|
||||
|
||||
std::option::Option::Some(Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -160,8 +248,11 @@ impl Labels {
|
|||
|ident, fields, DiagnosticConcreteArgs { labels, .. }| {
|
||||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
labels.as_ref().and_then(|labels| {
|
||||
let variant_labels = labels.0.iter().map(|label| {
|
||||
let Label { span, label, ty } = label;
|
||||
let variant_labels = labels.0.iter().filter_map(|label| {
|
||||
let Label { span, label, ty, lbl_ty } = label;
|
||||
if *lbl_ty == LabelType::Collection {
|
||||
return None;
|
||||
}
|
||||
let field = match &span {
|
||||
syn::Member::Named(ident) => ident.clone(),
|
||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||
|
|
@ -169,24 +260,56 @@ impl Labels {
|
|||
}
|
||||
};
|
||||
let var = quote! { __miette_internal_var };
|
||||
if let Some(display) = label {
|
||||
let display = if let Some(display) = label {
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
quote! {
|
||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||
std::option::Option::Some(format!(#fmt #args)),
|
||||
#var.clone(),
|
||||
))
|
||||
}
|
||||
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
||||
} else {
|
||||
quote! {
|
||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||
std::option::Option::None,
|
||||
#var.clone(),
|
||||
))
|
||||
}
|
||||
quote! { std::option::Option::None }
|
||||
};
|
||||
let ctor = if *lbl_ty == LabelType::Primary {
|
||||
quote! { miette::LabeledSpan::new_primary_with_span }
|
||||
} else {
|
||||
quote! { miette::LabeledSpan::new_with_span }
|
||||
};
|
||||
|
||||
Some(quote! {
|
||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||
.map(|#var| #ctor(
|
||||
#display,
|
||||
#var.clone(),
|
||||
))
|
||||
})
|
||||
});
|
||||
let collections_chain = labels.0.iter().filter_map(|label| {
|
||||
let Label { span, label, ty: _, lbl_ty } = label;
|
||||
if *lbl_ty != LabelType::Collection {
|
||||
return None;
|
||||
}
|
||||
let field = match &span {
|
||||
syn::Member::Named(ident) => ident.clone(),
|
||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||
format_ident!("_{}", index)
|
||||
}
|
||||
};
|
||||
let display = if let Some(display) = label {
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
||||
} else {
|
||||
quote! { std::option::Option::None }
|
||||
};
|
||||
Some(quote! {
|
||||
.chain({
|
||||
let display = #display;
|
||||
#field.iter().map(move |span| {
|
||||
use miette::macro_helpers::{ToLabelSpanWrapper,ToLabeledSpan};
|
||||
let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone());
|
||||
if display.is_some() && labeled_span.label().is_none() {
|
||||
labeled_span.set_label(display.clone());
|
||||
}
|
||||
Some(labeled_span)
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
let variant_name = ident.clone();
|
||||
match &fields {
|
||||
|
|
@ -194,9 +317,12 @@ impl Labels {
|
|||
_ => Some(quote! {
|
||||
Self::#variant_name #display_pat => {
|
||||
use miette::macro_helpers::ToOption;
|
||||
std::option::Option::Some(std::boxed::Box::new(vec![
|
||||
let labels_iter = vec![
|
||||
#(#variant_labels),*
|
||||
].into_iter().filter(Option::is_some).map(Option::unwrap)))
|
||||
]
|
||||
.into_iter()
|
||||
#(#collections_chain)*;
|
||||
std::option::Option::Some(std::boxed::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap)))
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
|
||||
pub struct SourceCode {
|
||||
source_code: syn::Member,
|
||||
is_option: bool,
|
||||
}
|
||||
|
||||
impl SourceCode {
|
||||
|
|
@ -27,6 +28,19 @@ impl SourceCode {
|
|||
for (i, field) in fields.iter().enumerate() {
|
||||
for attr in &field.attrs {
|
||||
if attr.path().is_ident("source_code") {
|
||||
let is_option = if let syn::Type::Path(syn::TypePath {
|
||||
path: syn::Path { segments, .. },
|
||||
..
|
||||
}) = &field.ty
|
||||
{
|
||||
segments
|
||||
.last()
|
||||
.map(|seg| seg.ident == "Option")
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let source_code = if let Some(ident) = field.ident.clone() {
|
||||
syn::Member::Named(ident)
|
||||
} else {
|
||||
|
|
@ -35,7 +49,10 @@ impl SourceCode {
|
|||
span: field.span(),
|
||||
})
|
||||
};
|
||||
return Ok(Some(SourceCode { source_code }));
|
||||
return Ok(Some(SourceCode {
|
||||
source_code,
|
||||
is_option,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,11 +62,21 @@ impl SourceCode {
|
|||
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
||||
let (display_pat, _display_members) = display_pat_members(fields);
|
||||
let src = &self.source_code;
|
||||
let ret = if self.is_option {
|
||||
quote! {
|
||||
self.#src.as_ref().map(|s| s as _)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
Some(&self.#src)
|
||||
}
|
||||
};
|
||||
|
||||
Some(quote! {
|
||||
#[allow(unused_variables)]
|
||||
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> {
|
||||
let Self #display_pat = self;
|
||||
Some(&self.#src)
|
||||
#ret
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -68,10 +95,19 @@ impl SourceCode {
|
|||
}
|
||||
};
|
||||
let variant_name = ident.clone();
|
||||
let ret = if source_code.is_option {
|
||||
quote! {
|
||||
#field.as_ref().map(|s| s as _)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
std::option::Option::Some(#field)
|
||||
}
|
||||
};
|
||||
match &fields {
|
||||
syn::Fields::Unit => None,
|
||||
_ => Some(quote! {
|
||||
Self::#variant_name #display_pat => std::option::Option::Some(#field),
|
||||
Self::#variant_name #display_pat => #ret,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
edition = "2021"
|
||||
wrap_comments = true
|
||||
format_code_in_doc_comments = true
|
||||
|
|
|
|||
42
src/error.rs
42
src/error.rs
|
|
@ -1,27 +1,51 @@
|
|||
use std::io;
|
||||
use std::{fmt, io};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{self as miette, Diagnostic};
|
||||
use crate::Diagnostic;
|
||||
|
||||
/**
|
||||
Error enum for miette. Used by certain operations in the protocol.
|
||||
*/
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MietteError {
|
||||
/// Wrapper around [`std::io::Error`]. This is returned when something went
|
||||
/// wrong while reading a [`SourceCode`](crate::SourceCode).
|
||||
#[error(transparent)]
|
||||
#[diagnostic(code(miette::io_error), url(docsrs))]
|
||||
IoError(#[from] 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")]
|
||||
#[diagnostic(
|
||||
code(miette::span_out_of_bounds),
|
||||
help("Double-check your spans. Do you have an off-by-one error?"),
|
||||
url(docsrs)
|
||||
)]
|
||||
OutOfBounds,
|
||||
}
|
||||
|
||||
impl Diagnostic for MietteError {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
match self {
|
||||
MietteError::IoError(_) => Some(Box::new("miette::io_error")),
|
||||
MietteError::OutOfBounds => Some(Box::new("miette::span_out_of_bounds")),
|
||||
}
|
||||
}
|
||||
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
match self {
|
||||
MietteError::IoError(_) => None,
|
||||
MietteError::OutOfBounds => Some(Box::new(
|
||||
"Double-check your spans. Do you have an off-by-one error?",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
let crate_version = env!("CARGO_PKG_VERSION");
|
||||
let variant = match self {
|
||||
MietteError::IoError(_) => "#variant.IoError",
|
||||
MietteError::OutOfBounds => "#variant.OutOfBounds",
|
||||
};
|
||||
Some(Box::new(format!(
|
||||
"https://docs.rs/miette/{}/miette/enum.MietteError.html{}",
|
||||
crate_version, variant,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ impl Report {
|
|||
|
||||
/// Create a new error object from a printable error message.
|
||||
///
|
||||
/// If the argument implements std::error::Error, prefer `Report::new`
|
||||
/// If the argument implements [`std::error::Error`], prefer `Report::new`
|
||||
/// instead which preserves the underlying error's cause chain and
|
||||
/// backtrace. If the argument may or may not implement std::error::Error
|
||||
/// backtrace. If the argument may or may not implement [`std::error::Error`]
|
||||
/// now or in the future, use `miette!(err)` which handles either way
|
||||
/// correctly.
|
||||
///
|
||||
|
|
@ -206,7 +206,7 @@ impl Report {
|
|||
/// Create a new error from an error message to wrap the existing error.
|
||||
///
|
||||
/// For attaching a higher level error message to a `Result` as it is
|
||||
/// propagated, the [crate::WrapErr] extension trait may be more
|
||||
/// propagated, the [`WrapErr`](crate::WrapErr) extension trait may be more
|
||||
/// convenient than this function.
|
||||
///
|
||||
/// The primary reason to use `error.wrap_err(...)` instead of
|
||||
|
|
@ -233,7 +233,7 @@ impl Report {
|
|||
unsafe { Report::construct(error, vtable, handler) }
|
||||
}
|
||||
|
||||
/// Compatibility re-export of wrap_err for interop with `anyhow`
|
||||
/// Compatibility re-export of `wrap_err` for interop with `anyhow`
|
||||
pub fn context<D>(self, msg: D) -> Self
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
|
|
|
|||
|
|
@ -222,6 +222,9 @@ macro_rules! ensure {
|
|||
/// ## `anyhow`/`eyre` Users
|
||||
///
|
||||
/// You can just replace `use`s of the `anyhow!`/`eyre!` macros with `miette!`.
|
||||
///
|
||||
/// [`diagnostic!`]: crate::diagnostic!
|
||||
/// [`Report`]: crate::Report
|
||||
#[macro_export]
|
||||
macro_rules! miette {
|
||||
($($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => {
|
||||
|
|
@ -282,6 +285,8 @@ macro_rules! miette {
|
|||
)]
|
||||
/// assert_eq!(diag.message, "1 + 2 = 3");
|
||||
/// ```
|
||||
///
|
||||
/// [`MietteDiagnostic`]: crate::MietteDiagnostic
|
||||
#[macro_export]
|
||||
macro_rules! diagnostic {
|
||||
($fmt:literal $($arg:tt)*) => {{
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@
|
|||
use core::fmt::Display;
|
||||
|
||||
use std::error::Error as StdError;
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub use into_diagnostic::*;
|
||||
|
|
@ -25,10 +24,10 @@ pub use ReportHandler as EyreContext;
|
|||
#[allow(unreachable_pub)]
|
||||
pub use WrapErr as Context;
|
||||
|
||||
#[cfg(not(feature = "fancy-no-backtrace"))]
|
||||
#[cfg(not(feature = "fancy-base"))]
|
||||
use crate::DebugReportHandler;
|
||||
use crate::Diagnostic;
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[cfg(feature = "fancy-base")]
|
||||
use crate::MietteHandler;
|
||||
|
||||
use error::ErrorImpl;
|
||||
|
|
@ -62,7 +61,7 @@ unsafe impl Send for Report {}
|
|||
pub type ErrorHook =
|
||||
Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;
|
||||
|
||||
static HOOK: OnceCell<ErrorHook> = OnceCell::new();
|
||||
static HOOK: OnceLock<ErrorHook> = OnceLock::new();
|
||||
|
||||
/// Error indicating that [`set_hook()`] was unable to install the provided
|
||||
/// [`ErrorHook`].
|
||||
|
|
@ -103,9 +102,9 @@ fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler>
|
|||
}
|
||||
|
||||
fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler + 'static> {
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[cfg(feature = "fancy-base")]
|
||||
return Box::new(MietteHandler::new());
|
||||
#[cfg(not(feature = "fancy-no-backtrace"))]
|
||||
#[cfg(not(feature = "fancy-base"))]
|
||||
return Box::new(DebugReportHandler::new());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,35 +6,9 @@ use crate::{Diagnostic, LabeledSpan, Report, SourceCode};
|
|||
|
||||
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,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Display for DisplayError<M>
|
||||
where
|
||||
M: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> StdError for DisplayError<M> where M: Display + 'static {}
|
||||
impl<M> Diagnostic for DisplayError<M> where M: Display + 'static {}
|
||||
|
||||
impl<M> Debug for MessageError<M>
|
||||
where
|
||||
M: Display + Debug,
|
||||
|
|
@ -56,21 +30,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>);
|
||||
|
||||
|
|
@ -163,7 +122,7 @@ impl<E: Diagnostic, C: SourceCode> Diagnostic for WithSourceCode<E, C> {
|
|||
}
|
||||
|
||||
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
||||
Some(&self.source_code)
|
||||
self.error.source_code().or(Some(&self.source_code))
|
||||
}
|
||||
|
||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||
|
|
@ -197,7 +156,7 @@ impl<C: SourceCode> Diagnostic for WithSourceCode<Report, C> {
|
|||
}
|
||||
|
||||
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
||||
Some(&self.source_code)
|
||||
self.error.source_code().or(Some(&self.source_code))
|
||||
}
|
||||
|
||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||
|
|
@ -232,3 +191,88 @@ impl<C> StdError for WithSourceCode<Report, C> {
|
|||
self.error.source()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{Diagnostic, LabeledSpan, Report, SourceCode, SourceSpan};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("inner")]
|
||||
struct Inner {
|
||||
pub(crate) at: SourceSpan,
|
||||
pub(crate) source_code: Option<String>,
|
||||
}
|
||||
|
||||
impl Diagnostic for Inner {
|
||||
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
|
||||
Some(Box::new(std::iter::once(LabeledSpan::underline(self.at))))
|
||||
}
|
||||
|
||||
fn source_code(&self) -> Option<&dyn SourceCode> {
|
||||
self.source_code.as_ref().map(|s| s as _)
|
||||
}
|
||||
}
|
||||
|
||||
#[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";
|
||||
let outer_source = "abc";
|
||||
|
||||
let report = Report::from(Inner {
|
||||
at: (0..5).into(),
|
||||
source_code: Some(inner_source.to_string()),
|
||||
})
|
||||
.with_source_code(outer_source.to_string());
|
||||
|
||||
let underlined = String::from_utf8(
|
||||
report
|
||||
.source_code()
|
||||
.unwrap()
|
||||
.read_span(&(0..5).into(), 0, 0)
|
||||
.unwrap()
|
||||
.data()
|
||||
.to_vec(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(underlined, "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "fancy")]
|
||||
fn two_source_codes() {
|
||||
let inner_source = "hello world";
|
||||
let outer_source = "abc";
|
||||
|
||||
let report = Report::from(Outer {
|
||||
errors: vec![
|
||||
Inner {
|
||||
at: (0..5).into(),
|
||||
source_code: Some(inner_source.to_string()),
|
||||
},
|
||||
Inner {
|
||||
at: (1..2).into(),
|
||||
source_code: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
.with_source_code(outer_source.to_string());
|
||||
|
||||
let message = format!("{:?}", report);
|
||||
assert!(message.contains(inner_source));
|
||||
assert!(message.contains(outer_source));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
200
src/handler.rs
200
src/handler.rs
|
|
@ -1,5 +1,7 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::highlighters::Highlighter;
|
||||
use crate::highlighters::MietteHighlighter;
|
||||
use crate::protocol::Diagnostic;
|
||||
use crate::GraphicalReportHandler;
|
||||
use crate::GraphicalTheme;
|
||||
|
|
@ -55,6 +57,11 @@ pub struct MietteHandlerOpts {
|
|||
pub(crate) context_lines: Option<usize>,
|
||||
pub(crate) tab_width: Option<usize>,
|
||||
pub(crate) with_cause_chain: Option<bool>,
|
||||
pub(crate) break_words: Option<bool>,
|
||||
pub(crate) wrap_lines: Option<bool>,
|
||||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||
pub(crate) highlighter: Option<MietteHighlighter>,
|
||||
}
|
||||
|
||||
impl MietteHandlerOpts {
|
||||
|
|
@ -80,12 +87,79 @@ impl MietteHandlerOpts {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set a syntax highlighter when rendering in graphical mode.
|
||||
/// Use [`force_graphical()`](MietteHandlerOpts::force_graphical()) to
|
||||
/// force graphical mode.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// Use
|
||||
/// [`without_syntax_highlighting()`](MietteHandlerOpts::without_syntax_highlighting())
|
||||
/// To disable highlighting completely.
|
||||
///
|
||||
/// Setting this option will not force color output. In all cases, the
|
||||
/// current color configuration via
|
||||
/// [`color()`](MietteHandlerOpts::color()) takes precedence over
|
||||
/// highlighter configuration.
|
||||
pub fn with_syntax_highlighting(
|
||||
mut self,
|
||||
highlighter: impl Highlighter + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
self.highlighter = Some(MietteHighlighter::from(highlighter));
|
||||
self
|
||||
}
|
||||
|
||||
/// Disables syntax highlighting when rendering in graphical mode.
|
||||
/// Use [`force_graphical()`](MietteHandlerOpts::force_graphical()) to
|
||||
/// force graphical mode.
|
||||
///
|
||||
/// Syntax highlighting is disabled by default unless the
|
||||
/// `syntect-highlighter` feature is enabled. Call this method if you want
|
||||
/// to disable highlighting when building with this feature.
|
||||
pub fn without_syntax_highlighting(mut self) -> Self {
|
||||
self.highlighter = Some(MietteHighlighter::nocolor());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width to wrap the report at. Defaults to 80.
|
||||
pub fn width(mut self, width: usize) -> Self {
|
||||
self.width = Some(width);
|
||||
self
|
||||
}
|
||||
|
||||
/// If true, long lines can be wrapped.
|
||||
///
|
||||
/// If false, long lines will not be broken when they exceed the width.
|
||||
///
|
||||
/// Defaults to true.
|
||||
pub fn wrap_lines(mut self, wrap_lines: bool) -> Self {
|
||||
self.wrap_lines = Some(wrap_lines);
|
||||
self
|
||||
}
|
||||
|
||||
/// If true, long words can be broken when wrapping.
|
||||
///
|
||||
/// If false, long words will not be broken when they exceed the width.
|
||||
///
|
||||
/// Defaults to true.
|
||||
pub fn break_words(mut self, break_words: bool) -> Self {
|
||||
self.break_words = Some(break_words);
|
||||
self
|
||||
}
|
||||
/// Sets the `textwrap::WordSeparator` to use when determining wrap points.
|
||||
pub fn word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
|
||||
self.word_separator = Some(word_separator);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `textwrap::WordSplitter` to use when determining wrap points.
|
||||
pub fn word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
|
||||
self.word_splitter = Some(word_splitter);
|
||||
self
|
||||
}
|
||||
/// Include the cause chain of the top-level error in the report.
|
||||
pub fn with_cause_chain(mut self) -> Self {
|
||||
self.with_cause_chain = Some(true);
|
||||
|
|
@ -191,17 +265,15 @@ impl MietteHandlerOpts {
|
|||
let characters = match self.unicode {
|
||||
Some(true) => ThemeCharacters::unicode(),
|
||||
Some(false) => ThemeCharacters::ascii(),
|
||||
None if supports_unicode::on(supports_unicode::Stream::Stderr) => {
|
||||
ThemeCharacters::unicode()
|
||||
}
|
||||
None if syscall::supports_unicode() => ThemeCharacters::unicode(),
|
||||
None => ThemeCharacters::ascii(),
|
||||
};
|
||||
let styles = if self.color == Some(false) {
|
||||
ThemeStyles::none()
|
||||
} else if let Some(color) = supports_color::on(supports_color::Stream::Stderr) {
|
||||
} else if let Some(color_has_16m) = syscall::supports_color_has_16m() {
|
||||
match self.rgb_colors {
|
||||
RgbColors::Always => ThemeStyles::rgb(),
|
||||
RgbColors::Preferred if color.has_16m => ThemeStyles::rgb(),
|
||||
RgbColors::Preferred if color_has_16m => ThemeStyles::rgb(),
|
||||
_ => ThemeStyles::ansi(),
|
||||
}
|
||||
} else if self.color == Some(true) {
|
||||
|
|
@ -212,11 +284,31 @@ 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 theme = self.theme.unwrap_or(GraphicalTheme { characters, styles });
|
||||
let mut handler = GraphicalReportHandler::new()
|
||||
let mut handler = GraphicalReportHandler::new_themed(theme)
|
||||
.with_width(width)
|
||||
.with_links(linkify)
|
||||
.with_theme(theme);
|
||||
.with_links(linkify);
|
||||
handler.highlighter = highlighter;
|
||||
if let Some(with_cause_chain) = self.with_cause_chain {
|
||||
if with_cause_chain {
|
||||
handler = handler.with_cause_chain();
|
||||
|
|
@ -233,6 +325,19 @@ impl MietteHandlerOpts {
|
|||
if let Some(w) = self.tab_width {
|
||||
handler = handler.tab_width(w);
|
||||
}
|
||||
if let Some(b) = self.break_words {
|
||||
handler = handler.with_break_words(b)
|
||||
}
|
||||
if let Some(b) = self.wrap_lines {
|
||||
handler = handler.with_wrap_lines(b)
|
||||
}
|
||||
if let Some(s) = self.word_separator {
|
||||
handler = handler.with_word_separator(s)
|
||||
}
|
||||
if let Some(s) = self.word_splitter {
|
||||
handler = handler.with_word_splitter(s)
|
||||
}
|
||||
|
||||
MietteHandler {
|
||||
inner: Box::new(handler),
|
||||
}
|
||||
|
|
@ -257,26 +362,13 @@ impl MietteHandlerOpts {
|
|||
if let Some(linkify) = self.linkify {
|
||||
linkify
|
||||
} else {
|
||||
supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr)
|
||||
syscall::supports_hyperlinks()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(miri))]
|
||||
pub(crate) fn get_width(&self) -> usize {
|
||||
self.width.unwrap_or_else(|| {
|
||||
terminal_size::terminal_size()
|
||||
.unwrap_or((terminal_size::Width(80), terminal_size::Height(0)))
|
||||
.0
|
||||
.0 as usize
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(miri)]
|
||||
// miri doesn't support a syscall (specifically ioctl)
|
||||
// performed by terminal_size, which causes test execution to fail
|
||||
// so when miri is running we'll just fallback to a constant
|
||||
pub(crate) fn get_width(&self) -> usize {
|
||||
self.width.unwrap_or(80)
|
||||
self.width
|
||||
.unwrap_or_else(|| syscall::terminal_width().unwrap_or(80))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -321,3 +413,63 @@ impl ReportHandler for MietteHandler {
|
|||
self.inner.debug(diagnostic, f)
|
||||
}
|
||||
}
|
||||
|
||||
mod syscall {
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
#[inline]
|
||||
pub(super) fn terminal_width() -> Option<usize> {
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "fancy-no-syscall", miri))] {
|
||||
None
|
||||
} else {
|
||||
terminal_size::terminal_size().map(|size| size.0 .0 as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn supports_hyperlinks() -> bool {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "fancy-no-syscall")] {
|
||||
false
|
||||
} else {
|
||||
supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
#[inline]
|
||||
pub(super) fn supports_color() -> bool {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "fancy-no-syscall")] {
|
||||
false
|
||||
} else {
|
||||
supports_color::on(supports_color::Stream::Stderr).is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn supports_color_has_16m() -> Option<bool> {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "fancy-no-syscall")] {
|
||||
None
|
||||
} else {
|
||||
supports_color::on(supports_color::Stream::Stderr).map(|color| color.has_16m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn supports_unicode() -> bool {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "fancy-no-syscall")] {
|
||||
false
|
||||
} else {
|
||||
supports_unicode::on(supports_unicode::Stream::Stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -96,7 +96,7 @@ impl JSONReportHandler {
|
|||
}
|
||||
write!(f, r#""{}""#, escape(&error.to_string()))?;
|
||||
}
|
||||
write!(f, "],")?
|
||||
write!(f, "],")?;
|
||||
} else {
|
||||
write!(f, r#""causes": [],"#)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,20 +5,20 @@ Reporters included with `miette`.
|
|||
#[allow(unreachable_pub)]
|
||||
pub use debug::*;
|
||||
#[allow(unreachable_pub)]
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[cfg(feature = "fancy-base")]
|
||||
pub use graphical::*;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use json::*;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use narratable::*;
|
||||
#[allow(unreachable_pub)]
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[cfg(feature = "fancy-base")]
|
||||
pub use theme::*;
|
||||
|
||||
mod debug;
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[cfg(feature = "fancy-base")]
|
||||
mod graphical;
|
||||
mod json;
|
||||
mod narratable;
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[cfg(feature = "fancy-base")]
|
||||
mod theme;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use is_terminal::IsTerminal;
|
||||
use std::io::IsTerminal;
|
||||
|
||||
use owo_colors::Style;
|
||||
|
||||
/**
|
||||
|
|
@ -55,9 +56,9 @@ impl GraphicalTheme {
|
|||
|
||||
/// A "basic" graphical theme that skips colors and unicode characters and
|
||||
/// just does monochrome ascii art. If you want a completely non-graphical
|
||||
/// rendering of your `Diagnostic`s, check out
|
||||
/// [crate::NarratableReportHandler], or write your own
|
||||
/// [crate::ReportHandler]!
|
||||
/// rendering of your [`Diagnostic`](crate::Diagnostic)s, check out
|
||||
/// [`NarratableReportHandler`](crate::NarratableReportHandler), or write
|
||||
/// your own [`ReportHandler`](crate::ReportHandler)
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
characters: ThemeCharacters::ascii(),
|
||||
|
|
@ -79,7 +80,8 @@ impl Default for GraphicalTheme {
|
|||
}
|
||||
|
||||
/**
|
||||
Styles for various parts of graphical rendering for the [crate::GraphicalReportHandler].
|
||||
Styles for various parts of graphical rendering for the
|
||||
[`GraphicalReportHandler`](crate::GraphicalReportHandler).
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ThemeStyles {
|
||||
|
|
@ -159,7 +161,7 @@ impl ThemeStyles {
|
|||
// https://github.com/zesterer/ariadne/blob/e3cb394cb56ecda116a0a1caecd385a49e7f6662/src/draw.rs
|
||||
|
||||
/// Characters to be used when drawing when using
|
||||
/// [crate::GraphicalReportHandler].
|
||||
/// [`GraphicalReportHandler`](crate::GraphicalReportHandler).
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ThemeCharacters {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
use owo_colors::Style;
|
||||
|
||||
use crate::SpanContents;
|
||||
|
||||
use super::{Highlighter, HighlighterState};
|
||||
|
||||
/// The default syntax highlighter. It applies `Style::default()` to input text.
|
||||
/// This is used by default when no syntax highlighting features are enabled.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlankHighlighter;
|
||||
|
||||
impl Highlighter for BlankHighlighter {
|
||||
fn start_highlighter_state<'h>(
|
||||
&'h self,
|
||||
_source: &dyn SpanContents<'_>,
|
||||
) -> Box<dyn super::HighlighterState + 'h> {
|
||||
Box::new(BlankHighlighterState)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BlankHighlighter {
|
||||
fn default() -> Self {
|
||||
BlankHighlighter
|
||||
}
|
||||
}
|
||||
|
||||
/// The default highlighter state. It applies `Style::default()` to input text.
|
||||
/// This is used by default when no syntax highlighting features are enabled.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlankHighlighterState;
|
||||
|
||||
impl HighlighterState for BlankHighlighterState {
|
||||
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<owo_colors::Styled<&'s str>> {
|
||||
vec![Style::default().style(line)]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
//! This module provides a trait for creating custom syntax highlighters that
|
||||
//! highlight [`Diagnostic`](crate::Diagnostic) source code with ANSI escape
|
||||
//! sequences when rendering with the [`GraphicalReportHighlighter`](crate::GraphicalReportHandler).
|
||||
//!
|
||||
//! It also provides built-in highlighter implementations that you can use out of the box.
|
||||
//! By default, there are no syntax highlighters exported by miette
|
||||
//! (except for the no-op [`BlankHighlighter`]).
|
||||
//! To enable support for specific highlighters, you should enable their associated feature flag.
|
||||
//!
|
||||
//! Currently supported syntax highlighters and their feature flags:
|
||||
//! * `syntect-highlighter` - Enables [`syntect`](https://docs.rs/syntect/latest/syntect/) syntax highlighting support via the [`SyntectHighlighter`]
|
||||
//!
|
||||
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use crate::SpanContents;
|
||||
use owo_colors::Styled;
|
||||
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
pub use self::syntect::*;
|
||||
pub use blank::*;
|
||||
|
||||
mod blank;
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
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].
|
||||
///
|
||||
/// 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]
|
||||
/// 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
|
||||
/// responsible for the actual rendering.
|
||||
fn start_highlighter_state<'h>(
|
||||
&'h self,
|
||||
source: &dyn SpanContents<'_>,
|
||||
) -> Box<dyn HighlighterState + 'h>;
|
||||
}
|
||||
|
||||
/// A stateful highlighter that incrementally highlights lines of a particular
|
||||
/// source code.
|
||||
///
|
||||
/// The [GraphicalReportHandler](crate::GraphicalReportHandler)
|
||||
/// will create a highlighter state by calling
|
||||
/// [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
|
||||
/// mutable parsing and highlighting state.
|
||||
pub trait HighlighterState {
|
||||
/// Highlight an individual line from the source code by returning a vector of [Styled]
|
||||
/// regions.
|
||||
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>>;
|
||||
}
|
||||
|
||||
/// Arcified trait object for Highlighter. Used internally by [GraphicalReportHandler]
|
||||
///
|
||||
/// 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>);
|
||||
|
||||
impl MietteHighlighter {
|
||||
pub(crate) fn nocolor() -> Self {
|
||||
Self::from(BlankHighlighter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
pub(crate) fn syntect_truecolor() -> Self {
|
||||
Self::from(SyntectHighlighter::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MietteHighlighter {
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
fn default() -> Self {
|
||||
use std::io::IsTerminal;
|
||||
match std::env::var("NO_COLOR") {
|
||||
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
|
||||
//TODO: should use ANSI styling instead of 24-bit truecolor here
|
||||
Self(Arc::new(SyntectHighlighter::default()))
|
||||
}
|
||||
Ok(string) if string != "0" => MietteHighlighter::nocolor(),
|
||||
_ => Self(Arc::new(SyntectHighlighter::default())),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "syntect-highlighter"))]
|
||||
fn default() -> Self {
|
||||
return MietteHighlighter::nocolor();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Highlighter + Send + Sync + 'static> From<T> for MietteHighlighter {
|
||||
fn from(value: T) -> Self {
|
||||
Self(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for MietteHighlighter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "MietteHighlighter(...)")
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MietteHighlighter {
|
||||
type Target = dyn Highlighter + Send + Sync;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
use std::path::Path;
|
||||
|
||||
// all syntect imports are explicitly qualified, but their paths are shortened for convenience
|
||||
mod syntect {
|
||||
pub(super) use syntect::{
|
||||
highlighting::{
|
||||
Color, HighlightIterator, HighlightState, Highlighter, Style, Theme, ThemeSet,
|
||||
},
|
||||
parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet},
|
||||
};
|
||||
}
|
||||
|
||||
use owo_colors::{Rgb, Style, Styled};
|
||||
|
||||
use crate::{
|
||||
highlighters::{Highlighter, HighlighterState},
|
||||
SpanContents,
|
||||
};
|
||||
|
||||
use super::BlankHighlighterState;
|
||||
|
||||
/// Highlights miette [SourceCode] 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.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SyntectHighlighter {
|
||||
theme: syntect::Theme,
|
||||
syntax_set: syntect::SyntaxSet,
|
||||
use_bg_color: bool,
|
||||
}
|
||||
|
||||
impl Default for SyntectHighlighter {
|
||||
fn default() -> Self {
|
||||
let theme_set = syntect::ThemeSet::load_defaults();
|
||||
let theme = theme_set.themes["base16-ocean.dark"].clone();
|
||||
Self::new_themed(theme, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter for SyntectHighlighter {
|
||||
fn start_highlighter_state<'h>(
|
||||
&'h self,
|
||||
source: &dyn SpanContents<'_>,
|
||||
) -> Box<dyn HighlighterState + 'h> {
|
||||
if let Some(syntax) = self.detect_syntax(source) {
|
||||
let highlighter = syntect::Highlighter::new(&self.theme);
|
||||
let parse_state = syntect::ParseState::new(syntax);
|
||||
let highlight_state =
|
||||
syntect::HighlightState::new(&highlighter, syntect::ScopeStack::new());
|
||||
Box::new(SyntectHighlighterState {
|
||||
syntax_set: &self.syntax_set,
|
||||
highlighter,
|
||||
parse_state,
|
||||
highlight_state,
|
||||
use_bg_color: self.use_bg_color,
|
||||
})
|
||||
} else {
|
||||
Box::new(BlankHighlighterState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SyntectHighlighter {
|
||||
/// Create a syntect highlighter with the given theme and syntax set.
|
||||
pub fn new(syntax_set: syntect::SyntaxSet, theme: syntect::Theme, use_bg_color: bool) -> Self {
|
||||
Self {
|
||||
theme,
|
||||
syntax_set,
|
||||
use_bg_color,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a syntect highlighter with the given theme and the default syntax set.
|
||||
pub fn new_themed(theme: syntect::Theme, use_bg_color: bool) -> Self {
|
||||
Self::new(
|
||||
syntect::SyntaxSet::load_defaults_nonewlines(),
|
||||
theme,
|
||||
use_bg_color,
|
||||
)
|
||||
}
|
||||
|
||||
/// Determine syntect SyntaxReference to use for given SourceCode
|
||||
fn detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference> {
|
||||
// use language if given
|
||||
if let Some(language) = contents.language() {
|
||||
return self.syntax_set.find_syntax_by_name(language);
|
||||
}
|
||||
// otherwise try to use any file extension provided in the name
|
||||
if let Some(name) = contents.name() {
|
||||
if let Some(ext) = Path::new(name).extension() {
|
||||
return self
|
||||
.syntax_set
|
||||
.find_syntax_by_extension(ext.to_string_lossy().as_ref());
|
||||
}
|
||||
}
|
||||
// finally, attempt to guess syntax based on first line
|
||||
return self.syntax_set.find_syntax_by_first_line(
|
||||
&std::str::from_utf8(contents.data())
|
||||
.ok()?
|
||||
.split('\n')
|
||||
.next()?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stateful highlighting iterator for [SyntectHighlighter]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SyntectHighlighterState<'h> {
|
||||
syntax_set: &'h syntect::SyntaxSet,
|
||||
highlighter: syntect::Highlighter<'h>,
|
||||
parse_state: syntect::ParseState,
|
||||
highlight_state: syntect::HighlightState,
|
||||
use_bg_color: bool,
|
||||
}
|
||||
|
||||
impl<'h> HighlighterState for SyntectHighlighterState<'h> {
|
||||
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) {
|
||||
let use_bg_color = self.use_bg_color;
|
||||
syntect::HighlightIterator::new(
|
||||
&mut self.highlight_state,
|
||||
&ops,
|
||||
line,
|
||||
&mut self.highlighter,
|
||||
)
|
||||
.map(|(style, str)| (convert_style(style, use_bg_color).style(str)))
|
||||
.collect()
|
||||
} else {
|
||||
vec![Style::default().style(line)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let fg = blend_fg_color(syntect_style);
|
||||
let bg = convert_color(syntect_style.background);
|
||||
Style::new().color(fg).on_color(bg)
|
||||
} else {
|
||||
let fg = convert_color(syntect_style.foreground);
|
||||
Style::new().color(fg)
|
||||
}
|
||||
}
|
||||
|
||||
/// Blend foreground RGB into background RGB according to alpha channel
|
||||
#[inline]
|
||||
fn blend_fg_color(syntect_style: syntect::Style) -> Rgb {
|
||||
let fg = syntect_style.foreground;
|
||||
if fg.a == 0xff {
|
||||
return convert_color(fg);
|
||||
}
|
||||
let bg = syntect_style.background;
|
||||
let ratio = fg.a as u32;
|
||||
let r = (fg.r as u32 * ratio + bg.r as u32 * (255 - ratio)) / 255;
|
||||
let g = (fg.g as u32 * ratio + bg.g as u32 * (255 - ratio)) / 255;
|
||||
let b = (fg.b as u32 * ratio + bg.b as u32 * (255 - ratio)) / 255;
|
||||
Rgb(r as u8, g as u8, b as u8)
|
||||
}
|
||||
|
||||
/// Convert syntect color into owo color.
|
||||
///
|
||||
/// Note: ignores alpha channel. use [`blend_fg_color`] if you need that
|
||||
///
|
||||
#[inline]
|
||||
fn convert_color(color: syntect::Color) -> Rgb {
|
||||
Rgb(color.r, color.g, color.b)
|
||||
}
|
||||
145
src/lib.rs
145
src/lib.rs
|
|
@ -43,10 +43,14 @@
|
|||
//! - [... in `main()`](#-in-main)
|
||||
//! - [... diagnostic code URLs](#-diagnostic-code-urls)
|
||||
//! - [... snippets](#-snippets)
|
||||
//! - [... help text](#-help-text)
|
||||
//! - [... severity level](#-severity-level)
|
||||
//! - [... multiple related errors](#-multiple-related-errors)
|
||||
//! - [... delayed source code](#-delayed-source-code)
|
||||
//! - [... handler options](#-handler-options)
|
||||
//! - [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||
//! - [... syntax highlighting](#-syntax-highlighting)
|
||||
//! - [... collection of labels](#-collection-of-labels)
|
||||
//! - [Acknowledgements](#acknowledgements)
|
||||
//! - [License](#license)
|
||||
//!
|
||||
|
|
@ -109,7 +113,7 @@
|
|||
//! // The Source that we're gonna be printing snippets out of.
|
||||
//! // This can be a String if you don't have or care about file names.
|
||||
//! #[source_code]
|
||||
//! src: NamedSource,
|
||||
//! src: NamedSource<String>,
|
||||
//! // Snippets and highlights can be included in the diagnostic!
|
||||
//! #[label("This bit here")]
|
||||
//! bad_bit: SourceSpan,
|
||||
|
|
@ -186,7 +190,7 @@
|
|||
//!
|
||||
//! ```rust
|
||||
//! // lib/error.rs
|
||||
//! use miette::Diagnostic;
|
||||
//! use miette::{Diagnostic, SourceSpan};
|
||||
//! use thiserror::Error;
|
||||
//!
|
||||
//! #[derive(Error, Diagnostic, Debug)]
|
||||
|
|
@ -198,6 +202,18 @@
|
|||
//! #[error("Oops it blew up")]
|
||||
//! #[diagnostic(code(my_lib::bad_code))]
|
||||
//! BadThingHappened,
|
||||
//!
|
||||
//! #[error(transparent)]
|
||||
//! // Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
||||
//! #[diagnostic(transparent)]
|
||||
//! AnotherError(#[from] AnotherError),
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Error, Diagnostic, Debug)]
|
||||
//! #[error("another error")]
|
||||
//! pub struct AnotherError {
|
||||
//! #[label("here")]
|
||||
//! pub at: SourceSpan
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
|
|
@ -292,6 +308,23 @@
|
|||
//! miette = { version = "X.Y.Z", features = ["fancy"] }
|
||||
//! ```
|
||||
//!
|
||||
//! Another way to display a diagnostic is by printing them using the debug formatter.
|
||||
//! This is, in fact, what returning diagnostics from main ends up doing.
|
||||
//! To do it yourself, you can write the following:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use miette::{IntoDiagnostic, Result};
|
||||
//! use semver::Version;
|
||||
//!
|
||||
//! fn just_a_random_function() {
|
||||
//! let version_result: Result<Version> = "1.2.x".parse().into_diagnostic();
|
||||
//! match version_result {
|
||||
//! Err(e) => println!("{:?}", e),
|
||||
//! Ok(version) => println!("{}", version),
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### ... diagnostic code URLs
|
||||
//!
|
||||
//! `miette` supports providing a URL for individual diagnostics. This URL will
|
||||
|
|
@ -393,7 +426,7 @@
|
|||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! #### ... help text
|
||||
//! ### ... help text
|
||||
//! `miette` provides two facilities for supplying help text for your errors:
|
||||
//!
|
||||
//! The first is the `#[help()]` format attribute that applies to structs or
|
||||
|
|
@ -429,6 +462,19 @@
|
|||
//! };
|
||||
//! ```
|
||||
//!
|
||||
//! ### ... severity level
|
||||
//! `miette` provides a way to set the severity level of a diagnostic.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use miette::Diagnostic;
|
||||
//! use thiserror::Error;
|
||||
//!
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("welp")]
|
||||
//! #[diagnostic(severity(Warning))]
|
||||
//! struct Foo;
|
||||
//! ```
|
||||
//!
|
||||
//! ### ... multiple related errors
|
||||
//!
|
||||
//! `miette` supports collecting multiple errors into a single diagnostic, and
|
||||
|
|
@ -581,6 +627,7 @@
|
|||
//! .unicode(false)
|
||||
//! .context_lines(3)
|
||||
//! .tab_width(4)
|
||||
//! .break_words(true)
|
||||
//! .build(),
|
||||
//! )
|
||||
//! }))
|
||||
|
|
@ -604,7 +651,7 @@
|
|||
//!
|
||||
//! let source = "2 + 2 * 2 = 8".to_string();
|
||||
//! let report = miette!(
|
||||
//! labels = vec[
|
||||
//! labels = vec![
|
||||
//! LabeledSpan::at(12..13, "this should be 6"),
|
||||
//! ],
|
||||
//! help = "'*' has greater precedence than '+'",
|
||||
|
|
@ -613,6 +660,89 @@
|
|||
//! println!("{:?}", report)
|
||||
//! ```
|
||||
//!
|
||||
//! ### ... syntax highlighting
|
||||
//!
|
||||
//! `miette` can be configured to highlight syntax in source code snippets.
|
||||
//!
|
||||
//! <!-- TODO: screenshot goes here once default Theme is decided -->
|
||||
//!
|
||||
//! To use the built-in highlighting functionality, you must enable the
|
||||
//! `syntect-highlighter` crate feature. When this feature is enabled, `miette` will
|
||||
//! 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
|
||||
//! 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
|
||||
//! 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.
|
||||
//!
|
||||
//! If you want to use a custom highlighter, you can provide a custom
|
||||
//! implementation of the [`Highlighter`](highlighters::Highlighter)
|
||||
//! trait to [`MietteHandlerOpts`] by calling the
|
||||
//! [`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
||||
//! method. See the [`highlighters`] module docs for more details.
|
||||
//!
|
||||
//! ### ... collection of labels
|
||||
//!
|
||||
//! When the number of labels is unknown, you can use a collection of `SourceSpan`
|
||||
//! (or any type convertible into `SourceSpan`). For this, add the `collection`
|
||||
//! parameter to `label` and use any type than can be iterated over for the field.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("oops!")]
|
||||
//! struct MyError {
|
||||
//! #[label("main issue")]
|
||||
//! primary_span: SourceSpan,
|
||||
//!
|
||||
//! #[label(collection, "related to this")]
|
||||
//! other_spans: Vec<Range<usize>>,
|
||||
//! }
|
||||
//!
|
||||
//! let report: miette::Report = MyError {
|
||||
//! primary_span: (6, 9).into(),
|
||||
//! other_spans: vec![19..26, 30..41],
|
||||
//! }.into();
|
||||
//!
|
||||
//! println!("{:?}", report.with_source_code("About something or another or yet another ...".to_string()));
|
||||
//! ```
|
||||
//!
|
||||
//! A collection can also be of `LabeledSpan` if you want to have different text
|
||||
//! for different labels. Labels with no text will use the one from the `label`
|
||||
//! attribute
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("oops!")]
|
||||
//! struct MyError {
|
||||
//! #[label("main issue")]
|
||||
//! primary_span: SourceSpan,
|
||||
//!
|
||||
//! #[label(collection, "related to this")]
|
||||
//! other_spans: Vec<LabeledSpan>, // LabeledSpan
|
||||
//! }
|
||||
//!
|
||||
//! let report: miette::Report = MyError {
|
||||
//! primary_span: (6, 9).into(),
|
||||
//! other_spans: vec![
|
||||
//! LabeledSpan::new(None, 19, 7), // Use default text `related to this`
|
||||
//! LabeledSpan::new(Some("and also this".to_string()), 30, 11), // Use specific text
|
||||
//! ],
|
||||
//! }.into();
|
||||
//!
|
||||
//! println!("{:?}", report.with_source_code("About something or another or yet another ...".to_string()));
|
||||
//! ```
|
||||
//!
|
||||
//! ## MSRV
|
||||
//!
|
||||
//! This crate requires rustc 1.70.0 or later.
|
||||
//!
|
||||
//! ## Acknowledgements
|
||||
//!
|
||||
//! `miette` was not developed in a void. It owes enormous credit to various
|
||||
|
|
@ -640,11 +770,12 @@
|
|||
//! and some from [`thiserror`](https://github.com/dtolnay/thiserror), also
|
||||
//! under the Apache License. Some code is taken from
|
||||
//! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
||||
#[cfg(feature = "derive")]
|
||||
pub use miette_derive::*;
|
||||
|
||||
pub use error::*;
|
||||
pub use eyreish::*;
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[cfg(feature = "fancy-base")]
|
||||
pub use handler::*;
|
||||
pub use handlers::*;
|
||||
pub use miette_diagnostic::*;
|
||||
|
|
@ -657,9 +788,11 @@ mod chain;
|
|||
mod diagnostic_chain;
|
||||
mod error;
|
||||
mod eyreish;
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[cfg(feature = "fancy-base")]
|
||||
mod handler;
|
||||
mod handlers;
|
||||
#[cfg(feature = "fancy-base")]
|
||||
pub mod highlighters;
|
||||
#[doc(hidden)]
|
||||
pub mod macro_helpers;
|
||||
mod miette_diagnostic;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::protocol::{LabeledSpan, SourceSpan};
|
||||
|
||||
// Huge thanks to @jam1gamer for this hack:
|
||||
// https://twitter.com/jam1garner/status/1515887996444323840
|
||||
|
||||
|
|
@ -36,3 +38,24 @@ impl<T> ToOption for &OptionalWrapper<T> {
|
|||
Some(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub struct ToLabelSpanWrapper {}
|
||||
pub trait ToLabeledSpan<T> {
|
||||
#[doc(hidden)]
|
||||
fn to_labeled_span(span: T) -> LabeledSpan;
|
||||
}
|
||||
impl ToLabeledSpan<LabeledSpan> for ToLabelSpanWrapper {
|
||||
fn to_labeled_span(span: LabeledSpan) -> LabeledSpan {
|
||||
span
|
||||
}
|
||||
}
|
||||
impl<T> ToLabeledSpan<T> for ToLabelSpanWrapper
|
||||
where
|
||||
T: Into<SourceSpan>,
|
||||
{
|
||||
fn to_labeled_span(span: T) -> LabeledSpan {
|
||||
LabeledSpan::new_with_span(None, span.into())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ impl MietteDiagnostic {
|
|||
/// ```
|
||||
pub fn and_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self {
|
||||
let mut all_labels = self.labels.unwrap_or_default();
|
||||
all_labels.extend(labels.into_iter());
|
||||
all_labels.extend(labels);
|
||||
self.labels = Some(all_labels);
|
||||
self
|
||||
}
|
||||
|
|
@ -292,14 +292,16 @@ fn test_serialize_miette_diagnostic() {
|
|||
"offset": 0,
|
||||
"length": 0
|
||||
},
|
||||
"label": "label1"
|
||||
"label": "label1",
|
||||
"primary": false
|
||||
},
|
||||
{
|
||||
"span": {
|
||||
"offset": 1,
|
||||
"length": 2
|
||||
},
|
||||
"label": "label2"
|
||||
"label": "label2",
|
||||
"primary": false
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
@ -350,14 +352,16 @@ fn test_deserialize_miette_diagnostic() {
|
|||
"offset": 0,
|
||||
"length": 0
|
||||
},
|
||||
"label": "label1"
|
||||
"label": "label1",
|
||||
"primary": false
|
||||
},
|
||||
{
|
||||
"span": {
|
||||
"offset": 1,
|
||||
"length": 2
|
||||
},
|
||||
"label": "label2"
|
||||
"label": "label2",
|
||||
"primary": false
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,27 +3,34 @@ use crate::{MietteError, MietteSpanContents, SourceCode, SpanContents};
|
|||
/// Utility struct for when you have a regular [`SourceCode`] type that doesn't
|
||||
/// implement `name`. For example [`String`]. Or if you want to override the
|
||||
/// `name` returned by the `SourceCode`.
|
||||
pub struct NamedSource {
|
||||
source: Box<dyn SourceCode + 'static>,
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct NamedSource<S: SourceCode + 'static> {
|
||||
source: S,
|
||||
name: String,
|
||||
language: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for NamedSource {
|
||||
impl<S: SourceCode> std::fmt::Debug for NamedSource<S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("NamedSource")
|
||||
.field("name", &self.name)
|
||||
.field("source", &"<redacted>");
|
||||
.field("source", &"<redacted>")
|
||||
.field("language", &self.language);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NamedSource {
|
||||
impl<S: SourceCode + 'static> NamedSource<S> {
|
||||
/// Create a new `NamedSource` using a regular [`SourceCode`] and giving
|
||||
/// its returned [`SpanContents`] a name.
|
||||
pub fn new(name: impl AsRef<str>, source: impl SourceCode + Send + Sync + 'static) -> Self {
|
||||
pub fn new(name: impl AsRef<str>, source: S) -> Self
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
Self {
|
||||
source: Box::new(source),
|
||||
source,
|
||||
name: name.as_ref().to_string(),
|
||||
language: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -34,28 +41,38 @@ impl NamedSource {
|
|||
|
||||
/// Returns a reference the inner [`SourceCode`] type for this
|
||||
/// `NamedSource`.
|
||||
pub fn inner(&self) -> &(dyn SourceCode + 'static) {
|
||||
&*self.source
|
||||
pub fn inner(&self) -> &S {
|
||||
&self.source
|
||||
}
|
||||
|
||||
/// Sets the [`language`](SpanContents::language) for this source code.
|
||||
pub fn with_language(mut self, language: impl Into<String>) -> Self {
|
||||
self.language = Some(language.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceCode for NamedSource {
|
||||
impl<S: SourceCode + 'static> SourceCode for NamedSource<S> {
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
span: &crate::SourceSpan,
|
||||
context_lines_before: usize,
|
||||
context_lines_after: usize,
|
||||
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||
let contents = self
|
||||
.inner()
|
||||
.read_span(span, context_lines_before, context_lines_after)?;
|
||||
Ok(Box::new(MietteSpanContents::new_named(
|
||||
let inner_contents =
|
||||
self.inner()
|
||||
.read_span(span, context_lines_before, context_lines_after)?;
|
||||
let mut contents = MietteSpanContents::new_named(
|
||||
self.name.clone(),
|
||||
contents.data(),
|
||||
*contents.span(),
|
||||
contents.line(),
|
||||
contents.column(),
|
||||
contents.line_count(),
|
||||
)))
|
||||
inner_contents.data(),
|
||||
*inner_contents.span(),
|
||||
inner_contents.line(),
|
||||
inner_contents.column(),
|
||||
inner_contents.line_count(),
|
||||
);
|
||||
if let Some(language) = &self.language {
|
||||
contents = contents.with_language(language);
|
||||
}
|
||||
Ok(Box::new(contents))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
136
src/protocol.rs
136
src/protocol.rs
|
|
@ -69,16 +69,45 @@ pub trait Diagnostic: std::error::Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Box<dyn Diagnostic> {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
(**self).source()
|
||||
}
|
||||
macro_rules! box_error_impls {
|
||||
($($box_type:ty),*) => {
|
||||
$(
|
||||
impl std::error::Error for $box_type {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
(**self).source()
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
||||
self.source()
|
||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
||||
self.source()
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
box_error_impls! {
|
||||
Box<dyn Diagnostic>,
|
||||
Box<dyn Diagnostic + Send>,
|
||||
Box<dyn Diagnostic + Send + Sync>
|
||||
}
|
||||
|
||||
macro_rules! box_borrow_impls {
|
||||
($($box_type:ty),*) => {
|
||||
$(
|
||||
impl std::borrow::Borrow<dyn Diagnostic> for $box_type {
|
||||
fn borrow(&self) -> &(dyn Diagnostic + 'static) {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
box_borrow_impls! {
|
||||
Box<dyn Diagnostic + Send>,
|
||||
Box<dyn Diagnostic + Send + Sync>
|
||||
}
|
||||
|
||||
impl<T: Diagnostic + Send + Sync + 'static> From<T>
|
||||
for Box<dyn Diagnostic + Send + Sync + 'static>
|
||||
{
|
||||
|
|
@ -167,6 +196,7 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Sen
|
|||
*/
|
||||
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Default)]
|
||||
pub enum Severity {
|
||||
/// Just some help. Here's how you could be doing it better.
|
||||
Advice,
|
||||
|
|
@ -174,15 +204,10 @@ pub enum Severity {
|
|||
Warning,
|
||||
/// Critical failure. The program cannot continue.
|
||||
/// This is the default severity, if you don't specify another one.
|
||||
#[default]
|
||||
Error,
|
||||
}
|
||||
|
||||
impl Default for Severity {
|
||||
fn default() -> Self {
|
||||
Severity::Error
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn test_serialize_severity() {
|
||||
|
|
@ -220,7 +245,7 @@ whole thing--meaning you should be able to support `SourceCode`s which are
|
|||
gigabytes or larger in size.
|
||||
*/
|
||||
pub trait SourceCode: Send + Sync {
|
||||
/// Read the bytes for a specific span from this SourceCode, keeping a
|
||||
/// Read the bytes for a specific span from this `SourceCode`, keeping a
|
||||
/// certain number of lines before and after the span as context.
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
|
|
@ -237,6 +262,7 @@ pub struct LabeledSpan {
|
|||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
label: Option<String>,
|
||||
span: SourceSpan,
|
||||
primary: bool,
|
||||
}
|
||||
|
||||
impl LabeledSpan {
|
||||
|
|
@ -244,7 +270,8 @@ impl LabeledSpan {
|
|||
pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
|
||||
Self {
|
||||
label,
|
||||
span: SourceSpan::new(SourceOffset(offset), SourceOffset(len)),
|
||||
span: SourceSpan::new(SourceOffset(offset), len),
|
||||
primary: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,9 +280,24 @@ impl LabeledSpan {
|
|||
Self {
|
||||
label,
|
||||
span: span.into(),
|
||||
primary: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new labeled primary span using an existing span.
|
||||
pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
|
||||
Self {
|
||||
label,
|
||||
span: span.into(),
|
||||
primary: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the text of the label
|
||||
pub fn set_label(&mut self, label: Option<String>) {
|
||||
self.label = label;
|
||||
}
|
||||
|
||||
/// Makes a new label at specified span
|
||||
///
|
||||
/// # Examples
|
||||
|
|
@ -328,6 +370,11 @@ impl LabeledSpan {
|
|||
pub const fn is_empty(&self) -> bool {
|
||||
self.span.is_empty()
|
||||
}
|
||||
|
||||
/// True if this `LabeledSpan` is a primary span.
|
||||
pub const fn primary(&self) -> bool {
|
||||
self.primary
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
|
|
@ -338,7 +385,8 @@ fn test_serialize_labeled_span() {
|
|||
assert_eq!(
|
||||
json!(LabeledSpan::new(None, 0, 0)),
|
||||
json!({
|
||||
"span": { "offset": 0, "length": 0 }
|
||||
"span": { "offset": 0, "length": 0, },
|
||||
"primary": false,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -346,9 +394,10 @@ fn test_serialize_labeled_span() {
|
|||
json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
|
||||
json!({
|
||||
"label": "label",
|
||||
"span": { "offset": 0, "length": 0 }
|
||||
"span": { "offset": 0, "length": 0, },
|
||||
"primary": false,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
|
|
@ -358,23 +407,26 @@ fn test_deserialize_labeled_span() {
|
|||
|
||||
let span: LabeledSpan = serde_json::from_value(json!({
|
||||
"label": null,
|
||||
"span": { "offset": 0, "length": 0 }
|
||||
"span": { "offset": 0, "length": 0, },
|
||||
"primary": false,
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
||||
|
||||
let span: LabeledSpan = serde_json::from_value(json!({
|
||||
"span": { "offset": 0, "length": 0 }
|
||||
"span": { "offset": 0, "length": 0, },
|
||||
"primary": false
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
||||
|
||||
let span: LabeledSpan = serde_json::from_value(json!({
|
||||
"label": "label",
|
||||
"span": { "offset": 0, "length": 0 }
|
||||
"span": { "offset": 0, "length": 0, },
|
||||
"primary": false
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0))
|
||||
assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -399,6 +451,15 @@ pub trait SpanContents<'a> {
|
|||
fn column(&self) -> usize;
|
||||
/// Total number of lines covered by this `SpanContents`.
|
||||
fn line_count(&self) -> usize;
|
||||
|
||||
/// Optional method. The language name for this source code, if any.
|
||||
/// This is used to drive syntax highlighting.
|
||||
///
|
||||
/// Examples: Rust, TOML, C
|
||||
///
|
||||
fn language(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -418,6 +479,8 @@ pub struct MietteSpanContents<'a> {
|
|||
line_count: usize,
|
||||
// Optional filename
|
||||
name: Option<String>,
|
||||
// Optional language
|
||||
language: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> MietteSpanContents<'a> {
|
||||
|
|
@ -436,6 +499,7 @@ impl<'a> MietteSpanContents<'a> {
|
|||
column,
|
||||
line_count,
|
||||
name: None,
|
||||
language: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -455,8 +519,15 @@ impl<'a> MietteSpanContents<'a> {
|
|||
column,
|
||||
line_count,
|
||||
name: Some(name),
|
||||
language: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the [`language`](SourceCode::language) for syntax highlighting.
|
||||
pub fn with_language(mut self, language: impl Into<String>) -> Self {
|
||||
self.language = Some(language.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
||||
|
|
@ -478,10 +549,13 @@ impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
|||
fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
}
|
||||
fn language(&self) -> Option<&str> {
|
||||
self.language.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Span within a [`SourceCode`]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct SourceSpan {
|
||||
/// The start of the span.
|
||||
|
|
@ -492,10 +566,10 @@ pub struct SourceSpan {
|
|||
|
||||
impl SourceSpan {
|
||||
/// Create a new [`SourceSpan`].
|
||||
pub const fn new(start: SourceOffset, length: SourceOffset) -> Self {
|
||||
pub const fn new(start: SourceOffset, length: usize) -> Self {
|
||||
Self {
|
||||
offset: start,
|
||||
length: length.offset(),
|
||||
length,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -525,8 +599,8 @@ impl From<(ByteOffset, usize)> for SourceSpan {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<(SourceOffset, SourceOffset)> for SourceSpan {
|
||||
fn from((start, len): (SourceOffset, SourceOffset)) -> Self {
|
||||
impl From<(SourceOffset, usize)> for SourceSpan {
|
||||
fn from((start, len): (SourceOffset, usize)) -> Self {
|
||||
Self::new(start, len)
|
||||
}
|
||||
}
|
||||
|
|
@ -563,7 +637,7 @@ fn test_serialize_source_span() {
|
|||
assert_eq!(
|
||||
json!(SourceSpan::from(0)),
|
||||
json!({ "offset": 0, "length": 0})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
|
|
@ -572,7 +646,7 @@ fn test_deserialize_source_span() {
|
|||
use serde_json::json;
|
||||
|
||||
let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
|
||||
assert_eq!(span, SourceSpan::from(0))
|
||||
assert_eq!(span, SourceSpan::from(0));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -583,7 +657,7 @@ pub type ByteOffset = usize;
|
|||
/**
|
||||
Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`]
|
||||
*/
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct SourceOffset(ByteOffset);
|
||||
|
||||
|
|
@ -674,12 +748,12 @@ fn test_source_offset_from_location() {
|
|||
fn test_serialize_source_offset() {
|
||||
use serde_json::json;
|
||||
|
||||
assert_eq!(json!(SourceOffset::from(0)), 0)
|
||||
assert_eq!(json!(SourceOffset::from(0)), 0);
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn test_deserialize_source_offset() {
|
||||
let offset: SourceOffset = serde_json::from_str("0").unwrap();
|
||||
assert_eq!(offset, SourceOffset::from(0))
|
||||
assert_eq!(offset, SourceOffset::from(0));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
/*!
|
||||
Default trait implementations for [`SourceCode`].
|
||||
*/
|
||||
use std::{
|
||||
borrow::{Cow, ToOwned},
|
||||
collections::VecDeque,
|
||||
fmt::Debug,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{borrow::Cow, collections::VecDeque, fmt::Debug, sync::Arc};
|
||||
|
||||
use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
use lazy_static::lazy_static;
|
||||
use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler, RgbColors};
|
||||
use regex::Regex;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::sync::Mutex;
|
||||
use thiserror::Error;
|
||||
|
|
@ -42,16 +43,29 @@ fn color_format(handler: MietteHandler) -> ColorFormat {
|
|||
}
|
||||
}
|
||||
|
||||
/// Runs a function with an environment variable set to a specific value, then
|
||||
/// sets it back to it's original value once completed.
|
||||
fn with_env_var<F: FnOnce()>(var: &str, value: &str, body: F) {
|
||||
let old_value = std::env::var_os(var);
|
||||
std::env::set_var(var, value);
|
||||
body();
|
||||
if let Some(old_value) = old_value {
|
||||
std::env::set_var(var, old_value);
|
||||
} else {
|
||||
std::env::remove_var(var);
|
||||
/// Store the current value of an environment variable on construction, and then
|
||||
/// restore that value when the guard is dropped.
|
||||
struct EnvVarGuard<'a> {
|
||||
var: &'a str,
|
||||
old_value: Option<OsString>,
|
||||
}
|
||||
|
||||
impl EnvVarGuard<'_> {
|
||||
fn new(var: &str) -> EnvVarGuard<'_> {
|
||||
EnvVarGuard {
|
||||
var,
|
||||
old_value: std::env::var_os(var),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvVarGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(old_value) = &self.old_value {
|
||||
std::env::set_var(self.var, old_value);
|
||||
} else {
|
||||
std::env::remove_var(self.var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,22 +86,33 @@ fn check_colors<F: Fn(MietteHandlerOpts) -> MietteHandlerOpts>(
|
|||
//
|
||||
// Since environment variables are shared for the entire process, we need
|
||||
// to ensure that only one test that modifies these env vars runs at a time.
|
||||
let guard = COLOR_ENV_VARS.lock().unwrap();
|
||||
let lock = COLOR_ENV_VARS.lock().unwrap();
|
||||
|
||||
with_env_var("NO_COLOR", "1", || {
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), no_support);
|
||||
});
|
||||
with_env_var("FORCE_COLOR", "1", || {
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), ansi_support);
|
||||
});
|
||||
with_env_var("FORCE_COLOR", "3", || {
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), rgb_support);
|
||||
});
|
||||
let guards = (
|
||||
EnvVarGuard::new("NO_COLOR"),
|
||||
EnvVarGuard::new("FORCE_COLOR"),
|
||||
);
|
||||
// Clear color environment variables that may be set outside of 'cargo test'
|
||||
std::env::remove_var("NO_COLOR");
|
||||
std::env::remove_var("FORCE_COLOR");
|
||||
|
||||
drop(guard);
|
||||
std::env::set_var("NO_COLOR", "1");
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), no_support);
|
||||
std::env::remove_var("NO_COLOR");
|
||||
|
||||
std::env::set_var("FORCE_COLOR", "1");
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), ansi_support);
|
||||
std::env::remove_var("FORCE_COLOR");
|
||||
|
||||
std::env::set_var("FORCE_COLOR", "3");
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), rgb_support);
|
||||
std::env::remove_var("FORCE_COLOR");
|
||||
|
||||
drop(guards);
|
||||
drop(lock);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
@ -189,7 +193,7 @@ fn fmt_help() {
|
|||
|
||||
assert_eq!(
|
||||
"1 x hello x \"2\"".to_string(),
|
||||
FooStruct("hello".into()).help().unwrap().to_string()
|
||||
FooStruct("hello").help().unwrap().to_string()
|
||||
);
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
|
@ -201,12 +205,7 @@ fn fmt_help() {
|
|||
|
||||
assert_eq!(
|
||||
"1 x hello x \"2\"".to_string(),
|
||||
BarStruct {
|
||||
my_field: "hello".into()
|
||||
}
|
||||
.help()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
BarStruct { my_field: "hello" }.help().unwrap().to_string()
|
||||
);
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
|
@ -224,7 +223,7 @@ fn fmt_help() {
|
|||
|
||||
assert_eq!(
|
||||
"1 x bar x \"2\"".to_string(),
|
||||
FooEnum::X("bar".into()).help().unwrap().to_string()
|
||||
FooEnum::X("bar").help().unwrap().to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -250,12 +249,7 @@ fn help_field() {
|
|||
|
||||
assert_eq!(
|
||||
"x".to_string(),
|
||||
Foo {
|
||||
do_this: Some("x".into())
|
||||
}
|
||||
.help()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
Foo { do_this: Some("x") }.help().unwrap().to_string()
|
||||
);
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
|
@ -271,16 +265,11 @@ fn help_field() {
|
|||
|
||||
assert_eq!(
|
||||
"x".to_string(),
|
||||
Bar::A(Some("x".into())).help().unwrap().to_string()
|
||||
Bar::A(Some("x")).help().unwrap().to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"x".to_string(),
|
||||
Bar::B {
|
||||
do_this: Some("x".into())
|
||||
}
|
||||
.help()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
Bar::B { do_this: Some("x") }.help().unwrap().to_string()
|
||||
);
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
|
@ -288,20 +277,14 @@ fn help_field() {
|
|||
#[diagnostic()]
|
||||
struct Baz<'a>(#[help] Option<&'a str>);
|
||||
|
||||
assert_eq!(
|
||||
"x".to_string(),
|
||||
Baz(Some("x".into())).help().unwrap().to_string()
|
||||
);
|
||||
assert_eq!("x".to_string(), Baz(Some("x")).help().unwrap().to_string());
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic()]
|
||||
struct Quux<'a>(#[help] &'a str);
|
||||
|
||||
assert_eq!(
|
||||
"x".to_string(),
|
||||
Quux("x".into()).help().unwrap().to_string()
|
||||
);
|
||||
assert_eq!("x".to_string(), Quux("x").help().unwrap().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -309,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,
|
||||
|
|
@ -331,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,
|
||||
|
|
@ -427,7 +412,7 @@ impl ForwardsTo {
|
|||
fn new() -> Self {
|
||||
ForwardsTo {
|
||||
src: SNIPPET_TEXT.into(),
|
||||
label: SourceSpan::new(11.into(), 6.into()),
|
||||
label: SourceSpan::new(11.into(), 6),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -589,7 +574,7 @@ fn test_unit_struct_display() {
|
|||
#[error("unit only")]
|
||||
#[diagnostic(code(foo::bar::overridden), help("hello from unit help"))]
|
||||
struct UnitOnly;
|
||||
assert_eq!(UnitOnly.help().unwrap().to_string(), "hello from unit help")
|
||||
assert_eq!(UnitOnly.help().unwrap().to_string(), "hello from unit help");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -603,5 +588,47 @@ fn test_unit_enum_display() {
|
|||
assert_eq!(
|
||||
Enum::UnitVariant.help().unwrap().to_string(),
|
||||
"hello from unit help"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_optional_source_code() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("struct with optional source")]
|
||||
struct Struct {
|
||||
#[source_code]
|
||||
src: Option<String>,
|
||||
}
|
||||
assert!(Struct { src: None }.source_code().is_none());
|
||||
assert!(Struct {
|
||||
src: Some("".to_string())
|
||||
}
|
||||
.source_code()
|
||||
.is_some());
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
enum Enum {
|
||||
#[error("variant1 with optional source")]
|
||||
Variant1 {
|
||||
#[source_code]
|
||||
src: Option<String>,
|
||||
},
|
||||
#[error("variant2 with optional source")]
|
||||
Variant2 {
|
||||
#[source_code]
|
||||
src: Option<String>,
|
||||
},
|
||||
}
|
||||
assert!(Enum::Variant1 { src: None }.source_code().is_none());
|
||||
assert!(Enum::Variant1 {
|
||||
src: Some("".to_string())
|
||||
}
|
||||
.source_code()
|
||||
.is_some());
|
||||
assert!(Enum::Variant2 { src: None }.source_code().is_none());
|
||||
assert!(Enum::Variant2 {
|
||||
src: Some("".to_string())
|
||||
}
|
||||
.source_code()
|
||||
.is_some());
|
||||
}
|
||||
|
|
|
|||
1237
tests/graphical.rs
1237
tests/graphical.rs
File diff suppressed because it is too large
Load Diff
|
|
@ -29,7 +29,7 @@ fn single_line_with_wide_char() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ fn single_line_highlight() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ fn single_line_highlight_offset_zero() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ fn single_line_highlight_with_empty_span() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -172,7 +172,7 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -208,7 +208,7 @@ fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -244,7 +244,7 @@ fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "x"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "y"]
|
||||
|
|
@ -288,7 +288,7 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "these two lines"]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -325,7 +325,7 @@ fn multiline_highlight_flyby() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "block 1"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "block 2"]
|
||||
|
|
@ -378,7 +378,7 @@ fn multiline_highlight_no_label() -> Result<(), MietteError> {
|
|||
#[source]
|
||||
source: Inner,
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "block 1"]
|
||||
highlight1: SourceSpan,
|
||||
#[label]
|
||||
|
|
@ -444,7 +444,7 @@ fn multiple_multiline_highlights_adjacent() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "this bit here"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
|
|
@ -492,7 +492,7 @@ fn multiple_multiline_highlights_overlapping_lines() -> Result<(), MietteError>
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "this bit here"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
|
|
@ -520,7 +520,7 @@ fn multiple_multiline_highlights_overlapping_offsets() -> Result<(), MietteError
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "this bit here"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
|
|
@ -559,7 +559,7 @@ fn related() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[related]
|
||||
|
|
@ -614,7 +614,7 @@ fn related_source_code_propagation() -> Result<(), MietteError> {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[related]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ fn enum_uses_base_attr() {
|
|||
enum MyBad {
|
||||
Only {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
},
|
||||
|
|
@ -32,7 +32,7 @@ fn enum_uses_variant_attr() {
|
|||
#[diagnostic(code(error::on::variant))]
|
||||
Only {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
},
|
||||
|
|
@ -55,7 +55,7 @@ fn multiple_attrs_allowed_on_item() {
|
|||
enum MyBad {
|
||||
Only {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
},
|
||||
|
|
@ -79,7 +79,7 @@ fn multiple_attrs_allowed_on_variant() {
|
|||
#[diagnostic(help("try doing it correctly"))]
|
||||
Only {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
},
|
||||
|
|
@ -104,7 +104,7 @@ fn attrs_can_be_split_between_item_and_variants() {
|
|||
#[diagnostic(url("https://example.com/foo/bar"))]
|
||||
Only {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
},
|
||||
|
|
@ -130,7 +130,7 @@ fn attr_not_required() {
|
|||
enum MyBad {
|
||||
Only {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,306 @@
|
|||
use std::{
|
||||
collections::{LinkedList, VecDeque},
|
||||
ops::Range,
|
||||
};
|
||||
|
||||
// Testing of the `diagnostic` attr used by derive(Diagnostic)
|
||||
use miette::{Diagnostic, LabeledSpan, NamedSource, SourceSpan};
|
||||
use thiserror::Error;
|
||||
|
||||
#[test]
|
||||
fn attr_collection_in_enum() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
enum MyBad {
|
||||
Only {
|
||||
#[source_code]
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[label(collection, "and here")]
|
||||
highlight2: Vec<SourceSpan>,
|
||||
},
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad::Only {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).into(),
|
||||
highlight2: vec![(1, 2).into(), (3, 4).into()],
|
||||
};
|
||||
let mut label_iter = err.labels().unwrap();
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 1usize, 2usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_collection_in_struct() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[label(collection, "and here")]
|
||||
highlight2: Vec<SourceSpan>,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).into(),
|
||||
highlight2: vec![(1, 2).into(), (3, 4).into()],
|
||||
};
|
||||
let mut label_iter = err.labels().unwrap();
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 1usize, 2usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_collection_as_deque() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[label(collection, "and here")]
|
||||
highlight2: VecDeque<SourceSpan>,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).into(),
|
||||
highlight2: VecDeque::from([(1, 2).into(), (3, 4).into()]),
|
||||
};
|
||||
let mut label_iter = err.labels().unwrap();
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 1usize, 2usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_collection_as_linked_list() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[label(collection, "and here")]
|
||||
highlight2: LinkedList<SourceSpan>,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).into(),
|
||||
highlight2: LinkedList::from([(1, 2).into(), (3, 4).into()]),
|
||||
};
|
||||
let mut label_iter = err.labels().unwrap();
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 1usize, 2usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_collection_of_tuple() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[label(collection, "and here")]
|
||||
highlight2: Vec<(usize, usize)>,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).into(),
|
||||
highlight2: vec![(1, 2), (3, 4)],
|
||||
};
|
||||
let mut label_iter = err.labels().unwrap();
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 1usize, 2usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_collection_of_range() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[label(collection, "and here")]
|
||||
highlight2: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).into(),
|
||||
highlight2: vec![1..3, 3..7],
|
||||
};
|
||||
let mut label_iter = err.labels().unwrap();
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 1usize, 2usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_collection_of_labeled_span_in_struct() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[label(collection, "then there")]
|
||||
highlight2: Vec<LabeledSpan>,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).into(),
|
||||
highlight2: vec![
|
||||
LabeledSpan::new_with_span(Some("continuing here".to_string()), (1, 2)),
|
||||
LabeledSpan::new_with_span(None, (3, 4)),
|
||||
],
|
||||
};
|
||||
let mut label_iter = err.labels().unwrap();
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("continuing here".into()), 1usize, 2usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("then there".into()), 3usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_collection_of_labeled_span_in_enum() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
enum MyBad {
|
||||
Only {
|
||||
#[source_code]
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[label(collection, "then there")]
|
||||
highlight2: Vec<LabeledSpan>,
|
||||
},
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad::Only {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).into(),
|
||||
highlight2: vec![
|
||||
LabeledSpan::new_with_span(Some("continuing here".to_string()), (1, 2)),
|
||||
LabeledSpan::new_with_span(None, (3, 4)),
|
||||
],
|
||||
};
|
||||
let mut label_iter = err.labels().unwrap();
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("continuing here".into()), 1usize, 2usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("then there".into()), 3usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_collection_multi() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[label(collection, "and here")]
|
||||
highlight2: Vec<SourceSpan>,
|
||||
#[label(collection, "and there")]
|
||||
highlight3: Vec<SourceSpan>,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).into(),
|
||||
highlight2: vec![(1, 2).into(), (3, 4).into()],
|
||||
highlight3: vec![(5, 6).into(), (7, 8).into()],
|
||||
};
|
||||
let mut label_iter = err.labels().unwrap();
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 1usize, 2usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and there".into()), 5usize, 6usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
let err_span = label_iter.next().unwrap();
|
||||
let expectation = LabeledSpan::new(Some("and there".into()), 7usize, 8usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use miette::{miette, Diagnostic};
|
||||
use thiserror::Error;
|
||||
|
||||
#[test]
|
||||
fn test_source() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Bar")]
|
||||
struct Bar;
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Foo")]
|
||||
struct Foo {
|
||||
#[source]
|
||||
bar: Bar,
|
||||
}
|
||||
|
||||
let e = miette!(Foo { bar: Bar });
|
||||
let mut chain = e.chain();
|
||||
|
||||
assert_eq!("Foo", chain.next().unwrap().to_string());
|
||||
assert_eq!("Bar", chain.next().unwrap().to_string());
|
||||
assert!(chain.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_source_boxed() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Bar")]
|
||||
struct Bar;
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Foo")]
|
||||
struct Foo {
|
||||
#[source]
|
||||
bar: Box<dyn Diagnostic + Send + Sync>,
|
||||
}
|
||||
|
||||
let error = miette!(Foo { bar: Box::new(Bar) });
|
||||
|
||||
let mut chain = error.chain();
|
||||
|
||||
assert_eq!("Foo", chain.next().unwrap().to_string());
|
||||
assert_eq!("Bar", chain.next().unwrap().to_string());
|
||||
assert!(chain.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_source_arc() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Bar")]
|
||||
struct Bar;
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Foo")]
|
||||
struct Foo {
|
||||
#[source]
|
||||
bar: Arc<dyn Diagnostic + Send + Sync>,
|
||||
}
|
||||
|
||||
let error = miette!(Foo { bar: Arc::new(Bar) });
|
||||
|
||||
let mut chain = error.chain();
|
||||
|
||||
assert_eq!("Foo", chain.next().unwrap().to_string());
|
||||
assert_eq!("Bar", chain.next().unwrap().to_string());
|
||||
assert!(chain.next().is_none());
|
||||
}
|
||||
|
|
@ -41,6 +41,14 @@ struct TestTupleError(#[diagnostic_source] AnErr);
|
|||
#[error("TestError")]
|
||||
struct TestBoxedError(#[diagnostic_source] Box<dyn Diagnostic>);
|
||||
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("TestError")]
|
||||
struct TestBoxedSendError(#[diagnostic_source] Box<dyn Diagnostic + Send>);
|
||||
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("TestError")]
|
||||
struct TestBoxedSendSyncError(#[diagnostic_source] Box<dyn Diagnostic + Send + Sync>);
|
||||
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("TestError")]
|
||||
struct TestArcedError(#[diagnostic_source] std::sync::Arc<dyn Diagnostic>);
|
||||
|
|
@ -71,15 +79,22 @@ fn test_diagnostic_source() {
|
|||
let error = TestBoxedError(Box::new(AnErr));
|
||||
assert!(error.diagnostic_source().is_some());
|
||||
|
||||
let error = TestBoxedSendError(Box::new(AnErr));
|
||||
assert!(error.diagnostic_source().is_some());
|
||||
|
||||
let error = TestBoxedSendSyncError(Box::new(AnErr));
|
||||
assert!(error.diagnostic_source().is_some());
|
||||
|
||||
let error = TestArcedError(std::sync::Arc::new(AnErr));
|
||||
assert!(error.diagnostic_source().is_some());
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[test]
|
||||
fn test_diagnostic_source_pass_extra_info() {
|
||||
let diag = TestBoxedError(Box::new(SourceError {
|
||||
code: String::from("Hello\nWorld!"),
|
||||
help: format!("Have you tried turning it on and off again?"),
|
||||
help: String::from("Have you tried turning it on and off again?"),
|
||||
label: (1, 4),
|
||||
}));
|
||||
let mut out = String::new();
|
||||
|
|
@ -89,9 +104,10 @@ 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:1]
|
||||
╭─[1:2]
|
||||
1 │ Hello
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
|
|
@ -106,6 +122,7 @@ fn test_diagnostic_source_pass_extra_info() {
|
|||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[test]
|
||||
fn test_diagnostic_source_is_output() {
|
||||
let diag = TestStructError {
|
||||
|
|
@ -122,7 +139,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,6 +165,7 @@ struct NestedError {
|
|||
the_other_err: Box<dyn Diagnostic>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[test]
|
||||
fn test_nested_diagnostic_source_is_output() {
|
||||
let inner_error = TestStructError {
|
||||
|
|
@ -169,7 +188,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
|
||||
|
|
@ -191,3 +211,89 @@ fn test_nested_diagnostic_source_is_output() {
|
|||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("A multi-error happened")]
|
||||
struct MultiError {
|
||||
#[related]
|
||||
related_errs: Vec<Box<dyn Diagnostic>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[test]
|
||||
fn test_nested_cause_chains_for_related_errors_are_output() {
|
||||
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 multi_error = MultiError {
|
||||
related_errs: vec![Box::new(first_error), Box::new(second_error)],
|
||||
};
|
||||
let diag = NestedError {
|
||||
code: String::from("the outside world"),
|
||||
label: (6, 4),
|
||||
the_other_err: Box::new(multi_error),
|
||||
};
|
||||
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 nested error happened
|
||||
╰─▶ × A multi-error happened
|
||||
|
||||
Error:
|
||||
× A nested error happened
|
||||
├─▶ × TestError
|
||||
│
|
||||
╰─▶ × A complex error happened
|
||||
╭────
|
||||
1 │ This is another error
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
help: You should fix this
|
||||
|
||||
╭────
|
||||
1 │ right here
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
|
||||
Error:
|
||||
× A complex error happened
|
||||
╭────
|
||||
1 │ You're actually a mess
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
help: Get a grip...
|
||||
|
||||
╭────
|
||||
1 │ the outside world
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
|
||||
Yooo, a footer
|
||||
"#;
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -52,7 +52,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -66,7 +65,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -98,7 +97,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -112,7 +110,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -144,7 +142,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -158,7 +155,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -190,7 +187,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -204,7 +200,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -235,7 +231,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -249,7 +244,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -281,7 +276,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -295,7 +289,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "x"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "y"]
|
||||
|
|
@ -347,7 +341,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -361,7 +354,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "these two lines"]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
|
@ -393,7 +386,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -407,7 +399,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "block 1"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "block 2"]
|
||||
|
|
@ -456,7 +448,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -472,7 +463,7 @@ mod json_report_handler {
|
|||
#[source]
|
||||
source: Inner,
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "block 1"]
|
||||
highlight1: SourceSpan,
|
||||
#[label]
|
||||
|
|
@ -532,7 +523,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -546,7 +536,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "this bit here"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
|
|
@ -588,7 +578,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -602,7 +591,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "this bit here"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
|
|
@ -644,7 +633,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -658,7 +646,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label = "this bit here"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
|
|
@ -700,7 +688,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -728,7 +715,6 @@ mod json_report_handler {
|
|||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -742,7 +728,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[related]
|
||||
|
|
@ -822,7 +808,6 @@ mod json_report_handler {
|
|||
}]
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
@ -836,7 +821,7 @@ mod json_report_handler {
|
|||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
src: NamedSource<String>,
|
||||
#[label("this bit here")]
|
||||
highlight: SourceSpan,
|
||||
#[related]
|
||||
|
|
@ -920,7 +905,6 @@ mod json_report_handler {
|
|||
}]
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ mod drop;
|
|||
|
||||
use self::drop::{DetectDrop, Flag};
|
||||
use miette::Report;
|
||||
use std::marker::Unpin;
|
||||
use std::mem;
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in New Issue