• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stackus / goht / 14768173832

01 May 2025 02:03AM UTC coverage: 79.722% (+0.2%) from 79.532%
14768173832

push

github

stackus
update documentation

2693 of 3378 relevant lines covered (79.72%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.21
/compiler/nodes.go
1
package compiler
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "html"
7
        "regexp"
8
        "slices"
9
        "strconv"
10
        "strings"
11

12
        "github.com/stackus/goht"
13
)
14

15
type nodeType int
16

17
const (
18
        nError nodeType = iota
19
        nRoot
20
        nGoCode
21
        nTemplate
22
        nIndent
23
        nDoctype
24
        nElement
25
        nNewLine
26
        nComment
27
        nText
28
        nRawText
29
        nUnescape
30
        nSilentScriptNode
31
        nScriptNode
32
        nRenderCommand
33
        nChildrenCommand
34
        nSlotCommand
35
        nFilter
36
)
37

38
func (n nodeType) String() string {
1✔
39
        switch n {
1✔
40
        case nError:
×
41
                return "Error"
×
42
        case nRoot:
1✔
43
                return "Root"
1✔
44
        case nGoCode:
1✔
45
                return "GoCode"
1✔
46
        case nTemplate:
1✔
47
                return "Template"
1✔
48
        case nIndent:
×
49
                return "Indent"
×
50
        case nDoctype:
1✔
51
                return "Doctype"
1✔
52
        case nElement:
1✔
53
                return "Element"
1✔
54
        case nNewLine:
1✔
55
                return "NewLine"
1✔
56
        case nComment:
1✔
57
                return "Comment"
1✔
58
        case nText:
1✔
59
                return "Text"
1✔
60
        case nRawText:
×
61
                return "RawText"
×
62
        case nUnescape:
1✔
63
                return "Unescape"
1✔
64
        case nSilentScriptNode:
1✔
65
                return "SilentScript"
1✔
66
        case nScriptNode:
1✔
67
                return "Script"
1✔
68
        case nRenderCommand:
1✔
69
                return "RenderCommand"
1✔
70
        case nChildrenCommand:
1✔
71
                return "ChildrenCommand"
1✔
72
        case nSlotCommand:
×
73
                return "SlotCommand"
×
74
        case nFilter:
×
75
                return "Filter"
×
76
        default:
×
77
                return "!Unknown!"
×
78
        }
79
}
80

81
type nodeBase interface {
82
        // Type returns the type of the node.
83
        Type() nodeType
84
        // Indent returns the indentation level of the node.
85
        Indent() int
86
        // Origin returns the origin of the node.
87
        Origin() token
88
        // Children return the children of the node.
89
        Children() []nodeBase
90
        // AddChild adds a child to the node.
91
        AddChild(nodeBase)
92
        // SetNextSibling sets the next sibling of the node.
93
        SetNextSibling(nodeBase)
94
        // Source returns the source code of the node.
95
        Source(tw *templateWriter) error
96
        Tree(buf *bytes.Buffer, indent int) string
97
}
98

99
type parsingNode interface {
100
        nodeBase
101
        // parse parses token data
102
        parse(*parser) error
103
}
104

105
type node struct {
106
        typ          nodeType
107
        indent       int
108
        origin       token
109
        children     []nodeBase
110
        nextSibling  nodeBase
111
        keepNewlines bool
112
}
113

114
func newNode(typ nodeType, indent int, origin token) node {
1✔
115
        return node{
1✔
116
                typ:    typ,
1✔
117
                indent: indent,
1✔
118
                origin: origin,
1✔
119
        }
1✔
120
}
1✔
121

122
func (n *node) Type() nodeType {
1✔
123
        return n.typ
1✔
124
}
1✔
125

126
func (n *node) Indent() int {
1✔
127
        return n.indent
1✔
128
}
1✔
129

130
func (n *node) Origin() token {
×
131
        return n.origin
×
132
}
×
133

134
func (n *node) Children() []nodeBase {
×
135
        return n.children
×
136
}
×
137

138
func (n *node) SetNextSibling(sibling nodeBase) {
1✔
139
        n.nextSibling = sibling
1✔
140
}
1✔
141

142
func (n *node) AddChild(c nodeBase) {
1✔
143
        if len(n.children) > 0 {
2✔
144
                n.children[len(n.children)-1].SetNextSibling(c)
1✔
145
        }
1✔
146
        n.children = append(n.children, c)
1✔
147
}
148

149
func (n *node) Tree(buf *bytes.Buffer, indent int) string {
1✔
150
        lead := strings.Repeat("\t", indent)
1✔
151
        buf.WriteString(lead + n.Type().String() + "\n")
1✔
152
        for _, c := range n.children {
2✔
153
                c.Tree(buf, indent+1)
1✔
154
        }
1✔
155
        return buf.String()
1✔
156
}
157

158
func (n *node) errorf(format string, args ...interface{}) error {
1✔
159
        return PositionalError{
1✔
160
                Line:   n.origin.line,
1✔
161
                Column: n.origin.col,
1✔
162
                Err:    fmt.Errorf(format, args...),
1✔
163
        }
1✔
164
}
1✔
165

166
func (n *node) handleNode(p *parser, indent int) error {
1✔
167
        t := p.peek()
1✔
168
        _ = t
1✔
169
        switch p.peek().Type() {
1✔
170
        case tKeepNewlines:
1✔
171
                n.keepNewlines = true
1✔
172
                p.next()
1✔
173
        case tRubyComment:
1✔
174
                p.next()
1✔
175
        case tNewLine:
1✔
176
                p.next()
1✔
177
                if n.keepNewlines {
2✔
178
                        p.addChild(NewNewLineNode(t))
1✔
179
                }
1✔
180
        case tIndent:
1✔
181
                nextIndent := len(p.peek().lit)
1✔
182
                if nextIndent <= n.indent {
2✔
183
                        return p.backToIndent(nextIndent - 1)
1✔
184
                }
1✔
185
                p.next()
1✔
186
                return n.handleNode(p, nextIndent)
1✔
187
        case tDoctype:
1✔
188
                p.addChild(NewDoctypeNode(p.next()))
1✔
189
        case tTag, tId, tClass:
1✔
190
                p.addNode(NewElementNode(p.next(), indent, n.keepNewlines))
1✔
191
        case tAttrName:
1✔
192
                p.addNode(NewElementNode(p.peek(), indent, n.keepNewlines))
1✔
193
        case tComment:
1✔
194
                p.addNode(NewCommentNode(p.next(), indent, n.keepNewlines))
1✔
195
        case tUnescaped:
1✔
196
                p.addNode(NewUnescapeNode(p.next(), indent))
1✔
197
        case tPlainText, tPreserveText, tEscapedText, tDynamicText:
1✔
198
                p.addChild(NewTextNode(p.next()))
1✔
199
        case tRawText:
1✔
200
                p.addNode(NewRawTextNode(p.next(), indent))
1✔
201
        case tSilentScript:
1✔
202
                p.addNode(NewSilentScriptNode(p.next(), indent, n.keepNewlines))
1✔
203
        case tScript:
1✔
204
                p.addChild(NewScriptNode(p.next(), n.keepNewlines))
1✔
205
        case tRenderCommand:
1✔
206
                p.addNode(NewRenderCommandNode(p.next(), indent, n.keepNewlines))
1✔
207
        case tChildrenCommand:
1✔
208
                p.addChild(NewChildrenCommandNode(p.next()))
1✔
209
        case tSlotCommand:
1✔
210
                p.addNode(NewSlotCommandNode(p.next(), indent, n.keepNewlines))
1✔
211
        case tFilterStart:
1✔
212
                t := p.next()
1✔
213
                switch t.lit {
1✔
214
                case "javascript":
1✔
215
                        p.addNode(NewJavaScriptFilterNode(t, indent))
1✔
216
                case "css":
1✔
217
                        p.addNode(NewCssFilterNode(t, indent))
1✔
218
                case "plain", "escaped", "preserve":
1✔
219
                        p.addNode(NewTextFilterNode(t, indent))
1✔
220
                default:
×
221
                        return n.errorf("unknown filter: %s", t)
×
222
                }
223
        case tTemplateEnd:
1✔
224
                return p.backToType(nTemplate)
1✔
225
        case tEOF:
1✔
226
                return n.errorf("template is incomplete: %s", p.peek())
1✔
227
        case tError:
×
228
                return PositionalError{
×
229
                        Line:   t.line,
×
230
                        Column: t.col,
×
231
                        Err:    fmt.Errorf(t.lit),
×
232
                }
×
233
        default:
×
234
                return n.errorf("unexpected: %s", p.peek())
×
235
        }
236
        return nil
1✔
237
}
238

