mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(linter): add hyperlinks to diagnostic messages (#5318)
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.  > notice the underline under the error code
This commit is contained in:
parent
cd63336c7e
commit
9c22ce9c99
4 changed files with 52 additions and 15 deletions
|
|
@ -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)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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 {
|
||||
|
|
@ -112,6 +116,7 @@ impl OxcDiagnostic {
|
|||
help: None,
|
||||
severity: Severity::Error,
|
||||
code: OxcCode::default(),
|
||||
url: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
@ -124,6 +129,7 @@ impl OxcDiagnostic {
|
|||
help: None,
|
||||
severity: Severity::Warning,
|
||||
code: OxcCode::default(),
|
||||
url: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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)]
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue