Ruby: diagnostics: add support for markdown messages

This commit is contained in:
Arthur Baars
2023-02-16 13:13:40 +01:00
parent 006ee5aad9
commit 51f34eb3e9
3 changed files with 120 additions and 58 deletions

View File

@@ -88,7 +88,7 @@ pub struct LogWriter {
} }
impl LogWriter { impl LogWriter {
pub fn message(&self, id: &str, name: &str) -> DiagnosticMessage { pub fn new_entry(&self, id: &str, name: &str) -> DiagnosticMessage {
DiagnosticMessage { DiagnosticMessage {
timestamp: chrono::Utc::now(), timestamp: chrono::Utc::now(),
source: Source { source: Source {
@@ -199,6 +199,17 @@ impl DiagnosticLoggers {
} }
} }
fn longest_backtick_sequence_length(text: &str) -> usize {
let mut count = 0;
for c in text.chars() {
if c == '`' {
count += 1;
} else {
count = 0;
}
}
count
}
impl DiagnosticMessage { impl DiagnosticMessage {
pub fn full_error_message(&self) -> String { pub fn full_error_message(&self) -> String {
match &self.location { match &self.location {
@@ -216,12 +227,38 @@ impl DiagnosticMessage {
} }
} }
pub fn text(&mut self, text: &str) -> &mut Self { fn text(&mut self, text: &str) -> &mut Self {
self.plaintext_message = text.to_owned(); self.plaintext_message = text.to_owned();
self self
} }
#[allow(unused)] pub fn message(&mut self, text: &str, args: &[&str]) -> &mut Self {
let parts = text.split("{}");
let args = args.iter().chain(std::iter::repeat(&""));
let mut plain = String::with_capacity(2 * text.len());
let mut markdown = String::with_capacity(2 * text.len());
for (p, a) in parts.zip(args) {
plain.push_str(p);
plain.push_str(a);
markdown.push_str(p);
if a.len() > 0 {
let count = longest_backtick_sequence_length(a) + 1;
markdown.push_str(&"`".repeat(count));
if count > 1 {
markdown.push_str(" ");
}
markdown.push_str(a);
if count > 1 {
markdown.push_str(" ");
}
markdown.push_str(&"`".repeat(count));
}
}
self.text(&plain);
self.markdown(&markdown);
self
}
pub fn markdown(&mut self, text: &str) -> &mut Self { pub fn markdown(&mut self, text: &str) -> &mut Self {
self.markdown_message = text.to_owned(); self.markdown_message = text.to_owned();
self self
@@ -276,3 +313,20 @@ impl DiagnosticMessage {
self self
} }
} }
#[test]
fn test_message() {
let mut m = DiagnosticLoggers::new("foo")
.logger()
.new_entry("id", "name");
m.message("hello: {}", &["hello"]);
assert_eq!("hello: hello", m.plaintext_message);
assert_eq!("hello: `hello`", m.markdown_message);
let mut m = DiagnosticLoggers::new("foo")
.logger()
.new_entry("id", "name");
m.message("hello with backticks: {}", &["`hello`"]);
assert_eq!("hello with backticks: `hello`", m.plaintext_message);
assert_eq!("hello with backticks: `` `hello` ``", m.markdown_message);
}

View File

@@ -276,7 +276,8 @@ impl<'a> Visitor<'a> {
fn record_parse_error_for_node( fn record_parse_error_for_node(
&mut self, &mut self,
error_message: String, message: &str,
args: &[&str],
node: Node, node: Node,
status_page: bool, status_page: bool,
) { ) {
@@ -291,10 +292,11 @@ impl<'a> Visitor<'a> {
); );
let mut mesg = self let mut mesg = self
.diagnostics_writer .diagnostics_writer
.message("parse-error", "Parse error"); .new_entry("parse-error", "Parse error");
&mesg.severity(diagnostics::Severity::Error) &mesg
.severity(diagnostics::Severity::Error)
.location(self.path, start_line, start_column, end_line, end_column) .location(self.path, start_line, start_column, end_line, end_column)
.text(&error_message); .message(message, args);
if status_page { if status_page {
&mesg.status_page(); &mesg.status_page();
} }
@@ -302,15 +304,19 @@ impl<'a> Visitor<'a> {
} }
fn enter_node(&mut self, node: Node) -> bool { fn enter_node(&mut self, node: Node) -> bool {
if node.is_error() || node.is_missing() { if node.is_missing() {
let error_message = if node.is_missing() { self.record_parse_error_for_node(
format!("parse error: expecting '{}'", node.kind()) "parse error: expecting {}",
} else { &[node.kind()],
"parse error".to_string() node,
}; true,
self.record_parse_error_for_node(error_message, node, true); );
return false; return false;
} }
if node.is_error() {
self.record_parse_error_for_node("parse error", &[], node, true);
return false;
};
let id = self.trap_writer.fresh_id(); let id = self.trap_writer.fresh_id();
@@ -390,14 +396,13 @@ impl<'a> Visitor<'a> {
} }
} }
_ => { _ => {
let error_message = format!("unknown table type: '{}'", node.kind());
self.record_parse_error( self.record_parse_error(
loc, loc,
self.diagnostics_writer self.diagnostics_writer
.message("parse-error", "Parse error") .new_entry("parse-error", "Parse error")
.severity(diagnostics::Severity::Error) .severity(diagnostics::Severity::Error)
.location(self.path, start_line, start_column, end_line, end_column) .location(self.path, start_line, start_column, end_line, end_column)
.text(&error_message), .message("unknown table type: {}", &[node.kind()]),
); );
valid = false; valid = false;
@@ -445,23 +450,29 @@ impl<'a> Visitor<'a> {
values.push(trap::Arg::Label(child_node.label)); values.push(trap::Arg::Label(child_node.label));
} }
} else if field.name.is_some() { } else if field.name.is_some() {
let error_message = format!( self.record_parse_error_for_node(
"type mismatch for field {}::{} with type {:?} != {:?}", "type mismatch for field {}::{} with type {} != {}",
node.kind(), &[
child_node.field_name.unwrap_or("child"), node.kind(),
child_node.type_name, child_node.field_name.unwrap_or("child"),
field.type_info &format!("{:?}", child_node.type_name),
&format!("{:?}", field.type_info),
],
*node,
false,
); );
self.record_parse_error_for_node(error_message, *node,false);
} }
} else if child_node.field_name.is_some() || child_node.type_name.named { } else if child_node.field_name.is_some() || child_node.type_name.named {
let error_message = format!( self.record_parse_error_for_node(
"value for unknown field: {}::{} and type {:?}", "value for unknown field: {}::{} and type {}",
node.kind(), &[
&child_node.field_name.unwrap_or("child"), node.kind(),
&child_node.type_name &child_node.field_name.unwrap_or("child"),
&format!("{:?}", child_node.type_name),
],
*node,
false,
); );
self.record_parse_error_for_node(error_message, *node, false);
} }
} }
let mut args = Vec::new(); let mut args = Vec::new();
@@ -484,7 +495,7 @@ impl<'a> Visitor<'a> {
node.kind(), node.kind(),
column_name column_name
); );
self.record_parse_error_for_node(error_message, *node, false); self.record_parse_error_for_node(&error_message, &[], *node, false);
} }
} }
Storage::Table { Storage::Table {
@@ -494,13 +505,12 @@ impl<'a> Visitor<'a> {
} => { } => {
for (index, child_value) in child_values.iter().enumerate() { for (index, child_value) in child_values.iter().enumerate() {
if !*has_index && index > 0 { if !*has_index && index > 0 {
let error_message = format!( self.record_parse_error_for_node(
"too many values for field: {}::{}", "too many values for field: {}::{}",
node.kind(), &[node.kind(), table_name],
table_name, *node,
false,
); );
self.record_parse_error_for_node(error_message, *node, false);
break; break;
} }
let mut args = vec![trap::Arg::Label(parent_id)]; let mut args = vec![trap::Arg::Label(parent_id)];
@@ -597,8 +607,8 @@ fn location_for(visitor: &mut Visitor, n: Node) -> (usize, usize, usize, usize)
visitor.diagnostics_writer.write( visitor.diagnostics_writer.write(
visitor visitor
.diagnostics_writer .diagnostics_writer
.message("internal-error", "Internal error") .new_entry("internal-error", "Internal error")
.text("expecting a line break symbol, but none found while correcting end column value") .message("expecting a line break symbol, but none found while correcting end column value", &[])
.severity(diagnostics::Severity::Error), .severity(diagnostics::Severity::Error),
); );
} }
@@ -612,12 +622,11 @@ fn location_for(visitor: &mut Visitor, n: Node) -> (usize, usize, usize, usize)
visitor.diagnostics_writer.write( visitor.diagnostics_writer.write(
visitor visitor
.diagnostics_writer .diagnostics_writer
.message("internal-error", "Internal error") .new_entry("internal-error", "Internal error")
.text(&format!( .message(
"cannot correct end column value: end_byte index {} is not in range [1,{}]", "cannot correct end column value: end_byte index {} is not in range [1,{}]",
index, &[&index.to_string(), &source.len().to_string()],
source.len() )
))
.severity(diagnostics::Severity::Error), .severity(diagnostics::Severity::Error),
); );
} }

View File

@@ -73,8 +73,8 @@ fn main() -> std::io::Result<()> {
Err(e) => { Err(e) => {
main_thread_logger.write( main_thread_logger.write(
main_thread_logger main_thread_logger
.message("configuration-error", "Configuration error") .new_entry("configuration-error", "Configuration error")
.text(&format!("{}; defaulting to 1 thread.", e)) .message("{}; defaulting to 1 thread.", &[&e])
.severity(diagnostics::Severity::Warning), .severity(diagnostics::Severity::Warning),
); );
1 1
@@ -94,8 +94,8 @@ fn main() -> std::io::Result<()> {
Err(e) => { Err(e) => {
main_thread_logger.write( main_thread_logger.write(
main_thread_logger main_thread_logger
.message("configuration-error", "Configuration error") .new_entry("configuration-error", "Configuration error")
.text(&format!("{}; using gzip.", e)) .message("{}; using gzip.", &[&e])
.severity(diagnostics::Severity::Warning), .severity(diagnostics::Severity::Warning),
); );
trap::Compression::Gzip trap::Compression::Gzip
@@ -197,16 +197,15 @@ fn main() -> std::io::Result<()> {
needs_conversion = false; needs_conversion = false;
diagnostics_writer.write( diagnostics_writer.write(
diagnostics_writer diagnostics_writer
.message( .new_entry(
"character-decoding-error", "character-decoding-error",
"Character decoding error", "Character decoding error",
) )
.file(&path.to_string_lossy()) .file(&path.to_string_lossy())
.text(&format!( .message(
"could not decode the file contents as '{}': {}", "could not decode the file contents as {}: {}",
&encoding_name, &[&encoding_name, &msg],
msg, )
))
.status_page() .status_page()
.severity(diagnostics::Severity::Warning), .severity(diagnostics::Severity::Warning),
); );
@@ -216,12 +215,12 @@ fn main() -> std::io::Result<()> {
} else { } else {
diagnostics_writer.write( diagnostics_writer.write(
diagnostics_writer diagnostics_writer
.message("unknown-character-encoding", "Unknown character encoding") .new_entry("unknown-character-encoding", "Unknown character encoding")
.file(&path.to_string_lossy()) .file(&path.to_string_lossy())
.text(&format!( .message(
"unknown character encoding '{}' in '#encoding:' directive.", "unknown character encoding {} in {} directive.",
&encoding_name &[&encoding_name, "#encoding:"],
)) )
.status_page() .status_page()
.help_link("https://docs.ruby-lang.org/en/3.2/syntax/comments_rdoc.html#label-encoding+Directive") .help_link("https://docs.ruby-lang.org/en/3.2/syntax/comments_rdoc.html#label-encoding+Directive")
.severity(diagnostics::Severity::Warning), .severity(diagnostics::Severity::Warning),