239
type RootNode struct {
240
        node
241
        pkg         token
242
        imports     []string
243
        userImports []token
244
}
245

246
func NewRootNode() *RootNode {
1✔
247
        return &RootNode{
1✔
248
                node: newNode(nRoot, 0, token{typ: tRoot, line: 0, col: 0}),
1✔
249
                pkg:  token{typ: tPackage, line: 0, col: 0, lit: "main"},
1✔
250
                imports: []string{
1✔
251
                        "\"context\"",
1✔
252
                        "\"io\"",
1✔
253
                        "\"github.com/stackus/goht\"",
1✔
254
                },
1✔
255
        }
1✔
256
}
1✔
257

258
func (n *RootNode) Source(tw *templateWriter) error {
1✔
259
        if _, err := tw.Write(
1✔
260
                fmt.Sprintf("// Code generated by GoHT %s - DO NOT EDIT.\n// https://github.com/stackus/goht\n\n",
1✔
261
                        goht.Version(),
1✔
262
                )); err != nil {
1✔
263
                return err
×
264
        }
×
265

266
        if _, err := tw.Write("package "); err != nil {
1✔
267
                return err
×
268
        }
×
269
        r, err := tw.Write(n.pkg.lit)
1✔
270
        if err != nil {
1✔
271
                return err
×
272
        }
×
273
        tw.Add(n.pkg, r)
1✔
274
        if _, err := tw.Write("\n\n"); err != nil {
1✔
275
                return err
×
276
        }
×
277

278
        for _, pkg := range n.imports {
2✔
279
                if _, err := tw.Write("import " + pkg + "\n"); err != nil {
1✔
280
                        return err
×
281
                }
×
282
        }
283

284
        if len(n.userImports) > 0 {
2✔
285
                if _, err := tw.Write("import (\n"); err != nil {
1✔
286
                        return err
×
287
                }
×
288
                itw := tw.Indent(1)
1✔
289
                for _, pkg := range n.userImports {
2✔
290
                        r, err := itw.WriteIndent(pkg.lit)
1✔
291
                        if err != nil {
1✔
292
                                return err
×
293
                        }
×
294
                        tw.Add(pkg, r)
1✔
295
                        if _, err := itw.Write("\n"); err != nil {
1✔
296
                                return err
×
297
                        }
×
298
                }
299
                if _, err := tw.Write(")\n"); err != nil {
1✔
300
                        return err
×
301
                }
×
302
        }
303

304
        for _, c := range n.children {
2✔
305
                if err := c.Source(tw); err != nil {
1✔
306
                        return err
×
307
                }
×
308
        }
309
        return nil
1✔
310
}
311

312
func (n *RootNode) addImport(t token) {
1✔
313
        if slices.Contains(n.imports, t.lit) {
1✔
314
                return
×
315
        }
×
316
        for _, i := range n.userImports {
1✔
317
                if i.lit == t.lit {
×
318
                        return
×
319
                }
×
320
        }
321
        n.userImports = append(n.userImports, t)
1✔
322
}
323

324
func (n *RootNode) parse(p *parser) error {
1✔
325
        switch p.peek().Type() {
1✔
326
        case tPackage:
1✔
327
                n.pkg = p.next()
1✔
328
        case tImport:
1✔
329
                t := p.next()
1✔
330
                n.addImport(t)
1✔
331
        case tGoCode, tNewLine:
1✔
332
                p.addNode(NewCodeNode(p.next()))
1✔
333
        case tTemplateStart:
1✔
334
                p.addNode(NewTemplateNode(p.next()))
1✔
335
        case tEOF:
1✔
336
                p.next()
1✔
337
                return nil
1✔
338
        default:
×
339
                return n.errorf("unexpected: %s", p.peek())
×
340
        }
341

342
        return nil
1✔
343
}
344

345
type CodeNode struct {
346
        node
347
        text   *strings.Builder
348
        tokens []token
349
}
350

351
func NewCodeNode(t token) *CodeNode {
1✔
352
        builder := &strings.Builder{}
1✔
353
        builder.WriteString(t.lit)
1✔
354
        return &CodeNode{
1✔
355
                node:   newNode(nGoCode, 0, t),
1✔
356
                text:   builder,
1✔
357
                tokens: []token{t},
1✔
358
        }
1✔
359
}
1✔
360

361
func (n *CodeNode) Source(tw *templateWriter) error {
1✔
362
        for _, t := range n.tokens {
2✔
363
                if r, err := tw.Write(t.lit); err != nil {
1✔
364
                        return err
×
365
                } else if t.typ != tNewLine {
2✔
366
                        tw.Add(t, r)
1✔
367
                }
1✔
368
        }
369
        return nil
1✔
370
}
371

372
func (n *CodeNode) parse(p *parser) error {
1✔
373
        switch p.peek().Type() {
1✔
374
        case tGoCode, tNewLine:
1✔
375
                t := p.next()
1✔
376
                _, err := n.text.WriteString(t.lit)
1✔
377
                if err != nil {
1✔
378
                        return err
×
379
                }
×
380
                n.tokens = append(n.tokens, t)
1✔
381
                return nil
1✔
382
        case tPackage, tImport, tTemplateStart, tEOF:
1✔
383
                return p.backToType(nRoot)
1✔
384
        default:
×
385
                return n.errorf("unexpected: %s", p.peek())
×
386
        }
387
}
388

389
type TemplateNode struct {
390
        node
391
        decl string
392
}
393

394
func NewTemplateNode(t token) *TemplateNode {
1✔
395
        return &TemplateNode{
1✔
396
                node: newNode(nTemplate, -1, t),
1✔
397
                decl: t.lit,
1✔
398
        }
1✔
399
}
1✔
400

401
func (n *TemplateNode) Source(tw *templateWriter) error {
1✔
402
        entry := ` goht.Template {
1✔
403
        return goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) {
1✔
404
                __buf, __isBuf := __w.(goht.Buffer)
1✔
405
                if !__isBuf {
1✔
406
                        __buf = goht.GetBuffer()
1✔
407
                        defer goht.ReleaseBuffer(__buf)
1✔
408
                }
1✔
409
                var __children goht.Template
1✔
410
                ctx, __children = goht.PopChildren(ctx)
1✔
411
                _ = __children
1✔
412
`
1✔
413
        exit := `                if !__isBuf {
1✔
414
                        _, __err = __w.Write(__buf.Bytes())
1✔
415
                }
1✔
416
                return
1✔
417
        })
1✔
418
}`
1✔
419
        tw.ResetVarName()
1✔
420
        if _, err := tw.Write("func "); err != nil {
1✔
421
                return err
×
422
        }
×
423
        if r, err := tw.Write(n.decl); err != nil {
1✔
424
                return err
×
425
        } else {
1✔
426
                tw.Add(n.origin, r)
1✔
427
        }
1✔
428
        if _, err := tw.Write(entry); err != nil {
1✔
429
                return err
×
430
        }
×
431

432
        itw := tw.Indent(2)
1✔
433
        for _, c := range n.children {
2✔
434
                if err := c.Source(itw); err != nil {
1✔
435
                        return err
×
436
                }
×
437
        }
438
        // ensure the template ends with a newline
439
        if !n.keepNewlines {
1✔
440
                if _, err := itw.WriteStringLiteral("\\n"); err != nil {
×
441
                        return err
×
442
                }
×
443
        }
444
        if _, err := itw.Close(); err != nil {
1✔
445
                return err
×
446
        }
×
447

448
        _, err := tw.Write(exit)
1✔
449
        return err
1✔
450
}
451

452
func (n *TemplateNode) parse(p *parser) error {
1✔
453
        switch p.peek().Type() {
1✔
454
        case tTemplateEnd:
1✔
455
                p.next()
1✔
456
                return p.backToType(nRoot)
1✔
457
        default:
1✔
458
                return n.handleNode(p, 0)
1✔
459
        }
460
}
461

462
type DoctypeNode struct {
463
        node
464
        doctype string
465
}
466

467
func NewDoctypeNode(t token) *DoctypeNode {
1✔
468
        return &DoctypeNode{
1✔
469
                node:    newNode(nDoctype, 0, t),
1✔
470
                doctype: t.lit,
1✔
471
        }
1✔
472
}
1✔
473

474
func (n *DoctypeNode) Source(tw *templateWriter) error {
1✔
475
        _, err := tw.WriteStringLiteral(`<!DOCTYPE html>`)
1✔
476
        return err
1✔
477
}
1✔
478

479
type attribute struct {
480
        name      string
481
        isBoolean bool
482
        isDynamic bool
483
        value     string
484
        origin    token
485
}
486

487
type ElementNode struct {
488
        node
489
        tag                 string
490
        id                  string
491
        classes             []token
492
        objectRef           *token
493
        attributes          *OrderedMap[attribute]
494
        attributesCmd       string
495
        disallowChildren    bool
496
        isSelfClosing       bool
497
        nukeInnerWhitespace bool
498
        nukeOuterWhitespace bool
499
        addWhitespaceBefore bool
500
        addWhitespaceAfter  bool
501
        isComplete          bool
502
}
503

504
func NewElementNode(t token, indent int, keepNewlines bool) *ElementNode {
1✔
505
        n := &ElementNode{
1✔
506
                node:       newNode(nElement, indent, t),
1✔
507
                tag:        "div",
1✔
508
                attributes: NewOrderedMap[attribute](),
1✔
509
        }
1✔
510

1✔
511
        switch t.Type() {
1✔
512
        case tTag:
1✔
513
                n.tag = t.lit
1✔
514
        case tId:
1✔
515
                n.id = t.lit
1✔
516
        case tClass:
1✔
517
                n.classes = append(n.classes, t)
1✔
518
        }
519

520
        if keepNewlines {
2✔
521
                n.keepNewlines = true
1✔
522
        }
1✔
523

524
        return n
1✔
525
}
526

527
func (n *ElementNode) Source(tw *templateWriter) error {
1✔
528
        if n.nukeOuterWhitespace {
2✔
529
                if _, err := tw.WriteStringLiteral(goht.NukeBefore); err != nil {
1✔
530
                        return err
×
531
                }
×
532
        }
533
        if n.addWhitespaceBefore {
1✔
534
                if _, err := tw.WriteStringLiteral(" "); err != nil {
×
535
                        return err
×
536
                }
×
537
        }
538

539
        if _, err := tw.WriteStringLiteral("<" + n.tag); err != nil {
1✔
540
                return err
×
541
        }
×
542
        // write attributes (and classes)
543
        if err := n.renderAttributes(tw); err != nil {
1✔
544
                return err
×
545
        }
×
546
        // close the tag
547
        if n.isSelfClosing {
1✔
548
                // add a "/" to the end of the tag as long as it's not in the list of tags that shouldn't get it
×
549
                if !slices.Contains(selfClosedTags, strings.ToLower(n.tag)) {
×
550
                        if _, err := tw.WriteStringLiteral("/"); err != nil {
×
551
                                return err
×
552
                        }
×
553
                }
554
        }
555
        if _, err := tw.WriteStringLiteral(">"); err != nil {
1✔
556
                return err
×
557
        }
×
558
        if n.isSelfClosing {
1✔
559
                return nil
×
560
        }
×
561

562
        if n.nukeInnerWhitespace {
2✔
563
                if _, err := tw.WriteStringLiteral(goht.NukeAfter); err != nil {
1✔
564
                        return err
×
565
                }
×
566
        }
567

568
        // ignore children if there is only a newline
569
        if !(len(n.children) == 1 && n.children[0].Type() == nNewLine) {
2✔
570
                for _, c := range n.children {
2✔
571
                        if err := c.Source(tw); err != nil {
1✔
572
                                return err
×
573
                        }
×
574
                }
575
        }
576

577
        if n.nukeInnerWhitespace {
2✔
578
                if _, err := tw.WriteStringLiteral(goht.NukeBefore); err != nil {
1✔
579
                        return err
×
580
                }
×
581
        }
582

583
        if _, err := tw.WriteStringLiteral("</" + n.tag + ">"); err != nil {
1✔
584
                return err
×
585
        }
×
586
        if n.nukeOuterWhitespace {
2✔
587
                if _, err := tw.WriteStringLiteral(goht.NukeAfter); err != nil {
1✔
588
                        return err
×
589
                }
×
590
        } else if n.keepNewlines {
2✔
591
                if _, err := tw.WriteStringLiteral("\\n"); err != nil {
1✔
592
                        return err
×
593
                }
×
594
        }
595
        if n.addWhitespaceAfter {
1✔
596
                if _, err := tw.WriteStringLiteral(" "); err != nil {
×
597
                        return err
×
598
                }
×
599
        }
600

601
        return nil
1✔
602
}
603

604
func (n *ElementNode) renderAttributes(tw *templateWriter) error {
1✔
605
        if n.objectRef != nil {
2✔
606
                vName := tw.GetVarName()
1✔
607
                if _, err := tw.WriteIndent(`if ` + vName + ` := goht.ObjectID(`); err != nil {
1✔
608
                        return err
×
609
                }
×
610
                if r, err := tw.Write(n.objectRef.lit); err != nil {
1✔
611
                        return err
×
612
                } else {
1✔
613
                        tw.Add(*n.objectRef, r)
1✔
614
                }
1✔
615
                if _, err := tw.Write("); " + vName + " != \"\" {\n"); err != nil {
1✔
616
                        return err
×
617
                }
×
618
                if _, err := tw.WriteIndent("\t" + `if _, __err = __buf.WriteString(" id=\""+` + vName + `+"\""` + "); __err != nil { return }\n"); err != nil {
1✔
619
                        return err
×
620
                }
×
621
                if _, err := tw.WriteIndent("}\n"); err != nil {
1✔
622
                        return err
×
623
                }
×
624
        }
625
        if n.id != "" {
2✔
626
                if _, err := tw.WriteStringLiteral(` id=\"` + html.EscapeString(n.id) + `\"`); err != nil {
1✔
627
                        return err
×
628
                }
×
629
        }
630
        if err := n.renderClass(tw); err != nil {
1✔
631
                return err
×
632
        }
×
633
        for _, key := range n.attributes.keys {
2✔
634
                attr := n.attributes.values[key]
1✔
635
                if attr.value == "" {
2✔
636
                        if _, err := tw.WriteStringLiteral(` ` + attr.name); err != nil {
1✔
637
                                return err
×
638
                        }
×
639
                        continue
1✔
640
                }
641
                if attr.isBoolean {
2✔
642
                        if _, err := tw.WriteIndent("if "); err != nil {
1✔
643
                                return err
×
644
                        }
×
645
                        if r, err := tw.Write(attr.value); err != nil {
1✔
646
                                return err
×
647
                        } else {
1✔
648
                                tw.Add(attr.origin, r)
1✔
649
                        }
1✔
650
                        if _, err := tw.Write(" {\n"); err != nil {
1✔
651
                                return err
×
652
                        }
×
653
                        itw := tw.Indent(1)
1✔
654
                        if _, err := itw.WriteStringLiteral(" " + attr.name); err != nil {
1✔
655
                                return err
×
656
                        }
×
657
                        if _, err := itw.Close(); err != nil {
1✔
658
                                return err
×
659
                        }
×
660
                        if _, err := tw.WriteIndent("}\n"); err != nil {
1✔
661
                                return err
×
662
                        }
×
663
                        continue
1✔
664
                }
665
                if _, err := tw.WriteStringLiteral(` ` + attr.name + `=\"`); err != nil {
1✔
666
                        return err
×
667
                }
×
668
                if attr.isDynamic {
2✔
669
                        if _, err := tw.WriteIndent(`if _, __err = __buf.WriteString(goht.EscapeString(`); err != nil {
1✔
670
                                return err
×
671
                        }
×
672
                        if err := writeFormattedText(tw, attr.origin); err != nil {
1✔
673
                                return err
×
674
                        }
×
675
                        if _, err := tw.Write(")+" + `"\""` + "); __err != nil { return }\n"); err != nil {
1✔
676
                                return err
×
677
                        }
×
678
                        continue
1✔
679
                }
680
                if _, err := tw.WriteStringLiteral(html.EscapeString(attr.value) + `\"`); err != nil {
1✔
681
                        return err
×
682
                }
×
683
        }
684
        if n.attributesCmd != "" {
2✔
685
                vName := tw.GetVarName()
1✔
686
                if _, err := tw.WriteIndent(`var ` + vName + " string\n"); err != nil {
1✔
687
                        return err
×
688
                }
×
689
                if _, err := tw.WriteIndent(vName + `, __err = goht.BuildAttributeList(` + n.attributesCmd + ")\n"); err != nil {
1✔
690
                        return err
×
691
                }
×
692
                if _, err := tw.WriteErrorHandler(); err != nil {
1✔
693
                        return err
×
694
                }
×
695
                if _, err := tw.WriteStringIndent(`" "+` + vName); err != nil {
1✔
696
                        return err
×
697
                }
×
698
        }
699
        return nil
1✔
700
}
701

