about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume.gomez@huawei.com>2023-04-27 15:09:43 +0200
committerGuillaume Gomez <guillaume.gomez@huawei.com>2023-09-15 21:32:28 +0200
commit4ce17fa30eeef32235e9b305f56b5651c6d35276 (patch)
tree952b21a15056c6032fc423d0362fcd3f72898daf
parentd829fee6b5859de516dadaaba30db758bb567268 (diff)
downloadrust-4ce17fa30eeef32235e9b305f56b5651c6d35276.tar.gz
rust-4ce17fa30eeef32235e9b305f56b5651c6d35276.zip
Add support for double quotes in markdown codeblock attributes
-rw-r--r--src/doc/rustdoc/src/unstable-features.md11
-rw-r--r--src/librustdoc/html/markdown.rs121
-rw-r--r--src/librustdoc/html/markdown/tests.rs12
-rw-r--r--tests/rustdoc-ui/custom_code_classes_in_docs-warning3.rs17
-rw-r--r--tests/rustdoc-ui/custom_code_classes_in_docs-warning3.stderr33
5 files changed, 145 insertions, 49 deletions
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index bb62a0bc9cc..b5d8c819418 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -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;
+```
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index a25a6f7d35d..6bd4e775c0e 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -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('"', ""));
     }
 }
 
diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs
index dd3d0ebac0c..b0b4de65cca 100644
--- a/src/librustdoc/html/markdown/tests.rs
+++ b/src/librustdoc/html/markdown/tests.rs
@@ -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]
diff --git a/tests/rustdoc-ui/custom_code_classes_in_docs-warning3.rs b/tests/rustdoc-ui/custom_code_classes_in_docs-warning3.rs
new file mode 100644
index 00000000000..57d9038cb0c
--- /dev/null
+++ b/tests/rustdoc-ui/custom_code_classes_in_docs-warning3.rs
@@ -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() {}
diff --git a/tests/rustdoc-ui/custom_code_classes_in_docs-warning3.stderr b/tests/rustdoc-ui/custom_code_classes_in_docs-warning3.stderr
new file mode 100644
index 00000000000..7432af19360
--- /dev/null
+++ b/tests/rustdoc-ui/custom_code_classes_in_docs-warning3.stderr
@@ -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
+