mirror of https://github.com/zkat/miette.git
Compare commits
No commits in common. "main" and "miette-derive-v5.10.0" have entirely different histories.
main
...
miette-der
|
|
@ -10,18 +10,14 @@ jobs:
|
||||||
name: Check fmt & build docs
|
name: Check fmt & build docs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v1
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
profile: minimal
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- name: Install cargo-readme
|
override: true
|
||||||
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
|
||||||
|
|
@ -32,53 +28,40 @@ jobs:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
features: [fancy, syntect-highlighter]
|
rust: [1.56.0, stable]
|
||||||
rust: [1.82.0, stable]
|
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
exclude:
|
|
||||||
- features: syntect-highlighter
|
|
||||||
rust: 1.82.0
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v1
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
profile: minimal
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
components: clippy
|
components: clippy
|
||||||
|
override: true
|
||||||
- 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 fancy
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: matrix.rust == '1.82.0'
|
if: matrix.rust == '1.56.0'
|
||||||
run: cargo test --all --verbose --features ${{matrix.features}} no-format-args-capture
|
run: cargo test --all --verbose --features fancy 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
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v1
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
profile: minimal
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
components: miri,rust-src
|
components: miri,rust-src
|
||||||
|
override: true
|
||||||
- name: Run tests with miri
|
- name: Run tests with miri
|
||||||
env:
|
env:
|
||||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
|
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
|
||||||
|
|
@ -92,10 +75,13 @@ jobs:
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v1
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
profile: minimal
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
- name: Run minimal version build
|
- name: Run minimal version build
|
||||||
run: cargo build -Z direct-minimal-versions --features fancy,no-format-args-capture
|
run: cargo build -Z minimal-versions --all-features
|
||||||
|
|
||||||
|
|
|
||||||
19
AGENTS.md
19
AGENTS.md
|
|
@ -1,19 +0,0 @@
|
||||||
# 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.
|
|
||||||
165
CHANGELOG.md
165
CHANGELOG.md
|
|
@ -1,170 +1,5 @@
|
||||||
# `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>
|
|
||||||
## 6.0.0 (2024-02-04)
|
|
||||||
|
|
||||||
The long-awaited 6.0 release of `miette` is here, with TONS of goodies, not
|
|
||||||
least of which is syntax highlighting support!
|
|
||||||
|
|
||||||
It also comes with a few breaking changes so make sure to check below and
|
|
||||||
update your code as needed!
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **labels:** Add support for primary label in specifying line/col information (#291) ([db0b7e40](https://github.com/zkat/miette/commit/db0b7e403a5ae52ae360991b6508490d8c579886))
|
|
||||||
* **derive:** Allow optional sources in derive (#301) ([88d00e0e](https://github.com/zkat/miette/commit/88d00e0e20bf95e03b8f81dcd5adf38c917e190e))
|
|
||||||
* **derive:** Make `miette-derive` be able to be turned off (#304) ([c7ba5b7e](https://github.com/zkat/miette/commit/c7ba5b7e52e05991cecd3ca925c710bbe49850b9))
|
|
||||||
* **graphical:** Expose additional `textwrap` options (#321) ([fd77257c](https://github.com/zkat/miette/commit/fd77257cee0f5d03aa7dccb4ba8cbaa40c1a88c6))
|
|
||||||
* **graphical:** support rendering labels that contain newlines (#318) ([865d67c8](https://github.com/zkat/miette/commit/865d67c8dda119ddd03ac43be22f4fa272a9f433))
|
|
||||||
* **graphical:** Add `wrap_lines: bool` option allowing wrapping be disabled entirely (#328) ([b0744462](https://github.com/zkat/miette/commit/b0744462adbbfbb6d845f382db36be883c7f3c45))
|
|
||||||
* **graphical:** render disjoint snippets separately for cleaner output (#324) ([19c22143](https://github.com/zkat/miette/commit/19c22143cb544616046784e35c5e78cc5b881289))
|
|
||||||
* **deps:** Bump terminal-size to v0.3.0 (#308) ([c0a298e5](https://github.com/zkat/miette/commit/c0a298e5a8d699acf9fcd61b5d5fa4f6279a47ab))
|
|
||||||
* **BREAKING CHANGE**: This requires an MSRV bump to 1.70.0.
|
|
||||||
* **source-code:** Don't override provided source code (#300) ([0d5c2ce7](https://github.com/zkat/miette/commit/0d5c2ce7536b0ea205346595d8a00d00bfb6cbd2))
|
|
||||||
* **BREAKING CHANGE**: Source code is no longer overridden if it was provided by the diagnostic's own `source_code()` impl.
|
|
||||||
* **source:** use `usize` for length (#265) ([fad0e76a](https://github.com/zkat/miette/commit/fad0e76ad2e19d5cac13cf8324338aca0d623d93))
|
|
||||||
* **BREAKING CHANGE**: This changes `SourceSpan`'s length type to `usize`.
|
|
||||||
* **source:** Allow inner source type of a NamedSource to be borrowed (#254) ([1df3b1a5](https://github.com/zkat/miette/commit/1df3b1a537f2e54cd40ec45f5cd851337a22e95a))
|
|
||||||
* **BREAKING CHANGE**: This makes the `NamedSource` type generic over its `Source` type, instead of boxing it.
|
|
||||||
* **highlighting:** add syntax highlighting support with syntect crate (#313) ([e65d0a78](https://github.com/zkat/miette/commit/e65d0a78cc639653f061a45d8ce35b1a3551ade7))
|
|
||||||
* **deps:** remove is-terminal dep in favor of `std::io::IsTerminal` ([e5c7ae46](https://github.com/zkat/miette/commit/e5c7ae469e40a8bc102e1fca3b8fd4b2ec137696))
|
|
||||||
* **deps:** remove once_cell dep in favor of `std::sync::OnceLock` ([4c48584f](https://github.com/zkat/miette/commit/4c48584f304414c6924bede3308b455cfef60749))
|
|
||||||
* **BREAKING CHANGE**: This requires an MSRV bump to 1.70.0.
|
|
||||||
* **deps:** bump some semver-breaking deps to newer versions ([29d000f2](https://github.com/zkat/miette/commit/29d000f201b259a056867a2876384f97653a6e9e))
|
|
||||||
* **MSRV:** Actually bump the MSRV to 1.70.0 ([ab59a7bc](https://github.com/zkat/miette/commit/ab59a7bc9bceace5761a862ee2ebff3e5943b12f))
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **misc:** Improve ci and fix clippy (#290) ([cc81382a](https://github.com/zkat/miette/commit/cc81382a6070dd226a20e4a39518d88e957ac0e1))
|
|
||||||
* **tests:** Fix `cargo test` with default features. (#294) ([1f448e47](https://github.com/zkat/miette/commit/1f448e47751d0f914134b0e9138fdb1a5a95d55c))
|
|
||||||
* **clippy:** Add missing semicolons where nothing is returned. (#293) ([06b34823](https://github.com/zkat/miette/commit/06b348230aaf153b8b050322f05e5d185351d2d1))
|
|
||||||
* **graphical:** Extend error text span to whole code points (#312) ([a8b4ae01](https://github.com/zkat/miette/commit/a8b4ae012aa0cf03b53a18f013c2b3f76c5040e7))
|
|
||||||
* **formatting:** Fix formatting bug when an empty span is not aligned to a char boundary (#314) ([3d6f903d](https://github.com/zkat/miette/commit/3d6f903df0e7c9d0eb9a1fdbbf0028bab5496429))
|
|
||||||
* **docs:** add example to README and docs fixing #96 (#319) ([251d6d59](https://github.com/zkat/miette/commit/251d6d59292397458328ef57fb7957faedafd019))
|
|
||||||
* **graphical:** rendering bug on small spans in large spans (#316) ([7ff4f874](https://github.com/zkat/miette/commit/7ff4f874d693a665af4df40f4e94505013e3e262))
|
|
||||||
* **graphical:** render cause chains for inner errors (#330) ([cb2ae2e1](https://github.com/zkat/miette/commit/cb2ae2e18b446a5e90885faf8a30b5672c307df8))
|
|
||||||
* **handler:** remove the two extra `is_terminal` sys call from `MietteHandlerOpts::build` (#325) ([f1dc89c0](https://github.com/zkat/miette/commit/f1dc89c07640445d224b61ef96c6b25fcdf62dee))
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
* **README:** Move import of `NamedResult` to where it is used (#309) ([d37ada87](https://github.com/zkat/miette/commit/d37ada876a5831d3f47622274e334c9a24aa5d2b))
|
|
||||||
|
|
||||||
<a name="5.10.0"></a>
|
<a name="5.10.0"></a>
|
||||||
## 5.10.0 (2023-07-16)
|
## 5.10.0 (2023-07-16)
|
||||||
|
|
||||||
|
|
|
||||||
74
Cargo.toml
74
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "miette"
|
name = "miette"
|
||||||
version = "7.6.0"
|
version = "5.10.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,68 +9,56 @@ 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.82.0"
|
rust-version = "1.56.0"
|
||||||
exclude = ["images/", "tests/", "miette-derive/"]
|
exclude = ["images/", "tests/", "miette-derive/"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
miette-derive = { path = "miette-derive", version = "=7.6.0", optional = true }
|
thiserror = "1.0.40"
|
||||||
unicode-width = "0.2.0"
|
miette-derive = { path = "miette-derive", version = "=5.10.0" }
|
||||||
cfg-if = "1.0.0"
|
once_cell = "1.8.0"
|
||||||
|
unicode-width = "0.1.9"
|
||||||
|
|
||||||
owo-colors = { version = "4.0.0", optional = true }
|
owo-colors = { version = "3.0.0", optional = true }
|
||||||
textwrap = { version = "0.16.0", default-features = false, features = ["unicode-linebreak", "unicode-width"], optional = true }
|
is-terminal = { version = "0.4.0", optional = true }
|
||||||
supports-hyperlinks = { version = "3.0.0", optional = true }
|
textwrap = { version = "0.15.0", optional = true }
|
||||||
supports-color = { version = "3.0.0", optional = true }
|
supports-hyperlinks = { version = "2.0.0", optional = true }
|
||||||
supports-unicode = { version = "3.0.0", optional = true }
|
supports-color = { version = "2.0.0", optional = true }
|
||||||
backtrace = { version = "0.3.69", optional = true }
|
supports-unicode = { version = "2.0.0", optional = true }
|
||||||
terminal_size = { version = "0.4.0", optional = true }
|
backtrace = { version = "0.3.61", optional = true }
|
||||||
|
terminal_size = { version = "0.1.17", optional = true }
|
||||||
backtrace-ext = { version = "0.2.1", optional = true }
|
backtrace-ext = { version = "0.2.1", optional = true }
|
||||||
serde = { version = "1.0.196", features = ["derive"], optional = true }
|
serde = { version = "1.0.162", features = ["derive"], optional = true }
|
||||||
syntect = { version = "5.1.0", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
thiserror = "2.0.11"
|
semver = "1.0.4"
|
||||||
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.3"
|
indenter = "0.3.0"
|
||||||
rustversion = "1.0"
|
rustversion = "1.0"
|
||||||
trybuild = { version = "1.0.89", features = ["diff"] }
|
trybuild = { version = "1.0.19", features = ["diff"] }
|
||||||
syn = { version = "2.0.87", features = ["full"] }
|
syn = { version = "2.0", features = ["full"] }
|
||||||
regex = "1.10"
|
regex = "1.5"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
|
||||||
serde = { version = "1.0.196", features = ["derive"] }
|
serde_json = "1.0.64"
|
||||||
serde_json = "1.0.113"
|
|
||||||
strip-ansi-escapes = "0.2.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["derive"]
|
default = []
|
||||||
derive = ["dep:miette-derive"]
|
|
||||||
no-format-args-capture = []
|
no-format-args-capture = []
|
||||||
fancy-base = [
|
|
||||||
"dep:owo-colors",
|
|
||||||
"dep:textwrap",
|
|
||||||
]
|
|
||||||
fancy-no-syscall = [
|
|
||||||
"fancy-base",
|
|
||||||
]
|
|
||||||
fancy-no-backtrace = [
|
fancy-no-backtrace = [
|
||||||
"fancy-base",
|
"owo-colors",
|
||||||
"dep:terminal_size",
|
"is-terminal",
|
||||||
"dep:supports-hyperlinks",
|
"textwrap",
|
||||||
"dep:supports-color",
|
"terminal_size",
|
||||||
"dep:supports-unicode",
|
"supports-hyperlinks",
|
||||||
|
"supports-color",
|
||||||
|
"supports-unicode",
|
||||||
]
|
]
|
||||||
fancy = ["fancy-no-backtrace", "dep:backtrace", "dep:backtrace-ext"]
|
fancy = ["fancy-no-backtrace", "backtrace", "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"]
|
|
||||||
|
|
|
||||||
225
README.md
225
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,15 +44,10 @@ diagnostic error code: ruget::api::bad_json
|
||||||
- [... 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)
|
|
||||||
- [... primary label](#-primary-label)
|
|
||||||
- [... collection of labels](#-collection-of-labels)
|
|
||||||
- [Acknowledgements](#acknowledgements)
|
- [Acknowledgements](#acknowledgements)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
|
|
@ -101,7 +96,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, NamedSource, SourceSpan};
|
use miette::{Diagnostic, SourceSpan};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
|
@ -115,7 +110,7 @@ struct MyBad {
|
||||||
// The Source that we're gonna be printing snippets out of.
|
// The Source that we're gonna be printing snippets out of.
|
||||||
// This can be a String if you don't have or care about file names.
|
// This can be a String if you don't have or care about file names.
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
// Snippets and highlights can be included in the diagnostic!
|
// Snippets and highlights can be included in the diagnostic!
|
||||||
#[label("This bit here")]
|
#[label("This bit here")]
|
||||||
bad_bit: SourceSpan,
|
bad_bit: SourceSpan,
|
||||||
|
|
@ -128,11 +123,12 @@ 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::Result;
|
use miette::{NamedSource, 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),
|
||||||
|
|
@ -162,20 +158,17 @@ 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:
|
||||||
\
|
\
|
||||||
diagnostic error code: oops::my::bad (link)
|
Error: Types mismatched for operation.
|
||||||
Error: oops!
|
Diagnostic severity: error
|
||||||
|
Begin snippet starting at line 1, column 1
|
||||||
\
|
\
|
||||||
Begin snippet for bad_file.rs starting
|
snippet line 1: 3 + "5"
|
||||||
at line 2, column 3
|
label starting at line 1, column 1: int
|
||||||
\
|
label starting at line 1, column 1: doesn't support these values.
|
||||||
snippet line 1: source
|
label starting at line 1, column 1: string
|
||||||
\
|
diagnostic help: Change int or string to be the right types and try again.
|
||||||
snippet line 2: text
|
diagnostic code: nu::parser::unsupported_operation
|
||||||
highlight starting at line 1, column 3: This bit here
|
For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
|
||||||
\
|
|
||||||
snippet line 3: here
|
|
||||||
\
|
|
||||||
diagnostic help: try doing it better next time?">
|
|
||||||
|
|
||||||
### Using
|
### Using
|
||||||
|
|
||||||
|
|
@ -211,17 +204,6 @@ 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)]
|
||||||
|
|
@ -256,7 +238,7 @@ use miette::{IntoDiagnostic, Result};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
pub fn some_tool() -> Result<Version> {
|
pub fn some_tool() -> Result<Version> {
|
||||||
"1.2.x".parse().into_diagnostic()
|
Ok("1.2.x".parse().into_diagnostic()?)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -271,24 +253,24 @@ use miette::{IntoDiagnostic, Result, WrapErr};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
pub fn some_tool() -> Result<Version> {
|
pub fn some_tool() -> Result<Version> {
|
||||||
"1.2.x"
|
Ok("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, Result};
|
use miette::{miette, IntoDiagnostic, Result, WrapErr};
|
||||||
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";
|
||||||
version
|
Ok(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.
|
||||||
|
|
@ -300,9 +282,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};
|
||||||
|
|
@ -323,23 +305,6 @@ enabled:
|
||||||
miette = { version = "X.Y.Z", features = ["fancy"] }
|
miette = { version = "X.Y.Z", features = ["fancy"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
Another way to display a diagnostic is by printing them using the debug formatter.
|
|
||||||
This is, in fact, what returning diagnostics from main ends up doing.
|
|
||||||
To do it yourself, you can write the following:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use miette::{IntoDiagnostic, Result};
|
|
||||||
use semver::Version;
|
|
||||||
|
|
||||||
fn just_a_random_function() {
|
|
||||||
let version_result: Result<Version> = "1.2.x".parse().into_diagnostic();
|
|
||||||
match version_result {
|
|
||||||
Err(e) => println!("{:?}", e),
|
|
||||||
Ok(version) => println!("{}", version),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### ... diagnostic code URLs
|
#### ... diagnostic code URLs
|
||||||
|
|
||||||
`miette` supports providing a URL for individual diagnostics. This URL will
|
`miette` supports providing a URL for individual diagnostics. This URL will
|
||||||
|
|
@ -441,7 +406,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
|
||||||
|
|
@ -477,19 +442,6 @@ 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
|
||||||
|
|
@ -642,7 +594,6 @@ miette::set_hook(Box::new(|_| {
|
||||||
.unicode(false)
|
.unicode(false)
|
||||||
.context_lines(3)
|
.context_lines(3)
|
||||||
.tab_width(4)
|
.tab_width(4)
|
||||||
.break_words(true)
|
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
|
|
@ -657,14 +608,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 '+'",
|
||||||
|
|
@ -673,120 +624,6 @@ let report = miette!(
|
||||||
println!("{:?}", report)
|
println!("{:?}", report)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ... syntax highlighting
|
|
||||||
|
|
||||||
`miette` can be configured to highlight syntax in source code snippets.
|
|
||||||
|
|
||||||
<!-- TODO: screenshot goes here once default Theme is decided -->
|
|
||||||
|
|
||||||
To use the built-in highlighting functionality, you must enable the
|
|
||||||
`syntect-highlighter` crate feature. When this feature is enabled, `miette` will
|
|
||||||
automatically use the [`syntect`] crate to highlight the `#[source_code]`
|
|
||||||
field of your [`Diagnostic`].
|
|
||||||
|
|
||||||
Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
|
|
||||||
* [`language()`](SpanContents::language) - Provides the name of the language
|
|
||||||
as a string. For example `"Rust"` will indicate Rust syntax highlighting.
|
|
||||||
You can set the language of the [`SpanContents`] produced by a
|
|
||||||
[`NamedSource`] via the [`with_language`](NamedSource::with_language)
|
|
||||||
method.
|
|
||||||
* [`name()`](SpanContents::name) - In the absence of an explicitly set
|
|
||||||
language, the name is assumed to contain a file name or file path.
|
|
||||||
The highlighter will check for a file extension at the end of the name and
|
|
||||||
try to guess the syntax from that.
|
|
||||||
|
|
||||||
If you want to use a custom highlighter, you can provide a custom
|
|
||||||
implementation of the [`Highlighter`](highlighters::Highlighter)
|
|
||||||
trait to [`MietteHandlerOpts`] by calling the
|
|
||||||
[`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
|
||||||
method. See the [`highlighters`] module docs for more details.
|
|
||||||
|
|
||||||
#### ... 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
|
|
||||||
|
|
||||||
This crate requires rustc 1.82.0 or later.
|
|
||||||
|
|
||||||
### Acknowledgements
|
### Acknowledgements
|
||||||
|
|
||||||
`miette` was not developed in a void. It owes enormous credit to various
|
`miette` was not developed in a void. It owes enormous credit to various
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
msrv = "1.82.0"
|
msrv = "1.56.0"
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
//! 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: 54 KiB After Width: | Height: | Size: 292 KiB |
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "miette-derive"
|
name = "miette-derive"
|
||||||
version = "7.6.0"
|
version = "5.10.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.83"
|
proc-macro2 = "1.0"
|
||||||
quote = "1.0.35"
|
quote = "1.0"
|
||||||
syn = "2.0.87"
|
syn = "2.0.11"
|
||||||
|
|
|
||||||
|
|
@ -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<_> = std::iter::repeat_n(quote! { _, }, *index).collect();
|
let underscores: Vec<_> = core::iter::repeat(quote! { _, }).take(*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,23 +16,14 @@ 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,
|
||||||
lbl_ty: LabelType,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LabelAttr {
|
struct LabelAttr {
|
||||||
label: Option<Display>,
|
label: Option<Display>,
|
||||||
lbl_ty: LabelType,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for LabelAttr {
|
impl Parse for LabelAttr {
|
||||||
|
|
@ -49,26 +40,10 @@ impl Parse for LabelAttr {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let la = input.lookahead1();
|
let la = input.lookahead1();
|
||||||
let (lbl_ty, label) = if la.peek(syn::token::Paren) {
|
let label = if la.peek(syn::token::Paren) {
|
||||||
// #[label(primary?, "{}", x)]
|
// #[label("{}", x)]
|
||||||
let content;
|
let content;
|
||||||
parenthesized!(content in input);
|
parenthesized!(content in input);
|
||||||
|
|
||||||
let attr = match content.parse::<Option<syn::Ident>>()? {
|
|
||||||
Some(ident) if ident == "primary" => {
|
|
||||||
let _ = content.parse::<Token![,]>();
|
|
||||||
LabelType::Primary
|
|
||||||
}
|
|
||||||
Some(ident) if ident == "collection" => {
|
|
||||||
let _ = content.parse::<Token![,]>();
|
|
||||||
LabelType::Collection
|
|
||||||
}
|
|
||||||
Some(_) => {
|
|
||||||
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or either the keyword `primary` or `collection`."));
|
|
||||||
}
|
|
||||||
_ => LabelType::Default,
|
|
||||||
};
|
|
||||||
|
|
||||||
if content.peek(syn::LitStr) {
|
if content.peek(syn::LitStr) {
|
||||||
let fmt = content.parse()?;
|
let fmt = content.parse()?;
|
||||||
let args = if content.is_empty() {
|
let args = if content.is_empty() {
|
||||||
|
|
@ -81,27 +56,22 @@ impl Parse for LabelAttr {
|
||||||
args,
|
args,
|
||||||
has_bonus_display: false,
|
has_bonus_display: false,
|
||||||
};
|
};
|
||||||
(attr, Some(display))
|
Some(display)
|
||||||
} else if !content.is_empty() {
|
|
||||||
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or either the keyword `primary` or `collection`."));
|
|
||||||
} else {
|
} else {
|
||||||
(attr, None)
|
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The first argument must be a literal string."));
|
||||||
}
|
}
|
||||||
} else if la.peek(Token![=]) {
|
} else if la.peek(Token![=]) {
|
||||||
// #[label = "blabla"]
|
// #[label = "blabla"]
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
(
|
Some(Display {
|
||||||
LabelType::Default,
|
fmt: input.parse()?,
|
||||||
Some(Display {
|
args: TokenStream::new(),
|
||||||
fmt: input.parse()?,
|
has_bonus_display: false,
|
||||||
args: TokenStream::new(),
|
})
|
||||||
has_bonus_display: false,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(LabelType::Default, None)
|
None
|
||||||
};
|
};
|
||||||
Ok(LabelAttr { label, lbl_ty })
|
Ok(LabelAttr { label })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,25 +100,12 @@ impl Labels {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
let LabelAttr { label, lbl_ty } =
|
let LabelAttr { label } =
|
||||||
syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
|
syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
|
||||||
|
|
||||||
if lbl_ty == LabelType::Primary
|
|
||||||
&& labels
|
|
||||||
.iter()
|
|
||||||
.any(|l: &Label| l.lbl_ty == LabelType::Primary)
|
|
||||||
{
|
|
||||||
return Err(syn::Error::new(
|
|
||||||
field.span(),
|
|
||||||
"Cannot have more than one primary label.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
labels.push(Label {
|
labels.push(Label {
|
||||||
label,
|
label,
|
||||||
span,
|
span,
|
||||||
ty: field.ty.clone(),
|
ty: field.ty.clone(),
|
||||||
lbl_ty,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,81 +119,36 @@ 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().filter_map(|highlight| {
|
let labels = self.0.iter().map(|highlight| {
|
||||||
let Label {
|
let Label { span, label, ty } = highlight;
|
||||||
span,
|
|
||||||
label,
|
|
||||||
ty,
|
|
||||||
lbl_ty,
|
|
||||||
} = highlight;
|
|
||||||
if *lbl_ty == LabelType::Collection {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
let display = if let Some(display) = label {
|
if let Some(display) = label {
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
quote! {
|
||||||
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
|
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||||
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
|
#var.clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! { std::option::Option::None }
|
quote! {
|
||||||
};
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
let ctor = if *lbl_ty == LabelType::Primary {
|
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||||
quote! { miette::LabeledSpan::new_primary_with_span }
|
std::option::Option::None,
|
||||||
} else {
|
#var.clone(),
|
||||||
quote! { miette::LabeledSpan::new_with_span }
|
))
|
||||||
};
|
}
|
||||||
|
|
||||||
Some(quote! {
|
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
|
||||||
.map(|#var| #ctor(
|
|
||||||
#display,
|
|
||||||
#var.clone(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
let collections_chain = self.0.iter().filter_map(|label| {
|
|
||||||
let Label {
|
|
||||||
span,
|
|
||||||
label,
|
|
||||||
ty: _,
|
|
||||||
lbl_ty,
|
|
||||||
} = label;
|
|
||||||
if *lbl_ty != LabelType::Collection {
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
let display = if let Some(display) = label {
|
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
|
||||||
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
|
||||||
} else {
|
|
||||||
quote! { std::option::Option::None }
|
|
||||||
};
|
|
||||||
Some(quote! {
|
|
||||||
.chain({
|
|
||||||
let display = #display;
|
|
||||||
self.#span.iter().map(move |span| {
|
|
||||||
use miette::macro_helpers::{ToLabelSpanWrapper,ToLabeledSpan};
|
|
||||||
let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone());
|
|
||||||
if display.is_some() && labeled_span.label().is_none() {
|
|
||||||
labeled_span.set_label(display.clone())
|
|
||||||
}
|
|
||||||
Some(labeled_span)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(quote! {
|
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)))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -248,11 +160,8 @@ 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().filter_map(|label| {
|
let variant_labels = labels.0.iter().map(|label| {
|
||||||
let Label { span, label, ty, lbl_ty } = label;
|
let Label { span, label, 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, .. }) => {
|
||||||
|
|
@ -260,56 +169,24 @@ impl Labels {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
let display = if let Some(display) = label {
|
if let Some(display) = label {
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
quote! {
|
||||||
} else {
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||||
quote! { std::option::Option::None }
|
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||||
};
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
let ctor = if *lbl_ty == LabelType::Primary {
|
#var.clone(),
|
||||||
quote! { miette::LabeledSpan::new_primary_with_span }
|
))
|
||||||
} else {
|
|
||||||
quote! { miette::LabeledSpan::new_with_span }
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(quote! {
|
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
|
||||||
.map(|#var| #ctor(
|
|
||||||
#display,
|
|
||||||
#var.clone(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
let collections_chain = labels.0.iter().filter_map(|label| {
|
|
||||||
let Label { span, label, ty: _, lbl_ty } = label;
|
|
||||||
if *lbl_ty != LabelType::Collection {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let field = match &span {
|
|
||||||
syn::Member::Named(ident) => ident.clone(),
|
|
||||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
|
||||||
format_ident!("_{}", index)
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
let display = if let Some(display) = label {
|
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
|
||||||
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
|
||||||
} else {
|
} else {
|
||||||
quote! { std::option::Option::None }
|
quote! {
|
||||||
};
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||||
Some(quote! {
|
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||||
.chain({
|
std::option::Option::None,
|
||||||
let display = #display;
|
#var.clone(),
|
||||||
#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 {
|
||||||
|
|
@ -317,12 +194,9 @@ 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;
|
||||||
let labels_iter = vec![
|
std::option::Option::Some(std::boxed::Box::new(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)))
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ use crate::{
|
||||||
|
|
||||||
pub struct SourceCode {
|
pub struct SourceCode {
|
||||||
source_code: syn::Member,
|
source_code: syn::Member,
|
||||||
is_option: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceCode {
|
impl SourceCode {
|
||||||
|
|
@ -28,19 +27,6 @@ impl SourceCode {
|
||||||
for (i, field) in fields.iter().enumerate() {
|
for (i, field) in fields.iter().enumerate() {
|
||||||
for attr in &field.attrs {
|
for attr in &field.attrs {
|
||||||
if attr.path().is_ident("source_code") {
|
if attr.path().is_ident("source_code") {
|
||||||
let is_option = if let syn::Type::Path(syn::TypePath {
|
|
||||||
path: syn::Path { segments, .. },
|
|
||||||
..
|
|
||||||
}) = &field.ty
|
|
||||||
{
|
|
||||||
segments
|
|
||||||
.last()
|
|
||||||
.map(|seg| seg.ident == "Option")
|
|
||||||
.unwrap_or(false)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let source_code = if let Some(ident) = field.ident.clone() {
|
let source_code = if let Some(ident) = field.ident.clone() {
|
||||||
syn::Member::Named(ident)
|
syn::Member::Named(ident)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -49,10 +35,7 @@ impl SourceCode {
|
||||||
span: field.span(),
|
span: field.span(),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
return Ok(Some(SourceCode {
|
return Ok(Some(SourceCode { source_code }));
|
||||||
source_code,
|
|
||||||
is_option,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,21 +45,11 @@ impl SourceCode {
|
||||||
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 src = &self.source_code;
|
let src = &self.source_code;
|
||||||
let ret = if self.is_option {
|
|
||||||
quote! {
|
|
||||||
self.#src.as_ref().map(|s| s as _)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
Some(&self.#src)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> {
|
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> {
|
||||||
let Self #display_pat = self;
|
let Self #display_pat = self;
|
||||||
#ret
|
Some(&self.#src)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -95,19 +68,10 @@ impl SourceCode {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let variant_name = ident.clone();
|
let variant_name = ident.clone();
|
||||||
let ret = if source_code.is_option {
|
|
||||||
quote! {
|
|
||||||
#field.as_ref().map(|s| s as _)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
std::option::Option::Some(#field)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match &fields {
|
match &fields {
|
||||||
syn::Fields::Unit => None,
|
syn::Fields::Unit => None,
|
||||||
_ => Some(quote! {
|
_ => Some(quote! {
|
||||||
Self::#variant_name #display_pat => #ret,
|
Self::#variant_name #display_pat => std::option::Option::Some(#field),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,40 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use syn::spanned::Spanned;
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
spanned::Spanned,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) enum MemberOrString {
|
||||||
|
Member(syn::Member),
|
||||||
|
String(syn::LitStr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for MemberOrString {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
use MemberOrString::*;
|
||||||
|
match self {
|
||||||
|
Member(member) => member.to_tokens(tokens),
|
||||||
|
String(string) => string.to_tokens(tokens),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for MemberOrString {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let lookahead = input.lookahead1();
|
||||||
|
if lookahead.peek(syn::Ident) || lookahead.peek(syn::LitInt) {
|
||||||
|
Ok(MemberOrString::Member(input.parse()?))
|
||||||
|
} else if lookahead.peek(syn::LitStr) {
|
||||||
|
Ok(MemberOrString::String(input.parse()?))
|
||||||
|
} else {
|
||||||
|
Err(syn::Error::new(
|
||||||
|
input.span(),
|
||||||
|
"Expected a string or a field reference.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
wrap_comments = true
|
||||||
|
format_code_in_doc_comments = true
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ 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 std::fmt::Debug for ErrorKind<'_> {
|
impl<'a> std::fmt::Debug for ErrorKind<'a> {
|
||||||
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 std::fmt::Debug for ErrorKind<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ErrorKind<'_> {
|
impl<'a> std::fmt::Display for ErrorKind<'a> {
|
||||||
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),
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
/*!
|
|
||||||
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>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
126
src/error.rs
126
src/error.rs
|
|
@ -1,125 +1,27 @@
|
||||||
use std::{
|
use std::io;
|
||||||
error::Error,
|
|
||||||
fmt::{self, Display},
|
|
||||||
io,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::Diagnostic;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::{self as miette, 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)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
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).
|
||||||
IoError(io::Error),
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(miette::io_error), url(docsrs))]
|
||||||
|
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")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(miette::span_out_of_bounds),
|
||||||
|
help("Double-check your spans. Do you have an off-by-one error?"),
|
||||||
|
url(docsrs)
|
||||||
|
)]
|
||||||
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 {
|
|
||||||
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
|
||||||
match self {
|
|
||||||
MietteError::IoError(_) => Some(Box::new("miette::io_error")),
|
|
||||||
MietteError::OutOfBounds => Some(Box::new("miette::span_out_of_bounds")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
|
||||||
match self {
|
|
||||||
MietteError::IoError(_) => None,
|
|
||||||
MietteError::OutOfBounds => Some(Box::new(
|
|
||||||
"Double-check your spans. Do you have an off-by-one error?",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
|
||||||
let crate_version = env!("CARGO_PKG_VERSION");
|
|
||||||
let variant = match self {
|
|
||||||
MietteError::IoError(_) => "#variant.IoError",
|
|
||||||
MietteError::OutOfBounds => "#variant.OutOfBounds",
|
|
||||||
};
|
|
||||||
Some(Box::new(format!(
|
|
||||||
"https://docs.rs/miette/{}/miette/enum.MietteError.html{}",
|
|
||||||
crate_version, variant,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,44 +38,6 @@ 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,7 +21,6 @@ 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,
|
||||||
|
|
@ -31,9 +30,9 @@ impl Report {
|
||||||
|
|
||||||
/// Create a new error object from a printable error message.
|
/// Create a new error object from a printable error message.
|
||||||
///
|
///
|
||||||
/// If the argument implements [`std::error::Error`], prefer `Report::new`
|
/// If the argument implements std::error::Error, prefer `Report::new`
|
||||||
/// instead which preserves the underlying error's cause chain and
|
/// instead which preserves the underlying error's cause chain and
|
||||||
/// backtrace. If the argument may or may not implement [`std::error::Error`]
|
/// backtrace. If the argument may or may not implement std::error::Error
|
||||||
/// now or in the future, use `miette!(err)` which handles either way
|
/// now or in the future, use `miette!(err)` which handles either way
|
||||||
/// correctly.
|
/// correctly.
|
||||||
///
|
///
|
||||||
|
|
@ -67,7 +66,6 @@ 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,
|
||||||
|
|
@ -88,7 +86,6 @@ 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,
|
||||||
|
|
@ -110,7 +107,6 @@ 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,
|
||||||
|
|
@ -135,7 +131,6 @@ 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,
|
||||||
|
|
@ -160,7 +155,6 @@ 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);
|
||||||
|
|
@ -186,7 +180,6 @@ 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,
|
||||||
|
|
@ -213,7 +206,7 @@ impl Report {
|
||||||
/// Create a new error from an error message to wrap the existing error.
|
/// Create a new error from an error message to wrap the existing error.
|
||||||
///
|
///
|
||||||
/// For attaching a higher level error message to a `Result` as it is
|
/// For attaching a higher level error message to a `Result` as it is
|
||||||
/// propagated, the [`WrapErr`](crate::WrapErr) extension trait may be more
|
/// propagated, the [crate::WrapErr] extension trait may be more
|
||||||
/// convenient than this function.
|
/// convenient than this function.
|
||||||
///
|
///
|
||||||
/// The primary reason to use `error.wrap_err(...)` instead of
|
/// The primary reason to use `error.wrap_err(...)` instead of
|
||||||
|
|
@ -240,7 +233,7 @@ impl Report {
|
||||||
unsafe { Report::construct(error, vtable, handler) }
|
unsafe { Report::construct(error, vtable, handler) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compatibility re-export of `wrap_err` for interop with `anyhow`
|
/// Compatibility re-export of wrap_err for interop with `anyhow`
|
||||||
pub fn context<D>(self, msg: D) -> Self
|
pub fn context<D>(self, msg: D) -> Self
|
||||||
where
|
where
|
||||||
D: Display + Send + Sync + 'static,
|
D: Display + Send + Sync + 'static,
|
||||||
|
|
@ -269,7 +262,6 @@ 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()) }
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +272,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().next_back().unwrap()
|
self.chain().last().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.
|
||||||
|
|
@ -418,21 +410,13 @@ impl Report {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide source code for this error
|
/// Provide source code for this error
|
||||||
pub fn with_source_code(self, source_code: impl SourceCode + 'static) -> Report {
|
pub fn with_source_code(self, source_code: impl SourceCode + Send + Sync + '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
|
||||||
|
|
@ -440,7 +424,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
@ -550,8 +533,7 @@ 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.
|
||||||
let unerased = e.cast::<ErrorImpl<E>>().boxed();
|
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>.
|
||||||
|
|
@ -562,8 +544,7 @@ 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.
|
||||||
let unerased = e.cast::<ErrorImpl<E>>().boxed();
|
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>.
|
||||||
|
|
@ -730,12 +711,22 @@ 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,
|
||||||
|
|
@ -755,7 +746,6 @@ 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 {
|
||||||
|
|
@ -767,7 +757,6 @@ 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 {
|
||||||
|
|
@ -779,14 +768,12 @@ 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,24 +1,12 @@
|
||||||
use std::{error::Error, fmt::Display};
|
use thiserror::Error;
|
||||||
|
|
||||||
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)]
|
#[derive(Debug, Error)]
|
||||||
pub(crate) struct DiagnosticError(pub(crate) Box<dyn std::error::Error + Send + Sync + 'static>);
|
#[error(transparent)]
|
||||||
|
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 {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -43,26 +31,3 @@ 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,7 +63,6 @@ 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,
|
||||||
|
|
@ -85,7 +84,6 @@ 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>,
|
||||||
|
|
@ -107,7 +105,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
use core::fmt::Display;
|
use core::fmt::Display;
|
||||||
|
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::sync::OnceLock;
|
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
#[allow(unreachable_pub)]
|
#[allow(unreachable_pub)]
|
||||||
pub use into_diagnostic::*;
|
pub use into_diagnostic::*;
|
||||||
|
|
@ -24,10 +25,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-base"))]
|
#[cfg(not(feature = "fancy-no-backtrace"))]
|
||||||
use crate::DebugReportHandler;
|
use crate::DebugReportHandler;
|
||||||
use crate::Diagnostic;
|
use crate::Diagnostic;
|
||||||
#[cfg(feature = "fancy-base")]
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
use crate::MietteHandler;
|
use crate::MietteHandler;
|
||||||
|
|
||||||
use error::ErrorImpl;
|
use error::ErrorImpl;
|
||||||
|
|
@ -57,11 +58,11 @@ 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>;
|
||||||
|
|
||||||
static HOOK: OnceLock<ErrorHook> = OnceLock::new();
|
static HOOK: OnceCell<ErrorHook> = OnceCell::new();
|
||||||
|
|
||||||
/// Error indicating that [`set_hook()`] was unable to install the provided
|
/// Error indicating that [`set_hook()`] was unable to install the provided
|
||||||
/// [`ErrorHook`].
|
/// [`ErrorHook`].
|
||||||
|
|
@ -102,14 +103,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-base")]
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
return Box::new(MietteHandler::new());
|
return Box::new(MietteHandler::new());
|
||||||
#[cfg(not(feature = "fancy-base"))]
|
#[cfg(not(feature = "fancy-no-backtrace"))]
|
||||||
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 +122,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 +131,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,7 +173,11 @@ pub trait ReportHandler: core::any::Any + Send + Sync {
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn debug(&self, error: &dyn Diagnostic, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
|
fn debug(
|
||||||
|
&self,
|
||||||
|
error: &(dyn Diagnostic),
|
||||||
|
f: &mut core::fmt::Formatter<'_>,
|
||||||
|
) -> core::fmt::Result;
|
||||||
|
|
||||||
/// Override for the `Display` format
|
/// Override for the `Display` format
|
||||||
fn display(
|
fn display(
|
||||||
|
|
@ -471,7 +476,6 @@ 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<T> Copy for Ref<'_, T> where T: ?Sized {}
|
impl<'a, T> Copy for Ref<'a, T> where T: ?Sized {}
|
||||||
|
|
||||||
impl<T> Clone for Ref<'_, T>
|
impl<'a, T> Clone for Ref<'a, T>
|
||||||
where
|
where
|
||||||
T: ?Sized,
|
T: ?Sized,
|
||||||
{
|
{
|
||||||
|
|
@ -132,9 +132,9 @@ where
|
||||||
lifetime: PhantomData<&'a mut T>,
|
lifetime: PhantomData<&'a mut T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Copy for Mut<'_, T> where T: ?Sized {}
|
impl<'a, T> Copy for Mut<'a, T> where T: ?Sized {}
|
||||||
|
|
||||||
impl<T> Clone for Mut<'_, T>
|
impl<'a, T> Clone for Mut<'a, T>
|
||||||
where
|
where
|
||||||
T: ?Sized,
|
T: ?Sized,
|
||||||
{
|
{
|
||||||
|
|
@ -173,7 +173,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Mut<'_, T> {
|
impl<'a, T> Mut<'a, 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,6 +9,11 @@ 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,
|
||||||
|
|
@ -30,9 +35,6 @@ 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,
|
||||||
|
|
@ -54,6 +56,21 @@ 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>);
|
||||||
|
|
||||||
|
|
@ -146,7 +163,7 @@ impl<E: Diagnostic, C: SourceCode> Diagnostic for WithSourceCode<E, C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
||||||
self.error.source_code().or(Some(&self.source_code))
|
Some(&self.source_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||||
|
|
@ -180,7 +197,7 @@ impl<C: SourceCode> Diagnostic for WithSourceCode<Report, C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
||||||
self.error.source_code().or(Some(&self.source_code))
|
Some(&self.source_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||||
|
|
@ -215,88 +232,3 @@ impl<C> StdError for WithSourceCode<Report, C> {
|
||||||
self.error.source()
|
self.error.source()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::{Diagnostic, LabeledSpan, Report, SourceCode, SourceSpan};
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
#[error("inner")]
|
|
||||||
struct Inner {
|
|
||||||
pub(crate) at: SourceSpan,
|
|
||||||
pub(crate) source_code: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Diagnostic for Inner {
|
|
||||||
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
|
|
||||||
Some(Box::new(std::iter::once(LabeledSpan::underline(self.at))))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source_code(&self) -> Option<&dyn SourceCode> {
|
|
||||||
self.source_code.as_ref().map(|s| s as _)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_override() {
|
|
||||||
let inner_source = "hello world";
|
|
||||||
let outer_source = "abc";
|
|
||||||
|
|
||||||
let report = Report::from(Inner {
|
|
||||||
at: (0..5).into(),
|
|
||||||
source_code: Some(inner_source.to_string()),
|
|
||||||
})
|
|
||||||
.with_source_code(outer_source.to_string());
|
|
||||||
|
|
||||||
let underlined = String::from_utf8(
|
|
||||||
report
|
|
||||||
.source_code()
|
|
||||||
.unwrap()
|
|
||||||
.read_span(&(0..5).into(), 0, 0)
|
|
||||||
.unwrap()
|
|
||||||
.data()
|
|
||||||
.to_vec(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(underlined, "hello");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "fancy")]
|
|
||||||
fn two_source_codes() {
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
#[error("outer")]
|
|
||||||
struct Outer {
|
|
||||||
pub(crate) errors: Vec<Inner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Diagnostic for Outer {
|
|
||||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
|
||||||
Some(Box::new(self.errors.iter().map(|e| e as _)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let inner_source = "hello world";
|
|
||||||
let outer_source = "abc";
|
|
||||||
|
|
||||||
let report = Report::from(Outer {
|
|
||||||
errors: vec![
|
|
||||||
Inner {
|
|
||||||
at: (0..5).into(),
|
|
||||||
source_code: Some(inner_source.to_string()),
|
|
||||||
},
|
|
||||||
Inner {
|
|
||||||
at: (1..2).into(),
|
|
||||||
source_code: None,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
.with_source_code(outer_source.to_string());
|
|
||||||
|
|
||||||
let message = format!("{:?}", report);
|
|
||||||
assert!(message.contains(inner_source));
|
|
||||||
assert!(message.contains(outer_source));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
373
src/handler.rs
373
src/handler.rs
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::highlighters::Highlighter;
|
use std::fmt;
|
||||||
use crate::highlighters::MietteHighlighter;
|
|
||||||
use crate::protocol::Diagnostic;
|
use crate::protocol::Diagnostic;
|
||||||
use crate::GraphicalReportHandler;
|
use crate::GraphicalReportHandler;
|
||||||
use crate::GraphicalTheme;
|
use crate::GraphicalTheme;
|
||||||
|
|
@ -7,21 +7,24 @@ 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, Default)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
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.
|
||||||
|
|
||||||
|
|
@ -52,12 +55,6 @@ pub struct MietteHandlerOpts {
|
||||||
pub(crate) context_lines: Option<usize>,
|
pub(crate) context_lines: Option<usize>,
|
||||||
pub(crate) tab_width: Option<usize>,
|
pub(crate) tab_width: Option<usize>,
|
||||||
pub(crate) with_cause_chain: Option<bool>,
|
pub(crate) with_cause_chain: Option<bool>,
|
||||||
pub(crate) break_words: Option<bool>,
|
|
||||||
pub(crate) wrap_lines: Option<bool>,
|
|
||||||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
|
||||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
|
||||||
pub(crate) highlighter: Option<MietteHighlighter>,
|
|
||||||
pub(crate) show_related_as_nested: Option<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MietteHandlerOpts {
|
impl MietteHandlerOpts {
|
||||||
|
|
@ -83,81 +80,12 @@ impl MietteHandlerOpts {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a syntax highlighter when rendering in graphical mode.
|
|
||||||
/// Use [`force_graphical()`](MietteHandlerOpts::force_graphical()) to
|
|
||||||
/// force graphical mode.
|
|
||||||
///
|
|
||||||
/// Syntax highlighting is disabled by default unless the
|
|
||||||
/// `syntect-highlighter` feature is enabled. Call this method
|
|
||||||
/// to override the default and use a custom highlighter
|
|
||||||
/// implementation instead.
|
|
||||||
///
|
|
||||||
/// Use
|
|
||||||
/// [`without_syntax_highlighting()`](MietteHandlerOpts::without_syntax_highlighting())
|
|
||||||
/// To disable highlighting completely.
|
|
||||||
///
|
|
||||||
/// Setting this option will not force color output. In all cases, the
|
|
||||||
/// current color configuration via
|
|
||||||
/// [`color()`](MietteHandlerOpts::color()) takes precedence over
|
|
||||||
/// highlighter configuration. However, this option does take precedence over
|
|
||||||
/// [`rgb_colors()`](MietteHandlerOpts::rgb_colors()) (meaning syntax highlighting will be
|
|
||||||
/// enabled regardless of the value of [`MietteHandlerOpts::rgb_colors`]).
|
|
||||||
pub fn with_syntax_highlighting(
|
|
||||||
mut self,
|
|
||||||
highlighter: impl Highlighter + Send + Sync + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.highlighter = Some(MietteHighlighter::from(highlighter));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Disables syntax highlighting when rendering in graphical mode.
|
|
||||||
/// Use [`force_graphical()`](MietteHandlerOpts::force_graphical()) to
|
|
||||||
/// force graphical mode.
|
|
||||||
///
|
|
||||||
/// Syntax highlighting is disabled by default unless the
|
|
||||||
/// `syntect-highlighter` feature is enabled. Call this method if you want
|
|
||||||
/// to disable highlighting when building with this feature.
|
|
||||||
pub fn without_syntax_highlighting(mut self) -> Self {
|
|
||||||
self.highlighter = Some(MietteHighlighter::nocolor());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the width to wrap the report at. Defaults to 80.
|
/// Sets the width to wrap the report at. Defaults to 80.
|
||||||
pub fn width(mut self, width: usize) -> Self {
|
pub fn width(mut self, width: usize) -> Self {
|
||||||
self.width = Some(width);
|
self.width = Some(width);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If true, long lines can be wrapped.
|
|
||||||
///
|
|
||||||
/// If false, long lines will not be broken when they exceed the width.
|
|
||||||
///
|
|
||||||
/// Defaults to true.
|
|
||||||
pub fn wrap_lines(mut self, wrap_lines: bool) -> Self {
|
|
||||||
self.wrap_lines = Some(wrap_lines);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If true, long words can be broken when wrapping.
|
|
||||||
///
|
|
||||||
/// If false, long words will not be broken when they exceed the width.
|
|
||||||
///
|
|
||||||
/// Defaults to true.
|
|
||||||
pub fn break_words(mut self, break_words: bool) -> Self {
|
|
||||||
self.break_words = Some(break_words);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// Sets the `textwrap::WordSeparator` to use when determining wrap points.
|
|
||||||
pub fn word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
|
|
||||||
self.word_separator = Some(word_separator);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `textwrap::WordSplitter` to use when determining wrap points.
|
|
||||||
pub fn word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
|
|
||||||
self.word_splitter = Some(word_splitter);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// Include the cause chain of the top-level error in the report.
|
/// Include the cause chain of the top-level error in the report.
|
||||||
pub fn with_cause_chain(mut self) -> Self {
|
pub fn with_cause_chain(mut self) -> Self {
|
||||||
self.with_cause_chain = Some(true);
|
self.with_cause_chain = Some(true);
|
||||||
|
|
@ -170,18 +98,6 @@ 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.
|
||||||
///
|
///
|
||||||
|
|
@ -205,8 +121,6 @@ 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
|
||||||
|
|
@ -277,15 +191,17 @@ 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 syscall::supports_unicode() => ThemeCharacters::unicode(),
|
None if supports_unicode::on(supports_unicode::Stream::Stderr) => {
|
||||||
|
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_has_16m) = syscall::supports_color_has_16m() {
|
} else if let Some(color) = supports_color::on(supports_color::Stream::Stderr) {
|
||||||
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) {
|
||||||
|
|
@ -296,13 +212,11 @@ impl MietteHandlerOpts {
|
||||||
} else {
|
} else {
|
||||||
ThemeStyles::none()
|
ThemeStyles::none()
|
||||||
};
|
};
|
||||||
let highlighter_opt =
|
|
||||||
HighlighterOption::select(self.color, self.highlighter, syscall::supports_color());
|
|
||||||
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()
|
||||||
.with_width(width)
|
.with_width(width)
|
||||||
.with_links(linkify);
|
.with_links(linkify)
|
||||||
handler.highlighter = highlighter_opt.into();
|
.with_theme(theme);
|
||||||
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();
|
||||||
|
|
@ -319,22 +233,6 @@ impl MietteHandlerOpts {
|
||||||
if let Some(w) = self.tab_width {
|
if let Some(w) = self.tab_width {
|
||||||
handler = handler.tab_width(w);
|
handler = handler.tab_width(w);
|
||||||
}
|
}
|
||||||
if let Some(b) = self.break_words {
|
|
||||||
handler = handler.with_break_words(b)
|
|
||||||
}
|
|
||||||
if let Some(b) = self.wrap_lines {
|
|
||||||
handler = handler.with_wrap_lines(b)
|
|
||||||
}
|
|
||||||
if let Some(s) = self.word_separator {
|
|
||||||
handler = handler.with_word_separator(s)
|
|
||||||
}
|
|
||||||
if let Some(s) = self.word_splitter {
|
|
||||||
handler = handler.with_word_splitter(s)
|
|
||||||
}
|
|
||||||
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),
|
||||||
}
|
}
|
||||||
|
|
@ -359,13 +257,26 @@ impl MietteHandlerOpts {
|
||||||
if let Some(linkify) = self.linkify {
|
if let Some(linkify) = self.linkify {
|
||||||
linkify
|
linkify
|
||||||
} else {
|
} else {
|
||||||
syscall::supports_hyperlinks()
|
supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
pub(crate) fn get_width(&self) -> usize {
|
pub(crate) fn get_width(&self) -> usize {
|
||||||
self.width
|
self.width.unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| syscall::terminal_width().unwrap_or(80))
|
terminal_size::terminal_size()
|
||||||
|
.unwrap_or((terminal_size::Width(80), terminal_size::Height(0)))
|
||||||
|
.0
|
||||||
|
.0 as usize
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(miri)]
|
||||||
|
// miri doesn't support a syscall (specifically ioctl)
|
||||||
|
// performed by terminal_size, which causes test execution to fail
|
||||||
|
// so when miri is running we'll just fallback to a constant
|
||||||
|
pub(crate) fn get_width(&self) -> usize {
|
||||||
|
self.width.unwrap_or(80)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -402,7 +313,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);
|
||||||
}
|
}
|
||||||
|
|
@ -410,219 +321,3 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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()))?;
|
||||||
|
|
@ -96,7 +96,7 @@ impl JSONReportHandler {
|
||||||
}
|
}
|
||||||
write!(f, r#""{}""#, escape(&error.to_string()))?;
|
write!(f, r#""{}""#, escape(&error.to_string()))?;
|
||||||
}
|
}
|
||||||
write!(f, "],")?;
|
write!(f, "],")?
|
||||||
} else {
|
} else {
|
||||||
write!(f, r#""causes": [],"#)?;
|
write!(f, r#""causes": [],"#)?;
|
||||||
}
|
}
|
||||||
|
|
@ -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-base")]
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
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-base")]
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
pub use theme::*;
|
pub use theme::*;
|
||||||
|
|
||||||
mod debug;
|
mod debug;
|
||||||
#[cfg(feature = "fancy-base")]
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
mod graphical;
|
mod graphical;
|
||||||
mod json;
|
mod json;
|
||||||
mod narratable;
|
mod narratable;
|
||||||
#[cfg(feature = "fancy-base")]
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use std::io::IsTerminal;
|
use is_terminal::IsTerminal;
|
||||||
|
|
||||||
use owo_colors::Style;
|
use owo_colors::Style;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -56,9 +55,9 @@ impl GraphicalTheme {
|
||||||
|
|
||||||
/// A "basic" graphical theme that skips colors and unicode characters and
|
/// A "basic" graphical theme that skips colors and unicode characters and
|
||||||
/// just does monochrome ascii art. If you want a completely non-graphical
|
/// just does monochrome ascii art. If you want a completely non-graphical
|
||||||
/// rendering of your [`Diagnostic`](crate::Diagnostic)s, check out
|
/// rendering of your `Diagnostic`s, check out
|
||||||
/// [`NarratableReportHandler`](crate::NarratableReportHandler), or write
|
/// [crate::NarratableReportHandler], or write your own
|
||||||
/// your own [`ReportHandler`](crate::ReportHandler)
|
/// [crate::ReportHandler]!
|
||||||
pub fn none() -> Self {
|
pub fn none() -> Self {
|
||||||
Self {
|
Self {
|
||||||
characters: ThemeCharacters::ascii(),
|
characters: ThemeCharacters::ascii(),
|
||||||
|
|
@ -71,7 +70,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::none()
|
Self::ascii()
|
||||||
}
|
}
|
||||||
Ok(string) if string != "0" => Self::unicode_nocolor(),
|
Ok(string) if string != "0" => Self::unicode_nocolor(),
|
||||||
_ => Self::unicode(),
|
_ => Self::unicode(),
|
||||||
|
|
@ -80,8 +79,7 @@ impl Default for GraphicalTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Styles for various parts of graphical rendering for the
|
Styles for various parts of graphical rendering for the [crate::GraphicalReportHandler].
|
||||||
[`GraphicalReportHandler`](crate::GraphicalReportHandler).
|
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ThemeStyles {
|
pub struct ThemeStyles {
|
||||||
|
|
@ -161,7 +159,7 @@ impl ThemeStyles {
|
||||||
// https://github.com/zesterer/ariadne/blob/e3cb394cb56ecda116a0a1caecd385a49e7f6662/src/draw.rs
|
// https://github.com/zesterer/ariadne/blob/e3cb394cb56ecda116a0a1caecd385a49e7f6662/src/draw.rs
|
||||||
|
|
||||||
/// Characters to be used when drawing when using
|
/// Characters to be used when drawing when using
|
||||||
/// [`GraphicalReportHandler`](crate::GraphicalReportHandler).
|
/// [crate::GraphicalReportHandler].
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct ThemeCharacters {
|
pub struct ThemeCharacters {
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
use owo_colors::Style;
|
|
||||||
|
|
||||||
use crate::SpanContents;
|
|
||||||
|
|
||||||
use super::{Highlighter, HighlighterState};
|
|
||||||
|
|
||||||
/// The default syntax highlighter. It applies `Style::default()` to input text.
|
|
||||||
/// This is used by default when no syntax highlighting features are enabled.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BlankHighlighter;
|
|
||||||
|
|
||||||
impl Highlighter for BlankHighlighter {
|
|
||||||
fn start_highlighter_state<'h>(
|
|
||||||
&'h self,
|
|
||||||
_source: &dyn SpanContents<'_>,
|
|
||||||
) -> Box<dyn super::HighlighterState + 'h> {
|
|
||||||
Box::new(BlankHighlighterState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for BlankHighlighter {
|
|
||||||
fn default() -> Self {
|
|
||||||
BlankHighlighter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The default highlighter state. It applies `Style::default()` to input text.
|
|
||||||
/// This is used by default when no syntax highlighting features are enabled.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BlankHighlighterState;
|
|
||||||
|
|
||||||
impl HighlighterState for BlankHighlighterState {
|
|
||||||
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<owo_colors::Styled<&'s str>> {
|
|
||||||
vec![Style::default().style(line)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
//! This module provides a trait for creating custom syntax highlighters that
|
|
||||||
//! highlight [`Diagnostic`](crate::Diagnostic) source code with ANSI escape
|
|
||||||
//! sequences when rendering with the [`GraphicalReportHighlighter`](crate::GraphicalReportHandler).
|
|
||||||
//!
|
|
||||||
//! It also provides built-in highlighter implementations that you can use out of the box.
|
|
||||||
//! By default, there are no syntax highlighters exported by miette
|
|
||||||
//! (except for the no-op [`BlankHighlighter`]).
|
|
||||||
//! To enable support for specific highlighters, you should enable their associated feature flag.
|
|
||||||
//!
|
|
||||||
//! Currently supported syntax highlighters and their feature flags:
|
|
||||||
//! * `syntect-highlighter` - Enables [`syntect`](https://docs.rs/syntect/latest/syntect/) syntax highlighting support via the [`SyntectHighlighter`]
|
|
||||||
//!
|
|
||||||
|
|
||||||
use std::{ops::Deref, sync::Arc};
|
|
||||||
|
|
||||||
use crate::SpanContents;
|
|
||||||
use owo_colors::Styled;
|
|
||||||
|
|
||||||
#[cfg(feature = "syntect-highlighter")]
|
|
||||||
pub use self::syntect::*;
|
|
||||||
pub use blank::*;
|
|
||||||
|
|
||||||
mod blank;
|
|
||||||
#[cfg(feature = "syntect-highlighter")]
|
|
||||||
mod syntect;
|
|
||||||
|
|
||||||
/// A syntax highlighter for highlighting miette [`SourceCode`](crate::SourceCode) snippets.
|
|
||||||
pub trait Highlighter {
|
|
||||||
/// Creates a new [`HighlighterState`] to begin parsing and highlighting
|
|
||||||
/// a [`SpanContents`].
|
|
||||||
///
|
|
||||||
/// The [`GraphicalReportHandler`](crate::GraphicalReportHandler) will call
|
|
||||||
/// this method at the start of rendering a [`SpanContents`].
|
|
||||||
///
|
|
||||||
/// The [`SpanContents`] is provided as input only so that the [`Highlighter`]
|
|
||||||
/// can detect language syntax and make other initialization decisions prior
|
|
||||||
/// to highlighting, but it is not intended that the Highlighter begin
|
|
||||||
/// highlighting at this point. The returned [`HighlighterState`] is
|
|
||||||
/// responsible for the actual rendering.
|
|
||||||
fn start_highlighter_state<'h>(
|
|
||||||
&'h self,
|
|
||||||
source: &dyn SpanContents<'_>,
|
|
||||||
) -> Box<dyn HighlighterState + 'h>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A stateful highlighter that incrementally highlights lines of a particular
|
|
||||||
/// source code.
|
|
||||||
///
|
|
||||||
/// The [`GraphicalReportHandler`](crate::GraphicalReportHandler)
|
|
||||||
/// will create a highlighter state by calling
|
|
||||||
/// [`start_highlighter_state`](Highlighter::start_highlighter_state) at the
|
|
||||||
/// start of rendering, then it will iteratively call
|
|
||||||
/// [`highlight_line`](HighlighterState::highlight_line) to render individual
|
|
||||||
/// highlighted lines. This allows [`Highlighter`] implementations to maintain
|
|
||||||
/// mutable parsing and highlighting state.
|
|
||||||
pub trait HighlighterState {
|
|
||||||
/// Highlight an individual line from the source code by returning a vector of [Styled]
|
|
||||||
/// regions.
|
|
||||||
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Arcified trait object for Highlighter. Used internally by [`GraphicalReportHandler`]
|
|
||||||
///
|
|
||||||
/// Wrapping the trait object in this way allows us to implement `Debug` and `Clone`.
|
|
||||||
#[derive(Clone)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub(crate) struct MietteHighlighter(Arc<dyn Highlighter + Send + Sync>);
|
|
||||||
|
|
||||||
impl MietteHighlighter {
|
|
||||||
pub(crate) fn nocolor() -> Self {
|
|
||||||
Self::from(BlankHighlighter)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "syntect-highlighter")]
|
|
||||||
pub(crate) fn syntect_truecolor() -> Self {
|
|
||||||
Self::from(SyntectHighlighter::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for MietteHighlighter {
|
|
||||||
#[cfg(feature = "syntect-highlighter")]
|
|
||||||
fn default() -> Self {
|
|
||||||
use std::io::IsTerminal;
|
|
||||||
match std::env::var("NO_COLOR") {
|
|
||||||
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
|
|
||||||
//TODO: should use ANSI styling instead of 24-bit truecolor here
|
|
||||||
Self(Arc::new(SyntectHighlighter::default()))
|
|
||||||
}
|
|
||||||
Ok(string) if string != "0" => MietteHighlighter::nocolor(),
|
|
||||||
_ => Self(Arc::new(SyntectHighlighter::default())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "syntect-highlighter"))]
|
|
||||||
fn default() -> Self {
|
|
||||||
MietteHighlighter::nocolor()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Highlighter + Send + Sync + 'static> From<T> for MietteHighlighter {
|
|
||||||
fn from(value: T) -> Self {
|
|
||||||
Self(Arc::new(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for MietteHighlighter {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "MietteHighlighter(...)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for MietteHighlighter {
|
|
||||||
type Target = dyn Highlighter + Send + Sync;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&*self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
// all syntect imports are explicitly qualified, but their paths are shortened for convenience
|
|
||||||
#[allow(clippy::module_inception)]
|
|
||||||
mod syntect {
|
|
||||||
pub(super) use syntect::{
|
|
||||||
highlighting::{
|
|
||||||
Color, HighlightIterator, HighlightState, Highlighter, Style, Theme, ThemeSet,
|
|
||||||
},
|
|
||||||
parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
use owo_colors::{Rgb, Style, Styled};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
highlighters::{Highlighter, HighlighterState},
|
|
||||||
SpanContents,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::BlankHighlighterState;
|
|
||||||
|
|
||||||
/// Highlights miette [`SpanContents`] with the [syntect](https://docs.rs/syntect/latest/syntect/) highlighting crate.
|
|
||||||
///
|
|
||||||
/// Currently only 24-bit truecolor output is supported due to syntect themes
|
|
||||||
/// representing color as RGBA.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct SyntectHighlighter {
|
|
||||||
theme: syntect::Theme,
|
|
||||||
syntax_set: syntect::SyntaxSet,
|
|
||||||
use_bg_color: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SyntectHighlighter {
|
|
||||||
fn default() -> Self {
|
|
||||||
let theme_set = syntect::ThemeSet::load_defaults();
|
|
||||||
let theme = theme_set.themes["base16-ocean.dark"].clone();
|
|
||||||
Self::new_themed(theme, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Highlighter for SyntectHighlighter {
|
|
||||||
fn start_highlighter_state<'h>(
|
|
||||||
&'h self,
|
|
||||||
source: &dyn SpanContents<'_>,
|
|
||||||
) -> Box<dyn HighlighterState + 'h> {
|
|
||||||
if let Some(syntax) = self.detect_syntax(source) {
|
|
||||||
let highlighter = syntect::Highlighter::new(&self.theme);
|
|
||||||
let parse_state = syntect::ParseState::new(syntax);
|
|
||||||
let highlight_state =
|
|
||||||
syntect::HighlightState::new(&highlighter, syntect::ScopeStack::new());
|
|
||||||
Box::new(SyntectHighlighterState {
|
|
||||||
syntax_set: &self.syntax_set,
|
|
||||||
highlighter,
|
|
||||||
parse_state,
|
|
||||||
highlight_state,
|
|
||||||
use_bg_color: self.use_bg_color,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Box::new(BlankHighlighterState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SyntectHighlighter {
|
|
||||||
/// Create a syntect highlighter with the given theme and syntax set.
|
|
||||||
pub fn new(syntax_set: syntect::SyntaxSet, theme: syntect::Theme, use_bg_color: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
theme,
|
|
||||||
syntax_set,
|
|
||||||
use_bg_color,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a syntect highlighter with the given theme and the default syntax set.
|
|
||||||
pub fn new_themed(theme: syntect::Theme, use_bg_color: bool) -> Self {
|
|
||||||
Self::new(
|
|
||||||
syntect::SyntaxSet::load_defaults_nonewlines(),
|
|
||||||
theme,
|
|
||||||
use_bg_color,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine syntect [`SyntaxReference`] to use for given [`SpanContents`].
|
|
||||||
fn detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference> {
|
|
||||||
// use language if given
|
|
||||||
if let Some(language) = contents.language() {
|
|
||||||
return self.syntax_set.find_syntax_by_name(language);
|
|
||||||
}
|
|
||||||
// otherwise try to use any file extension provided in the name
|
|
||||||
if let Some(name) = contents.name() {
|
|
||||||
if let Some(ext) = Path::new(name).extension() {
|
|
||||||
return self
|
|
||||||
.syntax_set
|
|
||||||
.find_syntax_by_extension(ext.to_string_lossy().as_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// finally, attempt to guess syntax based on first line
|
|
||||||
self.syntax_set.find_syntax_by_first_line(
|
|
||||||
std::str::from_utf8(contents.data())
|
|
||||||
.ok()?
|
|
||||||
.split('\n')
|
|
||||||
.next()?,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stateful highlighting iterator for [`SyntectHighlighter`].
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct SyntectHighlighterState<'h> {
|
|
||||||
syntax_set: &'h syntect::SyntaxSet,
|
|
||||||
highlighter: syntect::Highlighter<'h>,
|
|
||||||
parse_state: syntect::ParseState,
|
|
||||||
highlight_state: syntect::HighlightState,
|
|
||||||
use_bg_color: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HighlighterState for SyntectHighlighterState<'_> {
|
|
||||||
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>> {
|
|
||||||
if let Ok(ops) = self.parse_state.parse_line(line, self.syntax_set) {
|
|
||||||
let use_bg_color = self.use_bg_color;
|
|
||||||
syntect::HighlightIterator::new(
|
|
||||||
&mut self.highlight_state,
|
|
||||||
&ops,
|
|
||||||
line,
|
|
||||||
&self.highlighter,
|
|
||||||
)
|
|
||||||
.map(|(style, str)| convert_style(style, use_bg_color).style(str))
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
vec![Style::default().style(line)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert syntect [`syntect::Style`] into `owo_colors` [`Style`]
|
|
||||||
#[inline]
|
|
||||||
fn convert_style(syntect_style: syntect::Style, use_bg_color: bool) -> Style {
|
|
||||||
if use_bg_color {
|
|
||||||
let fg = blend_fg_color(syntect_style);
|
|
||||||
let bg = convert_color(syntect_style.background);
|
|
||||||
Style::new().color(fg).on_color(bg)
|
|
||||||
} else {
|
|
||||||
let fg = convert_color(syntect_style.foreground);
|
|
||||||
Style::new().color(fg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Blend foreground RGB into background RGB according to alpha channel
|
|
||||||
#[inline]
|
|
||||||
fn blend_fg_color(syntect_style: syntect::Style) -> Rgb {
|
|
||||||
let fg = syntect_style.foreground;
|
|
||||||
if fg.a == 0xff {
|
|
||||||
return convert_color(fg);
|
|
||||||
}
|
|
||||||
let bg = syntect_style.background;
|
|
||||||
let ratio = fg.a as u32;
|
|
||||||
let r = (fg.r as u32 * ratio + bg.r as u32 * (255 - ratio)) / 255;
|
|
||||||
let g = (fg.g as u32 * ratio + bg.g as u32 * (255 - ratio)) / 255;
|
|
||||||
let b = (fg.b as u32 * ratio + bg.b as u32 * (255 - ratio)) / 255;
|
|
||||||
Rgb(r as u8, g as u8, b as u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert syntect color into owo color.
|
|
||||||
///
|
|
||||||
/// Note: ignores alpha channel. use [`blend_fg_color`] if you need that
|
|
||||||
///
|
|
||||||
#[inline]
|
|
||||||
fn convert_color(color: syntect::Color) -> Rgb {
|
|
||||||
Rgb(color.r, color.g, color.b)
|
|
||||||
}
|
|
||||||
235
src/lib.rs
235
src/lib.rs
|
|
@ -1,6 +1,5 @@
|
||||||
#![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!
|
||||||
//!
|
//!
|
||||||
|
|
@ -28,9 +27,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 -->
|
||||||
//!
|
//!
|
||||||
|
|
@ -44,15 +43,10 @@
|
||||||
//! - [... 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)
|
|
||||||
//! - [... primary label](#-primary-label)
|
|
||||||
//! - [... collection of labels](#-collection-of-labels)
|
|
||||||
//! - [Acknowledgements](#acknowledgements)
|
//! - [Acknowledgements](#acknowledgements)
|
||||||
//! - [License](#license)
|
//! - [License](#license)
|
||||||
//!
|
//!
|
||||||
|
|
@ -101,7 +95,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, NamedSource, SourceSpan};
|
//! use miette::{Diagnostic, SourceSpan};
|
||||||
//! use thiserror::Error;
|
//! use thiserror::Error;
|
||||||
//!
|
//!
|
||||||
//! #[derive(Error, Debug, Diagnostic)]
|
//! #[derive(Error, Debug, Diagnostic)]
|
||||||
|
|
@ -115,7 +109,7 @@
|
||||||
//! // The Source that we're gonna be printing snippets out of.
|
//! // The Source that we're gonna be printing snippets out of.
|
||||||
//! // This can be a String if you don't have or care about file names.
|
//! // This can be a String if you don't have or care about file names.
|
||||||
//! #[source_code]
|
//! #[source_code]
|
||||||
//! src: NamedSource<String>,
|
//! src: NamedSource,
|
||||||
//! // Snippets and highlights can be included in the diagnostic!
|
//! // Snippets and highlights can be included in the diagnostic!
|
||||||
//! #[label("This bit here")]
|
//! #[label("This bit here")]
|
||||||
//! bad_bit: SourceSpan,
|
//! bad_bit: SourceSpan,
|
||||||
|
|
@ -128,11 +122,12 @@
|
||||||
//! 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::Result;
|
//! use miette::{NamedSource, 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),
|
||||||
|
|
@ -162,20 +157,17 @@
|
||||||
//! <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:
|
||||||
//! \
|
//! \
|
||||||
//! diagnostic error code: oops::my::bad (link)
|
//! Error: Types mismatched for operation.
|
||||||
//! Error: oops!
|
//! Diagnostic severity: error
|
||||||
|
//! Begin snippet starting at line 1, column 1
|
||||||
//! \
|
//! \
|
||||||
//! Begin snippet for bad_file.rs starting
|
//! snippet line 1: 3 + "5"
|
||||||
//! at line 2, column 3
|
//! label starting at line 1, column 1: int
|
||||||
//! \
|
//! label starting at line 1, column 1: doesn't support these values.
|
||||||
//! snippet line 1: source
|
//! label starting at line 1, column 1: string
|
||||||
//! \
|
//! diagnostic help: Change int or string to be the right types and try again.
|
||||||
//! snippet line 2: text
|
//! diagnostic code: nu::parser::unsupported_operation
|
||||||
//! highlight starting at line 1, column 3: This bit here
|
//! For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
|
||||||
//! \
|
|
||||||
//! snippet line 3: here
|
|
||||||
//! \
|
|
||||||
//! diagnostic help: try doing it better next time?">
|
|
||||||
//!
|
//!
|
||||||
//! ## Using
|
//! ## Using
|
||||||
//!
|
//!
|
||||||
|
|
@ -211,17 +203,6 @@
|
||||||
//! // 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)]
|
||||||
|
|
@ -256,7 +237,7 @@
|
||||||
//! use semver::Version;
|
//! use semver::Version;
|
||||||
//!
|
//!
|
||||||
//! pub fn some_tool() -> Result<Version> {
|
//! pub fn some_tool() -> Result<Version> {
|
||||||
//! "1.2.x".parse().into_diagnostic()
|
//! Ok("1.2.x".parse().into_diagnostic()?)
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
|
@ -271,24 +252,24 @@
|
||||||
//! use semver::Version;
|
//! use semver::Version;
|
||||||
//!
|
//!
|
||||||
//! pub fn some_tool() -> Result<Version> {
|
//! pub fn some_tool() -> Result<Version> {
|
||||||
//! "1.2.x"
|
//! Ok("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, Result};
|
//! use miette::{miette, IntoDiagnostic, Result, WrapErr};
|
||||||
//! 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";
|
||||||
//! version
|
//! Ok(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.
|
||||||
|
|
@ -300,9 +281,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};
|
||||||
|
|
@ -323,23 +304,6 @@
|
||||||
//! miette = { version = "X.Y.Z", features = ["fancy"] }
|
//! miette = { version = "X.Y.Z", features = ["fancy"] }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Another way to display a diagnostic is by printing them using the debug formatter.
|
|
||||||
//! This is, in fact, what returning diagnostics from main ends up doing.
|
|
||||||
//! To do it yourself, you can write the following:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use miette::{IntoDiagnostic, Result};
|
|
||||||
//! use semver::Version;
|
|
||||||
//!
|
|
||||||
//! fn just_a_random_function() {
|
|
||||||
//! let version_result: Result<Version> = "1.2.x".parse().into_diagnostic();
|
|
||||||
//! match version_result {
|
|
||||||
//! Err(e) => println!("{:?}", e),
|
|
||||||
//! Ok(version) => println!("{}", version),
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ### ... diagnostic code URLs
|
//! ### ... diagnostic code URLs
|
||||||
//!
|
//!
|
||||||
//! `miette` supports providing a URL for individual diagnostics. This URL will
|
//! `miette` supports providing a URL for individual diagnostics. This URL will
|
||||||
|
|
@ -441,7 +405,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
|
||||||
|
|
@ -477,19 +441,6 @@
|
||||||
//! };
|
//! };
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### ... 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
|
||||||
|
|
@ -642,11 +593,11 @@
|
||||||
//! .unicode(false)
|
//! .unicode(false)
|
||||||
//! .context_lines(3)
|
//! .context_lines(3)
|
||||||
//! .tab_width(4)
|
//! .tab_width(4)
|
||||||
//! .break_words(true)
|
|
||||||
//! .build(),
|
//! .build(),
|
||||||
//! )
|
//! )
|
||||||
//! }))
|
//! }))
|
||||||
//!
|
//!
|
||||||
|
//! # .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
|
||||||
|
|
@ -657,15 +608,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 '+'",
|
||||||
|
|
@ -674,120 +625,6 @@
|
||||||
//! println!("{:?}", report)
|
//! println!("{:?}", report)
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### ... syntax highlighting
|
|
||||||
//!
|
|
||||||
//! `miette` can be configured to highlight syntax in source code snippets.
|
|
||||||
//!
|
|
||||||
//! <!-- TODO: screenshot goes here once default Theme is decided -->
|
|
||||||
//!
|
|
||||||
//! To use the built-in highlighting functionality, you must enable the
|
|
||||||
//! `syntect-highlighter` crate feature. When this feature is enabled, `miette` will
|
|
||||||
//! automatically use the [`syntect`] crate to highlight the `#[source_code]`
|
|
||||||
//! field of your [`Diagnostic`].
|
|
||||||
//!
|
|
||||||
//! Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
|
|
||||||
//! * [`language()`](SpanContents::language) - Provides the name of the language
|
|
||||||
//! as a string. For example `"Rust"` will indicate Rust syntax highlighting.
|
|
||||||
//! You can set the language of the [`SpanContents`] produced by a
|
|
||||||
//! [`NamedSource`] via the [`with_language`](NamedSource::with_language)
|
|
||||||
//! method.
|
|
||||||
//! * [`name()`](SpanContents::name) - In the absence of an explicitly set
|
|
||||||
//! language, the name is assumed to contain a file name or file path.
|
|
||||||
//! The highlighter will check for a file extension at the end of the name and
|
|
||||||
//! try to guess the syntax from that.
|
|
||||||
//!
|
|
||||||
//! If you want to use a custom highlighter, you can provide a custom
|
|
||||||
//! implementation of the [`Highlighter`](highlighters::Highlighter)
|
|
||||||
//! trait to [`MietteHandlerOpts`] by calling the
|
|
||||||
//! [`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
|
||||||
//! method. See the [`highlighters`] module docs for more details.
|
|
||||||
//!
|
|
||||||
//! ### ... 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
|
|
||||||
//!
|
|
||||||
//! This crate requires rustc 1.82.0 or later.
|
|
||||||
//!
|
|
||||||
//! ## Acknowledgements
|
//! ## Acknowledgements
|
||||||
//!
|
//!
|
||||||
//! `miette` was not developed in a void. It owes enormous credit to various
|
//! `miette` was not developed in a void. It owes enormous credit to various
|
||||||
|
|
@ -815,12 +652,11 @@
|
||||||
//! and some from [`thiserror`](https://github.com/dtolnay/thiserror), also
|
//! and some from [`thiserror`](https://github.com/dtolnay/thiserror), also
|
||||||
//! under the Apache License. Some code is taken from
|
//! under the Apache License. Some code is taken from
|
||||||
//! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
//! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
||||||
#[cfg(feature = "derive")]
|
|
||||||
pub use miette_derive::*;
|
pub use miette_derive::*;
|
||||||
|
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use eyreish::*;
|
pub use eyreish::*;
|
||||||
#[cfg(feature = "fancy-base")]
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
pub use handler::*;
|
pub use handler::*;
|
||||||
pub use handlers::*;
|
pub use handlers::*;
|
||||||
pub use miette_diagnostic::*;
|
pub use miette_diagnostic::*;
|
||||||
|
|
@ -831,14 +667,11 @@ 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-base")]
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
mod handler;
|
mod handler;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
#[cfg(feature = "fancy-base")]
|
|
||||||
pub mod highlighters;
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod macro_helpers;
|
pub mod macro_helpers;
|
||||||
mod miette_diagnostic;
|
mod miette_diagnostic;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -38,24 +36,3 @@ 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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ impl MietteDiagnostic {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn and_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self {
|
pub fn and_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self {
|
||||||
let mut all_labels = self.labels.unwrap_or_default();
|
let mut all_labels = self.labels.unwrap_or_default();
|
||||||
all_labels.extend(labels);
|
all_labels.extend(labels.into_iter());
|
||||||
self.labels = Some(all_labels);
|
self.labels = Some(all_labels);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -292,16 +292,14 @@ fn test_serialize_miette_diagnostic() {
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"length": 0
|
"length": 0
|
||||||
},
|
},
|
||||||
"label": "label1",
|
"label": "label1"
|
||||||
"primary": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"span": {
|
"span": {
|
||||||
"offset": 1,
|
"offset": 1,
|
||||||
"length": 2
|
"length": 2
|
||||||
},
|
},
|
||||||
"label": "label2",
|
"label": "label2"
|
||||||
"primary": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
@ -352,16 +350,14 @@ fn test_deserialize_miette_diagnostic() {
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"length": 0
|
"length": 0
|
||||||
},
|
},
|
||||||
"label": "label1",
|
"label": "label1"
|
||||||
"primary": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"span": {
|
"span": {
|
||||||
"offset": 1,
|
"offset": 1,
|
||||||
"length": 2
|
"length": 2
|
||||||
},
|
},
|
||||||
"label": "label2",
|
"label": "label2"
|
||||||
"primary": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,34 +3,27 @@ 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 {
|
||||||
pub struct NamedSource<S: SourceCode + 'static> {
|
source: Box<dyn SourceCode + 'static>,
|
||||||
source: S,
|
|
||||||
name: String,
|
name: String,
|
||||||
language: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: SourceCode> std::fmt::Debug for NamedSource<S> {
|
impl std::fmt::Debug for NamedSource {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("NamedSource")
|
f.debug_struct("NamedSource")
|
||||||
.field("name", &self.name)
|
.field("name", &self.name)
|
||||||
.field("source", &"<redacted>")
|
.field("source", &"<redacted>");
|
||||||
.field("language", &self.language);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: SourceCode + 'static> NamedSource<S> {
|
impl NamedSource {
|
||||||
/// Create a new `NamedSource` using a regular [`SourceCode`] and giving
|
/// Create a new `NamedSource` using a regular [`SourceCode`] and giving
|
||||||
/// its returned [`SpanContents`] a name.
|
/// its returned [`SpanContents`] a name.
|
||||||
pub fn new(name: impl AsRef<str>, source: S) -> Self
|
pub fn new(name: impl AsRef<str>, source: impl SourceCode + Send + Sync + 'static) -> Self {
|
||||||
where
|
|
||||||
S: Send + Sync,
|
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
source,
|
source: Box::new(source),
|
||||||
name: name.as_ref().to_string(),
|
name: name.as_ref().to_string(),
|
||||||
language: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,38 +34,28 @@ impl<S: SourceCode + 'static> NamedSource<S> {
|
||||||
|
|
||||||
/// Returns a reference the inner [`SourceCode`] type for this
|
/// Returns a reference the inner [`SourceCode`] type for this
|
||||||
/// `NamedSource`.
|
/// `NamedSource`.
|
||||||
pub fn inner(&self) -> &S {
|
pub fn inner(&self) -> &(dyn SourceCode + 'static) {
|
||||||
&self.source
|
&*self.source
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the [`language`](SpanContents::language) for this source code.
|
|
||||||
pub fn with_language(mut self, language: impl Into<String>) -> Self {
|
|
||||||
self.language = Some(language.into());
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: SourceCode + 'static> SourceCode for NamedSource<S> {
|
impl SourceCode for NamedSource {
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &crate::SourceSpan,
|
span: &crate::SourceSpan,
|
||||||
context_lines_before: usize,
|
context_lines_before: usize,
|
||||||
context_lines_after: usize,
|
context_lines_after: usize,
|
||||||
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||||
let inner_contents =
|
let contents = self
|
||||||
self.inner()
|
.inner()
|
||||||
.read_span(span, context_lines_before, context_lines_after)?;
|
.read_span(span, context_lines_before, context_lines_after)?;
|
||||||
let mut contents = MietteSpanContents::new_named(
|
Ok(Box::new(MietteSpanContents::new_named(
|
||||||
self.name.clone(),
|
self.name.clone(),
|
||||||
inner_contents.data(),
|
contents.data(),
|
||||||
*inner_contents.span(),
|
*contents.span(),
|
||||||
inner_contents.line(),
|
contents.line(),
|
||||||
inner_contents.column(),
|
contents.column(),
|
||||||
inner_contents.line_count(),
|
contents.line_count(),
|
||||||
);
|
)))
|
||||||
if let Some(language) = &self.language {
|
|
||||||
contents = contents.with_language(language);
|
|
||||||
}
|
|
||||||
Ok(Box::new(contents))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
src/panic.rs
58
src/panic.rs
|
|
@ -1,8 +1,7 @@
|
||||||
use std::{error::Error, fmt::Display};
|
|
||||||
|
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{Context, Diagnostic, Result};
|
use crate::{self as miette, 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() {
|
||||||
|
|
@ -13,7 +12,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.clone_from(msg);
|
message = msg.clone();
|
||||||
}
|
}
|
||||||
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() {
|
||||||
|
|
@ -26,27 +25,11 @@ pub fn set_panic_hook() {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
|
#[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;
|
||||||
|
|
@ -101,32 +84,3 @@ 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
155
src/protocol.rs
155
src/protocol.rs
|
|
@ -12,7 +12,7 @@ use std::{
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{DiagnosticError, MietteError};
|
use crate::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_error_impls {
|
macro_rules! box_impls {
|
||||||
($($box_type:ty),*) => {
|
($($box_type:ty),*) => {
|
||||||
$(
|
$(
|
||||||
impl std::error::Error for $box_type {
|
impl std::error::Error for $box_type {
|
||||||
|
|
@ -85,29 +85,12 @@ macro_rules! box_error_impls {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
box_error_impls! {
|
box_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>
|
||||||
{
|
{
|
||||||
|
|
@ -134,7 +117,7 @@ impl From<&str> for Box<dyn Diagnostic> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Box<dyn Diagnostic + Send + Sync + '_> {
|
impl<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
|
||||||
fn from(s: &str) -> Self {
|
fn from(s: &str) -> Self {
|
||||||
From::from(String::from(s))
|
From::from(String::from(s))
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +157,18 @@ 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 {
|
||||||
Box::new(DiagnosticError(s))
|
#[derive(thiserror::Error)]
|
||||||
|
#[error(transparent)]
|
||||||
|
struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
|
||||||
|
impl fmt::Debug for BoxedDiagnostic {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic for BoxedDiagnostic {}
|
||||||
|
|
||||||
|
Box::new(BoxedDiagnostic(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +179,6 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Sen
|
||||||
*/
|
*/
|
||||||
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[derive(Default)]
|
|
||||||
pub enum Severity {
|
pub enum Severity {
|
||||||
/// Just some help. Here's how you could be doing it better.
|
/// Just some help. Here's how you could be doing it better.
|
||||||
Advice,
|
Advice,
|
||||||
|
|
@ -193,10 +186,15 @@ pub enum Severity {
|
||||||
Warning,
|
Warning,
|
||||||
/// Critical failure. The program cannot continue.
|
/// Critical failure. The program cannot continue.
|
||||||
/// This is the default severity, if you don't specify another one.
|
/// This is the default severity, if you don't specify another one.
|
||||||
#[default]
|
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Severity {
|
||||||
|
fn default() -> Self {
|
||||||
|
Severity::Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialize_severity() {
|
fn test_serialize_severity() {
|
||||||
|
|
@ -234,7 +232,7 @@ whole thing--meaning you should be able to support `SourceCode`s which are
|
||||||
gigabytes or larger in size.
|
gigabytes or larger in size.
|
||||||
*/
|
*/
|
||||||
pub trait SourceCode: Send + Sync {
|
pub trait SourceCode: Send + Sync {
|
||||||
/// Read the bytes for a specific span from this `SourceCode`, keeping a
|
/// Read the bytes for a specific span from this SourceCode, keeping a
|
||||||
/// certain number of lines before and after the span as context.
|
/// certain number of lines before and after the span as context.
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
|
@ -251,7 +249,6 @@ pub struct LabeledSpan {
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
label: Option<String>,
|
label: Option<String>,
|
||||||
span: SourceSpan,
|
span: SourceSpan,
|
||||||
primary: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LabeledSpan {
|
impl LabeledSpan {
|
||||||
|
|
@ -259,8 +256,7 @@ impl LabeledSpan {
|
||||||
pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
|
pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
label,
|
label,
|
||||||
span: SourceSpan::new(SourceOffset(offset), len),
|
span: SourceSpan::new(SourceOffset(offset), SourceOffset(len)),
|
||||||
primary: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,24 +265,9 @@ impl LabeledSpan {
|
||||||
Self {
|
Self {
|
||||||
label,
|
label,
|
||||||
span: span.into(),
|
span: span.into(),
|
||||||
primary: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a new labeled primary span using an existing span.
|
|
||||||
pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
|
|
||||||
Self {
|
|
||||||
label,
|
|
||||||
span: span.into(),
|
|
||||||
primary: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the text of the label
|
|
||||||
pub fn set_label(&mut self, label: Option<String>) {
|
|
||||||
self.label = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Makes a new label at specified span
|
/// Makes a new label at specified span
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
@ -359,11 +340,6 @@ impl LabeledSpan {
|
||||||
pub const fn is_empty(&self) -> bool {
|
pub const fn is_empty(&self) -> bool {
|
||||||
self.span.is_empty()
|
self.span.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True if this `LabeledSpan` is a primary span.
|
|
||||||
pub const fn primary(&self) -> bool {
|
|
||||||
self.primary
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
@ -374,8 +350,7 @@ fn test_serialize_labeled_span() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json!(LabeledSpan::new(None, 0, 0)),
|
json!(LabeledSpan::new(None, 0, 0)),
|
||||||
json!({
|
json!({
|
||||||
"span": { "offset": 0, "length": 0, },
|
"span": { "offset": 0, "length": 0 }
|
||||||
"primary": false,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -383,10 +358,9 @@ fn test_serialize_labeled_span() {
|
||||||
json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
|
json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
|
||||||
json!({
|
json!({
|
||||||
"label": "label",
|
"label": "label",
|
||||||
"span": { "offset": 0, "length": 0, },
|
"span": { "offset": 0, "length": 0 }
|
||||||
"primary": false,
|
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
@ -396,26 +370,23 @@ fn test_deserialize_labeled_span() {
|
||||||
|
|
||||||
let span: LabeledSpan = serde_json::from_value(json!({
|
let span: LabeledSpan = serde_json::from_value(json!({
|
||||||
"label": null,
|
"label": null,
|
||||||
"span": { "offset": 0, "length": 0, },
|
"span": { "offset": 0, "length": 0 }
|
||||||
"primary": false,
|
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
||||||
|
|
||||||
let span: LabeledSpan = serde_json::from_value(json!({
|
let span: LabeledSpan = serde_json::from_value(json!({
|
||||||
"span": { "offset": 0, "length": 0, },
|
"span": { "offset": 0, "length": 0 }
|
||||||
"primary": false
|
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
||||||
|
|
||||||
let span: LabeledSpan = serde_json::from_value(json!({
|
let span: LabeledSpan = serde_json::from_value(json!({
|
||||||
"label": "label",
|
"label": "label",
|
||||||
"span": { "offset": 0, "length": 0, },
|
"span": { "offset": 0, "length": 0 }
|
||||||
"primary": false
|
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
|
assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -440,15 +411,6 @@ pub trait SpanContents<'a> {
|
||||||
fn column(&self) -> usize;
|
fn column(&self) -> usize;
|
||||||
/// Total number of lines covered by this `SpanContents`.
|
/// Total number of lines covered by this `SpanContents`.
|
||||||
fn line_count(&self) -> usize;
|
fn line_count(&self) -> usize;
|
||||||
|
|
||||||
/// Optional method. The language name for this source code, if any.
|
|
||||||
/// This is used to drive syntax highlighting.
|
|
||||||
///
|
|
||||||
/// Examples: Rust, TOML, C
|
|
||||||
///
|
|
||||||
fn language(&self) -> Option<&str> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -468,8 +430,6 @@ pub struct MietteSpanContents<'a> {
|
||||||
line_count: usize,
|
line_count: usize,
|
||||||
// Optional filename
|
// Optional filename
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
// Optional language
|
|
||||||
language: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MietteSpanContents<'a> {
|
impl<'a> MietteSpanContents<'a> {
|
||||||
|
|
@ -488,7 +448,6 @@ impl<'a> MietteSpanContents<'a> {
|
||||||
column,
|
column,
|
||||||
line_count,
|
line_count,
|
||||||
name: None,
|
name: None,
|
||||||
language: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -508,15 +467,8 @@ impl<'a> MietteSpanContents<'a> {
|
||||||
column,
|
column,
|
||||||
line_count,
|
line_count,
|
||||||
name: Some(name),
|
name: Some(name),
|
||||||
language: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`language`](SpanContents::language) for syntax highlighting.
|
|
||||||
pub fn with_language(mut self, language: impl Into<String>) -> Self {
|
|
||||||
self.language = Some(language.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
||||||
|
|
@ -538,13 +490,10 @@ impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
||||||
fn name(&self) -> Option<&str> {
|
fn name(&self) -> Option<&str> {
|
||||||
self.name.as_deref()
|
self.name.as_deref()
|
||||||
}
|
}
|
||||||
fn language(&self) -> Option<&str> {
|
|
||||||
self.language.as_deref()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Span within a [`SourceCode`]
|
/// Span within a [`SourceCode`]
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, 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.
|
||||||
|
|
@ -555,10 +504,10 @@ pub struct SourceSpan {
|
||||||
|
|
||||||
impl SourceSpan {
|
impl SourceSpan {
|
||||||
/// Create a new [`SourceSpan`].
|
/// Create a new [`SourceSpan`].
|
||||||
pub const fn new(start: SourceOffset, length: usize) -> Self {
|
pub const fn new(start: SourceOffset, length: SourceOffset) -> Self {
|
||||||
Self {
|
Self {
|
||||||
offset: start,
|
offset: start,
|
||||||
length,
|
length: length.offset(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -588,8 +537,8 @@ impl From<(ByteOffset, usize)> for SourceSpan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(SourceOffset, usize)> for SourceSpan {
|
impl From<(SourceOffset, SourceOffset)> for SourceSpan {
|
||||||
fn from((start, len): (SourceOffset, usize)) -> Self {
|
fn from((start, len): (SourceOffset, SourceOffset)) -> Self {
|
||||||
Self::new(start, len)
|
Self::new(start, len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -603,28 +552,6 @@ 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 }
|
||||||
|
|
@ -648,7 +575,7 @@ fn test_serialize_source_span() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json!(SourceSpan::from(0)),
|
json!(SourceSpan::from(0)),
|
||||||
json!({ "offset": 0, "length": 0})
|
json!({ "offset": 0, "length": 0})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
@ -657,7 +584,7 @@ fn test_deserialize_source_span() {
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
|
let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
|
||||||
assert_eq!(span, SourceSpan::from(0));
|
assert_eq!(span, SourceSpan::from(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -668,7 +595,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, Ord, PartialOrd, Hash)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct SourceOffset(ByteOffset);
|
pub struct SourceOffset(ByteOffset);
|
||||||
|
|
||||||
|
|
@ -759,12 +686,12 @@ fn test_source_offset_from_location() {
|
||||||
fn test_serialize_source_offset() {
|
fn test_serialize_source_offset() {
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
assert_eq!(json!(SourceOffset::from(0)), 0);
|
assert_eq!(json!(SourceOffset::from(0)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_source_offset() {
|
fn test_deserialize_source_offset() {
|
||||||
let offset: SourceOffset = serde_json::from_str("0").unwrap();
|
let offset: SourceOffset = serde_json::from_str("0").unwrap();
|
||||||
assert_eq!(offset, SourceOffset::from(0));
|
assert_eq!(offset, SourceOffset::from(0))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
/*!
|
/*!
|
||||||
Default trait implementations for [`SourceCode`].
|
Default trait implementations for [`SourceCode`].
|
||||||
*/
|
*/
|
||||||
use std::{borrow::Cow, collections::VecDeque, fmt::Debug, sync::Arc};
|
use std::{
|
||||||
|
borrow::{Cow, ToOwned},
|
||||||
|
collections::VecDeque,
|
||||||
|
fmt::Debug,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
|
use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
|
||||||
|
|
||||||
|
|
@ -104,7 +109,7 @@ impl SourceCode for [u8] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceCode for &[u8] {
|
impl<'src> SourceCode for &'src [u8] {
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &SourceSpan,
|
span: &SourceSpan,
|
||||||
|
|
@ -143,7 +148,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 SourceCode for &str {
|
impl<'s> SourceCode for &'s str {
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &SourceSpan,
|
span: &SourceSpan,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
#![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::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -42,34 +42,23 @@ fn color_format(handler: MietteHandler) -> ColorFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store the current value of an environment variable on construction, and then
|
/// Runs a function with an environment variable set to a specific value, then
|
||||||
/// restore that value when the guard is dropped.
|
/// sets it back to it's original value once completed.
|
||||||
struct EnvVarGuard<'a> {
|
fn with_env_var<F: FnOnce()>(var: &str, value: &str, body: F) {
|
||||||
var: &'a str,
|
let old_value = std::env::var_os(var);
|
||||||
old_value: Option<OsString>,
|
std::env::set_var(var, value);
|
||||||
}
|
body();
|
||||||
|
if let Some(old_value) = old_value {
|
||||||
impl EnvVarGuard<'_> {
|
std::env::set_var(var, old_value);
|
||||||
fn new(var: &str) -> EnvVarGuard<'_> {
|
} else {
|
||||||
EnvVarGuard {
|
std::env::remove_var(var);
|
||||||
var,
|
|
||||||
old_value: std::env::var_os(var),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for EnvVarGuard<'_> {
|
lazy_static! {
|
||||||
fn drop(&mut self) {
|
static ref COLOR_ENV_VARS: Mutex<()> = Mutex::new(());
|
||||||
if let Some(old_value) = &self.old_value {
|
|
||||||
std::env::set_var(self.var, old_value);
|
|
||||||
} else {
|
|
||||||
std::env::remove_var(self.var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static 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.
|
||||||
fn check_colors<F: Fn(MietteHandlerOpts) -> MietteHandlerOpts>(
|
fn check_colors<F: Fn(MietteHandlerOpts) -> MietteHandlerOpts>(
|
||||||
|
|
@ -83,33 +72,22 @@ fn check_colors<F: Fn(MietteHandlerOpts) -> MietteHandlerOpts>(
|
||||||
//
|
//
|
||||||
// Since environment variables are shared for the entire process, we need
|
// Since environment variables are shared for the entire process, we need
|
||||||
// to ensure that only one test that modifies these env vars runs at a time.
|
// to ensure that only one test that modifies these env vars runs at a time.
|
||||||
let lock = COLOR_ENV_VARS.lock().unwrap();
|
let guard = COLOR_ENV_VARS.lock().unwrap();
|
||||||
|
|
||||||
let guards = (
|
with_env_var("NO_COLOR", "1", || {
|
||||||
EnvVarGuard::new("NO_COLOR"),
|
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||||
EnvVarGuard::new("FORCE_COLOR"),
|
assert_eq!(color_format(handler), no_support);
|
||||||
);
|
});
|
||||||
// Clear color environment variables that may be set outside of 'cargo test'
|
with_env_var("FORCE_COLOR", "1", || {
|
||||||
std::env::remove_var("NO_COLOR");
|
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||||
std::env::remove_var("FORCE_COLOR");
|
assert_eq!(color_format(handler), ansi_support);
|
||||||
|
});
|
||||||
|
with_env_var("FORCE_COLOR", "3", || {
|
||||||
|
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||||
|
assert_eq!(color_format(handler), rgb_support);
|
||||||
|
});
|
||||||
|
|
||||||
std::env::set_var("NO_COLOR", "1");
|
drop(guard);
|
||||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
|
||||||
assert_eq!(color_format(handler), no_support);
|
|
||||||
std::env::remove_var("NO_COLOR");
|
|
||||||
|
|
||||||
std::env::set_var("FORCE_COLOR", "1");
|
|
||||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
|
||||||
assert_eq!(color_format(handler), ansi_support);
|
|
||||||
std::env::remove_var("FORCE_COLOR");
|
|
||||||
|
|
||||||
std::env::set_var("FORCE_COLOR", "3");
|
|
||||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
|
||||||
assert_eq!(color_format(handler), rgb_support);
|
|
||||||
std::env::remove_var("FORCE_COLOR");
|
|
||||||
|
|
||||||
drop(guards);
|
|
||||||
drop(lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,12 @@ 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))]
|
||||||
|
|
@ -31,7 +29,6 @@ fn related() {
|
||||||
|
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
#[error("welp2")]
|
#[error("welp2")]
|
||||||
#[allow(dead_code)]
|
|
||||||
struct Baz;
|
struct Baz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +37,6 @@ 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>,
|
||||||
|
|
@ -193,7 +189,7 @@ fn fmt_help() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"1 x hello x \"2\"".to_string(),
|
"1 x hello x \"2\"".to_string(),
|
||||||
FooStruct("hello").help().unwrap().to_string()
|
FooStruct("hello".into()).help().unwrap().to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -205,7 +201,12 @@ fn fmt_help() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"1 x hello x \"2\"".to_string(),
|
"1 x hello x \"2\"".to_string(),
|
||||||
BarStruct { my_field: "hello" }.help().unwrap().to_string()
|
BarStruct {
|
||||||
|
my_field: "hello".into()
|
||||||
|
}
|
||||||
|
.help()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -223,7 +224,7 @@ fn fmt_help() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"1 x bar x \"2\"".to_string(),
|
"1 x bar x \"2\"".to_string(),
|
||||||
FooEnum::X("bar").help().unwrap().to_string()
|
FooEnum::X("bar".into()).help().unwrap().to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -249,7 +250,12 @@ fn help_field() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"x".to_string(),
|
"x".to_string(),
|
||||||
Foo { do_this: Some("x") }.help().unwrap().to_string()
|
Foo {
|
||||||
|
do_this: Some("x".into())
|
||||||
|
}
|
||||||
|
.help()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -265,11 +271,16 @@ fn help_field() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"x".to_string(),
|
"x".to_string(),
|
||||||
Bar::A(Some("x")).help().unwrap().to_string()
|
Bar::A(Some("x".into())).help().unwrap().to_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"x".to_string(),
|
"x".to_string(),
|
||||||
Bar::B { do_this: Some("x") }.help().unwrap().to_string()
|
Bar::B {
|
||||||
|
do_this: Some("x".into())
|
||||||
|
}
|
||||||
|
.help()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -277,14 +288,20 @@ fn help_field() {
|
||||||
#[diagnostic()]
|
#[diagnostic()]
|
||||||
struct Baz<'a>(#[help] Option<&'a str>);
|
struct Baz<'a>(#[help] Option<&'a str>);
|
||||||
|
|
||||||
assert_eq!("x".to_string(), Baz(Some("x")).help().unwrap().to_string());
|
assert_eq!(
|
||||||
|
"x".to_string(),
|
||||||
|
Baz(Some("x".into())).help().unwrap().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic()]
|
#[diagnostic()]
|
||||||
struct Quux<'a>(#[help] &'a str);
|
struct Quux<'a>(#[help] &'a str);
|
||||||
|
|
||||||
assert_eq!("x".to_string(), Quux("x").help().unwrap().to_string());
|
assert_eq!(
|
||||||
|
"x".to_string(),
|
||||||
|
Quux("x".into()).help().unwrap().to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -292,7 +309,6 @@ 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,
|
||||||
|
|
@ -315,7 +331,6 @@ 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,
|
||||||
|
|
@ -412,7 +427,7 @@ impl ForwardsTo {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
ForwardsTo {
|
ForwardsTo {
|
||||||
src: SNIPPET_TEXT.into(),
|
src: SNIPPET_TEXT.into(),
|
||||||
label: SourceSpan::new(11.into(), 6),
|
label: SourceSpan::new(11.into(), 6.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -574,7 +589,7 @@ fn test_unit_struct_display() {
|
||||||
#[error("unit only")]
|
#[error("unit only")]
|
||||||
#[diagnostic(code(foo::bar::overridden), help("hello from unit help"))]
|
#[diagnostic(code(foo::bar::overridden), help("hello from unit help"))]
|
||||||
struct UnitOnly;
|
struct UnitOnly;
|
||||||
assert_eq!(UnitOnly.help().unwrap().to_string(), "hello from unit help");
|
assert_eq!(UnitOnly.help().unwrap().to_string(), "hello from unit help")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -588,47 +603,5 @@ fn test_unit_enum_display() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Enum::UnitVariant.help().unwrap().to_string(),
|
Enum::UnitVariant.help().unwrap().to_string(),
|
||||||
"hello from unit help"
|
"hello from unit help"
|
||||||
);
|
)
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_optional_source_code() {
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
|
||||||
#[error("struct with optional source")]
|
|
||||||
struct Struct {
|
|
||||||
#[source_code]
|
|
||||||
src: Option<String>,
|
|
||||||
}
|
|
||||||
assert!(Struct { src: None }.source_code().is_none());
|
|
||||||
assert!(Struct {
|
|
||||||
src: Some("".to_string())
|
|
||||||
}
|
|
||||||
.source_code()
|
|
||||||
.is_some());
|
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
|
||||||
enum Enum {
|
|
||||||
#[error("variant1 with optional source")]
|
|
||||||
Variant1 {
|
|
||||||
#[source_code]
|
|
||||||
src: Option<String>,
|
|
||||||
},
|
|
||||||
#[error("variant2 with optional source")]
|
|
||||||
Variant2 {
|
|
||||||
#[source_code]
|
|
||||||
src: Option<String>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert!(Enum::Variant1 { src: None }.source_code().is_none());
|
|
||||||
assert!(Enum::Variant1 {
|
|
||||||
src: Some("".to_string())
|
|
||||||
}
|
|
||||||
.source_code()
|
|
||||||
.is_some());
|
|
||||||
assert!(Enum::Variant2 { src: None }.source_code().is_none());
|
|
||||||
assert!(Enum::Variant2 {
|
|
||||||
src: Some("".to_string())
|
|
||||||
}
|
|
||||||
.source_code()
|
|
||||||
.is_some());
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1462
tests/graphical.rs
1462
tests/graphical.rs
File diff suppressed because it is too large
Load Diff
|
|
@ -29,7 +29,7 @@ fn single_line_with_wide_char() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +65,7 @@ fn single_line_highlight() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +101,7 @@ fn single_line_highlight_offset_zero() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -136,7 +136,7 @@ fn single_line_highlight_with_empty_span() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +172,7 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label]
|
#[label]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +208,7 @@ fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +244,7 @@ fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "x"]
|
#[label = "x"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label = "y"]
|
#[label = "y"]
|
||||||
|
|
@ -288,7 +288,7 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "these two lines"]
|
#[label = "these two lines"]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -325,7 +325,7 @@ fn multiline_highlight_flyby() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "block 1"]
|
#[label = "block 1"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label = "block 2"]
|
#[label = "block 2"]
|
||||||
|
|
@ -378,7 +378,7 @@ fn multiline_highlight_no_label() -> Result<(), MietteError> {
|
||||||
#[source]
|
#[source]
|
||||||
source: Inner,
|
source: Inner,
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "block 1"]
|
#[label = "block 1"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label]
|
#[label]
|
||||||
|
|
@ -444,7 +444,7 @@ fn multiple_multiline_highlights_adjacent() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "this bit here"]
|
#[label = "this bit here"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label = "also this bit"]
|
#[label = "also this bit"]
|
||||||
|
|
@ -492,7 +492,7 @@ fn multiple_multiline_highlights_overlapping_lines() -> Result<(), MietteError>
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "this bit here"]
|
#[label = "this bit here"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label = "also this bit"]
|
#[label = "also this bit"]
|
||||||
|
|
@ -520,7 +520,7 @@ fn multiple_multiline_highlights_overlapping_offsets() -> Result<(), MietteError
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "this bit here"]
|
#[label = "this bit here"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label = "also this bit"]
|
#[label = "also this bit"]
|
||||||
|
|
@ -559,7 +559,7 @@ fn related() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
#[related]
|
#[related]
|
||||||
|
|
@ -614,7 +614,7 @@ fn related_source_code_propagation() -> Result<(), MietteError> {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
#[related]
|
#[related]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
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)]
|
||||||
|
|
@ -160,7 +159,7 @@ impl Diagnostic for CustomDiagnostic {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_boxed_custom_diagnostic() {
|
fn test_boxed_custom_diagnostic() {
|
||||||
fn assert_report<T: ?Sized + Diagnostic>(report: &impl Deref<Target = T>) {
|
fn assert_report(report: &Report) {
|
||||||
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()),
|
||||||
|
|
@ -216,16 +215,10 @@ 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!"),
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ fn enum_uses_base_attr() {
|
||||||
enum MyBad {
|
enum MyBad {
|
||||||
Only {
|
Only {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
},
|
},
|
||||||
|
|
@ -32,7 +32,7 @@ fn enum_uses_variant_attr() {
|
||||||
#[diagnostic(code(error::on::variant))]
|
#[diagnostic(code(error::on::variant))]
|
||||||
Only {
|
Only {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
},
|
},
|
||||||
|
|
@ -55,7 +55,7 @@ fn multiple_attrs_allowed_on_item() {
|
||||||
enum MyBad {
|
enum MyBad {
|
||||||
Only {
|
Only {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
},
|
},
|
||||||
|
|
@ -79,7 +79,7 @@ fn multiple_attrs_allowed_on_variant() {
|
||||||
#[diagnostic(help("try doing it correctly"))]
|
#[diagnostic(help("try doing it correctly"))]
|
||||||
Only {
|
Only {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
},
|
},
|
||||||
|
|
@ -104,7 +104,7 @@ fn attrs_can_be_split_between_item_and_variants() {
|
||||||
#[diagnostic(url("https://example.com/foo/bar"))]
|
#[diagnostic(url("https://example.com/foo/bar"))]
|
||||||
Only {
|
Only {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
},
|
},
|
||||||
|
|
@ -130,7 +130,7 @@ fn attr_not_required() {
|
||||||
enum MyBad {
|
enum MyBad {
|
||||||
Only {
|
Only {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,306 +0,0 @@
|
||||||
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,14 +41,6 @@ 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>);
|
||||||
|
|
@ -79,22 +71,15 @@ 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_diagnostic_source_pass_extra_info() {
|
fn test_diagnostic_source_pass_extra_info() {
|
||||||
let diag = TestBoxedError(Box::new(SourceError {
|
let diag = TestBoxedError(Box::new(SourceError {
|
||||||
code: String::from("Hello\nWorld!"),
|
code: String::from("Hello\nWorld!"),
|
||||||
help: String::from("Have you tried turning it on and off again?"),
|
help: format!("Have you tried turning it on and off again?"),
|
||||||
label: (1, 4),
|
label: (1, 4),
|
||||||
}));
|
}));
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
|
|
@ -104,10 +89,9 @@ 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#"
|
let expected = r#" × TestError
|
||||||
× TestError
|
|
||||||
╰─▶ × A complex error happened
|
╰─▶ × A complex error happened
|
||||||
╭─[1:2]
|
╭─[1:1]
|
||||||
1 │ Hello
|
1 │ Hello
|
||||||
· ──┬─
|
· ──┬─
|
||||||
· ╰── here
|
· ╰── here
|
||||||
|
|
@ -118,12 +102,10 @@ fn test_diagnostic_source_pass_extra_info() {
|
||||||
|
|
||||||
this is a footer
|
this is a footer
|
||||||
"#
|
"#
|
||||||
.trim_start_matches('\n');
|
.to_string();
|
||||||
|
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_diagnostic_source_is_output() {
|
fn test_diagnostic_source_is_output() {
|
||||||
let diag = TestStructError {
|
let diag = TestStructError {
|
||||||
|
|
@ -140,8 +122,7 @@ fn test_diagnostic_source_is_output() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("{}", out);
|
println!("{}", out);
|
||||||
|
|
||||||
let expected = r#"
|
let expected = r#" × TestError
|
||||||
× TestError
|
|
||||||
╰─▶ × A complex error happened
|
╰─▶ × A complex error happened
|
||||||
╭────
|
╭────
|
||||||
1 │ right here
|
1 │ right here
|
||||||
|
|
@ -150,13 +131,11 @@ 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 {
|
||||||
|
|
@ -168,7 +147,6 @@ struct NestedError {
|
||||||
the_other_err: Box<dyn Diagnostic>,
|
the_other_err: Box<dyn Diagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nested_diagnostic_source_is_output() {
|
fn test_nested_diagnostic_source_is_output() {
|
||||||
let inner_error = TestStructError {
|
let inner_error = TestStructError {
|
||||||
|
|
@ -191,8 +169,7 @@ fn test_nested_diagnostic_source_is_output() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("{}", out);
|
println!("{}", out);
|
||||||
|
|
||||||
let expected = r#"
|
let expected = r#" × A nested error happened
|
||||||
× A nested error happened
|
|
||||||
├─▶ × TestError
|
├─▶ × TestError
|
||||||
│
|
│
|
||||||
╰─▶ × A complex error happened
|
╰─▶ × A complex error happened
|
||||||
|
|
@ -210,208 +187,7 @@ fn test_nested_diagnostic_source_is_output() {
|
||||||
╰────
|
╰────
|
||||||
|
|
||||||
Yooo, a footer
|
Yooo, a footer
|
||||||
"#
|
"#;
|
||||||
.trim_start_matches('\n');
|
|
||||||
|
|
||||||
assert_eq!(expected, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
|
||||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
|
||||||
#[error("A multi-error happened")]
|
|
||||||
struct MultiError {
|
|
||||||
#[related]
|
|
||||||
related_errs: Vec<Box<dyn Diagnostic>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
|
||||||
#[test]
|
|
||||||
fn test_nested_cause_chains_for_related_errors_are_output() {
|
|
||||||
let inner_error = TestStructError {
|
|
||||||
asdf_inner_foo: SourceError {
|
|
||||||
code: String::from("This is another error"),
|
|
||||||
help: String::from("You should fix this"),
|
|
||||||
label: (3, 4),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let first_error = NestedError {
|
|
||||||
code: String::from("right here"),
|
|
||||||
label: (6, 4),
|
|
||||||
the_other_err: Box::new(inner_error),
|
|
||||||
};
|
|
||||||
let second_error = SourceError {
|
|
||||||
code: String::from("You're actually a mess"),
|
|
||||||
help: String::from("Get a grip..."),
|
|
||||||
label: (3, 4),
|
|
||||||
};
|
|
||||||
let multi_error = MultiError {
|
|
||||||
related_errs: vec![Box::new(first_error), Box::new(second_error)],
|
|
||||||
};
|
|
||||||
let diag = NestedError {
|
|
||||||
code: String::from("the outside world"),
|
|
||||||
label: (6, 4),
|
|
||||||
the_other_err: Box::new(multi_error),
|
|
||||||
};
|
|
||||||
let mut out = String::new();
|
|
||||||
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
|
||||||
.with_width(80)
|
|
||||||
.with_footer("Yooo, a footer".to_string())
|
|
||||||
.render_report(&mut out, &diag)
|
|
||||||
.unwrap();
|
|
||||||
println!("{}", out);
|
|
||||||
|
|
||||||
let expected = r#"
|
|
||||||
× A nested error happened
|
|
||||||
╰─▶ × A multi-error happened
|
|
||||||
|
|
||||||
Error:
|
|
||||||
× A nested error happened
|
|
||||||
├─▶ × TestError
|
|
||||||
│
|
|
||||||
╰─▶ × A complex error happened
|
|
||||||
╭────
|
|
||||||
1 │ This is another error
|
|
||||||
· ──┬─
|
|
||||||
· ╰── here
|
|
||||||
╰────
|
|
||||||
help: You should fix this
|
|
||||||
|
|
||||||
╭────
|
|
||||||
1 │ right here
|
|
||||||
· ──┬─
|
|
||||||
· ╰── here
|
|
||||||
╰────
|
|
||||||
|
|
||||||
Error:
|
|
||||||
× A complex error happened
|
|
||||||
╭────
|
|
||||||
1 │ You're actually a mess
|
|
||||||
· ──┬─
|
|
||||||
· ╰── here
|
|
||||||
╰────
|
|
||||||
help: Get a grip...
|
|
||||||
|
|
||||||
╭────
|
|
||||||
1 │ the outside world
|
|
||||||
· ──┬─
|
|
||||||
· ╰── here
|
|
||||||
╰────
|
|
||||||
|
|
||||||
Yooo, a footer
|
|
||||||
"#
|
|
||||||
.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,6 +80,7 @@ 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()));
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +52,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -65,7 +66,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +98,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -110,7 +112,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -142,6 +144,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -155,7 +158,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -187,6 +190,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -200,7 +204,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label]
|
#[label]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +235,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -244,7 +249,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -276,6 +281,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -289,7 +295,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "x"]
|
#[label = "x"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label = "y"]
|
#[label = "y"]
|
||||||
|
|
@ -341,6 +347,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -354,7 +361,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "these two lines"]
|
#[label = "these two lines"]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
@ -386,6 +393,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -399,7 +407,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "block 1"]
|
#[label = "block 1"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label = "block 2"]
|
#[label = "block 2"]
|
||||||
|
|
@ -448,6 +456,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -463,7 +472,7 @@ mod json_report_handler {
|
||||||
#[source]
|
#[source]
|
||||||
source: Inner,
|
source: Inner,
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "block 1"]
|
#[label = "block 1"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label]
|
#[label]
|
||||||
|
|
@ -523,6 +532,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -536,7 +546,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "this bit here"]
|
#[label = "this bit here"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label = "also this bit"]
|
#[label = "also this bit"]
|
||||||
|
|
@ -578,6 +588,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -591,7 +602,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "this bit here"]
|
#[label = "this bit here"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label = "also this bit"]
|
#[label = "also this bit"]
|
||||||
|
|
@ -633,6 +644,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -646,7 +658,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label = "this bit here"]
|
#[label = "this bit here"]
|
||||||
highlight1: SourceSpan,
|
highlight1: SourceSpan,
|
||||||
#[label = "also this bit"]
|
#[label = "also this bit"]
|
||||||
|
|
@ -688,6 +700,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -715,6 +728,7 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -728,7 +742,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
#[related]
|
#[related]
|
||||||
|
|
@ -808,6 +822,7 @@ mod json_report_handler {
|
||||||
}]
|
}]
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -821,7 +836,7 @@ mod json_report_handler {
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("this bit here")]
|
#[label("this bit here")]
|
||||||
highlight: SourceSpan,
|
highlight: SourceSpan,
|
||||||
#[related]
|
#[related]
|
||||||
|
|
@ -905,6 +920,7 @@ mod json_report_handler {
|
||||||
}]
|
}]
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ 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