Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat(rome_diagnostics): add a new version of diagnostics (#3222)
Browse files Browse the repository at this point in the history
* feat(rome_diagnostics): add a new version of diagnostics aligned with the features of Rome JS

* Apply suggestions from code review

Co-authored-by: Micha Reiser <[email protected]>
Co-authored-by: Emanuele Stoppa <[email protected]>

* address PR review

* update snapshots

* move "Fatal" from a diagnostic tag to a severity level

Co-authored-by: Micha Reiser <[email protected]>
Co-authored-by: Emanuele Stoppa <[email protected]>
  • Loading branch information
3 people committed Sep 22, 2022
1 parent 38a7286 commit 980184f
Show file tree
Hide file tree
Showing 112 changed files with 5,315 additions and 238 deletions.
25 changes: 24 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions crates/rome_console/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub use crate::write::{Termcolor, Write, HTML};
use crate::{markup, Markup, MarkupElement};

/// A stack-allocated linked-list of [MarkupElement] slices
#[derive(Clone, Copy)]
pub enum MarkupElements<'a> {
Root,
Node(&'a Self, &'a [MarkupElement<'a>]),
Expand Down Expand Up @@ -59,6 +60,16 @@ impl<'fmt> Formatter<'fmt> {
}
}

pub fn wrap_writer<'b: 'c, 'c>(
&'b mut self,
wrap: impl FnOnce(&'b mut dyn Write) -> &'c mut dyn Write,
) -> Formatter<'c> {
Formatter {
state: self.state,
writer: wrap(self.writer),
}
}

/// Return a new instance of the [Formatter] with `elements` appended to its element stack
fn with_elements<'b>(&'b mut self, elements: &'b [MarkupElement]) -> Formatter<'b> {
Formatter {
Expand Down
6 changes: 4 additions & 2 deletions crates/rome_console/src/markup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub enum MarkupElement<'fmt> {
Success,
Warn,
Info,
Inverse,
Hyperlink { href: Cow<'fmt, str> },
}

Expand Down Expand Up @@ -66,7 +67,7 @@ impl<'fmt> MarkupElement<'fmt> {
color.set_fg(Some(BLUE));
}

MarkupElement::Hyperlink { .. } => {}
MarkupElement::Inverse | MarkupElement::Hyperlink { .. } => {}
}
}

Expand All @@ -80,6 +81,7 @@ impl<'fmt> MarkupElement<'fmt> {
MarkupElement::Success => MarkupElement::Success,
MarkupElement::Warn => MarkupElement::Warn,
MarkupElement::Info => MarkupElement::Info,
MarkupElement::Inverse => MarkupElement::Inverse,
MarkupElement::Hyperlink { href } => MarkupElement::Hyperlink {
href: Cow::Owned(match href {
Cow::Borrowed(href) => href.to_string(),
Expand Down Expand Up @@ -222,7 +224,7 @@ impl Display for MarkupBuf {
impl Debug for MarkupBuf {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
for node in &self.0 {
write!(fmt, "{node:?}")?;
Debug::fmt(node, fmt)?;
}
Ok(())
}
Expand Down
10 changes: 7 additions & 3 deletions crates/rome_console/src/write/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,17 @@ fn push_styles<W: io::Write>(fmt: &mut W, elements: &MarkupElements) -> io::Resu
elements.for_each(&mut |styles| {
for style in styles {
match style {
MarkupElement::Emphasis => write!(fmt, "<em>")?,
MarkupElement::Emphasis => write!(fmt, "<strong>")?,
MarkupElement::Dim => write!(fmt, "<span style=\"opacity: 0.8;\">")?,
MarkupElement::Italic => write!(fmt, "<i>")?,
MarkupElement::Underline => write!(fmt, "<u>")?,
MarkupElement::Error => write!(fmt, "<span style=\"color: Tomato;\">")?,
MarkupElement::Success => write!(fmt, "<span style=\"color: MediumSeaGreen;\">")?,
MarkupElement::Warn => write!(fmt, "<span style=\"color: Orange;\">")?,
MarkupElement::Info => write!(fmt, "<span style=\"color: rgb(38, 148, 255);\">")?,
MarkupElement::Inverse => {
write!(fmt, "<span style=\"color: #000; background-color: #ddd;\">")?
}
MarkupElement::Hyperlink { href } => write!(fmt, "<a href=\"{href}\">")?,
}
}
Expand All @@ -52,14 +55,15 @@ fn pop_styles<W: io::Write>(fmt: &mut W, elements: &MarkupElements) -> io::Resul
elements.for_each_rev(&mut |styles| {
for style in styles.iter().rev() {
match style {
MarkupElement::Emphasis => write!(fmt, "</em>")?,
MarkupElement::Emphasis => write!(fmt, "</strong>")?,
MarkupElement::Italic => write!(fmt, "</i>")?,
MarkupElement::Underline => write!(fmt, "</u>")?,
MarkupElement::Dim
| MarkupElement::Error
| MarkupElement::Success
| MarkupElement::Warn
| MarkupElement::Info => write!(fmt, "</span>")?,
| MarkupElement::Info
| MarkupElement::Inverse => write!(fmt, "</span>")?,
MarkupElement::Hyperlink { .. } => write!(fmt, "</a>")?,
}
}
Expand Down
50 changes: 42 additions & 8 deletions crates/rome_console/src/write/termcolor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{
io,
};

use termcolor::{ColorSpec, WriteColor};
use termcolor::{Color, ColorSpec, WriteColor};
use unicode_width::UnicodeWidthChar;

use crate::{fmt::MarkupElements, MarkupElement};
Expand Down Expand Up @@ -75,19 +75,33 @@ where
{
let mut color = ColorSpec::new();
let mut link = None;
let mut inverse = false;

state.for_each(&mut |elements| {
for element in elements {
if let MarkupElement::Hyperlink { href } = element {
link = Some(href);
} else {
element.update_color(&mut color);
match element {
MarkupElement::Inverse => {
inverse = !inverse;
}
MarkupElement::Hyperlink { href } => {
link = Some(href);
}
_ => {
element.update_color(&mut color);
}
}
}

Ok(())
})?;

if inverse {
let fg = color.fg().map_or(Color::White, |c| *c);
let bg = color.bg().map_or(Color::Black, |c| *c);
color.set_bg(Some(fg));
color.set_fg(Some(bg));
}

if let Err(err) = writer.set_color(&color) {
writer.reset()?;
return Err(err);
Expand All @@ -98,7 +112,7 @@ where
// `is_synchronous` is used to check if the underlying writer
// is using the Windows Console API, that does not support ANSI
// escape codes. Generally this would only be true when running
// in the legacy `cmd.exe` terminal emulator, since int modern
// in the legacy `cmd.exe` terminal emulator, since in modern
// clients like the Windows Terminal ANSI is used instead
if writer.supports_color() && !writer.is_synchronous() {
write!(writer, "\x1b]8;;{href}\x1b\\")?;
Expand All @@ -124,7 +138,10 @@ struct SanitizeAdapter<W> {
error: io::Result<()>,
}

impl<W: io::Write> fmt::Write for SanitizeAdapter<W> {
impl<W> fmt::Write for SanitizeAdapter<W>
where
W: WriteColor,
{
fn write_str(&mut self, content: &str) -> fmt::Result {
let mut buffer = [0; 4];

Expand All @@ -134,6 +151,10 @@ impl<W: io::Write> fmt::Write for SanitizeAdapter<W> {
let is_zero_width = UnicodeWidthChar::width(item).map_or(true, |width| width == 0);
let item = if !is_whitespace && is_zero_width {
char::REPLACEMENT_CHARACTER
} else if cfg!(windows) || !self.writer.supports_color() {
// Unicode is currently poorly supported on most Windows
// terminal clients, so we always strip emojis in Windows
unicode_to_ascii(item)
} else {
item
};
Expand All @@ -149,6 +170,18 @@ impl<W: io::Write> fmt::Write for SanitizeAdapter<W> {
}
}

/// Replace emoji characters with similar but more widely supported ASCII
/// characters
fn unicode_to_ascii(c: char) -> char {
match c {
'\u{2714}' => '\u{221a}',
'\u{2139}' => 'i',
'\u{26a0}' => '!',
'\u{2716}' => '\u{00d7}',
_ => c,
}
}

#[cfg(test)]
mod tests {
use std::{fmt::Write, str::from_utf8};
Expand All @@ -173,8 +206,9 @@ mod tests {
let mut buffer = Vec::new();

{
let writer = termcolor::Ansi::new(&mut buffer);
let mut adapter = SanitizeAdapter {
writer: &mut buffer,
writer,
error: Ok(()),
};

Expand Down
34 changes: 29 additions & 5 deletions crates/rome_diagnostics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,39 @@ edition = "2021"
license = "MIT"
description = "Pretty error reporting library based on codespan-reporting built for the RSLint project"

[[example]]
name = "cli"
test = true

[[example]]
name = "fs"
test = true

[[example]]
name = "lint"
test = true

[[example]]
name = "serde"
test = true

[dependencies]
rome_rowan = { path = "../rome_rowan" }
rome_console = { path = "../rome_console" }
rome_console = { path = "../rome_console", features = ["serde_markup"] }
rome_diagnostics_macros = { path = "../rome_diagnostics_macros" }
rome_diagnostics_categories = { path = "../rome_diagnostics_categories" }
rome_text_edit = { path = "../rome_text_edit", features = ["serde"] }
rome_text_size = { path = "../rome_text_size" }
unicode-width = "0.1.9"
serde = { version = "1.0.133", optional = true, features = ["derive"] }
rome_text_edit = { path = "../rome_text_edit" }
serde = { version = "1.0.133", features = ["derive"] }
termcolor = "1.1.2"
colored = "2.0.0"
schemars = { version = "0.8.10", optional = true }
bitflags = "1.3.2"
backtrace = "0.3.66"

[features]
serde = ["dep:serde", "schemars", "rome_text_edit/serde", "rome_console/serde_markup"]
schema = ["schemars"]

[dev-dependencies]
trybuild = "1.0"
serde_json = "1.0.74"
8 changes: 8 additions & 0 deletions crates/rome_diagnostics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# `rome_diagnostics`

This crate contains the types and utility functions used to implement errors
and diagnostics in the Rome codebase

## Acknowledgement

This crate was initially forked from [rslint_errors](https://github.com/rslint/rslint/tree/master/crates/rslint_errors). The design of the new `Diagnostic` trait, `Error` struct, `Context` trait, and the `Diagnostic` derive macro in `rome_diagnostics_macros` are inspired by various fantastic crates in the Rust error handling space: [miette](https://github.com/zkat/miette), [anyhow](https://github.com/dtolnay/anyhow) and [thiserror](https://github.com/dtolnay/thiserror)
63 changes: 63 additions & 0 deletions crates/rome_diagnostics/examples/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::io;

use rome_console::{markup, ConsoleExt, EnvConsole};
use rome_diagnostics::v2::{Advices, Diagnostic, LogCategory, PrintDiagnostic, Resource, Visit};
use rome_rowan::{TextRange, TextSize};

#[derive(Debug, Diagnostic)]
#[diagnostic(
category = "flags/invalid",
message(
description = "Unknown command {command_name}",
message("Unknown command "<Emphasis>{self.command_name}</Emphasis>)
),
tags(FIXABLE),
)]
struct CliDiagnostic {
command_name: String,
#[location(resource)]
path: Resource<&'static str>,
#[location(span)]
span: TextRange,
#[location(source_code)]
source_code: String,
#[advice]
advices: CliAdvices,
}

#[derive(Debug)]
struct CliAdvices {
suggested_name: String,
suggested_command: String,
}

impl Advices for CliAdvices {
fn record(&self, visitor: &mut dyn Visit) -> io::Result<()> {
visitor.record_log(
LogCategory::Info,
&markup! {
"Did you mean "<Emphasis>{self.suggested_name}</Emphasis>" instead?"
},
)?;

visitor.record_command(&self.suggested_command)?;

visitor.record_log(LogCategory::Info, &"To see all available commands run")?;
visitor.record_command("rome --help")
}
}

pub fn main() {
let diag = CliDiagnostic {
command_name: String::from("formqt"),
path: Resource::Argv,
span: TextRange::new(TextSize::from(5), TextSize::from(11)),
source_code: String::from("rome formqt file.js"),
advices: CliAdvices {
suggested_name: String::from("format"),
suggested_command: String::from("rome format file.js"),
},
};

EnvConsole::default().error(markup!({ PrintDiagnostic(&diag) }));
}
Loading

0 comments on commit 980184f

Please sign in to comment.