blob: 49abd7dad68abae5339be7b3499f3b7fbfa29d37 [file] [edit]
use crate::Span;
use annotate_snippets::renderer::{DEFAULT_TERM_WIDTH, Renderer};
use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Origin, Snippet};
use core::panic::Location;
use std::borrow::Cow;
use std::io::Write as _;
use std::process;
pub struct DiagCx {
out: anstream::Stderr,
renderer: Renderer,
has_err: bool,
}
impl Default for DiagCx {
fn default() -> Self {
let width = termize::dimensions().map_or(DEFAULT_TERM_WIDTH, |(w, _)| w);
Self {
out: anstream::stderr(),
renderer: Renderer::styled().term_width(width),
has_err: false,
}
}
}
impl Drop for DiagCx {
fn drop(&mut self) {
if self.has_err {
self.emit_err(&[
Group::with_title(
Level::ERROR
.with_name("internal error")
.primary_title("errors were found, but it was assumed none occurred"),
),
Group::with_title(Level::NOTE.secondary_title("any produced results may be incorrect")),
]);
process::exit(1);
}
}
}
impl DiagCx {
pub fn exit_on_err(&self) {
if self.has_err {
process::exit(1);
}
}
#[track_caller]
pub fn exit_assume_err(&mut self) -> ! {
if !self.has_err {
self.emit_err(&[
Group::with_title(
Level::ERROR
.with_name("internal error")
.primary_title("errors were expected, but failed to occur"),
),
mk_loc_group(),
]);
}
process::exit(1);
}
}
fn sp_to_snip(kind: AnnotationKind, sp: Span<'_>) -> Snippet<'_, Annotation<'_>> {
let line_starts = sp.file.line_starts();
let first_line = match line_starts.binary_search(&sp.range.start) {
Ok(x) => x,
// Note: `Err(0)` isn't possible since `0` is always the first start.
Err(x) => x - 1,
};
let start = line_starts[first_line] as usize;
let last_line = match line_starts.binary_search(&sp.range.end) {
Ok(x) => x,
Err(x) => x - 1,
};
let end = line_starts
.get(last_line + 1)
.map_or(sp.file.contents.len(), |&x| x as usize);
Snippet::source(&sp.file.contents[start..end])
.line_start(first_line + 1)
.path(sp.file.path.get())
.annotation(kind.span((sp.range.start as usize - start..sp.range.end as usize - start).into()))
}
fn mk_spanned_primary<'a>(level: Level<'a>, sp: Span<'a>, msg: impl Into<Cow<'a, str>>) -> Group<'a> {
level
.primary_title(msg.into())
.element(sp_to_snip(AnnotationKind::Primary, sp))
}
fn mk_spanned_secondary<'a>(level: Level<'a>, sp: Span<'a>, msg: impl Into<Cow<'a, str>>) -> Group<'a> {
level
.secondary_title(msg.into())
.element(sp_to_snip(AnnotationKind::Context, sp))
}
#[track_caller]
fn mk_loc_group() -> Group<'static> {
let loc = Location::caller();
Level::INFO.secondary_title("error created here").element(
Origin::path(loc.file())
.line(loc.line() as usize)
.char_column(loc.column() as usize),
)
}
impl DiagCx {
fn emit_err(&mut self, groups: &[Group<'_>]) {
let mut s = self.renderer.render(groups);
s.push('\n');
self.out.write_all(s.as_bytes()).unwrap();
self.has_err = true;
}
#[track_caller]
pub fn emit_spanned_err<'a>(&mut self, sp: Span<'a>, msg: impl Into<Cow<'a, str>>) {
self.emit_err(&[mk_spanned_primary(Level::ERROR, sp, msg.into()), mk_loc_group()]);
}
#[track_caller]
pub fn emit_spanless_err<'a>(&mut self, msg: impl Into<Cow<'a, str>>) {
self.emit_err(&[
Group::with_title(Level::ERROR.primary_title(msg.into())),
mk_loc_group(),
]);
}
#[track_caller]
pub fn emit_already_deprecated(&mut self, name: &str) {
self.emit_spanless_err(format!("lint `{name}` is already deprecated"));
}
#[track_caller]
pub fn emit_duplicate_lint(&mut self, sp: Span<'_>, first_sp: Span<'_>) {
self.emit_err(&[
mk_spanned_primary(Level::ERROR, sp, "duplicate lint name declared"),
mk_spanned_secondary(Level::NOTE, first_sp, "previous declaration here"),
mk_loc_group(),
]);
}
#[track_caller]
pub fn emit_not_clippy_lint_name(&mut self, sp: Span<'_>) {
self.emit_err(&[
mk_spanned_primary(Level::ERROR, sp, "not a clippy lint name"),
Group::with_title(Level::HELP.secondary_title("add the `clippy::` tool prefix")),
mk_loc_group(),
]);
}
#[track_caller]
pub fn emit_unknown_lint(&mut self, name: &str) {
self.emit_spanless_err(format!("unknown lint `{name}`"));
}
}