summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--src/lib.rs2
-rw-r--r--src/spans.rs21
-rw-r--r--tests/span-tests/demo.html1
-rw-r--r--tests/span-tests/demo.out7
-rw-r--r--tests/test_spans.rs63
6 files changed, 88 insertions, 7 deletions
diff --git a/Cargo.toml b/Cargo.toml
index a9cb994..13f8e5d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ version = "0.2.1"
include = ["src/**/*", "LICENSE", "README.md"]
[dev-dependencies]
+codespan-reporting = "0.11.1"
pretty_assertions = "1.0.0"
serde = { version = "1.0.130", features = ["derive"] }
serde_json = "1.0.71"
diff --git a/src/lib.rs b/src/lib.rs
index 40f9588..4b74d61 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,7 +9,7 @@ mod error;
mod machine;
mod never;
mod reader;
-mod spans;
+pub mod spans;
mod tokenizer;
mod utils;
diff --git a/src/spans.rs b/src/spans.rs
index 9d02940..72b30c0 100644
--- a/src/spans.rs
+++ b/src/spans.rs
@@ -1,3 +1,4 @@
+//! Source code spans.
use std::{
collections::{BTreeSet, VecDeque},
marker::PhantomData,
@@ -8,13 +9,18 @@ use crate::{Doctype, Emitter, EndTag, Error, Reader, StartTag, Token};
type Span = std::ops::Range<usize>;
+/// A trait to be implemented by readers that track their own position.
pub trait GetPos {
+ /// Returns the byte index of the current position.
fn get_pos(&self) -> usize;
}
-struct PosTracker<R> {
- reader: R,
- position: usize,
+/// Wraps a [`Reader`] so that it implements [`GetPos`].
+pub struct PosTracker<R> {
+ /// The wrapped reader.
+ pub reader: R,
+ /// The current position.
+ pub position: usize,
}
impl<R> GetPos for PosTracker<R> {
@@ -145,12 +151,15 @@ impl<R: GetPos> Emitter<R> for SpanEmitter<R> {
fn init_start_tag(&mut self, reader: &R) {
self.current_token = Some(Token::StartTag(StartTag {
- name_span: reader.get_pos()..reader.get_pos(),
+ name_span: reader.get_pos() - 1..reader.get_pos() - 1,
..Default::default()
}));
}
- fn init_end_tag(&mut self, _reader: &R) {
- self.current_token = Some(Token::EndTag(Default::default()));
+ fn init_end_tag(&mut self, reader: &R) {
+ self.current_token = Some(Token::EndTag(EndTag {
+ name_span: reader.get_pos() - 1..reader.get_pos() - 1,
+ ..Default::default()
+ }));
self.seen_attributes.clear();
}
diff --git a/tests/span-tests/demo.html b/tests/span-tests/demo.html
new file mode 100644
index 0000000..900f74c
--- /dev/null
+++ b/tests/span-tests/demo.html
@@ -0,0 +1 @@
+this is a tag: <h1>test</h1>
diff --git a/tests/span-tests/demo.out b/tests/span-tests/demo.out
new file mode 100644
index 0000000..98d6766
--- /dev/null
+++ b/tests/span-tests/demo.out
@@ -0,0 +1,7 @@
+note:
+ ┌─ test.html:1:17
+ │
+1 │ this is a tag: <h1>test</h1>
+ │ ^^ ^^ end tag
+ │ │
+ │ start tag
diff --git a/tests/test_spans.rs b/tests/test_spans.rs
new file mode 100644
index 0000000..a3d1c96
--- /dev/null
+++ b/tests/test_spans.rs
@@ -0,0 +1,63 @@
+use std::include_str;
+
+use codespan_reporting::{
+ self,
+ diagnostic::{Diagnostic, Label},
+ files::SimpleFiles,
+ term::{self, termcolor::Buffer},
+};
+use html5gum::{
+ spans::{PosTracker, SpanEmitter},
+ Readable, Token, Tokenizer,
+};
+
+#[test]
+fn test() {
+ let html = include_str!("span-tests/demo.html");
+
+ let mut files = SimpleFiles::new();
+ let file_id = files.add("test.html", html);
+ let mut labels = Vec::new();
+
+ for token in Tokenizer::new_with_emitter(
+ PosTracker {
+ reader: html.to_reader(),
+ position: 0,
+ },
+ SpanEmitter::default(),
+ )
+ .infallible()
+ {
+ if let Token::StartTag(tag) = token {
+ labels.push(Label::primary(file_id, tag.name_span).with_message("start tag"));
+ } else if let Token::EndTag(tag) = token {
+ labels.push(Label::primary(file_id, tag.name_span).with_message("end tag"));
+ }
+ }
+
+ let diagnostic = Diagnostic::note().with_labels(labels);
+
+ let mut writer = Buffer::no_color();
+ let config = codespan_reporting::term::Config::default();
+ term::emit(&mut writer, &config, &files, &diagnostic).unwrap();
+
+ let actual = remove_trailing_spaces(std::str::from_utf8(writer.as_slice()).unwrap());
+ let expected = include_str!("span-tests/demo.out");
+
+ if actual != expected {
+ println!(
+ "EXPECTED:\n{banner}\n{expected}{banner}\n\nACTUAL OUTPUT:\n{banner}\n{actual}{banner}",
+ banner = "-".repeat(30),
+ expected = expected,
+ actual = actual
+ );
+ panic!("failed");
+ }
+}
+
+fn remove_trailing_spaces(text: &str) -> String {
+ text.lines()
+ .map(|l| l.trim_end())
+ .collect::<Vec<_>>()
+ .join("\n")
+}