702
func (n *ElementNode) renderClass(tw *templateWriter) error {
1✔
703
        if n.objectRef != nil {
2✔
704
                n.classes = append(n.classes, *n.objectRef)
1✔
705
        }
1✔
706
        if classes, ok := n.attributes.Get("class"); ok {
1✔
707
                if classes.isDynamic {
×
708
                        n.classes = append(n.classes, classes.origin)
×
709
                } else {
×
710
                        n.classes = append(n.classes, classes.origin)
×
711
                }
×
712
                n.attributes.Delete("class")
×
713
        }
714
        if len(n.classes) == 0 {
2✔
715
                return nil
1✔
716
        }
1✔
717
        allQuoted := true
1✔
718
        for _, class := range n.classes {
2✔
719
                switch class.typ {
1✔
720
                case tObjectRef, tAttrDynamicValue:
1✔
721
                        allQuoted = false
1✔
722
                default:
1✔
723
                }
724
                if !allQuoted {
2✔
725
                        break
1✔
726
                }
727
        }
728
        if allQuoted {
2✔
729
                classes := make([]string, len(n.classes))
1✔
730
                for i, class := range n.classes {
2✔
731
                        var name string
1✔
732
                        var err error
1✔
733
                        switch class.typ {
1✔
734
                        case tClass:
1✔
735
                                name = class.lit
1✔
736
                        case tAttrEscapedValue:
×
737
                                name, err = strconv.Unquote(class.lit)
×
738
                                if err != nil {
×
739
                                        return fmt.Errorf("failed to unquote class: %s error: %w", class.lit, err)
×
740
                                }
×
741
                        }
742
                        classes[i] = name
1✔
743
                }
744
                _, err := tw.WriteStringLiteral(` class=\"` + strings.Join(classes, " ") + `\"`)
1✔
745
                return err
1✔
746
        }
747

748
        vName := tw.GetVarName()
1✔
749
        if _, err := tw.WriteIndent(`var ` + vName + " string\n"); err != nil {
1✔
750
                return err
×
751
        }
×
752
        if _, err := tw.WriteIndent(vName + ", __err = goht.BuildClassList("); err != nil {
1✔
753
                return err
×
754
        }
×
755
        for i, class := range n.classes {
2✔
756
                switch class.typ {
1✔
757
                case tObjectRef:
1✔
758
                        if _, err := tw.Write("goht.ObjectClass(" + class.lit + ")"); err != nil {
1✔
759
                                return err
×
760
                        }
×
761
                case tAttrDynamicValue:
×
762
                        if r, err := tw.Write(class.lit); err != nil {
×
763
                                return err
×
764
                        } else {
×
765
                                tw.Add(class, r)
×
766
                        }
×
767
                case tClass:
×
768
                        if _, err := tw.Write(strconv.Quote(class.lit)); err != nil {
×
769
                                return err
×
770
                        }
×
771
                default:
×
772
                        if _, err := tw.Write(class.lit); err != nil {
×
773
                                return err
×
774
                        }
×
775
                }
776
                if i < len(n.classes)-1 {
1✔
777
                        if _, err := tw.Write(", "); err != nil {
×
778
                                return err
×
779
                        }
×
780
                }
781
        }
782
        if _, err := tw.Write(")\n"); err != nil {
1✔
783
                return err
×
784
        }
×
785
        if _, err := tw.WriteErrorHandler(); err != nil {
1✔
786
                return err
×
787
        }
×
788
        if _, err := tw.WriteStringIndent(`" class=\""+` + vName + `+"\""`); err != nil {
1✔
789
                return err
×
790
        }
×
791

792
        return nil
1✔
793
}
794

795
var selfClosedTags = []string{
796
        "area", "base", "basefont", "br", "col",
797
        "embed", "frame", "hr", "img", "input",
798
        "isindex", "keygen", "link", "menuitem",
799
        "meta", "param", "source", "track", "wbr",
800
}
801

802
func (n *ElementNode) parse(p *parser) error {
1✔
803
        t := p.peek()
1✔
804
        _ = t
1✔
805
        if n.isComplete {
2✔
806
                switch p.peek().Type() {
1✔
807
                case tIndent:
1✔
808
                        nextIndent := len(p.peek().lit)
1✔
809
                        if nextIndent <= n.indent {
2✔
810
                                return p.backToIndent(nextIndent - 1)
1✔
811
                        }
1✔
812
                        if nextIndent > n.indent && (n.disallowChildren || n.isSelfClosing) {
2✔
813
                                if n.isSelfClosing {
1✔
814
                                        return n.errorf("illegal nesting: self-closing tags can't have content %s", p.peek())
×
815
                                }
×
816
                                return n.errorf("illegal nesting: content can't be both given on the same line and nested %s", p.peek())
1✔
817
                        }
818
                default:
1✔
819
                        return n.handleNode(p, n.indent+1)
1✔
820
                }
821
        }
822
        switch p.peek().Type() {
1✔
823
        case tNewLine:
1✔
824
                p.next()
1✔
825
                n.isComplete = true
1✔
826
                if slices.Contains(selfClosedTags, n.tag) {
2✔
827
                        n.isSelfClosing = true
1✔
828
                }
1✔
829
                if n.isSelfClosing || len(n.children) > 0 {
2✔
830
                        n.disallowChildren = true
1✔
831
                }
1✔
832
                if len(n.children) == 0 && n.keepNewlines {
2✔
833
                        n.AddChild(NewNewLineNode(t))
1✔
834
                }
1✔
835
        case tId:
1✔
836
                n.id = html.EscapeString(p.next().lit)
1✔
837
        case tClass:
1✔
838
                n.classes = append(n.classes, p.next())
1✔
839
        case tObjectRef:
1✔
840
                t := p.next()
1✔
841
                n.objectRef = &t
1✔
842
        case tAttrName:
1✔
843
                return n.parseAttributes(p)
1✔
844
        case tAttributesCommand:
1✔
845
                n.attributesCmd = p.next().lit
1✔
846
        case tVoidTag:
1✔
847
                p.next()
1✔
848
                n.isSelfClosing = true
1✔
849
        case tNukeOuterWhitespace:
1✔
850
                p.next()
1✔
851
                n.nukeOuterWhitespace = true
1✔
852
        case tNukeInnerWhitespace:
1✔
853
                p.next()
1✔
854
                n.nukeInnerWhitespace = true
1✔
855
        case tAddWhitespaceBefore:
×
856
                p.next()
×
857
                n.addWhitespaceBefore = true
×
858
        case tAddWhitespaceAfter:
×
859
                p.next()
×
860
                n.addWhitespaceAfter = true
×
861
        default:
1✔
862
                return n.handleNode(p, n.indent+1)
1✔
863
        }
864
        return nil
1✔
865
}
866

