mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 14:55:26 +00:00
Add support for double quotes in markdown codeblock attributes
This commit is contained in:
parent
d829fee6b5
commit
4ce17fa30e
@ -654,3 +654,14 @@ pub struct Bar;
|
||||
|
||||
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.
|
||||
|
||||
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;
|
||||
```
|
||||
|
@ -892,6 +892,75 @@ impl<'a, 'tcx> TagIterator<'a, 'tcx> {
|
||||
extra.error_invalid_codeblock_attr(err);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns false if the string is unfinished.
|
||||
fn skip_string(&mut self) -> bool {
|
||||
while let Some((_, c)) = self.inner.next() {
|
||||
if c == '"' {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
self.emit_error("unclosed quote string: missing `\"` at the end");
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_in_attribute_block(&mut self, start: usize) -> Option<TokenKind<'a>> {
|
||||
while let Some((pos, c)) = self.inner.next() {
|
||||
if is_separator(c) {
|
||||
return Some(TokenKind::Attribute(&self.data[start..pos]));
|
||||
} else if c == '{' {
|
||||
// There shouldn't be a nested block!
|
||||
self.emit_error("unexpected `{` inside attribute block (`{}`)");
|
||||
let attr = &self.data[start..pos];
|
||||
if attr.is_empty() {
|
||||
return self.next();
|
||||
}
|
||||
self.inner.next();
|
||||
return Some(TokenKind::Attribute(attr));
|
||||
} else if c == '}' {
|
||||
self.is_in_attribute_block = false;
|
||||
let attr = &self.data[start..pos];
|
||||
if attr.is_empty() {
|
||||
return self.next();
|
||||
}
|
||||
return Some(TokenKind::Attribute(attr));
|
||||
} else if c == '"' && !self.skip_string() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// Unclosed attribute block!
|
||||
self.emit_error("unclosed attribute block (`{}`): missing `}` at the end");
|
||||
let token = &self.data[start..];
|
||||
if token.is_empty() { None } else { Some(TokenKind::Attribute(token)) }
|
||||
}
|
||||
|
||||
fn parse_outside_attribute_block(&mut self, start: usize) -> Option<TokenKind<'a>> {
|
||||
while let Some((pos, c)) = self.inner.next() {
|
||||
if is_separator(c) {
|
||||
return Some(TokenKind::Token(&self.data[start..pos]));
|
||||
} else if c == '{' {
|
||||
self.is_in_attribute_block = true;
|
||||
let token = &self.data[start..pos];
|
||||
if token.is_empty() {
|
||||
return self.next();
|
||||
}
|
||||
return Some(TokenKind::Token(token));
|
||||
} else if c == '}' {
|
||||
// We're not in a block so it shouldn't be there!
|
||||
self.emit_error("unexpected `}` outside attribute block (`{}`)");
|
||||
let token = &self.data[start..pos];
|
||||
if token.is_empty() {
|
||||
return self.next();
|
||||
}
|
||||
self.inner.next();
|
||||
return Some(TokenKind::Attribute(token));
|
||||
} else if c == '"' && !self.skip_string() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
let token = &self.data[start..];
|
||||
if token.is_empty() { None } else { Some(TokenKind::Token(token)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> {
|
||||
@ -905,55 +974,9 @@ impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> {
|
||||
return None;
|
||||
};
|
||||
if self.is_in_attribute_block {
|
||||
while let Some((pos, c)) = self.inner.next() {
|
||||
if is_separator(c) {
|
||||
return Some(TokenKind::Attribute(&self.data[start..pos]));
|
||||
} else if c == '{' {
|
||||
// There shouldn't be a nested block!
|
||||
self.emit_error("unexpected `{` inside attribute block (`{}`)");
|
||||
let attr = &self.data[start..pos];
|
||||
if attr.is_empty() {
|
||||
return self.next();
|
||||
}
|
||||
self.inner.next();
|
||||
return Some(TokenKind::Attribute(attr));
|
||||
} else if c == '}' {
|
||||
self.is_in_attribute_block = false;
|
||||
let attr = &self.data[start..pos];
|
||||
if attr.is_empty() {
|
||||
return self.next();
|
||||
}
|
||||
return Some(TokenKind::Attribute(attr));
|
||||
}
|
||||
}
|
||||
// Unclosed attribute block!
|
||||
self.emit_error("unclosed attribute block (`{}`): missing `}` at the end");
|
||||
let token = &self.data[start..];
|
||||
if token.is_empty() { None } else { Some(TokenKind::Attribute(token)) }
|
||||
self.parse_in_attribute_block(start)
|
||||
} else {
|
||||
while let Some((pos, c)) = self.inner.next() {
|
||||
if is_separator(c) {
|
||||
return Some(TokenKind::Token(&self.data[start..pos]));
|
||||
} else if c == '{' {
|
||||
self.is_in_attribute_block = true;
|
||||
let token = &self.data[start..pos];
|
||||
if token.is_empty() {
|
||||
return self.next();
|
||||
}
|
||||
return Some(TokenKind::Token(token));
|
||||
} else if c == '}' {
|
||||
// We're not in a block so it shouldn't be there!
|
||||
self.emit_error("unexpected `}` outside attribute block (`{}`)");
|
||||
let token = &self.data[start..pos];
|
||||
if token.is_empty() {
|
||||
return self.next();
|
||||
}
|
||||
self.inner.next();
|
||||
return Some(TokenKind::Attribute(token));
|
||||
}
|
||||
}
|
||||
let token = &self.data[start..];
|
||||
if token.is_empty() { None } else { Some(TokenKind::Token(token)) }
|
||||
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}`"));
|
||||
}
|
||||
} else {
|
||||
data.added_classes.push(class.to_owned());
|
||||
data.added_classes.push(class.replace('"', ""));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,6 +218,18 @@ fn test_lang_string_parse() {
|
||||
rust: false,
|
||||
..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]
|
||||
|
17
tests/rustdoc-ui/custom_code_classes_in_docs-warning3.rs
Normal file
17
tests/rustdoc-ui/custom_code_classes_in_docs-warning3.rs
Normal 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() {}
|
33
tests/rustdoc-ui/custom_code_classes_in_docs-warning3.stderr
Normal file
33
tests/rustdoc-ui/custom_code_classes_in_docs-warning3.stderr
Normal 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
|
||||
|
Loading…
Reference in New Issue
Block a user