diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml
index a2d75c3..14be261 100644
--- a/.github/workflows/fuzz.yml
+++ b/.github/workflows/fuzz.yml
@@ -10,10 +10,25 @@ on:
jobs:
check:
runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./fuzz
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- - run: cd fuzz
- - run: cargo check
+
+ - run: cargo fmt --all -- --check
+
+ - name: Check fuzz code compiles
+ run: cargo check
+
+ - name: Build AFL target
+ run: make setup-afl
+
+ - name: Build libfuzzer target
+ run: |
+ rustup toolchain install nightly
+ cargo install cargo-fuzz
+ cargo +nightly fuzz build main_libfuzzer
diff --git a/examples/tokenize_with_spans.rs b/examples/tokenize_with_spans.rs
new file mode 100644
index 0000000..02bb4ef
--- /dev/null
+++ b/examples/tokenize_with_spans.rs
@@ -0,0 +1,12 @@
+//! Let's you easily try out the tokenizer with e.g.
+//! printf '
Hello world!
' | cargo run --example=tokenize_with_spans
+use html5gum::{DefaultEmitter, IoReader, Tokenizer};
+
+fn main() {
+ let emitter = DefaultEmitter::::new_with_span();
+ for token in
+ Tokenizer::new_with_emitter(IoReader::new(std::io::stdin().lock()), emitter).flatten()
+ {
+ println!("{:?}", token);
+ }
+}
diff --git a/fuzz/.github/workflows/fuzz.yml b/fuzz/.github/workflows/fuzz.yml
new file mode 100644
index 0000000..e69de29
diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock
index 1dbd1dc..5ba4b23 100644
--- a/fuzz/Cargo.lock
+++ b/fuzz/Cargo.lock
@@ -38,6 +38,12 @@ version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
+[[package]]
+name = "anyhow"
+version = "1.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
+
[[package]]
name = "arbitrary"
version = "1.3.2"
@@ -46,11 +52,10 @@ checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
[[package]]
name = "ast_node"
-version = "3.0.0"
+version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91fb5864e2f5bf9fd9797b94b2dfd1554d4c3092b535008b27d7e15c86675a2f"
+checksum = "2eb025ef00a6da925cf40870b9c8d008526b6004ece399cb0974209720f0b194"
dependencies = [
- "proc-macro2",
"quote",
"swc_macros_common",
"syn 2.0.98",
@@ -64,9 +69,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "better_scoped_tls"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50fd297a11c709be8348aec039c8b91de16075d2b2bdaee1bd562c0875993664"
+checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609"
dependencies = [
"scoped-tls",
]
@@ -83,15 +88,6 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
-[[package]]
-name = "bumpalo"
-version = "3.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
-dependencies = [
- "allocator-api2",
-]
-
[[package]]
name = "byteorder"
version = "1.5.0"
@@ -100,9 +96,19 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.10.0"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
+
+[[package]]
+name = "bytes-str"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
+checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3"
+dependencies = [
+ "bytes",
+ "serde",
+]
[[package]]
name = "cc"
@@ -220,6 +226,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
[[package]]
name = "foldhash"
version = "0.1.4"
@@ -237,11 +249,10 @@ dependencies = [
[[package]]
name = "from_variant"
-version = "2.0.0"
+version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d7ccf961415e7aa17ef93dcb6c2441faaa8e768abe09e659b908089546f74c5"
+checksum = "e5ff35a391aef949120a0340d690269b3d9f63460a6106e99bd07b961f345ea9"
dependencies = [
- "proc-macro2",
"swc_macros_common",
"syn 2.0.98",
]
@@ -273,18 +284,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
- "wasi 0.9.0+wasi-snapshot-preview1",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasi",
]
[[package]]
@@ -319,42 +319,26 @@ dependencies = [
[[package]]
name = "hstr"
-version = "0.2.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dae404c0c5d4e95d4858876ab02eecd6a196bb8caa42050dfa809938833fc412"
-dependencies = [
- "hashbrown 0.14.5",
- "new_debug_unreachable",
- "once_cell",
- "phf 0.11.2",
- "rustc-hash 1.1.0",
- "triomphe",
-]
-
-[[package]]
-name = "hstr"
-version = "1.0.0"
+version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71399f53a92ef72ee336a4b30201c6e944827e14e0af23204c291aad9c24cc85"
+checksum = "0c43c0a9e8fbdb3bb9dc8eee85e1e2ac81605418b4c83b6b7413cbf14d56ca5c"
dependencies = [
"hashbrown 0.14.5",
"new_debug_unreachable",
"once_cell",
- "phf 0.11.2",
- "rustc-hash 2.1.1",
+ "rustc-hash",
+ "serde",
"triomphe",
]
[[package]]
name = "html5ever"
-version = "0.29.1"
+version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c"
+checksum = "6452c4751a24e1b99c3260d505eaeee76a050573e61f30ac2c924ddc7236f01e"
dependencies = [
"log",
- "mac",
"markup5ever",
- "match_token",
]
[[package]]
@@ -368,7 +352,7 @@ dependencies = [
[[package]]
name = "html5gum"
-version = "0.7.0"
+version = "0.8.1"
dependencies = [
"jetscii",
]
@@ -382,11 +366,11 @@ dependencies = [
"encoding_rs",
"html5ever",
"html5gum 0.6.1",
- "html5gum 0.7.0",
+ "html5gum 0.8.1",
"libfuzzer-sys",
"lol_html",
"pretty_assertions",
- "swc_common 5.0.0",
+ "swc_common",
"swc_html_ast",
"swc_html_parser",
]
@@ -626,27 +610,13 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "markup5ever"
-version = "0.14.0"
+version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82c88c6129bd24319e62a0359cb6b958fa7e8be6e19bb1663bc396b90883aca5"
+checksum = "6c3294c4d74d0742910f8c7b466f44dda9eb2d5742c1e430138df290a1e8451c"
dependencies = [
"log",
- "phf 0.11.2",
- "phf_codegen 0.11.2",
- "string_cache",
- "string_cache_codegen",
"tendril",
-]
-
-[[package]]
-name = "match_token"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.98",
+ "web_atoms",
]
[[package]]
@@ -755,11 +725,12 @@ dependencies = [
[[package]]
name = "phf"
-version = "0.11.2"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
+checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [
- "phf_shared 0.11.2",
+ "phf_shared 0.13.1",
+ "serde",
]
[[package]]
@@ -774,12 +745,12 @@ dependencies = [
[[package]]
name = "phf_codegen"
-version = "0.11.2"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
+checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
dependencies = [
- "phf_generator 0.11.2",
- "phf_shared 0.11.2",
+ "phf_generator 0.13.1",
+ "phf_shared 0.13.1",
]
[[package]]
@@ -789,27 +760,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared 0.8.0",
- "rand 0.7.3",
-]
-
-[[package]]
-name = "phf_generator"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
-dependencies = [
- "phf_shared 0.10.0",
- "rand 0.8.5",
+ "rand",
]
[[package]]
name = "phf_generator"
-version = "0.11.2"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
+checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
dependencies = [
- "phf_shared 0.11.2",
- "rand 0.8.5",
+ "fastrand",
+ "phf_shared 0.13.1",
]
[[package]]
@@ -832,25 +793,16 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
- "siphasher",
+ "siphasher 0.3.11",
]
[[package]]
name = "phf_shared"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
-dependencies = [
- "siphasher",
-]
-
-[[package]]
-name = "phf_shared"
-version = "0.11.2"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
+checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
dependencies = [
- "siphasher",
+ "siphasher 1.0.1",
]
[[package]]
@@ -899,26 +851,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "ptr_meta"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90"
-dependencies = [
- "ptr_meta_derive",
-]
-
-[[package]]
-name = "ptr_meta_derive"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.98",
-]
-
[[package]]
name = "quote"
version = "1.0.37"
@@ -934,25 +866,14 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
- "getrandom 0.1.16",
+ "getrandom",
"libc",
- "rand_chacha 0.2.2",
- "rand_core 0.5.1",
+ "rand_chacha",
+ "rand_core",
"rand_hc",
"rand_pcg",
]
-[[package]]
-name = "rand"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
-dependencies = [
- "libc",
- "rand_chacha 0.3.1",
- "rand_core 0.6.4",
-]
-
[[package]]
name = "rand_chacha"
version = "0.2.2"
@@ -960,17 +881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
- "rand_core 0.5.1",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.6.4",
+ "rand_core",
]
[[package]]
@@ -979,16 +890,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
- "getrandom 0.1.16",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom 0.2.15",
+ "getrandom",
]
[[package]]
@@ -997,7 +899,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
- "rand_core 0.5.1",
+ "rand_core",
]
[[package]]
@@ -1006,7 +908,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
- "rand_core 0.5.1",
+ "rand_core",
]
[[package]]
@@ -1018,12 +920,6 @@ dependencies = [
"bitflags 2.6.0",
]
-[[package]]
-name = "rustc-hash"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
-
[[package]]
name = "rustc-hash"
version = "2.1.1"
@@ -1083,18 +979,28 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
-version = "1.0.214"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.214"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@@ -1103,14 +1009,15 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.132"
+version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
+checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
+ "serde_core",
]
[[package]]
@@ -1135,6 +1042,12 @@ version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
+[[package]]
+name = "siphasher"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
+
[[package]]
name = "smallvec"
version = "1.13.2"
@@ -1149,137 +1062,69 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "string_cache"
-version = "0.8.7"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
+checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
dependencies = [
"new_debug_unreachable",
- "once_cell",
"parking_lot",
- "phf_shared 0.10.0",
+ "phf_shared 0.13.1",
"precomputed-hash",
"serde",
]
[[package]]
name = "string_cache_codegen"
-version = "0.5.2"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
+checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
dependencies = [
- "phf_generator 0.10.0",
- "phf_shared 0.10.0",
+ "phf_generator 0.13.1",
+ "phf_shared 0.13.1",
"proc-macro2",
"quote",
]
[[package]]
name = "string_enum"
-version = "1.0.0"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9fe66b8ee349846ce2f9557a26b8f1e74843c4a13fb381f9a3d73617a5f956a"
+checksum = "ae36a4951ca7bd1cfd991c241584a9824a70f6aff1e7d4f693fb3f2465e4030e"
dependencies = [
- "proc-macro2",
"quote",
"swc_macros_common",
"syn 2.0.98",
]
-[[package]]
-name = "swc_allocator"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "117d5d3289663f53022ebf157df8a42b3872d7ac759e63abf96b5987b85d4af3"
-dependencies = [
- "bumpalo",
- "hashbrown 0.14.5",
- "ptr_meta",
- "rustc-hash 1.1.0",
- "triomphe",
-]
-
-[[package]]
-name = "swc_allocator"
-version = "4.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc6b926f0d94bbb34031fe5449428cfa1268cdc0b31158d6ad9c97e0fc1e79dd"
-dependencies = [
- "allocator-api2",
- "bumpalo",
- "ptr_meta",
- "rustc-hash 2.1.1",
- "triomphe",
-]
-
-[[package]]
-name = "swc_atoms"
-version = "3.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "151a6feb82b989a087433baca7f6a6eb4fcf83f828c479eecd039c9312d60e10"
-dependencies = [
- "hstr 0.2.12",
- "once_cell",
- "rustc-hash 1.1.0",
- "serde",
-]
-
[[package]]
name = "swc_atoms"
-version = "5.0.0"
+version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d7077ba879f95406459bc0c81f3141c529b34580bc64d7ab7bd15e7118a0391"
+checksum = "d4ccbe2ecad10ad7432100f878a107b1d972a8aee83ca53184d00c23a078bb8a"
dependencies = [
- "hstr 1.0.0",
+ "hstr",
"once_cell",
- "rustc-hash 2.1.1",
"serde",
]
[[package]]
name = "swc_common"
-version = "5.0.0"
+version = "18.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a521e8120dc0401580864a643b5bffa035c29fc3fc41697c972743d4f008ed22"
+checksum = "cd86b909e39cba2b3d9ab898d3fb3a912e3b9b809919394fca6fbf1ab970a8b8"
dependencies = [
+ "anyhow",
"ast_node",
"better_scoped_tls",
- "cfg-if",
+ "bytes-str",
"either",
"from_variant",
- "new_debug_unreachable",
"num-bigint",
"once_cell",
- "rustc-hash 1.1.0",
+ "rustc-hash",
"serde",
- "siphasher",
- "swc_allocator 2.0.0",
- "swc_atoms 3.0.0",
- "swc_eq_ignore_macros",
- "swc_visit",
- "tracing",
- "unicode-width",
- "url",
-]
-
-[[package]]
-name = "swc_common"
-version = "8.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26fbd21a1179166b5635d4b7a6b5930cf34b803a7361e0297b04f84dc820db04"
-dependencies = [
- "ast_node",
- "better_scoped_tls",
- "cfg-if",
- "either",
- "from_variant",
- "new_debug_unreachable",
- "num-bigint",
- "once_cell",
- "rustc-hash 2.1.1",
- "serde",
- "siphasher",
- "swc_allocator 4.0.0",
- "swc_atoms 5.0.0",
+ "siphasher 0.3.11",
+ "swc_atoms",
"swc_eq_ignore_macros",
"swc_visit",
"tracing",
@@ -1289,9 +1134,9 @@ dependencies = [
[[package]]
name = "swc_eq_ignore_macros"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e96e15288bf385ab85eb83cff7f9e2d834348da58d0a31b33bdb572e66ee413e"
+checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632"
dependencies = [
"proc-macro2",
"quote",
@@ -1300,48 +1145,47 @@ dependencies = [
[[package]]
name = "swc_html_ast"
-version = "8.0.0"
+version = "18.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a948a1084e92498dcb8e1693369637b3245cc2f0e44faa2adb73c25674ebcbd3"
+checksum = "9d6054a221fb5adb7a16462ee9640b55e005407055df09e76dcd9cecda99a9dd"
dependencies = [
"is-macro",
"string_enum",
- "swc_atoms 5.0.0",
- "swc_common 8.0.0",
+ "swc_atoms",
+ "swc_common",
]
[[package]]
name = "swc_html_parser"
-version = "8.0.0"
+version = "18.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8429579f9b92c420213f8eed963211fdfeab7ddc5bf194ef7312523a6c92ccfb"
+checksum = "cbd555dbbc13a82cea7d66ebb822ad1a913bf375ad7fcf0a132601a6dcebd713"
dependencies = [
- "rustc-hash 2.1.1",
- "swc_atoms 5.0.0",
- "swc_common 8.0.0",
+ "rustc-hash",
+ "swc_atoms",
+ "swc_common",
"swc_html_ast",
"swc_html_utils",
]
[[package]]
name = "swc_html_utils"
-version = "8.0.0"
+version = "16.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1be00b7a98d31c9bc0eb59b5aa523f0ecb246e13fefc77ec6a7170c44b5eb766"
+checksum = "2eb1627e0e88ee72bcda640a807751af2bf4a841da12ca284679e076340602e2"
dependencies = [
"once_cell",
- "rustc-hash 2.1.1",
+ "rustc-hash",
"serde",
"serde_json",
- "swc_atoms 5.0.0",
- "swc_common 8.0.0",
+ "swc_atoms",
]
[[package]]
name = "swc_macros_common"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a509f56fca05b39ba6c15f3e58636c3924c78347d63853632ed2ffcb6f5a0ac7"
+checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae"
dependencies = [
"proc-macro2",
"quote",
@@ -1350,9 +1194,9 @@ dependencies = [
[[package]]
name = "swc_visit"
-version = "2.0.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9138b6a36bbe76dd6753c4c0794f7e26480ea757bee499738bedbbb3ae3ec5f3"
+checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2"
dependencies = [
"either",
"new_debug_unreachable",
@@ -1434,9 +1278,9 @@ dependencies = [
[[package]]
name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
@@ -1445,9 +1289,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
-version = "0.1.27"
+version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
@@ -1456,9 +1300,9 @@ dependencies = [
[[package]]
name = "tracing-core"
-version = "0.1.32"
+version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
dependencies = [
"once_cell",
]
@@ -1481,9 +1325,9 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-width"
-version = "0.1.14"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "url"
@@ -1527,10 +1371,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
-name = "wasi"
-version = "0.11.0+wasi-snapshot-preview1"
+name = "web_atoms"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+checksum = "acd0c322f146d0f8aad130ce6c187953889359584497dac6561204c8e17bb43d"
+dependencies = [
+ "phf 0.13.1",
+ "phf_codegen 0.13.1",
+ "string_cache",
+ "string_cache_codegen",
+]
[[package]]
name = "windows-sys"
diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml
index 4644302..40b914a 100644
--- a/fuzz/Cargo.toml
+++ b/fuzz/Cargo.toml
@@ -14,10 +14,10 @@ pretty_assertions = "1.0.0"
# thirdparty crates to fuzz against
html5gum_old = { version = "=0.6.1", package = "html5gum" }
-html5ever = "0.29.1"
-swc_common = "5.0"
-swc_html_parser = "8.0"
-swc_html_ast = "8.0"
+html5ever = "0.36.1"
+swc_common = "18.0"
+swc_html_parser = "18.0"
+swc_html_ast = "18.0"
# lol-html and its dependencies
lol_html = { version = "2.2", features = ["integration_test"] }
diff --git a/fuzz/Makefile b/fuzz/Makefile
index 3f953ad..fe9c435 100644
--- a/fuzz/Makefile
+++ b/fuzz/Makefile
@@ -42,7 +42,7 @@ afl-requeue:
.PHONY: requeue
setup-afl:
- which cargo-afl || cargo install afl
+ which cargo-afl || cargo install cargo-afl
CARGO_TARGET_DIR=./target-afl/ cargo afl build --release --bin main_afl --features afl
afl: in setup-afl
diff --git a/fuzz/README.md b/fuzz/README.md
index 37051b8..d41fd37 100644
--- a/fuzz/README.md
+++ b/fuzz/README.md
@@ -11,6 +11,8 @@ target.
* `FUZZ_BASIC=1` to run html5gum on the input, exhaust the token iterator but
discard the output. This can only find crashes and hangs.
+* `FUZZ_SPAN_INVARIANTS=1` to validate span correctness and find bugs in span
+ attribution.
* `FUZZ_OLD_HTML5GUM=1` to run html5gum against an older version of itself, and
crash when html5gum produces different output than the old "reference
version". This can be used to find bugs in patches to html5gum.
@@ -58,6 +60,7 @@ stdout.
* Run `FUZZ_BASIC=1 make -e afl-next` after fuzzing to get the next crash and
run afl-tmin on it. It will print the testcase as JSON string to check back
into e.g. a file in `tests/custom-html5lib-tests/`.
+* Run `FUZZ_BASIC=1 make -e afl-skip` to skip over one fuzzing result.
## cargo fuzz
diff --git a/fuzz/src/testcase/html5ever.rs b/fuzz/src/testcase/html5ever.rs
index 90fb447..62645fd 100644
--- a/fuzz/src/testcase/html5ever.rs
+++ b/fuzz/src/testcase/html5ever.rs
@@ -1,10 +1,9 @@
use std::cell::RefCell;
use html5ever::buffer_queue::BufferQueue;
+use html5ever::interface::TokenizerResult;
use html5ever::tendril::format_tendril;
-use html5ever::tokenizer::{
- TagKind, Token as Token2, TokenSinkResult, TokenizerOpts, TokenizerResult,
-};
+use html5ever::tokenizer::{TagKind, Token as Token2, TokenSinkResult, TokenizerOpts};
use html5gum::{Emitter, Reader, Token};
use pretty_assertions::assert_eq;
@@ -85,7 +84,7 @@ impl> TokenSinkInner {
}
(None, Token2::EOFToken) => {}
(Some(Token::Comment(comment)), Token2::CommentToken(comment2)) => {
- assert_eq!(comment, comment2.as_ref().as_bytes().to_owned());
+ assert_eq!(*comment, comment2.as_ref().as_bytes().to_owned());
}
(Some(Token::Doctype(doctype)), Token2::DoctypeToken(doctype2)) => {
assert_eq!(
diff --git a/fuzz/src/testcase/lolhtml.rs b/fuzz/src/testcase/lolhtml.rs
index d705a12..85412e9 100644
--- a/fuzz/src/testcase/lolhtml.rs
+++ b/fuzz/src/testcase/lolhtml.rs
@@ -114,20 +114,25 @@ impl<'a> TransformController for TestTransformController<'a> {
self_closing: t.self_closing(),
// TODO
attributes: Default::default(),
+ ..Default::default()
}));
}
Token2::EndTag(t) => {
self.testing_tokenizer.push(Token::EndTag(EndTag {
name: t.name().into_bytes().into(),
+ ..Default::default()
}));
}
Token2::Doctype(d) => {
- self.testing_tokenizer.push(Token::Doctype(Doctype {
- force_quirks: d.force_quirks(),
- name: d.name().unwrap_or_default().into_bytes().into(),
- public_identifier: d.public_id().map(|x| x.into_bytes().into()),
- system_identifier: d.system_id().map(|x| x.into_bytes().into()),
- }));
+ self.testing_tokenizer.push(Token::Doctype(
+ Doctype {
+ force_quirks: d.force_quirks(),
+ name: d.name().unwrap_or_default().into_bytes().into(),
+ public_identifier: d.public_id().map(|x| x.into_bytes().into()),
+ system_identifier: d.system_id().map(|x| x.into_bytes().into()),
+ }
+ .into(),
+ ));
}
}
diff --git a/fuzz/src/testcase/mod.rs b/fuzz/src/testcase/mod.rs
index 91693e8..c9b36ae 100644
--- a/fuzz/src/testcase/mod.rs
+++ b/fuzz/src/testcase/mod.rs
@@ -3,6 +3,7 @@ use std::env;
mod html5ever;
mod lolhtml;
mod old_html5gum;
+mod span_invariants;
mod swc;
pub fn run(s: &[u8]) {
@@ -41,6 +42,11 @@ pub fn run(s: &[u8]) {
did_anything = true;
}
+ if env::var("FUZZ_SPAN_INVARIANTS").unwrap() == "1" {
+ span_invariants::validate_span_invariants(s);
+ did_anything = true;
+ }
+
if !did_anything {
panic!("running empty testcase, enable either FUZZ_OLD_HTML5GUM or FUZZ_HTML5EVER");
}
diff --git a/fuzz/src/testcase/old_html5gum.rs b/fuzz/src/testcase/old_html5gum.rs
index 148991a..22dec06 100644
--- a/fuzz/src/testcase/old_html5gum.rs
+++ b/fuzz/src/testcase/old_html5gum.rs
@@ -45,7 +45,10 @@ pub fn run_old_html5gum(s: &str) {
}
x if x.starts_with("if-testing-contains:") => {
if testing_tokens.contains(&html5gum::Token::Error(
- x["if-testing-contains:".len()..].parse().unwrap(),
+ x["if-testing-contains:".len()..]
+ .parse::()
+ .unwrap()
+ .into(),
)) {
reference_tokens.retain(isnt_old_error);
testing_tokens.retain(isnt_error);
@@ -68,17 +71,24 @@ pub fn run_old_html5gum(s: &str) {
.map(|(k, v)| (Vec::from(k).into(), Vec::from(v).into()))
.collect(),
self_closing: x.self_closing,
+ ..Default::default()
}),
html5gum_old::Token::EndTag(x) => Token::EndTag(EndTag {
name: Vec::from(x.name).into(),
+ ..Default::default()
}),
- html5gum_old::Token::Error(x) => Token::Error(x.to_string().parse().unwrap()),
- html5gum_old::Token::Doctype(x) => Token::Doctype(Doctype {
- name: Vec::from(x.name).into(),
- force_quirks: x.force_quirks,
- public_identifier: x.public_identifier.map(|x| Vec::from(x).into()),
- system_identifier: x.system_identifier.map(|x| Vec::from(x).into()),
- }),
+ html5gum_old::Token::Error(x) => {
+ Token::Error(x.to_string().parse::().unwrap().into())
+ }
+ html5gum_old::Token::Doctype(x) => Token::Doctype(
+ Doctype {
+ name: Vec::from(x.name).into(),
+ force_quirks: x.force_quirks,
+ public_identifier: x.public_identifier.map(|x| Vec::from(x).into()),
+ system_identifier: x.system_identifier.map(|x| Vec::from(x).into()),
+ }
+ .into(),
+ ),
})
.collect();
diff --git a/fuzz/src/testcase/span_invariants.rs b/fuzz/src/testcase/span_invariants.rs
new file mode 100644
index 0000000..d35bb90
--- /dev/null
+++ b/fuzz/src/testcase/span_invariants.rs
@@ -0,0 +1,223 @@
+use html5gum::{DefaultEmitter, Token, Tokenizer};
+
+/// Validates span invariants for all tokens produced from the input.
+///
+/// This fuzzer checks that:
+/// 1. Spans have valid bounds (start <= end <= input.len())
+/// 2. Spans point to correct content in the input
+/// 3. Spans are non-overlapping and ordered
+/// 4. Spans are non-empty for structural tokens (tags, comments, doctypes)
+///
+/// This would have caught the bug fixed in commit 505de5b where end tag positions
+/// were incorrectly tracked in naive state switching mode.
+pub fn validate_span_invariants(input: &[u8]) {
+ // Use DefaultEmitter with span tracking enabled
+ let mut emitter = DefaultEmitter::::new_with_span();
+ // Enable naive state switching to test both modes
+ emitter.naively_switch_states(true);
+
+ let tokenizer = Tokenizer::new_with_emitter(input, emitter);
+
+ let mut last_end: Option = None;
+
+ for result in tokenizer {
+ let token = match result {
+ Ok(token) => token,
+ Err(_) => continue, // Errors are expected, we're fuzzing
+ };
+
+ validate_token_span(&token, input, &mut last_end);
+ }
+}
+
+/// Validates the span of a single token against the input.
+fn validate_token_span(token: &Token, input: &[u8], last_end: &mut Option) {
+ match token {
+ Token::StartTag(tag) => {
+ validate_span(&tag.span, input, "StartTag", last_end);
+
+ // Start tags must have non-empty spans
+ assert!(
+ tag.span.start < tag.span.end,
+ "StartTag has empty span: {}..{}",
+ tag.span.start,
+ tag.span.end
+ );
+
+ // Verify the span actually contains the tag
+ if tag.span.end <= input.len() {
+ let content = &input[tag.span.start..tag.span.end];
+ // Start tags should begin with '<' and contain the tag name
+ assert!(
+ content.starts_with(b"<"),
+ "StartTag span does not start with '<': {:?} at {}..{}",
+ String::from_utf8_lossy(content),
+ tag.span.start,
+ tag.span.end
+ );
+ // The tag name should appear in the content
+ assert!(
+ content
+ .windows(tag.name.len())
+ .any(|window| window == &tag.name[..]),
+ "StartTag span does not contain tag name '{}': {:?} at {}..{}",
+ String::from_utf8_lossy(&tag.name),
+ String::from_utf8_lossy(content),
+ tag.span.start,
+ tag.span.end
+ );
+ }
+
+ // Validate attribute value spans
+ for (_attr_name, attr_value) in &tag.attributes {
+ validate_span(&attr_value.span, input, "Attribute value", &mut None);
+
+ // Note: Attribute value spans may include the entire attribute declaration
+ // (name="value") or just the value depending on implementation.
+ // We just validate basic span invariants here.
+ }
+ }
+ Token::EndTag(tag) => {
+ validate_span(&tag.span, input, "EndTag", last_end);
+
+ // End tags must have non-empty spans
+ assert!(
+ tag.span.start < tag.span.end,
+ "EndTag has empty span: {}..{} for tag '{}'",
+ tag.span.start,
+ tag.span.end,
+ String::from_utf8_lossy(&tag.name)
+ );
+
+ // Verify the span actually contains the end tag
+ if tag.span.end <= input.len() {
+ let content = &input[tag.span.start..tag.span.end];
+ // End tags should start with ''
+ assert!(
+ content.starts_with(b""),
+ "EndTag span does not start with '': {:?} at {}..{} for tag '{}'",
+ String::from_utf8_lossy(content),
+ tag.span.start,
+ tag.span.end,
+ String::from_utf8_lossy(&tag.name)
+ );
+ // The tag name should appear in the content
+ assert!(
+ content
+ .windows(tag.name.len())
+ .any(|window| window == &tag.name[..]),
+ "EndTag span does not contain tag name '{}': {:?} at {}..{}",
+ String::from_utf8_lossy(&tag.name),
+ String::from_utf8_lossy(content),
+ tag.span.start,
+ tag.span.end
+ );
+ }
+ }
+ Token::String(s) => {
+ validate_span(&s.span, input, "String", last_end);
+
+ // Note: String token values may differ from raw span content due to
+ // HTML entity decoding or character reference processing.
+ // The key invariant is that the span points to valid input bounds.
+ // Strings can have empty spans (e.g., empty text nodes).
+ }
+ Token::Comment(c) => {
+ validate_span(&c.span, input, "Comment", last_end);
+
+ // Comments must have non-empty spans
+ assert!(
+ c.span.start < c.span.end,
+ "Comment has empty span: {}..{}",
+ c.span.start,
+ c.span.end
+ );
+
+ // Verify comment span contains the comment markers and content
+ if c.span.end <= input.len() {
+ let content = &input[c.span.start..c.span.end];
+ // Comments should start with ' {
+ validate_span(&d.span, input, "Doctype", last_end);
+
+ // Doctypes must have non-empty spans
+ assert!(
+ d.span.start < d.span.end,
+ "Doctype has empty span: {}..{}",
+ d.span.start,
+ d.span.end
+ );
+
+ // Verify doctype span starts with ' {
+ validate_span(&e.span, input, "Error", last_end);
+ // Errors can have empty spans (they may point to a position rather than a range)
+ }
+ }
+}
+
+/// Validates basic span invariants.
+fn validate_span(
+ span: &html5gum::Span,
+ input: &[u8],
+ token_type: &str,
+ last_end: &mut Option,
+) {
+ // Invariant 1: start <= end
+ assert!(
+ span.start <= span.end,
+ "{} span has start > end: {}..{}",
+ token_type,
+ span.start,
+ span.end
+ );
+
+ // Invariant 2: end <= input.len()
+ assert!(
+ span.end <= input.len(),
+ "{} span exceeds input bounds: {}..{} (input len: {})",
+ token_type,
+ span.start,
+ span.end,
+ input.len()
+ );
+
+ // Invariant 3: Spans should be ordered (non-decreasing start positions)
+ // However, error tokens can be interleaved and may have empty spans pointing to
+ // positions within other tokens, so we only enforce ordering for non-empty spans
+ if span.start < span.end {
+ // Only check ordering for non-empty spans
+ if let Some(prev_end) = last_end {
+ assert!(
+ span.start >= *prev_end,
+ "{} span starts before previous span ended: current {}..{}, previous ended at {}",
+ token_type,
+ span.start,
+ span.end,
+ prev_end
+ );
+ }
+ // Update last_end only for non-empty spans
+ *last_end = Some(span.end);
+ }
+}
diff --git a/fuzz/src/testcase/swc.rs b/fuzz/src/testcase/swc.rs
index 5479e3c..8368168 100644
--- a/fuzz/src/testcase/swc.rs
+++ b/fuzz/src/testcase/swc.rs
@@ -33,12 +33,15 @@ pub fn run_swc(s: &str) {
system_id,
..
} => {
- transformed_swc_tokens.push(html5gum::Token::Doctype(html5gum::Doctype {
- name: name.unwrap_or_default().to_string().into_bytes().into(),
- public_identifier: public_id.map(|x| x.to_string().into_bytes().into()),
- system_identifier: system_id.map(|x| x.to_string().into_bytes().into()),
- force_quirks,
- }));
+ transformed_swc_tokens.push(html5gum::Token::Doctype(
+ html5gum::Doctype {
+ name: name.unwrap_or_default().to_string().into_bytes().into(),
+ public_identifier: public_id.map(|x| x.to_string().into_bytes().into()),
+ system_identifier: system_id.map(|x| x.to_string().into_bytes().into()),
+ force_quirks,
+ }
+ .into(),
+ ));
}
Token::StartTag {
tag_name,
@@ -66,11 +69,13 @@ pub fn run_swc(s: &str) {
gum_attributes
},
+ ..Default::default()
}));
}
Token::EndTag { tag_name, .. } => {
transformed_swc_tokens.push(html5gum::Token::EndTag(html5gum::EndTag {
name: tag_name.to_string().into_bytes().into(),
+ ..Default::default()
}));
}
Token::Comment { data, .. } => {
diff --git a/src/emitters/default.rs b/src/emitters/default.rs
index f3f8547..33fcb95 100644
--- a/src/emitters/default.rs
+++ b/src/emitters/default.rs
@@ -188,7 +188,7 @@ pub struct Doctype {
/// The token type used by default. You can define your own token type by implementing the
/// [`crate::Emitter`] trait and using [`crate::Tokenizer::new_with_emitter`].
#[derive(Debug, PartialEq, Eq, Clone)]
-pub enum Token {
+pub enum Token {
/// A HTML start tag.
StartTag(StartTag),
/// A HTML end tag.
diff --git a/src/span.rs b/src/span.rs
index ee86345..369c453 100644
--- a/src/span.rs
+++ b/src/span.rs
@@ -48,6 +48,16 @@ impl SpanBound for usize {
}
/// A value together with its [`Span`].
+///
+/// This type implements [`Deref`](std::ops::Deref) and [`DerefMut`](std::ops::DerefMut),
+/// allowing you to access the inner value directly without using `.value`:
+///
+/// ```
+/// # use html5gum::Spanned;
+/// let spanned: Spanned = "hello".to_string().into();
+/// assert_eq!(spanned.len(), 5); // calls String::len() via Deref
+/// assert_eq!(&*spanned, "hello"); // dereference to get &String
+/// ```
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Spanned {
@@ -55,6 +65,24 @@ pub struct Spanned {
pub span: Span,
}
+impl From for Spanned {
+ fn from(value: T) -> Self {
+ Self {
+ value,
+ span: Span::default(),
+ }
+ }
+}
+
+impl From> for Spanned {
+ fn from(value: Vec) -> Self {
+ Self {
+ value: value.into(),
+ span: Span::default(),
+ }
+ }
+}
+
impl std::ops::Deref for Spanned {
type Target = T;