package generator import ( "crypto/sha256" "encoding/hex" "fmt" "html" "io" "path/filepath" "reflect" "strconv" "strings" "time" "unicode" _ "embed" "github.com/a-h/templ/parser/v2" ) type GenerateOpt func(g *generator) error // WithVersion enables the version to be included in the generated code. func WithVersion(v string) GenerateOpt { return func(g *generator) error { g.options.Version = v return nil } } // WithTimestamp enables the generated date to be included in the generated code. func WithTimestamp(d time.Time) GenerateOpt { return func(g *generator) error { g.options.GeneratedDate = d.Format(time.RFC3339) return nil } } // WithFileName sets the filename of the templ file in template rendering error messages. func WithFileName(name string) GenerateOpt { return func(g *generator) error { if filepath.IsAbs(name) { _, g.options.FileName = filepath.Split(name) return nil } g.options.FileName = name return nil } } // WithSkipCodeGeneratedComment skips the code generated comment at the top of the file. // gopls disables edit related functionality for generated files, so the templ LSP may // wish to skip generation of this comment so that gopls provides expected results. func WithSkipCodeGeneratedComment() GenerateOpt { return func(g *generator) error { g.options.SkipCodeGeneratedComment = true return nil } } type GeneratorOutput struct { Options GeneratorOptions `json:"meta"` SourceMap *parser.SourceMap `json:"sourceMap"` Literals []string `json:"literals"` } type GeneratorOptions struct { // Version of templ. Version string // FileName to include in error messages if string expressions return an error. FileName string // SkipCodeGeneratedComment skips the code generated comment at the top of the file. SkipCodeGeneratedComment bool // GeneratedDate to include as a comment. GeneratedDate string } // HasChanged returns true if the generated file should be written to disk, and therefore, also // requires a recompilation. func HasChanged(previous, updated GeneratorOutput) bool { // If generator options have changed, we need to recompile. if previous.Options.Version != updated.Options.Version { return true } if previous.Options.FileName != updated.Options.FileName { return true } if previous.Options.SkipCodeGeneratedComment != updated.Options.SkipCodeGeneratedComment { return true } // We don't check the generated date as it's not used for determining if the file has changed. // If the number of literals has changed, we need to recompile. if len(previous.Literals) != len(updated.Literals) { return true } // If the Go code has changed, we need to recompile. if len(previous.SourceMap.Expressions) != len(updated.SourceMap.Expressions) { return true } for i, prev := range previous.SourceMap.Expressions { if prev != updated.SourceMap.Expressions[i] { return true } } return false } // Generate generates Go code from the input template file to w, and returns a map of the location of Go expressions in the template // to the location of the generated Go code in the output. func Generate(template parser.TemplateFile, w io.Writer, opts ...GenerateOpt) (op GeneratorOutput, err error) { g := &generator{ tf: template, w: NewRangeWriter(w), sourceMap: parser.NewSourceMap(), } for _, opt := range opts { if err = opt(g); err != nil { return } } err = g.generate() if err != nil { return op, err } op.Options = g.options op.SourceMap = g.sourceMap op.Literals = g.w.Literals return op, nil } type generator struct { tf parser.TemplateFile w *RangeWriter sourceMap *parser.SourceMap variableID int childrenVar string options GeneratorOptions } func (g *generator) generate() (err error) { if err = g.writeCodeGeneratedComment(); err != nil { return } if err = g.writeVersionComment(); err != nil { return } if err = g.writeGeneratedDateComment(); err != nil { return } if err = g.writeHeader(); err != nil { return } if err = g.writePackage(); err != nil { return } if err = g.writeImports(); err != nil { return } if err = g.writeTemplateNodes(); err != nil { return } if err = g.writeBlankAssignmentForRuntimeImport(); err != nil { return } return err } // See https://pkg.go.dev/cmd/go#hdr-Generate_Go_files_by_processing_source // Automatically generated files have a comment in the header that instructs the LSP // to stop operating. func (g *generator) writeCodeGeneratedComment() (err error) { if g.options.SkipCodeGeneratedComment { // Write an empty comment so that the file is the same shape. _, err = g.w.Write("//\n\n") return err } _, err = g.w.Write("// Code generated by templ - DO NOT EDIT.\n\n") return err } func (g *generator) writeVersionComment() (err error) { if g.options.Version != "" { _, err = g.w.Write("// templ: version: " + g.options.Version + "\n") } return err } func (g *generator) writeGeneratedDateComment() (err error) { if g.options.GeneratedDate != "" { _, err = g.w.Write("// templ: generated: " + g.options.GeneratedDate + "\n") } return err } func (g *generator) writeHeader() (err error) { if len(g.tf.Header) == 0 { return nil } for _, n := range g.tf.Header { if err := g.writeGoExpression(n); err != nil { return err } } return err } func (g *generator) writePackage() error { var r parser.Range var err error // package ... if r, err = g.w.Write(g.tf.Package.Expression.Value + "\n\n"); err != nil { return err } g.sourceMap.Add(g.tf.Package.Expression, r) if _, err = g.w.Write("//lint:file-ignore SA4006 This context is only used if a nested component is present.\n\n"); err != nil { return err } return nil } func (g *generator) writeImports() error { var err error // Always import templ because it's the interface type of all templates. if _, err = g.w.Write("import \"github.com/a-h/templ\"\n"); err != nil { return err } if _, err = g.w.Write("import templruntime \"github.com/a-h/templ/runtime\"\n"); err != nil { return err } if _, err = g.w.Write("\n"); err != nil { return err } return nil } func (g *generator) writeTemplateNodes() error { for i := 0; i < len(g.tf.Nodes); i++ { switch n := g.tf.Nodes[i].(type) { case parser.TemplateFileGoExpression: if err := g.writeGoExpression(n); err != nil { return err } case parser.HTMLTemplate: if err := g.writeTemplate(i, n); err != nil { return err } case parser.CSSTemplate: if err := g.writeCSS(n); err != nil { return err } case parser.ScriptTemplate: if err := g.writeScript(n); err != nil { return err } default: return fmt.Errorf("unknown node type: %v", reflect.TypeOf(n)) } } return nil } func (g *generator) writeCSS(n parser.CSSTemplate) error { var r parser.Range var tgtSymbolRange parser.Range var err error var indentLevel int // func if r, err = g.w.Write("func "); err != nil { return err } tgtSymbolRange.From = r.From if r, err = g.w.Write(n.Expression.Value); err != nil { return err } g.sourceMap.Add(n.Expression, r) // templ.CSSClass { if _, err = g.w.Write(" templ.CSSClass {\n"); err != nil { return err } { indentLevel++ // templ_7745c5c3_CSSBuilder := templruntim.GetBuilder() if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()\n"); err != nil { return err } for i := 0; i < len(n.Properties); i++ { switch p := n.Properties[i].(type) { case parser.ConstantCSSProperty: // Constant CSS property values are not sanitized. if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_CSSBuilder.WriteString("+createGoString(p.String(true))+")\n"); err != nil { return err } case parser.ExpressionCSSProperty: // templ_7745c5c3_CSSBuilder.WriteString(templ.SanitizeCSS('name', p.Expression())) if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("templ_7745c5c3_CSSBuilder.WriteString(string(templ.SanitizeCSS(`%s`, ", p.Name)); err != nil { return err } if r, err = g.w.Write(p.Value.Expression.Value); err != nil { return err } g.sourceMap.Add(p.Value.Expression, r) if _, err = g.w.Write(")))\n"); err != nil { return err } default: return fmt.Errorf("unknown CSS property type: %v", reflect.TypeOf(p)) } } if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("templ_7745c5c3_CSSID := templ.CSSID(`%s`, templ_7745c5c3_CSSBuilder.String())\n", n.Name)); err != nil { return err } // return templ.CSS { if _, err = g.w.WriteIndent(indentLevel, "return templ.ComponentCSSClass{\n"); err != nil { return err } { indentLevel++ // ID: templ_7745c5c3_CSSID, if _, err = g.w.WriteIndent(indentLevel, "ID: templ_7745c5c3_CSSID,\n"); err != nil { return err } // Class: templ.SafeCSS(".cssID{" + templ.CSSBuilder.String() + "}"), if _, err = g.w.WriteIndent(indentLevel, "Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),\n"); err != nil { return err } indentLevel-- } if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil { return err } indentLevel-- } // } if r, err = g.w.WriteIndent(indentLevel, "}\n\n"); err != nil { return err } // Keep a track of symbol ranges for the LSP. tgtSymbolRange.To = r.To g.sourceMap.AddSymbolRange(n.Range, tgtSymbolRange) return nil } func (g *generator) writeGoExpression(n parser.TemplateFileGoExpression) (err error) { var tgtSymbolRange parser.Range r, err := g.w.Write(n.Expression.Value) if err != nil { return err } tgtSymbolRange.From = r.From g.sourceMap.Add(n.Expression, r) v := n.Expression.Value lineSlice := strings.Split(v, "\n") lastLine := lineSlice[len(lineSlice)-1] if strings.HasPrefix(lastLine, "//") { if _, err = g.w.WriteIndent(0, "\n"); err != nil { return err } return err } if r, err = g.w.WriteIndent(0, "\n\n"); err != nil { return err } // Keep a track of symbol ranges for the LSP. tgtSymbolRange.To = r.To g.sourceMap.AddSymbolRange(n.Expression.Range, tgtSymbolRange) return err } func (g *generator) writeTemplBuffer(indentLevel int) (err error) { // templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)\n"); err != nil { return err } // if !templ_7745c5c3_IsBuffer { // defer func() { // templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) // if templ_7745c5c3_Err == nil { // templ_7745c5c3_Err = templ_7745c5c3_BufErr // } // }() // } if _, err = g.w.WriteIndent(indentLevel, "if !templ_7745c5c3_IsBuffer {\n"); err != nil { return err } { indentLevel++ if _, err = g.w.WriteIndent(indentLevel, "defer func() {\n"); err != nil { return err } { indentLevel++ if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)\n"); err != nil { return err } if _, err = g.w.WriteIndent(indentLevel, "if templ_7745c5c3_Err == nil {\n"); err != nil { return err } { indentLevel++ if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_Err = templ_7745c5c3_BufErr\n"); err != nil { return err } indentLevel-- } if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil { return err } indentLevel-- } if _, err = g.w.WriteIndent(indentLevel, "}()\n"); err != nil { return err } indentLevel-- } if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil { return err } return } func (g *generator) writeTemplate(nodeIdx int, t parser.HTMLTemplate) error { var r parser.Range var tgtSymbolRange parser.Range var err error var indentLevel int // func if r, err = g.w.Write("func "); err != nil { return err } tgtSymbolRange.From = r.From // (r *Receiver) Name(params []string) if r, err = g.w.Write(t.Expression.Value); err != nil { return err } g.sourceMap.Add(t.Expression, r) // templ.Component { if _, err = g.w.Write(" templ.Component {\n"); err != nil { return err } indentLevel++ // return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { if _, err = g.w.WriteIndent(indentLevel, "return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {\n"); err != nil { return err } { indentLevel++ if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context\n"); err != nil { return err } if _, err = g.w.WriteIndent(indentLevel, "if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {\n"); err != nil { return err } { indentLevel++ if _, err = g.w.WriteIndent(indentLevel, "return templ_7745c5c3_CtxErr"); err != nil { return err } indentLevel-- } if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil { return err } if err := g.writeTemplBuffer(indentLevel); err != nil { return err } // ctx = templ.InitializeContext(ctx) if _, err = g.w.WriteIndent(indentLevel, "ctx = templ.InitializeContext(ctx)\n"); err != nil { return err } g.childrenVar = g.createVariableName() // templ_7745c5c3_Var1 := templ.GetChildren(ctx) // if templ_7745c5c3_Var1 == nil { // templ_7745c5c3_Var1 = templ.NopComponent // } if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("%s := templ.GetChildren(ctx)\n", g.childrenVar)); err != nil { return err } if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("if %s == nil {\n", g.childrenVar)); err != nil { return err } { indentLevel++ if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("%s = templ.NopComponent\n", g.childrenVar)); err != nil { return err } indentLevel-- } if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil { return err } // ctx = templ.ClearChildren(children) if _, err = g.w.WriteIndent(indentLevel, "ctx = templ.ClearChildren(ctx)\n"); err != nil { return err } // Nodes. if err = g.writeNodes(indentLevel, stripWhitespace(t.Children), nil); err != nil { return err } // return nil if _, err = g.w.WriteIndent(indentLevel, "return nil\n"); err != nil { return err } indentLevel-- } // }) if _, err = g.w.WriteIndent(indentLevel, "})\n"); err != nil { return err } indentLevel-- // } // Note: gofmt wants to remove a single empty line at the end of a file // so we have to make sure we don't output one if this is the last node. closingBrace := "}\n\n" if nodeIdx+1 >= len(g.tf.Nodes) { closingBrace = "}\n" } if r, err = g.w.WriteIndent(indentLevel, closingBrace); err != nil { return err } // Keep a track of symbol ranges for the LSP. tgtSymbolRange.To = r.To g.sourceMap.AddSymbolRange(t.Range, tgtSymbolRange) return nil } func stripWhitespace(input []parser.Node) (output []parser.Node) { for i, n := range input { if _, isWhiteSpace := n.(parser.Whitespace); !isWhiteSpace { output = append(output, input[i]) } } return output } func stripLeadingWhitespace(nodes []parser.Node) []parser.Node { for i := 0; i < len(nodes); i++ { n := nodes[i] if _, isWhiteSpace := n.(parser.Whitespace); !isWhiteSpace { return nodes[i:] } } return []parser.Node{} } func stripTrailingWhitespace(nodes []parser.Node) []parser.Node { for i := len(nodes) - 1; i >= 0; i-- { n := nodes[i] if _, isWhiteSpace := n.(parser.Whitespace); !isWhiteSpace { return nodes[0 : i+1] } } return []parser.Node{} } func stripLeadingAndTrailingWhitespace(nodes []parser.Node) []parser.Node { return stripTrailingWhitespace(stripLeadingWhitespace(nodes)) } func (g *generator) writeNodes(indentLevel int, nodes []parser.Node, next parser.Node) error { for i, curr := range nodes { var nextNode parser.Node if i+1 < len(nodes) { nextNode = nodes[i+1] } if nextNode == nil { nextNode = next } if err := g.writeNode(indentLevel, curr, nextNode); err != nil { return err } } return nil } func (g *generator) writeNode(indentLevel int, current parser.Node, next parser.Node) (err error) { switch n := current.(type) { case parser.DocType: err = g.writeDocType(indentLevel, n) case parser.Element: err = g.writeElement(indentLevel, n) case parser.HTMLComment: err = g.writeComment(indentLevel, n) case parser.ChildrenExpression: err = g.writeChildrenExpression(indentLevel) case parser.RawElement: err = g.writeRawElement(indentLevel, n) case parser.ForExpression: err = g.writeForExpression(indentLevel, n, next) case parser.CallTemplateExpression: err = g.writeCallTemplateExpression(indentLevel, n) case parser.TemplElementExpression: err = g.writeTemplElementExpression(indentLevel, n) case parser.IfExpression: err = g.writeIfExpression(indentLevel, n, next) case parser.SwitchExpression: err = g.writeSwitchExpression(indentLevel, n, next) case parser.StringExpression: err = g.writeStringExpression(indentLevel, n.Expression) case parser.GoCode: err = g.writeGoCode(indentLevel, n.Expression) case parser.Whitespace: err = g.writeWhitespace(indentLevel, n) case parser.Text: err = g.writeText(indentLevel, n) case parser.GoComment: // Do not render Go comments in the output HTML. return default: return fmt.Errorf("unhandled type: %v", reflect.TypeOf(n)) } // Write trailing whitespace, if there is a next node that might need the space. // If the next node is inline or text, we might need it. // If the current node is a block element, we don't need it. needed := (isInlineOrText(current) && isInlineOrText(next)) if ws, ok := current.(parser.WhitespaceTrailer); ok && needed { if err := g.writeWhitespaceTrailer(indentLevel, ws.Trailing()); err != nil { return err } } return } func isInlineOrText(next parser.Node) bool { // While these are formatted as blocks when they're written in the HTML template. // They're inline - i.e. there's no whitespace rendered around them at runtime for minification. if next == nil { return false } switch n := next.(type) { case parser.IfExpression: return true case parser.SwitchExpression: return true case parser.ForExpression: return true case parser.Element: return !n.IsBlockElement() case parser.Text: return true case parser.StringExpression: return true } return false } func (g *generator) writeWhitespaceTrailer(indentLevel int, n parser.TrailingSpace) (err error) { if n == parser.SpaceNone { return nil } // Normalize whitespace for minified output. In HTML, a single space is equivalent to // any number of spaces, tabs, or newlines. if n == parser.SpaceVertical { n = parser.SpaceHorizontal } if _, err = g.w.WriteStringLiteral(indentLevel, string(n)); err != nil { return err } return nil } func (g *generator) writeDocType(indentLevel int, n parser.DocType) (err error) { if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf("", n.Value)); err != nil { return err } return nil } func (g *generator) writeIfExpression(indentLevel int, n parser.IfExpression, nextNode parser.Node) (err error) { var r parser.Range // if if _, err = g.w.WriteIndent(indentLevel, `if `); err != nil { return err } // x == y { if r, err = g.w.Write(n.Expression.Value); err != nil { return err } g.sourceMap.Add(n.Expression, r) // { if _, err = g.w.Write(` {` + "\n"); err != nil { return err } { indentLevel++ if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(n.Then), nextNode); err != nil { return err } indentLevel-- } for _, elseIf := range n.ElseIfs { // } else if { if _, err = g.w.WriteIndent(indentLevel, `} else if `); err != nil { return err } // x == y { if r, err = g.w.Write(elseIf.Expression.Value); err != nil { return err } g.sourceMap.Add(elseIf.Expression, r) // { if _, err = g.w.Write(` {` + "\n"); err != nil { return err } { indentLevel++ if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(elseIf.Then), nextNode); err != nil { return err } indentLevel-- } } if len(n.Else) > 0 { // } else { if _, err = g.w.WriteIndent(indentLevel, `} else {`+"\n"); err != nil { return err } { indentLevel++ if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(n.Else), nextNode); err != nil { return err } indentLevel-- } } // } if _, err = g.w.WriteIndent(indentLevel, `}`+"\n"); err != nil { return err } return nil } func (g *generator) writeSwitchExpression(indentLevel int, n parser.SwitchExpression, next parser.Node) (err error) { var r parser.Range // switch if _, err = g.w.WriteIndent(indentLevel, `switch `); err != nil { return err } // val if r, err = g.w.Write(n.Expression.Value); err != nil { return err } g.sourceMap.Add(n.Expression, r) // { if _, err = g.w.Write(` {` + "\n"); err != nil { return err } if len(n.Cases) > 0 { for _, c := range n.Cases { // case x: // default: if r, err = g.w.WriteIndent(indentLevel, c.Expression.Value); err != nil { return err } g.sourceMap.Add(c.Expression, r) indentLevel++ if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(c.Children), next); err != nil { return err } indentLevel-- } } // } if _, err = g.w.WriteIndent(indentLevel, `}`+"\n"); err != nil { return err } return nil } func (g *generator) writeChildrenExpression(indentLevel int) (err error) { if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("templ_7745c5c3_Err = %s.Render(ctx, templ_7745c5c3_Buffer)\n", g.childrenVar)); err != nil { return err } if err = g.writeErrorHandler(indentLevel); err != nil { return err } return nil } func (g *generator) writeTemplElementExpression(indentLevel int, n parser.TemplElementExpression) (err error) { if len(n.Children) == 0 { return g.writeSelfClosingTemplElementExpression(indentLevel, n) } return g.writeBlockTemplElementExpression(indentLevel, n) } func (g *generator) writeBlockTemplElementExpression(indentLevel int, n parser.TemplElementExpression) (err error) { var r parser.Range childrenName := g.createVariableName() if _, err = g.w.WriteIndent(indentLevel, childrenName+" := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {\n"); err != nil { return err } indentLevel++ if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context\n"); err != nil { return err } if err := g.writeTemplBuffer(indentLevel); err != nil { return err } // ctx = templ.InitializeContext(ctx) if _, err = g.w.WriteIndent(indentLevel, "ctx = templ.InitializeContext(ctx)\n"); err != nil { return err } if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(n.Children), nil); err != nil { return err } // return nil if _, err = g.w.WriteIndent(indentLevel, "return nil\n"); err != nil { return err } indentLevel-- if _, err = g.w.WriteIndent(indentLevel, "})\n"); err != nil { return err } if _, err = g.w.WriteIndent(indentLevel, `templ_7745c5c3_Err = `); err != nil { return err } if r, err = g.w.Write(n.Expression.Value); err != nil { return err } g.sourceMap.Add(n.Expression, r) // .Render(templ.WithChildren(ctx, children), templ_7745c5c3_Buffer) if _, err = g.w.Write(".Render(templ.WithChildren(ctx, " + childrenName + "), templ_7745c5c3_Buffer)\n"); err != nil { return err } if err = g.writeErrorHandler(indentLevel); err != nil { return err } return nil } func (g *generator) writeSelfClosingTemplElementExpression(indentLevel int, n parser.TemplElementExpression) (err error) { if _, err = g.w.WriteIndent(indentLevel, `templ_7745c5c3_Err = `); err != nil { return err } // Template expression. var r parser.Range if r, err = g.w.Write(n.Expression.Value); err != nil { return err } g.sourceMap.Add(n.Expression, r) // .Render(ctx, templ_7745c5c3_Buffer) if _, err = g.w.Write(".Render(ctx, templ_7745c5c3_Buffer)\n"); err != nil { return err } if err = g.writeErrorHandler(indentLevel); err != nil { return err } return nil } func (g *generator) writeCallTemplateExpression(indentLevel int, n parser.CallTemplateExpression) (err error) { if _, err = g.w.WriteIndent(indentLevel, `templ_7745c5c3_Err = `); err != nil { return err } // Template expression. var r parser.Range if r, err = g.w.Write(n.Expression.Value); err != nil { return err } g.sourceMap.Add(n.Expression, r) // .Render(ctx, templ_7745c5c3_Buffer) if _, err = g.w.Write(".Render(ctx, templ_7745c5c3_Buffer)\n"); err != nil { return err } if err = g.writeErrorHandler(indentLevel); err != nil { return err } return nil } func (g *generator) writeForExpression(indentLevel int, n parser.ForExpression, next parser.Node) (err error) { var r parser.Range // for if _, err = g.w.WriteIndent(indentLevel, `for `); err != nil { return err } // i, v := range p.Stuff if r, err = g.w.Write(n.Expression.Value); err != nil { return err } g.sourceMap.Add(n.Expression, r) // { if _, err = g.w.Write(` {` + "\n"); err != nil { return err } // Children. indentLevel++ if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(n.Children), next); err != nil { return err } indentLevel-- // } if _, err = g.w.WriteIndent(indentLevel, `}`+"\n"); err != nil { return err } return nil } func (g *generator) writeErrorHandler(indentLevel int) (err error) { _, err = g.w.WriteIndent(indentLevel, "if templ_7745c5c3_Err != nil {\n") if err != nil { return err } indentLevel++ _, err = g.w.WriteIndent(indentLevel, "return templ_7745c5c3_Err\n") if err != nil { return err } indentLevel-- _, err = g.w.WriteIndent(indentLevel, "}\n") if err != nil { return err } return err } func (g *generator) writeExpressionErrorHandler(indentLevel int, expression parser.Expression) (err error) { _, err = g.w.WriteIndent(indentLevel, "if templ_7745c5c3_Err != nil {\n") if err != nil { return err } indentLevel++ line := int(expression.Range.To.Line + 1) col := int(expression.Range.To.Col) _, err = g.w.WriteIndent(indentLevel, "return templ.Error{Err: templ_7745c5c3_Err, FileName: "+createGoString(g.options.FileName)+", Line: "+strconv.Itoa(line)+", Col: "+strconv.Itoa(col)+"}\n") if err != nil { return err } indentLevel-- _, err = g.w.WriteIndent(indentLevel, "}\n") if err != nil { return err } return err } func copyAttributes(attr []parser.Attribute) []parser.Attribute { o := make([]parser.Attribute, len(attr)) for i, a := range attr { if c, ok := a.(parser.ConditionalAttribute); ok { c.Then = copyAttributes(c.Then) c.Else = copyAttributes(c.Else) o[i] = c continue } o[i] = a } return o } func (g *generator) writeElement(indentLevel int, n parser.Element) (err error) { if len(n.Attributes) == 0 { //
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(`<%s>`, html.EscapeString(n.Name))); err != nil { return err } } else { attrs := copyAttributes(n.Attributes) // if err = g.writeElementCSS(indentLevel, attrs); err != nil { return err } // if err = g.writeElementScript(indentLevel, attrs); err != nil { return err } //
if _, err = g.w.WriteStringLiteral(indentLevel, `>`); err != nil { return err } } // Skip children and close tag for void elements. if n.IsVoidElement() && len(n.Children) == 0 { return nil } // Children. if err = g.writeNodes(indentLevel, stripWhitespace(n.Children), nil); err != nil { return err } //
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(``, html.EscapeString(n.Name))); err != nil { return err } return err } func (g *generator) writeAttributeCSS(indentLevel int, attr parser.ExpressionAttribute) (result parser.ExpressionAttribute, ok bool, err error) { var r parser.Range name := html.EscapeString(attr.Name) if name != "class" { ok = false return } // Create a class name for the style. // The expression can either be expecting a templ.Classes call, or an expression that returns // var templ_7745c5c3_CSSClasses = []any{ classesName := g.createVariableName() if _, err = g.w.WriteIndent(indentLevel, "var "+classesName+" = []any{"); err != nil { return } // p.Name() if r, err = g.w.Write(attr.Expression.Value); err != nil { return } g.sourceMap.Add(attr.Expression, r) // }\n if _, err = g.w.Write("}\n"); err != nil { return } // Render the CSS before the element if required. // templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_CSSClasses...) if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, "+classesName+"...)\n"); err != nil { return } if err = g.writeErrorHandler(indentLevel); err != nil { return } // Rewrite the ExpressionAttribute to point at the new variable. attr.Expression = parser.Expression{ Value: "templ.CSSClasses(" + classesName + ").String()", } return attr, true, nil } func (g *generator) writeAttributesCSS(indentLevel int, attrs []parser.Attribute) (err error) { for i := 0; i < len(attrs); i++ { if attr, ok := attrs[i].(parser.ExpressionAttribute); ok { attr, ok, err = g.writeAttributeCSS(indentLevel, attr) if err != nil { return err } if ok { attrs[i] = attr } } if cattr, ok := attrs[i].(parser.ConditionalAttribute); ok { err = g.writeAttributesCSS(indentLevel, cattr.Then) if err != nil { return err } err = g.writeAttributesCSS(indentLevel, cattr.Else) if err != nil { return err } attrs[i] = cattr } } return nil } func (g *generator) writeElementCSS(indentLevel int, attrs []parser.Attribute) (err error) { return g.writeAttributesCSS(indentLevel, attrs) } func isScriptAttribute(name string) bool { for _, prefix := range []string{"on", "hx-on:"} { if strings.HasPrefix(name, prefix) { return true } } return false } func (g *generator) writeElementScript(indentLevel int, attrs []parser.Attribute) (err error) { var scriptExpressions []string for _, attr := range attrs { scriptExpressions = append(scriptExpressions, getAttributeScripts(attr)...) } if len(scriptExpressions) == 0 { return } // Render the scripts before the element if required. // templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, a, b, c) if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, "+strings.Join(scriptExpressions, ", ")+")\n"); err != nil { return err } if err = g.writeErrorHandler(indentLevel); err != nil { return err } return err } func getAttributeScripts(attr parser.Attribute) (scripts []string) { if attr, ok := attr.(parser.ConditionalAttribute); ok { for _, attr := range attr.Then { scripts = append(scripts, getAttributeScripts(attr)...) } for _, attr := range attr.Else { scripts = append(scripts, getAttributeScripts(attr)...) } } if attr, ok := attr.(parser.ExpressionAttribute); ok { name := html.EscapeString(attr.Name) if isScriptAttribute(name) { scripts = append(scripts, attr.Expression.Value) } } return scripts } func (g *generator) writeBoolConstantAttribute(indentLevel int, attr parser.BoolConstantAttribute) (err error) { name := html.EscapeString(attr.Name) if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(` %s`, name)); err != nil { return err } return nil } func (g *generator) writeConstantAttribute(indentLevel int, attr parser.ConstantAttribute) (err error) { name := html.EscapeString(attr.Name) value := html.EscapeString(attr.Value) value = strconv.Quote(value) value = value[1 : len(value)-1] if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(` %s=\"%s\"`, name, value)); err != nil { return err } return nil } func (g *generator) writeBoolExpressionAttribute(indentLevel int, attr parser.BoolExpressionAttribute) (err error) { name := html.EscapeString(attr.Name) // if if _, err = g.w.WriteIndent(indentLevel, `if `); err != nil { return err } // x == y var r parser.Range if r, err = g.w.Write(attr.Expression.Value); err != nil { return err } g.sourceMap.Add(attr.Expression, r) // { if _, err = g.w.Write(` {` + "\n"); err != nil { return err } { indentLevel++ if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(` %s`, name)); err != nil { return err } indentLevel-- } // } if _, err = g.w.WriteIndent(indentLevel, `}`+"\n"); err != nil { return err } return nil } func (g *generator) writeExpressionAttributeValueURL(indentLevel int, attr parser.ExpressionAttribute) (err error) { vn := g.createVariableName() // var vn templ.SafeURL = if _, err = g.w.WriteIndent(indentLevel, "var "+vn+" templ.SafeURL = "); err != nil { return err } // p.Name() var r parser.Range if r, err = g.w.Write(attr.Expression.Value); err != nil { return err } g.sourceMap.Add(attr.Expression, r) if _, err = g.w.Write("\n"); err != nil { return err } if _, err = g.w.WriteIndent(indentLevel, "_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string("+vn+")))\n"); err != nil { return err } return g.writeErrorHandler(indentLevel) } func (g *generator) writeExpressionAttributeValueScript(indentLevel int, attr parser.ExpressionAttribute) (err error) { // It's a JavaScript handler, and requires special handling, because we expect a JavaScript expression. vn := g.createVariableName() // var vn templ.ComponentScript = if _, err = g.w.WriteIndent(indentLevel, "var "+vn+" templ.ComponentScript = "); err != nil { return err } // p.Name() var r parser.Range if r, err = g.w.Write(attr.Expression.Value); err != nil { return err } g.sourceMap.Add(attr.Expression, r) if _, err = g.w.Write("\n"); err != nil { return err } if _, err = g.w.WriteIndent(indentLevel, "_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("+vn+".Call)\n"); err != nil { return err } return g.writeErrorHandler(indentLevel) } func (g *generator) writeExpressionAttributeValueDefault(indentLevel int, attr parser.ExpressionAttribute) (err error) { var r parser.Range vn := g.createVariableName() // var vn string if _, err = g.w.WriteIndent(indentLevel, "var "+vn+" string\n"); err != nil { return err } // vn, templ_7745c5c3_Err = templ.JoinStringErrs( if _, err = g.w.WriteIndent(indentLevel, vn+", templ_7745c5c3_Err = templ.JoinStringErrs("); err != nil { return err } // p.Name() if r, err = g.w.Write(attr.Expression.Value); err != nil { return err } g.sourceMap.Add(attr.Expression, r) // ) if _, err = g.w.Write(")\n"); err != nil { return err } // Attribute expression error handler. err = g.writeExpressionErrorHandler(indentLevel, attr.Expression) if err != nil { return err } // _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(vn) if _, err = g.w.WriteIndent(indentLevel, "_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("+vn+"))\n"); err != nil { return err } return g.writeErrorHandler(indentLevel) } func (g *generator) writeExpressionAttributeValueStyle(indentLevel int, attr parser.ExpressionAttribute) (err error) { var r parser.Range vn := g.createVariableName() // var vn string if _, err = g.w.WriteIndent(indentLevel, "var "+vn+" string\n"); err != nil { return err } // vn, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues( if _, err = g.w.WriteIndent(indentLevel, vn+", templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("); err != nil { return err } // value if r, err = g.w.Write(attr.Expression.Value); err != nil { return err } g.sourceMap.Add(attr.Expression, r) // ) if _, err = g.w.Write(")\n"); err != nil { return err } // Attribute expression error handler. err = g.writeExpressionErrorHandler(indentLevel, attr.Expression) if err != nil { return err } // _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(vn)) if _, err = g.w.WriteIndent(indentLevel, "_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("+vn+"))\n"); err != nil { return err } return g.writeErrorHandler(indentLevel) } func (g *generator) writeExpressionAttribute(indentLevel int, elementName string, attr parser.ExpressionAttribute) (err error) { attrName := html.EscapeString(attr.Name) // Name if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(` %s=`, attrName)); err != nil { return err } // Open quote. if _, err = g.w.WriteStringLiteral(indentLevel, `\"`); err != nil { return err } // Value. if (elementName == "a" && attr.Name == "href") || (elementName == "form" && attr.Name == "action") { if err := g.writeExpressionAttributeValueURL(indentLevel, attr); err != nil { return err } } else if isScriptAttribute(attr.Name) { if err := g.writeExpressionAttributeValueScript(indentLevel, attr); err != nil { return err } } else if attr.Name == "style" { if err := g.writeExpressionAttributeValueStyle(indentLevel, attr); err != nil { return err } } else { if err := g.writeExpressionAttributeValueDefault(indentLevel, attr); err != nil { return err } } // Close quote. if _, err = g.w.WriteStringLiteral(indentLevel, `\"`); err != nil { return err } return nil } func (g *generator) writeSpreadAttributes(indentLevel int, attr parser.SpreadAttributes) (err error) { // templ.RenderAttributes(ctx, w, spreadAttrs) if _, err = g.w.WriteIndent(indentLevel, `templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, `); err != nil { return err } // spreadAttrs var r parser.Range if r, err = g.w.Write(attr.Expression.Value); err != nil { return err } g.sourceMap.Add(attr.Expression, r) // ) if _, err = g.w.Write(")\n"); err != nil { return err } if err = g.writeErrorHandler(indentLevel); err != nil { return err } return nil } func (g *generator) writeConditionalAttribute(indentLevel int, elementName string, attr parser.ConditionalAttribute) (err error) { // if if _, err = g.w.WriteIndent(indentLevel, `if `); err != nil { return err } // x == y var r parser.Range if r, err = g.w.Write(attr.Expression.Value); err != nil { return err } g.sourceMap.Add(attr.Expression, r) // { if _, err = g.w.Write(` {` + "\n"); err != nil { return err } { indentLevel++ if err = g.writeElementAttributes(indentLevel, elementName, attr.Then); err != nil { return err } indentLevel-- } if len(attr.Else) > 0 { // } else { if _, err = g.w.WriteIndent(indentLevel, `} else {`+"\n"); err != nil { return err } { indentLevel++ if err = g.writeElementAttributes(indentLevel, elementName, attr.Else); err != nil { return err } indentLevel-- } } // } if _, err = g.w.WriteIndent(indentLevel, `}`+"\n"); err != nil { return err } return nil } func (g *generator) writeElementAttributes(indentLevel int, name string, attrs []parser.Attribute) (err error) { for i := 0; i < len(attrs); i++ { switch attr := attrs[i].(type) { case parser.BoolConstantAttribute: err = g.writeBoolConstantAttribute(indentLevel, attr) case parser.ConstantAttribute: err = g.writeConstantAttribute(indentLevel, attr) case parser.BoolExpressionAttribute: err = g.writeBoolExpressionAttribute(indentLevel, attr) case parser.ExpressionAttribute: err = g.writeExpressionAttribute(indentLevel, name, attr) case parser.SpreadAttributes: err = g.writeSpreadAttributes(indentLevel, attr) case parser.ConditionalAttribute: err = g.writeConditionalAttribute(indentLevel, name, attr) default: err = fmt.Errorf("unknown attribute type %s", reflect.TypeOf(attrs[i])) } } return } func (g *generator) writeRawElement(indentLevel int, n parser.RawElement) (err error) { if len(n.Attributes) == 0 { //
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(`<%s>`, html.EscapeString(n.Name))); err != nil { return err } } else { // if err = g.writeElementScript(indentLevel, n.Attributes); err != nil { return err } //
if _, err = g.w.WriteStringLiteral(indentLevel, `>`); err != nil { return err } } // Contents. if err = g.writeText(indentLevel, parser.Text{Value: n.Contents}); err != nil { return err } //
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(``, html.EscapeString(n.Name))); err != nil { return err } return err } func (g *generator) writeComment(indentLevel int, c parser.HTMLComment) (err error) { // if _, err = g.w.WriteStringLiteral(indentLevel, "-->"); err != nil { return err } return err } func (g *generator) createVariableName() string { g.variableID++ return "templ_7745c5c3_Var" + strconv.Itoa(g.variableID) } func (g *generator) writeGoCode(indentLevel int, e parser.Expression) (err error) { if strings.TrimSpace(e.Value) == "" { return } var r parser.Range if r, err = g.w.WriteIndent(indentLevel, e.Value+"\n"); err != nil { return err } g.sourceMap.Add(e, r) return nil } func (g *generator) writeStringExpression(indentLevel int, e parser.Expression) (err error) { if strings.TrimSpace(e.Value) == "" { return } var r parser.Range vn := g.createVariableName() // var vn string if _, err = g.w.WriteIndent(indentLevel, "var "+vn+" string\n"); err != nil { return err } // vn, templ_7745c5c3_Err = templ.JoinStringErrs( if _, err = g.w.WriteIndent(indentLevel, vn+", templ_7745c5c3_Err = templ.JoinStringErrs("); err != nil { return err } // p.Name() if r, err = g.w.Write(e.Value); err != nil { return err } g.sourceMap.Add(e, r) // ) if _, err = g.w.Write(")\n"); err != nil { return err } // String expression error handler. err = g.writeExpressionErrorHandler(indentLevel, e) if err != nil { return err } // _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(vn) if _, err = g.w.WriteIndent(indentLevel, "_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("+vn+"))\n"); err != nil { return err } if err = g.writeErrorHandler(indentLevel); err != nil { return err } return nil } func (g *generator) writeWhitespace(indentLevel int, n parser.Whitespace) (err error) { if len(n.Value) == 0 { return } // _, err = templ_7745c5c3_Buffer.WriteString(` `) if _, err = g.w.WriteStringLiteral(indentLevel, " "); err != nil { return err } return nil } func (g *generator) writeText(indentLevel int, n parser.Text) (err error) { quoted := strconv.Quote(n.Value) _, err = g.w.WriteStringLiteral(indentLevel, quoted[1:len(quoted)-1]) return err } func createGoString(s string) string { var sb strings.Builder sb.WriteRune('`') sects := strings.Split(s, "`") for i := 0; i < len(sects); i++ { sb.WriteString(sects[i]) if len(sects) > i+1 { sb.WriteString("` + \"`\" + `") } } sb.WriteRune('`') return sb.String() } func (g *generator) writeScript(t parser.ScriptTemplate) error { var r parser.Range var tgtSymbolRange parser.Range var err error var indentLevel int // func if r, err = g.w.Write("func "); err != nil { return err } tgtSymbolRange.From = r.From if r, err = g.w.Write(t.Name.Value); err != nil { return err } g.sourceMap.Add(t.Name, r) // ( if _, err = g.w.Write("("); err != nil { return err } // Write parameters. if r, err = g.w.Write(t.Parameters.Value); err != nil { return err } g.sourceMap.Add(t.Parameters, r) // ) templ.ComponentScript { if _, err = g.w.Write(") templ.ComponentScript {\n"); err != nil { return err } indentLevel++ // return templ.ComponentScript{ if _, err = g.w.WriteIndent(indentLevel, "return templ.ComponentScript{\n"); err != nil { return err } { indentLevel++ fn := functionName(t.Name.Value, t.Value) goFn := createGoString(fn) // Name: "scriptName", if _, err = g.w.WriteIndent(indentLevel, "Name: "+goFn+",\n"); err != nil { return err } // Function: `function scriptName(a, b, c){` + `constantScriptValue` + `}`, prefix := "function " + fn + "(" + stripTypes(t.Parameters.Value) + "){" body := strings.TrimLeftFunc(t.Value, unicode.IsSpace) suffix := "}" if _, err = g.w.WriteIndent(indentLevel, "Function: "+createGoString(prefix+body+suffix)+",\n"); err != nil { return err } // Call: templ.SafeScript(scriptName, a, b, c) if _, err = g.w.WriteIndent(indentLevel, "Call: templ.SafeScript("+goFn+", "+stripTypes(t.Parameters.Value)+"),\n"); err != nil { return err } // CallInline: templ.SafeScriptInline(scriptName, a, b, c) if _, err = g.w.WriteIndent(indentLevel, "CallInline: templ.SafeScriptInline("+goFn+", "+stripTypes(t.Parameters.Value)+"),\n"); err != nil { return err } indentLevel-- } // } if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil { return err } indentLevel-- // } if r, err = g.w.WriteIndent(indentLevel, "}\n\n"); err != nil { return err } // Keep track of the symbol range for the LSP. tgtSymbolRange.To = r.To g.sourceMap.AddSymbolRange(t.Range, tgtSymbolRange) return nil } // writeBlankAssignmentForRuntimeImport writes out a blank identifier assignment. // This ensures that even if the github.com/a-h/templ/runtime package is not used in the generated code, // the Go compiler will not complain about the unused import. func (g *generator) writeBlankAssignmentForRuntimeImport() error { var err error if _, err = g.w.Write("var _ = templruntime.GeneratedTemplate"); err != nil { return err } return nil } func functionName(name string, body string) string { h := sha256.New() h.Write([]byte(body)) hp := hex.EncodeToString(h.Sum(nil))[0:4] return "__templ_" + name + "_" + hp } func stripTypes(parameters string) string { variableNames := []string{} params := strings.Split(parameters, ",") for i := 0; i < len(params); i++ { p := strings.Split(strings.TrimSpace(params[i]), " ") variableNames = append(variableNames, strings.TrimSpace(p[0])) } return strings.Join(variableNames, ", ") }