@@ -12,13 +12,22 @@ import (
1212 guixast "github.com/gaarutyunov/guix/pkg/ast"
1313)
1414
15+ // childComponentInfo holds information about a child component instance
16+ type childComponentInfo struct {
17+ varName string
18+ componentType string
19+ element * guixast.Element // The element with props
20+ }
21+
1522// Generator generates Go code from Guix components
1623type Generator struct {
17- fset * token.FileSet
18- pkg string
19- components map [string ]bool // Track component names for this file
20- hoistedVars map [string ]bool // Track hoisted variable names in current component
21- currentCompBody * guixast.Body // Current component body being generated
24+ fset * token.FileSet
25+ pkg string
26+ components map [string ]bool // Track component names for this file
27+ hoistedVars map [string ]bool // Track hoisted variable names in current component
28+ hoistedComponents []* childComponentInfo // Track hoisted child components
29+ hoistedComponentMap map [* guixast.Element ]* childComponentInfo // Map elements to their hoisted component info
30+ currentCompBody * guixast.Body // Current component body being generated
2231}
2332
2433// New creates a new code generator
@@ -121,6 +130,9 @@ func (g *Generator) generateImports(file *guixast.File) *ast.GenDecl {
121130func (g * Generator ) generateComponent (comp * guixast.Component ) []ast.Decl {
122131 var decls []ast.Decl
123132
133+ // Reset hoisted component map for this component
134+ g .hoistedComponentMap = make (map [* guixast.Element ]* childComponentInfo )
135+
124136 // Generate Props struct if component has parameters
125137 if len (comp .Params ) > 0 {
126138 decls = append (decls , g .generatePropsStruct (comp ))
@@ -214,6 +226,39 @@ func (g *Generator) nodeHasTemplateInterpolation(node *guixast.Node) bool {
214226 return false
215227}
216228
229+ // collectChildComponents recursively collects child component usages from nodes
230+ func (g * Generator ) collectChildComponents (nodes []* guixast.Node ) []* childComponentInfo {
231+ var childComponents []* childComponentInfo
232+ g .collectChildComponentsFromNodes (nodes , & childComponents )
233+ return childComponents
234+ }
235+
236+ func (g * Generator ) collectChildComponentsFromNodes (nodes []* guixast.Node , result * []* childComponentInfo ) {
237+ for _ , node := range nodes {
238+ if node .Element != nil {
239+ elem := node .Element
240+ // Check if this is a component (capitalized tag name)
241+ isComponent := g .components [elem .Tag ] || (len (elem .Tag ) > 0 && elem .Tag [0 ] >= 'A' && elem .Tag [0 ] <= 'Z' && ! knownDOMElements [elem .Tag ])
242+
243+ if isComponent {
244+ // Generate a variable name for this component instance
245+ varName := strings .ToLower (elem .Tag [:1 ]) + elem .Tag [1 :] + "Instance"
246+ childInfo := & childComponentInfo {
247+ varName : varName ,
248+ componentType : elem .Tag ,
249+ element : elem ,
250+ }
251+ * result = append (* result , childInfo )
252+ // Add to map for quick lookup during render generation
253+ g .hoistedComponentMap [elem ] = childInfo
254+ }
255+
256+ // Recursively check children
257+ g .collectChildComponentsFromNodes (elem .Children , result )
258+ }
259+ }
260+ }
261+
217262// generatePropsStruct generates a Props struct for component parameters
218263func (g * Generator ) generatePropsStruct (comp * guixast.Component ) * ast.GenDecl {
219264 fields := make ([]* ast.Field , len (comp .Params ))
@@ -386,6 +431,18 @@ func (g *Generator) generateComponentStruct(comp *guixast.Component) *ast.GenDec
386431 }
387432 }
388433 }
434+
435+ // Add child component instance fields
436+ // Collect child components from the template
437+ childComponents := g .collectChildComponents (comp .Body .Children )
438+ for _ , childInfo := range childComponents {
439+ fields = append (fields , & ast.Field {
440+ Names : []* ast.Ident {ast .NewIdent (childInfo .varName )},
441+ Type : & ast.StarExpr {
442+ X : ast .NewIdent (childInfo .componentType ),
443+ },
444+ })
445+ }
389446 }
390447
391448 // Add listenersStarted flag if component has channel parameters
@@ -488,6 +545,16 @@ func (g *Generator) generateConstructor(comp *guixast.Component) *ast.FuncDecl {
488545
489546 // Initialize hoisted variables (like channels) in constructor
490547 if comp .Body != nil {
548+ // Set up hoisted vars map for expression generation
549+ g .hoistedVars = make (map [string ]bool )
550+ g .currentCompBody = comp .Body
551+ for _ , varDecl := range comp .Body .VarDecls {
552+ if len (varDecl .Names ) == 1 && len (varDecl .Values ) == 1 &&
553+ g .inferTypeFromExpr (varDecl .Values [0 ]) != nil {
554+ g .hoistedVars [varDecl .Names [0 ]] = true
555+ }
556+ }
557+
491558 for _ , varDecl := range comp .Body .VarDecls {
492559 // Only initialize single-variable declarations with inferable types
493560 if len (varDecl .Names ) == 1 && len (varDecl .Values ) == 1 &&
@@ -503,6 +570,34 @@ func (g *Generator) generateConstructor(comp *guixast.Component) *ast.FuncDecl {
503570 })
504571 }
505572 }
573+
574+ // Initialize child component instances
575+ childComponents := g .collectChildComponents (comp .Body .Children )
576+ for _ , childInfo := range childComponents {
577+ // Generate option calls from props
578+ optionCalls := []ast.Expr {}
579+ for _ , prop := range childInfo .element .Props {
580+ optionCalls = append (optionCalls , & ast.CallExpr {
581+ Fun : ast .NewIdent (prop .Name ),
582+ Args : []ast.Expr {g .generateExpr (prop .Value )},
583+ })
584+ }
585+
586+ // Generate: c.childInstance = NewChildComponent(options...)
587+ bodyStmts = append (bodyStmts , & ast.AssignStmt {
588+ Lhs : []ast.Expr {& ast.SelectorExpr {
589+ X : ast .NewIdent ("c" ),
590+ Sel : ast .NewIdent (childInfo .varName ),
591+ }},
592+ Tok : token .ASSIGN ,
593+ Rhs : []ast.Expr {
594+ & ast.CallExpr {
595+ Fun : ast .NewIdent ("New" + childInfo .componentType ),
596+ Args : optionCalls ,
597+ },
598+ },
599+ })
600+ }
506601 }
507602
508603 bodyStmts = append (bodyStmts , & ast.ReturnStmt {
@@ -545,6 +640,11 @@ func (g *Generator) generateRenderMethod(comp *guixast.Component) *ast.FuncDecl
545640 }
546641 }
547642
643+ // Populate hoisted component map for this component
644+ if comp .Body != nil {
645+ g .collectChildComponents (comp .Body .Children )
646+ }
647+
548648 body := g .generateBody (comp .Body )
549649
550650 return & ast.FuncDecl {
@@ -794,7 +894,22 @@ func (g *Generator) generateElement(elem *guixast.Element) ast.Expr {
794894
795895 // Generate function call
796896 if isComponent {
797- // Custom component: wrap in IIFE to call BindApp before Render
897+ // Check if this component is hoisted
898+ if hoistedInfo , isHoisted := g .hoistedComponentMap [elem ]; isHoisted {
899+ // Hoisted component: just call Render on the hoisted instance
900+ // Generate: c.counterInstance.Render()
901+ return & ast.CallExpr {
902+ Fun : & ast.SelectorExpr {
903+ X : & ast.SelectorExpr {
904+ X : ast .NewIdent ("c" ),
905+ Sel : ast .NewIdent (hoistedInfo .varName ),
906+ },
907+ Sel : ast .NewIdent ("Render" ),
908+ },
909+ }
910+ }
911+
912+ // Non-hoisted component: wrap in IIFE to call BindApp before Render
798913 // Generate:
799914 // func() *runtime.VNode {
800915 // comp := NewComponent(...)
@@ -1453,6 +1568,40 @@ func (g *Generator) generateBindAppMethod(comp *guixast.Component) *ast.FuncDecl
14531568 }
14541569 }
14551570
1571+ // Call BindApp on child component instances
1572+ if comp .Body != nil {
1573+ childComponents := g .collectChildComponents (comp .Body .Children )
1574+ for _ , childInfo := range childComponents {
1575+ // Generate: if c.childInstance != nil { c.childInstance.BindApp(app) }
1576+ stmts = append (stmts , & ast.IfStmt {
1577+ Cond : & ast.BinaryExpr {
1578+ X : & ast.SelectorExpr {
1579+ X : ast .NewIdent ("c" ),
1580+ Sel : ast .NewIdent (childInfo .varName ),
1581+ },
1582+ Op : token .NEQ ,
1583+ Y : ast .NewIdent ("nil" ),
1584+ },
1585+ Body : & ast.BlockStmt {
1586+ List : []ast.Stmt {
1587+ & ast.ExprStmt {
1588+ X : & ast.CallExpr {
1589+ Fun : & ast.SelectorExpr {
1590+ X : & ast.SelectorExpr {
1591+ X : ast .NewIdent ("c" ),
1592+ Sel : ast .NewIdent (childInfo .varName ),
1593+ },
1594+ Sel : ast .NewIdent ("BindApp" ),
1595+ },
1596+ Args : []ast.Expr {ast .NewIdent ("app" )},
1597+ },
1598+ },
1599+ },
1600+ },
1601+ })
1602+ }
1603+ }
1604+
14561605 // Set flag after starting listeners
14571606 if hasChannels {
14581607 stmts = append (stmts , & ast.AssignStmt {
0 commit comments