immigrant_schema/
diagnostics.rs1#[cfg(feature = "hi-doc")]
2use hi_doc::{Formatting, SnippetBuilder, Text};
3#[cfg(feature = "tree-sitter-highlight")]
4use tree_sitter_highlight::HighlightConfiguration;
5
6use crate::span::SimpleSpan;
7
8#[derive(PartialEq, Eq, Clone, Copy)]
9pub enum Severity {
10 Warning,
11 Error,
12}
13
14#[derive(Clone)]
15pub struct ReportPart {
16 pub msg: String,
17 pub severity: Severity,
18 pub annotations: Vec<Annotation>,
19}
20
21#[derive(Clone)]
22pub struct Annotation {
23 pub span: SimpleSpan,
24 pub msg: String,
25}
26
27#[derive(Default, Clone)]
28pub struct Report {
29 pub parts: Vec<ReportPart>,
30}
31
32#[cfg(feature = "tree-sitter-highlight")]
33fn highlight(b: &mut SnippetBuilder) {
34 let language = tree_sitter_immigrant::LANGUAGE;
35 let mut config = HighlightConfiguration::new(
36 language.into(),
37 "immigrant",
38 tree_sitter_immigrant::HIGHLIGHTS_QUERY,
39 tree_sitter_immigrant::INJECTIONS_QUERY,
40 tree_sitter_immigrant::LOCALS_QUERY,
41 )
42 .expect("highlight configuration is valid");
43 config.configure(&[
44 "punctuation.bracket",
45 "keyword",
46 "property",
47 "type",
48 ]);
49 b.highlight(config, |name, _str| {
50 match name {
51 1 => Formatting::rgb([255, 50, 50]),
52 2 => Formatting::rgb([50, 150, 50]),
53 3 => Formatting::rgb([120, 150, 50]),
54 _ => Formatting::listchar(),
55 }
56 });
57}
58
59impl Report {
60 pub fn new() -> Self {
61 Self::default()
62 }
63 #[cfg(feature = "hi-doc")]
64 pub fn to_hi_doc(self, src: &str) -> Vec<hi_doc::Source> {
65 let mut out = Vec::new();
66
67 let mut snippet_src = src.to_owned();
68 snippet_src.push(' ');
70
71 for part in self.parts {
72 let mut builder = SnippetBuilder::new(&snippet_src);
73 #[cfg(feature = "tree-sitter-highlight")]
74 highlight(&mut builder);
75 for ele in part.annotations {
77 let mut ann = builder.error(Text::fragment(
78 format!("{}: {}", part.msg, ele.msg),
79 Formatting::rgb([127, 127, 255]),
80 ));
81 if ele.span.start == ele.span.end {
82 ann = ann.range(ele.span.start as usize..=ele.span.end as usize);
84 } else {
85 ann = ann.range(ele.span.start as usize..=ele.span.end as usize - 1);
86 }
87 ann.build();
88 }
89 out.push(builder.build())
90 }
91 out
92 }
93 pub fn is_error(&self) -> bool {
94 self.parts.iter().any(|p| p.severity == Severity::Error)
95 }
96
97 pub fn error(&mut self, msg: impl AsRef<str>) -> PartBuilder<'_> {
98 let part = ReportPart {
99 msg: msg.as_ref().to_owned(),
100 severity: Severity::Error,
101 annotations: vec![],
102 };
103 self.parts.push(part);
104 PartBuilder {
105 part: self.parts.last_mut().expect("just inserted"),
106 }
107 }
108}
109
110pub struct PartBuilder<'r> {
111 part: &'r mut ReportPart,
112}
113impl PartBuilder<'_> {
114 pub fn annotate(&mut self, msg: impl AsRef<str>, span: SimpleSpan) -> &mut Self {
115 self.part.annotations.push(Annotation {
116 span,
117 msg: msg.as_ref().to_owned(),
118 });
119 self
120 }
121}
122
123#[test]
124#[cfg(feature = "hi-doc")]
125fn diagnostics() {
126 use crate::process::NamingConvention;
127 use crate::root::SchemaProcessOptions;
128 use crate::uid::RenameMap;
129
130 let mut rn = RenameMap::new();
131
132 let mut report = Report::new();
133
134 let src = r#"
135 scalar idd = "INTEGER";
136 table A {
137 idd;
138 };
139 table A {
140 idd;
141 };
142 "#;
143 crate::parser::parse(
144 src,
145 false,
146 &SchemaProcessOptions {
147 generator_supports_domain: true,
148 naming_convention: NamingConvention::Postgres,
149 },
150 &mut rn,
151 &mut report,
152 )
153 .expect("parsed");
154
155 assert!(report.is_error());
156 let hidoc = report.to_hi_doc(src);
157 for hidoc in hidoc {
158 let ansi = hi_doc::source_to_ansi(&hidoc);
159 println!("{ansi}")
160 }
161}