better message for duplicate fields/tags

This commit is contained in:
Folkert 2020-04-14 21:18:37 +02:00
parent 18c6c37c04
commit 7e4a1ef4bd
6 changed files with 367 additions and 52 deletions

View file

@ -17,6 +17,8 @@ const CYCLE_LN: &str = ["| ", "│ "][!IS_WINDOWS as usize];
const CYCLE_MID: &str = ["| |", "│ ↓"][!IS_WINDOWS as usize];
const CYCLE_END: &str = ["+-<---+", "└─────┘"][!IS_WINDOWS as usize];
const GUTTER_BAR: &str = "";
pub fn cycle<'b>(
alloc: &'b RocDocAllocator<'b>,
indent: usize,
@ -429,40 +431,91 @@ pub fn can_problem<'b>(
field_name,
field_region,
record_region,
replaced_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This record defines the "),
alloc.record_field(field_name),
alloc.record_field(field_name.clone()),
alloc.reflow(" field twice!"),
]),
alloc.region_with_subregion(record_region, field_region),
alloc.reflow("In the rest of the program, I will use the second definition."),
alloc.region_all_the_things(
record_region,
replaced_region,
field_region,
Annotation::Error,
),
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
alloc.region_all_the_things(
record_region,
field_region,
field_region,
Annotation::TypoSuggestion,
),
alloc.concat(vec![
alloc.reflow("For clarity, remove the previous "),
alloc.record_field(field_name),
alloc.reflow(" definitions from this record."),
]),
]),
Problem::DuplicateRecordFieldType {
field_name,
field_region,
record_region,
replaced_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This record type defines the "),
alloc.record_field(field_name),
alloc.record_field(field_name.clone()),
alloc.reflow(" field twice!"),
]),
alloc.region_with_subregion(record_region, field_region),
alloc.reflow("In the rest of the program, I will use the second definition."),
alloc.region_all_the_things(
record_region,
replaced_region,
field_region,
Annotation::Error,
),
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
alloc.region_all_the_things(
record_region,
field_region,
field_region,
Annotation::TypoSuggestion,
),
alloc.concat(vec![
alloc.reflow("For clarity, remove the previous "),
alloc.record_field(field_name),
alloc.reflow(" definitions from this record type."),
]),
]),
Problem::DuplicateTag {
tag_name,
tag_union_region,
tag_region,
replaced_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This tag union type defines the "),
alloc.tag_name(tag_name),
alloc.tag_name(tag_name.clone()),
alloc.reflow(" tag twice!"),
]),
alloc.region_with_subregion(tag_union_region, tag_region),
alloc.reflow("In the rest of the program, I will use the second definition."),
alloc.region_all_the_things(
tag_union_region,
replaced_region,
tag_region,
Annotation::Error,
),
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
alloc.region_all_the_things(
tag_union_region,
tag_region,
tag_region,
Annotation::TypoSuggestion,
),
alloc.concat(vec![
alloc.reflow("For clarity, remove the previous "),
alloc.tag_name(tag_name),
alloc.reflow(" definitions from this tag union type."),
]),
]),
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
};
@ -793,6 +846,107 @@ impl<'a> RocDocAllocator<'a> {
.annotate(Annotation::Hint)
}
pub fn region_all_the_things(
&'a self,
region: roc_region::all::Region,
sub_region1: roc_region::all::Region,
sub_region2: roc_region::all::Region,
error_annotation: Annotation,
) -> DocBuilder<'a, Self, Annotation> {
debug_assert!(region.contains(&sub_region1));
debug_assert!(region.contains(&sub_region2));
// if true, the final line of the snippet will be some ^^^ that point to the region where
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
// where the problem is.
let error_highlight_line = region.start_line == region.end_line;
let max_line_number_length = (region.end_line + 1).to_string().len();
let indent = 2;
let mut result = self.nil();
for i in region.start_line..=region.end_line {
let line_number_string = (i + 1).to_string();
let line_number = line_number_string;
let this_line_number_length = line_number.len();
let line = self.src_lines[i as usize];
let rest_of_line = if !line.trim().is_empty() {
self.text(line).indent(indent)
} else {
self.nil()
};
let highlight = !error_highlight_line
&& ((i >= sub_region1.start_line && i <= sub_region1.end_line)
|| (i >= sub_region2.start_line && i <= sub_region2.end_line));
let source_line = if highlight {
self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(self.text(">").annotate(error_annotation))
.append(rest_of_line)
} else if error_highlight_line {
self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(rest_of_line)
} else {
self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(self.text(" "))
.append(rest_of_line)
};
result = result.append(source_line);
if i != region.end_line {
result = result.append(self.line())
}
}
if error_highlight_line {
let overlapping = sub_region2.start_col < sub_region1.end_col;
let highlight = if overlapping {
self.text("^".repeat((sub_region2.end_col - sub_region1.start_col) as usize))
} else {
let highlight1 = "^".repeat((sub_region1.end_col - sub_region1.start_col) as usize);
let highlight2 = if sub_region1 == sub_region2 {
"".repeat(0)
} else {
"^".repeat((sub_region2.end_col - sub_region2.start_col) as usize)
};
let inbetween = " "
.repeat((sub_region2.start_col.saturating_sub(sub_region1.end_col)) as usize);
self.text(highlight1)
.append(self.text(inbetween))
.append(self.text(highlight2))
};
let highlight_line = self
.line()
.append(self.text(" ".repeat(max_line_number_length)))
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(if sub_region1.is_empty() && sub_region2.is_empty() {
self.nil()
} else {
self.text(" ".repeat(sub_region1.start_col as usize))
.indent(indent)
.append(highlight)
.annotate(error_annotation)
});
result = result.append(highlight_line);
}
result.annotate(Annotation::CodeBlock)
}
pub fn region_with_subregion(
&'a self,
region: roc_region::all::Region,
@ -835,18 +989,18 @@ impl<'a> RocDocAllocator<'a> {
{
self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text("").annotate(Annotation::GutterBar))
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(self.text(">").annotate(Annotation::Error))
.append(rest_of_line)
} else if error_highlight_line {
self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text("").annotate(Annotation::GutterBar))
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(rest_of_line)
} else {
self.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text("").annotate(Annotation::GutterBar))
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(self.text(" "))
.append(rest_of_line)
};
@ -863,7 +1017,7 @@ impl<'a> RocDocAllocator<'a> {
let highlight_line = self
.line()
.append(self.text(" ".repeat(max_line_number_length)))
.append(self.text("").annotate(Annotation::GutterBar))
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(if highlight_text.is_empty() {
self.nil()
} else {
@ -882,6 +1036,40 @@ impl<'a> RocDocAllocator<'a> {
self.region_with_subregion(region, region)
}
pub fn region_without_error(
&'a self,
region: roc_region::all::Region,
) -> DocBuilder<'a, Self, Annotation> {
let mut result = self.nil();
for i in region.start_line..=region.end_line {
let line = if i == region.start_line {
if i == region.end_line {
&self.src_lines[i as usize][region.start_col as usize..region.end_col as usize]
} else {
&self.src_lines[i as usize][region.start_col as usize..]
}
} else if i == region.end_line {
&self.src_lines[i as usize][0..region.end_col as usize]
} else {
self.src_lines[i as usize]
};
let rest_of_line = if !line.trim().is_empty() {
self.text(line).annotate(Annotation::CodeBlock)
} else {
self.nil()
};
result = result.append(rest_of_line);
if i != region.end_line {
result = result.append(self.line())
}
}
result.indent(4)
}
pub fn ident(&'a self, ident: Ident) -> DocBuilder<'a, Self, Annotation> {
self.text(format!("{}", ident.as_inline_str()))
.annotate(Annotation::Symbol)
@ -917,6 +1105,7 @@ pub enum Annotation {
pub struct CiWrite<W> {
style_stack: Vec<Annotation>,
in_type_block: bool,
in_code_block: bool,
upstream: W,
}
@ -925,6 +1114,7 @@ impl<W> CiWrite<W> {
CiWrite {
style_stack: vec![],
in_type_block: false,
in_code_block: false,
upstream,
}
}
@ -972,6 +1162,9 @@ where
TypeBlock => {
self.in_type_block = true;
}
CodeBlock => {
self.in_code_block = true;
}
Emphasized => {
self.write_str("*")?;
}
@ -980,7 +1173,7 @@ where
}
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
| TypeVariable
if !self.in_type_block =>
if !self.in_type_block && !self.in_code_block =>
{
self.write_str("`")?;
}
@ -1000,6 +1193,9 @@ where
TypeBlock => {
self.in_type_block = false;
}
CodeBlock => {
self.in_code_block = false;
}
Emphasized => {
self.write_str("*")?;
}
@ -1008,7 +1204,7 @@ where
}
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
| TypeVariable
if !self.in_type_block =>
if !self.in_type_block && !self.in_code_block =>
{
self.write_str("`")?;
}