diff options
38 files changed, 1113 insertions, 420 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index a7c24e4..4139f46 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,5 +1,5 @@ { "git": { - "sha1": "8b22d900058f407646e3c12d353abb890bc78816" + "sha1": "50bceff36e169673b49bfffa1809bba6be9bd07f" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 93532b5..abd9bb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.9.5] - 2020-06-24 + +### Changed + +- Sections of source code that are marked with primary labels are now rendered + using the primary highlight color. + +## [0.9.4] - 2020-05-18 + +### Changed + +- We have made the caret rendering easier to read when there are multiple + labels on the same line. We also avoid printing trailing borders on the + final source source snippet if no notes are present. Instead of this: + ``` + ┌─ one_line.rs:3:5 + │ + 3 │ v.push(v.pop().unwrap()); + │ - first borrow later used by call + │ ---- first mutable borrow occurs here + │ ^ second mutable borrow occurs here + │ + ``` + …we now render the following: + ``` + ┌─ one_line.rs:3:5 + │ + 3 │ v.push(v.pop().unwrap()); + │ - ---- ^ second mutable borrow occurs here + │ │ │ + │ │ first mutable borrow occurs here + │ first borrow later used by call + ``` + +### Fixed + +- Diagnostic rendering no longer panics if label ranges are between UTF-8 + character boundaries. + ## [0.9.3] - 2020-04-29 ### Changed @@ -206,7 +245,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.2.1] - 2019-02-26 ## [0.2.0] - 2018-10-11 -[Unreleased]: https://github.com/brendanzab/codespan/compare/v0.9.3...HEAD +[Unreleased]: https://github.com/brendanzab/codespan/compare/v0.9.5...HEAD +[0.9.4]: https://github.com/brendanzab/codespan/compare/v0.9.4...v0.9.5 +[0.9.4]: https://github.com/brendanzab/codespan/compare/v0.9.3...v0.9.4 [0.9.3]: https://github.com/brendanzab/codespan/compare/v0.9.2...v0.9.3 [0.9.2]: https://github.com/brendanzab/codespan/compare/v0.9.1...v0.9.2 [0.9.1]: https://github.com/brendanzab/codespan/compare/v0.9.0...v0.9.1 @@ -11,9 +11,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.28" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff" +checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" [[package]] name = "arrayref" @@ -69,9 +69,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.52" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" [[package]] name = "cfg-if" @@ -81,9 +81,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "clap" -version = "2.33.0" +version = "2.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" dependencies = [ "ansi_term", "atty", @@ -108,7 +108,7 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.9.3" +version = "0.9.5" dependencies = [ "anyhow", "insta", @@ -161,22 +161,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] -name = "dirs" -version = "2.0.2" +name = "dirs-next" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +checksum = "1cbcf9241d9e8d106295bd496bbe2e9cffd5fa098f2a8c9e2bbcbf09773c11a8" dependencies = [ "cfg-if", - "dirs-sys", + "dirs-sys-next", ] [[package]] -name = "dirs-sys" -version = "0.3.4" +name = "dirs-sys-next" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +checksum = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a" dependencies = [ - "cfg-if", "libc", "redox_users", "winapi", @@ -184,9 +183,9 @@ dependencies = [ [[package]] name = "dtoa" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" +checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" [[package]] name = "encode_unicode" @@ -216,9 +215,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" +checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" dependencies = [ "libc", ] @@ -239,9 +238,9 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "lazy_static" @@ -251,15 +250,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.69" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" [[package]] name = "linked-hash-map" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" [[package]] name = "log" @@ -344,18 +343,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.10" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.3" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ "proc-macro2", ] @@ -391,12 +390,12 @@ dependencies = [ [[package]] name = "rustyline" -version = "6.1.2" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd20b28d972040c627e209eb29f19c24a71a19d661cc5a220089176e20ee202" +checksum = "3358c21cbbc1a751892528db4e1de4b7a2b6a73f001e215aaba97d712cfa9777" dependencies = [ "cfg-if", - "dirs", + "dirs-next", "libc", "log", "memchr", @@ -410,9 +409,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "scopeguard" @@ -422,18 +421,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.106" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.106" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" dependencies = [ "proc-macro2", "quote", @@ -442,9 +441,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.52" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd" +checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226" dependencies = [ "itoa", "ryu", @@ -453,9 +452,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.11" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" +checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5" dependencies = [ "dtoa", "linked-hash-map", @@ -471,9 +470,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" +checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c" dependencies = [ "clap", "lazy_static", @@ -482,9 +481,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a" +checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118" dependencies = [ "heck", "proc-macro-error", @@ -495,9 +494,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.18" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" +checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" dependencies = [ "proc-macro2", "quote", @@ -572,9 +571,9 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "unindent" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" +checksum = "af41d708427f8fd0e915dcebb2cae0f0e6acb2a939b2d399c265c39a38a18942" [[package]] name = "utf8parse" @@ -584,15 +583,15 @@ checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" [[package]] name = "vec_map" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "void" @@ -639,9 +638,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "yaml-rust" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" dependencies = [ "linked-hash-map", ] @@ -13,7 +13,7 @@ [package] edition = "2018" name = "codespan-reporting" -version = "0.9.3" +version = "0.9.5" authors = ["Brendan Zabarauskas <bjzaba@yahoo.com.au>"] exclude = ["assets/**"] description = "Beautiful diagnostic reporting for text-based programming languages" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index f7bb15f..427e854 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "codespan-reporting" -version = "0.9.3" +version = "0.9.5" readme = "../README.md" license = "Apache-2.0" authors = ["Brendan Zabarauskas <bjzaba@yahoo.com.au>"] @@ -1,7 +1,5 @@ name: "codespan-reporting" -description: - "Beautiful diagnostic reporting for text-based programming languages." - +description: "Beautiful diagnostic reporting for text-based programming languages." third_party { url { type: HOMEPAGE @@ -11,7 +9,11 @@ third_party { type: GIT value: "https://github.com/brendanzab/codespan" } - version: "0.9.3" - last_upgrade_date { year: 2020 month: 5 day: 11 } + version: "0.9.5" license_type: NOTICE + last_upgrade_date { + year: 2020 + month: 7 + day: 10 + } } diff --git a/src/diagnostic.rs b/src/diagnostic.rs index 6655a91..559c899 100644 --- a/src/diagnostic.rs +++ b/src/diagnostic.rs @@ -67,7 +67,7 @@ pub struct Label<FileId> { pub style: LabelStyle, /// The file that we are labelling. pub file_id: FileId, - /// The range we are going to include in the final snippet. + /// The range in bytes we are going to include in the final snippet. pub range: Range<usize>, /// An optional message to provide some additional information for the /// underlined code. These should not include line breaks. diff --git a/src/files.rs b/src/files.rs index a717555..41691b9 100644 --- a/src/files.rs +++ b/src/files.rs @@ -191,6 +191,7 @@ pub fn column_index(source: &str, line_range: Range<usize>, byte_index: usize) - /// /// assert_eq!(line_index(&line_starts, 5), Some(1)); /// ``` +// NOTE: this is copied in `codespan::file::line_starts` and should be kept in sync. pub fn line_starts<'source>(source: &'source str) -> impl 'source + Iterator<Item = usize> { std::iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1)) } diff --git a/src/term/config.rs b/src/term/config.rs index a3bd03a..7b834d7 100644 --- a/src/term/config.rs +++ b/src/term/config.rs @@ -1,4 +1,3 @@ -use std::io; use termcolor::{Color, ColorSpec}; use crate::diagnostic::{LabelStyle, Severity}; @@ -31,58 +30,6 @@ impl Default for Config { } } -impl Config { - /// Measure the width of a string, taking into account the tab width. - pub fn width(&self, s: &str) -> usize { - use unicode_width::UnicodeWidthChar; - - s.chars() - .map(|ch| match ch { - '\t' => self.tab_width, - _ => ch.width().unwrap_or(0), - }) - .sum() - } - - /// Construct a source writer using the current config. - pub fn source<'a, W: ?Sized>(&self, writer: &'a mut W) -> SourceWriter<&'a mut W> { - SourceWriter { - upstream: writer, - tab_width: self.tab_width, - } - } -} - -/// Writer that replaces tab characters with the configured number of spaces. -pub struct SourceWriter<W> { - upstream: W, - tab_width: usize, -} - -impl<W: io::Write> io::Write for SourceWriter<W> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - let mut last_term = 0usize; - for (i, ch) in buf.iter().enumerate() { - if *ch == b'\t' { - self.upstream.write_all(&buf[last_term..i])?; - last_term = i + 1; - write!( - self.upstream, - "{space: >width$}", - space = "", - width = self.tab_width, - )?; - } - } - self.upstream.write_all(&buf[last_term..])?; - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - self.upstream.flush() - } -} - /// The display style to use when rendering diagnostics. #[derive(Clone, Debug)] pub enum DisplayStyle { @@ -279,29 +226,10 @@ pub struct Chars { /// The character to use for the left of a multi-line label. /// Defaults to: `'│'`. pub multi_left: char, -} -impl Chars { - pub fn single_caret_char(&self, label_style: LabelStyle) -> char { - match label_style { - LabelStyle::Primary => self.single_primary_caret, - LabelStyle::Secondary => self.single_secondary_caret, - } - } - - pub fn multi_caret_char_start(&self, label_style: LabelStyle) -> char { - match label_style { - LabelStyle::Primary => self.multi_primary_caret_start, - LabelStyle::Secondary => self.multi_secondary_caret_start, - } - } - - pub fn multi_caret_char_end(&self, label_style: LabelStyle) -> char { - match label_style { - LabelStyle::Primary => self.multi_primary_caret_end, - LabelStyle::Secondary => self.multi_secondary_caret_end, - } - } + /// The character to use for the left of a pointer underneath a caret. + /// Defaults to: `'│'`. + pub pointer_left: char, } impl Default for Chars { @@ -326,6 +254,8 @@ impl Default for Chars { multi_bottom_left: '╰', multi_bottom: '─', multi_left: '│', + + pointer_left: '│', } } } diff --git a/src/term/renderer.rs b/src/term/renderer.rs index a8b8601..ab5656e 100644 --- a/src/term/renderer.rs +++ b/src/term/renderer.rs @@ -30,25 +30,25 @@ pub enum MultiLabel<'diagnostic> { /// ```text /// ╭ /// ``` - TopLeft(LabelStyle), + TopLeft, /// Multi-line label top. /// /// ```text /// ╭────────────^ /// ``` - Top(LabelStyle, RangeTo<usize>), + Top(RangeTo<usize>), /// Left vertical labels for multi-line labels. /// /// ```text /// │ /// ``` - Left(LabelStyle), + Left, /// Multi-line label bottom, with an optional message. /// /// ```text /// ╰────────────^ blah blah /// ``` - Bottom(LabelStyle, RangeTo<usize>, &'diagnostic str), + Bottom(RangeTo<usize>, &'diagnostic str), } #[derive(Copy, Clone)] @@ -71,7 +71,6 @@ type Underline = (LabelStyle, VerticalBound); /// │ │ │ │ │ /// ┌──────────────────────────────────────────────────────────────────────────── /// header ── │ error[0001]: oh noes, a cupcake has occurred! -/// empty ── │ /// snippet start ── │ ┌─ test:9:0 /// snippet empty ── │ │ /// snippet line ── │ 9 │ ╭ Cupcake ipsum dolor. Sit amet marshmallow topping cheesecake @@ -84,8 +83,10 @@ type Underline = (LabelStyle, VerticalBound); /// snippet break ── │ · │ /// snippet line ── │ 38 │ │ Brownie lemon drops chocolate jelly-o candy canes. Danish marzipan /// snippet line ── │ 39 │ │ jujubes soufflé carrot cake marshmallow tiramisu caramels candy canes. -/// │ │ │ ^^^^^^^^^^^^^^^^^^ blah blah -/// │ │ │ -------------------- blah blah +/// │ │ │ ^^^^^^^^^^^^^^^^^^^ -------------------- blah blah +/// │ │ │ │ +/// │ │ │ blah blah +/// │ │ │ note: this is a note /// snippet line ── │ 40 │ │ Fruitcake jelly-o danish toffee. Tootsie roll pastry cheesecake /// snippet line ── │ 41 │ │ soufflé marzipan. Chocolate bar oat cake jujubes lollipop pastry /// snippet line ── │ 42 │ │ cupcake. Candy canes cupcake toffee gingerbread candy canes muffin @@ -227,8 +228,12 @@ impl<'writer, 'config> Renderer<'writer, 'config> { severity: Severity, single_labels: &[SingleLabel<'_>], num_multi_labels: usize, - multi_labels: &[(usize, MultiLabel<'_>)], + multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)], ) -> io::Result<()> { + // Trim trailing newlines, linefeeds, and null chars from source, if they exist. + // FIXME: Use the number of trimmed placeholders when rendering single line carets + let source = source.trim_end_matches(['\n', '\r', '\0'].as_ref()); + // Write source line // // ```text @@ -243,37 +248,272 @@ impl<'writer, 'config> Renderer<'writer, 'config> { let mut multi_labels_iter = multi_labels.iter().peekable(); for label_column in 0..num_multi_labels { match multi_labels_iter.peek() { - Some((label_index, label)) if *label_index == label_column => { + Some((label_index, label_style, label)) if *label_index == label_column => { match label { - MultiLabel::TopLeft(label_style) => { + MultiLabel::TopLeft => { self.label_multi_top_left(severity, *label_style)?; } MultiLabel::Top(..) => self.inner_gutter_space()?, - MultiLabel::Left(label_style) | MultiLabel::Bottom(label_style, ..) => { + MultiLabel::Left | MultiLabel::Bottom(..) => { self.label_multi_left(severity, *label_style, None)?; } } multi_labels_iter.next(); } - Some((_, _)) | None => self.inner_gutter_space()?, + Some((_, _, _)) | None => self.inner_gutter_space()?, } } - // Write source - write!(self.config.source(self.writer), " {}", source.trim_end())?; + // Write source text + write!(self, " ")?; + let mut in_primary = false; + for (metrics, ch) in self.char_metrics(source.char_indices()) { + let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8()); + + // Check if we are overlapping a primary label + let is_primary = single_labels.iter().any(|(ls, range, _)| { + *ls == LabelStyle::Primary && is_overlapping(range, &column_range) + }) || multi_labels.iter().any(|(_, ls, label)| { + *ls == LabelStyle::Primary + && match label { + MultiLabel::Top(range) => column_range.start >= range.end, + MultiLabel::TopLeft | MultiLabel::Left => true, + MultiLabel::Bottom(range, _) => column_range.end <= range.end, + } + }); + + // Set the source color if we are in a primary label + if is_primary && !in_primary { + self.set_color(self.styles().label(severity, LabelStyle::Primary))?; + in_primary = true; + } else if !is_primary && in_primary { + self.reset()?; + in_primary = false; + } + + match ch { + '\t' => (0..metrics.unicode_width).try_for_each(|_| write!(self, " "))?, + _ => write!(self, "{}", ch)?, + } + } + if in_primary { + self.reset()?; + } write!(self, "\n")?; } // Write single labels underneath source // // ```text - // │ │ │ ^^^^ oh noes + // │ - ---- ^^^ second mutable borrow occurs here + // │ │ │ + // │ │ first mutable borrow occurs here + // │ first borrow later used by call + // │ help: some help here // ``` - for (label_style, range, message) in single_labels.iter() { + if !single_labels.is_empty() { + // Our plan is as follows: + // + // 1. Do an initial scan to find: + // - The number of non-empty messages. + // - The right-most start and end positions of labels. + // - A candidate for a trailing label (where the label's message + // is printed to the left of the caret). + // 2. Check if the trailing label candidate overlaps another label - + // if so we print it underneath the carets with the other labels. + // 3. Print a line of carets, and (possibly) the trailing message + // to the left. + // 4. Print vertical lines pointing to the carets, and the messages + // for those carets. + // + // We try our best avoid introducing new dynamic allocations, + // instead preferring to iterate over the labels multiple times. It + // is unclear what the performance tradeoffs are however, so further + // investigation may be required. + + // The number of non-empty messages to print. + let mut num_messages = 0; + // The right-most start position, eg: + // + // ```text + // -^^^^---- ^^^^^^^ + // │ + // right-most start position + // ``` + let mut max_label_start = 0; + // The right-most end position, eg: + // + // ```text + // -^^^^---- ^^^^^^^ + // │ + // right-most end position + // ``` + let mut max_label_end = 0; + // A trailing message, eg: + // + // ```text + // ^^^ second mutable borrow occurs here + // ``` + let mut trailing_label = None; + + for (label_index, label) in single_labels.iter().enumerate() { + let (_, range, message) = label; + if !message.is_empty() { + num_messages += 1; + } + max_label_start = std::cmp::max(max_label_start, range.start); + max_label_end = std::cmp::max(max_label_end, range.end); + // This is a candidate for the trailing label, so let's record it. + if range.end == max_label_end { + if message.is_empty() { + trailing_label = None; + } else { + trailing_label = Some((label_index, label)); + } + } + } + if let Some((trailing_label_index, (_, trailing_range, _))) = trailing_label { + // Check to see if the trailing label candidate overlaps any of + // the other labels on the current line. + if single_labels + .iter() + .enumerate() + .filter(|(label_index, _)| *label_index != trailing_label_index) + .any(|(_, (_, range, _))| is_overlapping(trailing_range, range)) + { + // If it does, we'll instead want to render it below the + // carets along with the other hanging labels. + trailing_label = None; + } + } + + // Write a line of carets + // + // ```text + // │ ^^^^^^ -------^^^^^^^^^-------^^^^^----- ^^^^ trailing label message + // ``` self.outer_gutter(outer_padding)?; self.border_left()?; self.inner_gutter(severity, num_multi_labels, multi_labels)?; - self.label_single(severity, *label_style, source, range.clone(), message)?; + write!(self, " ")?; + + let mut previous_label_style = None; + let placeholder_metrics = Metrics { + byte_index: source.len(), + unicode_width: 1, + }; + for (metrics, ch) in self + .char_metrics(source.char_indices()) + // Add a placeholder source column at the end to allow for + // printing carets at the end of lines, eg: + // + // ```text + // 1 │ Hello world! + // │ ^ + // ``` + .chain(std::iter::once((placeholder_metrics, '\0'))) + { + // Find the current label style at this column + let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8()); + let current_label_style = single_labels + .iter() + .filter(|(_, range, _)| is_overlapping(range, &column_range)) + .map(|(label_style, _, _)| *label_style) + .max_by_key(label_priority_key); + + // Update writer style if necessary + if previous_label_style != current_label_style { + match current_label_style { + None => self.reset()?, + Some(label_style) => { + self.set_color(self.styles().label(severity, label_style))?; + } + } + } + + let caret_ch = match current_label_style { + Some(LabelStyle::Primary) => Some(self.chars().single_primary_caret), + Some(LabelStyle::Secondary) => Some(self.chars().single_secondary_caret), + // Only print padding if we are before the end of the last single line caret + None if metrics.byte_index < max_label_end => Some(' '), + None => None, + }; + if let Some(caret_ch) = caret_ch { + // FIXME: improve rendering of carets between character boundaries + (0..metrics.unicode_width).try_for_each(|_| write!(self, "{}", caret_ch))?; + } + + previous_label_style = current_label_style; + } + // Reset style if it was previously set + if previous_label_style.is_some() { + self.reset()?; + } + // Write first trailing label message + if let Some((_, (label_style, _, message))) = trailing_label { + write!(self, " ")?; + self.set_color(self.styles().label(severity, *label_style))?; + write!(self, "{}", message)?; + self.reset()?; + } + write!(self, "\n")?; + + // Write hanging labels pointing to carets + // + // ```text + // │ │ │ + // │ │ first mutable borrow occurs here + // │ first borrow later used by call + // │ help: some help here + // ``` + if num_messages > trailing_label.iter().count() { + // Write first set of vertical lines before hanging labels + // + // ```text + // │ │ │ + // ``` + self.outer_gutter(outer_padding)?; + self.border_left()?; + self.inner_gutter(severity, num_multi_labels, multi_labels)?; + write!(self, " ")?; + self.caret_pointers( + severity, + max_label_start, + single_labels, + trailing_label, + source.char_indices(), + )?; + write!(self, "\n")?; + + // Write hanging labels pointing to carets + // + // ```text + // │ │ first mutable borrow occurs here + // │ first borrow later used by call + // │ help: some help here + // ``` + for (label_style, range, message) in + hanging_labels(single_labels, trailing_label).rev() + { + self.outer_gutter(outer_padding)?; + self.border_left()?; + self.inner_gutter(severity, num_multi_labels, multi_labels)?; + write!(self, " ")?; + self.caret_pointers( + severity, + max_label_start, + single_labels, + trailing_label, + source + .char_indices() + .take_while(|(byte_index, _)| *byte_index < range.start), + )?; + self.set_color(self.styles().label(severity, *label_style))?; + write!(self, "{}", message)?; + self.reset()?; + write!(self, "\n")?; + } + } } // Write top or bottom label carets underneath source @@ -282,11 +522,11 @@ impl<'writer, 'config> Renderer<'writer, 'config> { // │ ╰───│──────────────────^ woops // │ ╭─│─────────^ // ``` - for (multi_label_index, (_, label)) in multi_labels.iter().enumerate() { + for (multi_label_index, (_, label_style, label)) in multi_labels.iter().enumerate() { let (label_style, range, bottom_message) = match label { - MultiLabel::TopLeft(_) | MultiLabel::Left(_) => continue, // no label caret needed - MultiLabel::Top(ls, range) => (*ls, range, None), - MultiLabel::Bottom(ls, range, message) => (*ls, range, Some(message)), + MultiLabel::TopLeft | MultiLabel::Left => continue, // no label caret needed + MultiLabel::Top(range) => (*label_style, range, None), + MultiLabel::Bottom(range, message) => (*label_style, range, Some(message)), }; self.outer_gutter(outer_padding)?; @@ -301,22 +541,22 @@ impl<'writer, 'config> Renderer<'writer, 'config> { let mut multi_labels_iter = multi_labels.iter().enumerate().peekable(); for label_column in 0..num_multi_labels { match multi_labels_iter.peek() { - Some((i, (label_index, label))) if *label_index == label_column => { + Some((i, (label_index, ls, label))) if *label_index == label_column => { match label { - MultiLabel::TopLeft(ls) | MultiLabel::Left(ls) => { + MultiLabel::TopLeft | MultiLabel::Left => { self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?; } - MultiLabel::Top(ls, ..) if multi_label_index > *i => { + MultiLabel::Top(..) if multi_label_index > *i => { self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?; } - MultiLabel::Bottom(ls, ..) if multi_label_index < *i => { + MultiLabel::Bottom(..) if multi_label_index < *i => { self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?; } - MultiLabel::Top(ls, ..) if multi_label_index == *i => { + MultiLabel::Top(..) if multi_label_index == *i => { underline = Some((*ls, VerticalBound::Top)); self.label_multi_top_left(severity, label_style)? } - MultiLabel::Bottom(ls, ..) if multi_label_index == *i => { + MultiLabel::Bottom(..) if multi_label_index == *i => { underline = Some((*ls, VerticalBound::Bottom)); self.label_multi_bottom_left(severity, label_style)?; } @@ -353,7 +593,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { outer_padding: usize, severity: Severity, num_multi_labels: usize, - multi_labels: &[(usize, MultiLabel<'_>)], + multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)], ) -> io::Result<()> { self.outer_gutter(outer_padding)?; self.border_left()?; @@ -372,7 +612,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { outer_padding: usize, severity: Severity, num_multi_labels: usize, - multi_labels: &[(usize, MultiLabel<'_>)], + multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)], ) -> io::Result<()> { self.outer_gutter(outer_padding)?; self.border_left_break()?; @@ -406,12 +646,39 @@ impl<'writer, 'config> Renderer<'writer, 'config> { Ok(()) } + /// Adds tab-stop aware unicode-width computations to an iterator over + /// character indices. Assumes that the character indices begin at the start + /// of the line. + fn char_metrics( + &self, + char_indices: impl Iterator<Item = (usize, char)>, + ) -> impl Iterator<Item = (Metrics, char)> { + use unicode_width::UnicodeWidthChar; + + let tab_width = self.config.tab_width; + let mut unicode_column = 0; + + char_indices.map(move |(byte_index, ch)| { + let metrics = Metrics { + byte_index, + unicode_width: match (ch, tab_width) { + ('\t', 0) => 0, // Guard divide-by-zero + ('\t', _) => tab_width - (unicode_column % tab_width), + (ch, _) => ch.width().unwrap_or(0), + }, + }; + unicode_column += metrics.unicode_width; + + (metrics, ch) + }) + } + /// Location focus. fn snippet_locus(&mut self, locus: &Locus) -> io::Result<()> { write!( self, - "{origin}:{line_number}:{column_number}", - origin = locus.name, + "{name}:{line_number}:{column_number}", + name = locus.name, line_number = locus.location.line_number, column_number = locus.location.column_number, ) @@ -419,7 +686,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { /// The outer gutter of a source line. fn outer_gutter(&mut self, outer_padding: usize) -> io::Result<()> { - write!(self, "{space: >width$}", space = "", width = outer_padding,)?; + write!(self, "{space: >width$}", space = "", width = outer_padding)?; write!(self, " ")?; Ok(()) } @@ -454,34 +721,37 @@ impl<'writer, 'config> Renderer<'writer, 'config> { Ok(()) } - /// Single-line label with a message. - /// - /// ```text - /// ^^ expected `Int` but found `String` - /// ``` - fn label_single( + /// Write vertical lines pointing to carets. + fn caret_pointers( &mut self, severity: Severity, - label_style: LabelStyle, - source: &str, - range: Range<usize>, - message: &str, + max_label_start: usize, + single_labels: &[SingleLabel<'_>], + trailing_label: Option<(usize, &SingleLabel<'_>)>, + char_indices: impl Iterator<Item = (usize, char)>, ) -> io::Result<()> { - let space_source = slice_at_char_boundaries(source, 0..range.start); - let space_len = self.config.width(space_source); - write!(self, " {space: >width$}", space = "", width = space_len)?; - self.set_color(self.styles().label(severity, label_style))?; - let source = slice_at_char_boundaries(source, range); - // We use `usize::max` here to ensure that we print at least one - // label character - even when we have a zero-length span. - for _ in 0..usize::max(self.config.width(source), 1) { - write!(self, "{}", self.chars().single_caret_char(label_style))?; - } - if !message.is_empty() { - write!(self, " {}", message)?; + for (metrics, ch) in self.char_metrics(char_indices) { + let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8()); + let label_style = hanging_labels(single_labels, trailing_label) + .filter(|(_, range, _)| column_range.contains(&range.start)) + .map(|(label_style, _, _)| *label_style) + .max_by_key(label_priority_key); + + let mut spaces = match label_style { + None => 0..metrics.unicode_width, + Some(label_style) => { + self.set_color(self.styles().label(severity, label_style))?; + write!(self, "{}", self.chars().pointer_left)?; + self.reset()?; + 1..metrics.unicode_width + } + }; + // Only print padding if we are before the end of the last single line caret + if metrics.byte_index <= max_label_start { + spaces.try_for_each(|_| write!(self, " "))?; + } } - self.reset()?; - write!(self, "\n")?; + Ok(()) } @@ -511,36 +781,6 @@ impl<'writer, 'config> Renderer<'writer, 'config> { Ok(()) } - /// The top of a multi-line label. - fn label_multi_top_line( - &mut self, - severity: Severity, - label_style: LabelStyle, - len: usize, - ) -> io::Result<()> { - self.set_color(self.styles().label(severity, label_style))?; - for _ in 0..len { - write!(self, "{}", self.config.chars.multi_top)?; - } - self.reset()?; - Ok(()) - } - - /// The top of a multi-line label. - fn label_multi_bottom_line( - &mut self, - severity: Severity, - label_style: LabelStyle, - len: usize, - ) -> io::Result<()> { - self.set_color(self.styles().label(severity, label_style))?; - for _ in 0..len { - write!(self, "{}", self.config.chars.multi_bottom)?; - } - self.reset()?; - Ok(()) - } - /// The top-left of a multi-line label. /// /// ```text @@ -588,10 +828,21 @@ impl<'writer, 'config> Renderer<'writer, 'config> { range: RangeTo<usize>, ) -> io::Result<()> { self.set_color(self.styles().label(severity, label_style))?; - for _ in 0..(self.config.width(&source[range.clone()]) + 1) { - write!(self, "{}", self.chars().multi_top)?; + + for (metrics, _) in self + .char_metrics(source.char_indices()) + .take_while(|(metrics, _)| metrics.byte_index < range.end + 1) + { + // FIXME: improve rendering of carets between character boundaries + (0..metrics.unicode_width) + .try_for_each(|_| write!(self, "{}", self.chars().multi_top))?; } - write!(self, "{}", self.chars().multi_caret_char_start(label_style))?; + + let caret_start = match label_style { + LabelStyle::Primary => self.config.chars.multi_primary_caret_start, + LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start, + }; + write!(self, "{}", caret_start)?; self.reset()?; write!(self, "\n")?; Ok(()) @@ -611,10 +862,21 @@ impl<'writer, 'config> Renderer<'writer, 'config> { message: &str, ) -> io::Result<()> { self.set_color(self.styles().label(severity, label_style))?; - for _ in 0..self.config.width(&source[range.clone()]) { - write!(self, "{}", self.chars().multi_bottom)?; + + for (metrics, _) in self + .char_metrics(source.char_indices()) + .take_while(|(metrics, _)| metrics.byte_index < range.end) + { + // FIXME: improve rendering of carets between character boundaries + (0..metrics.unicode_width) + .try_for_each(|_| write!(self, "{}", self.chars().multi_bottom))?; } - write!(self, "{}", self.chars().multi_caret_char_end(label_style))?; + + let caret_end = match label_style { + LabelStyle::Primary => self.config.chars.multi_primary_caret_start, + LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start, + }; + write!(self, "{}", caret_end)?; if !message.is_empty() { write!(self, " {}", message)?; } @@ -631,8 +893,16 @@ impl<'writer, 'config> Renderer<'writer, 'config> { ) -> io::Result<()> { match underline { None => self.inner_gutter_space(), - Some((ls, VerticalBound::Top)) => self.label_multi_top_line(severity, ls, 2), - Some((ls, VerticalBound::Bottom)) => self.label_multi_bottom_line(severity, ls, 2), + Some((label_style, vertical_bound)) => { + self.set_color(self.styles().label(severity, label_style))?; + let ch = match vertical_bound { + VerticalBound::Top => self.config.chars.multi_top, + VerticalBound::Bottom => self.config.chars.multi_bottom, + }; + write!(self, "{0}{0}", ch)?; + self.reset()?; + Ok(()) + } } } @@ -646,16 +916,14 @@ impl<'writer, 'config> Renderer<'writer, 'config> { &mut self, severity: Severity, num_multi_labels: usize, - multi_labels: &[(usize, MultiLabel<'_>)], + multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)], ) -> io::Result<()> { let mut multi_labels_iter = multi_labels.iter().peekable(); for label_column in 0..num_multi_labels { match multi_labels_iter.peek() { - Some((label_index, label)) if *label_index == label_column => match label { - MultiLabel::TopLeft(label_style) - | MultiLabel::Left(label_style) - | MultiLabel::Bottom(label_style, ..) => { - self.label_multi_left(severity, *label_style, None)?; + Some((label_index, ls, label)) if *label_index == label_column => match label { + MultiLabel::TopLeft | MultiLabel::Left | MultiLabel::Bottom(..) => { + self.label_multi_left(severity, *ls, None)?; multi_labels_iter.next(); } MultiLabel::Top(..) => { @@ -663,7 +931,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { multi_labels_iter.next(); } }, - Some((_, _)) | None => self.inner_gutter_space()?, + Some((_, _, _)) | None => self.inner_gutter_space()?, } } @@ -699,83 +967,36 @@ impl<'writer, 'config> WriteColor for Renderer<'writer, 'config> { } } -/// Searches for character boundary from byte_index towards the end of the string. -fn closest_char_boundary(s: &str, byte_index: usize) -> usize { - let length = s.len(); - for index in byte_index..=length { - if s.is_char_boundary(index) { - return index; - } - } - length -} - -/// Searches for character boundary from byte_index towards the start of the string. -fn closest_char_boundary_rev(s: &str, byte_index: usize) -> usize { - for index in (0..=byte_index).rev() { - if s.is_char_boundary(index) { - return index; - } - } - 0 +struct Metrics { + byte_index: usize, + unicode_width: usize, } -/// Finds a valid unicode boundaries looking from `range.start` towards the beginning of the string. -/// From `range.end` towards the end of the string. Returning a `&str` of all characters -/// that overlapping the range. -fn slice_at_char_boundaries<'a>(s: &'a str, range: Range<usize>) -> &'a str { - let start = closest_char_boundary_rev(s, range.start); - let end = closest_char_boundary(s, range.end); - &s[start..end] +/// Check if two ranges overlap +fn is_overlapping(range0: &Range<usize>, range1: &Range<usize>) -> bool { + let start = std::cmp::max(range0.start, range1.start); + let end = std::cmp::min(range0.end, range1.end); + start < end } -#[cfg(test)] -mod test { - use super::*; - use std::iter::repeat; - - #[test] - fn test_boundary() { - let s = "🌞"; - assert_eq!(closest_char_boundary(s, 0), 0); - assert_eq!(closest_char_boundary_rev(s, 0), 0); - for i in 1..s.len() { - assert_eq!(closest_char_boundary_rev(s, i), 0); - assert_eq!(closest_char_boundary(s, i), s.len()); - } +/// For prioritizing primary labels over secondary labels when rendering carets. +fn label_priority_key(label_style: &LabelStyle) -> u8 { + match label_style { + LabelStyle::Secondary => 0, + LabelStyle::Primary => 1, } +} - #[test] - fn test_boundaries() { - let s = "🌑🌒🌓🌔"; - let individually = ["🌑", "🌒", "🌓", "🌔"]; - - let mut expect = Vec::new(); - // [(0, 0, "", ""), - // (0, 4, "🌑", "🌑"), repeated 4 times, - // (4, 4, "", ""), once - // (4, 8, "🌒", "🌒"), repeated 4 times, and so on (+4, +4) - // ... - // (16, 16, "", ""); 21] - expect.push((0, 0, "", "")); - for (idx, &char) in individually.iter().enumerate() { - let n = char.len(); - assert_eq!(n, 4); - let expected_start = (idx % n) * n; - let expected_end = (idx % n) * n + n; - expect.extend(repeat((expected_start, expected_end, char, char)).take(n - 1)); - expect.push((expected_end, expected_end, "", "")); - } - - // drop mut. - let expect = expect; - let mut found = Vec::new(); - for i in 0..=s.len() { - let sliced = slice_at_char_boundaries(s, i..i); - let prev = closest_char_boundary_rev(s, i); - let next = closest_char_boundary(s, i); - found.push((prev, next, &s[prev..next], sliced)); - } - assert_eq!(found, expect); - } +/// Return an iterator that yields the labels that require hanging messages +/// rendered underneath them. +fn hanging_labels<'labels, 'diagnostic>( + single_labels: &'labels [SingleLabel<'diagnostic>], + trailing_label: Option<(usize, &'labels SingleLabel<'diagnostic>)>, +) -> impl 'labels + DoubleEndedIterator<Item = &'labels SingleLabel<'diagnostic>> { + single_labels + .iter() + .enumerate() + .filter(|(_, (_, _, message))| !message.is_empty()) + .filter(move |(i, _)| trailing_label.map_or(true, |(j, _)| *i != j)) + .map(|(_, label)| label) } diff --git a/src/term/views.rs b/src/term/views.rs index e6f6cc1..aa79914 100644 --- a/src/term/views.rs +++ b/src/term/views.rs @@ -68,7 +68,7 @@ where range: std::ops::Range<usize>, // TODO: How do we reuse these allocations? single_labels: Vec<SingleLabel<'diagnostic>>, - multi_labels: Vec<(usize, MultiLabel<'diagnostic>)>, + multi_labels: Vec<(usize, LabelStyle, MultiLabel<'diagnostic>)>, } // TODO: Make this data structure external, to allow for allocation reuse @@ -143,14 +143,21 @@ where // to piggyback off its lexicographic comparison implementation. (range.start, range.end).cmp(&(label_start, label_end)) }) { - // If the ranges are the same, order the labels as they - // originally were specified in the diagnostic. - Ok(index) => index + 1, - Err(index) => index, + // If the ranges are the same, order the labels in reverse + // to how they were originally specified in the diagnostic. + // This helps with printing in the renderer. + Ok(index) | Err(index) => index, }; + // Ensure that we print at least one caret, even when we + // have a zero-length source range. + let mut label_range = label_start..label_end; + if label_range.len() == 0 { + label_range.end = label_range.start + 1; + } + line.single_labels - .insert(index, (label.style, label_start..label_end, &label.message)); + .insert(index, (label.style, label_range, &label.message)); } else { // Multiple lines // @@ -182,7 +189,7 @@ where // ```text // 4 │ ╭ case (mod num 5) (mod num 3) of // ``` - "" => (label_index, MultiLabel::TopLeft(label.style)), + "" => (label_index, label.style, MultiLabel::TopLeft), // There's source code in the prefix, so run a label // underneath it to get to the start of the range. // @@ -190,7 +197,7 @@ where // 4 │ fizz₁ num = case (mod num 5) (mod num 3) of // │ ╭─────────────^ // ``` - _ => (label_index, MultiLabel::Top(label.style, ..label_start)), + _ => (label_index, label.style, MultiLabel::Top(..label_start)), }); // Marked lines @@ -210,7 +217,7 @@ where labeled_file .get_or_insert_line(line_index, line_range, line_number) .multi_labels - .push((label_index, MultiLabel::Left(label.style))); + .push((label_index, label.style, MultiLabel::Left)); } // Last labeled line @@ -226,13 +233,12 @@ where .multi_labels .push(( label_index, - MultiLabel::Bottom(label.style, ..label_end, &label.message), + label.style, + MultiLabel::Bottom(..label_end, &label.message), )); } } - // TODO: Insert `None` spaces in `labeled_files` - // Header and message // // ```text @@ -254,7 +260,8 @@ where // │ ^^ expected `Int` but found `String` // │ // ``` - for labeled_file in labeled_files { + let mut labeled_files = labeled_files.into_iter().peekable(); + while let Some(labeled_file) = labeled_files.next() { let source = files.source(labeled_file.file_id).unwrap(); let source = source.as_ref(); @@ -330,12 +337,22 @@ where } } } - renderer.render_snippet_empty( - outer_padding, - self.diagnostic.severity, - labeled_file.num_multi_labels, - ¤t_labels, - )?; + + // Check to see if we should render a trailing border after the + // final line of the snippet. + if labeled_files.peek().is_none() && self.diagnostic.notes.is_empty() { + // We don't render a border if we are at the final newline + // without trailing notes, because it would end up looking too + // spaced-out in combination with the final new line. + } else { + // Render the trailing snippet border. + renderer.render_snippet_empty( + outer_padding, + self.diagnostic.severity, + labeled_file.num_multi_labels, + ¤t_labels, + )?; + } } // Additional notes diff --git a/tests/snapshots/term__empty_ranges__rich_color.snap b/tests/snapshots/term__empty_ranges__rich_color.snap index d1e9a2e..89bb2c0 100644 --- a/tests/snapshots/term__empty_ranges__rich_color.snap +++ b/tests/snapshots/term__empty_ranges__rich_color.snap @@ -5,22 +5,25 @@ expression: TEST_DATA.emit_color(&config) {fg:Green bold bright}note{bold bright}: middle{/} {fg:Blue}┌─{/} hello:1:7 {fg:Blue}│{/} -{fg:Blue}1{/} {fg:Blue}│{/} Hello world! - {fg:Blue}│{/} {fg:Green}^ middle{/} - {fg:Blue}│{/} +{fg:Blue}1{/} {fg:Blue}│{/} Hello {fg:Green}w{/}orld! + {fg:Blue}│{/} {fg:Green}^{/} {fg:Green}middle{/} {fg:Green bold bright}note{bold bright}: end of line{/} {fg:Blue}┌─{/} hello:1:13 {fg:Blue}│{/} {fg:Blue}1{/} {fg:Blue}│{/} Hello world! - {fg:Blue}│{/} {fg:Green}^ end of line{/} - {fg:Blue}│{/} + {fg:Blue}│{/} {fg:Green}^{/} {fg:Green}end of line{/} -{fg:Green bold bright}note{bold bright}: end of file{/} +{fg:Green bold bright}note{bold bright}: end of line{/} {fg:Blue}┌─{/} hello:2:11 {fg:Blue}│{/} {fg:Blue}2{/} {fg:Blue}│{/} Bye world! - {fg:Blue}│{/} {fg:Green}^ end of file{/} + {fg:Blue}│{/} {fg:Green}^{/} {fg:Green}end of line{/} + +{fg:Green bold bright}note{bold bright}: end of file{/} + {fg:Blue}┌─{/} hello:3:4 {fg:Blue}│{/} +{fg:Blue}3{/} {fg:Blue}│{/} + {fg:Blue}│{/} {fg:Green}^{/} {fg:Green}end of file{/} diff --git a/tests/snapshots/term__empty_ranges__rich_no_color.snap b/tests/snapshots/term__empty_ranges__rich_no_color.snap index fa28550..862605c 100644 --- a/tests/snapshots/term__empty_ranges__rich_no_color.snap +++ b/tests/snapshots/term__empty_ranges__rich_no_color.snap @@ -7,20 +7,23 @@ note: middle │ 1 │ Hello world! │ ^ middle - │ note: end of line ┌─ hello:1:13 │ 1 │ Hello world! │ ^ end of line - │ -note: end of file +note: end of line ┌─ hello:2:11 │ 2 │ Bye world! - │ ^ end of file + │ ^ end of line + +note: end of file + ┌─ hello:3:4 │ +3 │ + │ ^ end of file diff --git a/tests/snapshots/term__empty_ranges__short_color.snap b/tests/snapshots/term__empty_ranges__short_color.snap index b3930e0..3713ee4 100644 --- a/tests/snapshots/term__empty_ranges__short_color.snap +++ b/tests/snapshots/term__empty_ranges__short_color.snap @@ -4,5 +4,6 @@ expression: TEST_DATA.emit_color(&config) --- hello:1:7: {fg:Green bold bright}note{bold bright}: middle{/} hello:1:13: {fg:Green bold bright}note{bold bright}: end of line{/} -hello:2:11: {fg:Green bold bright}note{bold bright}: end of file{/} +hello:2:11: {fg:Green bold bright}note{bold bright}: end of line{/} +hello:3:4: {fg:Green bold bright}note{bold bright}: end of file{/} diff --git a/tests/snapshots/term__empty_ranges__short_no_color.snap b/tests/snapshots/term__empty_ranges__short_no_color.snap index 5e2edfc..635a830 100644 --- a/tests/snapshots/term__empty_ranges__short_no_color.snap +++ b/tests/snapshots/term__empty_ranges__short_no_color.snap @@ -4,5 +4,6 @@ expression: TEST_DATA.emit_no_color(&config) --- hello:1:7: note: middle hello:1:13: note: end of line -hello:2:11: note: end of file +hello:2:11: note: end of line +hello:3:4: note: end of file diff --git a/tests/snapshots/term__fizz_buzz__rich_color.snap b/tests/snapshots/term__fizz_buzz__rich_color.snap index 63bfdb9..a051eef 100644 --- a/tests/snapshots/term__fizz_buzz__rich_color.snap +++ b/tests/snapshots/term__fizz_buzz__rich_color.snap @@ -6,14 +6,14 @@ expression: TEST_DATA.emit_color(&config) {fg:Blue}┌─{/} FizzBuzz.fun:3:15 {fg:Blue}│{/} {fg:Blue}3{/} {fg:Blue}│{/} fizz₁ : Nat → String - {fg:Blue}│{/} {fg:Blue}------ expected type `String` found here{/} + {fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}expected type `String` found here{/} {fg:Blue}4{/} {fg:Blue}│{/} fizz₁ num = case (mod num 5) (mod num 3) of {fg:Blue}│{/} {fg:Blue}╭{/}{fg:Blue}─────────────'{/} {fg:Blue}5{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 0 => "FizzBuzz" {fg:Blue}6{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 _ => "Fizz" {fg:Blue}7{/} {fg:Blue}│{/} {fg:Blue}│{/} _ 0 => "Buzz" -{fg:Blue}8{/} {fg:Blue}│{/} {fg:Blue}│{/} _ _ => num - {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^ expected `String`, found `Nat`{/} +{fg:Blue}8{/} {fg:Blue}│{/} {fg:Blue}│{/} _ _ => {fg:Red}num{/} + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^{/} {fg:Red}expected `String`, found `Nat`{/} {fg:Blue}│{/} {fg:Blue}╰{/}{fg:Blue}──────────────' `case` clauses have incompatible types{/} {fg:Blue}│{/} {fg:Blue}={/} expected type `String` @@ -23,17 +23,17 @@ expression: TEST_DATA.emit_color(&config) {fg:Blue}┌─{/} FizzBuzz.fun:10:15 {fg:Blue}│{/} {fg:Blue}10{/} {fg:Blue}│{/} fizz₂ : Nat → String - {fg:Blue}│{/} {fg:Blue}------ expected type `String` found here{/} + {fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}expected type `String` found here{/} {fg:Blue}11{/} {fg:Blue}│{/} fizz₂ num = {fg:Blue}12{/} {fg:Blue}│{/} {fg:Blue}╭{/} case (mod num 5) (mod num 3) of {fg:Blue}13{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 0 => "FizzBuzz" - {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}---------- this is found to be of type `String`{/} + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}----------{/} {fg:Blue}this is found to be of type `String`{/} {fg:Blue}14{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 _ => "Fizz" - {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------ this is found to be of type `String`{/} + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}this is found to be of type `String`{/} {fg:Blue}15{/} {fg:Blue}│{/} {fg:Blue}│{/} _ 0 => "Buzz" - {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------ this is found to be of type `String`{/} -{fg:Blue}16{/} {fg:Blue}│{/} {fg:Blue}│{/} _ _ => num - {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^ expected `String`, found `Nat`{/} + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}this is found to be of type `String`{/} +{fg:Blue}16{/} {fg:Blue}│{/} {fg:Blue}│{/} _ _ => {fg:Red}num{/} + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^{/} {fg:Red}expected `String`, found `Nat`{/} {fg:Blue}│{/} {fg:Blue}╰{/}{fg:Blue}──────────────────' `case` clauses have incompatible types{/} {fg:Blue}│{/} {fg:Blue}={/} expected type `String` diff --git a/tests/snapshots/term__multifile__rich_color.snap b/tests/snapshots/term__multifile__rich_color.snap index 5c3be31..514be49 100644 --- a/tests/snapshots/term__multifile__rich_color.snap +++ b/tests/snapshots/term__multifile__rich_color.snap @@ -5,29 +5,29 @@ expression: TEST_DATA.emit_color(&config) {fg:Red bold bright}error{bold bright}: unknown builtin: `NATRAL`{/} {fg:Blue}┌─{/} Data/Nat.fun:7:13 {fg:Blue}│{/} -{fg:Blue}7{/} {fg:Blue}│{/} {-# BUILTIN NATRAL Nat #-} - {fg:Blue}│{/} {fg:Red}^^^^^^ unknown builtin{/} +{fg:Blue}7{/} {fg:Blue}│{/} {-# BUILTIN {fg:Red}NATRAL{/} Nat #-} + {fg:Blue}│{/} {fg:Red}^^^^^^{/} {fg:Red}unknown builtin{/} {fg:Blue}│{/} {fg:Blue}={/} there is a builtin with a similar name: `NATURAL` {fg:Yellow bold bright}warning{bold bright}: unused parameter pattern: `n₂`{/} {fg:Blue}┌─{/} Data/Nat.fun:17:16 {fg:Blue}│{/} -{fg:Blue}17{/} {fg:Blue}│{/} zero - succ n₂ = zero - {fg:Blue}│{/} {fg:Yellow}^^ unused parameter{/} +{fg:Blue}17{/} {fg:Blue}│{/} zero - succ {fg:Yellow}n₂{/} = zero + {fg:Blue}│{/} {fg:Yellow}^^{/} {fg:Yellow}unused parameter{/} {fg:Blue}│{/} {fg:Blue}={/} consider using a wildcard pattern: `_` {fg:Red bold bright}error[E0001]{bold bright}: unexpected type in application of `_+_`{/} {fg:Blue}┌─{/} Test.fun:4:11 {fg:Blue}│{/} -{fg:Blue} 4{/} {fg:Blue}│{/} _ = 123 + "hello" - {fg:Blue}│{/} {fg:Red}^^^^^^^ expected `Nat`, found `String`{/} +{fg:Blue} 4{/} {fg:Blue}│{/} _ = 123 + {fg:Red}"hello"{/} + {fg:Blue}│{/} {fg:Red}^^^^^^^{/} {fg:Red}expected `Nat`, found `String`{/} {fg:Blue}│{/} {fg:Blue}┌─{/} Data/Nat.fun:11:1 {fg:Blue}│{/} {fg:Blue}11{/} {fg:Blue}│{/} _+_ : Nat → Nat → Nat - {fg:Blue}│{/} {fg:Blue}--------------------- based on the definition of `_+_`{/} + {fg:Blue}│{/} {fg:Blue}---------------------{/} {fg:Blue}based on the definition of `_+_`{/} {fg:Blue}│{/} {fg:Blue}={/} expected type `Nat` found type `String` diff --git a/tests/snapshots/term__multiline_overlapping__rich_color.snap b/tests/snapshots/term__multiline_overlapping__rich_color.snap index 87f8bed..fee20e1 100644 --- a/tests/snapshots/term__multiline_overlapping__rich_color.snap +++ b/tests/snapshots/term__multiline_overlapping__rich_color.snap @@ -7,14 +7,14 @@ expression: TEST_DATA.emit_color(&config) {fg:Blue}│{/} {fg:Blue}1{/} {fg:Blue}│{/} {fg:Blue}╭{/} match line_index.compare(self.last_line_index()) { {fg:Blue}2{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Less => Ok(self.line_starts()[line_index.to_usize()]), - {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}--------------------------------------------- this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/} + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}---------------------------------------------{/} {fg:Blue}this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/} {fg:Blue}3{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Equal => Ok(self.source_span().end()), - {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}---------------------------- this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/} -{fg:Blue}4{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Greater => LineIndexOutOfBoundsError { + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}----------------------------{/} {fg:Blue}this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/} +{fg:Blue}4{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Greater => {fg:Red}LineIndexOutOfBoundsError {{/} {fg:Blue}│{/} {fg:Red}╭{/}{fg:Red}─{/}{fg:Blue}│{/}{fg:Red}──────────────────────────────────^{/} -{fg:Blue}5{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} given: line_index, -{fg:Blue}6{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} max: self.last_line_index(), -{fg:Blue}7{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} }, +{fg:Blue}5{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} {fg:Red} given: line_index,{/} +{fg:Blue}6{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} {fg:Red} max: self.last_line_index(),{/} +{fg:Blue}7{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} {fg:Red} }{/}, {fg:Blue}│{/} {fg:Red}╰{/}{fg:Red}─{/}{fg:Blue}│{/}{fg:Red}─────────────^ expected enum `Result`, found struct `LineIndexOutOfBoundsError`{/} {fg:Blue}8{/} {fg:Blue}│{/} {fg:Blue}│{/} } {fg:Blue}│{/} {fg:Blue}╰{/}{fg:Blue}─────────' `match` arms have incompatible types{/} diff --git a/tests/snapshots/term__overlapping__rich_color.snap b/tests/snapshots/term__overlapping__rich_color.snap new file mode 100644 index 0000000..92f061f --- /dev/null +++ b/tests/snapshots/term__overlapping__rich_color.snap @@ -0,0 +1,58 @@ +--- +source: codespan-reporting/tests/term.rs +expression: TEST_DATA.emit_color(&config) +--- +{fg:Red bold bright}error[E0666]{bold bright}: nested `impl Trait` is not allowed{/} + {fg:Blue}┌─{/} nested_impl_trait.rs:5:46 + {fg:Blue}│{/} +{fg:Blue}5{/} {fg:Blue}│{/} fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<{fg:Red}impl Debug{/}> { x } + {fg:Blue}│{/} {fg:Blue}----------{fg:Red}^^^^^^^^^^{fg:Blue}-{/} + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}│{/} + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}nested `impl Trait` here{/} + {fg:Blue}│{/} {fg:Blue}outer `impl Trait`{/} + +{fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/} + {fg:Blue}┌─{/} typeck_type_placeholder_item.rs:1:18 + {fg:Blue}│{/} +{fg:Blue}1{/} {fg:Blue}│{/} fn fn_test1() -> {fg:Red}_{/} { 5 } + {fg:Blue}│{/} {fg:Red}^{/} + {fg:Blue}│{/} {fg:Red}│{/} + {fg:Blue}│{/} {fg:Red}not allowed in type signatures{/} + {fg:Blue}│{/} {fg:Blue}help: replace with the correct return type: `i32`{/} + +{fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/} + {fg:Blue}┌─{/} typeck_type_placeholder_item.rs:2:24 + {fg:Blue}│{/} +{fg:Blue}2{/} {fg:Blue}│{/} fn fn_test2(x: i32) -> ({fg:Red}_{/}, {fg:Red}_{/}) { (x, x) } + {fg:Blue}│{/} {fg:Blue}-{fg:Red}^{fg:Blue}--{fg:Red}^{fg:Blue}-{/} + {fg:Blue}│{/} {fg:Blue}│{/}{fg:Red}│{/} {fg:Red}│{/} + {fg:Blue}│{/} {fg:Blue}│{/}{fg:Red}│{/} {fg:Red}not allowed in type signatures{/} + {fg:Blue}│{/} {fg:Blue}│{/}{fg:Red}not allowed in type signatures{/} + {fg:Blue}│{/} {fg:Blue}help: replace with the correct return type: `(i32, i32)`{/} + +{fg:Red bold bright}error[E0277]{bold bright}: `std::rc::Rc<()>` cannot be sent between threads safely{/} + {fg:Blue}┌─{/} no_send_res_ports.rs:25:5 + {fg:Blue}│{/} +{fg:Blue}25{/} {fg:Blue}│{/} {fg:Red}thread::spawn{/}(move|| { + {fg:Blue}│{/} {fg:Red}^^^^^^^^^^^^^{/} {fg:Red}`std::rc::Rc<()>` cannot be sent between threads safely{/} + {fg:Blue}│{/} {fg:Blue}╭{/}{fg:Blue}───────────────────'{/} +{fg:Blue}26{/} {fg:Blue}│{/} {fg:Blue}│{/} let y = x; +{fg:Blue}27{/} {fg:Blue}│{/} {fg:Blue}│{/} println!("{:?}", y); +{fg:Blue}28{/} {fg:Blue}│{/} {fg:Blue}│{/} }); + {fg:Blue}│{/} {fg:Blue}╰{/}{fg:Blue}──────' within this `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`{/} + {fg:Blue}│{/} + {fg:Blue}┌─{/} libstd/thread/mod.rs:5:8 + {fg:Blue}│{/} +{fg:Blue} 5{/} {fg:Blue}│{/} F: Send + 'static, + {fg:Blue}│{/} {fg:Blue}----{/} {fg:Blue}required by this bound in `std::thread::spawn`{/} + {fg:Blue}│{/} + {fg:Blue}={/} help: within `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>` + {fg:Blue}={/} note: required because it appears within the type `Port<()>` + {fg:Blue}={/} note: required because it appears within the type `main::Foo` + {fg:Blue}={/} note: required because it appears within the type `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]` + +{fg:Red bold bright}error{bold bright}: aborting due 5 previous errors{/} + {fg:Blue}={/} Some errors have detailed explanations: E0121, E0277, E0666. + {fg:Blue}={/} For more information about an error, try `rustc --explain E0121`. + + diff --git a/tests/snapshots/term__overlapping__rich_no_color.snap b/tests/snapshots/term__overlapping__rich_no_color.snap new file mode 100644 index 0000000..e4a944d --- /dev/null +++ b/tests/snapshots/term__overlapping__rich_no_color.snap @@ -0,0 +1,58 @@ +--- +source: codespan-reporting/tests/term.rs +expression: TEST_DATA.emit_no_color(&config) +--- +error[E0666]: nested `impl Trait` is not allowed + ┌─ nested_impl_trait.rs:5:46 + │ +5 │ fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<impl Debug> { x } + │ ----------^^^^^^^^^^- + │ │ │ + │ │ nested `impl Trait` here + │ outer `impl Trait` + +error[E0121]: the type placeholder `_` is not allowed within types on item signatures + ┌─ typeck_type_placeholder_item.rs:1:18 + │ +1 │ fn fn_test1() -> _ { 5 } + │ ^ + │ │ + │ not allowed in type signatures + │ help: replace with the correct return type: `i32` + +error[E0121]: the type placeholder `_` is not allowed within types on item signatures + ┌─ typeck_type_placeholder_item.rs:2:24 + │ +2 │ fn fn_test2(x: i32) -> (_, _) { (x, x) } + │ -^--^- + │ ││ │ + │ ││ not allowed in type signatures + │ │not allowed in type signatures + │ help: replace with the correct return type: `(i32, i32)` + +error[E0277]: `std::rc::Rc<()>` cannot be sent between threads safely + ┌─ no_send_res_ports.rs:25:5 + │ +25 │ thread::spawn(move|| { + │ ^^^^^^^^^^^^^ `std::rc::Rc<()>` cannot be sent between threads safely + │ ╭───────────────────' +26 │ │ let y = x; +27 │ │ println!("{:?}", y); +28 │ │ }); + │ ╰──────' within this `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]` + │ + ┌─ libstd/thread/mod.rs:5:8 + │ + 5 │ F: Send + 'static, + │ ---- required by this bound in `std::thread::spawn` + │ + = help: within `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>` + = note: required because it appears within the type `Port<()>` + = note: required because it appears within the type `main::Foo` + = note: required because it appears within the type `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]` + +error: aborting due 5 previous errors + = Some errors have detailed explanations: E0121, E0277, E0666. + = For more information about an error, try `rustc --explain E0121`. + + diff --git a/tests/snapshots/term__overlapping__short_color.snap b/tests/snapshots/term__overlapping__short_color.snap new file mode 100644 index 0000000..8e33cbf --- /dev/null +++ b/tests/snapshots/term__overlapping__short_color.snap @@ -0,0 +1,11 @@ +--- +source: codespan-reporting/tests/term.rs +expression: TEST_DATA.emit_color(&config) +--- +nested_impl_trait.rs:5:56: {fg:Red bold bright}error[E0666]{bold bright}: nested `impl Trait` is not allowed{/} +typeck_type_placeholder_item.rs:1:18: {fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/} +typeck_type_placeholder_item.rs:2:25: {fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/} +typeck_type_placeholder_item.rs:2:28: {fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/} +no_send_res_ports.rs:25:5: {fg:Red bold bright}error[E0277]{bold bright}: `std::rc::Rc<()>` cannot be sent between threads safely{/} +{fg:Red bold bright}error{bold bright}: aborting due 5 previous errors{/} + diff --git a/tests/snapshots/term__overlapping__short_no_color.snap b/tests/snapshots/term__overlapping__short_no_color.snap new file mode 100644 index 0000000..47ed5db --- /dev/null +++ b/tests/snapshots/term__overlapping__short_no_color.snap @@ -0,0 +1,11 @@ +--- +source: codespan-reporting/tests/term.rs +expression: TEST_DATA.emit_no_color(&config) +--- +nested_impl_trait.rs:5:56: error[E0666]: nested `impl Trait` is not allowed +typeck_type_placeholder_item.rs:1:18: error[E0121]: the type placeholder `_` is not allowed within types on item signatures +typeck_type_placeholder_item.rs:2:25: error[E0121]: the type placeholder `_` is not allowed within types on item signatures +typeck_type_placeholder_item.rs:2:28: error[E0121]: the type placeholder `_` is not allowed within types on item signatures +no_send_res_ports.rs:25:5: error[E0277]: `std::rc::Rc<()>` cannot be sent between threads safely +error: aborting due 5 previous errors + diff --git a/tests/snapshots/term__one_line__rich_color.snap b/tests/snapshots/term__same_line__rich_color.snap index 6f0a7f4..59bd616 100644 --- a/tests/snapshots/term__one_line__rich_color.snap +++ b/tests/snapshots/term__same_line__rich_color.snap @@ -5,11 +5,11 @@ expression: TEST_DATA.emit_color(&config) {fg:Red bold bright}error[E0499]{bold bright}: cannot borrow `v` as mutable more than once at a time{/} {fg:Blue}┌─{/} one_line.rs:3:5 {fg:Blue}│{/} -{fg:Blue}3{/} {fg:Blue}│{/} v.push(v.pop().unwrap()); - {fg:Blue}│{/} {fg:Blue}- first borrow later used by call{/} - {fg:Blue}│{/} {fg:Blue}---- first mutable borrow occurs here{/} - {fg:Blue}│{/} {fg:Red}^ second mutable borrow occurs here{/} - {fg:Blue}│{/} +{fg:Blue}3{/} {fg:Blue}│{/} v.push({fg:Red}v{/}.pop().unwrap()); + {fg:Blue}│{/} {fg:Blue}-{/} {fg:Blue}----{/} {fg:Red}^{/} {fg:Red}second mutable borrow occurs here{/} + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}│{/} + {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}first mutable borrow occurs here{/} + {fg:Blue}│{/} {fg:Blue}first borrow later used by call{/} {fg:Red bold bright}error{bold bright}: aborting due to previous error{/} {fg:Blue}={/} For more information about this error, try `rustc --explain E0499`. diff --git a/tests/snapshots/term__one_line__rich_no_color.snap b/tests/snapshots/term__same_line__rich_no_color.snap index f857bf1..7342f3f 100644 --- a/tests/snapshots/term__one_line__rich_no_color.snap +++ b/tests/snapshots/term__same_line__rich_no_color.snap @@ -6,10 +6,10 @@ error[E0499]: cannot borrow `v` as mutable more than once at a time ┌─ one_line.rs:3:5 │ 3 │ v.push(v.pop().unwrap()); - │ - first borrow later used by call - │ ---- first mutable borrow occurs here - │ ^ second mutable borrow occurs here - │ + │ - ---- ^ second mutable borrow occurs here + │ │ │ + │ │ first mutable borrow occurs here + │ first borrow later used by call error: aborting due to previous error = For more information about this error, try `rustc --explain E0499`. diff --git a/tests/snapshots/term__one_line__short_color.snap b/tests/snapshots/term__same_line__short_color.snap index c2d86c7..c2d86c7 100644 --- a/tests/snapshots/term__one_line__short_color.snap +++ b/tests/snapshots/term__same_line__short_color.snap diff --git a/tests/snapshots/term__one_line__short_no_color.snap b/tests/snapshots/term__same_line__short_no_color.snap index d4f94e1..d4f94e1 100644 --- a/tests/snapshots/term__one_line__short_no_color.snap +++ b/tests/snapshots/term__same_line__short_no_color.snap diff --git a/tests/snapshots/term__same_ranges__rich_color.snap b/tests/snapshots/term__same_ranges__rich_color.snap index 0860add..e6ec885 100644 --- a/tests/snapshots/term__same_ranges__rich_color.snap +++ b/tests/snapshots/term__same_ranges__rich_color.snap @@ -5,9 +5,10 @@ expression: TEST_DATA.emit_color(&config) {fg:Red bold bright}error{bold bright}: Unexpected token{/} {fg:Blue}┌─{/} same_range:1:5 {fg:Blue}│{/} -{fg:Blue}1{/} {fg:Blue}│{/} ::S { } - {fg:Blue}│{/} {fg:Red}^ Unexpected '{'{/} - {fg:Blue}│{/} {fg:Blue}- Expected '('{/} - {fg:Blue}│{/} +{fg:Blue}1{/} {fg:Blue}│{/} ::S {fg:Red}{{/} } + {fg:Blue}│{/} {fg:Red}^{/} + {fg:Blue}│{/} {fg:Red}│{/} + {fg:Blue}│{/} {fg:Red}Unexpected '{'{/} + {fg:Blue}│{/} {fg:Blue}Expected '('{/} diff --git a/tests/snapshots/term__same_ranges__rich_no_color.snap b/tests/snapshots/term__same_ranges__rich_no_color.snap index 462fc89..09510e6 100644 --- a/tests/snapshots/term__same_ranges__rich_no_color.snap +++ b/tests/snapshots/term__same_ranges__rich_no_color.snap @@ -6,8 +6,9 @@ error: Unexpected token ┌─ same_range:1:5 │ 1 │ ::S { } - │ ^ Unexpected '{' - │ - Expected '(' - │ + │ ^ + │ │ + │ Unexpected '{' + │ Expected '(' diff --git a/tests/snapshots/term__tab_columns__tab_width_2_no_color.snap b/tests/snapshots/term__tab_columns__tab_width_2_no_color.snap new file mode 100644 index 0000000..0faa8d8 --- /dev/null +++ b/tests/snapshots/term__tab_columns__tab_width_2_no_color.snap @@ -0,0 +1,23 @@ +--- +source: codespan-reporting/tests/term.rs +expression: TEST_DATA.emit_no_color(&config) +--- +warning: tab test + ┌─ tab_columns:1:2 + │ +1 │ hello + │ ^^^^^ +2 │ ∙ hello + │ ^^^^^ +3 │ ∙∙ hello + │ ^^^^^ +4 │ ∙∙∙ hello + │ ^^^^^ +5 │ ∙∙∙∙ hello + │ ^^^^^ +6 │ ∙∙∙∙∙ hello + │ ^^^^^ +7 │ ∙∙∙∙∙∙ hello + │ ^^^^^ + + diff --git a/tests/snapshots/term__tab_columns__tab_width_3_no_color.snap b/tests/snapshots/term__tab_columns__tab_width_3_no_color.snap new file mode 100644 index 0000000..40f20a6 --- /dev/null +++ b/tests/snapshots/term__tab_columns__tab_width_3_no_color.snap @@ -0,0 +1,23 @@ +--- +source: codespan-reporting/tests/term.rs +expression: TEST_DATA.emit_no_color(&config) +--- +warning: tab test + ┌─ tab_columns:1:2 + │ +1 │ hello + │ ^^^^^ +2 │ ∙ hello + │ ^^^^^ +3 │ ∙∙ hello + │ ^^^^^ +4 │ ∙∙∙ hello + │ ^^^^^ +5 │ ∙∙∙∙ hello + │ ^^^^^ +6 │ ∙∙∙∙∙ hello + │ ^^^^^ +7 │ ∙∙∙∙∙∙ hello + │ ^^^^^ + + diff --git a/tests/snapshots/term__tab_columns__tab_width_6_no_color.snap b/tests/snapshots/term__tab_columns__tab_width_6_no_color.snap new file mode 100644 index 0000000..018a402 --- /dev/null +++ b/tests/snapshots/term__tab_columns__tab_width_6_no_color.snap @@ -0,0 +1,23 @@ +--- +source: codespan-reporting/tests/term.rs +expression: TEST_DATA.emit_no_color(&config) +--- +warning: tab test + ┌─ tab_columns:1:2 + │ +1 │ hello + │ ^^^^^ +2 │ ∙ hello + │ ^^^^^ +3 │ ∙∙ hello + │ ^^^^^ +4 │ ∙∙∙ hello + │ ^^^^^ +5 │ ∙∙∙∙ hello + │ ^^^^^ +6 │ ∙∙∙∙∙ hello + │ ^^^^^ +7 │ ∙∙∙∙∙∙ hello + │ ^^^^^ + + diff --git a/tests/snapshots/term__tab_columns__tab_width_default_no_color.snap b/tests/snapshots/term__tab_columns__tab_width_default_no_color.snap new file mode 100644 index 0000000..67cd187 --- /dev/null +++ b/tests/snapshots/term__tab_columns__tab_width_default_no_color.snap @@ -0,0 +1,23 @@ +--- +source: codespan-reporting/tests/term.rs +expression: TEST_DATA.emit_no_color(&config) +--- +warning: tab test + ┌─ tab_columns:1:2 + │ +1 │ hello + │ ^^^^^ +2 │ ∙ hello + │ ^^^^^ +3 │ ∙∙ hello + │ ^^^^^ +4 │ ∙∙∙ hello + │ ^^^^^ +5 │ ∙∙∙∙ hello + │ ^^^^^ +6 │ ∙∙∙∙∙ hello + │ ^^^^^ +7 │ ∙∙∙∙∙∙ hello + │ ^^^^^ + + diff --git a/tests/snapshots/term__tabbed__tab_width_3_no_color.snap b/tests/snapshots/term__tabbed__tab_width_3_no_color.snap index e8d797c..f7328c3 100644 --- a/tests/snapshots/term__tabbed__tab_width_3_no_color.snap +++ b/tests/snapshots/term__tabbed__tab_width_3_no_color.snap @@ -7,20 +7,17 @@ warning: unknown weapon `DogJaw` │ 3 │ Weapon: DogJaw │ ^^^^^^ the weapon - │ warning: unknown condition `attack-cooldown` ┌─ tabbed:4:23 │ -4 │ ReloadingCondition: attack-cooldown - │ ^^^^^^^^^^^^^^^ the condition - │ +4 │ ReloadingCondition: attack-cooldown + │ ^^^^^^^^^^^^^^^ the condition warning: unknown field `Foo` ┌─ tabbed:5:2 │ 5 │ Foo: Bar │ ^^^ the field - │ diff --git a/tests/snapshots/term__tabbed__tab_width_6_no_color.snap b/tests/snapshots/term__tabbed__tab_width_6_no_color.snap index 72991cf..bcd1b2b 100644 --- a/tests/snapshots/term__tabbed__tab_width_6_no_color.snap +++ b/tests/snapshots/term__tabbed__tab_width_6_no_color.snap @@ -7,20 +7,17 @@ warning: unknown weapon `DogJaw` │ 3 │ Weapon: DogJaw │ ^^^^^^ the weapon - │ warning: unknown condition `attack-cooldown` ┌─ tabbed:4:23 │ -4 │ ReloadingCondition: attack-cooldown - │ ^^^^^^^^^^^^^^^ the condition - │ +4 │ ReloadingCondition: attack-cooldown + │ ^^^^^^^^^^^^^^^ the condition warning: unknown field `Foo` ┌─ tabbed:5:2 │ 5 │ Foo: Bar │ ^^^ the field - │ diff --git a/tests/snapshots/term__tabbed__tab_width_default_no_color.snap b/tests/snapshots/term__tabbed__tab_width_default_no_color.snap index d3c0ee2..d3a41a0 100644 --- a/tests/snapshots/term__tabbed__tab_width_default_no_color.snap +++ b/tests/snapshots/term__tabbed__tab_width_default_no_color.snap @@ -7,20 +7,17 @@ warning: unknown weapon `DogJaw` │ 3 │ Weapon: DogJaw │ ^^^^^^ the weapon - │ warning: unknown condition `attack-cooldown` ┌─ tabbed:4:23 │ -4 │ ReloadingCondition: attack-cooldown - │ ^^^^^^^^^^^^^^^ the condition - │ +4 │ ReloadingCondition: attack-cooldown + │ ^^^^^^^^^^^^^^^ the condition warning: unknown field `Foo` ┌─ tabbed:5:2 │ 5 │ Foo: Bar │ ^^^ the field - │ diff --git a/tests/snapshots/term__unicode_spans__rich_no_color.snap b/tests/snapshots/term__unicode_spans__rich_no_color.snap index e76de4b..653a60e 100644 --- a/tests/snapshots/term__unicode_spans__rich_no_color.snap +++ b/tests/snapshots/term__unicode_spans__rich_no_color.snap @@ -6,10 +6,24 @@ error[E01]: cow may not jump during new moon. ┌─ moon_jump.rs:1:1 │ 1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄 - │ ^^ Invalid jump - │ -- Cow range does not start at boundary. - │ ------ Cow does not start or end at boundary. + │ ^^ Invalid jump + +note: invalid unicode range + ┌─ moon_jump.rs:1:1 + │ +1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄 + │ -- Cow range does not start at boundary. + +note: invalid unicode range + ┌─ moon_jump.rs:1:3 + │ +1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄 │ -- Cow range does not end at boundary. + +note: invalid unicode range + ┌─ moon_jump.rs:1:1 │ +1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄 + │ ------ Cow does not start or end at boundary. diff --git a/tests/snapshots/term__unicode_spans__short_no_color.snap b/tests/snapshots/term__unicode_spans__short_no_color.snap index 9085082..1b19451 100644 --- a/tests/snapshots/term__unicode_spans__short_no_color.snap +++ b/tests/snapshots/term__unicode_spans__short_no_color.snap @@ -3,4 +3,7 @@ source: codespan-reporting/tests/term.rs expression: TEST_DATA.emit_no_color(&config) --- moon_jump.rs:1:1: error[E01]: cow may not jump during new moon. +note: invalid unicode range +note: invalid unicode range +note: invalid unicode range diff --git a/tests/term.rs b/tests/term.rs index 95cc670..ee02372 100644 --- a/tests/term.rs +++ b/tests/term.rs @@ -87,20 +87,23 @@ mod empty { test_emit!(short_no_color); } -/// Based on: https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/codemap_tests/one_line.stderr -mod one_line { +/// Based on: +/// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/codemap_tests/one_line.stderr +mod same_line { use super::*; lazy_static::lazy_static! { - static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, String>> = { - let file = SimpleFile::new( + static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = { + let mut files = SimpleFiles::new(); + + let file_id1 = files.add( "one_line.rs", unindent::unindent(r#" fn main() { let mut v = vec![Some("foo"), Some("bar")]; v.push(v.pop().unwrap()); } - "#) + "#), ); let diagnostics = vec![ @@ -108,11 +111,11 @@ mod one_line { .with_code("E0499") .with_message("cannot borrow `v` as mutable more than once at a time") .with_labels(vec![ - Label::primary((), 71..72) + Label::primary(file_id1, 71..72) .with_message("second mutable borrow occurs here"), - Label::secondary((), 64..65) + Label::secondary(file_id1, 64..65) .with_message("first borrow later used by call"), - Label::secondary((), 66..70) + Label::secondary(file_id1, 66..70) .with_message("first mutable borrow occurs here"), ]), Diagnostic::error() @@ -122,7 +125,149 @@ mod one_line { ]), ]; - TestData { files: file, diagnostics } + TestData { files, diagnostics } + }; + } + + test_emit!(rich_color); + test_emit!(short_color); + test_emit!(rich_no_color); + test_emit!(short_no_color); +} + +/// Based on: +/// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/nested_impl_trait.stderr +/// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/typeck/typeck_type_placeholder_item.stderr +/// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/no_send_res_ports.stderr +mod overlapping { + use super::*; + + lazy_static::lazy_static! { + static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = { + let mut files = SimpleFiles::new(); + + let file_id1 = files.add( + "nested_impl_trait.rs", + unindent::unindent(r#" + use std::fmt::Debug; + + fn fine(x: impl Into<u32>) -> impl Into<u32> { x } + + fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<impl Debug> { x } + "#), + ); + let file_id2 = files.add( + "typeck_type_placeholder_item.rs", + unindent::unindent(r#" + fn fn_test1() -> _ { 5 } + fn fn_test2(x: i32) -> (_, _) { (x, x) } + "#), + ); + let file_id3 = files.add( + "libstd/thread/mod.rs", + unindent::unindent(r#" + #[stable(feature = "rust1", since = "1.0.0")] + pub fn spawn<F, T>(self, f: F) -> io::Result<JoinHandle<T>> + where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, + { + unsafe { self.spawn_unchecked(f) } + } + "#), + ); + let file_id4 = files.add( + "no_send_res_ports.rs", + unindent::unindent(r#" + use std::thread; + use std::rc::Rc; + + #[derive(Debug)] + struct Port<T>(Rc<T>); + + fn main() { + #[derive(Debug)] + struct Foo { + _x: Port<()>, + } + + impl Drop for Foo { + fn drop(&mut self) {} + } + + fn foo(x: Port<()>) -> Foo { + Foo { + _x: x + } + } + + let x = foo(Port(Rc::new(()))); + + thread::spawn(move|| { + let y = x; + println!("{:?}", y); + }); + } + "#), + ); + + let diagnostics = vec![ + Diagnostic::error() + .with_code("E0666") + .with_message("nested `impl Trait` is not allowed") + .with_labels(vec![ + Label::primary(file_id1, 129..139) + .with_message("nested `impl Trait` here"), + Label::secondary(file_id1, 119..140) + .with_message("outer `impl Trait`"), + ]), + Diagnostic::error() + .with_code("E0121") + .with_message("the type placeholder `_` is not allowed within types on item signatures") + .with_labels(vec![ + Label::primary(file_id2, 17..18) + .with_message("not allowed in type signatures"), + Label::secondary(file_id2, 17..18) + .with_message("help: replace with the correct return type: `i32`"), + ]), + Diagnostic::error() + .with_code("E0121") + .with_message("the type placeholder `_` is not allowed within types on item signatures") + .with_labels(vec![ + Label::primary(file_id2, 49..50) + .with_message("not allowed in type signatures"), + Label::primary(file_id2, 52..53) + .with_message("not allowed in type signatures"), + Label::secondary(file_id2, 48..54) + .with_message("help: replace with the correct return type: `(i32, i32)`"), + ]), + Diagnostic::error() + .with_code("E0277") + .with_message("`std::rc::Rc<()>` cannot be sent between threads safely") + .with_labels(vec![ + Label::primary(file_id4, 339..352) + .with_message("`std::rc::Rc<()>` cannot be sent between threads safely"), + Label::secondary(file_id4, 353..416) + .with_message("within this `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`"), + Label::secondary(file_id3, 141..145) + .with_message("required by this bound in `std::thread::spawn`"), + ]) + .with_notes(vec![ + "help: within `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>`".to_owned(), + "note: required because it appears within the type `Port<()>`".to_owned(), + "note: required because it appears within the type `main::Foo`".to_owned(), + "note: required because it appears within the type `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`".to_owned(), + ]), + Diagnostic::error() + .with_message("aborting due 5 previous errors") + .with_notes(vec![ + "Some errors have detailed explanations: E0121, E0277, E0666.".to_owned(), + "For more information about an error, try `rustc --explain E0121`.".to_owned(), + ]), + ]; + + TestData { files, diagnostics } }; } @@ -185,7 +330,7 @@ mod empty_ranges { lazy_static::lazy_static! { static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, &'static str>> = { - let file = SimpleFile::new("hello", "Hello world!\nBye world!"); + let file = SimpleFile::new("hello", "Hello world!\nBye world!\n "); let eof = file.source().len(); let diagnostics = vec![ @@ -196,6 +341,9 @@ mod empty_ranges { .with_message("end of line") .with_labels(vec![Label::primary((), 12..12).with_message("end of line")]), Diagnostic::note() + .with_message("end of line") + .with_labels(vec![Label::primary((), 23..23).with_message("end of line")]), + Diagnostic::note() .with_message("end of file") .with_labels(vec![Label::primary((), eof..eof).with_message("end of file")]), ]; @@ -483,10 +631,83 @@ mod tabbed { fn tab_width_default_no_color() { let config = TEST_CONFIG.clone(); - insta::assert_snapshot!( - "tab_width_default_no_color", - TEST_DATA.emit_no_color(&config) - ); + insta::assert_snapshot!(TEST_DATA.emit_no_color(&config)); + } + + #[test] + fn tab_width_3_no_color() { + let config = Config { + tab_width: 3, + ..TEST_CONFIG.clone() + }; + + insta::assert_snapshot!(TEST_DATA.emit_no_color(&config)); + } + + #[test] + fn tab_width_6_no_color() { + let config = Config { + tab_width: 6, + ..TEST_CONFIG.clone() + }; + + insta::assert_snapshot!(TEST_DATA.emit_no_color(&config)); + } +} + +mod tab_columns { + use super::*; + + lazy_static::lazy_static! { + static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = { + let mut files = SimpleFiles::new(); + + let source = [ + "\thello", + "∙\thello", + "∙∙\thello", + "∙∙∙\thello", + "∙∙∙∙\thello", + "∙∙∙∙∙\thello", + "∙∙∙∙∙∙\thello", + ].join("\n"); + let hello_ranges = source + .match_indices("hello") + .map(|(start, hello)| start..(start+hello.len())) + .collect::<Vec<_>>(); + + let file_id = files.add("tab_columns", source); + + let diagnostics = vec![ + Diagnostic::warning() + .with_message("tab test") + .with_labels( + hello_ranges + .into_iter() + .map(|range| Label::primary(file_id, range)) + .collect(), + ), + ]; + + TestData { files, diagnostics } + }; + } + + #[test] + fn tab_width_default_no_color() { + let config = TEST_CONFIG.clone(); + + insta::assert_snapshot!(TEST_DATA.emit_no_color(&config)); + } + + #[test] + fn tab_width_2_no_color() { + let config = Config { + tab_width: 2, + ..TEST_CONFIG.clone() + }; + + insta::assert_snapshot!(TEST_DATA.emit_no_color(&config)); } #[test] @@ -510,7 +731,8 @@ mod tabbed { } } -/// Based on: https://github.com/TheSamsa/rust/blob/75cf41afb468152611212271bae026948cd3ba46/src/test/ui/codemap_tests/unicode.stderr +/// Based on: +/// - https://github.com/TheSamsa/rust/blob/75cf41afb468152611212271bae026948cd3ba46/src/test/ui/codemap_tests/unicode.stderr mod unicode { use super::*; @@ -595,14 +817,26 @@ mod unicode_spans { .with_labels(vec![ Label::primary((), invalid_start..invalid_end) .with_message("Invalid jump"), + ]), + Diagnostic::note() + .with_message("invalid unicode range") + .with_labels(vec![ Label::secondary((), invalid_start.."🐄".len()) .with_message("Cow range does not start at boundary."), + ]), + Diagnostic::note() + .with_message("invalid unicode range") + .with_labels(vec![ Label::secondary((), "🐄🌑".len().."🐄🌑🐄".len() - 1) .with_message("Cow range does not end at boundary."), + ]), + Diagnostic::note() + .with_message("invalid unicode range") + .with_labels(vec![ Label::secondary((), invalid_start.."🐄🌑🐄".len() - 1) .with_message("Cow does not start or end at boundary."), - - ])]; + ]), + ]; TestData{files: file, diagnostics } }; } |