867
func (n *ElementNode) parseAttributes(p *parser) error {
1✔
868
        for {
2✔
869
                if p.peek().Type() != tAttrName {
2✔
870
                        break
1✔
871
                }
872
                isBoolean := false
1✔
873
                isDynamic := false
1✔
874
                value := ""
1✔
875
                var origin token
1✔
876

1✔
877
                name := p.next().lit
1✔
878
                switch p.peek().Type() {
1✔
879
                case tAttrOperator:
1✔
880
                        op := p.next().lit
1✔
881
                        switch op {
1✔
882
                        case "?":
1✔
883
                                isBoolean = true
1✔
884
                                if p.peek().Type() != tAttrDynamicValue {
1✔
885
                                        return n.errorf("expected dynamic value: %s", p.peek())
×
886
                                }
×
887
                        }
888
                default:
1✔
889
                        n.attributes.Set(name, attribute{
1✔
890
                                name: name,
1✔
891
                        })
1✔
892
                        continue
1✔
893
                }
894
                if p.peek().Type() != tAttrDynamicValue && p.peek().Type() != tAttrEscapedValue {
1✔
895
                        return n.errorf("expected attribute value: %s", p.peek())
×
896
                }
×
897
                origin = p.next()
1✔
898
                if origin.typ == tAttrDynamicValue {
2✔
899
                        isDynamic = true
1✔
900
                        value = origin.lit
1✔
901
                } else {
2✔
902
                        value, _ = strconv.Unquote(origin.lit)
1✔
903
                }
1✔
904

905
                n.attributes.Set(name, attribute{
1✔
906
                        name:      name,
1✔
907
                        isBoolean: isBoolean,
1✔
908
                        isDynamic: isDynamic,
1✔
909
                        value:     value,
1✔
910
                        origin:    origin,
1✔
911
                })
1✔
912
        }
913
        return nil
1✔
914
}
915

916
func (n *ElementNode) Tree(buf *bytes.Buffer, indent int) string {
1✔
917
        lead := strings.Repeat("\t", indent)
1✔
918
        // build a list of attributes
1✔
919
        attrs := make([]string, 0, n.attributes.Len())
1✔
920

1✔
921
        err := n.attributes.Range(func(_ string, attr attribute) (bool, error) {
2✔
922
                a := attr.name
1✔
923
                if attr.value == "" {
2✔
924
                        attrs = append(attrs, a)
1✔
925
                        return true, nil
1✔
926
                }
1✔
927
                if attr.isBoolean {
1✔
928
                        a += "?"
×
929
                }
×
930
                a += "="
1✔
931
                if attr.isDynamic {
2✔
932
                        a += "{" + attr.value + "}"
1✔
933
                } else {
2✔
934
                        a += fmt.Sprintf("%q", attr.value)
1✔
935
                }
1✔
936
                attrs = append(attrs, a)
1✔
937
                return true, nil
1✔
938
        })
939
        if err != nil {
1✔
940
                panic(err)
×
941
        }
942
        if n.attributesCmd != "" {
2✔
943
                attrs = append(attrs, "@attrs={"+n.attributesCmd+"...}")
1✔
944
        }
1✔
945

946
        buf.WriteString(lead + n.Type().String() + " " + n.tag + "(" + strings.Join(attrs, ",") + ")\n")
1✔
947
        for _, c := range n.children {
2✔
948
                c.Tree(buf, indent+1)
1✔
949
        }
1✔
950
        return buf.String()
1✔
951
}
952

953
type NewLineNode struct {
954
        node
955
}
956

957
func NewNewLineNode(t token) *NewLineNode {
1✔
958
        return &NewLineNode{
1✔
959
                node: newNode(nNewLine, 0, t),
1✔
960
        }
1✔
961
}
1✔
962

963
func (n *NewLineNode) Source(tw *templateWriter) error {
1✔
964
        _, err := tw.WriteStringLiteral("\\n")
1✔
965
        return err
1✔
966
}
1✔
967

968
type CommentNode struct {
969
        node
970
        text string
971
}
972

973
func NewCommentNode(t token, indent int, keepNewlines bool) *CommentNode {
1✔
974
        n := &CommentNode{
1✔
975
                node: newNode(nComment, indent, t),
1✔
976
                text: t.lit,
1✔
977
        }
1✔
978

1✔
979
        if keepNewlines {
2✔
980
                n.keepNewlines = true
1✔
981
        }
1✔
982

983
        return n
1✔
984
}
985

986
func (n *CommentNode) Source(tw *templateWriter) error {
1✔
987
        if n.text != "" {
2✔
988
                if _, err := tw.WriteStringLiteral("<!--" + html.EscapeString(n.text) + "-->"); err != nil {
1✔
989
                        return err
×
990
                }
×
991
                if n.keepNewlines {
2✔
992
                        if _, err := tw.WriteStringLiteral("\\n"); err != nil {
1✔
993
                                return err
×
994
                        }
×
995
                }
996
        }
997
        // ignore children if there is only a newline
998
        if len(n.children) == 0 || len(n.children) == 1 && n.children[0].Type() == nNewLine {
2✔
999
                return nil
1✔
1000
        }
1✔
1001
        if _, err := tw.WriteStringLiteral("<!--"); err != nil {
1✔
1002
                return err
×
1003
        }
×
1004

1005
        for _, c := range n.children {
2✔
1006
                if err := c.Source(tw); err != nil {
1✔
1007
                        return err
×
1008
                }
×
1009
        }
1010

1011
        if _, err := tw.WriteStringLiteral("-->"); err != nil {
1✔
1012
                return err
×
1013
        }
×
1014
        if n.keepNewlines {
2✔
1015
                if _, err := tw.WriteStringLiteral("\\n"); err != nil {
1✔
1016
                        return err
×
1017
                }
×
1018
        }
1019

1020
        return nil
1✔
1021
}
1022

1023
func (n *CommentNode) parse(p *parser) error {
1✔
1024
        if p.peek().Type() == tIndent {
2✔
1025
                nextIndent := len(p.peek().lit)
1✔
1026
                if nextIndent <= n.indent {
2✔
1027
                        return p.backToIndent(nextIndent - 1)
1✔
1028
                }
1✔
1029
                if nextIndent > n.indent && n.text != "" {
1✔
1030
                        return n.errorf("illegal nesting: content can't be both given on the same line and nested %s", p.peek())
×
1031
                }
×
1032
        }
1033
        return n.handleNode(p, n.indent+1)
1✔
1034
}
1035

1036
type TextNode struct {
1037
        node
1038
        text       string
1039
        isDynamic  bool
1040
        isPlain    bool
1041
        isPreserve bool
1042
}
1043

1044
func NewTextNode(t token) *TextNode {
1✔
1045
        return &TextNode{
1✔
1046
                node:       newNode(nText, 0, t),
1✔
1047
                text:       t.lit,
1✔
1048
                isDynamic:  t.Type() == tDynamicText,
1✔
1049
                isPlain:    t.Type() == tPlainText,
1✔
1050
                isPreserve: t.Type() == tPreserveText,
1✔
1051
        }
1✔
1052
}
1✔
1053

1054
var reFmtText = regexp.MustCompile(`\A(%[^ ]+?) (.+)\z`)
1055

1056
func writeFormattedText(tw *templateWriter, t token) error {
1✔
1057
        if matches := reFmtText.FindStringSubmatch(t.lit); matches != nil {
1✔
1058
                if _, err := tw.Write(`goht.FormatString("`); err != nil {
×
1059
                        return err
×
1060
                }
×
1061
                if r, err := tw.Write(matches[1]); err != nil {
×
1062
                        return err
×
1063
                } else {
×
1064
                        fmtT := token{
×
1065
                                typ:  tDynamicText,
×
1066
                                line: t.line,
×
1067
                                col:  t.col,
×
1068
                                lit:  matches[1],
×
1069
                        }
×
1070
                        tw.Add(fmtT, r)
×
1071
                }
×
1072
                if _, err := tw.Write(`", `); err != nil {
×
1073
                        return err
×
1074
                }
×
1075
                if r, err := tw.Write(matches[2]); err != nil {
×
1076
                        return err
×
1077
                } else {
×
1078
                        strT := token{
×
1079
                                typ:  tDynamicText,
×
1080
                                line: t.line,
×
1081
                                col:  t.col + len(t.lit) - len(matches[2]),
×
1082
                                lit:  matches[2],
×
1083
                        }
×
1084
                        tw.Add(strT, r)
×
1085
                }
×
1086
                if _, err := tw.Write(")"); err != nil {
×
1087
                        return err
×
1088
                }
×
1089
                return nil
×
1090
        }
1091
        if r, err := tw.Write(strings.TrimSpace(strings.Replace(t.lit, "\n", " ", -1))); err != nil {
1✔
1092
                return err
×
1093
        } else {
1✔
1094
                tw.Add(t, r)
1✔
1095
        }
1✔
1096
        return nil
1✔
1097
}
1098

