# RSX and Autofmt Architecture The `dioxus-rsx` crate parses JSX-like syntax into Rust code, while `dioxus-autofmt` provides formatting capabilities. ## RSX Parsing ### Entry Point: CallBody Root struct for `rsx! {}` macro contents: ``` CallBody ├── TemplateBody (list of BodyNode roots) ├── template_idx: Cell └── span: Option ``` `CallBody::new()` initializes template indices and cascades hotreload info through nested structures. ### BodyNode Enum Six variants representing RSX content: - `Element(Element)` - HTML elements (div, span) - `Component(Component)` - User components - `Text(TextNode)` - String literals with interpolation - `RawExpr(ExprNode)` - Braced expressions `{expr}` - `ForLoop(ForLoop)` - `for pat in expr { body }` - `IfChain(IfChain)` - `if cond { } else { }` ### Parsing Priority 1. Peek for LitStr → TextNode 2. Peek for `for` → ForLoop 3. Peek for `if` → IfChain 4. Peek for `match` → RawExpr 5. Peek for Brace → RawExpr 6. Web components: Ident + `-` → Element 7. Lowercase ident → Element 8. Otherwise → Component (fallback) ## Key AST Types ### Element ``` Element ├── name: ElementName (Ident or Custom) ├── raw_attributes: Vec ├── merged_attributes: Vec // After combining duplicates ├── spreads: Vec ├── children: Vec ├── brace: Option └── diagnostics: Diagnostics ``` `merge_attributes()` collapses duplicate attribute names using `IfmtInput` with space delimiter. ### Attribute ``` Attribute ├── name: AttributeName (BuiltIn, Custom, or Spread) ├── value: AttributeValue ├── colon: Option ├── dyn_idx: DynIdx └── el_name: Option ``` ### AttributeValue Variants - `Shorthand(Ident)` - attribute without value - `AttrLiteral(HotLiteral)` - hotreloadable literal - `EventTokens(PartialClosure)` - event handlers - `IfExpr(IfAttributeValue)` - conditional attributes - `AttrExpr(PartialExpr)` - arbitrary expressions ### HotLiteral ``` HotLiteral ├── Fmted(HotReloadFormattedSegment) // "{expr}" interpolation ├── Float(LitFloat) ├── Int(LitInt) └── Bool(LitBool) ``` ### IfmtInput (Formatted Strings) Parses string contents into segments: - `Segment::Literal(String)` - plain text - `Segment::Formatted(FormattedSegment)` - `{expr}` interpolation Parsing rules: - `{{` → literal `{` - `}}` → literal `}` - `{expr}` → formatted segment - `{expr:format_args}` → formatted with format spec ### Component ``` Component ├── name: syn::Path ├── generics: Option ├── fields: Vec ├── component_literal_dyn_idx: Vec ├── spreads: Vec ├── children: TemplateBody ├── dyn_idx: DynIdx └── diagnostics: Diagnostics ``` ### ForLoop ``` ForLoop ├── for_token, pat, in_token ├── expr: Box ├── body: TemplateBody └── dyn_idx: DynIdx ``` Generates: `(expr).into_iter().map(|pat| { body })` ### IfChain ``` IfChain ├── if_token, cond: Box ├── then_branch: TemplateBody ├── else_if_branch: Option> ├── else_branch: Option └── dyn_idx: DynIdx ``` ### DynIdx `Cell>` for tracking dynamic indices. Transparent in PartialEq/Eq/Hash. Used for hot-reload mapping. ## Code Generation ### TemplateBody → VNode::Template Generated output structure: 1. `__TEMPLATE_ROOTS` - Static array of TemplateNode 2. **Dynamic nodes** - Components, interpolated text, loops, conditionals 3. **Dynamic attributes** - Non-static attribute values 4. **Dynamic literal pool** - In debug, vec of formatted values 5. **Dynamic value pool** - Maps literal indices to values ### Template Structure ```rust dioxus_core::Element::Ok({ #[cfg(debug_assertions)] fn __original_template() -> &'static HotReloadedTemplate { ... } let __dynamic_nodes: [DynamicNode; N] = [ ... ]; let __dynamic_attributes: [Box<[Attribute]>; M] = [ ... ]; static __TEMPLATE_ROOTS: &[TemplateNode] = &[ ... ]; // Template reference and rendering }) ``` ### Generated TemplateNode Types - `Element { tag, namespace, attrs, children }` - Static element - `Text { text }` - Static text - `Dynamic { id }` - References dynamic node pool ## Template System ### TemplateBody Structure ``` TemplateBody ├── roots: Vec ├── template_idx: DynIdx ├── node_paths: Vec> // Path to each dynamic node ├── attr_paths: Vec<(Vec, usize)> // Path and attribute index ├── dynamic_text_segments: Vec └── diagnostics: Diagnostics ``` ### Template ID Assignment - `CallBody::next_template_idx()` generates sequential IDs - Each nested structure gets unique ID - Combined with `file!()`, `line!()`, `column!()` for source location ### HotReloadFormattedSegment Wraps `IfmtInput` with `dynamic_node_indexes: Vec`: - One DynIdx per `Segment::Formatted` entry - Maps formatted segments to dynamic nodes during hot-reload ## Autofmt System ### Entry Points - `try_fmt_file(contents, &syn::File, IndentOptions)` → Vec - `fmt_block(block_str, indent_level, IndentOptions)` → Option - `write_block_out(body)` → Option ### FormattedBlock ``` FormattedBlock ├── formatted: String ├── start: usize (byte offset) └── end: usize (byte offset) ``` ### Writer State ``` Writer ├── raw_src: &str ├── src: Vec<&str> // Lines ├── out: Buffer ├── cached_formats: HashMap └── invalid_exprs: Vec ``` ### Buffer ``` Buffer ├── buf: String ├── indent_level: usize └── indent: IndentOptions ``` ### Optimization Levels 1. **Empty**: `div {}` (no space inside) 2. **Oneliner**: `div { class: "x", child {} }` (single line) 3. **PropsOnTop**: Props multiline, children follow 4. **NoOpt**: Everything multiline ### Short-Circuit Optimization ```rust if formatted.len() <= 80 && !formatted.contains('\n') && !body_is_solo_expr && !formatted.trim().is_empty() { formatted = format!(" {formatted} "); // Collapse to single line } ``` ## Whitespace Handling Whitespace is significant in RSX: - Text nodes preserve exact whitespace - Comments must be preserved - Formatting must not change text node content ### Comment Preservation - `write_comments()` accumulates full-line comments before spans - `write_inline_comments()` preserves end-of-line comments - Comments tracked via `LineColumn` from Span ## Expression Formatting ### write_partial_expr() Strategy 1. Use `prettier_please` to unparse expressions 2. Handle nested rsx! macros specially 3. `unparse_expr()` visits macro calls: - Format nested rsx! blocks - Replace macros with marker unicode - Apply formatted blocks back ### Marker Replacement Uses marker `"𝕣𝕤𝕩"` to replace macros during unparse, avoiding conflicts with actual code. ## Indentation System ### IndentOptions ``` IndentOptions ├── width: usize ├── indent_string: String ("\t" or spaces) └── split_line_attributes: bool ``` ### Functions - `indent_str()` → returns indent string - `count_indents(line)` → estimates indent level - `line_length(line)` → estimates visible length ## Attribute Formatting ### write_attributes(props_same_line) - `true`: attrs on one line with spaces - `false`: each attr on new line, indented ### write_attribute_value() - `Shorthand` → just ident - `AttrLiteral` → uses Display impl - `EventTokens` → write_partial_expr() - `IfExpr` → write_attribute_if_chain() - `AttrExpr` → write_partial_expr() ## Extension Points ### Adding New Node Types 1. Add variant to BodyNode enum 2. Implement Parse trait 3. Implement ToTokens for code gen 4. Add write_* method to Writer ### Adding New Attribute Types 1. Add variant to AttributeValue 2. Implement Parse for detection 3. Add merge handling if needed 4. Add write_attribute_value case ### Changing Formatting Heuristics - Modify ShortOptimization logic - Adjust threshold constants (80, 100 chars) - Modify estimation functions