Przeglądaj źródła

fix autofmt: don't panic when writing blocks out without a srcfile (#2854)

* fix: don't panic when writing blocks out
* also fix serialization for hotreload
* fix windows line endings
Jonathan Kelley 10 miesięcy temu
rodzic
commit
4963aa3118

+ 1 - 1
packages/autofmt/src/lib.rs

@@ -93,7 +93,7 @@ pub fn try_fmt_file(
         writer.out.indent_level = writer
             .out
             .indent
-            .count_indents(writer.src[rsx_start.line - 1]);
+            .count_indents(writer.src.get(rsx_start.line - 1).unwrap_or(&""));
 
         // TESTME
         // Writing *should* not fail but it's possible that it does

+ 21 - 9
packages/autofmt/src/writer.rs

@@ -607,7 +607,11 @@ impl<'a> Writer<'a> {
 
         let mut comments = VecDeque::new();
 
-        for (id, line) in self.src[..line_start].iter().enumerate().rev() {
+        let Some(lines) = self.src.get(..line_start) else {
+            return comments;
+        };
+
+        for (id, line) in lines.iter().enumerate().rev() {
             if line.trim().starts_with("//") || line.is_empty() && id != 0 {
                 if id != 0 {
                     comments.push_front(id);
@@ -621,7 +625,11 @@ impl<'a> Writer<'a> {
     }
     fn apply_comments(&mut self, mut comments: VecDeque<usize>) -> Result {
         while let Some(comment_line) = comments.pop_front() {
-            let line = &self.src[comment_line].trim();
+            let Some(line) = self.src.get(comment_line) else {
+                continue;
+            };
+
+            let line = &line.trim();
 
             if line.is_empty() {
                 self.out.new_line()?;
@@ -690,13 +698,15 @@ impl<'a> Writer<'a> {
 
         for attr in attributes {
             if self.current_span_is_primary(attr.span().start()) {
-                'line: for line in self.src[..attr.span().start().line - 1].iter().rev() {
-                    match (line.trim().starts_with("//"), line.is_empty()) {
-                        (true, _) => return 100000,
-                        (_, true) => continue 'line,
-                        _ => break 'line,
+                if let Some(lines) = self.src.get(..attr.span().start().line - 1) {
+                    'line: for line in lines.iter().rev() {
+                        match (line.trim().starts_with("//"), line.is_empty()) {
+                            (true, _) => return 100000,
+                            (_, true) => continue 'line,
+                            _ => break 'line,
+                        }
                     }
-                }
+                };
             }
 
             let name_len = match &attr.name {
@@ -738,7 +748,9 @@ impl<'a> Writer<'a> {
         writeln!(self.out)?;
 
         for idx in start.line..end.line {
-            let line = &self.src[idx];
+            let Some(line) = self.src.get(idx) else {
+                continue;
+            };
             if line.trim().starts_with("//") {
                 for _ in 0..self.out.indent_level + 1 {
                     write!(self.out, "    ")?

+ 21 - 0
packages/autofmt/tests/srcless.rs

@@ -0,0 +1,21 @@
+use dioxus_rsx::CallBody;
+use proc_macro2::TokenStream as TokenStream2;
+
+/// Ensure we can write RSX blocks without a source file
+///
+/// Useful in code generation use cases where we still want formatted code.
+#[test]
+fn write_block_out() {
+    let src = include_str!("./srcless/basic_expr.rsx");
+
+    let tokens: TokenStream2 = syn::parse_str(src).unwrap();
+    let parsed: CallBody = syn::parse2(tokens).unwrap();
+
+    let block = dioxus_autofmt::write_block_out(&parsed).unwrap();
+
+    // normalize line endings for windows tests to pass
+    pretty_assertions::assert_eq!(
+        block.trim().lines().collect::<Vec<_>>().join("\n"),
+        src.trim().lines().collect::<Vec<_>>().join("\n")
+    );
+}

+ 42 - 0
packages/autofmt/tests/srcless/basic_expr.rsx

@@ -0,0 +1,42 @@
+    div {
+        "hi"
+        {children}
+    }
+    Fragment {
+        Fragment {
+            Fragment {
+                Fragment {
+                    Fragment {
+                        div { "Finally have a real node!" }
+                    }
+                }
+            }
+        }
+    }
+    div { class, "hello world" }
+    h1 { class, "hello world" }
+    h1 { class, {children} }
+    h1 { class, id, {children} }
+    h1 { class,
+        "hello world"
+        {children}
+    }
+    h1 { id,
+        "hello world"
+        {children}
+    }
+    Other { class, children }
+    Other { class,
+        "hello world"
+        {children}
+    }
+    div {
+        class: "asdasd",
+        onclick: move |_| {
+            let a = 10;
+            let b = 40;
+            let c = 50;
+        },
+        "hi"
+    }
+    div { class: "asd", "Jon" }

+ 8 - 12
packages/core/src/hotreload_utils.rs

@@ -10,7 +10,6 @@ use crate::{
 };
 
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 #[doc(hidden)]
 #[derive(Debug, PartialEq, Clone)]
 pub struct HotreloadedLiteral {
@@ -19,7 +18,6 @@ pub struct HotreloadedLiteral {
 }
 
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 #[doc(hidden)]
 #[derive(Debug, PartialEq, Clone)]
 pub enum HotReloadLiteral {
@@ -71,7 +69,6 @@ impl Hash for HotReloadLiteral {
 }
 
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 #[doc(hidden)]
 #[derive(Debug, PartialEq, Eq, Clone, Hash)]
 pub struct FmtedSegments {
@@ -98,6 +95,8 @@ impl FmtedSegments {
     }
 }
 
+type StaticStr = &'static str;
+
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[doc(hidden)]
 #[derive(Debug, PartialEq, Eq, Clone, Hash)]
@@ -107,7 +106,7 @@ pub enum FmtSegment {
             feature = "serialize",
             serde(deserialize_with = "deserialize_string_leaky")
         )]
-        value: &'static str,
+        value: StaticStr,
     },
     Dynamic {
         id: usize,
@@ -313,16 +312,16 @@ impl DynamicValuePool {
 #[doc(hidden)]
 #[derive(Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 pub struct HotReloadTemplateWithLocation {
     pub location: String,
     pub template: HotReloadedTemplate,
 }
 
+type StaticTemplateArray = &'static [TemplateNode];
+
 #[doc(hidden)]
 #[derive(Debug, PartialEq, Clone)]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 pub struct HotReloadedTemplate {
     pub key: Option<FmtedSegments>,
     pub dynamic_nodes: Vec<HotReloadDynamicNode>,
@@ -332,7 +331,7 @@ pub struct HotReloadedTemplate {
         feature = "serialize",
         serde(deserialize_with = "crate::nodes::deserialize_leaky")
     )]
-    pub roots: &'static [TemplateNode],
+    pub roots: StaticTemplateArray,
     /// The template that is computed from the hot reload roots
     template: Template,
 }
@@ -425,7 +424,6 @@ impl HotReloadedTemplate {
 #[doc(hidden)]
 #[derive(Debug, PartialEq, Clone, Hash)]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 pub enum HotReloadDynamicNode {
     Dynamic(usize),
     Formatted(FmtedSegments),
@@ -434,7 +432,6 @@ pub enum HotReloadDynamicNode {
 #[doc(hidden)]
 #[derive(Debug, PartialEq, Clone, Hash)]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 pub enum HotReloadDynamicAttribute {
     Dynamic(usize),
     Named(NamedAttribute),
@@ -449,13 +446,13 @@ pub struct NamedAttribute {
         feature = "serialize",
         serde(deserialize_with = "crate::nodes::deserialize_string_leaky")
     )]
-    name: &'static str,
+    name: StaticStr,
     /// The namespace of this attribute. Does not exist in the HTML spec
     #[cfg_attr(
         feature = "serialize",
         serde(deserialize_with = "crate::nodes::deserialize_option_leaky")
     )]
-    namespace: Option<&'static str>,
+    namespace: Option<StaticStr>,
 
     value: HotReloadAttributeValue,
 }
@@ -477,7 +474,6 @@ impl NamedAttribute {
 #[doc(hidden)]
 #[derive(Debug, PartialEq, Clone, Hash)]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 pub enum HotReloadAttributeValue {
     Literal(HotReloadLiteral),
     Dynamic(usize),

+ 45 - 18
packages/core/src/nodes.rs

@@ -288,6 +288,11 @@ impl VNode {
     }
 }
 
+type StaticStr = &'static str;
+type StaticPathArray = &'static [&'static [u8]];
+type StaticTemplateArray = &'static [TemplateNode];
+type StaticTemplateAttributeArray = &'static [TemplateAttribute];
+
 /// A static layout of a UI tree that describes a set of dynamic and static nodes.
 ///
 /// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any
@@ -297,14 +302,13 @@ impl VNode {
 /// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
 /// ways, with the suggested approach being the unique code location (file, line, col, etc).
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 #[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord)]
 pub struct Template {
     /// The list of template nodes that make up the template
     ///
     /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
     #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
-    pub roots: &'static [TemplateNode],
+    pub roots: StaticTemplateArray,
 
     /// The paths of each node relative to the root of the template.
     ///
@@ -314,7 +318,7 @@ pub struct Template {
         feature = "serialize",
         serde(deserialize_with = "deserialize_bytes_leaky")
     )]
-    pub node_paths: &'static [&'static [u8]],
+    pub node_paths: StaticPathArray,
 
     /// The paths of each dynamic attribute relative to the root of the template
     ///
@@ -322,9 +326,9 @@ pub struct Template {
     /// topmost element, not the `roots` field.
     #[cfg_attr(
         feature = "serialize",
-        serde(deserialize_with = "deserialize_bytes_leaky")
+        serde(deserialize_with = "deserialize_bytes_leaky", bound = "")
     )]
-    pub attr_paths: &'static [&'static [u8]],
+    pub attr_paths: StaticPathArray,
 }
 
 impl std::hash::Hash for Template {
@@ -344,7 +348,9 @@ impl PartialEq for Template {
 }
 
 #[cfg(feature = "serialize")]
-pub(crate) fn deserialize_string_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a str, D::Error>
+pub(crate) fn deserialize_string_leaky<'a, 'de, D>(
+    deserializer: D,
+) -> Result<&'static str, D::Error>
 where
     D: serde::Deserializer<'de>,
 {
@@ -355,7 +361,9 @@ where
 }
 
 #[cfg(feature = "serialize")]
-fn deserialize_bytes_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a [&'a [u8]], D::Error>
+fn deserialize_bytes_leaky<'a, 'de, D>(
+    deserializer: D,
+) -> Result<&'static [&'static [u8]], D::Error>
 where
     D: serde::Deserializer<'de>,
 {
@@ -370,7 +378,7 @@ where
 }
 
 #[cfg(feature = "serialize")]
-pub(crate) fn deserialize_leaky<'a, 'de, T, D>(deserializer: D) -> Result<&'a [T], D::Error>
+pub(crate) fn deserialize_leaky<'a, 'de, T, D>(deserializer: D) -> Result<&'static [T], D::Error>
 where
     T: serde::Deserialize<'de>,
     D: serde::Deserializer<'de>,
@@ -421,7 +429,11 @@ pub enum TemplateNode {
         /// The name of the element
         ///
         /// IE for a div, it would be the string "div"
-        tag: &'static str,
+        #[cfg_attr(
+            feature = "serialize",
+            serde(deserialize_with = "deserialize_string_leaky")
+        )]
+        tag: StaticStr,
 
         /// The namespace of the element
         ///
@@ -431,17 +443,20 @@ pub enum TemplateNode {
             feature = "serialize",
             serde(deserialize_with = "deserialize_option_leaky")
         )]
-        namespace: Option<&'static str>,
+        namespace: Option<StaticStr>,
 
         /// A list of possibly dynamic attributes for this element
         ///
         /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
-        #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
-        attrs: &'static [TemplateAttribute],
+        #[cfg_attr(
+            feature = "serialize",
+            serde(deserialize_with = "deserialize_leaky", bound = "")
+        )]
+        attrs: StaticTemplateAttributeArray,
 
         /// A list of template nodes that define another set of template nodes
         #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
-        children: &'static [TemplateNode],
+        children: StaticTemplateArray,
     },
 
     /// This template node is just a piece of static text
@@ -449,9 +464,9 @@ pub enum TemplateNode {
         /// The actual text
         #[cfg_attr(
             feature = "serialize",
-            serde(deserialize_with = "deserialize_string_leaky")
+            serde(deserialize_with = "deserialize_string_leaky", bound = "")
         )]
-        text: &'static str,
+        text: StaticStr,
     },
 
     /// This template node is unknown, and needs to be created at runtime.
@@ -652,15 +667,27 @@ pub enum TemplateAttribute {
         /// The name of this attribute.
         ///
         /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
-        name: &'static str,
+        #[cfg_attr(
+            feature = "serialize",
+            serde(deserialize_with = "deserialize_string_leaky", bound = "")
+        )]
+        name: StaticStr,
 
         /// The value of this attribute, known at compile time
         ///
         /// Currently this only accepts &str, so values, even if they're known at compile time, are not known
-        value: &'static str,
+        #[cfg_attr(
+            feature = "serialize",
+            serde(deserialize_with = "deserialize_string_leaky", bound = "")
+        )]
+        value: StaticStr,
 
         /// The namespace of this attribute. Does not exist in the HTML spec
-        namespace: Option<&'static str>,
+        #[cfg_attr(
+            feature = "serialize",
+            serde(deserialize_with = "deserialize_option_leaky", bound = "")
+        )]
+        namespace: Option<StaticStr>,
     },
 
     /// The attribute in this position is actually determined dynamically at runtime

+ 0 - 2
packages/hot-reload/src/lib.rs

@@ -16,7 +16,6 @@ pub use ws_receiver::*;
 
 /// A message the hot reloading server sends to the client
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
-#[serde(bound(deserialize = "'de: 'static"))]
 pub enum DevserverMsg {
     /// Attempt a hotreload
     /// This includes all the templates/literals/assets/binary patches that have changed in one shot
@@ -47,7 +46,6 @@ pub enum ClientMsg {
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
-#[serde(bound(deserialize = "'de: 'static"))]
 pub struct HotReloadMsg {
     pub templates: Vec<HotReloadTemplateWithLocation>,
     pub assets: Vec<PathBuf>,

+ 2 - 2
packages/hot-reload/src/ws_receiver.rs

@@ -51,8 +51,8 @@ impl NativeReceiver {
             match res {
                 Ok(res) => match res {
                     Message::Text(text) => {
-                        let leaked: &'static str = Box::leak(text.into_boxed_str());
-                        let msg = serde_json::from_str::<DevserverMsg>(leaked);
+                        // let leaked: &'static str = Box::leak(text.into_boxed_str());
+                        let msg = serde_json::from_str::<DevserverMsg>(&text);
                         if let Ok(msg) = msg {
                             return Some(Ok(msg));
                         }

+ 2 - 2
packages/web/src/hot_reload.rs

@@ -57,9 +57,9 @@ fn make_ws(tx: UnboundedSender<HotReloadMsg>, poll_interval: i32, reload: bool)
 
             // The devserver messages have some &'static strs in them, so we need to leak the source string
             let string: String = text.into();
-            let leaked: &'static str = Box::leak(Box::new(string));
+            // let leaked: &'static str = Box::leak(Box::new(string));
 
-            match serde_json::from_str::<DevserverMsg>(leaked) {
+            match serde_json::from_str::<DevserverMsg>(&string) {
                 Ok(DevserverMsg::HotReload(hr)) => _ = tx_.unbounded_send(hr),
 
                 // todo: we want to throw a screen here that shows the user that the devserver has disconnected