1099
func (n *TextNode) Source(tw *templateWriter) error {
1✔
1100
        if n.isDynamic {
2✔
1101
                vName := tw.GetVarName()
1✔
1102
                if _, err := tw.WriteIndent(`var ` + vName + " string\n"); err != nil {
1✔
1103
                        return err
×
1104
                }
×
1105

1106
                if _, err := tw.WriteIndent(`if ` + vName + `, __err = goht.CaptureErrors(`); err != nil {
1✔
1107
                        return err
×
1108
                }
×
1109
                if !tw.isUnescaped {
2✔
1110
                        if _, err := tw.Write(`goht.EscapeString(`); err != nil {
1✔
1111
                                return err
×
1112
                        }
×
1113
                }
1114
                if err := writeFormattedText(tw, n.origin); err != nil {
1✔
1115
                        return err
×
1116
                }
×
1117
                if !tw.isUnescaped {
2✔
1118
                        if _, err := tw.Write(")"); err != nil {
1✔
1119
                                return err
×
1120
                        }
×
1121
                }
1122
                if _, err := tw.Write("); __err != nil { return }\n"); err != nil {
1✔
1123
                        return err
×
1124
                }
×
1125

1126
                if _, err := tw.WriteStringIndent(vName); err != nil {
1✔
1127
                        return err
×
1128
                }
×
1129
                return nil
1✔
1130
        }
1131

1132
        s := n.text
1✔
1133
        s = strconv.Quote(n.text)
1✔
1134
        s = s[1 : len(s)-1]
1✔
1135

1✔
1136
        if n.isPreserve {
2✔
1137
                start := len(s)
1✔
1138
                s = strings.TrimSuffix(s, "\\n")
1✔
1139
                s += strings.Repeat("&#x000A;", (start-len(s))/2)
1✔
1140
        }
1✔
1141

1142
        if n.isPlain || n.isPreserve || tw.isUnescaped {
2✔
1143
                _, err := tw.WriteStringLiteral(s)
1✔
1144
                return err
1✔
1145
        }
1✔
1146

1147
        _, err := tw.WriteStringLiteral(html.EscapeString(s))
1✔
1148
        return err
1✔
1149
}
1150

1151
func (n *TextNode) Tree(buf *bytes.Buffer, indent int) string {
1✔
1152
        lead := strings.Repeat("\t", indent)
1✔
1153
        typ := "(S)"
1✔
1154
        if n.isDynamic {
2✔
1155
                typ = "(D)"
1✔
1156
        }
1✔
1157
        buf.WriteString(lead + n.Type().String() + typ + "\n")
1✔
1158
        for _, c := range n.children {
1✔
1159
                c.Tree(buf, indent+1)
×
1160
        }
×
1161
        return buf.String()
1✔
1162
}
1163

1164
type RawTextNode struct {
1165
        node
1166
        text string
1167
}
1168

1169
func NewRawTextNode(t token, indent int) *RawTextNode {
1✔
1170
        return &RawTextNode{
1✔
1171
                node: newNode(nText, indent, t),
1✔
1172
                text: t.lit,
1✔
1173
        }
1✔
1174
}
1✔
1175

1176
func (n *RawTextNode) Source(tw *templateWriter) error {
×
1177
        if n.text != "" {
×
1178
                s := n.text
×
1179
                s = strconv.Quote(n.text)
×
1180
                s = s[1 : len(s)-1]
×
1181
                if _, err := tw.WriteStringLiteral(s); err != nil {
×
1182
                        return err
×
1183
                }
×
1184
        }
1185

1186
        return nil
×
1187
}
1188

1189
func (n *RawTextNode) Tree(buf *bytes.Buffer, indent int) string {
1✔
1190
        lead := strings.Repeat("\t", indent)
1✔
1191
        buf.WriteString(lead + n.Type().String() + "\n")
1✔
1192
        return buf.String()
1✔
1193
}
1✔
1194

1195
func (n *RawTextNode) parse(p *parser) error {
1✔
1196
        return p.backToParent()
1✔
1197
}
1✔
1198

1199
type UnescapeNode struct {
1200
        node
1201
}
1202

1203
func NewUnescapeNode(t token, indent int) *UnescapeNode {
1✔
1204
        return &UnescapeNode{
1✔
1205
                node: newNode(nUnescape, indent, t),
1✔
1206
        }
1✔
1207
}
1✔
1208

1209
func (n *UnescapeNode) Source(tw *templateWriter) error {
×
1210
        tw.isUnescaped = true
×
1211
        for _, child := range n.children {
×
1212
                err := child.Source(tw)
×
1213
                if err != nil {
×
1214
                        return err
×
1215
                }
×
1216
        }
1217
        tw.isUnescaped = false
×
1218
        return nil
×
1219
}
1220

1221
func (n *UnescapeNode) parse(p *parser) error {
1✔
1222
        t := p.peek()
1✔
1223
        _ = t
1✔
1224
        switch p.peek().Type() {
1✔
1225
        case tNewLine:
1✔
1226
                return p.backToParent()
1✔
1227
        default:
1✔
1228
                return n.handleNode(p, n.indent)
1✔
1229
        }
1230
}
1231

1232
type SilentScriptNode struct {
1233
        node
1234
        code       string
1235
        needsClose bool
1236
        forRender  bool
1237
}
1238

1239
func NewSilentScriptNode(t token, indent int, keepNewlines bool) *SilentScriptNode {
1✔
1240
        n := &SilentScriptNode{
1✔
1241
                node: newNode(nSilentScriptNode, indent, t),
1✔
1242
                code: t.lit,
1✔
1243
        }
1✔
1244

1✔
1245
        if keepNewlines {
2✔
1246
                n.keepNewlines = true
1✔
1247
        }
1✔
1248

1249
        return n
1✔
1250
}
1251

1252
var openingStatements = []string{"if", "else if", "for", "switch"}
1253
var elseStatements = []string{"else", "else if"}
1254

1255
func (n *SilentScriptNode) allowChildren() bool {
1✔
1256
        code := strings.TrimSpace(strings.Replace(n.code, "\n", " ", -1))
1✔
1257

1✔
1258
        allowChildren := false
1✔
1259
        for _, statement := range openingStatements {
2✔
1260
                if strings.HasPrefix(code, statement) {
2✔
1261
                        allowChildren = true
1✔
1262
                        break
1✔
1263
                }
1264
        }
1265
        if !allowChildren {
2✔
1266
                for _, statement := range elseStatements {
2✔
1267
                        if strings.HasPrefix(code, statement) {
2✔
1268
                                allowChildren = true
1✔
1269
                                break
1✔
1270
                        }
1271
                }
1272
        }
1273
        if strings.HasSuffix(code, "{") {
2✔
1274
                allowChildren = true
1✔
1275
        }
1✔
1276

1277
        return allowChildren
1✔
1278
}
1279

