1
Fork 0

Add support for double quotes in markdown codeblock attributes

This commit is contained in:
Guillaume Gomez 2023-04-27 15:09:43 +02:00
parent d829fee6b5
commit 4ce17fa30e
5 changed files with 145 additions and 49 deletions

View file

@ -654,3 +654,14 @@ pub struct Bar;
To be noted, `rust` and `.rust`/`class=rust` have different effects: `rust` indicates that this is To be noted, `rust` and `.rust`/`class=rust` have different effects: `rust` indicates that this is
a Rust code block whereas the two others add a "rust" CSS class on the code block. a Rust code block whereas the two others add a "rust" CSS class on the code block.
You can also use double quotes:
```rust
#![feature(custom_code_classes_in_docs)]
/// ```"not rust" {."hello everyone"}
/// int main(void) { return 0; }
/// ```
pub struct Bar;
```

View file

@ -892,19 +892,19 @@ impl<'a, 'tcx> TagIterator<'a, 'tcx> {
extra.error_invalid_codeblock_attr(err); extra.error_invalid_codeblock_attr(err);
} }
} }
}
impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> { /// Returns false if the string is unfinished.
type Item = TokenKind<'a>; fn skip_string(&mut self) -> bool {
while let Some((_, c)) = self.inner.next() {
fn next(&mut self) -> Option<Self::Item> { if c == '"' {
let Some(start) = self.skip_separators() else { return true;
if self.is_in_attribute_block {
self.emit_error("unclosed attribute block (`{}`): missing `}` at the end");
} }
return None; }
}; self.emit_error("unclosed quote string: missing `\"` at the end");
if self.is_in_attribute_block { false
}
fn parse_in_attribute_block(&mut self, start: usize) -> Option<TokenKind<'a>> {
while let Some((pos, c)) = self.inner.next() { while let Some((pos, c)) = self.inner.next() {
if is_separator(c) { if is_separator(c) {
return Some(TokenKind::Attribute(&self.data[start..pos])); return Some(TokenKind::Attribute(&self.data[start..pos]));
@ -924,13 +924,17 @@ impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> {
return self.next(); return self.next();
} }
return Some(TokenKind::Attribute(attr)); return Some(TokenKind::Attribute(attr));
} else if c == '"' && !self.skip_string() {
return None;
} }
} }
// Unclosed attribute block! // Unclosed attribute block!
self.emit_error("unclosed attribute block (`{}`): missing `}` at the end"); self.emit_error("unclosed attribute block (`{}`): missing `}` at the end");
let token = &self.data[start..]; let token = &self.data[start..];
if token.is_empty() { None } else { Some(TokenKind::Attribute(token)) } if token.is_empty() { None } else { Some(TokenKind::Attribute(token)) }
} else { }
fn parse_outside_attribute_block(&mut self, start: usize) -> Option<TokenKind<'a>> {
while let Some((pos, c)) = self.inner.next() { while let Some((pos, c)) = self.inner.next() {
if is_separator(c) { if is_separator(c) {
return Some(TokenKind::Token(&self.data[start..pos])); return Some(TokenKind::Token(&self.data[start..pos]));
@ -950,11 +954,30 @@ impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> {
} }
self.inner.next(); self.inner.next();
return Some(TokenKind::Attribute(token)); return Some(TokenKind::Attribute(token));
} else if c == '"' && !self.skip_string() {
return None;
} }
} }
let token = &self.data[start..]; let token = &self.data[start..];
if token.is_empty() { None } else { Some(TokenKind::Token(token)) } if token.is_empty() { None } else { Some(TokenKind::Token(token)) }
} }
}
impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> {
type Item = TokenKind<'a>;
fn next(&mut self) -> Option<Self::Item> {
let Some(start) = self.skip_separators() else {
if self.is_in_attribute_block {
self.emit_error("unclosed attribute block (`{}`): missing `}` at the end");
}
return None;
};
if self.is_in_attribute_block {
self.parse_in_attribute_block(start)
} else {
self.parse_outside_attribute_block(start)
}
} }
} }
@ -982,7 +1005,7 @@ fn handle_class(class: &str, after: &str, data: &mut LangString, extra: Option<&
extra.error_invalid_codeblock_attr(&format!("missing class name after `{after}`")); extra.error_invalid_codeblock_attr(&format!("missing class name after `{after}`"));
} }
} else { } else {
data.added_classes.push(class.to_owned()); data.added_classes.push(class.replace('"', ""));
} }
} }

View file

@ -218,6 +218,18 @@ fn test_lang_string_parse() {
rust: false, rust: false,
..Default::default() ..Default::default()
}); });
t(LangString {
original: r#"{class="first"}"#.into(),
added_classes: vec!["first".into()],
rust: false,
..Default::default()
});
t(LangString {
original: r#"{class=f"irst"}"#.into(),
added_classes: vec!["first".into()],
rust: false,
..Default::default()
});
} }
#[test] #[test]

View file

@ -0,0 +1,17 @@
// This test ensures that warnings are working as expected for "custom_code_classes_in_docs"
// feature.
#![feature(custom_code_classes_in_docs)]
#![deny(warnings)]
#![feature(no_core)]
#![no_core]
/// ```{class="}
/// main;
/// ```
//~^^^ ERROR unclosed quote string
//~| ERROR unclosed quote string
/// ```"
/// main;
/// ```
pub fn foo() {}

View file

@ -0,0 +1,33 @@
error: unclosed quote string: missing `"` at the end
--> $DIR/custom_code_classes_in_docs-warning3.rs:9:1
|
LL | / /// ```{class="}
LL | | /// main;
LL | | /// ```
LL | |
... |
LL | | /// main;
LL | | /// ```
| |_______^
|
note: the lint level is defined here
--> $DIR/custom_code_classes_in_docs-warning3.rs:5:9
|
LL | #![deny(warnings)]
| ^^^^^^^^
= note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]`
error: unclosed quote string: missing `"` at the end
--> $DIR/custom_code_classes_in_docs-warning3.rs:9:1
|
LL | / /// ```{class="}
LL | | /// main;
LL | | /// ```
LL | |
... |
LL | | /// main;
LL | | /// ```
| |_______^
error: aborting due to 2 previous errors