Skip to content

Commit 2973c1d

Browse files
committed
Initial diagnostic sketch
1 parent 7e7db3b commit 2973c1d

File tree

11 files changed

+384
-0
lines changed

11 files changed

+384
-0
lines changed

.editorconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
insert_final_newline = true
6+
charset = utf-8

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target
2+
Cargo.lock

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[workspace]
2+
members = [
3+
"crates/retort",
4+
]

crates/retort/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "retort"
3+
version = "0.1.0"
4+
edition = "2018"
5+
6+
authors = [
7+
"Brendan Zabarauskas <[email protected]>",
8+
"Christopher Durham <[email protected]>",
9+
"Yehuda Katz <[email protected]>",
10+
"Zibi Braniecki <[email protected]>",
11+
]
12+
13+
[dependencies]
14+
lsp-types = "0.61.0"

crates/retort/examples/rustc.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use {
2+
lsp_types::{Location, Position, Range as LSPRange},
3+
retort::{diagnostic::*, lsp},
4+
std::ops::Range,
5+
};
6+
7+
fn main() {
8+
let source = include_str!("rustc_source.txt");
9+
10+
let position = |i: usize| -> Position {
11+
let nl = (&source[..i]).rfind('\n').map(|i| i + 1).unwrap_or(0);
12+
Position {
13+
line: (&source[..nl]).chars().filter(|&c| c == '\n').count() as u64,
14+
character: (&source[nl..i])
15+
.chars()
16+
.map(|c| c.len_utf16())
17+
.sum::<usize>() as u64,
18+
}
19+
};
20+
21+
let diagnostic = Diagnostic::build()
22+
.primary(
23+
Annotation::build()
24+
.span(50..777)
25+
.message("mismatched types")
26+
.build(),
27+
)
28+
.code("E0308")
29+
.level(Level::Err)
30+
.secondary(
31+
Annotation::build()
32+
.span(55..69)
33+
.message("expected `Option<String>` because of return type")
34+
.build(),
35+
)
36+
.secondary(
37+
Annotation::build()
38+
.span(76..775)
39+
.message("expected enum `std::option::Option`, found ()")
40+
.build(),
41+
)
42+
.build();
43+
44+
let out = lsp::render(
45+
Some(diagnostic),
46+
Some("retort rustc example".to_string()),
47+
|range: Range<usize>| {
48+
Location::new(
49+
"example:rustc_source.txt".parse().unwrap(),
50+
LSPRange::new(position(range.start), position(range.end)),
51+
)
52+
},
53+
);
54+
55+
println!("{:#?}", out);
56+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
2+
3+
4+
5+
6+
7+
8+
9+
10+
11+
12+
13+
14+
15+
16+
17+
18+
19+
20+
21+
22+
23+
24+
25+
26+
27+
28+
29+
30+
31+
32+
33+
34+
35+
36+
37+
38+
39+
40+
41+
42+
43+
44+
45+
46+
47+
48+
49+
50+
51+
) -> Option<String> {
52+
for ann in annotations {
53+
match (ann.range.0, ann.range.1) {
54+
(None, None) => continue,
55+
(Some(start), Some(end)) if start > end_index => continue,
56+
(Some(start), Some(end)) if start >= start_index => {
57+
let label = if let Some(ref label) = ann.label {
58+
format!(" {}", label)
59+
} else {
60+
String::from("")
61+
};
62+
63+
return Some(format!(
64+
"{}{}{}",
65+
" ".repeat(start - start_index),
66+
"^".repeat(end - start),
67+
label
68+
));
69+
}
70+
_ => continue,
71+
}
72+
}
73+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use super::*;
2+
use std::{fmt::Display, mem};
3+
4+
impl<Span: crate::Span> Diagnostic<Span> {
5+
pub fn build() -> DiagnosticBuilder<Span> {
6+
DiagnosticBuilder {
7+
primary: None,
8+
code: None,
9+
secondary: vec![],
10+
level: None,
11+
}
12+
}
13+
}
14+
15+
#[derive(Debug)]
16+
pub struct DiagnosticBuilder<Span> {
17+
primary: Option<Annotation<Span>>,
18+
code: Option<String>,
19+
secondary: Vec<Annotation<Span>>,
20+
level: Option<Level>,
21+
}
22+
23+
impl<Span: crate::Span> DiagnosticBuilder<Span> {
24+
pub fn primary(&mut self, annotation: Annotation<Span>) -> &mut Self {
25+
self.primary = Some(annotation);
26+
self
27+
}
28+
29+
pub fn code(&mut self, code: impl Display) -> &mut Self {
30+
self.code = Some(code.to_string());
31+
self
32+
}
33+
34+
pub fn secondary(&mut self, annotation: Annotation<Span>) -> &mut Self {
35+
self.secondary.push(annotation);
36+
self
37+
}
38+
39+
pub fn level(&mut self, level: Level) -> &mut Self {
40+
self.level = Some(level);
41+
self
42+
}
43+
44+
pub fn build(&mut self) -> Diagnostic<Span> {
45+
let mut swindle = vec![];
46+
mem::swap(&mut swindle, &mut self.secondary);
47+
Diagnostic {
48+
primary: self
49+
.primary
50+
.take()
51+
.expect("incomplete `Diagnostic` without `primary`"),
52+
code: self.code.take(),
53+
secondary: swindle,
54+
level: self.level.take(),
55+
non_exhaustive: (),
56+
}
57+
}
58+
}
59+
60+
impl<Span: crate::Span> Annotation<Span> {
61+
pub fn build() -> AnnotationBuilder<Span> {
62+
AnnotationBuilder {
63+
span: None,
64+
message: String::new(),
65+
}
66+
}
67+
}
68+
69+
#[derive(Debug)]
70+
pub struct AnnotationBuilder<Span> {
71+
span: Option<Span>,
72+
message: String,
73+
}
74+
75+
impl<Span: crate::Span> AnnotationBuilder<Span> {
76+
pub fn span(&mut self, span: impl Into<Span>) -> &mut Self {
77+
self.span = Some(span.into());
78+
self
79+
}
80+
81+
pub fn message(&mut self, message: impl Display) -> &mut Self {
82+
self.message = message.to_string();
83+
self
84+
}
85+
86+
pub fn build(&mut self) -> Annotation<Span> {
87+
let mut swizzle = String::new();
88+
mem::swap(&mut swizzle, &mut self.message);
89+
Annotation {
90+
span: self
91+
.span
92+
.take()
93+
.expect("incomplete `Annotation` without `span`"),
94+
message: swizzle,
95+
non_exhaustive: (),
96+
}
97+
}
98+
}

crates/retort/src/diagnostic/mod.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
mod builders;
2+
pub use builders::*;
3+
4+
// Note on LSP conversion:
5+
// range: primary.span.range
6+
// severity: level
7+
// code: code
8+
// source: (give to the renderer)
9+
// message: primary.message
10+
// related_information: secondary
11+
// location.uri: span.origin
12+
// location.range: span.range
13+
// message: message
14+
15+
#[derive(Debug, Clone)]
16+
pub struct Diagnostic<Span> {
17+
pub primary: Annotation<Span>,
18+
pub code: Option<String>,
19+
pub secondary: Vec<Annotation<Span>>,
20+
pub level: Option<Level>,
21+
non_exhaustive: (),
22+
}
23+
24+
#[derive(Debug, Clone)]
25+
pub struct Annotation<Span> {
26+
pub span: Span,
27+
pub message: String,
28+
non_exhaustive: (),
29+
}
30+
31+
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
32+
pub enum Level {
33+
Err,
34+
Warn,
35+
Info,
36+
Hint,
37+
}

crates/retort/src/lib.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use {
2+
std::{fmt, ops::Range},
3+
};
4+
5+
pub mod diagnostic;
6+
pub mod lsp;
7+
8+
pub trait Span: fmt::Debug + Clone {
9+
type SourceHandle: Clone;
10+
type Index: Copy;
11+
12+
fn start(&self) -> Self::Index;
13+
fn end(&self) -> Self::Index;
14+
fn new(&self, start: Self::Index, end: Self::Index) -> Self;
15+
fn resource(&self) -> Option<&Self::SourceHandle>;
16+
}
17+
18+
impl Span for Range<usize> {
19+
type SourceHandle = &'static str;
20+
type Index = usize;
21+
22+
fn start(&self) -> usize {
23+
self.start
24+
}
25+
26+
fn end(&self) -> usize {
27+
self.end
28+
}
29+
30+
fn new(&self, start: usize, end: usize) -> Self {
31+
start..end
32+
}
33+
34+
fn resource(&self) -> Option<&Self::SourceHandle> {
35+
Some(&"example:")
36+
}
37+
}

crates/retort/src/lsp.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use {
2+
crate::{diagnostic::*, Span as _},
3+
lsp_types::{
4+
Diagnostic as LSPDiagnostic, DiagnosticRelatedInformation, DiagnosticSeverity,
5+
NumberOrString, PublishDiagnosticsParams, Url,
6+
},
7+
std::{borrow::Borrow, collections::HashMap},
8+
};
9+
10+
impl Level {
11+
fn as_lsp(self) -> DiagnosticSeverity {
12+
match self {
13+
Level::Err => DiagnosticSeverity::Error,
14+
Level::Warn => DiagnosticSeverity::Warning,
15+
Level::Info => DiagnosticSeverity::Information,
16+
Level::Hint => DiagnosticSeverity::Hint,
17+
}
18+
}
19+
}
20+
21+
pub fn render<Span: crate::Span>(
22+
diagnostics: impl IntoIterator<Item = Diagnostic<Span>>,
23+
source: Option<String>,
24+
mut span_resolver: impl FnMut(Span) -> lsp_types::Location,
25+
) -> Vec<PublishDiagnosticsParams>
26+
where
27+
Span::SourceHandle: Borrow<str>,
28+
{
29+
let mut out: HashMap<Url, Vec<LSPDiagnostic>> = HashMap::new();
30+
31+
for diagnostic in diagnostics {
32+
let location = span_resolver(diagnostic.primary.span);
33+
out.entry(location.uri).or_default().push(LSPDiagnostic {
34+
range: location.range,
35+
severity: diagnostic.level.map(Level::as_lsp),
36+
code: diagnostic.code.map(NumberOrString::String),
37+
source: source.clone(),
38+
message: diagnostic.primary.message,
39+
related_information: Some(
40+
diagnostic
41+
.secondary
42+
.into_iter()
43+
.map(|diagnostic| DiagnosticRelatedInformation {
44+
location: span_resolver(diagnostic.span),
45+
message: diagnostic.message,
46+
})
47+
.collect(),
48+
),
49+
})
50+
}
51+
52+
out.into_iter()
53+
.map(|(uri, diagnostics)| PublishDiagnosticsParams { uri, diagnostics })
54+
.collect()
55+
}

0 commit comments

Comments
 (0)