aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src/diagnostic.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-21 15:10:38 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-21 15:12:52 +0800
commit9976b31a56bcc3ed37ac878b351bd8d097dc67cc (patch)
treee7b092aee5e82f5d4c1f50332a17f1d2176ae51b /mingling_cli/src/diagnostic.rs
parentee01756279ef17487d19659adedc0639547bbd25 (diff)
feat(mling): add linter framework with async support and registry
generation
Diffstat (limited to 'mingling_cli/src/diagnostic.rs')
-rw-r--r--mingling_cli/src/diagnostic.rs99
1 files changed, 99 insertions, 0 deletions
diff --git a/mingling_cli/src/diagnostic.rs b/mingling_cli/src/diagnostic.rs
new file mode 100644
index 0000000..527e879
--- /dev/null
+++ b/mingling_cli/src/diagnostic.rs
@@ -0,0 +1,99 @@
+use annotate_snippets::level::{ERROR, HELP, NOTE, WARNING};
+use annotate_snippets::{AnnotationKind, Group, Renderer, Snippet};
+use cargo_metadata::diagnostic::Diagnostic;
+use mingling::macros::{buffer, group, r_println, renderer};
+
+group!(Diagnostic);
+
+#[renderer(buffer)]
+pub fn render_diagnostic(diagnostic: Diagnostic) {
+ let report = diagnostic_to_report(&diagnostic);
+ let renderer = Renderer::styled();
+ let rendered = renderer.render(&report);
+ r_println!("{rendered}");
+}
+
+fn cargo_level_to_annotate(
+ level: cargo_metadata::diagnostic::DiagnosticLevel,
+) -> &'static annotate_snippets::Level<'static> {
+ use cargo_metadata::diagnostic::DiagnosticLevel;
+ match level {
+ DiagnosticLevel::Ice | DiagnosticLevel::Error => &ERROR,
+ DiagnosticLevel::Warning => &WARNING,
+ DiagnosticLevel::Note | DiagnosticLevel::FailureNote => &NOTE,
+ DiagnosticLevel::Help => &HELP,
+ _ => &ERROR,
+ }
+}
+
+/// 把 1-based char offset 转成 0-based byte offset
+fn char_offset_to_byte_offset(s: &str, char_offset: usize) -> usize {
+ s.char_indices()
+ .nth(char_offset.saturating_sub(1))
+ .map(|(i, _)| i)
+ .unwrap_or(s.len())
+}
+
+fn diagnostic_to_report<'a>(d: &'a Diagnostic) -> Vec<Group<'a>> {
+ let level = cargo_level_to_annotate(d.level);
+ let mut title = level.clone().primary_title(d.message.as_str());
+ if let Some(ref code) = d.code {
+ title = title.id(code.code.as_str());
+ }
+
+ let mut group = Group::with_title(title);
+
+ for span in &d.spans {
+ if !span.is_primary {
+ continue;
+ }
+
+ let source: String = span
+ .text
+ .iter()
+ .map(|l| l.text.as_str())
+ .collect::<Vec<_>>()
+ .join("\n");
+
+ let byte_range = if span.text.len() == 1 {
+ let line = &span.text[0];
+ let start = char_offset_to_byte_offset(&line.text, line.highlight_start);
+ let end = char_offset_to_byte_offset(&line.text, line.highlight_end);
+ start..end
+ } else {
+ let first = &span.text[0];
+ let last = span.text.last().unwrap();
+ let start = char_offset_to_byte_offset(&first.text, first.highlight_start);
+ let prefix_len: usize = span.text[..span.text.len() - 1]
+ .iter()
+ .map(|l| l.text.len() + 1)
+ .sum();
+ let end = prefix_len + char_offset_to_byte_offset(&last.text, last.highlight_end);
+ start..end
+ };
+
+ let mut snippet = Snippet::source(source)
+ .line_start(span.line_start)
+ .path(span.file_name.as_str());
+
+ if let Some(ref label) = span.label {
+ let annotation = AnnotationKind::Primary
+ .span(byte_range)
+ .label(label.as_str());
+ snippet = snippet.annotation(annotation);
+ } else {
+ let annotation = AnnotationKind::Primary.span(byte_range);
+ snippet = snippet.annotation(annotation);
+ }
+
+ group = group.element(snippet);
+ }
+
+ for child in &d.children {
+ let msg_level = cargo_level_to_annotate(child.level);
+ let msg = msg_level.clone().message(child.message.as_str());
+ group = group.element(msg);
+ }
+
+ vec![group]
+}