299 lines
8.0 KiB
Markdown
299 lines
8.0 KiB
Markdown
# 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<usize>
|
|
└── span: Option<Span>
|
|
```
|
|
|
|
`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<Attribute>
|
|
├── merged_attributes: Vec<Attribute> // After combining duplicates
|
|
├── spreads: Vec<Spread>
|
|
├── children: Vec<BodyNode>
|
|
├── brace: Option<Brace>
|
|
└── 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<Token![:]>
|
|
├── dyn_idx: DynIdx
|
|
└── el_name: Option<ElementName>
|
|
```
|
|
|
|
### 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<AngleBracketedGenericArguments>
|
|
├── fields: Vec<Attribute>
|
|
├── component_literal_dyn_idx: Vec<DynIdx>
|
|
├── spreads: Vec<Spread>
|
|
├── children: TemplateBody
|
|
├── dyn_idx: DynIdx
|
|
└── diagnostics: Diagnostics
|
|
```
|
|
|
|
### ForLoop
|
|
```
|
|
ForLoop
|
|
├── for_token, pat, in_token
|
|
├── expr: Box<Expr>
|
|
├── body: TemplateBody
|
|
└── dyn_idx: DynIdx
|
|
```
|
|
Generates: `(expr).into_iter().map(|pat| { body })`
|
|
|
|
### IfChain
|
|
```
|
|
IfChain
|
|
├── if_token, cond: Box<Expr>
|
|
├── then_branch: TemplateBody
|
|
├── else_if_branch: Option<Box<IfChain>>
|
|
├── else_branch: Option<TemplateBody>
|
|
└── dyn_idx: DynIdx
|
|
```
|
|
|
|
### DynIdx
|
|
`Cell<Option<usize>>` 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<BodyNode>
|
|
├── template_idx: DynIdx
|
|
├── node_paths: Vec<Vec<u8>> // Path to each dynamic node
|
|
├── attr_paths: Vec<(Vec<u8>, usize)> // Path and attribute index
|
|
├── dynamic_text_segments: Vec<FormattedSegment>
|
|
└── 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<DynIdx>`:
|
|
- 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<FormattedBlock>
|
|
- `fmt_block(block_str, indent_level, IndentOptions)` → Option<String>
|
|
- `write_block_out(body)` → Option<String>
|
|
|
|
### 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<LineColumn, String>
|
|
└── invalid_exprs: Vec<Span>
|
|
```
|
|
|
|
### 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
|