1280
func (n *SilentScriptNode) Source(tw *templateWriter) error {
1✔
1281
        if n.forRender {
1✔
1282
                return nil
×
1283
        }
×
1284

1285
        code := strings.TrimSpace(strings.Replace(n.code, "\n", " ", -1))
1✔
1286

1✔
1287
        isOpening := false
1✔
1288
        for _, statement := range openingStatements {
2✔
1289
                if strings.HasPrefix(code, statement) {
2✔
1290
                        isOpening = true
1✔
1291
                        break
1✔
1292
                }
1293
        }
1294

1295
        start := ""
1✔
1296
        end := "\n"
1✔
1297
        if n.needsClose && !strings.HasPrefix(code, "}") {
1✔
1298
                start = "} "
×
1299
        }
×
1300
        if len(n.children) > 0 {
2✔
1301
                if isOpening && !strings.HasSuffix(code, "{") {
2✔
1302
                        end = " {\n"
1✔
1303
                }
1✔
1304
        }
1305

1306
        if _, err := tw.WriteIndent(start); err != nil {
1✔
1307
                return err
×
1308
        }
×
1309
        if r, err := tw.Write(code); err != nil {
1✔
1310
                return err
×
1311
        } else {
1✔
1312
                tw.Add(n.origin, r)
1✔
1313
        }
1✔
1314
        if _, err := tw.Write(end); err != nil {
1✔
1315
                return err
×
1316
        }
×
1317

1318
        if len(n.children) == 0 {
2✔
1319
                return nil
1✔
1320
        }
1✔
1321

1322
        itw := tw.Indent(1)
1✔
1323
        for _, c := range n.children {
2✔
1324
                if err := c.Source(itw); err != nil {
1✔
1325
                        return err
×
1326
                }
×
1327
        }
1328

1329
        if _, err := itw.Close(); err != nil {
1✔
1330
                return err
×
1331
        }
×
1332

1333
        // only do any of this when n is the opening statement
1334
        if isOpening {
2✔
1335
                if next, ok := n.nextSibling.(*SilentScriptNode); ok {
1✔
1336
                        if strings.HasPrefix(strings.TrimSpace(next.code), "}") {
×
1337
                                return nil
×
1338
                        }
×
1339
                        for _, stmt := range elseStatements {
×
1340
                                if strings.HasPrefix(next.code, stmt) {
×
1341
                                        next.needsClose = true
×
1342
                                        return nil
×
1343
                                }
×
1344
                        }
1345
                }
1346
                // either there's no next SilentScript, or it's not an else-type, so close now
1347
                if _, err := tw.WriteIndent("}\n"); err != nil {
1✔
1348
                        return err
×
1349
                }
×
1350
        }
1351

1352
        return nil
1✔
1353
}
1354

1355
func (n *SilentScriptNode) parse(p *parser) error {
1✔
1356
        switch p.peek().Type() {
1✔
1357
        case tNewLine:
1✔
1358
                p.next()
1✔
1359
                return nil
1✔
1360
        default:
1✔
1361
                if !n.allowChildren() {
2✔
1362
                        return p.backToParent()
1✔
1363
                }
1✔
1364
                return n.handleNode(p, n.indent+1)
1✔
1365
        }
1366
}
1367

1368
type ScriptNode struct {
1369
        node
1370
        code string
1371
}
1372

1373
func NewScriptNode(t token, keepNewlines bool) *ScriptNode {
1✔
1374
        n := &ScriptNode{
1✔
1375
                node: newNode(nScriptNode, 0, t),
1✔
1376
                code: t.lit,
1✔
1377
        }
1✔
1378

1✔
1379
        if keepNewlines {
2✔
1380
                n.keepNewlines = true
1✔
1381
        }
1✔
1382

1383
        return n
1✔
1384
}
1385

1386
func (n *ScriptNode) Source(tw *templateWriter) error {
1✔
1387
        vName := tw.GetVarName()
1✔
1388
        if _, err := tw.WriteIndent(`var ` + vName + " string\n"); err != nil {
1✔
1389
                return err
×
1390
        }
×
1391
        if _, err := tw.WriteIndent(`if ` + vName + `, __err = goht.CaptureErrors(`); err != nil {
1✔
1392
                return err
×
1393
        }
×
1394
        if !tw.isUnescaped {
2✔
1395
                if _, err := tw.Write(`goht.EscapeString(`); err != nil {
1✔
1396
                        return err
×
1397
                }
×
1398
        }
1399
        if err := writeFormattedText(tw, n.origin); err != nil {
1✔
1400
                return err
×
1401
        }
×
1402
        if !tw.isUnescaped {
2✔
1403
                if _, err := tw.Write(")"); err != nil {
1✔
1404
                        return err
×
1405
                }
×
1406
        }
1407
        if _, err := tw.Write("); __err != nil { return }\n"); err != nil {
1✔
1408
                return err
×
1409
        }
×
1410
        if _, err := tw.WriteStringIndent(vName); err != nil {
1✔
1411
                return err
×
1412
        }
×
1413
        return nil
1✔
1414
}
1415

1416
type RenderCommandNode struct {
1417
        node
1418
        command string
1419
}
1420

1421
func NewRenderCommandNode(t token, indent int, keepNewlines bool) *RenderCommandNode {
1✔
1422
        n := &RenderCommandNode{
1✔
1423
                node:    newNode(nRenderCommand, indent, t),
1✔
1424
                command: t.lit,
1✔
1425
        }
1✔
1426

1✔
1427
        if keepNewlines {
2✔
1428
                n.keepNewlines = true
1✔
1429
        }
1✔
1430

1431
        return n
1✔
1432
}
1433

1434
func (n *RenderCommandNode) Source(tw *templateWriter) error {
1✔
1435
        if len(n.children) == 0 {
2✔
1436
                if _, err := tw.WriteIndent("if __err = "); err != nil {
1✔
1437
                        return err
×
1438
                }
×
1439
                if r, err := tw.Write(strings.TrimSpace(strings.Replace(n.command, "\n", " ", -1))); err != nil {
1✔
1440
                        return err
×
1441
                } else {
1✔
1442
                        tw.Add(n.origin, r)
1✔
1443
                }
1✔
1444
                if _, err := tw.Write(".Render(ctx, __buf); __err != nil { return }\n"); err != nil {
1✔
1445
                        return err
×
1446
                }
×
1447
                return nil
1✔
1448
        }
1449

1450
        vName := tw.GetVarName()
1✔
1451

1✔
1452
        fnLine := vName + " := goht.TemplateFunc(func(ctx context.Context, __w io.Writer, __sts ...goht.SlottedTemplate) (__err error) {\n"
1✔
1453

1✔
1454
        if _, err := tw.WriteIndent(fnLine); err != nil {
1✔
1455
                return err
×
1456
        }
×
1457

1458
        itw := tw.Indent(1)
1✔
1459

1✔
1460
        lines := []string{
1✔
1461
                "__buf, __isBuf := __w.(goht.Buffer)\n",
1✔
1462
                "if !__isBuf {\n",
1✔
1463
                "        __buf = goht.GetBuffer()\n",
1✔
1464
                "        defer goht.ReleaseBuffer(__buf)\n",
1✔
1465
                "}\n",
1✔
1466
        }
1✔
1467
        for _, line := range lines {
2✔
1468
                if _, err := itw.WriteIndent(line); err != nil {
1✔
1469
                        return err
×
1470
                }
×
1471
        }
1472
        for _, c := range n.children {
2✔
1473
                if err := c.Source(itw); err != nil {
1✔
1474
                        return err
×
1475
                }
×
1476
        }
1477
        if _, err := itw.Close(); err != nil {
1✔
1478
                return err
×
1479
        }
×
1480

1481
        lines = []string{
1✔
1482
                "        if !__isBuf {\n",
1✔
1483
                "                _, __err = io.Copy(__w, __buf)\n",
1✔
1484
                "        }\n",
1✔
1485
                "        return\n",
1✔
1486
                "})\n",
1✔
1487
        }
1✔
1488
        for _, line := range lines {
2✔
1489
                if _, err := tw.WriteIndent(line); err != nil {
1✔
1490
                        return err
×
1491
                }
×
1492
        }
1493

1494
        if _, err := tw.WriteIndent("if __err = "); err != nil {
1✔
1495
                return err
×
1496
        }
×
1497
        if r, err := tw.Write(strings.TrimRight(n.command, " \t{")); err != nil {
1✔
1498
                return err
×
1499
        } else {
1✔
1500
                tw.Add(n.origin, r)
1✔
1501
        }
1✔
1502
        if _, err := tw.Write(".Render(goht.PushChildren(ctx, " + vName + "), __buf); __err != nil { return }\n"); err != nil {
1✔
1503
                return err
×
1504
        }
×
1505

1506
        if p, ok := n.nextSibling.(*SilentScriptNode); ok {
1✔
1507
                // skip the next sibling if it is a closing brace
×
1508
                if strings.TrimSpace(p.code) == "}" {
×
1509
                        p.forRender = true
×
1510
                }
×
1511
        }
1512
        return nil
1✔
1513
}
1514

1515
func (n *RenderCommandNode) parse(p *parser) error {
1✔
1516
        switch p.peek().Type() {
1✔
1517
        case tNewLine:
×
1518
                p.next()
×
1519
                return nil
×
1520
        default:
1✔
1521
                return n.handleNode(p, n.indent+1)
1✔
1522
        }
1523
}
1524

1525
type ChildrenCommandNode struct {
1526
        node
1527
}
1528

1529
func NewChildrenCommandNode(t token) *ChildrenCommandNode {
1✔
1530
        return &ChildrenCommandNode{
1✔
1531
                node: newNode(nChildrenCommand, 0, t),
1✔
1532
        }
1✔
1533
}
1✔
1534

1535
func (n *ChildrenCommandNode) Source(tw *templateWriter) error {
1✔
1536
        _, err := tw.WriteIndent("if __err = __children.Render(ctx, __buf); __err != nil { return }\n")
1✔
1537
        return err
1✔
1538
}
1✔
1539

1540
type SlotCommandNode struct {
1541
        node
1542
        slot string
1543
}
1544

1545
func NewSlotCommandNode(t token, indent int, keepNewlines bool) *SlotCommandNode {
1✔
1546
        n := &SlotCommandNode{
1✔
1547
                node: newNode(nSlotCommand, indent, t),
1✔
1548
                slot: t.lit,
1✔
1549
        }
1✔
1550

1✔
1551
        if keepNewlines {
2✔
1552
                n.keepNewlines = true
1✔
1553
        }
1✔
1554

1555
        return n
1✔
1556
}
1557

1558
func (n *SlotCommandNode) Source(tw *templateWriter) error {
1✔
1559
        if _, err := tw.WriteIndent("if __st := goht.GetSlottedTemplate(__sts, " + strconv.Quote(n.slot) + "); __st != nil {\n"); err != nil {
1✔
1560
                return err
×
1561
        }
×
1562

1563
        itw := tw.Indent(1)
1✔
1564

1✔
1565
        if _, err := itw.WriteIndent("if __err = __st.Render(ctx, __buf, __st.SlottedTemplates()...); __err != nil { return }\n"); err != nil {
1✔
1566
                return err
×
1567
        }
×
1568

1569
        if _, err := itw.Close(); err != nil {
1✔
1570
                return err
×
1571
        }
×
1572

1573
        if len(n.children) == 0 {
2✔
1574
                _, err := tw.WriteIndent("}\n")
1✔
1575
                return err
1✔
1576
        }
1✔
1577

1578
        if _, err := tw.WriteIndent("} else {\n"); err != nil {
1✔
1579
                return err
×
1580
        }
×
1581

1582
        itw = tw.Indent(1)
1✔
1583
        for _, c := range n.children {
2✔
1584
                if err := c.Source(itw); err != nil {
1✔
1585
                        return err
×
1586
                }
×
1587
        }
1588

1589
        if _, err := itw.Close(); err != nil {
1✔
1590
                return err
×
1591
        }
×
1592

1593
        if next, ok := n.nextSibling.(*SilentScriptNode); ok {
1✔
1594
                if strings.TrimSpace(next.code) == "}" {
×
1595
                        return nil
×
1596
                }
×
1597
        }
1598
        // either there's no next SilentScript, or it's not an else-type, so close now
1599
        if _, err := tw.WriteIndent("}\n"); err != nil {
1✔
1600
                return err
×
1601
        }
×
1602

1603
        return nil
1✔
1604
}
1605

1606
func (n *SlotCommandNode) parse(p *parser) error {
1✔
1607
        switch p.peek().Type() {
1✔
1608
        case tNewLine:
×
1609
                p.next()
×
1610
                return nil
×
1611
        default:
1✔
1612
                return n.handleNode(p, n.indent+1)
1✔
1613
        }
1614
}
1615

1616
type JavaScriptFilterNode struct {
1617
        node
1618
}
1619

1620
func NewJavaScriptFilterNode(t token, indent int) *JavaScriptFilterNode {
1✔
1621
        return &JavaScriptFilterNode{
1✔
1622
                node: newNode(nFilter, indent, t),
1✔
1623
        }
1✔
1624
}
1✔
1625

1626
func (n *JavaScriptFilterNode) Source(tw *templateWriter) error {
1✔
1627
        if _, err := tw.WriteStringLiteral("<script>\\n"); err != nil {
1✔
1628
                return err
×
1629
        }
×
1630
        for _, c := range n.children {
2✔
1631
                if err := c.Source(tw); err != nil {
1✔
1632
                        return err
×
1633
                }
×
1634
        }
1635
        _, err := tw.WriteStringLiteral("</script>")
1✔
1636
        return err
1✔
1637
}
1638

1639
func (n *JavaScriptFilterNode) parse(p *parser) error {
1✔
1640
        switch p.peek().Type() {
1✔
1641
        case tPlainText, tDynamicText:
1✔
1642
                n.AddChild(NewTextNode(p.next()))
1✔
1643
        case tFilterEnd:
1✔
1644
                p.next()
1✔
1645
                return p.backToParent()
1✔
1646
        case tEOF:
×
1647
                return n.errorf("javascript filter is incomplete: %s", p.peek())
×
1648
        default:
×
1649
                return n.errorf("unexpected token: %s", p.peek())
×
1650
        }
1651
        return nil
1✔
1652
}
1653

1654
type CssFilterNode struct {
1655
        node
1656
}
1657

1658
func NewCssFilterNode(t token, indent int) *CssFilterNode {
1✔
1659
        return &CssFilterNode{
1✔
1660
                node: newNode(nFilter, indent, t),
1✔
1661
        }
1✔
1662
}
1✔
1663

1664
func (n *CssFilterNode) Source(tw *templateWriter) error {
1✔
1665
        if _, err := tw.WriteStringLiteral("<style>\\n"); err != nil {
1✔
1666
                return err
×
1667
        }
×
1668
        for _, c := range n.children {
2✔
1669
                if err := c.Source(tw); err != nil {
1✔
1670
                        return err
×
1671
                }
×
1672
        }
1673
        _, err := tw.WriteStringLiteral("</style>")
1✔
1674
        return err
1✔
1675
}
1676

1677
func (n *CssFilterNode) parse(p *parser) error {
1✔
1678
        switch p.peek().Type() {
1✔
1679
        case tPlainText, tDynamicText:
1✔
1680
                n.AddChild(NewTextNode(p.next()))
1✔
1681
        case tFilterEnd:
1✔
1682
                p.next()
1✔
1683
                return p.backToParent()
1✔
1684
        case tEOF:
×
1685
                return n.errorf("css filter is incomplete: %s", p.peek())
×
1686
        default:
×
1687
                return n.errorf("unexpected token: %s", p.peek())
×
1688
        }
1689
        return nil
1✔
1690
}
1691

1692
type TextFilterNode struct {
1693
        node
1694
        isUnescaped bool
1695
}
1696

1697
func NewTextFilterNode(t token, indent int) *TextFilterNode {
1✔
1698
        return &TextFilterNode{
1✔
1699
                node:        newNode(nFilter, indent, t),
1✔
1700
                isUnescaped: t.lit == "plain" || t.lit == "preserve",
1✔
1701
        }
1✔
1702
}
1✔
1703

1704
func (n *TextFilterNode) Source(tw *templateWriter) error {
1✔
1705
        if n.isUnescaped {
2✔
1706
                tw.isUnescaped = true
1✔
1707
                defer func() {
2✔
1708
                        tw.isUnescaped = false
1✔
1709
                }()
1✔
1710
        }
1711
        for _, c := range n.children {
2✔
1712
                if err := c.Source(tw); err != nil {
1✔
1713
                        return err
×
1714
                }
×
1715
        }
1716
        if n.origin.lit == "preserve" {
2✔
1717
                _, err := tw.WriteStringLiteral("\\n")
1✔
1718
                return err
1✔
1719
        }
1✔
1720

1721
        return nil
1✔
1722
}
1723

1724
func (n *TextFilterNode) parse(p *parser) error {
1✔
1725
        switch p.peek().Type() {
1✔
1726
        case tPlainText, tEscapedText, tPreserveText, tDynamicText:
1✔
1727
                n.AddChild(NewTextNode(p.next()))
1✔
1728
        case tFilterEnd:
1✔
1729
                p.next()
1✔
1730
                return p.backToParent()
1✔
1731
        case tEOF:
×
1732
                return n.errorf("text filter is incomplete: %s", p.peek())
×
1733
        default:
×
1734
                return n.errorf("unexpected token: %s", p.peek())
×
1735
        }
1736
        return nil
1✔
1737
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc