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
This commit is contained in:
Jonathan Kelley 2024-08-16 21:55:30 -07:00 committed by GitHub
parent b47a6cf83e
commit 4963aa3118
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 142 additions and 46 deletions

View file

@ -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

View file

@ -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, " ")?

View file

@ -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")
);
}

View file

@ -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" }

View file

@ -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),

View file

@ -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

View file

@ -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>,

View file

@ -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));
}

View file

@ -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