Reject a REGISTERED directive name embedded mid-prose with an attrs brace (e.g. `… ::file-ref{path=x} …` inside a sentence). The block parser only reads directives at line start, so an embedded one would silently stay prose and its edge would be lost. Unregistered names stay prose (the inline rule); `std::fs` and `foo::ref{...}` never match — the colon run must not follow an identifier character.
(text: &str)
| 213 | /// prose (the inline rule); `std::fs` and `foo::ref{...}` never match — the |
| 214 | /// colon run must not follow an identifier character. |
| 215 | pub fn check_embedded_directives(text: &str) -> Result<()> { |
| 216 | let mut in_fence = false; |
| 217 | for raw_line in text.lines() { |
| 218 | let line = strip_html_comments(raw_line); |
| 219 | let t = line.trim_start(); |
| 220 | if t.starts_with("```") { |
| 221 | in_fence = !in_fence; |
| 222 | continue; |
| 223 | } |
| 224 | // Lines the block parser handles itself (directive opens/closes). |
| 225 | if in_fence || t.starts_with(':') { |
| 226 | continue; |
| 227 | } |
| 228 | let bytes = line.as_bytes(); |
| 229 | let mut i = 0; |
| 230 | while i < bytes.len() { |
| 231 | if bytes[i] != b':' { |
| 232 | i += 1; |
| 233 | continue; |
| 234 | } |
| 235 | let start = i; |
| 236 | while i < bytes.len() && bytes[i] == b':' { |
| 237 | i += 1; |
| 238 | } |
| 239 | let run = i - start; |
| 240 | if !(2..=3).contains(&run) { |
| 241 | continue; |
| 242 | } |
| 243 | if start > 0 { |
| 244 | let prev = bytes[start - 1]; |
| 245 | if prev.is_ascii_alphanumeric() || prev == b'_' { |
| 246 | continue; |
| 247 | } |
| 248 | } |
| 249 | let name_start = i; |
| 250 | while i < bytes.len() |
| 251 | && (bytes[i].is_ascii_lowercase() || bytes[i].is_ascii_digit() || bytes[i] == b'-') |
| 252 | { |
| 253 | i += 1; |
| 254 | } |
| 255 | if i == name_start || i >= bytes.len() || bytes[i] != b'{' { |
| 256 | continue; |
| 257 | } |
| 258 | let name = &line[name_start..i]; |
| 259 | if vocab::is_known_directive(name) { |
| 260 | return Err(CanonicalError::Directive(format!( |
| 261 | "directive '{}{name}' is embedded in prose — block \ |
| 262 | directives must start their own line", |
| 263 | ":".repeat(run) |
| 264 | ))); |
| 265 | } |
| 266 | } |
| 267 | } |
| 268 | Ok(()) |
| 269 | } |
| 270 | |
| 271 | /// Drop `<!-- … -->` spans so template guidance comments never trip the |
| 272 | /// embedded-directive check. |