feat: AGENTS.md and architecture guides for agents!
This commit is contained in:
1 parent
2ee8ecd477
commit
638c02e15a
12 files changed
+3324
No files matched your search
@@ -0,0 +1,298 @@
|
||||
# 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
|
||||
Reference in new issue
Block a user