Merge pull request #1372 from epage/render

fix(cli): Replace error renderer with annotate-snippets
This commit is contained in:
Ed Page
2025-09-03 15:13:19 -05:00
committed by GitHub
18 changed files with 325 additions and 392 deletions
Generated
+128 -60
View File
@@ -49,6 +49,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "annotate-snippets"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982b1e014494ccc9d4737fa74eee118cd67b1905d781b7b9d03b32d77858593f"
dependencies = [
"anstyle",
"unicode-width",
]
[[package]]
name = "anstream"
version = "0.6.19"
@@ -550,12 +560,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.5"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.48.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -775,6 +785,12 @@ version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "log"
version = "0.4.27"
@@ -1085,10 +1101,23 @@ dependencies = [
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys",
"linux-raw-sys 0.4.10",
"windows-sys 0.48.0",
]
[[package]]
name = "rustix"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys 0.9.4",
"windows-sys 0.60.2",
]
[[package]]
name = "rustversion"
version = "1.0.18"
@@ -1344,6 +1373,12 @@ dependencies = [
"syn 2.0.90",
]
[[package]]
name = "supports-unicode"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
[[package]]
name = "syn"
version = "1.0.109"
@@ -1375,18 +1410,18 @@ dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"rustix 0.38.19",
"windows-sys 0.48.0",
]
[[package]]
name = "terminal_size"
version = "0.4.0"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef"
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
dependencies = [
"rustix",
"windows-sys 0.59.0",
"rustix 1.0.8",
"windows-sys 0.60.2",
]
[[package]]
@@ -1540,6 +1575,7 @@ name = "typos-cli"
version = "1.36.0"
dependencies = [
"ahash",
"annotate-snippets",
"anstream",
"anstyle",
"anyhow",
@@ -1570,6 +1606,8 @@ dependencies = [
"serde_json",
"serde_regex",
"snapbox",
"supports-unicode",
"terminal_size",
"thread_local",
"toml",
"trycmd",
@@ -1577,10 +1615,7 @@ dependencies = [
"typos",
"typos-dict",
"typos-vars",
"unic-emoji-char",
"unicase",
"unicode-segmentation",
"unicode-width",
"varcon-core",
]
@@ -1615,47 +1650,6 @@ dependencies = [
"varcon-core",
]
[[package]]
name = "unic-char-property"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
dependencies = [
"unic-char-range",
]
[[package]]
name = "unic-char-range"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[package]]
name = "unic-common"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-emoji-char"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d"
dependencies = [
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-version"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
dependencies = [
"unic-common",
]
[[package]]
name = "unicase"
version = "2.8.1"
@@ -1668,12 +1662,6 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.2.1"
@@ -1809,6 +1797,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-sys"
version = "0.48.0"
@@ -1827,6 +1821,15 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.3",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
@@ -1851,13 +1854,30 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
@@ -1870,6 +1890,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@@ -1882,6 +1908,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@@ -1894,12 +1926,24 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@@ -1912,6 +1956,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
@@ -1924,6 +1974,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@@ -1936,6 +1992,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
@@ -1948,6 +2010,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.12"
+3 -3
View File
@@ -60,15 +60,12 @@ difflib = "0.4"
proc-exit = "2.0"
human-panic = "2.0.3"
content_inspector = "0.2.4"
unicode-segmentation = "1.11.0"
derive_more = { version = "2.0", features = ["from", "display"] }
derive_setters = "0.1"
itertools = "0.14"
serde_json = "1.0"
kstring = { version = "2.0.0", features = ["serde"] }
typed-arena = "2.0.2"
unicode-width = "0.2.0"
unic-emoji-char = "0.9.0"
thread_local = "1.1.8"
globset = "0.4.14"
anstyle = "1.0.6"
@@ -79,6 +76,9 @@ regex = "1.10.4"
encoding_rs = "0.8.34"
serde-sarif = "0.7.0"
schemars = { version = "0.9.0", features = ["preserve_order","semver1"], optional = true }
annotate-snippets = "0.12.2"
terminal_size = "0.4.3"
supports-unicode = "3.0.0"
[dev-dependencies]
assert_fs = "1.1"
+92 -219
View File
@@ -1,17 +1,22 @@
#![allow(clippy::needless_update)]
use std::borrow::Cow;
use std::io::Write as _;
use std::sync::{atomic, Mutex};
use annotate_snippets::Annotation;
use annotate_snippets::AnnotationKind;
use annotate_snippets::Group;
use annotate_snippets::Level;
use annotate_snippets::Origin;
use annotate_snippets::Snippet;
use anstream::stderr;
use anstream::stdout;
use serde_sarif::sarif;
use serde_sarif::sarif::{ArtifactChange, ArtifactContent, Fix, Replacement};
use typos_cli::report::{Context, Message, Report, Typo};
use unicode_width::UnicodeWidthStr;
use typos_cli::report::{Context, Error, Message, Report, Typo};
const ERROR: anstyle::Style = anstyle::AnsiColor::BrightRed.on_default();
const INFO: anstyle::Style = anstyle::AnsiColor::BrightBlue.on_default();
const GOOD: anstyle::Style = anstyle::AnsiColor::BrightGreen.on_default();
pub(crate) struct MessageStatus<'r> {
typos_found: atomic::AtomicBool,
@@ -66,11 +71,15 @@ pub(crate) struct PrintBrief;
impl Report for PrintBrief {
fn report(&self, msg: Message<'_>) -> Result<(), std::io::Error> {
let renderer = RENDERER.clone().short_message(true);
match &msg {
Message::BinaryFile(msg) => {
log::info!("{msg}");
}
Message::Typo(msg) => print_brief_correction(msg)?,
Message::Typo(msg) => {
let report = &[typo_to_group(msg)];
writeln!(stdout(), "{}", renderer.render(report))?;
}
Message::FileType(msg) => {
let info = INFO.render();
let reset = anstyle::Reset.render();
@@ -88,7 +97,8 @@ impl Report for PrintBrief {
writeln!(stdout().lock(), "{}", msg.data)?;
}
Message::Error(msg) => {
log::error!("{}: {}", context_display(&msg.context), msg.msg);
let report = &[error_to_group(msg)];
writeln!(stderr(), "{}", renderer.render(report))?;
}
_ => unimplemented!("New message {:?}", msg),
}
@@ -104,7 +114,10 @@ impl Report for PrintLong {
Message::BinaryFile(msg) => {
log::info!("{msg}");
}
Message::Typo(msg) => print_long_correction(msg)?,
Message::Typo(msg) => {
let report = &[typo_to_group(msg)];
writeln!(stdout(), "{}", RENDERER.render(report))?;
}
Message::FileType(msg) => {
let info = INFO.render();
let reset = anstyle::Reset.render();
@@ -122,7 +135,8 @@ impl Report for PrintLong {
writeln!(stdout().lock(), "{}", msg.data)?;
}
Message::Error(msg) => {
log::error!("{}: {}", context_display(&msg.context), msg.msg);
let report = &[error_to_group(msg)];
writeln!(stderr(), "{}", RENDERER.render(report))?;
}
_ => unimplemented!("New message {:?}", msg),
}
@@ -130,150 +144,86 @@ impl Report for PrintLong {
}
}
fn print_brief_correction(msg: &Typo<'_>) -> Result<(), std::io::Error> {
let error = ERROR.render();
let good = GOOD.render();
let info = INFO.render();
let reset = anstyle::Reset.render();
let start = String::from_utf8_lossy(&msg.buffer[0..msg.byte_offset]);
let column_number =
unicode_segmentation::UnicodeSegmentation::graphemes(start.as_ref(), true).count() + 1;
match &msg.corrections {
typos::Status::Valid => {}
fn typo_to_group<'t>(msg: &'t Typo<'t>) -> Group<'t> {
let title = match &msg.corrections {
typos::Status::Valid => unimplemented!("never valid words to report"),
typos::Status::Invalid => {
let divider = ":";
writeln!(
stdout().lock(),
"{info}{}{divider}{column_number}{reset}: `{error}{}{reset}` is disallowed",
context_display(&msg.context),
msg.typo,
)?;
format!("`{}` is disallowed", msg.typo,)
}
typos::Status::Corrections(corrections) => {
let divider = ":";
writeln!(
stdout().lock(),
"{info}{}{divider}{column_number}{reset}: `{error}{}{reset}` -> {}",
context_display(&msg.context),
format!(
"`{}` should be {}",
msg.typo,
itertools::join(
corrections.iter().map(|s| format!("`{good}{s}{reset}`")),
", "
)
)?;
itertools::join(corrections.iter().map(|s| format!("`{s}`")), ", ")
)
}
}
Ok(())
};
let group = Group::with_title(Level::ERROR.primary_title(Cow::Owned(title)));
let group = match &msg.context {
Some(Context::File(context)) => {
let path = context.path.as_os_str().to_string_lossy();
let line = String::from_utf8_lossy(msg.buffer.as_ref());
let snippet = Snippet::source(line)
.path(path)
.line_start(context.line_num);
append_corrections(msg, 0, snippet, group)
}
Some(Context::Path(context)) => {
let path = context
.path
.parent()
.unwrap_or(std::path::Path::new("."))
.join("");
let path = path.as_os_str().to_string_lossy();
let line = context.path.as_os_str().to_string_lossy();
let snippet = Snippet::source(line);
append_corrections(msg, path.len(), snippet, group)
}
Some(_) | None => group,
};
group
}
fn print_long_correction(msg: &Typo<'_>) -> Result<(), std::io::Error> {
let error = ERROR.render();
let good = GOOD.render();
let info = INFO.render();
let reset = anstyle::Reset.render();
let stdout = stdout();
let mut handle = stdout.lock();
let line = String::from_utf8_lossy(msg.buffer.as_ref());
let line = line.replace('\t', " ");
let start = String::from_utf8_lossy(&msg.buffer[0..msg.byte_offset]);
let column_number =
unicode_segmentation::UnicodeSegmentation::graphemes(start.as_ref(), true).count() + 1;
match &msg.corrections {
typos::Status::Valid => {}
typos::Status::Invalid => {
writeln!(
handle,
"{error}error{reset}: `{error}{}{reset}` is disallowed",
msg.typo,
)?;
}
typos::Status::Corrections(corrections) => {
writeln!(
handle,
"{error}error{reset}: `{error}{}{reset}` should be {}",
msg.typo,
itertools::join(
corrections.iter().map(|s| format!("`{good}{s}{reset}`")),
", "
)
)?;
}
}
let divider = ":";
writeln!(
handle,
"{info} --> {reset}{}{divider}{column_number}",
context_display(&msg.context),
)?;
if let Some(Context::File(context)) = &msg.context {
let line_num = context.line_num.to_string();
let line_indent: String = itertools::repeat_n(" ", line_num.len()).collect();
let line = line.trim_end();
let visible_column = calculate_visible_column_width(start.as_ref());
let visible_len = calculate_visible_column_width(msg.typo);
let hl_indent: String = itertools::repeat_n(" ", visible_column).collect();
let hl: String = itertools::repeat_n("^", visible_len).collect();
writeln!(handle, "{info}{line_indent} |{reset}")?;
writeln!(handle, "{info}{line_num} |{reset} {line}")?;
writeln!(
handle,
"{info}{line_indent} |{reset} {hl_indent}{error}{hl}{reset}",
)?;
writeln!(handle, "{info}{line_indent} |{reset}")?;
}
Ok(())
fn append_corrections<'t>(
msg: &'t Typo<'t>,
offset: usize,
snippet: Snippet<'t, Annotation<'t>>,
group: Group<'t>,
) -> Group<'t> {
let span_start = msg.byte_offset + offset;
let span_end = span_start + msg.typo.len();
let span = span_start..span_end;
let snippet = snippet.annotation(AnnotationKind::Primary.span(span));
group.element(snippet)
}
fn calculate_visible_column_width(str: &str) -> usize {
let mut result = 0;
let graphemes = unicode_segmentation::UnicodeSegmentation::graphemes(str, true);
for grapheme in graphemes {
result += if grapheme == "\t" {
// TODO: config tab width
1
} else if is_emoji(grapheme) {
// UnicodeWidthStr::width doesn't cover for emoji according to their README.
// See: https://github.com/unicode-rs/unicode-width#unicode-width
// Also, the actual rendered column width may differ from calculation, especially for emojis.
// In here, we expect emoji renderers should render this emoji properly.
2
fn error_to_group<'e>(error: &'e Error<'e>) -> Group<'e> {
let group = Group::with_title(Level::ERROR.primary_title(&error.msg));
match &error.context {
Some(Context::File(context)) => group.element(
Origin::path(context.path.as_os_str().to_string_lossy()).line(context.line_num),
),
Some(Context::Path(context)) => {
group.element(Origin::path(context.path.as_os_str().to_string_lossy()))
}
Some(_) | None => group,
}
}
static RENDERER: std::sync::LazyLock<annotate_snippets::Renderer> =
std::sync::LazyLock::new(|| {
let width = terminal_size::terminal_size()
.map(|(w, _)| w.0 as usize)
.unwrap_or(annotate_snippets::renderer::DEFAULT_TERM_WIDTH);
let decor_style = if supports_unicode::supports_unicode() {
annotate_snippets::renderer::DecorStyle::Unicode
} else {
UnicodeWidthStr::width(grapheme)
}
}
result
}
fn is_emoji(grapheme: &str) -> bool {
if grapheme.is_ascii() {
return false;
}
for ch in grapheme.chars() {
if unic_emoji_char::is_emoji(ch) {
return true;
}
}
false
}
fn context_display<'c>(context: &'c Option<Context<'c>>) -> &'c dyn std::fmt::Display {
context
.as_ref()
.map(|c| c as &dyn std::fmt::Display)
.unwrap_or(&"")
}
annotate_snippets::renderer::DecorStyle::Ascii
};
annotate_snippets::Renderer::styled()
.term_width(width)
.decor_style(decor_style)
});
#[derive(Copy, Clone, Debug)]
pub(crate) struct PrintJson;
@@ -500,80 +450,3 @@ fn typo_to_sarif_location(msg: &Typo<'_>) -> Result<sarif::Location, Box<dyn std
Ok(location)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_visible_column_width_visible_ascii() {
for c in '!'..'~' {
assert_eq!(1, calculate_visible_column_width(&c.to_string()));
}
}
#[test]
fn test_calculate_visible_column_width_horizontal_tab() {
assert_eq!(1, calculate_visible_column_width("\t"));
}
#[test]
fn test_calculate_visible_column_width_latin_cyrillic() {
let latin_cyrillic_chars = [
"À", /* U+00C0; Latin Capital Letter A with Grave */
"À", /* U+0041 U+0300; Latin Capital Letter A, Combining Grave Accent */
"А", /* U+0410 Cyrillic Capital Letter A */
];
for (i, ch) in latin_cyrillic_chars.iter().enumerate() {
let width = calculate_visible_column_width(ch);
assert_eq!(1, width, "latin_cyrillic[{i}]: {ch}",);
}
}
#[test]
fn test_calculate_visible_column_width_cjk() {
let cjk_chars = [
"中", /* U+4E2D */
"あ", /* U+3042 */
"한", /* U+1F635 U+200D U+1F4AB, NFC Korean */
"한", /* U+1F441 U+FE0F U+200D U+1F5E8 U+FE0F, NFD Korean */
];
for (i, ch) in cjk_chars.iter().enumerate() {
let width = calculate_visible_column_width(ch);
assert_eq!(2, width, "cjk[{i}]: {ch}");
}
}
#[test]
fn test_calculate_visible_column_width_simple_emojis() {
// First non-component emojis of each groups in "Full Emoji List, v14.0"
// https://unicode.org/Public/emoji/14.0/emoji-test.txt
let simple_emojis = [
"😀", /* U+1F600 */
"👋", /* U+1F44B */
"🐵", /* U+1F435 */
"🍇", /* U+1F347 */
"🌍", /* U+1F30D */
"🎃", /* U+1F383 */
"👓", /* U+1F453 */
"🏧", /* U+1F3E7 */
"🏁", /* U+1F3C1 */
];
for (i, ch) in simple_emojis.iter().enumerate() {
let width = calculate_visible_column_width(ch);
assert_eq!(2, width, "emoji[{i}]: {ch}");
}
}
#[test]
fn test_calculate_visible_column_width_zwj_sequences() {
let zwj_sequences = [
"😵‍💫", /* U+1F635 U+200D U+1F4AB */
"👁️‍🗨️", /* U+1F441 U+FE0F U+200D U+1F5E8 U+FE0F */
];
for (i, ch) in zwj_sequences.iter().enumerate() {
let width = calculate_visible_column_width(ch);
assert_eq!(2, width, "zwj[{i}]: {ch}");
}
}
}
@@ -4,16 +4,14 @@ status.code = 2
stdin = ""
stdout = """
error: `hello` is disallowed
--> ./file:1:1
|
1 | hello `hello`
| ^^^^^
|
╭▸ ./file:1:1
│
1 │ hello `hello`
╰╴━━━━━
error: `hello` is disallowed
--> ./file:1:8
|
1 | hello `hello`
| ^^^^^
|
╭▸ ./file:1:8
│
1 │ hello `hello`
╰╴ ━━━━━
"""
stderr = ""
@@ -2,11 +2,10 @@ bin.name = "typos"
stdin = ""
stdout = """
error: `hte` should be `the`
--> ./checked/file.txt:1:1
|
1 | hte
| ^^^
|
╭▸ ./checked/file.txt:1:1
│
1 │ hte
╰╴━━━
"""
stderr = ""
status.code = 2
@@ -3,10 +3,9 @@ status.code = 2
stdin = ""
stdout = """
error: `hello` should be `goodbye`
--> ./file.fail:1:1
|
1 | hello
| ^^^^^
|
╭▸ ./file.fail:1:1
│
1 │ hello
╰╴━━━━━
"""
stderr = ""
@@ -3,17 +3,15 @@ args = "--sort"
stdin = ""
stdout = """
error: `hello` should be `goodbye`
--> ./file.ignore:1:1
|
1 | hello `hello`
| ^^^^^
|
╭▸ ./file.ignore:1:1
│
1 │ hello `hello`
╰╴━━━━━
error: `olt` should be `old`
--> ./olt-manager.php:1:5
|
1 | One olt two
| ^^^
|
╭▸ ./olt-manager.php:1:5
│
1 │ One olt two
╰╴ ━━━
"""
stderr = ""
status.code = 2
@@ -3,10 +3,9 @@ status.code = 2
stdin = ""
stdout = """
error: `hello` should be `goodbye`
--> ./file.fail:1:1
|
1 | hello
| ^^^^^
|
╭▸ ./file.fail:1:1
│
1 │ hello
╰╴━━━━━
"""
stderr = ""
@@ -4,10 +4,9 @@ status.code = 2
stdin = ""
stdout = """
error: `Trailling` should be `Trailing`
--> ./file.txt:1:26
|
1 | public function noErrorOnTraillingSemicolonAndWhitespace(Connection $connection)
| ^^^^^^^^^
|
╭▸ ./file.txt:1:26
│
1 │ public function noErrorOnTraillingSemicolonAndWhitespace(Connection $connection)
╰╴ ━━━━━━━━━
"""
stderr = ""
@@ -6,17 +6,15 @@ d.fail
"""
stdout = """
error: `hello` should be `goodbye`
--> b.fail:1:1
|
1 | hello
| ^^^^^
|
╭▸ b.fail:1:1
│
1 │ hello
╰╴━━━━━
error: `hello` should be `goodbye`
--> d.fail:1:1
|
1 | hello
| ^^^^^
|
╭▸ d.fail:1:1
│
1 │ hello
╰╴━━━━━
"""
stderr = ""
status.code = 2
@@ -0,0 +1,4 @@
We should not correct
- goes
- ret
- prev
@@ -0,0 +1,10 @@
bin.name = "typos"
stdin = ""
stdout = """
error: `makro` should be `macro`
╭▸
1 │ ./makro.rs
╰╴ ━━━━━
"""
stderr = ""
status.code = 2
+8 -10
View File
@@ -2,17 +2,15 @@ bin.name = "typos"
stdin = "--sort"
stdout = """
error: `hello` should be `goodbye`
--> ./file.ignore:1:1
|
1 | hello
| ^^^^^
|
╭▸ ./file.ignore:1:1
│
1 │ hello
╰╴━━━━━
error: `hello` should be `goodbye`
--> ./file.ignore:6:1
|
6 | hello
| ^^^^^
|
╭▸ ./file.ignore:6:1
│
6 │ hello
╰╴━━━━━
"""
stderr = ""
status.code = 2
+8 -10
View File
@@ -2,17 +2,15 @@ bin.name = "typos"
stdin = "--sort"
stdout = """
error: `hello` should be `goodbye`
--> ./file.ignore:1:1
|
1 | hello
| ^^^^^
|
╭▸ ./file.ignore:1:1
│
1 │ hello
╰╴━━━━━
error: `hello` should be `goodbye`
--> ./file.ignore:3:1
|
3 | hello
| ^^^^^
|
╭▸ ./file.ignore:3:1
│
3 │ hello
╰╴━━━━━
"""
stderr = ""
status.code = 2
@@ -2,17 +2,15 @@ bin.name = "typos"
stdin = "--sort"
stdout = """
error: `hello` should be `goodbye`
--> ./file.ignore:1:1
|
1 | hello
| ^^^^^
|
╭▸ ./file.ignore:1:1
│
1 │ hello
╰╴━━━━━
error: `hello` should be `goodbye`
--> ./file.ignore:6:1
|
6 | hello
| ^^^^^
|
╭▸ ./file.ignore:6:1
│
6 │ hello
╰╴━━━━━
"""
stderr = ""
status.code = 2
@@ -39,34 +39,29 @@ Grapheme clusters: 1, codepoints: 1, UnicodeWidthStr::width() == 0
'''
stdout = """
error: `Apropriate` should be `Appropriate`
--> -:5:3
|
5 | 한 Apropriate world
| ^^^^^^^^^^
|
╭▸ -:5:3
│
5 │ 한 Apropriate world
╰╴ ━━━━━━━━━━
error: `Apropriate` should be `Appropriate`
--> -:12:3
|
12 | 한 Apropriate world
| ^^^^^^^^^^
|
╭▸ -:12:3
│
12 │ 한 Apropriate world
╰╴ ━━━━━━━━━━
error: `Apropriate` should be `Appropriate`
--> -:19:3
|
19 | 👁️‍🗨️ Apropriate world
| ^^^^^^^^^^
|
╭▸ -:19:7
│
19 │ 👁️🗨️ Apropriate world
╰╴ ━━━━━━━━━━
error: `Apropriate` should be `Appropriate`
--> -:26:3
|
26 | 😵‍💫 Apropriate world
| ^^^^^^^^^^
|
╭▸ -:26:5
│
26 │ 😵💫 Apropriate world
╰╴ ━━━━━━━━━━
error: `Apropriate` should be `Appropriate`
--> -:33:2
|
33 | Apropriate world
| ^^^^^^^^^^
|
╭▸ -:33:2
│
33 │ Apropriate world
╰╴ ━━━━━━━━━━
"""
stderr = ""
@@ -6,10 +6,9 @@ Apropriate world
'''
stdout = """
error: `Apropriate` should be `Appropriate`
--> -:1:1
|
1 | Apropriate world
| ^^^^^^^^^^
|
╭▸ -:1:1
│
1 │ Apropriate world
╰╴━━━━━━━━━━
"""
stderr = ""