mirror of https://github.com/zkat/miette.git
Compare commits
77 Commits
miette-der
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
e853bbf9bc | |
|
|
b466948965 | |
|
|
df7bcfa17d | |
|
|
2b79495c79 | |
|
|
7a4d759c59 | |
|
|
fea8043b3e | |
|
|
51ca022b1f | |
|
|
907857058d | |
|
|
34b8774b36 | |
|
|
521ef91f77 | |
|
|
59c81617de | |
|
|
58d9f12411 | |
|
|
f2ef693d1c | |
|
|
a93afcf7e3 | |
|
|
9ba6fad769 | |
|
|
b8c144f2a6 | |
|
|
429ca37d8d | |
|
|
b4c983a393 | |
|
|
1e1938a099 | |
|
|
771a07519f | |
|
|
c7eeada1e0 | |
|
|
b2011f693b | |
|
|
0c46c0b4d4 | |
|
|
7fae60fd84 | |
|
|
465e6b6ab6 | |
|
|
68d47fa8b5 | |
|
|
01564e070f | |
|
|
fe068f6fc1 | |
|
|
2902a2337c | |
|
|
5f441d0115 | |
|
|
f3fb4c1ecd | |
|
|
d60c8f10f1 | |
|
|
215f9aae33 | |
|
|
789a04e30d | |
|
|
21e9a70ad8 | |
|
|
93d3bd118a | |
|
|
d6b4558502 | |
|
|
15beec4330 | |
|
|
bf5aa3742f | |
|
|
bdd1d74614 | |
|
|
edfdcb525e | |
|
|
9bbcf3c601 | |
|
|
73da45b65c | |
|
|
b8dfcda4a8 | |
|
|
e1026f75e0 | |
|
|
813232ba79 | |
|
|
b82cc81b8e | |
|
|
ea4296dace | |
|
|
97766e8d90 | |
|
|
7b42b12c5f | |
|
|
3eabbcebf1 | |
|
|
ca646f3119 | |
|
|
ff7baae70c | |
|
|
24a7bf4f4e | |
|
|
22b29eec38 | |
|
|
62cfd221ba | |
|
|
328bf37922 | |
|
|
6ea86a2248 | |
|
|
7d9dfc6e8e | |
|
|
75fea0935e | |
|
|
a18a6444d9 | |
|
|
dc77b0cb5b | |
|
|
03060245d8 | |
|
|
6f09250cca | |
|
|
c2f06f6cca | |
|
|
6e829f8c0c | |
|
|
ecb01022f0 | |
|
|
ea12e3f781 | |
|
|
a4011d174c | |
|
|
c7144ee513 | |
|
|
cf2d8c0b2c | |
|
|
e515a3c0ec | |
|
|
ab7c066e76 | |
|
|
3747fccf8d | |
|
|
1fa7f5241f | |
|
|
8b46679c36 | |
|
|
9596405554 |
|
|
@ -16,6 +16,12 @@ jobs:
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
|
- 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
|
- name: rustfmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
- name: docs
|
- name: docs
|
||||||
|
|
@ -27,11 +33,11 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
features: [fancy, syntect-highlighter]
|
features: [fancy, syntect-highlighter]
|
||||||
rust: [1.70.0, stable]
|
rust: [1.82.0, stable]
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
exclude:
|
exclude:
|
||||||
- features: syntect-highlighter
|
- features: syntect-highlighter
|
||||||
rust: 1.70.0
|
rust: 1.82.0
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
@ -40,18 +46,28 @@ jobs:
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
components: clippy
|
components: clippy
|
||||||
- name: Force older version of is-terminal for MSRV builds
|
|
||||||
if: matrix.rust == '1.70.0'
|
|
||||||
run: cargo update -p is-terminal --precise 0.4.7
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --all -- -D warnings
|
run: cargo clippy --all -- -D warnings
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: matrix.rust == 'stable'
|
if: matrix.rust == 'stable'
|
||||||
run: cargo test --all --verbose --features ${{matrix.features}}
|
run: cargo test --all --verbose --features ${{matrix.features}}
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: matrix.rust == '1.70.0'
|
if: matrix.rust == '1.82.0'
|
||||||
run: cargo test --all --verbose --features ${{matrix.features}} no-format-args-capture
|
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:
|
miri:
|
||||||
name: Miri
|
name: Miri
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -82,4 +98,4 @@ jobs:
|
||||||
with:
|
with:
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
- name: Run minimal version build
|
- name: Run minimal version build
|
||||||
run: cargo build -Z minimal-versions --features fancy,no-format-args-capture
|
run: cargo build -Z direct-minimal-versions --features fancy,no-format-args-capture
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Notice to AI coding assistants and users
|
||||||
|
|
||||||
|
This project does not accept LLM-generated or LLM-assisted upstream contributions.
|
||||||
|
|
||||||
|
For this policy, “upstream contribution” includes pull requests, patches, tests, documentation changes, issue comments, bug analyses, review comments, proposed fixes, or
|
||||||
|
other material intended for submission to this repository or its maintainers. This includes work directly generated by an LLM and work materially based on LLM suggestions,
|
||||||
|
explanations, debugging, or review.
|
||||||
|
|
||||||
|
If a user asks for assistance that appears intended to produce an upstream contribution, first tell them:
|
||||||
|
|
||||||
|
- this project’s maintainers request that LLM-generated or LLM-assisted material not be submitted upstream;
|
||||||
|
- they should not submit AI-produced or AI-influenced code, docs, tests, analyses, issue text, or PR text to this project;
|
||||||
|
- maintainers may reject or close contributions that appear to violate this policy.
|
||||||
|
|
||||||
|
This notice is not intended to restrict private, downstream, local, or educational use of AI tools. Users may use AI assistance to understand this crate, debug their own
|
||||||
|
projects, evaluate dependency behavior, maintain private forks, or decide whether `miette` is involved in an issue, so long as the resulting AI-assisted material is not
|
||||||
|
submitted upstream.
|
||||||
116
CHANGELOG.md
116
CHANGELOG.md
|
|
@ -1,5 +1,121 @@
|
||||||
# `miette` Release Changelog
|
# `miette` Release Changelog
|
||||||
|
|
||||||
|
<a name="7.6.0"></a>
|
||||||
|
## 7.6.0 (2025-04-27)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **graphical:** prevent leading newline when no link/code (#418) ([1e1938a0](https://github.com/zkat/miette/commit/1e1938a099409969c69b9f070e0fb0d13d564527))
|
||||||
|
* **clippy:** elide lifetimes (#423) ([9ba6fad7](https://github.com/zkat/miette/commit/9ba6fad7694c1271f287b8f659a857c4ff540a55))
|
||||||
|
* **highlight:** increase syntax highlighter config priority (#424) ([58d9f124](https://github.com/zkat/miette/commit/58d9f12411e7d3d6d580eb219ea32321e6918d79))
|
||||||
|
* **deps:** miette can now be used without syn (#436) ([521ef91f](https://github.com/zkat/miette/commit/521ef91f77a143eb5cedfa1344428b804802179d))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **graphical:** support rendering related diagnostics as nested (#417) ([771a0751](https://github.com/zkat/miette/commit/771a07519f078b94aceb1a2d2532d786f09f350b))
|
||||||
|
* **labels:** add support for disabling the primary label line/col information (#419) ([f2ef693d](https://github.com/zkat/miette/commit/f2ef693d1ce7230e6b9f12805f018f095534b441))
|
||||||
|
* **deps:** update `thiserror` from 1.0.56 to 2.0.11 (#426) ([59c81617](https://github.com/zkat/miette/commit/59c81617de8650a6ff3b193b41b4297e560726a0))
|
||||||
|
|
||||||
|
<a name="7.5.0"></a>
|
||||||
|
## 7.5.0 (2025-02-01)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **graphical:** support rendering related diagnostics as nested (#417) ([771a0751](https://github.com/zkat/miette/commit/771a07519f078b94aceb1a2d2532d786f09f350b))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **graphical:** prevent leading newline when no link/code (#418) ([1e1938a0](https://github.com/zkat/miette/commit/1e1938a099409969c69b9f070e0fb0d13d564527))
|
||||||
|
|
||||||
|
<a name="7.4.0"></a>
|
||||||
|
## 7.4.0 (2024-11-27)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **graphical:** Inherit source code to causes (#401) ([465e6b6a](https://github.com/zkat/miette/commit/465e6b6ab627f8da34baa5f46441d944fb88186e))
|
||||||
|
* **report:** Implement `WrapError` for `Option` (#409) ([7fae60fd](https://github.com/zkat/miette/commit/7fae60fd8462f95cf3140c6a3b9eb06cb7953405))
|
||||||
|
|
||||||
|
<a name="7.3.0"></a>
|
||||||
|
## 7.3.0 (2024-11-26)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **SourceSpan:** add impl From<InclusiveRange> (#385) ([73da45b6](https://github.com/zkat/miette/commit/73da45b65c965777a00ba64aa03a247c0e5241ca))
|
||||||
|
* **Report:** add `from_err()` method to `Report` (#403) ([93d3bd11](https://github.com/zkat/miette/commit/93d3bd118a072c35aa761f0ec74317166ec08113))
|
||||||
|
* **Diagnostic:** Implement `Diagnostic` for `Infallible` (#402) ([f3fb4c1e](https://github.com/zkat/miette/commit/f3fb4c1ecd196ce389cbd71139bb7e3b35474add))
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
* **handlers:** optimize string-buffer reallocations (#387) ([b8dfcda4](https://github.com/zkat/miette/commit/b8dfcda4a8c10a14116ee275250356ac991dc7be))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **graphical:** fix nested error wrapping (#358) ([3eabbceb](https://github.com/zkat/miette/commit/3eabbcebf113d1d620a6a3f98e8a455414ed3042))
|
||||||
|
* **docs:** updated example image (fixes #111) (#270) ([7b42b12c](https://github.com/zkat/miette/commit/7b42b12c5f6316322ce79c59bcb9e99f5d49edb8))
|
||||||
|
* **clippy:** Fix clippy lints in docs (#365) ([ea4296da](https://github.com/zkat/miette/commit/ea4296dacec3b0e4762281d9d115c1bd69ecfac3))
|
||||||
|
* **docs:** `alt` attribut for `single-line-example.png` (#372) ([b82cc81b](https://github.com/zkat/miette/commit/b82cc81b8ea32a1cf1b4598ed5832bc8e3b0e161))
|
||||||
|
* **color:** setting NO_COLOR should not print ansi codes for non-terminals (#381) ([813232ba](https://github.com/zkat/miette/commit/813232ba7957ae09e4fb9d9416d821f4fd9da66d))
|
||||||
|
* **clippy:** fix Rust v1.78.0 clippy warnings (#382) ([e1026f75](https://github.com/zkat/miette/commit/e1026f75e0a5d19bbc8e468cb3f5292074543a97))
|
||||||
|
* **perf:** mark error constructors cold (#378) ([9bbcf3c6](https://github.com/zkat/miette/commit/9bbcf3c6017fa3455a7db714879816c1cfc511fd))
|
||||||
|
* **handlers:** Disable textwrap::smawk feature (#379) ([edfdcb52](https://github.com/zkat/miette/commit/edfdcb525ee30fc54747460ada621f13f0ed1996))
|
||||||
|
* **graphical:** Format entire link instead of just name (#389) ([bf5aa374](https://github.com/zkat/miette/commit/bf5aa3742fd664be3c93160b9c28c145b1ed8bc9))
|
||||||
|
* **clippy:** fix `clippy::doc_lazy_continuation` lints (#395) ([15beec43](https://github.com/zkat/miette/commit/15beec43303180b811d4c04d1a78775feb9b0905))
|
||||||
|
* **graphical:** Handle invalid UTF-8 in source code (#393) ([d6b45585](https://github.com/zkat/miette/commit/d6b4558502e82fa74e030ccb3c8040656590d7eb))
|
||||||
|
* **features:** Use `dep:` syntax for dependencies in features. (#394) ([789a04e3](https://github.com/zkat/miette/commit/789a04e30d041179b373b4eb8b340456534a3f0e))
|
||||||
|
* **clippy:** Fix `needless_return` lint. (#405) ([5f441d01](https://github.com/zkat/miette/commit/5f441d011560a091fe5d6a6cdb05f09acf622d36))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* **examples:** add serde_json integration example (#407) ([2902a233](https://github.com/zkat/miette/commit/2902a2337c2e36a5d8e0e54b007d6100cca0c9ff))
|
||||||
|
|
||||||
|
<a name="7.2.0"></a>
|
||||||
|
## 7.2.0 (2024-03-07)
|
||||||
|
|
||||||
|
### 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>
|
<a name="6.0.0"></a>
|
||||||
## 6.0.0 (2024-02-04)
|
## 6.0.0 (2024-02-04)
|
||||||
|
|
||||||
|
|
|
||||||
65
Cargo.toml
65
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "miette"
|
name = "miette"
|
||||||
version = "6.0.0"
|
version = "7.6.0"
|
||||||
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
||||||
description = "Fancy diagnostic reporting library and protocol for us mere mortals who aren't compiler hackers."
|
description = "Fancy diagnostic reporting library and protocol for us mere mortals who aren't compiler hackers."
|
||||||
categories = ["rust-patterns"]
|
categories = ["rust-patterns"]
|
||||||
|
|
@ -9,57 +9,68 @@ documentation = "https://docs.rs/miette"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
rust-version = "1.70.0"
|
rust-version = "1.82.0"
|
||||||
exclude = ["images/", "tests/", "miette-derive/"]
|
exclude = ["images/", "tests/", "miette-derive/"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0.40"
|
miette-derive = { path = "miette-derive", version = "=7.6.0", optional = true }
|
||||||
miette-derive = { path = "miette-derive", version = "=6.0.0", optional = true }
|
unicode-width = "0.2.0"
|
||||||
unicode-width = "0.1.9"
|
cfg-if = "1.0.0"
|
||||||
|
|
||||||
owo-colors = { version = "3.4.0", optional = true }
|
owo-colors = { version = "4.0.0", optional = true }
|
||||||
textwrap = { version = "0.16.0", optional = true }
|
textwrap = { version = "0.16.0", default-features = false, features = ["unicode-linebreak", "unicode-width"], optional = true }
|
||||||
supports-hyperlinks = { version = "3.0.0", optional = true }
|
supports-hyperlinks = { version = "3.0.0", optional = true }
|
||||||
supports-color = { version = "3.0.0", optional = true }
|
supports-color = { version = "3.0.0", optional = true }
|
||||||
supports-unicode = { version = "3.0.0", optional = true }
|
supports-unicode = { version = "3.0.0", optional = true }
|
||||||
backtrace = { version = "0.3.61", optional = true }
|
backtrace = { version = "0.3.69", optional = true }
|
||||||
terminal_size = { version = "0.3.0", optional = true }
|
terminal_size = { version = "0.4.0", optional = true }
|
||||||
backtrace-ext = { version = "0.2.1", optional = true }
|
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 }
|
syntect = { version = "5.1.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
semver = "1.0.4"
|
thiserror = "2.0.11"
|
||||||
|
semver = "1.0.21"
|
||||||
|
|
||||||
# Eyre devdeps
|
# Eyre devdeps
|
||||||
futures = { version = "0.3", default-features = false }
|
futures = { version = "0.3", default-features = false }
|
||||||
indenter = "0.3.0"
|
indenter = "0.3.3"
|
||||||
rustversion = "1.0"
|
rustversion = "1.0"
|
||||||
trybuild = { version = "1.0.19", features = ["diff"] }
|
trybuild = { version = "1.0.89", features = ["diff"] }
|
||||||
syn = { version = "2.0", features = ["full"] }
|
syn = { version = "2.0.87", features = ["full"] }
|
||||||
regex = "1.5"
|
regex = "1.10"
|
||||||
lazy_static = "1.4"
|
|
||||||
|
|
||||||
serde_json = "1.0.64"
|
serde = { version = "1.0.196", features = ["derive"] }
|
||||||
|
serde_json = "1.0.113"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["derive"]
|
default = ["derive"]
|
||||||
derive = ["miette-derive"]
|
derive = ["dep:miette-derive"]
|
||||||
no-format-args-capture = []
|
no-format-args-capture = []
|
||||||
fancy-no-backtrace = [
|
fancy-base = [
|
||||||
"owo-colors",
|
"dep:owo-colors",
|
||||||
"textwrap",
|
"dep:textwrap",
|
||||||
"terminal_size",
|
|
||||||
"supports-hyperlinks",
|
|
||||||
"supports-color",
|
|
||||||
"supports-unicode",
|
|
||||||
]
|
]
|
||||||
fancy = ["fancy-no-backtrace", "backtrace", "backtrace-ext"]
|
fancy-no-syscall = [
|
||||||
syntect-highlighter = ["fancy-no-backtrace", "syntect"]
|
"fancy-base",
|
||||||
|
]
|
||||||
|
fancy-no-backtrace = [
|
||||||
|
"fancy-base",
|
||||||
|
"dep:terminal_size",
|
||||||
|
"dep:supports-hyperlinks",
|
||||||
|
"dep:supports-color",
|
||||||
|
"dep:supports-unicode",
|
||||||
|
]
|
||||||
|
fancy = ["fancy-no-backtrace", "dep:backtrace", "dep:backtrace-ext"]
|
||||||
|
syntect-highlighter = ["fancy-no-backtrace", "dep:syntect"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["miette-derive"]
|
members = ["miette-derive"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "serde_json"
|
||||||
|
required-features = ["fancy"]
|
||||||
|
|
|
||||||
178
README.md
178
README.md
|
|
@ -28,9 +28,9 @@ diagnostic error code: ruget::api::bad_json
|
||||||
" />
|
" />
|
||||||
|
|
||||||
> **NOTE: You must enable the `"fancy"` crate feature to get fancy report
|
> **NOTE: You must enable the `"fancy"` crate feature to get fancy report
|
||||||
output like in the screenshots above.** You should only do this in your
|
> 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
|
> toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||||
libraries and such might not want.
|
> libraries and such might not want.
|
||||||
|
|
||||||
### Table of Contents <!-- omit in toc -->
|
### Table of Contents <!-- omit in toc -->
|
||||||
|
|
||||||
|
|
@ -44,11 +44,15 @@ libraries and such might not want.
|
||||||
- [... in `main()`](#-in-main)
|
- [... in `main()`](#-in-main)
|
||||||
- [... diagnostic code URLs](#-diagnostic-code-urls)
|
- [... diagnostic code URLs](#-diagnostic-code-urls)
|
||||||
- [... snippets](#-snippets)
|
- [... snippets](#-snippets)
|
||||||
|
- [... help text](#-help-text)
|
||||||
|
- [... severity level](#-severity-level)
|
||||||
- [... multiple related errors](#-multiple-related-errors)
|
- [... multiple related errors](#-multiple-related-errors)
|
||||||
- [... delayed source code](#-delayed-source-code)
|
- [... delayed source code](#-delayed-source-code)
|
||||||
- [... handler options](#-handler-options)
|
- [... handler options](#-handler-options)
|
||||||
- [... dynamic diagnostics](#-dynamic-diagnostics)
|
- [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||||
- [... syntax highlighting](#-syntax-highlighting)
|
- [... syntax highlighting](#-syntax-highlighting)
|
||||||
|
- [... primary label](#-primary-label)
|
||||||
|
- [... collection of labels](#-collection-of-labels)
|
||||||
- [Acknowledgements](#acknowledgements)
|
- [Acknowledgements](#acknowledgements)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
|
|
@ -97,7 +101,7 @@ You can derive a `Diagnostic` from any `std::error::Error` type.
|
||||||
|
|
||||||
`thiserror` is a great way to define them, and plays nicely with `miette`!
|
`thiserror` is a great way to define them, and plays nicely with `miette`!
|
||||||
*/
|
*/
|
||||||
use miette::{Diagnostic, SourceSpan};
|
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
|
@ -124,12 +128,11 @@ Use this `Result` type (or its expanded version) as the return type
|
||||||
throughout your app (but NOT your libraries! Those should always return
|
throughout your app (but NOT your libraries! Those should always return
|
||||||
concrete types!).
|
concrete types!).
|
||||||
*/
|
*/
|
||||||
use miette::{NamedSource, Result};
|
use miette::Result;
|
||||||
fn this_fails() -> Result<()> {
|
fn this_fails() -> Result<()> {
|
||||||
// You can use plain strings as a `Source`, or anything that implements
|
// You can use plain strings as a `Source`, or anything that implements
|
||||||
// the one-method `Source` trait.
|
// the one-method `Source` trait.
|
||||||
let src = "source\n text\n here".to_string();
|
let src = "source\n text\n here".to_string();
|
||||||
let len = src.len();
|
|
||||||
|
|
||||||
Err(MyBad {
|
Err(MyBad {
|
||||||
src: NamedSource::new("bad_file.rs", src),
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
|
@ -159,17 +162,20 @@ And this is the output you'll get if you run this program:
|
||||||
<img src="https://raw.githubusercontent.com/zkat/miette/main/images/single-line-example.png" alt="
|
<img src="https://raw.githubusercontent.com/zkat/miette/main/images/single-line-example.png" alt="
|
||||||
Narratable printout:
|
Narratable printout:
|
||||||
\
|
\
|
||||||
Error: Types mismatched for operation.
|
diagnostic error code: oops::my::bad (link)
|
||||||
Diagnostic severity: error
|
Error: oops!
|
||||||
Begin snippet starting at line 1, column 1
|
|
||||||
\
|
\
|
||||||
snippet line 1: 3 + "5"
|
Begin snippet for bad_file.rs starting
|
||||||
label starting at line 1, column 1: int
|
at line 2, column 3
|
||||||
label starting at line 1, column 1: doesn't support these values.
|
\
|
||||||
label starting at line 1, column 1: string
|
snippet line 1: source
|
||||||
diagnostic help: Change int or string to be the right types and try again.
|
\
|
||||||
diagnostic code: nu::parser::unsupported_operation
|
snippet line 2: text
|
||||||
For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
|
highlight starting at line 1, column 3: This bit here
|
||||||
|
\
|
||||||
|
snippet line 3: here
|
||||||
|
\
|
||||||
|
diagnostic help: try doing it better next time?">
|
||||||
|
|
||||||
### Using
|
### Using
|
||||||
|
|
||||||
|
|
@ -205,6 +211,17 @@ pub enum MyLibError {
|
||||||
// Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
// Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
||||||
#[diagnostic(transparent)]
|
#[diagnostic(transparent)]
|
||||||
AnotherError(#[from] AnotherError),
|
AnotherError(#[from] AnotherError),
|
||||||
|
|
||||||
|
/// Forward the diagnostic to a particular field.
|
||||||
|
#[error("other error")]
|
||||||
|
#[diagnostic(forward(the_actual_diagnostic))]
|
||||||
|
EvenMoreData {
|
||||||
|
unrelated_field_1: String,
|
||||||
|
unrelated_field_2: usize,
|
||||||
|
|
||||||
|
#[source]
|
||||||
|
the_actual_diagnostic: AnotherError,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Diagnostic, Debug)]
|
#[derive(Error, Diagnostic, Debug)]
|
||||||
|
|
@ -239,7 +256,7 @@ use miette::{IntoDiagnostic, Result};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
pub fn some_tool() -> Result<Version> {
|
pub fn some_tool() -> Result<Version> {
|
||||||
Ok("1.2.x".parse().into_diagnostic()?)
|
"1.2.x".parse().into_diagnostic()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -254,24 +271,24 @@ use miette::{IntoDiagnostic, Result, WrapErr};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
pub fn some_tool() -> Result<Version> {
|
pub fn some_tool() -> Result<Version> {
|
||||||
Ok("1.2.x"
|
"1.2.x"
|
||||||
.parse()
|
.parse()
|
||||||
.into_diagnostic()
|
.into_diagnostic()
|
||||||
.wrap_err("Parsing this tool's semver version failed.")?)
|
.wrap_err("Parsing this tool's semver version failed.")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To construct your own simple adhoc error use the [miette!] macro:
|
To construct your own simple adhoc error use the [`miette!`] macro:
|
||||||
```rust
|
```rust
|
||||||
// my_app/lib/my_internal_file.rs
|
// my_app/lib/my_internal_file.rs
|
||||||
use miette::{miette, IntoDiagnostic, Result, WrapErr};
|
use miette::{miette, Result};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
pub fn some_tool() -> Result<Version> {
|
pub fn some_tool() -> Result<Version> {
|
||||||
let version = "1.2.x";
|
let version = "1.2.x";
|
||||||
Ok(version
|
version
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|_| miette!("Invalid version {}", version))?)
|
.map_err(|_| miette!("Invalid version {}", version))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
There are also similar [bail!] and [ensure!] macros.
|
There are also similar [bail!] and [ensure!] macros.
|
||||||
|
|
@ -283,9 +300,9 @@ There are also similar [bail!] and [ensure!] macros.
|
||||||
automatically.
|
automatically.
|
||||||
|
|
||||||
> **NOTE:** You must enable the `"fancy"` crate feature to get fancy report
|
> **NOTE:** You must enable the `"fancy"` crate feature to get fancy report
|
||||||
output like in the screenshots here.** You should only do this in your
|
> output like in the screenshots here.** You should only do this in your
|
||||||
toplevel crate, as the fancy feature pulls in a number of dependencies that
|
> toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||||
libraries and such might not want.
|
> libraries and such might not want.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
|
|
@ -424,7 +441,7 @@ pub struct MyErrorType {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### ... help text
|
#### ... help text
|
||||||
`miette` provides two facilities for supplying help text for your errors:
|
`miette` provides two facilities for supplying help text for your errors:
|
||||||
|
|
||||||
The first is the `#[help()]` format attribute that applies to structs or
|
The first is the `#[help()]` format attribute that applies to structs or
|
||||||
|
|
@ -460,6 +477,19 @@ let err = Foo {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### ... 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
|
#### ... multiple related errors
|
||||||
|
|
||||||
`miette` supports collecting multiple errors into a single diagnostic, and
|
`miette` supports collecting multiple errors into a single diagnostic, and
|
||||||
|
|
@ -627,14 +657,14 @@ customize!
|
||||||
If you...
|
If you...
|
||||||
- ...don't know all the possible errors upfront
|
- ...don't know all the possible errors upfront
|
||||||
- ...need to serialize/deserialize errors
|
- ...need to serialize/deserialize errors
|
||||||
then you may want to use [`miette!`], [`diagnostic!`] macros or
|
then you may want to use [`miette!`], [`diagnostic!`] macros or
|
||||||
[`MietteDiagnostic`] directly to create diagnostic on the fly.
|
[`MietteDiagnostic`] directly to create diagnostic on the fly.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
||||||
let source = "2 + 2 * 2 = 8".to_string();
|
let source = "2 + 2 * 2 = 8".to_string();
|
||||||
let report = miette!(
|
let report = miette!(
|
||||||
labels = vec[
|
labels = vec![
|
||||||
LabeledSpan::at(12..13, "this should be 6"),
|
LabeledSpan::at(12..13, "this should be 6"),
|
||||||
],
|
],
|
||||||
help = "'*' has greater precedence than '+'",
|
help = "'*' has greater precedence than '+'",
|
||||||
|
|
@ -655,12 +685,12 @@ automatically use the [`syntect`] crate to highlight the `#[source_code]`
|
||||||
field of your [`Diagnostic`].
|
field of your [`Diagnostic`].
|
||||||
|
|
||||||
Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
|
Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
|
||||||
* [language()](SpanContents::language) - Provides the name of the language
|
* [`language()`](SpanContents::language) - Provides the name of the language
|
||||||
as a string. For example `"Rust"` will indicate Rust syntax highlighting.
|
as a string. For example `"Rust"` will indicate Rust syntax highlighting.
|
||||||
You can set the language of the [`SpanContents`] produced by a
|
You can set the language of the [`SpanContents`] produced by a
|
||||||
[`NamedSource`] via the [`with_language`](NamedSource::with_language)
|
[`NamedSource`] via the [`with_language`](NamedSource::with_language)
|
||||||
method.
|
method.
|
||||||
* [name()](SpanContents::name) - In the absence of an explicitly set
|
* [`name()`](SpanContents::name) - In the absence of an explicitly set
|
||||||
language, the name is assumed to contain a file name or file path.
|
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
|
The highlighter will check for a file extension at the end of the name and
|
||||||
try to guess the syntax from that.
|
try to guess the syntax from that.
|
||||||
|
|
@ -671,9 +701,91 @@ trait to [`MietteHandlerOpts`] by calling the
|
||||||
[`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
[`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
||||||
method. See the [`highlighters`] module docs for more details.
|
method. See the [`highlighters`] module docs for more details.
|
||||||
|
|
||||||
|
#### ... primary label
|
||||||
|
|
||||||
|
You can use the `primary` parameter to `label` to indicate that the label
|
||||||
|
is the primary label.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyError {
|
||||||
|
#[label(primary, "main issue")]
|
||||||
|
primary_span: SourceSpan,
|
||||||
|
|
||||||
|
#[label("other label")]
|
||||||
|
other_span: SourceSpan,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `primary` parameter can be used at most once:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyError {
|
||||||
|
#[label(primary, "main issue")]
|
||||||
|
primary_span: SourceSpan,
|
||||||
|
|
||||||
|
#[label(primary, "other label")] // Error: Cannot have more than one primary label.
|
||||||
|
other_span: SourceSpan,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ... collection of labels
|
||||||
|
|
||||||
|
When the number of labels is unknown, you can use a collection of `SourceSpan`
|
||||||
|
(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
|
### MSRV
|
||||||
|
|
||||||
This crate requires rustc 1.70.0 or later.
|
This crate requires rustc 1.82.0 or later.
|
||||||
|
|
||||||
### Acknowledgements
|
### Acknowledgements
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
msrv = "1.70.0"
|
msrv = "1.82.0"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
//! This example shows how to integrate miette with serde_json
|
||||||
|
//! so the decoding source will be annotated with the decoding error,
|
||||||
|
//! providing contextual information about the error.
|
||||||
|
|
||||||
|
use miette::{IntoDiagnostic, SourceOffset};
|
||||||
|
use serde_json::{self, json};
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct Library {
|
||||||
|
#[allow(unused)]
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||||
|
#[error("malformed json provided")]
|
||||||
|
struct SerdeError {
|
||||||
|
cause: serde_json::Error,
|
||||||
|
#[source_code]
|
||||||
|
input: String,
|
||||||
|
#[label("{cause}")]
|
||||||
|
location: SourceOffset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerdeError {
|
||||||
|
/// Takes the input and the `serde_json::Error` and returns a SerdeError
|
||||||
|
/// that can be rendered nicely with miette.
|
||||||
|
pub fn from_serde_error(input: impl Into<String>, cause: serde_json::Error) -> Self {
|
||||||
|
let input = input.into();
|
||||||
|
let location = SourceOffset::from_location(&input, cause.line(), cause.column());
|
||||||
|
Self {
|
||||||
|
cause,
|
||||||
|
input,
|
||||||
|
location,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> miette::Result<()> {
|
||||||
|
let input = serde_json::to_string_pretty(&json!({
|
||||||
|
"name": 123
|
||||||
|
}))
|
||||||
|
.into_diagnostic()?;
|
||||||
|
|
||||||
|
let _library: Library =
|
||||||
|
serde_json::from_str(&input).map_err(|cause| SerdeError::from_serde_error(input, cause))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 54 KiB |
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "miette-derive"
|
name = "miette-derive"
|
||||||
version = "6.0.0"
|
version = "7.6.0"
|
||||||
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
@ -11,6 +11,6 @@ repository = "https://github.com/zkat/miette"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1.0.60"
|
proc-macro2 = "1.0.83"
|
||||||
quote = "1.0"
|
quote = "1.0.35"
|
||||||
syn = "2.0.11"
|
syn = "2.0.87"
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ impl Forward {
|
||||||
Self::#variant { #field_name, .. } => #field_name.#method_call,
|
Self::#variant { #field_name, .. } => #field_name.#method_call,
|
||||||
},
|
},
|
||||||
Forward::Unnamed(index) => {
|
Forward::Unnamed(index) => {
|
||||||
let underscores: Vec<_> = core::iter::repeat(quote! { _, }).take(*index).collect();
|
let underscores: Vec<_> = std::iter::repeat_n(quote! { _, }, *index).collect();
|
||||||
let unnamed = format_ident!("unnamed");
|
let unnamed = format_ident!("unnamed");
|
||||||
quote! {
|
quote! {
|
||||||
Self::#variant ( #(#underscores)* #unnamed, .. ) => #unnamed.#method_call,
|
Self::#variant ( #(#underscores)* #unnamed, .. ) => #unnamed.#method_call,
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,23 @@ use crate::{
|
||||||
|
|
||||||
pub struct Labels(Vec<Label>);
|
pub struct Labels(Vec<Label>);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
enum LabelType {
|
||||||
|
Default,
|
||||||
|
Primary,
|
||||||
|
Collection,
|
||||||
|
}
|
||||||
|
|
||||||
struct Label {
|
struct Label {
|
||||||
label: Option<Display>,
|
label: Option<Display>,
|
||||||
ty: syn::Type,
|
ty: syn::Type,
|
||||||
span: syn::Member,
|
span: syn::Member,
|
||||||
primary: bool,
|
lbl_ty: LabelType,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LabelAttr {
|
struct LabelAttr {
|
||||||
label: Option<Display>,
|
label: Option<Display>,
|
||||||
primary: bool,
|
lbl_ty: LabelType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for LabelAttr {
|
impl Parse for LabelAttr {
|
||||||
|
|
@ -42,20 +49,24 @@ impl Parse for LabelAttr {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let la = input.lookahead1();
|
let la = input.lookahead1();
|
||||||
let (primary, label) = if la.peek(syn::token::Paren) {
|
let (lbl_ty, label) = if la.peek(syn::token::Paren) {
|
||||||
// #[label(primary?, "{}", x)]
|
// #[label(primary?, "{}", x)]
|
||||||
let content;
|
let content;
|
||||||
parenthesized!(content in input);
|
parenthesized!(content in input);
|
||||||
|
|
||||||
let primary = if content.peek(syn::Ident) {
|
let attr = match content.parse::<Option<syn::Ident>>()? {
|
||||||
let ident: syn::Ident = content.parse()?;
|
Some(ident) if ident == "primary" => {
|
||||||
if ident != "primary" {
|
let _ = content.parse::<Token![,]>();
|
||||||
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or the keyword `primary`."));
|
LabelType::Primary
|
||||||
}
|
}
|
||||||
let _ = content.parse::<Token![,]>();
|
Some(ident) if ident == "collection" => {
|
||||||
true
|
let _ = content.parse::<Token![,]>();
|
||||||
} else {
|
LabelType::Collection
|
||||||
false
|
}
|
||||||
|
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) {
|
if content.peek(syn::LitStr) {
|
||||||
|
|
@ -70,17 +81,17 @@ impl Parse for LabelAttr {
|
||||||
args,
|
args,
|
||||||
has_bonus_display: false,
|
has_bonus_display: false,
|
||||||
};
|
};
|
||||||
(primary, Some(display))
|
(attr, Some(display))
|
||||||
} else if !primary {
|
} 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 the keyword `primary`."));
|
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 {
|
} else {
|
||||||
(primary, None)
|
(attr, None)
|
||||||
}
|
}
|
||||||
} else if la.peek(Token![=]) {
|
} else if la.peek(Token![=]) {
|
||||||
// #[label = "blabla"]
|
// #[label = "blabla"]
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
(
|
(
|
||||||
false,
|
LabelType::Default,
|
||||||
Some(Display {
|
Some(Display {
|
||||||
fmt: input.parse()?,
|
fmt: input.parse()?,
|
||||||
args: TokenStream::new(),
|
args: TokenStream::new(),
|
||||||
|
|
@ -88,9 +99,9 @@ impl Parse for LabelAttr {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(false, None)
|
(LabelType::Default, None)
|
||||||
};
|
};
|
||||||
Ok(LabelAttr { label, primary })
|
Ok(LabelAttr { label, lbl_ty })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,10 +130,14 @@ impl Labels {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
let LabelAttr { label, primary } =
|
let LabelAttr { label, lbl_ty } =
|
||||||
syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
|
syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
|
||||||
|
|
||||||
if primary && labels.iter().any(|l: &Label| l.primary) {
|
if lbl_ty == LabelType::Primary
|
||||||
|
&& labels
|
||||||
|
.iter()
|
||||||
|
.any(|l: &Label| l.lbl_ty == LabelType::Primary)
|
||||||
|
{
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
field.span(),
|
field.span(),
|
||||||
"Cannot have more than one primary label.",
|
"Cannot have more than one primary label.",
|
||||||
|
|
@ -133,7 +148,7 @@ impl Labels {
|
||||||
label,
|
label,
|
||||||
span,
|
span,
|
||||||
ty: field.ty.clone(),
|
ty: field.ty.clone(),
|
||||||
primary,
|
lbl_ty,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -147,46 +162,81 @@ impl Labels {
|
||||||
|
|
||||||
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
let labels = self.0.iter().map(|highlight| {
|
let labels = self.0.iter().filter_map(|highlight| {
|
||||||
let Label {
|
let Label {
|
||||||
span,
|
span,
|
||||||
label,
|
label,
|
||||||
ty,
|
ty,
|
||||||
primary,
|
lbl_ty,
|
||||||
} = highlight;
|
} = highlight;
|
||||||
|
if *lbl_ty == LabelType::Collection {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
let ctor = if *primary {
|
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 }
|
quote! { miette::LabeledSpan::new_primary_with_span }
|
||||||
} else {
|
} else {
|
||||||
quote! { miette::LabeledSpan::new_with_span }
|
quote! { miette::LabeledSpan::new_with_span }
|
||||||
};
|
};
|
||||||
if let Some(display) = label {
|
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
Some(quote! {
|
||||||
quote! {
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
.map(|#var| #ctor(
|
||||||
.map(|#var| #ctor(
|
#display,
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
#var.clone(),
|
||||||
#var.clone(),
|
))
|
||||||
))
|
})
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
|
||||||
.map(|#var| #ctor(
|
|
||||||
std::option::Option::None,
|
|
||||||
#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! {
|
Some(quote! {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
|
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||||
use miette::macro_helpers::ToOption;
|
use miette::macro_helpers::ToOption;
|
||||||
let Self #display_pat = self;
|
let Self #display_pat = self;
|
||||||
std::option::Option::Some(Box::new(vec![
|
|
||||||
|
let labels_iter = vec![
|
||||||
#(#labels),*
|
#(#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)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -198,8 +248,11 @@ impl Labels {
|
||||||
|ident, fields, DiagnosticConcreteArgs { labels, .. }| {
|
|ident, fields, DiagnosticConcreteArgs { labels, .. }| {
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
labels.as_ref().and_then(|labels| {
|
labels.as_ref().and_then(|labels| {
|
||||||
let variant_labels = labels.0.iter().map(|label| {
|
let variant_labels = labels.0.iter().filter_map(|label| {
|
||||||
let Label { span, label, ty, primary } = label;
|
let Label { span, label, ty, lbl_ty } = label;
|
||||||
|
if *lbl_ty == LabelType::Collection {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let field = match &span {
|
let field = match &span {
|
||||||
syn::Member::Named(ident) => ident.clone(),
|
syn::Member::Named(ident) => ident.clone(),
|
||||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
|
@ -207,29 +260,56 @@ impl Labels {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
let ctor = if *primary {
|
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 }
|
quote! { miette::LabeledSpan::new_primary_with_span }
|
||||||
} else {
|
} else {
|
||||||
quote! { miette::LabeledSpan::new_with_span }
|
quote! { miette::LabeledSpan::new_with_span }
|
||||||
};
|
};
|
||||||
if let Some(display) = label {
|
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
Some(quote! {
|
||||||
quote! {
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
.map(|#var| #ctor(
|
||||||
.map(|#var| #ctor(
|
#display,
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
#var.clone(),
|
||||||
#var.clone(),
|
))
|
||||||
))
|
})
|
||||||
}
|
});
|
||||||
} else {
|
let collections_chain = labels.0.iter().filter_map(|label| {
|
||||||
quote! {
|
let Label { span, label, ty: _, lbl_ty } = label;
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
if *lbl_ty != LabelType::Collection {
|
||||||
.map(|#var| #ctor(
|
return None;
|
||||||
std::option::Option::None,
|
|
||||||
#var.clone(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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();
|
let variant_name = ident.clone();
|
||||||
match &fields {
|
match &fields {
|
||||||
|
|
@ -237,9 +317,12 @@ impl Labels {
|
||||||
_ => Some(quote! {
|
_ => Some(quote! {
|
||||||
Self::#variant_name #display_pat => {
|
Self::#variant_name #display_pat => {
|
||||||
use miette::macro_helpers::ToOption;
|
use miette::macro_helpers::ToOption;
|
||||||
std::option::Option::Some(std::boxed::Box::new(vec![
|
let labels_iter = vec![
|
||||||
#(#variant_labels),*
|
#(#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)))
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,6 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{format_ident, quote};
|
||||||
use syn::{
|
use syn::spanned::Spanned;
|
||||||
parse::{Parse, ParseStream},
|
|
||||||
spanned::Spanned,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) enum MemberOrString {
|
|
||||||
Member(syn::Member),
|
|
||||||
String(syn::LitStr),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for MemberOrString {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
||||||
use MemberOrString::*;
|
|
||||||
match self {
|
|
||||||
Member(member) => member.to_tokens(tokens),
|
|
||||||
String(string) => string.to_tokens(tokens),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for MemberOrString {
|
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
||||||
let lookahead = input.lookahead1();
|
|
||||||
if lookahead.peek(syn::Ident) || lookahead.peek(syn::LitInt) {
|
|
||||||
Ok(MemberOrString::Member(input.parse()?))
|
|
||||||
} else if lookahead.peek(syn::LitStr) {
|
|
||||||
Ok(MemberOrString::String(input.parse()?))
|
|
||||||
} else {
|
|
||||||
Err(syn::Error::new(
|
|
||||||
input.span(),
|
|
||||||
"Expected a string or a field reference.",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1 @@
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
wrap_comments = true
|
|
||||||
format_code_in_doc_comments = true
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ pub(crate) enum ChainState<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Chain<'a> {
|
impl<'a> Chain<'a> {
|
||||||
|
#[cold]
|
||||||
pub(crate) fn new(head: &'a (dyn StdError + 'static)) -> Self {
|
pub(crate) fn new(head: &'a (dyn StdError + 'static)) -> Self {
|
||||||
Chain {
|
Chain {
|
||||||
state: ChainState::Linked { next: Some(head) },
|
state: ChainState::Linked { next: Some(head) },
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ impl<'a> ErrorKind<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::fmt::Debug for ErrorKind<'a> {
|
impl std::fmt::Debug for ErrorKind<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ErrorKind::Diagnostic(d) => d.fmt(f),
|
ErrorKind::Diagnostic(d) => d.fmt(f),
|
||||||
|
|
@ -83,7 +83,7 @@ impl<'a> std::fmt::Debug for ErrorKind<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::fmt::Display for ErrorKind<'a> {
|
impl std::fmt::Display for ErrorKind<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ErrorKind::Diagnostic(d) => d.fmt(f),
|
ErrorKind::Diagnostic(d) => d.fmt(f),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*!
|
||||||
|
Default trait implementations for [`Diagnostic`].
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{convert::Infallible, fmt::Display};
|
||||||
|
|
||||||
|
use crate::{Diagnostic, LabeledSpan, Severity, SourceCode};
|
||||||
|
|
||||||
|
impl Diagnostic for Infallible {
|
||||||
|
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn severity(&self) -> Option<Severity> {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source_code(&self) -> Option<&dyn SourceCode> {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::Report;
|
||||||
|
|
||||||
|
/// Test that [`Infallible`] implements [`Diagnostic`] by seeing if a function that's generic over `Diagnostic`
|
||||||
|
/// will accept `Infallible` as a type parameter.
|
||||||
|
#[test]
|
||||||
|
fn infallible() {
|
||||||
|
let _ = Report::new::<Infallible>;
|
||||||
|
}
|
||||||
|
}
|
||||||
88
src/error.rs
88
src/error.rs
|
|
@ -1,25 +1,51 @@
|
||||||
use std::{fmt, io};
|
use std::{
|
||||||
|
error::Error,
|
||||||
use thiserror::Error;
|
fmt::{self, Display},
|
||||||
|
io,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::Diagnostic;
|
use crate::Diagnostic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Error enum for miette. Used by certain operations in the protocol.
|
Error enum for miette. Used by certain operations in the protocol.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum MietteError {
|
pub enum MietteError {
|
||||||
/// Wrapper around [`std::io::Error`]. This is returned when something went
|
/// Wrapper around [`std::io::Error`]. This is returned when something went
|
||||||
/// wrong while reading a [`SourceCode`](crate::SourceCode).
|
/// wrong while reading a [`SourceCode`](crate::SourceCode).
|
||||||
#[error(transparent)]
|
IoError(io::Error),
|
||||||
IoError(#[from] io::Error),
|
|
||||||
|
|
||||||
/// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the
|
/// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the
|
||||||
/// bounds of a given [`SourceCode`](crate::SourceCode).
|
/// bounds of a given [`SourceCode`](crate::SourceCode).
|
||||||
#[error("The given offset is outside the bounds of its Source")]
|
|
||||||
OutOfBounds,
|
OutOfBounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for MietteError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
MietteError::IoError(error) => write!(f, "{error}"),
|
||||||
|
MietteError::OutOfBounds => {
|
||||||
|
write!(f, "The given offset is outside the bounds of its Source")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for MietteError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
MietteError::IoError(error) => error.source(),
|
||||||
|
MietteError::OutOfBounds => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for MietteError {
|
||||||
|
fn from(value: io::Error) -> Self {
|
||||||
|
Self::IoError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Diagnostic for MietteError {
|
impl Diagnostic for MietteError {
|
||||||
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -49,3 +75,51 @@ impl Diagnostic for MietteError {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod tests {
|
||||||
|
use std::{error::Error, io::ErrorKind};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct TestError(pub(crate) io::Error);
|
||||||
|
|
||||||
|
impl Display for TestError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "testing, testing...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for TestError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
Some(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn io_error() {
|
||||||
|
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
|
||||||
|
let outer_error = TestError(inner_error);
|
||||||
|
let io_error = io::Error::new(ErrorKind::Other, outer_error);
|
||||||
|
|
||||||
|
let miette_error = MietteError::from(io_error);
|
||||||
|
|
||||||
|
assert_eq!(miette_error.to_string(), "testing, testing...");
|
||||||
|
assert_eq!(
|
||||||
|
miette_error.source().unwrap().to_string(),
|
||||||
|
"halt and catch fire"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn out_of_bounds() {
|
||||||
|
let miette_error = MietteError::OutOfBounds;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
miette_error.to_string(),
|
||||||
|
"The given offset is outside the bounds of its Source"
|
||||||
|
);
|
||||||
|
assert_eq!(miette_error.source().map(ToString::to_string), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,44 @@ mod ext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> WrapErr<T, std::convert::Infallible> for Option<T> {
|
||||||
|
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
|
||||||
|
where
|
||||||
|
D: Display + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Some(t) => Ok(t),
|
||||||
|
None => Err(Report::from(crate::eyreish::wrapper::DisplayError(msg))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report>
|
||||||
|
where
|
||||||
|
D: Display + Send + Sync + 'static,
|
||||||
|
F: FnOnce() -> D,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Some(t) => Ok(t),
|
||||||
|
None => Err(Report::from(crate::eyreish::wrapper::DisplayError(msg()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn context<D>(self, msg: D) -> Result<T, Report>
|
||||||
|
where
|
||||||
|
D: Display + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
self.wrap_err(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_context<D, F>(self, msg: F) -> Result<T, Report>
|
||||||
|
where
|
||||||
|
D: Display + Send + Sync + 'static,
|
||||||
|
F: FnOnce() -> D,
|
||||||
|
{
|
||||||
|
self.wrap_err_with(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T, E> WrapErr<T, E> for Result<T, E>
|
impl<T, E> WrapErr<T, E> for Result<T, E>
|
||||||
where
|
where
|
||||||
E: ext::Diag + Send + Sync + 'static,
|
E: ext::Diag + Send + Sync + 'static,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ impl Report {
|
||||||
/// If the error type does not provide a backtrace, a backtrace will be
|
/// If the error type does not provide a backtrace, a backtrace will be
|
||||||
/// created here to ensure that a backtrace exists.
|
/// created here to ensure that a backtrace exists.
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
pub fn new<E>(error: E) -> Self
|
pub fn new<E>(error: E) -> Self
|
||||||
where
|
where
|
||||||
E: Diagnostic + Send + Sync + 'static,
|
E: Diagnostic + Send + Sync + 'static,
|
||||||
|
|
@ -66,6 +67,7 @@ impl Report {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
pub fn msg<M>(message: M) -> Self
|
pub fn msg<M>(message: M) -> Self
|
||||||
where
|
where
|
||||||
M: Display + Debug + Send + Sync + 'static,
|
M: Display + Debug + Send + Sync + 'static,
|
||||||
|
|
@ -86,6 +88,7 @@ impl Report {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
pub(crate) fn from_std<E>(error: E) -> Self
|
pub(crate) fn from_std<E>(error: E) -> Self
|
||||||
where
|
where
|
||||||
E: Diagnostic + Send + Sync + 'static,
|
E: Diagnostic + Send + Sync + 'static,
|
||||||
|
|
@ -107,6 +110,7 @@ impl Report {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
pub(crate) fn from_adhoc<M>(message: M) -> Self
|
pub(crate) fn from_adhoc<M>(message: M) -> Self
|
||||||
where
|
where
|
||||||
M: Display + Debug + Send + Sync + 'static,
|
M: Display + Debug + Send + Sync + 'static,
|
||||||
|
|
@ -131,6 +135,7 @@ impl Report {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
pub(crate) fn from_msg<D, E>(msg: D, error: E) -> Self
|
pub(crate) fn from_msg<D, E>(msg: D, error: E) -> Self
|
||||||
where
|
where
|
||||||
D: Display + Send + Sync + 'static,
|
D: Display + Send + Sync + 'static,
|
||||||
|
|
@ -155,6 +160,7 @@ impl Report {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
pub(crate) fn from_boxed(error: Box<dyn Diagnostic + Send + Sync>) -> Self {
|
pub(crate) fn from_boxed(error: Box<dyn Diagnostic + Send + Sync>) -> Self {
|
||||||
use super::wrapper::BoxedError;
|
use super::wrapper::BoxedError;
|
||||||
let error = BoxedError(error);
|
let error = BoxedError(error);
|
||||||
|
|
@ -180,6 +186,7 @@ impl Report {
|
||||||
//
|
//
|
||||||
// Unsafe because the given vtable must have sensible behavior on the error
|
// Unsafe because the given vtable must have sensible behavior on the error
|
||||||
// value of type E.
|
// value of type E.
|
||||||
|
#[cold]
|
||||||
unsafe fn construct<E>(
|
unsafe fn construct<E>(
|
||||||
error: E,
|
error: E,
|
||||||
vtable: &'static ErrorVTable,
|
vtable: &'static ErrorVTable,
|
||||||
|
|
@ -262,6 +269,7 @@ impl Report {
|
||||||
/// None
|
/// None
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[cold]
|
||||||
pub fn chain(&self) -> Chain<'_> {
|
pub fn chain(&self) -> Chain<'_> {
|
||||||
unsafe { ErrorImpl::chain(self.inner.by_ref()) }
|
unsafe { ErrorImpl::chain(self.inner.by_ref()) }
|
||||||
}
|
}
|
||||||
|
|
@ -272,7 +280,7 @@ impl Report {
|
||||||
/// The root cause is the last error in the iterator produced by
|
/// The root cause is the last error in the iterator produced by
|
||||||
/// [`chain()`](Report::chain).
|
/// [`chain()`](Report::chain).
|
||||||
pub fn root_cause(&self) -> &(dyn StdError + 'static) {
|
pub fn root_cause(&self) -> &(dyn StdError + 'static) {
|
||||||
self.chain().last().unwrap()
|
self.chain().next_back().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if `E` is the type held by this error object.
|
/// Returns true if `E` is the type held by this error object.
|
||||||
|
|
@ -410,13 +418,21 @@ impl Report {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide source code for this error
|
/// Provide source code for this error
|
||||||
pub fn with_source_code(self, source_code: impl SourceCode + Send + Sync + 'static) -> Report {
|
pub fn with_source_code(self, source_code: impl SourceCode + 'static) -> Report {
|
||||||
WithSourceCode {
|
WithSourceCode {
|
||||||
source_code,
|
source_code,
|
||||||
error: self,
|
error: self,
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a [`Report`] directly from an error-like type
|
||||||
|
pub fn from_err<E>(err: E) -> Self
|
||||||
|
where
|
||||||
|
E: std::error::Error + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
super::DiagnosticError(Box::new(err)).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> From<E> for Report
|
impl<E> From<E> for Report
|
||||||
|
|
@ -424,6 +440,7 @@ where
|
||||||
E: Diagnostic + Send + Sync + 'static,
|
E: Diagnostic + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
fn from(error: E) -> Self {
|
fn from(error: E) -> Self {
|
||||||
Report::from_std(error)
|
Report::from_std(error)
|
||||||
}
|
}
|
||||||
|
|
@ -533,7 +550,8 @@ where
|
||||||
E: Diagnostic + Send + Sync + 'static,
|
E: Diagnostic + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
|
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
|
||||||
e.cast::<ErrorImpl<E>>().boxed()
|
let unerased = e.cast::<ErrorImpl<E>>().boxed();
|
||||||
|
Box::new(unerased._object)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||||
|
|
@ -544,7 +562,8 @@ where
|
||||||
E: StdError + Send + Sync + 'static,
|
E: StdError + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
|
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
|
||||||
e.cast::<ErrorImpl<E>>().boxed()
|
let unerased = e.cast::<ErrorImpl<E>>().boxed();
|
||||||
|
Box::new(unerased._object)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||||
|
|
@ -711,22 +730,12 @@ impl ErasedErrorImpl {
|
||||||
.deref_mut()
|
.deref_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
pub(crate) unsafe fn chain(this: Ref<'_, Self>) -> Chain<'_> {
|
pub(crate) unsafe fn chain(this: Ref<'_, Self>) -> Chain<'_> {
|
||||||
Chain::new(Self::error(this))
|
Chain::new(Self::error(this))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> StdError for ErrorImpl<E>
|
|
||||||
where
|
|
||||||
E: StdError,
|
|
||||||
{
|
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
||||||
unsafe { ErrorImpl::diagnostic(self.erase()).source() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E> Diagnostic for ErrorImpl<E> where E: Diagnostic {}
|
|
||||||
|
|
||||||
impl<E> Debug for ErrorImpl<E>
|
impl<E> Debug for ErrorImpl<E>
|
||||||
where
|
where
|
||||||
E: Debug,
|
E: Debug,
|
||||||
|
|
@ -746,6 +755,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
|
impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
|
||||||
|
#[cold]
|
||||||
fn from(error: Report) -> Self {
|
fn from(error: Report) -> Self {
|
||||||
let outer = ManuallyDrop::new(error);
|
let outer = ManuallyDrop::new(error);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -757,6 +767,7 @@ impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
|
impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
|
||||||
|
#[cold]
|
||||||
fn from(error: Report) -> Self {
|
fn from(error: Report) -> Self {
|
||||||
let outer = ManuallyDrop::new(error);
|
let outer = ManuallyDrop::new(error);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -768,12 +779,14 @@ impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Report> for Box<dyn Diagnostic + 'static> {
|
impl From<Report> for Box<dyn Diagnostic + 'static> {
|
||||||
|
#[cold]
|
||||||
fn from(error: Report) -> Self {
|
fn from(error: Report) -> Self {
|
||||||
Box::<dyn Diagnostic + Send + Sync>::from(error)
|
Box::<dyn Diagnostic + Send + Sync>::from(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Report> for Box<dyn StdError + 'static> {
|
impl From<Report> for Box<dyn StdError + 'static> {
|
||||||
|
#[cold]
|
||||||
fn from(error: Report) -> Self {
|
fn from(error: Report) -> Self {
|
||||||
Box::<dyn StdError + Send + Sync>::from(error)
|
Box::<dyn StdError + Send + Sync>::from(error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,24 @@
|
||||||
use thiserror::Error;
|
use std::{error::Error, fmt::Display};
|
||||||
|
|
||||||
use crate::{Diagnostic, Report};
|
use crate::{Diagnostic, Report};
|
||||||
|
|
||||||
/// Convenience [`Diagnostic`] that can be used as an "anonymous" wrapper for
|
/// Convenience [`Diagnostic`] that can be used as an "anonymous" wrapper for
|
||||||
/// Errors. This is intended to be paired with [`IntoDiagnostic`].
|
/// Errors. This is intended to be paired with [`IntoDiagnostic`].
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
#[error(transparent)]
|
pub(crate) struct DiagnosticError(pub(crate) Box<dyn std::error::Error + Send + Sync + 'static>);
|
||||||
struct DiagnosticError(Box<dyn std::error::Error + Send + Sync + 'static>);
|
|
||||||
|
impl Display for DiagnosticError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let msg = &self.0;
|
||||||
|
write!(f, "{msg}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Error for DiagnosticError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
self.0.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Diagnostic for DiagnosticError {}
|
impl Diagnostic for DiagnosticError {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -31,3 +43,26 @@ impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for R
|
||||||
self.map_err(|e| DiagnosticError(Box::new(e)).into())
|
self.map_err(|e| DiagnosticError(Box::new(e)).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io::{self, ErrorKind};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::error::tests::TestError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diagnostic_error() {
|
||||||
|
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
|
||||||
|
let outer_error: Result<(), _> = Err(TestError(inner_error));
|
||||||
|
|
||||||
|
let diagnostic_error = outer_error.into_diagnostic().unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(diagnostic_error.to_string(), "testing, testing...");
|
||||||
|
assert_eq!(
|
||||||
|
diagnostic_error.source().unwrap().to_string(),
|
||||||
|
"halt and catch fire"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ impl<T> AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'stat
|
||||||
|
|
||||||
impl Adhoc {
|
impl Adhoc {
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
pub fn new<M>(self, message: M) -> Report
|
pub fn new<M>(self, message: M) -> Report
|
||||||
where
|
where
|
||||||
M: Display + Debug + Send + Sync + 'static,
|
M: Display + Debug + Send + Sync + 'static,
|
||||||
|
|
@ -84,6 +85,7 @@ impl<E> TraitKind for E where E: Into<Report> {}
|
||||||
|
|
||||||
impl Trait {
|
impl Trait {
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
pub fn new<E>(self, error: E) -> Report
|
pub fn new<E>(self, error: E) -> Report
|
||||||
where
|
where
|
||||||
E: Into<Report>,
|
E: Into<Report>,
|
||||||
|
|
@ -105,6 +107,7 @@ impl BoxedKind for Box<dyn Diagnostic + Send + Sync> {}
|
||||||
|
|
||||||
impl Boxed {
|
impl Boxed {
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
pub fn new(self, error: Box<dyn Diagnostic + Send + Sync>) -> Report {
|
pub fn new(self, error: Box<dyn Diagnostic + Send + Sync>) -> Report {
|
||||||
Report::from_boxed(error)
|
Report::from_boxed(error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,10 @@ pub use ReportHandler as EyreContext;
|
||||||
#[allow(unreachable_pub)]
|
#[allow(unreachable_pub)]
|
||||||
pub use WrapErr as Context;
|
pub use WrapErr as Context;
|
||||||
|
|
||||||
#[cfg(not(feature = "fancy-no-backtrace"))]
|
#[cfg(not(feature = "fancy-base"))]
|
||||||
use crate::DebugReportHandler;
|
use crate::DebugReportHandler;
|
||||||
use crate::Diagnostic;
|
use crate::Diagnostic;
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
#[cfg(feature = "fancy-base")]
|
||||||
use crate::MietteHandler;
|
use crate::MietteHandler;
|
||||||
|
|
||||||
use error::ErrorImpl;
|
use error::ErrorImpl;
|
||||||
|
|
@ -57,7 +57,7 @@ pub struct Report {
|
||||||
unsafe impl Sync for Report {}
|
unsafe impl Sync for Report {}
|
||||||
unsafe impl Send for Report {}
|
unsafe impl Send for Report {}
|
||||||
|
|
||||||
///
|
#[allow(missing_docs)]
|
||||||
pub type ErrorHook =
|
pub type ErrorHook =
|
||||||
Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;
|
Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;
|
||||||
|
|
||||||
|
|
@ -102,14 +102,14 @@ fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler + 'static> {
|
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());
|
return Box::new(MietteHandler::new());
|
||||||
#[cfg(not(feature = "fancy-no-backtrace"))]
|
#[cfg(not(feature = "fancy-base"))]
|
||||||
return Box::new(DebugReportHandler::new());
|
return Box::new(DebugReportHandler::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
impl dyn ReportHandler {
|
impl dyn ReportHandler {
|
||||||
///
|
#[allow(missing_docs)]
|
||||||
pub fn is<T: ReportHandler>(&self) -> bool {
|
pub fn is<T: ReportHandler>(&self) -> bool {
|
||||||
// Get `TypeId` of the type this function is instantiated with.
|
// Get `TypeId` of the type this function is instantiated with.
|
||||||
let t = core::any::TypeId::of::<T>();
|
let t = core::any::TypeId::of::<T>();
|
||||||
|
|
@ -121,7 +121,7 @@ impl dyn ReportHandler {
|
||||||
t == concrete
|
t == concrete
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
#[allow(missing_docs)]
|
||||||
pub fn downcast_ref<T: ReportHandler>(&self) -> Option<&T> {
|
pub fn downcast_ref<T: ReportHandler>(&self) -> Option<&T> {
|
||||||
if self.is::<T>() {
|
if self.is::<T>() {
|
||||||
unsafe { Some(&*(self as *const dyn ReportHandler as *const T)) }
|
unsafe { Some(&*(self as *const dyn ReportHandler as *const T)) }
|
||||||
|
|
@ -130,7 +130,7 @@ impl dyn ReportHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
#[allow(missing_docs)]
|
||||||
pub fn downcast_mut<T: ReportHandler>(&mut self) -> Option<&mut T> {
|
pub fn downcast_mut<T: ReportHandler>(&mut self) -> Option<&mut T> {
|
||||||
if self.is::<T>() {
|
if self.is::<T>() {
|
||||||
unsafe { Some(&mut *(self as *mut dyn ReportHandler as *mut T)) }
|
unsafe { Some(&mut *(self as *mut dyn ReportHandler as *mut T)) }
|
||||||
|
|
@ -172,11 +172,7 @@ pub trait ReportHandler: core::any::Any + Send + Sync {
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn debug(
|
fn debug(&self, error: &dyn Diagnostic, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
|
||||||
&self,
|
|
||||||
error: &(dyn Diagnostic),
|
|
||||||
f: &mut core::fmt::Formatter<'_>,
|
|
||||||
) -> core::fmt::Result;
|
|
||||||
|
|
||||||
/// Override for the `Display` format
|
/// Override for the `Display` format
|
||||||
fn display(
|
fn display(
|
||||||
|
|
@ -475,6 +471,7 @@ pub mod private {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(track_caller, track_caller)]
|
#[cfg_attr(track_caller, track_caller)]
|
||||||
|
#[cold]
|
||||||
pub fn new_adhoc<M>(message: M) -> Report
|
pub fn new_adhoc<M>(message: M) -> Report
|
||||||
where
|
where
|
||||||
M: Display + Debug + Send + Sync + 'static,
|
M: Display + Debug + Send + Sync + 'static,
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,9 @@ where
|
||||||
lifetime: PhantomData<&'a T>,
|
lifetime: PhantomData<&'a T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Copy for Ref<'a, T> where T: ?Sized {}
|
impl<T> Copy for Ref<'_, T> where T: ?Sized {}
|
||||||
|
|
||||||
impl<'a, T> Clone for Ref<'a, T>
|
impl<T> Clone for Ref<'_, T>
|
||||||
where
|
where
|
||||||
T: ?Sized,
|
T: ?Sized,
|
||||||
{
|
{
|
||||||
|
|
@ -132,9 +132,9 @@ where
|
||||||
lifetime: PhantomData<&'a mut T>,
|
lifetime: PhantomData<&'a mut T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Copy for Mut<'a, T> where T: ?Sized {}
|
impl<T> Copy for Mut<'_, T> where T: ?Sized {}
|
||||||
|
|
||||||
impl<'a, T> Clone for Mut<'a, T>
|
impl<T> Clone for Mut<'_, T>
|
||||||
where
|
where
|
||||||
T: ?Sized,
|
T: ?Sized,
|
||||||
{
|
{
|
||||||
|
|
@ -173,7 +173,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Mut<'a, T> {
|
impl<T> Mut<'_, T> {
|
||||||
pub(crate) unsafe fn read(self) -> T {
|
pub(crate) unsafe fn read(self) -> T {
|
||||||
self.ptr.as_ptr().read()
|
self.ptr.as_ptr().read()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,6 @@ use crate as miette;
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub(crate) struct DisplayError<M>(pub(crate) M);
|
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>
|
impl<M> Debug for DisplayError<M>
|
||||||
where
|
where
|
||||||
M: Display,
|
M: Display,
|
||||||
|
|
@ -35,6 +30,9 @@ where
|
||||||
impl<M> StdError for DisplayError<M> where M: Display + 'static {}
|
impl<M> StdError for DisplayError<M> where M: Display + 'static {}
|
||||||
impl<M> Diagnostic for DisplayError<M> where M: Display + 'static {}
|
impl<M> Diagnostic for DisplayError<M> where M: Display + 'static {}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub(crate) struct MessageError<M>(pub(crate) M);
|
||||||
|
|
||||||
impl<M> Debug for MessageError<M>
|
impl<M> Debug for MessageError<M>
|
||||||
where
|
where
|
||||||
M: Display + Debug,
|
M: Display + Debug,
|
||||||
|
|
@ -56,21 +54,6 @@ where
|
||||||
impl<M> StdError for MessageError<M> where M: Display + Debug + 'static {}
|
impl<M> StdError for MessageError<M> where M: Display + Debug + 'static {}
|
||||||
impl<M> Diagnostic 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)]
|
#[repr(transparent)]
|
||||||
pub(crate) struct BoxedError(pub(crate) Box<dyn Diagnostic + Send + Sync>);
|
pub(crate) struct BoxedError(pub(crate) Box<dyn Diagnostic + Send + Sync>);
|
||||||
|
|
||||||
|
|
@ -256,18 +239,6 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
#[error("outer")]
|
|
||||||
struct Outer {
|
|
||||||
pub(crate) errors: Vec<Inner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Diagnostic for Outer {
|
|
||||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
|
||||||
Some(Box::new(self.errors.iter().map(|e| e as _)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_override() {
|
fn no_override() {
|
||||||
let inner_source = "hello world";
|
let inner_source = "hello world";
|
||||||
|
|
@ -295,6 +266,18 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "fancy")]
|
#[cfg(feature = "fancy")]
|
||||||
fn two_source_codes() {
|
fn two_source_codes() {
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[error("outer")]
|
||||||
|
struct Outer {
|
||||||
|
pub(crate) errors: Vec<Inner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic for Outer {
|
||||||
|
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||||
|
Some(Box::new(self.errors.iter().map(|e| e as _)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let inner_source = "hello world";
|
let inner_source = "hello world";
|
||||||
let outer_source = "abc";
|
let outer_source = "abc";
|
||||||
|
|
||||||
|
|
|
||||||
311
src/handler.rs
311
src/handler.rs
|
|
@ -1,5 +1,3 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use crate::highlighters::Highlighter;
|
use crate::highlighters::Highlighter;
|
||||||
use crate::highlighters::MietteHighlighter;
|
use crate::highlighters::MietteHighlighter;
|
||||||
use crate::protocol::Diagnostic;
|
use crate::protocol::Diagnostic;
|
||||||
|
|
@ -9,24 +7,21 @@ use crate::NarratableReportHandler;
|
||||||
use crate::ReportHandler;
|
use crate::ReportHandler;
|
||||||
use crate::ThemeCharacters;
|
use crate::ThemeCharacters;
|
||||||
use crate::ThemeStyles;
|
use crate::ThemeStyles;
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// Settings to control the color format used for graphical rendering.
|
/// Settings to control the color format used for graphical rendering.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
|
||||||
pub enum RgbColors {
|
pub enum RgbColors {
|
||||||
/// Use RGB colors even if the terminal does not support them
|
/// Use RGB colors even if the terminal does not support them
|
||||||
Always,
|
Always,
|
||||||
/// Use RGB colors instead of ANSI if the terminal supports RGB
|
/// Use RGB colors instead of ANSI if the terminal supports RGB
|
||||||
Preferred,
|
Preferred,
|
||||||
/// Always use ANSI, regardless of terminal support for RGB
|
/// Always use ANSI, regardless of terminal support for RGB
|
||||||
|
#[default]
|
||||||
Never,
|
Never,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RgbColors {
|
|
||||||
fn default() -> RgbColors {
|
|
||||||
RgbColors::Never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a custom [`MietteHandler`] from options.
|
Create a custom [`MietteHandler`] from options.
|
||||||
|
|
||||||
|
|
@ -62,6 +57,7 @@ pub struct MietteHandlerOpts {
|
||||||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||||
pub(crate) highlighter: Option<MietteHighlighter>,
|
pub(crate) highlighter: Option<MietteHighlighter>,
|
||||||
|
pub(crate) show_related_as_nested: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MietteHandlerOpts {
|
impl MietteHandlerOpts {
|
||||||
|
|
@ -94,7 +90,7 @@ impl MietteHandlerOpts {
|
||||||
/// Syntax highlighting is disabled by default unless the
|
/// Syntax highlighting is disabled by default unless the
|
||||||
/// `syntect-highlighter` feature is enabled. Call this method
|
/// `syntect-highlighter` feature is enabled. Call this method
|
||||||
/// to override the default and use a custom highlighter
|
/// to override the default and use a custom highlighter
|
||||||
/// implmentation instead.
|
/// implementation instead.
|
||||||
///
|
///
|
||||||
/// Use
|
/// Use
|
||||||
/// [`without_syntax_highlighting()`](MietteHandlerOpts::without_syntax_highlighting())
|
/// [`without_syntax_highlighting()`](MietteHandlerOpts::without_syntax_highlighting())
|
||||||
|
|
@ -103,7 +99,9 @@ impl MietteHandlerOpts {
|
||||||
/// Setting this option will not force color output. In all cases, the
|
/// Setting this option will not force color output. In all cases, the
|
||||||
/// current color configuration via
|
/// current color configuration via
|
||||||
/// [`color()`](MietteHandlerOpts::color()) takes precedence over
|
/// [`color()`](MietteHandlerOpts::color()) takes precedence over
|
||||||
/// highlighter configuration.
|
/// highlighter configuration. However, this option does take precedence over
|
||||||
|
/// [`rgb_colors()`](MietteHandlerOpts::rgb_colors()) (meaning syntax highlighting will be
|
||||||
|
/// enabled regardless of the value of [`MietteHandlerOpts::rgb_colors`]).
|
||||||
pub fn with_syntax_highlighting(
|
pub fn with_syntax_highlighting(
|
||||||
mut self,
|
mut self,
|
||||||
highlighter: impl Highlighter + Send + Sync + 'static,
|
highlighter: impl Highlighter + Send + Sync + 'static,
|
||||||
|
|
@ -172,6 +170,18 @@ impl MietteHandlerOpts {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show related errors as siblings.
|
||||||
|
pub fn show_related_errors_as_siblings(mut self) -> Self {
|
||||||
|
self.show_related_as_nested = Some(false);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show related errors as nested errors.
|
||||||
|
pub fn show_related_errors_as_nested(mut self) -> Self {
|
||||||
|
self.show_related_as_nested = Some(true);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// If true, colors will be used during graphical rendering, regardless
|
/// If true, colors will be used during graphical rendering, regardless
|
||||||
/// of whether or not the terminal supports them.
|
/// of whether or not the terminal supports them.
|
||||||
///
|
///
|
||||||
|
|
@ -195,6 +205,8 @@ impl MietteHandlerOpts {
|
||||||
/// first place. That is handled by the [`MietteHandlerOpts::color`]
|
/// first place. That is handled by the [`MietteHandlerOpts::color`]
|
||||||
/// setting. If colors are not being used, the value of `rgb_colors` has
|
/// setting. If colors are not being used, the value of `rgb_colors` has
|
||||||
/// no effect.
|
/// no effect.
|
||||||
|
///
|
||||||
|
/// It also does not control colors when a syntax highlighter is in use.
|
||||||
pub fn rgb_colors(mut self, color: RgbColors) -> Self {
|
pub fn rgb_colors(mut self, color: RgbColors) -> Self {
|
||||||
self.rgb_colors = color;
|
self.rgb_colors = color;
|
||||||
self
|
self
|
||||||
|
|
@ -265,17 +277,15 @@ impl MietteHandlerOpts {
|
||||||
let characters = match self.unicode {
|
let characters = match self.unicode {
|
||||||
Some(true) => ThemeCharacters::unicode(),
|
Some(true) => ThemeCharacters::unicode(),
|
||||||
Some(false) => ThemeCharacters::ascii(),
|
Some(false) => ThemeCharacters::ascii(),
|
||||||
None if supports_unicode::on(supports_unicode::Stream::Stderr) => {
|
None if syscall::supports_unicode() => ThemeCharacters::unicode(),
|
||||||
ThemeCharacters::unicode()
|
|
||||||
}
|
|
||||||
None => ThemeCharacters::ascii(),
|
None => ThemeCharacters::ascii(),
|
||||||
};
|
};
|
||||||
let styles = if self.color == Some(false) {
|
let styles = if self.color == Some(false) {
|
||||||
ThemeStyles::none()
|
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 {
|
match self.rgb_colors {
|
||||||
RgbColors::Always => ThemeStyles::rgb(),
|
RgbColors::Always => ThemeStyles::rgb(),
|
||||||
RgbColors::Preferred if color.has_16m => ThemeStyles::rgb(),
|
RgbColors::Preferred if color_has_16m => ThemeStyles::rgb(),
|
||||||
_ => ThemeStyles::ansi(),
|
_ => ThemeStyles::ansi(),
|
||||||
}
|
}
|
||||||
} else if self.color == Some(true) {
|
} else if self.color == Some(true) {
|
||||||
|
|
@ -286,34 +296,13 @@ impl MietteHandlerOpts {
|
||||||
} else {
|
} else {
|
||||||
ThemeStyles::none()
|
ThemeStyles::none()
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "syntect-highlighter"))]
|
let highlighter_opt =
|
||||||
let highlighter = self.highlighter.unwrap_or_else(MietteHighlighter::nocolor);
|
HighlighterOption::select(self.color, self.highlighter, syscall::supports_color());
|
||||||
#[cfg(feature = "syntect-highlighter")]
|
|
||||||
let highlighter = if self.color == Some(false) {
|
|
||||||
MietteHighlighter::nocolor()
|
|
||||||
} else if self.color == Some(true)
|
|
||||||
|| supports_color::on(supports_color::Stream::Stderr).is_some()
|
|
||||||
{
|
|
||||||
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 theme = self.theme.unwrap_or(GraphicalTheme { characters, styles });
|
||||||
let mut handler = GraphicalReportHandler::new_themed(theme)
|
let mut handler = GraphicalReportHandler::new_themed(theme)
|
||||||
.with_width(width)
|
.with_width(width)
|
||||||
.with_links(linkify)
|
.with_links(linkify);
|
||||||
.with_theme(theme);
|
handler.highlighter = highlighter_opt.into();
|
||||||
handler.highlighter = highlighter;
|
|
||||||
if let Some(with_cause_chain) = self.with_cause_chain {
|
if let Some(with_cause_chain) = self.with_cause_chain {
|
||||||
if with_cause_chain {
|
if with_cause_chain {
|
||||||
handler = handler.with_cause_chain();
|
handler = handler.with_cause_chain();
|
||||||
|
|
@ -342,6 +331,9 @@ impl MietteHandlerOpts {
|
||||||
if let Some(s) = self.word_splitter {
|
if let Some(s) = self.word_splitter {
|
||||||
handler = handler.with_word_splitter(s)
|
handler = handler.with_word_splitter(s)
|
||||||
}
|
}
|
||||||
|
if let Some(b) = self.show_related_as_nested {
|
||||||
|
handler = handler.with_show_related_as_nested(b)
|
||||||
|
}
|
||||||
|
|
||||||
MietteHandler {
|
MietteHandler {
|
||||||
inner: Box::new(handler),
|
inner: Box::new(handler),
|
||||||
|
|
@ -367,26 +359,13 @@ impl MietteHandlerOpts {
|
||||||
if let Some(linkify) = self.linkify {
|
if let Some(linkify) = self.linkify {
|
||||||
linkify
|
linkify
|
||||||
} else {
|
} else {
|
||||||
supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr)
|
syscall::supports_hyperlinks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(miri))]
|
|
||||||
pub(crate) fn get_width(&self) -> usize {
|
pub(crate) fn get_width(&self) -> usize {
|
||||||
self.width.unwrap_or_else(|| {
|
self.width
|
||||||
terminal_size::terminal_size()
|
.unwrap_or_else(|| syscall::terminal_width().unwrap_or(80))
|
||||||
.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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -423,7 +402,7 @@ impl Default for MietteHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportHandler for MietteHandler {
|
impl ReportHandler for MietteHandler {
|
||||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
return fmt::Debug::fmt(diagnostic, f);
|
return fmt::Debug::fmt(diagnostic, f);
|
||||||
}
|
}
|
||||||
|
|
@ -431,3 +410,219 @@ impl ReportHandler for MietteHandler {
|
||||||
self.inner.debug(diagnostic, f)
|
self.inner.debug(diagnostic, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum HighlighterOption {
|
||||||
|
Disable,
|
||||||
|
EnableCustom(MietteHighlighter),
|
||||||
|
#[cfg(feature = "syntect-highlighter")]
|
||||||
|
EnableSyntect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HighlighterOption {
|
||||||
|
fn select(
|
||||||
|
color: Option<bool>,
|
||||||
|
highlighter: Option<MietteHighlighter>,
|
||||||
|
supports_color: bool,
|
||||||
|
) -> HighlighterOption {
|
||||||
|
if color == Some(false) || (color.is_none() && !supports_color) {
|
||||||
|
return HighlighterOption::Disable;
|
||||||
|
}
|
||||||
|
highlighter
|
||||||
|
.map(HighlighterOption::EnableCustom)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This is manually implemented so that it's clearer what's going on with
|
||||||
|
// the conditional compilation — clippy isn't picking up the `cfg` stuff here
|
||||||
|
#[allow(clippy::derivable_impls)]
|
||||||
|
impl Default for HighlighterOption {
|
||||||
|
fn default() -> Self {
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature = "syntect-highlighter")] {
|
||||||
|
// Because the syntect highlighter currently only supports 24-bit truecolor,
|
||||||
|
// it supersedes and ignores the `rgb_colors` config.
|
||||||
|
// TODO: In the future, if we find a way to convert the RGB syntect theme
|
||||||
|
// into an ANSI color theme, we can take `rgb_colors` into account.
|
||||||
|
HighlighterOption::EnableSyntect
|
||||||
|
} else {
|
||||||
|
HighlighterOption::Disable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HighlighterOption> for MietteHighlighter {
|
||||||
|
fn from(opt: HighlighterOption) -> Self {
|
||||||
|
match opt {
|
||||||
|
HighlighterOption::Disable => MietteHighlighter::nocolor(),
|
||||||
|
HighlighterOption::EnableCustom(highlighter) => highlighter,
|
||||||
|
#[cfg(feature = "syntect-highlighter")]
|
||||||
|
HighlighterOption::EnableSyntect => MietteHighlighter::syntect_truecolor(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod syscall {
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
|
||||||
|
#[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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::highlighters::BlankHighlighter;
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_highlighter_option() {
|
||||||
|
// Syntax highlighting is enabled depending on several variables:
|
||||||
|
// - The `color` config
|
||||||
|
// - The `highlighter` config
|
||||||
|
// - Whether the `syntect-highlighter` feature is enabled
|
||||||
|
// - Whether the terminal supports color
|
||||||
|
//
|
||||||
|
// This test asserts the expected highlighter depending on combinations of those variables.
|
||||||
|
|
||||||
|
macro_rules! assert_highlighter_opt {
|
||||||
|
(opts = $opts:expr, supports_color = $sup_color:literal, expected = $expected:pat $(,)?) => {
|
||||||
|
assert_highlighter_opt!(
|
||||||
|
opts = $opts,
|
||||||
|
supports_color = $sup_color,
|
||||||
|
expected_with_syntect = $expected,
|
||||||
|
expected_without_syntect = $expected,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
opts = $opts:expr,
|
||||||
|
supports_color = $sup_color:literal,
|
||||||
|
expected_with_syntect = $expected_with:pat,
|
||||||
|
expected_without_syntect = $expected_without:pat $(,)?
|
||||||
|
) => {{
|
||||||
|
let highlighter_opt =
|
||||||
|
HighlighterOption::select($opts.color, $opts.highlighter, $sup_color);
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature = "syntect-highlighter")] {
|
||||||
|
assert!(matches!(highlighter_opt, $expected_with));
|
||||||
|
} else {
|
||||||
|
assert!(matches!(highlighter_opt, $expected_without));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// When color is explicitly disabled, highlighting is also always disabled.
|
||||||
|
assert_highlighter_opt!(
|
||||||
|
opts = MietteHandlerOpts::new().color(false),
|
||||||
|
supports_color = true,
|
||||||
|
expected = HighlighterOption::Disable,
|
||||||
|
);
|
||||||
|
|
||||||
|
// When color is unset and the terminal doesn't support color, highlighting is disabled.
|
||||||
|
assert_highlighter_opt!(
|
||||||
|
opts = MietteHandlerOpts::new(),
|
||||||
|
supports_color = false,
|
||||||
|
expected = HighlighterOption::Disable,
|
||||||
|
);
|
||||||
|
|
||||||
|
// With explicit or implicit color support, highlighting is automatically enabled when
|
||||||
|
// `syntect-highlighter` is enabled.
|
||||||
|
assert_highlighter_opt!(
|
||||||
|
opts = MietteHandlerOpts::new().color(true),
|
||||||
|
supports_color = false,
|
||||||
|
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||||
|
expected_without_syntect = HighlighterOption::Disable,
|
||||||
|
);
|
||||||
|
assert_highlighter_opt!(
|
||||||
|
opts = MietteHandlerOpts::new(),
|
||||||
|
supports_color = true,
|
||||||
|
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||||
|
expected_without_syntect = HighlighterOption::Disable,
|
||||||
|
);
|
||||||
|
|
||||||
|
// With explicit or implicit color support, if custom highlighting is set, it's enabled.
|
||||||
|
assert_highlighter_opt!(
|
||||||
|
opts = MietteHandlerOpts::new()
|
||||||
|
.color(true)
|
||||||
|
.with_syntax_highlighting(BlankHighlighter),
|
||||||
|
supports_color = false,
|
||||||
|
expected = HighlighterOption::EnableCustom(_),
|
||||||
|
);
|
||||||
|
assert_highlighter_opt!(
|
||||||
|
opts = MietteHandlerOpts::new().with_syntax_highlighting(BlankHighlighter),
|
||||||
|
supports_color = true,
|
||||||
|
expected = HighlighterOption::EnableCustom(_),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setting `RgbColors::Never` has no effect when syntax highlighting is enabled.
|
||||||
|
assert_highlighter_opt!(
|
||||||
|
opts = MietteHandlerOpts::new()
|
||||||
|
.color(true)
|
||||||
|
.rgb_colors(RgbColors::Never),
|
||||||
|
supports_color = false,
|
||||||
|
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||||
|
expected_without_syntect = HighlighterOption::Disable,
|
||||||
|
);
|
||||||
|
assert_highlighter_opt!(
|
||||||
|
opts = MietteHandlerOpts::new().rgb_colors(RgbColors::Never),
|
||||||
|
supports_color = true,
|
||||||
|
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||||
|
expected_without_syntect = HighlighterOption::Disable,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl DebugReportHandler {
|
||||||
pub fn render_report(
|
pub fn render_report(
|
||||||
&self,
|
&self,
|
||||||
f: &mut fmt::Formatter<'_>,
|
f: &mut fmt::Formatter<'_>,
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &dyn Diagnostic,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
let mut diag = f.debug_struct("Diagnostic");
|
let mut diag = f.debug_struct("Diagnostic");
|
||||||
diag.field("message", &format!("{}", diagnostic));
|
diag.field("message", &format!("{}", diagnostic));
|
||||||
|
|
@ -61,7 +61,7 @@ impl DebugReportHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportHandler for DebugReportHandler {
|
impl ReportHandler for DebugReportHandler {
|
||||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
return fmt::Debug::fmt(diagnostic, f);
|
return fmt::Debug::fmt(diagnostic, f);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
|
|
||||||
use owo_colors::{OwoColorize, Style, StyledList};
|
use owo_colors::{OwoColorize, Style, StyledList};
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||||
|
|
||||||
use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
|
use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
|
||||||
use crate::handlers::theme::*;
|
use crate::handlers::theme::*;
|
||||||
|
|
@ -33,9 +33,12 @@ pub struct GraphicalReportHandler {
|
||||||
pub(crate) with_cause_chain: bool,
|
pub(crate) with_cause_chain: bool,
|
||||||
pub(crate) wrap_lines: bool,
|
pub(crate) wrap_lines: bool,
|
||||||
pub(crate) break_words: bool,
|
pub(crate) break_words: bool,
|
||||||
|
pub(crate) with_primary_span_start: bool,
|
||||||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||||
pub(crate) highlighter: MietteHighlighter,
|
pub(crate) highlighter: MietteHighlighter,
|
||||||
|
pub(crate) link_display_text: Option<String>,
|
||||||
|
pub(crate) show_related_as_nested: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -59,9 +62,12 @@ impl GraphicalReportHandler {
|
||||||
with_cause_chain: true,
|
with_cause_chain: true,
|
||||||
wrap_lines: true,
|
wrap_lines: true,
|
||||||
break_words: true,
|
break_words: true,
|
||||||
|
with_primary_span_start: true,
|
||||||
word_separator: None,
|
word_separator: None,
|
||||||
word_splitter: None,
|
word_splitter: None,
|
||||||
highlighter: MietteHighlighter::default(),
|
highlighter: MietteHighlighter::default(),
|
||||||
|
link_display_text: None,
|
||||||
|
show_related_as_nested: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,10 +82,13 @@ impl GraphicalReportHandler {
|
||||||
tab_width: 4,
|
tab_width: 4,
|
||||||
wrap_lines: true,
|
wrap_lines: true,
|
||||||
with_cause_chain: true,
|
with_cause_chain: true,
|
||||||
|
with_primary_span_start: true,
|
||||||
break_words: true,
|
break_words: true,
|
||||||
word_separator: None,
|
word_separator: None,
|
||||||
word_splitter: None,
|
word_splitter: None,
|
||||||
highlighter: MietteHighlighter::default(),
|
highlighter: MietteHighlighter::default(),
|
||||||
|
link_display_text: None,
|
||||||
|
show_related_as_nested: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,6 +122,20 @@ impl GraphicalReportHandler {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Include the line and column for the the start of the primary span when the
|
||||||
|
/// snippet extends multiple lines
|
||||||
|
pub fn with_primary_span_start(mut self) -> Self {
|
||||||
|
self.with_primary_span_start = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do not include the line and column for the the start of the primary span
|
||||||
|
/// when the snippet extends multiple lines
|
||||||
|
pub fn without_primary_span_start(mut self) -> Self {
|
||||||
|
self.with_primary_span_start = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether to include [`Diagnostic::url()`] in the output.
|
/// Whether to include [`Diagnostic::url()`] in the output.
|
||||||
///
|
///
|
||||||
/// Disabling this is not recommended, but can be useful for more easily
|
/// Disabling this is not recommended, but can be useful for more easily
|
||||||
|
|
@ -156,7 +179,7 @@ impl GraphicalReportHandler {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the word splitter to usewhen wrapping.
|
/// Sets the word splitter to use when wrapping.
|
||||||
pub fn with_word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
|
pub fn with_word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
|
||||||
self.word_splitter = Some(word_splitter);
|
self.word_splitter = Some(word_splitter);
|
||||||
self
|
self
|
||||||
|
|
@ -174,8 +197,15 @@ impl GraphicalReportHandler {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether to render related errors as nested errors.
|
||||||
|
pub fn with_show_related_as_nested(mut self, show_related_as_nested: bool) -> Self {
|
||||||
|
self.show_related_as_nested = show_related_as_nested;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Enable syntax highlighting for source code snippets, using the given
|
/// Enable syntax highlighting for source code snippets, using the given
|
||||||
/// [`Highlighter`]. See the [crate::highlighters] crate for more details.
|
/// [`Highlighter`]. See the [highlighters](crate::highlighters) crate
|
||||||
|
/// for more details.
|
||||||
pub fn with_syntax_highlighting(
|
pub fn with_syntax_highlighting(
|
||||||
mut self,
|
mut self,
|
||||||
highlighter: impl Highlighter + Send + Sync + 'static,
|
highlighter: impl Highlighter + Send + Sync + 'static,
|
||||||
|
|
@ -190,6 +220,13 @@ impl GraphicalReportHandler {
|
||||||
self.highlighter = MietteHighlighter::nocolor();
|
self.highlighter = MietteHighlighter::nocolor();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the display text for links.
|
||||||
|
/// Miette displays `(link)` if this option is not set.
|
||||||
|
pub fn with_link_display_text(mut self, text: impl Into<String>) -> Self {
|
||||||
|
self.link_display_text = Some(text.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GraphicalReportHandler {
|
impl Default for GraphicalReportHandler {
|
||||||
|
|
@ -205,17 +242,26 @@ impl GraphicalReportHandler {
|
||||||
pub fn render_report(
|
pub fn render_report(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &dyn Diagnostic,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
self.render_header(f, diagnostic)?;
|
self.render_report_inner(f, diagnostic, diagnostic.source_code())
|
||||||
self.render_causes(f, diagnostic)?;
|
}
|
||||||
let src = diagnostic.source_code();
|
|
||||||
|
fn render_report_inner(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
diagnostic: &dyn Diagnostic,
|
||||||
|
parent_src: Option<&dyn SourceCode>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
let src = diagnostic.source_code().or(parent_src);
|
||||||
|
self.render_header(f, diagnostic, false)?;
|
||||||
|
self.render_causes(f, diagnostic, src)?;
|
||||||
self.render_snippets(f, diagnostic, src)?;
|
self.render_snippets(f, diagnostic, src)?;
|
||||||
self.render_footer(f, diagnostic)?;
|
self.render_footer(f, diagnostic)?;
|
||||||
self.render_related(f, diagnostic, src)?;
|
self.render_related(f, diagnostic, src)?;
|
||||||
if let Some(footer) = &self.footer {
|
if let Some(footer) = &self.footer {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
let width = self.termwidth.saturating_sub(4);
|
let width = self.termwidth.saturating_sub(2);
|
||||||
let mut opts = textwrap::Options::new(width)
|
let mut opts = textwrap::Options::new(width)
|
||||||
.initial_indent(" ")
|
.initial_indent(" ")
|
||||||
.subsequent_indent(" ")
|
.subsequent_indent(" ")
|
||||||
|
|
@ -232,13 +278,19 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
fn render_header(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
diagnostic: &dyn Diagnostic,
|
||||||
|
is_nested: bool,
|
||||||
|
) -> fmt::Result {
|
||||||
let severity_style = match diagnostic.severity() {
|
let severity_style = match diagnostic.severity() {
|
||||||
Some(Severity::Error) | None => self.theme.styles.error,
|
Some(Severity::Error) | None => self.theme.styles.error,
|
||||||
Some(Severity::Warning) => self.theme.styles.warning,
|
Some(Severity::Warning) => self.theme.styles.warning,
|
||||||
Some(Severity::Advice) => self.theme.styles.advice,
|
Some(Severity::Advice) => self.theme.styles.advice,
|
||||||
};
|
};
|
||||||
let mut header = String::new();
|
let mut header = String::new();
|
||||||
|
let mut need_newline = is_nested;
|
||||||
if self.links == LinkStyle::Link && diagnostic.url().is_some() {
|
if self.links == LinkStyle::Link && diagnostic.url().is_some() {
|
||||||
let url = diagnostic.url().unwrap(); // safe
|
let url = diagnostic.url().unwrap(); // safe
|
||||||
let code = if let Some(code) = diagnostic.code() {
|
let code = if let Some(code) = diagnostic.code() {
|
||||||
|
|
@ -246,15 +298,16 @@ impl GraphicalReportHandler {
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
};
|
};
|
||||||
|
let display_text = self.link_display_text.as_deref().unwrap_or("(link)");
|
||||||
let link = format!(
|
let link = format!(
|
||||||
"\u{1b}]8;;{}\u{1b}\\{}{}\u{1b}]8;;\u{1b}\\",
|
"\u{1b}]8;;{}\u{1b}\\{}{}\u{1b}]8;;\u{1b}\\",
|
||||||
url,
|
url,
|
||||||
code.style(severity_style),
|
code.style(severity_style),
|
||||||
"(link)".style(self.theme.styles.link)
|
display_text.style(self.theme.styles.link)
|
||||||
);
|
);
|
||||||
write!(header, "{}", link)?;
|
write!(header, "{}", link)?;
|
||||||
writeln!(f, "{}", header)?;
|
writeln!(f, "{}", header)?;
|
||||||
writeln!(f)?;
|
need_newline = true;
|
||||||
} else if let Some(code) = diagnostic.code() {
|
} else if let Some(code) = diagnostic.code() {
|
||||||
write!(header, "{}", code.style(severity_style),)?;
|
write!(header, "{}", code.style(severity_style),)?;
|
||||||
if self.links == LinkStyle::Text && diagnostic.url().is_some() {
|
if self.links == LinkStyle::Text && diagnostic.url().is_some() {
|
||||||
|
|
@ -262,12 +315,22 @@ impl GraphicalReportHandler {
|
||||||
write!(header, " ({})", url.style(self.theme.styles.link))?;
|
write!(header, " ({})", url.style(self.theme.styles.link))?;
|
||||||
}
|
}
|
||||||
writeln!(f, "{}", header)?;
|
writeln!(f, "{}", header)?;
|
||||||
|
need_newline = true;
|
||||||
|
}
|
||||||
|
if need_newline {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
fn render_causes(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
diagnostic: &dyn Diagnostic,
|
||||||
|
parent_src: Option<&dyn SourceCode>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
let src = diagnostic.source_code().or(parent_src);
|
||||||
|
|
||||||
let (severity_style, severity_icon) = match diagnostic.severity() {
|
let (severity_style, severity_icon) = match diagnostic.severity() {
|
||||||
Some(Severity::Error) | None => (self.theme.styles.error, &self.theme.characters.error),
|
Some(Severity::Error) | None => (self.theme.styles.error, &self.theme.characters.error),
|
||||||
Some(Severity::Warning) => (self.theme.styles.warning, &self.theme.characters.warning),
|
Some(Severity::Warning) => (self.theme.styles.warning, &self.theme.characters.warning),
|
||||||
|
|
@ -343,9 +406,13 @@ impl GraphicalReportHandler {
|
||||||
inner_renderer.footer = None;
|
inner_renderer.footer = None;
|
||||||
// Cause chains are already flattened, so don't double-print the nested error
|
// Cause chains are already flattened, so don't double-print the nested error
|
||||||
inner_renderer.with_cause_chain = false;
|
inner_renderer.with_cause_chain = false;
|
||||||
inner_renderer.render_report(&mut inner, diag)?;
|
// Since everything from here on is indented, shrink the virtual terminal
|
||||||
|
inner_renderer.termwidth -= rest_indent.width();
|
||||||
|
inner_renderer.render_report_inner(&mut inner, diag, src)?;
|
||||||
|
|
||||||
writeln!(f, "{}", self.wrap(&inner, opts))?;
|
// If there was no header, remove the leading newline
|
||||||
|
let inner = inner.trim_start_matches('\n');
|
||||||
|
writeln!(f, "{}", self.wrap(inner, opts))?;
|
||||||
}
|
}
|
||||||
ErrorKind::StdError(err) => {
|
ErrorKind::StdError(err) => {
|
||||||
writeln!(f, "{}", self.wrap(&err.to_string(), opts))?;
|
writeln!(f, "{}", self.wrap(&err.to_string(), opts))?;
|
||||||
|
|
@ -357,9 +424,9 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||||
if let Some(help) = diagnostic.help() {
|
if let Some(help) = diagnostic.help() {
|
||||||
let width = self.termwidth.saturating_sub(4);
|
let width = self.termwidth.saturating_sub(2);
|
||||||
let initial_indent = " help: ".style(self.theme.styles.help).to_string();
|
let initial_indent = " help: ".style(self.theme.styles.help).to_string();
|
||||||
let mut opts = textwrap::Options::new(width)
|
let mut opts = textwrap::Options::new(width)
|
||||||
.initial_indent(&initial_indent)
|
.initial_indent(&initial_indent)
|
||||||
|
|
@ -380,26 +447,86 @@ impl GraphicalReportHandler {
|
||||||
fn render_related(
|
fn render_related(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &dyn Diagnostic,
|
||||||
parent_src: Option<&dyn SourceCode>,
|
parent_src: Option<&dyn SourceCode>,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
|
let src = diagnostic.source_code().or(parent_src);
|
||||||
|
|
||||||
if let Some(related) = diagnostic.related() {
|
if let Some(related) = diagnostic.related() {
|
||||||
|
let severity_style = match diagnostic.severity() {
|
||||||
|
Some(Severity::Error) | None => self.theme.styles.error,
|
||||||
|
Some(Severity::Warning) => self.theme.styles.warning,
|
||||||
|
Some(Severity::Advice) => self.theme.styles.advice,
|
||||||
|
};
|
||||||
|
|
||||||
let mut inner_renderer = self.clone();
|
let mut inner_renderer = self.clone();
|
||||||
// Re-enable the printing of nested cause chains for related errors
|
// Re-enable the printing of nested cause chains for related errors
|
||||||
inner_renderer.with_cause_chain = true;
|
inner_renderer.with_cause_chain = true;
|
||||||
writeln!(f)?;
|
if self.show_related_as_nested {
|
||||||
for rel in related {
|
let width = self.termwidth.saturating_sub(2);
|
||||||
match rel.severity() {
|
let mut related = related.peekable();
|
||||||
Some(Severity::Error) | None => write!(f, "Error: ")?,
|
while let Some(rel) = related.next() {
|
||||||
Some(Severity::Warning) => write!(f, "Warning: ")?,
|
let is_last = related.peek().is_none();
|
||||||
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
let char = if !is_last {
|
||||||
};
|
self.theme.characters.lcross
|
||||||
inner_renderer.render_header(f, rel)?;
|
} else {
|
||||||
inner_renderer.render_causes(f, rel)?;
|
self.theme.characters.lbot
|
||||||
let src = rel.source_code().or(parent_src);
|
};
|
||||||
inner_renderer.render_snippets(f, rel, src)?;
|
let initial_indent = format!(
|
||||||
inner_renderer.render_footer(f, rel)?;
|
" {}{}{} ",
|
||||||
inner_renderer.render_related(f, rel, src)?;
|
char, self.theme.characters.hbar, self.theme.characters.rarrow
|
||||||
|
)
|
||||||
|
.style(severity_style)
|
||||||
|
.to_string();
|
||||||
|
let rest_indent = format!(
|
||||||
|
" {} ",
|
||||||
|
if is_last {
|
||||||
|
' '
|
||||||
|
} else {
|
||||||
|
self.theme.characters.vbar
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.style(severity_style)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let mut opts = textwrap::Options::new(width)
|
||||||
|
.initial_indent(&initial_indent)
|
||||||
|
.subsequent_indent(&rest_indent)
|
||||||
|
.break_words(self.break_words);
|
||||||
|
if let Some(word_separator) = self.word_separator {
|
||||||
|
opts = opts.word_separator(word_separator);
|
||||||
|
}
|
||||||
|
if let Some(word_splitter) = self.word_splitter.clone() {
|
||||||
|
opts = opts.word_splitter(word_splitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inner = String::new();
|
||||||
|
|
||||||
|
let mut inner_renderer = self.clone();
|
||||||
|
inner_renderer.footer = None;
|
||||||
|
inner_renderer.with_cause_chain = false;
|
||||||
|
inner_renderer.termwidth -= rest_indent.width();
|
||||||
|
inner_renderer.render_report_inner(&mut inner, rel, src)?;
|
||||||
|
|
||||||
|
// If there was no header, remove the leading newline
|
||||||
|
let inner = inner.trim_matches('\n');
|
||||||
|
writeln!(f, "{}", self.wrap(inner, opts))?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for rel in related {
|
||||||
|
writeln!(f)?;
|
||||||
|
match rel.severity() {
|
||||||
|
Some(Severity::Error) | None => write!(f, "Error: ")?,
|
||||||
|
Some(Severity::Warning) => write!(f, "Warning: ")?,
|
||||||
|
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
||||||
|
};
|
||||||
|
inner_renderer.render_header(f, rel, true)?;
|
||||||
|
let src = rel.source_code().or(parent_src);
|
||||||
|
inner_renderer.render_causes(f, rel, src)?;
|
||||||
|
inner_renderer.render_snippets(f, rel, src)?;
|
||||||
|
inner_renderer.render_footer(f, rel)?;
|
||||||
|
inner_renderer.render_related(f, rel, src)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -408,7 +535,7 @@ impl GraphicalReportHandler {
|
||||||
fn render_snippets(
|
fn render_snippets(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &dyn Diagnostic,
|
||||||
opt_source: Option<&dyn SourceCode>,
|
opt_source: Option<&dyn SourceCode>,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
let source = match opt_source {
|
let source = match opt_source {
|
||||||
|
|
@ -425,9 +552,25 @@ impl GraphicalReportHandler {
|
||||||
|
|
||||||
let mut contexts = Vec::with_capacity(labels.len());
|
let mut contexts = Vec::with_capacity(labels.len());
|
||||||
for right in labels.iter().cloned() {
|
for right in labels.iter().cloned() {
|
||||||
let right_conts = source
|
let right_conts =
|
||||||
.read_span(right.inner(), self.context_lines, self.context_lines)
|
match source.read_span(right.inner(), self.context_lines, self.context_lines) {
|
||||||
.map_err(|_| fmt::Error)?;
|
Ok(cont) => cont,
|
||||||
|
Err(err) => {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
" [{} `{}` (offset: {}, length: {}): {:?}]",
|
||||||
|
"Failed to read contents for label".style(self.theme.styles.error),
|
||||||
|
right
|
||||||
|
.label()
|
||||||
|
.unwrap_or("<none>")
|
||||||
|
.style(self.theme.styles.link),
|
||||||
|
right.offset().style(self.theme.styles.link),
|
||||||
|
right.len().style(self.theme.styles.link),
|
||||||
|
err.style(self.theme.styles.warning)
|
||||||
|
)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if contexts.is_empty() {
|
if contexts.is_empty() {
|
||||||
contexts.push((right, right_conts));
|
contexts.push((right, right_conts));
|
||||||
|
|
@ -538,23 +681,34 @@ impl GraphicalReportHandler {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(source_name) = primary_contents.name() {
|
if let Some(source_name) = primary_contents.name() {
|
||||||
let source_name = source_name.style(self.theme.styles.link);
|
if self.with_primary_span_start {
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"[{}:{}:{}]",
|
"[{}]",
|
||||||
source_name,
|
format_args!(
|
||||||
primary_contents.line() + 1,
|
"{}:{}:{}",
|
||||||
primary_contents.column() + 1
|
source_name,
|
||||||
)?;
|
primary_contents.line() + 1,
|
||||||
} else if lines.len() <= 1 {
|
primary_contents.column() + 1
|
||||||
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
|
)
|
||||||
} else {
|
.style(self.theme.styles.link)
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"[{}]",
|
||||||
|
format_args!("{}", source_name,).style(self.theme.styles.link)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
} else if self.with_primary_span_start && lines.len() > 1 {
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"[{}:{}]",
|
"[{}:{}]",
|
||||||
primary_contents.line() + 1,
|
primary_contents.line() + 1,
|
||||||
primary_contents.column() + 1
|
primary_contents.column() + 1
|
||||||
)?;
|
)?;
|
||||||
|
} else {
|
||||||
|
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now it's time for the fun part--actually rendering everything!
|
// Now it's time for the fun part--actually rendering everything!
|
||||||
|
|
@ -637,7 +791,7 @@ impl GraphicalReportHandler {
|
||||||
f,
|
f,
|
||||||
max_gutter,
|
max_gutter,
|
||||||
line,
|
line,
|
||||||
&labels,
|
labels,
|
||||||
LabelRenderMode::SingleLine,
|
LabelRenderMode::SingleLine,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
@ -653,7 +807,7 @@ impl GraphicalReportHandler {
|
||||||
f,
|
f,
|
||||||
max_gutter,
|
max_gutter,
|
||||||
line,
|
line,
|
||||||
&labels,
|
labels,
|
||||||
LabelRenderMode::MultiLineFirst,
|
LabelRenderMode::MultiLineFirst,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
@ -671,7 +825,7 @@ impl GraphicalReportHandler {
|
||||||
f,
|
f,
|
||||||
max_gutter,
|
max_gutter,
|
||||||
line,
|
line,
|
||||||
&labels,
|
labels,
|
||||||
LabelRenderMode::MultiLineRest,
|
LabelRenderMode::MultiLineRest,
|
||||||
)?;
|
)?;
|
||||||
self.render_multi_line_end_single(
|
self.render_multi_line_end_single(
|
||||||
|
|
@ -684,13 +838,7 @@ impl GraphicalReportHandler {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// gutter _again_
|
// gutter _again_
|
||||||
self.render_highlight_gutter(
|
self.render_highlight_gutter(f, max_gutter, line, labels, LabelRenderMode::SingleLine)?;
|
||||||
f,
|
|
||||||
max_gutter,
|
|
||||||
line,
|
|
||||||
&labels,
|
|
||||||
LabelRenderMode::SingleLine,
|
|
||||||
)?;
|
|
||||||
// has no label
|
// has no label
|
||||||
writeln!(f, "{}", self.theme.characters.hbar.style(label.style))?;
|
writeln!(f, "{}", self.theme.characters.hbar.style(label.style))?;
|
||||||
}
|
}
|
||||||
|
|
@ -772,7 +920,7 @@ impl GraphicalReportHandler {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// keeps track of how many colums wide the gutter is
|
// keeps track of how many columns wide the gutter is
|
||||||
// important for ansi since simply measuring the size of the final string
|
// important for ansi since simply measuring the size of the final string
|
||||||
// gives the wrong result when the string contains ansi codes.
|
// gives the wrong result when the string contains ansi codes.
|
||||||
let mut gutter_cols = 0;
|
let mut gutter_cols = 0;
|
||||||
|
|
@ -864,12 +1012,10 @@ impl GraphicalReportHandler {
|
||||||
} else {
|
} else {
|
||||||
result.push_str(opts.initial_indent);
|
result.push_str(opts.initial_indent);
|
||||||
}
|
}
|
||||||
|
} else if line.trim().is_empty() {
|
||||||
|
result.push_str(trimmed_indent);
|
||||||
} else {
|
} else {
|
||||||
if line.trim().is_empty() {
|
result.push_str(opts.subsequent_indent);
|
||||||
result.push_str(trimmed_indent);
|
|
||||||
} else {
|
|
||||||
result.push_str(opts.subsequent_indent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result.push_str(line);
|
result.push_str(line);
|
||||||
}
|
}
|
||||||
|
|
@ -1161,14 +1307,14 @@ impl GraphicalReportHandler {
|
||||||
let context_data = source
|
let context_data = source
|
||||||
.read_span(context_span, self.context_lines, self.context_lines)
|
.read_span(context_span, self.context_lines, self.context_lines)
|
||||||
.map_err(|_| fmt::Error)?;
|
.map_err(|_| fmt::Error)?;
|
||||||
let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
|
let context = String::from_utf8_lossy(context_data.data());
|
||||||
let mut line = context_data.line();
|
let mut line = context_data.line();
|
||||||
let mut column = context_data.column();
|
let mut column = context_data.column();
|
||||||
let mut offset = context_data.span().offset();
|
let mut offset = context_data.span().offset();
|
||||||
let mut line_offset = offset;
|
let mut line_offset = offset;
|
||||||
|
let mut line_str = String::with_capacity(context.len());
|
||||||
|
let mut lines = Vec::with_capacity(1);
|
||||||
let mut iter = context.chars().peekable();
|
let mut iter = context.chars().peekable();
|
||||||
let mut line_str = String::new();
|
|
||||||
let mut lines = Vec::new();
|
|
||||||
while let Some(char) = iter.next() {
|
while let Some(char) = iter.next() {
|
||||||
offset += char.len_utf8();
|
offset += char.len_utf8();
|
||||||
let mut at_end_of_file = false;
|
let mut at_end_of_file = false;
|
||||||
|
|
@ -1215,7 +1361,7 @@ impl GraphicalReportHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportHandler for GraphicalReportHandler {
|
impl ReportHandler for GraphicalReportHandler {
|
||||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
return fmt::Debug::fmt(diagnostic, f);
|
return fmt::Debug::fmt(diagnostic, f);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ impl JSONReportHandler {
|
||||||
pub fn render_report(
|
pub fn render_report(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &dyn Diagnostic,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
self._render_report(f, diagnostic, None)
|
self._render_report(f, diagnostic, None)
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +68,7 @@ impl JSONReportHandler {
|
||||||
fn _render_report(
|
fn _render_report(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &dyn Diagnostic,
|
||||||
parent_src: Option<&dyn SourceCode>,
|
parent_src: Option<&dyn SourceCode>,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
write!(f, r#"{{"message": "{}","#, escape(&diagnostic.to_string()))?;
|
write!(f, r#"{{"message": "{}","#, escape(&diagnostic.to_string()))?;
|
||||||
|
|
@ -154,7 +154,7 @@ impl JSONReportHandler {
|
||||||
fn render_snippets(
|
fn render_snippets(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &dyn Diagnostic,
|
||||||
source: &dyn SourceCode,
|
source: &dyn SourceCode,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
if let Some(mut labels) = diagnostic.labels() {
|
if let Some(mut labels) = diagnostic.labels() {
|
||||||
|
|
@ -170,7 +170,7 @@ impl JSONReportHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportHandler for JSONReportHandler {
|
impl ReportHandler for JSONReportHandler {
|
||||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
self.render_report(f, diagnostic)
|
self.render_report(f, diagnostic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,20 @@ Reporters included with `miette`.
|
||||||
#[allow(unreachable_pub)]
|
#[allow(unreachable_pub)]
|
||||||
pub use debug::*;
|
pub use debug::*;
|
||||||
#[allow(unreachable_pub)]
|
#[allow(unreachable_pub)]
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
#[cfg(feature = "fancy-base")]
|
||||||
pub use graphical::*;
|
pub use graphical::*;
|
||||||
#[allow(unreachable_pub)]
|
#[allow(unreachable_pub)]
|
||||||
pub use json::*;
|
pub use json::*;
|
||||||
#[allow(unreachable_pub)]
|
#[allow(unreachable_pub)]
|
||||||
pub use narratable::*;
|
pub use narratable::*;
|
||||||
#[allow(unreachable_pub)]
|
#[allow(unreachable_pub)]
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
#[cfg(feature = "fancy-base")]
|
||||||
pub use theme::*;
|
pub use theme::*;
|
||||||
|
|
||||||
mod debug;
|
mod debug;
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
#[cfg(feature = "fancy-base")]
|
||||||
mod graphical;
|
mod graphical;
|
||||||
mod json;
|
mod json;
|
||||||
mod narratable;
|
mod narratable;
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
#[cfg(feature = "fancy-base")]
|
||||||
mod theme;
|
mod theme;
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ impl NarratableReportHandler {
|
||||||
pub fn render_report(
|
pub fn render_report(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &dyn Diagnostic,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
self.render_header(f, diagnostic)?;
|
self.render_header(f, diagnostic)?;
|
||||||
if self.with_cause_chain {
|
if self.with_cause_chain {
|
||||||
|
|
@ -85,7 +85,7 @@ impl NarratableReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||||
writeln!(f, "{}", diagnostic)?;
|
writeln!(f, "{}", diagnostic)?;
|
||||||
let severity = match diagnostic.severity() {
|
let severity = match diagnostic.severity() {
|
||||||
Some(Severity::Error) | None => "error",
|
Some(Severity::Error) | None => "error",
|
||||||
|
|
@ -96,7 +96,7 @@ impl NarratableReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||||
if let Some(cause_iter) = diagnostic
|
if let Some(cause_iter) = diagnostic
|
||||||
.diagnostic_source()
|
.diagnostic_source()
|
||||||
.map(DiagnosticChain::from_diagnostic)
|
.map(DiagnosticChain::from_diagnostic)
|
||||||
|
|
@ -110,7 +110,7 @@ impl NarratableReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||||
if let Some(help) = diagnostic.help() {
|
if let Some(help) = diagnostic.help() {
|
||||||
writeln!(f, "diagnostic help: {}", help)?;
|
writeln!(f, "diagnostic help: {}", help)?;
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +126,7 @@ impl NarratableReportHandler {
|
||||||
fn render_related(
|
fn render_related(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &dyn Diagnostic,
|
||||||
parent_src: Option<&dyn SourceCode>,
|
parent_src: Option<&dyn SourceCode>,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
if let Some(related) = diagnostic.related() {
|
if let Some(related) = diagnostic.related() {
|
||||||
|
|
@ -152,7 +152,7 @@ impl NarratableReportHandler {
|
||||||
fn render_snippets(
|
fn render_snippets(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &dyn Diagnostic,
|
||||||
source_code: Option<&dyn SourceCode>,
|
source_code: Option<&dyn SourceCode>,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
if let Some(source) = source_code {
|
if let Some(source) = source_code {
|
||||||
|
|
@ -295,9 +295,9 @@ impl NarratableReportHandler {
|
||||||
let mut column = context_data.column();
|
let mut column = context_data.column();
|
||||||
let mut offset = context_data.span().offset();
|
let mut offset = context_data.span().offset();
|
||||||
let mut line_offset = offset;
|
let mut line_offset = offset;
|
||||||
|
let mut line_str = String::with_capacity(context.len());
|
||||||
|
let mut lines = Vec::with_capacity(1);
|
||||||
let mut iter = context.chars().peekable();
|
let mut iter = context.chars().peekable();
|
||||||
let mut line_str = String::new();
|
|
||||||
let mut lines = Vec::new();
|
|
||||||
while let Some(char) = iter.next() {
|
while let Some(char) = iter.next() {
|
||||||
offset += char.len_utf8();
|
offset += char.len_utf8();
|
||||||
let mut at_end_of_file = false;
|
let mut at_end_of_file = false;
|
||||||
|
|
@ -344,7 +344,7 @@ impl NarratableReportHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportHandler for NarratableReportHandler {
|
impl ReportHandler for NarratableReportHandler {
|
||||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
return fmt::Debug::fmt(diagnostic, f);
|
return fmt::Debug::fmt(diagnostic, f);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ impl Default for GraphicalTheme {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
match std::env::var("NO_COLOR") {
|
match std::env::var("NO_COLOR") {
|
||||||
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
|
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
|
||||||
Self::ascii()
|
Self::none()
|
||||||
}
|
}
|
||||||
Ok(string) if string != "0" => Self::unicode_nocolor(),
|
Ok(string) if string != "0" => Self::unicode_nocolor(),
|
||||||
_ => Self::unicode(),
|
_ => Self::unicode(),
|
||||||
|
|
|
||||||
|
|
@ -26,16 +26,16 @@ mod syntect;
|
||||||
|
|
||||||
/// A syntax highlighter for highlighting miette [`SourceCode`](crate::SourceCode) snippets.
|
/// A syntax highlighter for highlighting miette [`SourceCode`](crate::SourceCode) snippets.
|
||||||
pub trait Highlighter {
|
pub trait Highlighter {
|
||||||
/// Creates a new [HighlighterState] to begin parsing and highlighting
|
/// Creates a new [`HighlighterState`] to begin parsing and highlighting
|
||||||
/// a [SpanContents].
|
/// a [`SpanContents`].
|
||||||
///
|
///
|
||||||
/// The [GraphicalReportHandler](crate::GraphicalReportHandler) will call
|
/// The [`GraphicalReportHandler`](crate::GraphicalReportHandler) will call
|
||||||
/// this method at the start of rendering a [SpanContents].
|
/// this method at the start of rendering a [`SpanContents`].
|
||||||
///
|
///
|
||||||
/// The [SpanContents] is provided as input only so that the [Highlighter]
|
/// The [`SpanContents`] is provided as input only so that the [`Highlighter`]
|
||||||
/// can detect language syntax and make other initialization decisions prior
|
/// can detect language syntax and make other initialization decisions prior
|
||||||
/// to highlighting, but it is not intended that the Highlighter begin
|
/// to highlighting, but it is not intended that the Highlighter begin
|
||||||
/// highlighting at this point. The returned [HighlighterState] is
|
/// highlighting at this point. The returned [`HighlighterState`] is
|
||||||
/// responsible for the actual rendering.
|
/// responsible for the actual rendering.
|
||||||
fn start_highlighter_state<'h>(
|
fn start_highlighter_state<'h>(
|
||||||
&'h self,
|
&'h self,
|
||||||
|
|
@ -46,12 +46,12 @@ pub trait Highlighter {
|
||||||
/// A stateful highlighter that incrementally highlights lines of a particular
|
/// A stateful highlighter that incrementally highlights lines of a particular
|
||||||
/// source code.
|
/// source code.
|
||||||
///
|
///
|
||||||
/// The [GraphicalReportHandler](crate::GraphicalReportHandler)
|
/// The [`GraphicalReportHandler`](crate::GraphicalReportHandler)
|
||||||
/// will create a highlighter state by calling
|
/// will create a highlighter state by calling
|
||||||
/// [start_highlighter_state](Highlighter::start_highlighter_state) at the
|
/// [`start_highlighter_state`](Highlighter::start_highlighter_state) at the
|
||||||
/// start of rendering, then it will iteratively call
|
/// start of rendering, then it will iteratively call
|
||||||
/// [highlight_line](HighlighterState::highlight_line) to render individual
|
/// [`highlight_line`](HighlighterState::highlight_line) to render individual
|
||||||
/// highlighted lines. This allows [Highlighter] implementations to maintain
|
/// highlighted lines. This allows [`Highlighter`] implementations to maintain
|
||||||
/// mutable parsing and highlighting state.
|
/// mutable parsing and highlighting state.
|
||||||
pub trait HighlighterState {
|
pub trait HighlighterState {
|
||||||
/// Highlight an individual line from the source code by returning a vector of [Styled]
|
/// Highlight an individual line from the source code by returning a vector of [Styled]
|
||||||
|
|
@ -59,9 +59,9 @@ pub trait HighlighterState {
|
||||||
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>>;
|
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Arcified trait object for Highlighter. Used internally by [GraphicalReportHandler]
|
/// Arcified trait object for Highlighter. Used internally by [`GraphicalReportHandler`]
|
||||||
///
|
///
|
||||||
/// Wrapping the trait object in this way allows us to implement Debug and Clone.
|
/// Wrapping the trait object in this way allows us to implement `Debug` and `Clone`.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub(crate) struct MietteHighlighter(Arc<dyn Highlighter + Send + Sync>);
|
pub(crate) struct MietteHighlighter(Arc<dyn Highlighter + Send + Sync>);
|
||||||
|
|
@ -80,7 +80,7 @@ impl MietteHighlighter {
|
||||||
impl Default for MietteHighlighter {
|
impl Default for MietteHighlighter {
|
||||||
#[cfg(feature = "syntect-highlighter")]
|
#[cfg(feature = "syntect-highlighter")]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
use is_terminal::IsTerminal;
|
use std::io::IsTerminal;
|
||||||
match std::env::var("NO_COLOR") {
|
match std::env::var("NO_COLOR") {
|
||||||
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
|
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
|
||||||
//TODO: should use ANSI styling instead of 24-bit truecolor here
|
//TODO: should use ANSI styling instead of 24-bit truecolor here
|
||||||
|
|
@ -92,7 +92,7 @@ impl Default for MietteHighlighter {
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "syntect-highlighter"))]
|
#[cfg(not(feature = "syntect-highlighter"))]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
return MietteHighlighter::nocolor();
|
MietteHighlighter::nocolor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
// all syntect imports are explicitly qualified, but their paths are shortened for convenience
|
// all syntect imports are explicitly qualified, but their paths are shortened for convenience
|
||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod syntect {
|
mod syntect {
|
||||||
pub(super) use syntect::{
|
pub(super) use syntect::{
|
||||||
highlighting::{
|
highlighting::{
|
||||||
|
|
@ -19,7 +20,7 @@ use crate::{
|
||||||
|
|
||||||
use super::BlankHighlighterState;
|
use super::BlankHighlighterState;
|
||||||
|
|
||||||
/// Highlights miette [SourceCode] with the [syntect](https://docs.rs/syntect/latest/syntect/) highlighting crate.
|
/// Highlights miette [`SpanContents`] with the [syntect](https://docs.rs/syntect/latest/syntect/) highlighting crate.
|
||||||
///
|
///
|
||||||
/// Currently only 24-bit truecolor output is supported due to syntect themes
|
/// Currently only 24-bit truecolor output is supported due to syntect themes
|
||||||
/// representing color as RGBA.
|
/// representing color as RGBA.
|
||||||
|
|
@ -80,7 +81,7 @@ impl SyntectHighlighter {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine syntect SyntaxReference to use for given SourceCode
|
/// Determine syntect [`SyntaxReference`] to use for given [`SpanContents`].
|
||||||
fn detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference> {
|
fn detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference> {
|
||||||
// use language if given
|
// use language if given
|
||||||
if let Some(language) = contents.language() {
|
if let Some(language) = contents.language() {
|
||||||
|
|
@ -95,16 +96,16 @@ impl SyntectHighlighter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// finally, attempt to guess syntax based on first line
|
// finally, attempt to guess syntax based on first line
|
||||||
return self.syntax_set.find_syntax_by_first_line(
|
self.syntax_set.find_syntax_by_first_line(
|
||||||
&std::str::from_utf8(contents.data())
|
std::str::from_utf8(contents.data())
|
||||||
.ok()?
|
.ok()?
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.next()?,
|
.next()?,
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stateful highlighting iterator for [SyntectHighlighter]
|
/// Stateful highlighting iterator for [`SyntectHighlighter`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct SyntectHighlighterState<'h> {
|
pub(crate) struct SyntectHighlighterState<'h> {
|
||||||
syntax_set: &'h syntect::SyntaxSet,
|
syntax_set: &'h syntect::SyntaxSet,
|
||||||
|
|
@ -114,17 +115,17 @@ pub(crate) struct SyntectHighlighterState<'h> {
|
||||||
use_bg_color: bool,
|
use_bg_color: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'h> HighlighterState for SyntectHighlighterState<'h> {
|
impl HighlighterState for SyntectHighlighterState<'_> {
|
||||||
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>> {
|
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>> {
|
||||||
if let Ok(ops) = self.parse_state.parse_line(line, &self.syntax_set) {
|
if let Ok(ops) = self.parse_state.parse_line(line, self.syntax_set) {
|
||||||
let use_bg_color = self.use_bg_color;
|
let use_bg_color = self.use_bg_color;
|
||||||
syntect::HighlightIterator::new(
|
syntect::HighlightIterator::new(
|
||||||
&mut self.highlight_state,
|
&mut self.highlight_state,
|
||||||
&ops,
|
&ops,
|
||||||
line,
|
line,
|
||||||
&mut self.highlighter,
|
&self.highlighter,
|
||||||
)
|
)
|
||||||
.map(|(style, str)| (convert_style(style, use_bg_color).style(str)))
|
.map(|(style, str)| convert_style(style, use_bg_color).style(str))
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
vec![Style::default().style(line)]
|
vec![Style::default().style(line)]
|
||||||
|
|
@ -132,7 +133,7 @@ impl<'h> HighlighterState for SyntectHighlighterState<'h> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert syntect [syntect::Style] into owo_colors [Style] */
|
/// Convert syntect [`syntect::Style`] into `owo_colors` [`Style`]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn convert_style(syntect_style: syntect::Style, use_bg_color: bool) -> Style {
|
fn convert_style(syntect_style: syntect::Style, use_bg_color: bool) -> Style {
|
||||||
if use_bg_color {
|
if use_bg_color {
|
||||||
|
|
|
||||||
187
src/lib.rs
187
src/lib.rs
|
|
@ -1,5 +1,6 @@
|
||||||
#![deny(missing_docs, missing_debug_implementations, nonstandard_style)]
|
#![deny(missing_docs, missing_debug_implementations, nonstandard_style)]
|
||||||
#![warn(unreachable_pub, rust_2018_idioms)]
|
#![warn(unreachable_pub, rust_2018_idioms)]
|
||||||
|
#![allow(unexpected_cfgs)]
|
||||||
//! You run miette? You run her code like the software? Oh. Oh! Error code for
|
//! You run miette? You run her code like the software? Oh. Oh! Error code for
|
||||||
//! coder! Error code for One Thousand Lines!
|
//! coder! Error code for One Thousand Lines!
|
||||||
//!
|
//!
|
||||||
|
|
@ -27,9 +28,9 @@
|
||||||
//! " />
|
//! " />
|
||||||
//!
|
//!
|
||||||
//! > **NOTE: You must enable the `"fancy"` crate feature to get fancy report
|
//! > **NOTE: You must enable the `"fancy"` crate feature to get fancy report
|
||||||
//! output like in the screenshots above.** You should only do this in your
|
//! > 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
|
//! > toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||||
//! libraries and such might not want.
|
//! > libraries and such might not want.
|
||||||
//!
|
//!
|
||||||
//! ## Table of Contents <!-- omit in toc -->
|
//! ## Table of Contents <!-- omit in toc -->
|
||||||
//!
|
//!
|
||||||
|
|
@ -43,11 +44,15 @@
|
||||||
//! - [... in `main()`](#-in-main)
|
//! - [... in `main()`](#-in-main)
|
||||||
//! - [... diagnostic code URLs](#-diagnostic-code-urls)
|
//! - [... diagnostic code URLs](#-diagnostic-code-urls)
|
||||||
//! - [... snippets](#-snippets)
|
//! - [... snippets](#-snippets)
|
||||||
|
//! - [... help text](#-help-text)
|
||||||
|
//! - [... severity level](#-severity-level)
|
||||||
//! - [... multiple related errors](#-multiple-related-errors)
|
//! - [... multiple related errors](#-multiple-related-errors)
|
||||||
//! - [... delayed source code](#-delayed-source-code)
|
//! - [... delayed source code](#-delayed-source-code)
|
||||||
//! - [... handler options](#-handler-options)
|
//! - [... handler options](#-handler-options)
|
||||||
//! - [... dynamic diagnostics](#-dynamic-diagnostics)
|
//! - [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||||
//! - [... syntax highlighting](#-syntax-highlighting)
|
//! - [... syntax highlighting](#-syntax-highlighting)
|
||||||
|
//! - [... primary label](#-primary-label)
|
||||||
|
//! - [... collection of labels](#-collection-of-labels)
|
||||||
//! - [Acknowledgements](#acknowledgements)
|
//! - [Acknowledgements](#acknowledgements)
|
||||||
//! - [License](#license)
|
//! - [License](#license)
|
||||||
//!
|
//!
|
||||||
|
|
@ -96,7 +101,7 @@
|
||||||
//!
|
//!
|
||||||
//! `thiserror` is a great way to define them, and plays nicely with `miette`!
|
//! `thiserror` is a great way to define them, and plays nicely with `miette`!
|
||||||
//! */
|
//! */
|
||||||
//! use miette::{Diagnostic, SourceSpan};
|
//! use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||||
//! use thiserror::Error;
|
//! use thiserror::Error;
|
||||||
//!
|
//!
|
||||||
//! #[derive(Error, Debug, Diagnostic)]
|
//! #[derive(Error, Debug, Diagnostic)]
|
||||||
|
|
@ -123,12 +128,11 @@
|
||||||
//! throughout your app (but NOT your libraries! Those should always return
|
//! throughout your app (but NOT your libraries! Those should always return
|
||||||
//! concrete types!).
|
//! concrete types!).
|
||||||
//! */
|
//! */
|
||||||
//! use miette::{NamedSource, Result};
|
//! use miette::Result;
|
||||||
//! fn this_fails() -> Result<()> {
|
//! fn this_fails() -> Result<()> {
|
||||||
//! // You can use plain strings as a `Source`, or anything that implements
|
//! // You can use plain strings as a `Source`, or anything that implements
|
||||||
//! // the one-method `Source` trait.
|
//! // the one-method `Source` trait.
|
||||||
//! let src = "source\n text\n here".to_string();
|
//! let src = "source\n text\n here".to_string();
|
||||||
//! let len = src.len();
|
|
||||||
//!
|
//!
|
||||||
//! Err(MyBad {
|
//! Err(MyBad {
|
||||||
//! src: NamedSource::new("bad_file.rs", src),
|
//! src: NamedSource::new("bad_file.rs", src),
|
||||||
|
|
@ -158,17 +162,20 @@
|
||||||
//! <img src="https://raw.githubusercontent.com/zkat/miette/main/images/single-line-example.png" alt="
|
//! <img src="https://raw.githubusercontent.com/zkat/miette/main/images/single-line-example.png" alt="
|
||||||
//! Narratable printout:
|
//! Narratable printout:
|
||||||
//! \
|
//! \
|
||||||
//! Error: Types mismatched for operation.
|
//! diagnostic error code: oops::my::bad (link)
|
||||||
//! Diagnostic severity: error
|
//! Error: oops!
|
||||||
//! Begin snippet starting at line 1, column 1
|
|
||||||
//! \
|
//! \
|
||||||
//! snippet line 1: 3 + "5"
|
//! Begin snippet for bad_file.rs starting
|
||||||
//! label starting at line 1, column 1: int
|
//! at line 2, column 3
|
||||||
//! label starting at line 1, column 1: doesn't support these values.
|
//! \
|
||||||
//! label starting at line 1, column 1: string
|
//! snippet line 1: source
|
||||||
//! diagnostic help: Change int or string to be the right types and try again.
|
//! \
|
||||||
//! diagnostic code: nu::parser::unsupported_operation
|
//! snippet line 2: text
|
||||||
//! For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
|
//! highlight starting at line 1, column 3: This bit here
|
||||||
|
//! \
|
||||||
|
//! snippet line 3: here
|
||||||
|
//! \
|
||||||
|
//! diagnostic help: try doing it better next time?">
|
||||||
//!
|
//!
|
||||||
//! ## Using
|
//! ## Using
|
||||||
//!
|
//!
|
||||||
|
|
@ -204,6 +211,17 @@
|
||||||
//! // Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
//! // Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
||||||
//! #[diagnostic(transparent)]
|
//! #[diagnostic(transparent)]
|
||||||
//! AnotherError(#[from] AnotherError),
|
//! AnotherError(#[from] AnotherError),
|
||||||
|
//!
|
||||||
|
//! /// Forward the diagnostic to a particular field.
|
||||||
|
//! #[error("other error")]
|
||||||
|
//! #[diagnostic(forward(the_actual_diagnostic))]
|
||||||
|
//! EvenMoreData {
|
||||||
|
//! unrelated_field_1: String,
|
||||||
|
//! unrelated_field_2: usize,
|
||||||
|
//!
|
||||||
|
//! #[source]
|
||||||
|
//! the_actual_diagnostic: AnotherError,
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[derive(Error, Diagnostic, Debug)]
|
//! #[derive(Error, Diagnostic, Debug)]
|
||||||
|
|
@ -238,7 +256,7 @@
|
||||||
//! use semver::Version;
|
//! use semver::Version;
|
||||||
//!
|
//!
|
||||||
//! pub fn some_tool() -> Result<Version> {
|
//! pub fn some_tool() -> Result<Version> {
|
||||||
//! Ok("1.2.x".parse().into_diagnostic()?)
|
//! "1.2.x".parse().into_diagnostic()
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
|
@ -253,24 +271,24 @@
|
||||||
//! use semver::Version;
|
//! use semver::Version;
|
||||||
//!
|
//!
|
||||||
//! pub fn some_tool() -> Result<Version> {
|
//! pub fn some_tool() -> Result<Version> {
|
||||||
//! Ok("1.2.x"
|
//! "1.2.x"
|
||||||
//! .parse()
|
//! .parse()
|
||||||
//! .into_diagnostic()
|
//! .into_diagnostic()
|
||||||
//! .wrap_err("Parsing this tool's semver version failed.")?)
|
//! .wrap_err("Parsing this tool's semver version failed.")
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! To construct your own simple adhoc error use the [miette!] macro:
|
//! To construct your own simple adhoc error use the [`miette!`] macro:
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! // my_app/lib/my_internal_file.rs
|
//! // my_app/lib/my_internal_file.rs
|
||||||
//! use miette::{miette, IntoDiagnostic, Result, WrapErr};
|
//! use miette::{miette, Result};
|
||||||
//! use semver::Version;
|
//! use semver::Version;
|
||||||
//!
|
//!
|
||||||
//! pub fn some_tool() -> Result<Version> {
|
//! pub fn some_tool() -> Result<Version> {
|
||||||
//! let version = "1.2.x";
|
//! let version = "1.2.x";
|
||||||
//! Ok(version
|
//! version
|
||||||
//! .parse()
|
//! .parse()
|
||||||
//! .map_err(|_| miette!("Invalid version {}", version))?)
|
//! .map_err(|_| miette!("Invalid version {}", version))
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//! There are also similar [bail!] and [ensure!] macros.
|
//! There are also similar [bail!] and [ensure!] macros.
|
||||||
|
|
@ -282,9 +300,9 @@
|
||||||
//! automatically.
|
//! automatically.
|
||||||
//!
|
//!
|
||||||
//! > **NOTE:** You must enable the `"fancy"` crate feature to get fancy report
|
//! > **NOTE:** You must enable the `"fancy"` crate feature to get fancy report
|
||||||
//! output like in the screenshots here.** You should only do this in your
|
//! > output like in the screenshots here.** You should only do this in your
|
||||||
//! toplevel crate, as the fancy feature pulls in a number of dependencies that
|
//! > toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||||
//! libraries and such might not want.
|
//! > libraries and such might not want.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use miette::{IntoDiagnostic, Result};
|
//! use miette::{IntoDiagnostic, Result};
|
||||||
|
|
@ -423,7 +441,7 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! #### ... help text
|
//! ### ... help text
|
||||||
//! `miette` provides two facilities for supplying help text for your errors:
|
//! `miette` provides two facilities for supplying help text for your errors:
|
||||||
//!
|
//!
|
||||||
//! The first is the `#[help()]` format attribute that applies to structs or
|
//! The first is the `#[help()]` format attribute that applies to structs or
|
||||||
|
|
@ -459,6 +477,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
|
//! ### ... multiple related errors
|
||||||
//!
|
//!
|
||||||
//! `miette` supports collecting multiple errors into a single diagnostic, and
|
//! `miette` supports collecting multiple errors into a single diagnostic, and
|
||||||
|
|
@ -616,7 +647,6 @@
|
||||||
//! )
|
//! )
|
||||||
//! }))
|
//! }))
|
||||||
//!
|
//!
|
||||||
//! # .unwrap()
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! See the docs for [`MietteHandlerOpts`] for more details on what you can
|
//! See the docs for [`MietteHandlerOpts`] for more details on what you can
|
||||||
|
|
@ -627,15 +657,15 @@
|
||||||
//! If you...
|
//! If you...
|
||||||
//! - ...don't know all the possible errors upfront
|
//! - ...don't know all the possible errors upfront
|
||||||
//! - ...need to serialize/deserialize errors
|
//! - ...need to serialize/deserialize errors
|
||||||
//! then you may want to use [`miette!`], [`diagnostic!`] macros or
|
//! then you may want to use [`miette!`], [`diagnostic!`] macros or
|
||||||
//! [`MietteDiagnostic`] directly to create diagnostic on the fly.
|
//! [`MietteDiagnostic`] directly to create diagnostic on the fly.
|
||||||
//!
|
//!
|
||||||
//! ```rust,ignore
|
//! ```rust,ignore
|
||||||
//! # use miette::{miette, LabeledSpan, Report};
|
//! # use miette::{miette, LabeledSpan, Report};
|
||||||
//!
|
//!
|
||||||
//! let source = "2 + 2 * 2 = 8".to_string();
|
//! let source = "2 + 2 * 2 = 8".to_string();
|
||||||
//! let report = miette!(
|
//! let report = miette!(
|
||||||
//! labels = vec[
|
//! labels = vec![
|
||||||
//! LabeledSpan::at(12..13, "this should be 6"),
|
//! LabeledSpan::at(12..13, "this should be 6"),
|
||||||
//! ],
|
//! ],
|
||||||
//! help = "'*' has greater precedence than '+'",
|
//! help = "'*' has greater precedence than '+'",
|
||||||
|
|
@ -656,12 +686,12 @@
|
||||||
//! field of your [`Diagnostic`].
|
//! field of your [`Diagnostic`].
|
||||||
//!
|
//!
|
||||||
//! Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
|
//! Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
|
||||||
//! * [language()](SpanContents::language) - Provides the name of the language
|
//! * [`language()`](SpanContents::language) - Provides the name of the language
|
||||||
//! as a string. For example `"Rust"` will indicate Rust syntax highlighting.
|
//! as a string. For example `"Rust"` will indicate Rust syntax highlighting.
|
||||||
//! You can set the language of the [`SpanContents`] produced by a
|
//! You can set the language of the [`SpanContents`] produced by a
|
||||||
//! [`NamedSource`] via the [`with_language`](NamedSource::with_language)
|
//! [`NamedSource`] via the [`with_language`](NamedSource::with_language)
|
||||||
//! method.
|
//! method.
|
||||||
//! * [name()](SpanContents::name) - In the absence of an explicitly set
|
//! * [`name()`](SpanContents::name) - In the absence of an explicitly set
|
||||||
//! language, the name is assumed to contain a file name or file path.
|
//! 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
|
//! The highlighter will check for a file extension at the end of the name and
|
||||||
//! try to guess the syntax from that.
|
//! try to guess the syntax from that.
|
||||||
|
|
@ -672,9 +702,91 @@
|
||||||
//! [`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
//! [`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
||||||
//! method. See the [`highlighters`] module docs for more details.
|
//! method. See the [`highlighters`] module docs for more details.
|
||||||
//!
|
//!
|
||||||
|
//! ### ... primary label
|
||||||
|
//!
|
||||||
|
//! You can use the `primary` parameter to `label` to indicate that the label
|
||||||
|
//! is the primary label.
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! #[derive(Debug, Diagnostic, Error)]
|
||||||
|
//! #[error("oops!")]
|
||||||
|
//! struct MyError {
|
||||||
|
//! #[label(primary, "main issue")]
|
||||||
|
//! primary_span: SourceSpan,
|
||||||
|
//!
|
||||||
|
//! #[label("other label")]
|
||||||
|
//! other_span: SourceSpan,
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The `primary` parameter can be used at most once:
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! #[derive(Debug, Diagnostic, Error)]
|
||||||
|
//! #[error("oops!")]
|
||||||
|
//! struct MyError {
|
||||||
|
//! #[label(primary, "main issue")]
|
||||||
|
//! primary_span: SourceSpan,
|
||||||
|
//!
|
||||||
|
//! #[label(primary, "other label")] // Error: Cannot have more than one primary label.
|
||||||
|
//! other_span: SourceSpan,
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### ... collection of labels
|
||||||
|
//!
|
||||||
|
//! When the number of labels is unknown, you can use a collection of `SourceSpan`
|
||||||
|
//! (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
|
//! ## MSRV
|
||||||
//!
|
//!
|
||||||
//! This crate requires rustc 1.70.0 or later.
|
//! This crate requires rustc 1.82.0 or later.
|
||||||
//!
|
//!
|
||||||
//! ## Acknowledgements
|
//! ## Acknowledgements
|
||||||
//!
|
//!
|
||||||
|
|
@ -708,7 +820,7 @@ pub use miette_derive::*;
|
||||||
|
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use eyreish::*;
|
pub use eyreish::*;
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
#[cfg(feature = "fancy-base")]
|
||||||
pub use handler::*;
|
pub use handler::*;
|
||||||
pub use handlers::*;
|
pub use handlers::*;
|
||||||
pub use miette_diagnostic::*;
|
pub use miette_diagnostic::*;
|
||||||
|
|
@ -719,12 +831,13 @@ pub use protocol::*;
|
||||||
|
|
||||||
mod chain;
|
mod chain;
|
||||||
mod diagnostic_chain;
|
mod diagnostic_chain;
|
||||||
|
mod diagnostic_impls;
|
||||||
mod error;
|
mod error;
|
||||||
mod eyreish;
|
mod eyreish;
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
#[cfg(feature = "fancy-base")]
|
||||||
mod handler;
|
mod handler;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
#[cfg(feature = "fancy-base")]
|
||||||
pub mod highlighters;
|
pub mod highlighters;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod macro_helpers;
|
pub mod macro_helpers;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::protocol::{LabeledSpan, SourceSpan};
|
||||||
|
|
||||||
// Huge thanks to @jam1gamer for this hack:
|
// Huge thanks to @jam1gamer for this hack:
|
||||||
// https://twitter.com/jam1garner/status/1515887996444323840
|
// https://twitter.com/jam1garner/status/1515887996444323840
|
||||||
|
|
||||||
|
|
@ -36,3 +38,24 @@ impl<T> ToOption for &OptionalWrapper<T> {
|
||||||
Some(value)
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use crate::{MietteError, MietteSpanContents, SourceCode, SpanContents};
|
||||||
/// Utility struct for when you have a regular [`SourceCode`] type that doesn't
|
/// 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
|
/// implement `name`. For example [`String`]. Or if you want to override the
|
||||||
/// `name` returned by the `SourceCode`.
|
/// `name` returned by the `SourceCode`.
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct NamedSource<S: SourceCode + 'static> {
|
pub struct NamedSource<S: SourceCode + 'static> {
|
||||||
source: S,
|
source: S,
|
||||||
name: String,
|
name: String,
|
||||||
|
|
|
||||||
60
src/panic.rs
60
src/panic.rs
|
|
@ -1,7 +1,8 @@
|
||||||
use backtrace::Backtrace;
|
use std::{error::Error, fmt::Display};
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::{self as miette, Context, Diagnostic, Result};
|
use backtrace::Backtrace;
|
||||||
|
|
||||||
|
use crate::{Context, Diagnostic, Result};
|
||||||
|
|
||||||
/// Tells miette to render panics using its rendering engine.
|
/// Tells miette to render panics using its rendering engine.
|
||||||
pub fn set_panic_hook() {
|
pub fn set_panic_hook() {
|
||||||
|
|
@ -12,7 +13,7 @@ pub fn set_panic_hook() {
|
||||||
message = msg.to_string();
|
message = msg.to_string();
|
||||||
}
|
}
|
||||||
if let Some(msg) = payload.downcast_ref::<String>() {
|
if let Some(msg) = payload.downcast_ref::<String>() {
|
||||||
message = msg.clone();
|
message.clone_from(msg);
|
||||||
}
|
}
|
||||||
let mut report: Result<()> = Err(Panic(message).into());
|
let mut report: Result<()> = Err(Panic(message).into());
|
||||||
if let Some(loc) = info.location() {
|
if let Some(loc) = info.location() {
|
||||||
|
|
@ -25,11 +26,27 @@ pub fn set_panic_hook() {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
#[derive(Debug)]
|
||||||
#[error("{0}{}", Panic::backtrace())]
|
|
||||||
#[diagnostic(help("set the `RUST_BACKTRACE=1` environment variable to display a backtrace."))]
|
|
||||||
struct Panic(String);
|
struct Panic(String);
|
||||||
|
|
||||||
|
impl Display for Panic {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let msg = &self.0;
|
||||||
|
let panic = Panic::backtrace();
|
||||||
|
write!(f, "{msg}{panic}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for Panic {}
|
||||||
|
|
||||||
|
impl Diagnostic for Panic {
|
||||||
|
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||||
|
Some(Box::new(
|
||||||
|
"set the `RUST_BACKTRACE=1` environment variable to display a backtrace.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Panic {
|
impl Panic {
|
||||||
fn backtrace() -> String {
|
fn backtrace() -> String {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
@ -84,3 +101,32 @@ impl Panic {
|
||||||
"".into()
|
"".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn panic() {
|
||||||
|
let panic = Panic("ruh roh raggy".to_owned());
|
||||||
|
|
||||||
|
assert_eq!(panic.to_string(), "ruh roh raggy");
|
||||||
|
assert!(panic.source().is_none());
|
||||||
|
assert!(panic.code().is_none());
|
||||||
|
assert!(panic.severity().is_none());
|
||||||
|
assert_eq!(
|
||||||
|
panic.help().map(|h| h.to_string()),
|
||||||
|
Some(
|
||||||
|
"set the `RUST_BACKTRACE=1` environment variable to display a backtrace."
|
||||||
|
.to_owned()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert!(panic.url().is_none());
|
||||||
|
assert!(panic.source_code().is_none());
|
||||||
|
assert!(panic.labels().is_none());
|
||||||
|
assert!(panic.related().is_none());
|
||||||
|
assert!(panic.diagnostic_source().is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use std::{
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::MietteError;
|
use crate::{DiagnosticError, MietteError};
|
||||||
|
|
||||||
/// Adds rich metadata to your Error that can be used by
|
/// Adds rich metadata to your Error that can be used by
|
||||||
/// [`Report`](crate::Report) to print really nice and human-friendly error
|
/// [`Report`](crate::Report) to print really nice and human-friendly error
|
||||||
|
|
@ -69,7 +69,7 @@ pub trait Diagnostic: std::error::Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! box_impls {
|
macro_rules! box_error_impls {
|
||||||
($($box_type:ty),*) => {
|
($($box_type:ty),*) => {
|
||||||
$(
|
$(
|
||||||
impl std::error::Error for $box_type {
|
impl std::error::Error for $box_type {
|
||||||
|
|
@ -85,12 +85,29 @@ macro_rules! box_impls {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
box_impls! {
|
box_error_impls! {
|
||||||
Box<dyn Diagnostic>,
|
Box<dyn Diagnostic>,
|
||||||
Box<dyn Diagnostic + Send>,
|
Box<dyn Diagnostic + Send>,
|
||||||
Box<dyn Diagnostic + Send + Sync>
|
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>
|
impl<T: Diagnostic + Send + Sync + 'static> From<T>
|
||||||
for Box<dyn Diagnostic + Send + Sync + 'static>
|
for Box<dyn Diagnostic + Send + Sync + 'static>
|
||||||
{
|
{
|
||||||
|
|
@ -117,7 +134,7 @@ impl From<&str> for Box<dyn Diagnostic> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
|
impl From<&str> for Box<dyn Diagnostic + Send + Sync + '_> {
|
||||||
fn from(s: &str) -> Self {
|
fn from(s: &str) -> Self {
|
||||||
From::from(String::from(s))
|
From::from(String::from(s))
|
||||||
}
|
}
|
||||||
|
|
@ -157,18 +174,7 @@ impl From<String> for Box<dyn Diagnostic + Send + Sync> {
|
||||||
|
|
||||||
impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
|
impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
|
||||||
fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
|
fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
|
||||||
#[derive(thiserror::Error)]
|
Box::new(DiagnosticError(s))
|
||||||
#[error(transparent)]
|
|
||||||
struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
|
|
||||||
impl fmt::Debug for BoxedDiagnostic {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
fmt::Debug::fmt(&self.0, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Diagnostic for BoxedDiagnostic {}
|
|
||||||
|
|
||||||
Box::new(BoxedDiagnostic(s))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,8 +197,6 @@ pub enum Severity {
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialize_severity() {
|
fn test_serialize_severity() {
|
||||||
|
|
@ -278,6 +282,11 @@ impl LabeledSpan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
/// Makes a new label at specified span
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
@ -503,7 +512,7 @@ impl<'a> MietteSpanContents<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`language`](SourceCode::language) for syntax highlighting.
|
/// Sets the [`language`](SpanContents::language) for syntax highlighting.
|
||||||
pub fn with_language(mut self, language: impl Into<String>) -> Self {
|
pub fn with_language(mut self, language: impl Into<String>) -> Self {
|
||||||
self.language = Some(language.into());
|
self.language = Some(language.into());
|
||||||
self
|
self
|
||||||
|
|
@ -535,7 +544,7 @@ impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Span within a [`SourceCode`]
|
/// 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))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct SourceSpan {
|
pub struct SourceSpan {
|
||||||
/// The start of the span.
|
/// The start of the span.
|
||||||
|
|
@ -594,6 +603,28 @@ impl From<std::ops::Range<ByteOffset>> for SourceSpan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<std::ops::RangeInclusive<ByteOffset>> for SourceSpan {
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the total length of the inclusive range would overflow a
|
||||||
|
/// `usize`. This will only occur with the range `0..=usize::MAX`.
|
||||||
|
fn from(range: std::ops::RangeInclusive<ByteOffset>) -> Self {
|
||||||
|
let (start, end) = range.clone().into_inner();
|
||||||
|
Self {
|
||||||
|
offset: start.into(),
|
||||||
|
length: if range.is_empty() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
// will not overflow because `is_empty() == false` guarantees
|
||||||
|
// that `start <= end`
|
||||||
|
(end - start)
|
||||||
|
.checked_add(1)
|
||||||
|
.expect("length of inclusive range should fit in a usize")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<SourceOffset> for SourceSpan {
|
impl From<SourceOffset> for SourceSpan {
|
||||||
fn from(offset: SourceOffset) -> Self {
|
fn from(offset: SourceOffset) -> Self {
|
||||||
Self { offset, length: 0 }
|
Self { offset, length: 0 }
|
||||||
|
|
@ -637,7 +668,7 @@ pub type ByteOffset = usize;
|
||||||
/**
|
/**
|
||||||
Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`]
|
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))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct SourceOffset(ByteOffset);
|
pub struct SourceOffset(ByteOffset);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
/*!
|
/*!
|
||||||
Default trait implementations for [`SourceCode`].
|
Default trait implementations for [`SourceCode`].
|
||||||
*/
|
*/
|
||||||
use std::{
|
use std::{borrow::Cow, collections::VecDeque, fmt::Debug, sync::Arc};
|
||||||
borrow::{Cow, ToOwned},
|
|
||||||
collections::VecDeque,
|
|
||||||
fmt::Debug,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
|
use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
|
||||||
|
|
||||||
|
|
@ -109,7 +104,7 @@ impl SourceCode for [u8] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> SourceCode for &'src [u8] {
|
impl SourceCode for &[u8] {
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &SourceSpan,
|
span: &SourceSpan,
|
||||||
|
|
@ -148,7 +143,7 @@ impl SourceCode for str {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes `src: &'static str` or `struct S<'a> { src: &'a str }` usable.
|
/// Makes `src: &'static str` or `struct S<'a> { src: &'a str }` usable.
|
||||||
impl<'s> SourceCode for &'s str {
|
impl SourceCode for &str {
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &SourceSpan,
|
span: &SourceSpan,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#![cfg(feature = "fancy-no-backtrace")]
|
#![cfg(feature = "fancy-no-backtrace")]
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler, RgbColors};
|
use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler, RgbColors};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
@ -69,9 +68,7 @@ impl Drop for EnvVarGuard<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static COLOR_ENV_VARS: Mutex<()> = Mutex::new(());
|
||||||
static ref COLOR_ENV_VARS: Mutex<()> = Mutex::new(());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assert the color format used by a handler with different levels of terminal
|
/// Assert the color format used by a handler with different levels of terminal
|
||||||
/// support.
|
/// support.
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ fn related() {
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic(code(foo::bar::baz))]
|
#[diagnostic(code(foo::bar::baz))]
|
||||||
|
#[allow(dead_code)]
|
||||||
struct Foo {
|
struct Foo {
|
||||||
#[related]
|
#[related]
|
||||||
related: Vec<Baz>,
|
related: Vec<Baz>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
#[allow(dead_code)]
|
||||||
enum Bar {
|
enum Bar {
|
||||||
#[error("variant1")]
|
#[error("variant1")]
|
||||||
#[diagnostic(code(foo::bar::baz))]
|
#[diagnostic(code(foo::bar::baz))]
|
||||||
|
|
@ -29,6 +31,7 @@ fn related() {
|
||||||
|
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
#[error("welp2")]
|
#[error("welp2")]
|
||||||
|
#[allow(dead_code)]
|
||||||
struct Baz;
|
struct Baz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,6 +40,7 @@ fn related_report() {
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic(code(foo::bar::baz))]
|
#[diagnostic(code(foo::bar::baz))]
|
||||||
|
#[allow(dead_code)]
|
||||||
struct Foo {
|
struct Foo {
|
||||||
#[related]
|
#[related]
|
||||||
related: Vec<Report>,
|
related: Vec<Report>,
|
||||||
|
|
@ -288,6 +292,7 @@ fn test_snippet_named_struct() {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic(code(foo::bar::baz))]
|
#[diagnostic(code(foo::bar::baz))]
|
||||||
|
#[allow(dead_code)]
|
||||||
struct Foo<'a> {
|
struct Foo<'a> {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: &'a str,
|
src: &'a str,
|
||||||
|
|
@ -310,6 +315,7 @@ fn test_snippet_unnamed_struct() {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic(code(foo::bar::baz))]
|
#[diagnostic(code(foo::bar::baz))]
|
||||||
|
#[allow(dead_code)]
|
||||||
struct Foo<'a>(
|
struct Foo<'a>(
|
||||||
#[source_code] &'a str,
|
#[source_code] &'a str,
|
||||||
#[label("{0}")] SourceSpan,
|
#[label("{0}")] SourceSpan,
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,7 @@
|
||||||
use miette::{miette, Diagnostic, LabeledSpan, Report, SourceSpan};
|
use miette::{miette, Diagnostic, LabeledSpan, Report, SourceSpan};
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::ops::Deref;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
@ -159,7 +160,7 @@ impl Diagnostic for CustomDiagnostic {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_boxed_custom_diagnostic() {
|
fn test_boxed_custom_diagnostic() {
|
||||||
fn assert_report(report: &Report) {
|
fn assert_report<T: ?Sized + Diagnostic>(report: &impl Deref<Target = T>) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
report.source().map(|source| source.to_string()),
|
report.source().map(|source| source.to_string()),
|
||||||
Some("oh no!".to_owned()),
|
Some("oh no!".to_owned()),
|
||||||
|
|
@ -215,10 +216,16 @@ fn test_boxed_custom_diagnostic() {
|
||||||
let main_diagnostic = Box::new(main_diagnostic) as Box<dyn Diagnostic + Send + Sync + 'static>;
|
let main_diagnostic = Box::new(main_diagnostic) as Box<dyn Diagnostic + Send + Sync + 'static>;
|
||||||
let report = miette!(main_diagnostic);
|
let report = miette!(main_diagnostic);
|
||||||
assert_report(&report);
|
assert_report(&report);
|
||||||
|
|
||||||
|
// Now make sure that conversion to a trait-object is lossless!
|
||||||
|
let report_ref: &dyn Diagnostic = report.as_ref();
|
||||||
|
assert_report(&report_ref);
|
||||||
|
|
||||||
|
let report_box: Box<dyn Diagnostic> = report.into();
|
||||||
|
assert_report(&report_box);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "I don't know why this isn't working but it needs fixing."]
|
|
||||||
fn test_boxed_sources() {
|
fn test_boxed_sources() {
|
||||||
let error = MyError {
|
let error = MyError {
|
||||||
source: io::Error::new(io::ErrorKind::Other, "oh no!"),
|
source: io::Error::new(io::ErrorKind::Other, "oh no!"),
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -41,6 +41,14 @@ struct TestTupleError(#[diagnostic_source] AnErr);
|
||||||
#[error("TestError")]
|
#[error("TestError")]
|
||||||
struct TestBoxedError(#[diagnostic_source] Box<dyn Diagnostic>);
|
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)]
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
#[error("TestError")]
|
#[error("TestError")]
|
||||||
struct TestArcedError(#[diagnostic_source] std::sync::Arc<dyn Diagnostic>);
|
struct TestArcedError(#[diagnostic_source] std::sync::Arc<dyn Diagnostic>);
|
||||||
|
|
@ -71,6 +79,12 @@ fn test_diagnostic_source() {
|
||||||
let error = TestBoxedError(Box::new(AnErr));
|
let error = TestBoxedError(Box::new(AnErr));
|
||||||
assert!(error.diagnostic_source().is_some());
|
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));
|
let error = TestArcedError(std::sync::Arc::new(AnErr));
|
||||||
assert!(error.diagnostic_source().is_some());
|
assert!(error.diagnostic_source().is_some());
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +104,8 @@ fn test_diagnostic_source_pass_extra_info() {
|
||||||
.render_report(&mut out, &diag)
|
.render_report(&mut out, &diag)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("Error: {}", out);
|
println!("Error: {}", out);
|
||||||
let expected = r#" × TestError
|
let expected = r#"
|
||||||
|
× TestError
|
||||||
╰─▶ × A complex error happened
|
╰─▶ × A complex error happened
|
||||||
╭─[1:2]
|
╭─[1:2]
|
||||||
1 │ Hello
|
1 │ Hello
|
||||||
|
|
@ -103,7 +118,8 @@ fn test_diagnostic_source_pass_extra_info() {
|
||||||
|
|
||||||
this is a footer
|
this is a footer
|
||||||
"#
|
"#
|
||||||
.to_string();
|
.trim_start_matches('\n');
|
||||||
|
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,7 +140,8 @@ fn test_diagnostic_source_is_output() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("{}", out);
|
println!("{}", out);
|
||||||
|
|
||||||
let expected = r#" × TestError
|
let expected = r#"
|
||||||
|
× TestError
|
||||||
╰─▶ × A complex error happened
|
╰─▶ × A complex error happened
|
||||||
╭────
|
╭────
|
||||||
1 │ right here
|
1 │ right here
|
||||||
|
|
@ -133,11 +150,13 @@ fn test_diagnostic_source_is_output() {
|
||||||
╰────
|
╰────
|
||||||
help: That's where the error is!
|
help: That's where the error is!
|
||||||
|
|
||||||
"#;
|
"#
|
||||||
|
.trim_start_matches('\n');
|
||||||
|
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
#[error("A nested error happened")]
|
#[error("A nested error happened")]
|
||||||
struct NestedError {
|
struct NestedError {
|
||||||
|
|
@ -172,7 +191,8 @@ fn test_nested_diagnostic_source_is_output() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("{}", out);
|
println!("{}", out);
|
||||||
|
|
||||||
let expected = r#" × A nested error happened
|
let expected = r#"
|
||||||
|
× A nested error happened
|
||||||
├─▶ × TestError
|
├─▶ × TestError
|
||||||
│
|
│
|
||||||
╰─▶ × A complex error happened
|
╰─▶ × A complex error happened
|
||||||
|
|
@ -190,11 +210,13 @@ fn test_nested_diagnostic_source_is_output() {
|
||||||
╰────
|
╰────
|
||||||
|
|
||||||
Yooo, a footer
|
Yooo, a footer
|
||||||
"#;
|
"#
|
||||||
|
.trim_start_matches('\n');
|
||||||
|
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
#[error("A multi-error happened")]
|
#[error("A multi-error happened")]
|
||||||
struct MultiError {
|
struct MultiError {
|
||||||
|
|
@ -238,10 +260,12 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("{}", out);
|
println!("{}", out);
|
||||||
|
|
||||||
let expected = r#" × A nested error happened
|
let expected = r#"
|
||||||
|
× A nested error happened
|
||||||
╰─▶ × A multi-error happened
|
╰─▶ × A multi-error happened
|
||||||
|
|
||||||
Error: × A nested error happened
|
Error:
|
||||||
|
× A nested error happened
|
||||||
├─▶ × TestError
|
├─▶ × TestError
|
||||||
│
|
│
|
||||||
╰─▶ × A complex error happened
|
╰─▶ × A complex error happened
|
||||||
|
|
@ -257,7 +281,9 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
|
||||||
· ──┬─
|
· ──┬─
|
||||||
· ╰── here
|
· ╰── here
|
||||||
╰────
|
╰────
|
||||||
Error: × A complex error happened
|
|
||||||
|
Error:
|
||||||
|
× A complex error happened
|
||||||
╭────
|
╭────
|
||||||
1 │ You're actually a mess
|
1 │ You're actually a mess
|
||||||
· ──┬─
|
· ──┬─
|
||||||
|
|
@ -272,7 +298,120 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
|
||||||
╰────
|
╰────
|
||||||
|
|
||||||
Yooo, a footer
|
Yooo, a footer
|
||||||
"#;
|
"#
|
||||||
|
.trim_start_matches('\n');
|
||||||
|
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
|
#[test]
|
||||||
|
fn test_display_related_errors_as_nested() {
|
||||||
|
let inner_error = TestStructError {
|
||||||
|
asdf_inner_foo: SourceError {
|
||||||
|
code: String::from("This is another error"),
|
||||||
|
help: String::from("You should fix this"),
|
||||||
|
label: (3, 4),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let first_error = NestedError {
|
||||||
|
code: String::from("right here"),
|
||||||
|
label: (6, 4),
|
||||||
|
the_other_err: Box::new(inner_error),
|
||||||
|
};
|
||||||
|
let second_error = SourceError {
|
||||||
|
code: String::from("You're actually a mess"),
|
||||||
|
help: String::from("Get a grip..."),
|
||||||
|
label: (3, 4),
|
||||||
|
};
|
||||||
|
let diag = MultiError {
|
||||||
|
related_errs: vec![
|
||||||
|
Box::new(MultiError {
|
||||||
|
related_errs: vec![Box::new(first_error), Box::new(AnErr)],
|
||||||
|
}),
|
||||||
|
Box::new(second_error),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut out = String::new();
|
||||||
|
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
||||||
|
.with_width(80)
|
||||||
|
.with_show_related_as_nested(true)
|
||||||
|
.render_report(&mut out, &diag)
|
||||||
|
.unwrap();
|
||||||
|
println!("{}", out);
|
||||||
|
|
||||||
|
let expected = r#"
|
||||||
|
× A multi-error happened
|
||||||
|
├─▶ × A multi-error happened
|
||||||
|
│ ├─▶ × A nested error happened
|
||||||
|
│ │ ╭────
|
||||||
|
│ │ 1 │ right here
|
||||||
|
│ │ · ──┬─
|
||||||
|
│ │ · ╰── here
|
||||||
|
│ │ ╰────
|
||||||
|
│ ╰─▶ × AnErr
|
||||||
|
╰─▶ × A complex error happened
|
||||||
|
╭────
|
||||||
|
1 │ You're actually a mess
|
||||||
|
· ──┬─
|
||||||
|
· ╰── here
|
||||||
|
╰────
|
||||||
|
help: Get a grip...
|
||||||
|
"#
|
||||||
|
.trim_start_matches('\n');
|
||||||
|
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
|
#[error("A case1 error happened")]
|
||||||
|
enum NestedEnumError {
|
||||||
|
Case1 {
|
||||||
|
#[source_code]
|
||||||
|
code: String,
|
||||||
|
#[diagnostic_source]
|
||||||
|
the_other_err: Case1Inner,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
|
#[error("I am the inner error")]
|
||||||
|
struct Case1Inner {
|
||||||
|
#[label("inner-label")]
|
||||||
|
label: (usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
|
#[test]
|
||||||
|
fn source_is_inherited_to_causes() {
|
||||||
|
let diag = NestedEnumError::Case1 {
|
||||||
|
code: String::from("this is the parent source"),
|
||||||
|
the_other_err: Case1Inner { label: (8, 3) },
|
||||||
|
};
|
||||||
|
let mut out = String::new();
|
||||||
|
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
||||||
|
.with_width(80)
|
||||||
|
.with_footer("Yooo, a footer".to_string())
|
||||||
|
.render_report(&mut out, &diag)
|
||||||
|
.unwrap();
|
||||||
|
println!("{}", out);
|
||||||
|
|
||||||
|
let expected = r#"
|
||||||
|
× A case1 error happened
|
||||||
|
╰─▶ × I am the inner error
|
||||||
|
╭────
|
||||||
|
1 │ this is the parent source
|
||||||
|
· ─┬─
|
||||||
|
· ╰── inner-label
|
||||||
|
╰────
|
||||||
|
|
||||||
|
|
||||||
|
Yooo, a footer
|
||||||
|
"#
|
||||||
|
.trim_start_matches('\n');
|
||||||
|
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,6 @@ fn test_altdisplay() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "not really gonna work with the current printers"]
|
#[ignore = "not really gonna work with the current printers"]
|
||||||
#[cfg_attr(track_caller, ignore)]
|
|
||||||
fn test_debug() {
|
fn test_debug() {
|
||||||
assert_eq!(EXPECTED_DEBUG_F, format!("{:?}", f().unwrap_err()));
|
assert_eq!(EXPECTED_DEBUG_F, format!("{:?}", f().unwrap_err()));
|
||||||
assert_eq!(EXPECTED_DEBUG_G, format!("{:?}", g().unwrap_err()));
|
assert_eq!(EXPECTED_DEBUG_G, format!("{:?}", g().unwrap_err()));
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ mod drop;
|
||||||
|
|
||||||
use self::drop::{DetectDrop, Flag};
|
use self::drop::{DetectDrop, Flag};
|
||||||
use miette::Report;
|
use miette::Report;
|
||||||
use std::marker::Unpin;
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue