tamer: Consistent span diagram representation

I'll document it more formally eventually, but this settles on a mix of the
two: square brackets and dashes for intervals, `+` for intersecting lines,
byte offsets below interval endpoints, and names below that.

The docblock for `Span` itself iss still off; I'll probably just take one of
the test cases and paste it there at some point.

DEV-7145
main
Mike Gerwitz 2022-06-06 11:29:17 -04:00
parent bba181f573
commit 495c1438fd
7 changed files with 72 additions and 67 deletions

View File

@ -86,9 +86,11 @@ impl Diagnostic for StubError {
const FILE_FOO_BAR: &[u8] =
b"foo/bar line 1\nfoo/bar line 2\nfoo/bar line 3\nfoo/bar line 4";
// |-------+--+-| |-------+--+-| |-------+--+-| |-------+--+-|
// [-------+--+-] [-------+--+-] [-------+--+-] [-------+--+-]
// 0 | |13 15 | |28 30 | |43 45 | |58
// [--] [--] [--] [--]
// 8 11 23 26 38 41 53 56
//
// len: 14
const FILE_BAR_BAZ: &[u8] =
@ -96,7 +98,7 @@ const FILE_BAR_BAZ: &[u8] =
// Offsets for this are the same as `FILE_FOO_BAR`.
const FILE_INVALID_UTF8: &[u8] = b"bad \xC0!";
// |---- |
// [---- ]
// 0 5
const FILE_MANY_LINES: &[u8] = b"\

View File

@ -651,11 +651,11 @@ impl Line {
///
/// # Will have its columns reported as:
/// line 1
/// |-| [4,6]
/// [-] [4,6]
/// line 2
/// |----| [1,6]
/// [----] [1,6]
/// line 4
/// |^^| [1,4]
/// [--] [1,4]
/// ```
fn resolve_columns(&self, line: &str, span: Span) -> Column {
// The max(1) here is intended to accommodate zero-length spans.

View File

@ -74,9 +74,9 @@ fn rejects_span_with_endpoint_past_eof() {
fn first_byte_of_line() {
let ctx = Context::from("foo");
let buf = "line 1\nline 2\nline 3\nline 4";
// |--| |
// [--] |
// 7 10 |
// |----|
// [----]
// 12
let span = ctx.span(7, 4);
@ -107,7 +107,7 @@ fn last_byte_of_line() {
let buf = "line 1\nline 2\nline 3\nline 4";
// | |
// | 19
// |----|
// [----]
// 14
let span = ctx.span(19, 1);
@ -139,9 +139,9 @@ fn last_byte_of_line() {
fn last_byte_of_file_no_trailing_nl() {
let ctx = Context::from("foo");
let buf = "line 1\nline 2\nline 3";
// | |--|
// | [--]
// | 16 19
// |----|
// [----]
// 14
let span = ctx.span(16, 4);
@ -171,9 +171,9 @@ fn last_byte_of_file_no_trailing_nl() {
fn multiple_lines_first_last() {
let ctx = Context::from("foobar");
let buf = "line 1\nline start 2\nend line 3";
// | |-----+- +-| |
// | [-----+- +-] |
// | 12 | |22 |
// |----------| |--------|
// [----------] [--------]
// 7 18 20 29
let span = ctx.span(12, 11);
@ -217,9 +217,9 @@ fn multiple_lines_first_last() {
fn multiple_lines_middle_line_endpoints() {
let ctx = Context::from("foobar");
let buf = "line start 1\nline 2\nend line 3";
// | |-----+- +----+- +-| |
// | [-----+- +----+- +-] |
// | 5 | | | |22 |
// |----------| |----| |--------|
// [----------] [----] [--------]
// 0 11 13 18 20 29
let span = ctx.span(5, 18);
@ -276,7 +276,7 @@ fn multiple_lines_middle_line_endpoints() {
fn first_line() {
let ctx = Context::from("foobar");
let buf = "line 1\n";
// |----|
// [----]
// 0 5
let span = ctx.span(0, 6);
@ -313,7 +313,7 @@ fn newline_between_lines() {
let buf = "line 1\nline 2\nline 3";
// | ||
// | |13
// |----|
// [----]
// 7 12
let span = ctx.span(13, 1);
@ -351,7 +351,7 @@ fn zero_length_span() {
let buf = "line 1\nline 2\nline 3";
// | | |
// | 10 |
// |----|
// [----]
// 7 12
let span = ctx.span(10, 0);
@ -384,7 +384,7 @@ fn zero_length_span_at_eol() {
let buf = "line 1\nline 2\nline 3";
// | ||
// | |13
// |----|
// [----]
// 7 12
let span = ctx.span(13, 0);
@ -421,7 +421,7 @@ fn zero_length_span_at_bol() {
let buf = "line 1\nline 2\nline 3";
// | |
// 7 |
// |----|
// [----]
// 12
let span = ctx.span(7, 0);
@ -449,7 +449,7 @@ fn zero_length_span_at_bol() {
fn resolve_multiple_spans() {
let ctx = Context::from("multi");
let buf = "line 1\nline 2\nline 3";
// |----| |----|
// [----] [----]
// 7 12 14 19
// A B
@ -497,7 +497,7 @@ fn resolve_multiple_spans() {
fn resolve_same_span_multiple_times() {
let ctx = Context::from("multi");
let buf = "line 1\nline 2\nline 3";
// |----|
// [----]
// 7 12
// A
@ -529,7 +529,7 @@ fn resolve_same_span_multiple_times() {
fn resolve_earlier_span_after_later() {
let ctx = Context::from("multi");
let buf = "line 1\nline 2\nline 3";
// |----| |----|
// [----] [----]
// 0 5 7 12
// earlier later
@ -592,7 +592,7 @@ fn invalid_unicode_no_column() {
let ctx = Context::from("invalid-unicode");
let mut buf = b"bad \xC0!\n".to_vec();
// |---- |
// [---- ]
// 0 5
let span = ctx.span(0, 4);
@ -628,7 +628,7 @@ fn unicode_width() {
let ctx = Context::from("unicode-width");
let buf = "0:\0\n1:“\n2:😊";
// |-| |-| |--|
// [-] [-] [--]
// bytes: 0 2 4 8 10 15
// col: 1 2 1 3 1 4
@ -710,7 +710,7 @@ fn at_invalid_char_boundary() {
// Charcater is 4 bytes.
let buf = "(😊)";
// |--|
// [--]
// bytes: 0 5
// col: 1 4

View File

@ -86,13 +86,13 @@
//! # let ctx: Context = "some/path/foo".intern().into();
//! #
//! // Visualization of spans:
//! // [..... ..... ..... .....]
//! // [A====] [B==] |
//! // | | [C=] |
//! // | | [D====]
//! // | | [E] |
//! // [F=====] |
//! // [G=======] |
//! // [....,....,....,....,]
//! // [A-+-] [B-+]|
//! // | [C-] |
//! // | [D-+-]
//! // | [E]
//! // [F----] |
//! // [G------]
//!
//! let A = Span::new(2, 6, ctx);
//! let B = Span::new(10, 5, ctx);
@ -342,9 +342,10 @@ impl Span {
/// #
/// # let ctx: Context = "some/path/foo".intern().into();
/// #
/// // [..... .....]
/// // [A===]
/// // 2 6
/// // [0123456789]
/// // [---]
/// // 2 6
/// // A
/// let A = Span::new(2, 6, ctx);
///
/// assert_eq!(

View File

@ -44,19 +44,21 @@ pub struct Attr(pub QName, pub SymbolId, pub AttrSpan);
/// The diagram below illustrates the behavior of `AttrSpan`.
/// Note that the extra spaces surrounding the `=` are intentional to
/// illustrate what the behavior ought to be.
/// Spans are represented by `|---|` intervals,
/// Spans are represented by `[---]` intervals,
/// with the byte offset at each end,
/// and the single-letter span name centered below the interval.
/// `+` represents intersecting `-` and `|` lines.
///
/// ```text
/// <foo bar = "baz" />
/// |-| |+-+|
/// [-] [+-+]
/// 5 7 13| |17
/// |K |Q|
/// |K |Q||
/// | | ||
/// | [-]|
/// | 14 16
/// | V |
/// |-----------|
/// [-----------]
/// A
/// ```
///

View File

@ -164,7 +164,7 @@ impl<'s, B: BufRead, S: Escaper> XmlXirReader<'s, B, S> {
QuickXmlEvent::End(ele) => Some({
// </foo>
// |----| name + '<' + '/' + '>'
// [----] name + '<' + '/' + '>'
let span = ctx.span_or_zz(prev_pos, ele.name().len() + 3);
ele.name()
@ -187,7 +187,7 @@ impl<'s, B: BufRead, S: Escaper> XmlXirReader<'s, B, S> {
QuickXmlEvent::Text(bytes) => Some({
// <text>foo bar</text>
// |-----|
// [-----]
let span = ctx.span_or_zz(prev_pos, bytes.len());
bytes
@ -201,7 +201,7 @@ impl<'s, B: BufRead, S: Escaper> XmlXirReader<'s, B, S> {
// Comments are _not_ returned escaped.
QuickXmlEvent::Comment(bytes) => Some({
// <!-- foo -->
// |----------| " foo " + "<!--" + "-->"
// [----------] " foo " + "<!--" + "-->"
let span = ctx.span_or_zz(prev_pos, bytes.len() + 7);
bytes
@ -255,7 +255,7 @@ impl<'s, B: BufRead, S: Escaper> XmlXirReader<'s, B, S> {
// but it does not.
if ver != b"1.0" {
// <?xml version="X.Y"?>
// |-|
// [-]
let ver_pos = (ver.as_ptr() as usize) - decl_ptr;
let span = ctx.span_or_zz(ver_pos, ver.len());
@ -319,7 +319,7 @@ impl<'s, B: BufRead, S: Escaper> XmlXirReader<'s, B, S> {
Some(b'"' | b'\'') => {
return Err({
// <foo="bar" ...>
// |-------|
// [-------]
let span = ctx.span_or_zz(pos + 1, len);
Error::InvalidQName(
@ -343,10 +343,10 @@ impl<'s, B: BufRead, S: Escaper> XmlXirReader<'s, B, S> {
let noattr_add: usize = (!has_attrs).into();
// <tag ... />
// |--| name + '<'
// [--] name + '<'
//
// <tag>..</tag>
// |---| name + '<' + '>'
// [---] name + '<' + '>'
let span = ctx.span_or_zz(pos, len + 1 + noattr_add);
if has_attrs {
@ -361,7 +361,7 @@ impl<'s, B: BufRead, S: Escaper> XmlXirReader<'s, B, S> {
// Given this input, quick-xml ignores the bytes entirely:
// <foo bar>
// |--| missing `="value"`
// [--] missing `="value"`
//
// The whitespace check is to handle input like this:
// <foo />

View File

@ -96,7 +96,7 @@ macro_rules! new_sut {
#[test]
fn empty_node_without_prefix_or_attributes() {
new_sut!(sut = "<empty-node />");
// |---------| ||
// [---------] []
// 0 10
// A B
@ -116,7 +116,7 @@ fn empty_node_without_prefix_or_attributes() {
#[test]
fn does_not_resolve_xmlns() {
new_sut!(sut = r#"<no-ns xmlns="noresolve" />"#);
// |----| |---| |-------| ||
// [----] [---] [-------] []
// 0 5 7 11 14 22 25
// A B C D
@ -141,7 +141,7 @@ fn does_not_resolve_xmlns() {
#[test]
fn empty_node_with_prefix_without_attributes_unresolved() {
new_sut!(sut = r#"<x:empty-node xmlns:x="noresolve" />"#);
// |-----------| |-----| |-------| ||
// [-----------] [-----] [-------] []
// 0 12 14 20 23 31 34
// A B C D
@ -167,7 +167,7 @@ fn empty_node_with_prefix_without_attributes_unresolved() {
fn prefix_with_empty_local_name_invalid_qname() {
// No local name (trailing colon).
new_sut!(sut = r#"<x: xmlns:x="testns" />"#);
// ||
// []
// 1
// A
@ -187,7 +187,7 @@ fn prefix_with_empty_local_name_invalid_qname() {
#[test]
fn multiple_attrs_ordered() {
new_sut!(sut = r#"<ele foo="a" bar="b" b:baz="c" />"#);
// |--| |-| | |-| | |---| | ||
// [--] [-] | [-] | [---] | []
// 0 3 5 7 10 13 18 21 25 28 31
// A B C D E F G H
@ -218,7 +218,7 @@ fn multiple_attrs_ordered() {
#[test]
fn empty_attr_value() {
new_sut!(sut = r#"<ele empty="" />"#);
// |--| |---| | ||
// [--] [---] | []
// 0 3 5 9 12 14
// A B C D
// /
@ -247,7 +247,7 @@ fn empty_attr_value() {
#[test]
fn permits_duplicate_attrs() {
new_sut!(sut = r#"<dup attr="a" attr="b" />"#);
// |--| |--| | |--| | ||
// [--] [--] | [--] | []
// 0 3 5 8 11 14 17 20 23
// A B C D E F
@ -274,7 +274,7 @@ fn permits_duplicate_attrs() {
#[test]
fn child_node_self_closing() {
new_sut!(sut = r#"<root><child /></root>"#);
// |----||----| |||-----|
// [----][----] [][-----]
// 0 5`6 11 13`15 21
// A B C D
// /
@ -300,7 +300,7 @@ fn child_node_self_closing() {
#[test]
fn sibling_nodes() {
new_sut!(sut = r#"<root><child /><child /></root>"#);
// |----||----| |||----| |||-----|
// [----][----] [][----] [][-----]
// 0 5`6 11 13`15 20 22`24 30
// A B C D E F
@ -327,7 +327,7 @@ fn sibling_nodes() {
#[test]
fn child_node_with_attrs() {
new_sut!(sut = r#"<root><child foo="bar" /></root>"#);
// |----||----| |-| |-| |||-----|
// [----][----] [-] [-] [][-----]
// 0 5`6 11 13 18 20 23`25 31
// A B C D E F
@ -354,7 +354,7 @@ fn child_node_with_attrs() {
#[test]
fn child_text() {
new_sut!(sut = r#"<text>foo bar</text>"#);
// |----||-----||-----|
// [----][-----][-----]
// 0 5`6 12`13 19
// A B C
@ -375,7 +375,7 @@ fn child_text() {
#[test]
fn mixed_child_content() {
new_sut!(sut = r#"<text>foo<em>bar</em></text>"#);
// |----||-||--||-||---||-----|
// [----][-][--][-][---][-----]
// 0 5`6 9 12`13`16 21 27
// A B C D E F
@ -412,7 +412,7 @@ fn mixed_child_content_with_newlines() {
"#
);
// \n<root>\n <child />\n</root>\n
// |||----|| -||----| |||||-----|||
// [][----][ -][----] [][][-----][]
// 0 1 6`7 9`10 15 17| `20 26`27
// 19
// A B C D E F G H
@ -444,7 +444,7 @@ fn mixed_child_content_with_newlines() {
#[test]
fn comment() {
new_sut!(sut = r#"<!--root--><root><!--<child>--></root>"#);
// |---------||----||------------||-----|
// [---------][----][------------][-----]
// 0 10`11 16`17 30`31 37
// A B C D
@ -473,7 +473,7 @@ lines-->
</mult>"#
);
// <mult><!--comment\non multiple\nlines-->\n</mult>
// |----||----------- ------------ -------||||-----|
// [----][----------- ------------ -------][][-----]
// 0 5`6 37'38`39 45
// A B C D
@ -497,7 +497,7 @@ lines-->
#[test]
fn permits_mismatched_tags() {
new_sut!(sut = r#"<root><child /></mismatch>"#);
// |----||----| |||---------|
// [----][----] [][---------]
// 0 5`6 11 13`15 25
// A B C D
@ -585,7 +585,7 @@ fn attr_value_invalid_utf8() {
#[test]
fn valid_xml_decl_no_encoding() {
new_sut!(sut = r#"<?xml version="1.0"?><root />"#);
// |---| ||
// [---] []
// 21 25 27
// A B
// We do not yet emit a token for
@ -621,7 +621,7 @@ fn valid_xml_decl_with_encoding_upper() {
#[test]
fn invalid_xml_decl_version() {
new_sut!(sut = r#"<?xml version="1.1"?>"#);
// |-|
// [-]
// 15 17
// Unlike above, we do actually calculate a span here.
@ -637,7 +637,7 @@ fn invalid_xml_decl_version() {
#[test]
fn invalid_xml_encoding() {
new_sut!(sut = r#"<?xml version="1.0" encoding="latin-1"?>"#);
// |-----|
// [-----]
// 30 37
let span = DC.span(30, 7);
@ -783,7 +783,7 @@ fn empty_element_qname_with_space_no_attrs() {
#[test]
fn empty_element_qname_with_attr() {
new_sut!(sut = r#"<foo="bar">"#);
// |-------|
// [-------]
// 1 10
let span = DC.span(1, 9);