diff --git a/crates/mdbook-html/src/html/hide_lines.rs b/crates/mdbook-html/src/html/hide_lines.rs index 8dbe7b12c5..5413de3901 100644 --- a/crates/mdbook-html/src/html/hide_lines.rs +++ b/crates/mdbook-html/src/html/hide_lines.rs @@ -132,7 +132,7 @@ fn partition_rust_source(s: &str) -> (&str, &str) { r"^(?mx) ( (?: - ^[ \t]*\#!\[.* (?:\r?\n)? + ^[ \t]*(?:\#\x20)?[ \t]*\#!\[.* (?:\r?\n)? | ^\s* (?:\r?\n)? )* @@ -155,6 +155,36 @@ fn partition_rust_source(s: &str) -> (&str, &str) { s.split_at(split_idx) } +#[test] +fn wrap_rust_main_basic() { + // Code without fn main gets wrapped + assert_eq!( + wrap_rust_main("let x = 1;"), + Some("# #![allow(unused)]\n# fn main() {\nlet x = 1;\n# }".to_string()) + ); +} + +#[test] +fn wrap_rust_main_with_feature_attr() { + // Crate-level attributes should be placed before fn main (issue #2640) + // Without hidden prefix: + assert_eq!( + wrap_rust_main("#![feature(rustc_attrs)]\n#[rustc_on_unimplemented = \"oh no\"]\npub trait Foo {}"), + Some("# #![allow(unused)]\n#![feature(rustc_attrs)]\n# fn main() {\n#[rustc_on_unimplemented = \"oh no\"]\npub trait Foo {}\n# }".to_string()) + ); + // With hidden line prefix `# ` (the actual input from markdown): + assert_eq!( + wrap_rust_main("# #![feature(rustc_attrs)]\n#[rustc_on_unimplemented = \"oh no\"]\npub trait Foo {}"), + Some("# #![allow(unused)]\n# #![feature(rustc_attrs)]\n# fn main() {\n#[rustc_on_unimplemented = \"oh no\"]\npub trait Foo {}\n# }".to_string()) + ); +} + +#[test] +fn wrap_rust_main_already_has_main() { + // Code with fn main should not be wrapped + assert_eq!(wrap_rust_main("fn main() {}"), None); +} + #[test] fn it_partitions_rust_source() { assert_eq!(partition_rust_source(""), ("", "")); @@ -190,4 +220,9 @@ fn it_partitions_rust_source() { partition_rust_source(" // Example"), ("", " // Example") ); + // Hidden line prefix with crate attribute (issue #2640) + assert_eq!( + partition_rust_source("# #![feature(rustc_attrs)]\n#[foo]\npub trait Foo {}"), + ("# #![feature(rustc_attrs)]\n", "#[foo]\npub trait Foo {}") + ); } diff --git a/tests/testsuite/playground.rs b/tests/testsuite/playground.rs index d0aeb69981..973e41c0fc 100644 --- a/tests/testsuite/playground.rs +++ b/tests/testsuite/playground.rs @@ -17,6 +17,25 @@ fn playground_on_rust_code() { ); } +// Verifies that hidden crate-level attributes (like #![feature(...)]) are +// properly wrapped when fn main is generated. The attributes should appear +// before fn main, not inside it. (issue #2640) +#[test] +fn playground_hidden_feature_attr() { + BookTest::from_dir("playground/playground_hidden_feature_attr").check_main_file( + "book/feature-attr.html", + str![[r##" +
#![allow(unused)]
+#![feature(rustc_attrs)]
+fn main() {
+#[rustc_on_unimplemented = "oh no"]
+pub trait Foo {}
+}
+"##]],
+ );
+}
+
// When the playground is disabled, there should be no playground class.
#[test]
fn disabled_playground() {
diff --git a/tests/testsuite/playground/playground_hidden_feature_attr/book.toml b/tests/testsuite/playground/playground_hidden_feature_attr/book.toml
new file mode 100644
index 0000000000..7bf7c972c6
--- /dev/null
+++ b/tests/testsuite/playground/playground_hidden_feature_attr/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "playground_on_rust_code"
diff --git a/tests/testsuite/playground/playground_hidden_feature_attr/src/SUMMARY.md b/tests/testsuite/playground/playground_hidden_feature_attr/src/SUMMARY.md
new file mode 100644
index 0000000000..0e6f4e3ddb
--- /dev/null
+++ b/tests/testsuite/playground/playground_hidden_feature_attr/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+[Hidden Feature Attr](feature-attr.md)
diff --git a/tests/testsuite/playground/playground_hidden_feature_attr/src/feature-attr.md b/tests/testsuite/playground/playground_hidden_feature_attr/src/feature-attr.md
new file mode 100644
index 0000000000..bf995ef55d
--- /dev/null
+++ b/tests/testsuite/playground/playground_hidden_feature_attr/src/feature-attr.md
@@ -0,0 +1,7 @@
+# Feature Attr
+
+```rust
+# #![feature(rustc_attrs)]
+#[rustc_on_unimplemented = "oh no"]
+pub trait Foo {}
+```