Skip to content

Commit

Permalink
feat(linter): add hyperlinks to diagnostic messages (#5318)
Browse files Browse the repository at this point in the history
Adds hyperlinks to diagnostic codes.

Diagnostics without codes will not have links. In practice, this means linter diagnostics have links, while semantic and parser diagnostics do not.

![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/OVwvhVoSj4tgQWTUipoY/11790a3a-7dfa-4c6d-be43-550d8b6370a9.png)

> notice the underline under the error code
  • Loading branch information
DonIsaac committed Aug 29, 2024
1 parent cd63336 commit 9c22ce9
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 15 deletions.
22 changes: 17 additions & 5 deletions crates/oxc_diagnostics/src/graphic_reporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,24 @@ impl GraphicalReportHandler {
opts = opts.word_splitter(word_splitter);
}

let title = if let Some(code) = diagnostic.code() {
format!("{code}: {diagnostic}")
} else {
diagnostic.to_string()
let title = match (self.links, diagnostic.url(), diagnostic.code()) {
(LinkStyle::Link, Some(url), Some(code)) => {
// magic unicode escape sequences to make the terminal print a hyperlink
const CTL: &str = "\u{1b}]8;;";
const END: &str = "\u{1b}]8;;\u{1b}\\";
let code = code.style(severity_style);
let message = diagnostic.to_string();
let title = message.style(severity_style);
format!("{CTL}{url}\u{1b}\\{code}{END}: {title}",)
}
(_, _, Some(code)) => {
let title = format!("{code}: {}", diagnostic);
format!("{}", title.style(severity_style))
}
_ => {
format!("{}", diagnostic.to_string().style(severity_style))
}
};
let title = format!("{}", title.style(severity_style));
let title = textwrap::fill(&title, opts);
writeln!(f, "{}", title)?;

Expand Down
11 changes: 11 additions & 0 deletions crates/oxc_diagnostics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub struct OxcDiagnosticInner {
pub help: Option<Cow<'static, str>>,
pub severity: Severity,
pub code: OxcCode,
pub url: Option<Cow<'static, str>>,
}

impl fmt::Display for OxcDiagnostic {
Expand Down Expand Up @@ -101,6 +102,9 @@ impl Diagnostic for OxcDiagnostic {
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.code.is_some().then(|| Box::new(&self.code) as Box<dyn Display>)
}
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.url.as_ref().map(Box::new).map(|c| c as Box<dyn Display>)
}
}

impl OxcDiagnostic {
Expand All @@ -112,6 +116,7 @@ impl OxcDiagnostic {
help: None,
severity: Severity::Error,
code: OxcCode::default(),
url: None,
}),
}
}
Expand All @@ -124,6 +129,7 @@ impl OxcDiagnostic {
help: None,
severity: Severity::Warning,
code: OxcCode::default(),
url: None,
}),
}
}
Expand Down Expand Up @@ -205,6 +211,11 @@ impl OxcDiagnostic {
self
}

pub fn with_url<S: Into<Cow<'static, str>>>(mut self, url: S) -> Self {
self.inner.url = Some(url.into());
self
}

pub fn with_source_code<T: SourceCode + Send + Sync + 'static>(self, code: T) -> Error {
Error::from(self).with_source_code(code)
}
Expand Down
30 changes: 21 additions & 9 deletions crates/oxc_linter/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub struct LintContext<'a> {
eslint_config: Arc<OxlintConfig>,

// states
current_plugin_name: &'static str,
current_plugin_prefix: &'static str,
current_rule_name: &'static str,
#[cfg(debug_assertions)]
Expand All @@ -60,6 +61,7 @@ pub struct LintContext<'a> {
}

impl<'a> LintContext<'a> {
const WEBSITE_BASE_URL: &'static str = "https://oxc.rs/docs/guide/usage/linter/rules";
/// # Panics
/// If `semantic.cfg()` is `None`.
pub fn new(file_path: Box<Path>, semantic: Rc<Semantic<'a>>) -> Self {
Expand All @@ -81,6 +83,7 @@ impl<'a> LintContext<'a> {
fix: FixKind::None,
file_path: file_path.into(),
eslint_config: Arc::new(OxlintConfig::default()),
current_plugin_name: "eslint",
current_plugin_prefix: "eslint",
current_rule_name: "",
#[cfg(debug_assertions)]
Expand All @@ -102,6 +105,7 @@ impl<'a> LintContext<'a> {
}

pub fn with_plugin_name(mut self, plugin: &'static str) -> Self {
self.current_plugin_name = plugin;
self.current_plugin_prefix = plugin_name_to_prefix(plugin);
self
}
Expand Down Expand Up @@ -209,16 +213,24 @@ impl<'a> LintContext<'a> {
self.diagnostics.borrow().iter().cloned().collect::<Vec<_>>()
}

fn add_diagnostic(&self, message: Message<'a>) {
if !self.disable_directives.contains(self.current_rule_name, message.span()) {
let mut message = message;
message.error =
message.error.with_error_code(self.current_plugin_prefix, self.current_rule_name);
if message.error.severity != self.severity {
message.error = message.error.with_severity(self.severity);
}
self.diagnostics.borrow_mut().push(message);
fn add_diagnostic(&self, mut message: Message<'a>) {
if self.disable_directives.contains(self.current_rule_name, message.span()) {
return;
}
message.error = message
.error
.with_error_code(self.current_plugin_prefix, self.current_rule_name)
.with_url(format!(
"{}/{}/{}.html",
Self::WEBSITE_BASE_URL,
self.current_plugin_name,
self.current_rule_name
));
if message.error.severity != self.severity {
message.error = message.error.with_severity(self.severity);
}

self.diagnostics.borrow_mut().push(message);
}

/// Report a lint rule violation.
Expand Down
4 changes: 3 additions & 1 deletion crates/oxc_linter/src/tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,9 @@ impl Tester {
}
.to_string_lossy();

let handler = GraphicalReportHandler::new().with_theme(GraphicalTheme::unicode_nocolor());
let handler = GraphicalReportHandler::new()
.with_links(false)
.with_theme(GraphicalTheme::unicode_nocolor());
for diagnostic in result {
let diagnostic = diagnostic.error.with_source_code(NamedSource::new(
diagnostic_path.clone(),
Expand Down

0 comments on commit 9c22ce9

Please sign in to comment.