Changed: DB Params
This commit is contained in:
18
templ/parser/v2/goexpression/fuzz.sh
Executable file
18
templ/parser/v2/goexpression/fuzz.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
echo If
|
||||
go test -fuzz=FuzzIf -fuzztime=120s
|
||||
echo For
|
||||
go test -fuzz=FuzzFor -fuzztime=120s
|
||||
echo Switch
|
||||
go test -fuzz=FuzzSwitch -fuzztime=120s
|
||||
echo Case
|
||||
go test -fuzz=FuzzCaseStandard -fuzztime=120s
|
||||
echo Default
|
||||
go test -fuzz=FuzzCaseDefault -fuzztime=120s
|
||||
echo TemplExpression
|
||||
go test -fuzz=FuzzTemplExpression -fuzztime=120s
|
||||
echo Expression
|
||||
go test -fuzz=FuzzExpression -fuzztime=120s
|
||||
echo SliceArgs
|
||||
go test -fuzz=FuzzSliceArgs -fuzztime=120s
|
||||
echo Funcs
|
||||
go test -fuzz=FuzzFuncs -fuzztime=120s
|
343
templ/parser/v2/goexpression/parse.go
Normal file
343
templ/parser/v2/goexpression/parse.go
Normal file
@@ -0,0 +1,343 @@
|
||||
package goexpression
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrContainerFuncNotFound = errors.New("parser error: templ container function not found")
|
||||
ErrExpectedNodeNotFound = errors.New("parser error: expected node not found")
|
||||
)
|
||||
|
||||
var defaultRegexp = regexp.MustCompile(`^default\s*:`)
|
||||
|
||||
func Case(content string) (start, end int, err error) {
|
||||
if !(strings.HasPrefix(content, "case ") || defaultRegexp.MatchString(content)) {
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
}
|
||||
prefix := "switch {\n"
|
||||
src := prefix + content
|
||||
start, end, err = extract(src, func(body []ast.Stmt) (start, end int, err error) {
|
||||
sw, ok := body[0].(*ast.SwitchStmt)
|
||||
if !ok {
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
}
|
||||
if sw.Body == nil || len(sw.Body.List) == 0 {
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
}
|
||||
stmt, ok := sw.Body.List[0].(*ast.CaseClause)
|
||||
if !ok {
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
}
|
||||
start = int(stmt.Case) - 1
|
||||
end = int(stmt.Colon)
|
||||
return start, end, nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
// Since we added a `switch {` prefix, we need to remove it.
|
||||
start -= len(prefix)
|
||||
end -= len(prefix)
|
||||
return start, end, nil
|
||||
}
|
||||
|
||||
func If(content string) (start, end int, err error) {
|
||||
if !strings.HasPrefix(content, "if") {
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
}
|
||||
return extract(content, func(body []ast.Stmt) (start, end int, err error) {
|
||||
stmt, ok := body[0].(*ast.IfStmt)
|
||||
if !ok {
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
}
|
||||
start = int(stmt.If) + len("if")
|
||||
end = latestEnd(start, stmt.Init, stmt.Cond)
|
||||
return start, end, nil
|
||||
})
|
||||
}
|
||||
|
||||
func For(content string) (start, end int, err error) {
|
||||
if !strings.HasPrefix(content, "for") {
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
}
|
||||
return extract(content, func(body []ast.Stmt) (start, end int, err error) {
|
||||
stmt := body[0]
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.ForStmt:
|
||||
start = int(stmt.For) + len("for")
|
||||
end = latestEnd(start, stmt.Init, stmt.Cond, stmt.Post)
|
||||
return start, end, nil
|
||||
case *ast.RangeStmt:
|
||||
start = int(stmt.For) + len("for")
|
||||
end = latestEnd(start, stmt.Key, stmt.Value, stmt.X)
|
||||
return start, end, nil
|
||||
}
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
})
|
||||
}
|
||||
|
||||
func Switch(content string) (start, end int, err error) {
|
||||
if !strings.HasPrefix(content, "switch") {
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
}
|
||||
return extract(content, func(body []ast.Stmt) (start, end int, err error) {
|
||||
stmt := body[0]
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.SwitchStmt:
|
||||
start = int(stmt.Switch) + len("switch")
|
||||
end = latestEnd(start, stmt.Init, stmt.Tag)
|
||||
return start, end, nil
|
||||
case *ast.TypeSwitchStmt:
|
||||
start = int(stmt.Switch) + len("switch")
|
||||
end = latestEnd(start, stmt.Init, stmt.Assign)
|
||||
return start, end, nil
|
||||
}
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
})
|
||||
}
|
||||
|
||||
func TemplExpression(src string) (start, end int, err error) {
|
||||
var s scanner.Scanner
|
||||
fset := token.NewFileSet()
|
||||
file := fset.AddFile("", fset.Base(), len(src))
|
||||
errorHandler := func(pos token.Position, msg string) {
|
||||
err = fmt.Errorf("error parsing expression: %v", msg)
|
||||
}
|
||||
s.Init(file, []byte(src), errorHandler, scanner.ScanComments)
|
||||
|
||||
// Read chains of identifiers, e.g.:
|
||||
// components.Variable
|
||||
// components[0].Variable
|
||||
// components["name"].Function()
|
||||
// functionCall(withLots(), func() { return true })
|
||||
ep := NewExpressionParser()
|
||||
for {
|
||||
pos, tok, lit := s.Scan()
|
||||
stop, err := ep.Insert(pos, tok, lit)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0, ep.End, nil
|
||||
}
|
||||
|
||||
func Expression(src string) (start, end int, err error) {
|
||||
var s scanner.Scanner
|
||||
fset := token.NewFileSet()
|
||||
file := fset.AddFile("", fset.Base(), len(src))
|
||||
errorHandler := func(pos token.Position, msg string) {
|
||||
err = fmt.Errorf("error parsing expression: %v", msg)
|
||||
}
|
||||
s.Init(file, []byte(src), errorHandler, scanner.ScanComments)
|
||||
|
||||
// Read chains of identifiers and constants up until RBRACE, e.g.:
|
||||
// true
|
||||
// 123.45 == true
|
||||
// components.Variable
|
||||
// components[0].Variable
|
||||
// components["name"].Function()
|
||||
// functionCall(withLots(), func() { return true })
|
||||
// !true
|
||||
parenDepth := 0
|
||||
bracketDepth := 0
|
||||
braceDepth := 0
|
||||
loop:
|
||||
for {
|
||||
pos, tok, lit := s.Scan()
|
||||
if tok == token.EOF {
|
||||
break loop
|
||||
}
|
||||
switch tok {
|
||||
case token.LPAREN: // (
|
||||
parenDepth++
|
||||
case token.RPAREN: // )
|
||||
end = int(pos)
|
||||
parenDepth--
|
||||
case token.LBRACK: // [
|
||||
bracketDepth++
|
||||
case token.RBRACK: // ]
|
||||
end = int(pos)
|
||||
bracketDepth--
|
||||
case token.LBRACE: // {
|
||||
braceDepth++
|
||||
case token.RBRACE: // }
|
||||
braceDepth--
|
||||
if braceDepth < 0 {
|
||||
// We've hit the end of the expression.
|
||||
break loop
|
||||
}
|
||||
end = int(pos)
|
||||
case token.IDENT, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING:
|
||||
end = int(pos) + len(lit) - 1
|
||||
case token.SEMICOLON:
|
||||
continue
|
||||
case token.COMMENT:
|
||||
end = int(pos) + len(lit) - 1
|
||||
case token.ILLEGAL:
|
||||
return 0, 0, fmt.Errorf("illegal token: %v", lit)
|
||||
default:
|
||||
end = int(pos) + len(tok.String()) - 1
|
||||
}
|
||||
}
|
||||
return start, end, nil
|
||||
}
|
||||
|
||||
func SliceArgs(content string) (expr string, err error) {
|
||||
prefix := "package main\nvar templ_args = []any{"
|
||||
src := prefix + content + "}"
|
||||
|
||||
node, parseErr := parser.ParseFile(token.NewFileSet(), "", src, parser.AllErrors)
|
||||
if node == nil {
|
||||
return expr, parseErr
|
||||
}
|
||||
|
||||
var from, to int
|
||||
inspectFirstNode(node, func(n ast.Node) bool {
|
||||
decl, ok := n.(*ast.CompositeLit)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
from = int(decl.Lbrace)
|
||||
to = int(decl.Rbrace) - 1
|
||||
for _, e := range decl.Elts {
|
||||
to = int(e.End()) - 1
|
||||
}
|
||||
if to > int(decl.Rbrace)-1 {
|
||||
to = int(decl.Rbrace) - 1
|
||||
}
|
||||
betweenEndAndBrace := src[to : decl.Rbrace-1]
|
||||
var hasCodeBetweenEndAndBrace bool
|
||||
for _, r := range betweenEndAndBrace {
|
||||
if !unicode.IsSpace(r) {
|
||||
hasCodeBetweenEndAndBrace = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasCodeBetweenEndAndBrace {
|
||||
to = int(decl.Rbrace) - 1
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
return src[from:to], err
|
||||
}
|
||||
|
||||
// Func returns the Go code up to the opening brace of the function body.
|
||||
func Func(content string) (name, expr string, err error) {
|
||||
prefix := "package main\n"
|
||||
src := prefix + content
|
||||
|
||||
node, parseErr := parser.ParseFile(token.NewFileSet(), "", src, parser.AllErrors)
|
||||
if node == nil {
|
||||
return name, expr, parseErr
|
||||
}
|
||||
|
||||
inspectFirstNode(node, func(n ast.Node) bool {
|
||||
// Find the first function declaration.
|
||||
fn, ok := n.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
start := int(fn.Pos()) + len("func")
|
||||
end := fn.Type.Params.End() - 1
|
||||
if len(src) < int(end) {
|
||||
err = errors.New("parser error: function identifier")
|
||||
return false
|
||||
}
|
||||
expr = strings.Clone(src[start:end])
|
||||
name = fn.Name.Name
|
||||
return false
|
||||
})
|
||||
|
||||
return name, expr, err
|
||||
}
|
||||
|
||||
func latestEnd(start int, nodes ...ast.Node) (end int) {
|
||||
end = start
|
||||
for _, n := range nodes {
|
||||
if n == nil {
|
||||
continue
|
||||
}
|
||||
if int(n.End())-1 > end {
|
||||
end = int(n.End()) - 1
|
||||
}
|
||||
}
|
||||
return end
|
||||
}
|
||||
|
||||
func inspectFirstNode(node ast.Node, f func(ast.Node) bool) {
|
||||
var stop bool
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
if stop {
|
||||
return true
|
||||
}
|
||||
if f(n) {
|
||||
return true
|
||||
}
|
||||
stop = true
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// Extract a Go expression from the content.
|
||||
// The Go expression starts at "start" and ends at "end".
|
||||
// The reader should skip until "length" to pass over the expression and into the next
|
||||
// logical block.
|
||||
type Extractor func(body []ast.Stmt) (start, end int, err error)
|
||||
|
||||
func extract(content string, extractor Extractor) (start, end int, err error) {
|
||||
prefix := "package main\nfunc templ_container() {\n"
|
||||
src := prefix + content
|
||||
|
||||
node, parseErr := parser.ParseFile(token.NewFileSet(), "", src, parser.AllErrors)
|
||||
if node == nil {
|
||||
return 0, 0, parseErr
|
||||
}
|
||||
|
||||
var found bool
|
||||
inspectFirstNode(node, func(n ast.Node) bool {
|
||||
// Find the "templ_container" function.
|
||||
fn, ok := n.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if fn.Name == nil || fn.Name.Name != "templ_container" {
|
||||
err = ErrContainerFuncNotFound
|
||||
return false
|
||||
}
|
||||
if fn.Body == nil || len(fn.Body.List) == 0 {
|
||||
err = ErrExpectedNodeNotFound
|
||||
return false
|
||||
}
|
||||
found = true
|
||||
start, end, err = extractor(fn.Body.List)
|
||||
return false
|
||||
})
|
||||
if !found {
|
||||
return 0, 0, ErrExpectedNodeNotFound
|
||||
}
|
||||
|
||||
start -= len(prefix)
|
||||
end -= len(prefix)
|
||||
|
||||
if end > len(content) {
|
||||
end = len(content)
|
||||
}
|
||||
if start > end {
|
||||
start = end
|
||||
}
|
||||
|
||||
return start, end, err
|
||||
}
|
781
templ/parser/v2/goexpression/parse_test.go
Normal file
781
templ/parser/v2/goexpression/parse_test.go
Normal file
@@ -0,0 +1,781 @@
|
||||
package goexpression
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
var ifTests = []testInput{
|
||||
{
|
||||
name: "basic if",
|
||||
input: `true`,
|
||||
},
|
||||
{
|
||||
name: "if function call",
|
||||
input: `pkg.Func()`,
|
||||
},
|
||||
{
|
||||
name: "compound",
|
||||
input: "x := val(); x > 3",
|
||||
},
|
||||
{
|
||||
name: "if multiple",
|
||||
input: `x && y && (!z)`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestIf(t *testing.T) {
|
||||
prefix := "if "
|
||||
suffixes := []string{
|
||||
"{\n<div>\nif true content\n\t</div>}",
|
||||
" {\n<div>\nif true content\n\t</div>}",
|
||||
}
|
||||
for _, test := range ifTests {
|
||||
for i, suffix := range suffixes {
|
||||
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, If))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzIf(f *testing.F) {
|
||||
suffixes := []string{
|
||||
"{\n<div>\nif true content\n\t</div>}",
|
||||
" {\n<div>\nif true content\n\t</div>}",
|
||||
}
|
||||
for _, test := range ifTests {
|
||||
for _, suffix := range suffixes {
|
||||
f.Add("if " + test.input + suffix)
|
||||
}
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, src string) {
|
||||
start, end, err := If(src)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
panicIfInvalid(src, start, end)
|
||||
})
|
||||
}
|
||||
|
||||
func panicIfInvalid(src string, start, end int) {
|
||||
_ = src[start:end]
|
||||
}
|
||||
|
||||
var forTests = []testInput{
|
||||
{
|
||||
name: "three component",
|
||||
input: `i := 0; i < 100; i++`,
|
||||
},
|
||||
{
|
||||
name: "three component, empty",
|
||||
input: `; ; i++`,
|
||||
},
|
||||
{
|
||||
name: "while",
|
||||
input: `n < 5`,
|
||||
},
|
||||
{
|
||||
name: "infinite",
|
||||
input: ``,
|
||||
},
|
||||
{
|
||||
name: "range with index",
|
||||
input: `k, v := range m`,
|
||||
},
|
||||
{
|
||||
name: "range with key only",
|
||||
input: `k := range m`,
|
||||
},
|
||||
{
|
||||
name: "channel receive",
|
||||
input: `x := range channel`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestFor(t *testing.T) {
|
||||
prefix := "for "
|
||||
suffixes := []string{
|
||||
" {\n<div>\nloop content\n\t</div>}",
|
||||
}
|
||||
for _, test := range forTests {
|
||||
for i, suffix := range suffixes {
|
||||
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, For))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzFor(f *testing.F) {
|
||||
suffixes := []string{
|
||||
"",
|
||||
" {",
|
||||
" {}",
|
||||
" {\n<div>\nloop content\n\t</div>}",
|
||||
}
|
||||
for _, test := range forTests {
|
||||
for _, suffix := range suffixes {
|
||||
f.Add("for " + test.input + suffix)
|
||||
}
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, src string) {
|
||||
start, end, err := For(src)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
panicIfInvalid(src, start, end)
|
||||
})
|
||||
}
|
||||
|
||||
var switchTests = []testInput{
|
||||
{
|
||||
name: "switch",
|
||||
input: ``,
|
||||
},
|
||||
{
|
||||
name: "switch with expression",
|
||||
input: `x`,
|
||||
},
|
||||
{
|
||||
name: "switch with function call",
|
||||
input: `pkg.Func()`,
|
||||
},
|
||||
{
|
||||
name: "type switch",
|
||||
input: `x := x.(type)`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestSwitch(t *testing.T) {
|
||||
prefix := "switch "
|
||||
suffixes := []string{
|
||||
" {\ncase 1:\n\t<div>\n\tcase 2:\n\t\t<div>\n\tdefault:\n\t\t<div>\n\t</div>}",
|
||||
" {\ndefault:\n\t<div>\n\t</div>}",
|
||||
" {\n}",
|
||||
}
|
||||
for _, test := range switchTests {
|
||||
for i, suffix := range suffixes {
|
||||
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, Switch))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzSwitch(f *testing.F) {
|
||||
suffixes := []string{
|
||||
"",
|
||||
" {",
|
||||
" {}",
|
||||
" {\n<div>\nloop content\n\t</div>}",
|
||||
}
|
||||
for _, test := range switchTests {
|
||||
for _, suffix := range suffixes {
|
||||
f.Add(test.input + suffix)
|
||||
}
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
src := "switch " + s
|
||||
start, end, err := For(src)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
panicIfInvalid(src, start, end)
|
||||
})
|
||||
}
|
||||
|
||||
var caseTests = []testInput{
|
||||
{
|
||||
name: "case",
|
||||
input: `case 1:`,
|
||||
},
|
||||
{
|
||||
name: "case with expression",
|
||||
input: `case x > 3:`,
|
||||
},
|
||||
{
|
||||
name: "case with function call",
|
||||
input: `case pkg.Func():`,
|
||||
},
|
||||
{
|
||||
name: "case with multiple expressions",
|
||||
input: `case x > 3, x < 4:`,
|
||||
},
|
||||
{
|
||||
name: "case with multiple expressions and default",
|
||||
input: `case x > 3, x < 4, x == 5:`,
|
||||
},
|
||||
{
|
||||
name: "case with type switch",
|
||||
input: `case bool:`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestCase(t *testing.T) {
|
||||
suffixes := []string{
|
||||
"\n<div>\ncase 1 content\n\t</div>\n\tcase 3:",
|
||||
"\ndefault:\n\t<div>\n\t</div>}",
|
||||
"\n}",
|
||||
}
|
||||
for _, test := range caseTests {
|
||||
for i, suffix := range suffixes {
|
||||
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, "", suffix, Case))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzCaseStandard(f *testing.F) {
|
||||
suffixes := []string{
|
||||
"",
|
||||
"\n<div>\ncase 1 content\n\t</div>\n\tcase 3:",
|
||||
"\ndefault:\n\t<div>\n\t</div>}",
|
||||
"\n}",
|
||||
}
|
||||
for _, test := range caseTests {
|
||||
for _, suffix := range suffixes {
|
||||
f.Add(test.input + suffix)
|
||||
}
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, src string) {
|
||||
start, end, err := Case(src)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
panicIfInvalid(src, start, end)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCaseDefault(t *testing.T) {
|
||||
suffixes := []string{
|
||||
"\n<div>\ncase 1 content\n\t</div>\n\tcase 3:",
|
||||
"\ncase:\n\t<div>\n\t</div>}",
|
||||
"\n}",
|
||||
}
|
||||
tests := []testInput{
|
||||
{
|
||||
name: "default",
|
||||
input: `default:`,
|
||||
},
|
||||
{
|
||||
name: "default with padding",
|
||||
input: `default :`,
|
||||
},
|
||||
{
|
||||
name: "default with padding",
|
||||
input: `default :`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
for i, suffix := range suffixes {
|
||||
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, "", suffix, Case))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzCaseDefault(f *testing.F) {
|
||||
suffixes := []string{
|
||||
"",
|
||||
" ",
|
||||
"\n<div>\ncase 1 content\n\t</div>\n\tcase 3:",
|
||||
"\ncase:\n\t<div>\n\t</div>}",
|
||||
"\n}",
|
||||
}
|
||||
for _, suffix := range suffixes {
|
||||
f.Add("default:" + suffix)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, src string) {
|
||||
start, end, err := Case(src)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
panicIfInvalid(src, start, end)
|
||||
})
|
||||
}
|
||||
|
||||
var expressionTests = []testInput{
|
||||
{
|
||||
name: "string literal",
|
||||
input: `"hello"`,
|
||||
},
|
||||
{
|
||||
name: "string literal with escape",
|
||||
input: `"hello\n"`,
|
||||
},
|
||||
{
|
||||
name: "backtick string literal",
|
||||
input: "`hello`",
|
||||
},
|
||||
{
|
||||
name: "backtick string literal containing double quote",
|
||||
input: "`hello" + `"` + `world` + "`",
|
||||
},
|
||||
{
|
||||
name: "function call in package",
|
||||
input: `components.Other()`,
|
||||
},
|
||||
{
|
||||
name: "slice index call",
|
||||
input: `components[0].Other()`,
|
||||
},
|
||||
{
|
||||
name: "map index function call",
|
||||
input: `components["name"].Other()`,
|
||||
},
|
||||
{
|
||||
name: "function literal",
|
||||
input: `components["name"].Other(func() bool { return true })`,
|
||||
},
|
||||
{
|
||||
name: "multiline function call",
|
||||
input: `component(map[string]string{
|
||||
"namea": "name_a",
|
||||
"nameb": "name_b",
|
||||
})`,
|
||||
},
|
||||
{
|
||||
name: "call with braces and brackets",
|
||||
input: `templates.New(test{}, other())`,
|
||||
},
|
||||
{
|
||||
name: "bare variable",
|
||||
input: `component`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression",
|
||||
input: `direction == "newest"`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with parens",
|
||||
input: `len(data.previousPageUrl) == 0`,
|
||||
},
|
||||
{
|
||||
name: "string concat",
|
||||
input: `direction + "newest"`,
|
||||
},
|
||||
{
|
||||
name: "function call",
|
||||
input: `SplitRule(types.GroupMember{
|
||||
UserID: uuid.NewString(),
|
||||
Username: "user me",
|
||||
}, []types.GroupMember{
|
||||
{
|
||||
UserID: uuid.NewString(),
|
||||
Username: "user 1",
|
||||
},
|
||||
})`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestExpression(t *testing.T) {
|
||||
prefix := ""
|
||||
suffixes := []string{
|
||||
"",
|
||||
"}",
|
||||
"\t}",
|
||||
" }",
|
||||
}
|
||||
for _, test := range expressionTests {
|
||||
for i, suffix := range suffixes {
|
||||
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, Expression))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var templExpressionTests = []testInput{
|
||||
{
|
||||
name: "function call in package",
|
||||
input: `components.Other()`,
|
||||
},
|
||||
{
|
||||
name: "slice index call",
|
||||
input: `components[0].Other()`,
|
||||
},
|
||||
{
|
||||
name: "map index function call",
|
||||
input: `components["name"].Other()`,
|
||||
},
|
||||
{
|
||||
name: "multiline chain call",
|
||||
input: `components.
|
||||
Other()`,
|
||||
},
|
||||
{
|
||||
name: "map index function call backtick literal",
|
||||
input: "components[`name" + `"` + "`].Other()",
|
||||
},
|
||||
{
|
||||
name: "function literal",
|
||||
input: `components["name"].Other(func() bool { return true })`,
|
||||
},
|
||||
{
|
||||
name: "multiline function call",
|
||||
input: `component(map[string]string{
|
||||
"namea": "name_a",
|
||||
"nameb": "name_b",
|
||||
})`,
|
||||
},
|
||||
{
|
||||
name: "function call with slice of complex types",
|
||||
input: `tabs([]TabData{
|
||||
{Name: "A"},
|
||||
{Name: "B"},
|
||||
})`,
|
||||
},
|
||||
{
|
||||
name: "function call with slice of explicitly named complex types",
|
||||
input: `tabs([]TabData{
|
||||
TabData{Name: "A"},
|
||||
TabData{Name: "B"},
|
||||
})`,
|
||||
},
|
||||
{
|
||||
name: "function call with empty slice of strings",
|
||||
input: `Inner([]string{})`,
|
||||
},
|
||||
{
|
||||
name: "function call with empty slice of maps",
|
||||
input: `Inner([]map[string]any{})`,
|
||||
},
|
||||
{
|
||||
name: "function call with empty slice of anon structs",
|
||||
input: `Inner([]map[string]struct{}{})`,
|
||||
},
|
||||
{
|
||||
name: "function call with slice of pointers to complex types",
|
||||
input: `tabs([]*TabData{
|
||||
&{Name: "A"},
|
||||
&{Name: "B"},
|
||||
})`,
|
||||
},
|
||||
{
|
||||
name: "function call with slice of pointers to explicitly named complex types",
|
||||
input: `tabs([]*TabData{
|
||||
&TabData{Name: "A"},
|
||||
&TabData{Name: "B"},
|
||||
})`,
|
||||
},
|
||||
{
|
||||
name: "function call with array of explicit length",
|
||||
input: `tabs([2]TabData{
|
||||
{Name: "A"},
|
||||
{Name: "B"},
|
||||
})`,
|
||||
},
|
||||
{
|
||||
name: "function call with array of inferred length",
|
||||
input: `tabs([...]TabData{
|
||||
{Name: "A"},
|
||||
{Name: "B"},
|
||||
})`,
|
||||
},
|
||||
{
|
||||
name: "function call with function arg",
|
||||
input: `componentA(func(y []int) string {
|
||||
return "hi"
|
||||
})`,
|
||||
},
|
||||
{
|
||||
name: "function call with function called arg",
|
||||
input: `componentA(func(y []int) string {
|
||||
return "hi"
|
||||
}())`,
|
||||
},
|
||||
{
|
||||
name: "call with braces and brackets",
|
||||
input: `templates.New(test{}, other())`,
|
||||
},
|
||||
{
|
||||
name: "generic call",
|
||||
input: `templates.New(toString[[]int](data))`,
|
||||
},
|
||||
{
|
||||
name: "struct method call",
|
||||
input: `typeName{}.Method()`,
|
||||
},
|
||||
{
|
||||
name: "struct method call in other package",
|
||||
input: "layout.DefaultLayout{}.Compile()",
|
||||
},
|
||||
{
|
||||
name: "bare variable",
|
||||
input: `component`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestTemplExpression(t *testing.T) {
|
||||
prefix := ""
|
||||
suffixes := []string{
|
||||
"",
|
||||
"}",
|
||||
"\t}",
|
||||
" }",
|
||||
"</div>",
|
||||
"<p>/</p>",
|
||||
" just some text",
|
||||
" { <div>Child content</div> }",
|
||||
}
|
||||
for _, test := range templExpressionTests {
|
||||
for i, suffix := range suffixes {
|
||||
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, TemplExpression))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzTemplExpression(f *testing.F) {
|
||||
suffixes := []string{
|
||||
"",
|
||||
" }",
|
||||
" }}</a>\n}",
|
||||
"...",
|
||||
}
|
||||
for _, test := range expressionTests {
|
||||
for _, suffix := range suffixes {
|
||||
f.Add(test.input + suffix)
|
||||
}
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
src := "switch " + s
|
||||
start, end, err := TemplExpression(src)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
panicIfInvalid(src, start, end)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzExpression(f *testing.F) {
|
||||
suffixes := []string{
|
||||
"",
|
||||
" }",
|
||||
" }}</a>\n}",
|
||||
"...",
|
||||
}
|
||||
for _, test := range expressionTests {
|
||||
for _, suffix := range suffixes {
|
||||
f.Add(test.input + suffix)
|
||||
}
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
src := "switch " + s
|
||||
start, end, err := Expression(src)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
panicIfInvalid(src, start, end)
|
||||
})
|
||||
}
|
||||
|
||||
var sliceArgsTests = []testInput{
|
||||
{
|
||||
name: "no input",
|
||||
input: ``,
|
||||
},
|
||||
{
|
||||
name: "single input",
|
||||
input: `nil`,
|
||||
},
|
||||
{
|
||||
name: "inputs to function call",
|
||||
input: `a, b, "c"`,
|
||||
},
|
||||
{
|
||||
name: "function call in package",
|
||||
input: `components.Other()`,
|
||||
},
|
||||
{
|
||||
name: "slice index call",
|
||||
input: `components[0].Other()`,
|
||||
},
|
||||
{
|
||||
name: "map index function call",
|
||||
input: `components["name"].Other()`,
|
||||
},
|
||||
{
|
||||
name: "function literal",
|
||||
input: `components["name"].Other(func() bool { return true })`,
|
||||
},
|
||||
{
|
||||
name: "multiline function call",
|
||||
input: `component(map[string]string{
|
||||
"namea": "name_a",
|
||||
"nameb": "name_b",
|
||||
})`,
|
||||
},
|
||||
{
|
||||
name: "package name, but no variable or function",
|
||||
input: `fmt.`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestSliceArgs(t *testing.T) {
|
||||
suffixes := []string{
|
||||
"",
|
||||
"}",
|
||||
"}</a>\n}\nvar x = []struct {}{}",
|
||||
}
|
||||
for _, test := range sliceArgsTests {
|
||||
for i, suffix := range suffixes {
|
||||
t.Run(fmt.Sprintf("%s_%d", test.name, i), func(t *testing.T) {
|
||||
expr, err := SliceArgs(test.input + suffix)
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse slice args: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(test.input, expr); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzSliceArgs(f *testing.F) {
|
||||
suffixes := []string{
|
||||
"",
|
||||
"}",
|
||||
" }",
|
||||
"}</a>\n}\nvar x = []struct {}{}",
|
||||
}
|
||||
for _, test := range sliceArgsTests {
|
||||
for _, suffix := range suffixes {
|
||||
f.Add(test.input + suffix)
|
||||
}
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
_, err := SliceArgs(s)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestChildren(t *testing.T) {
|
||||
prefix := ""
|
||||
suffixes := []string{
|
||||
" }",
|
||||
" } <div>Other content</div>",
|
||||
"", // End of file.
|
||||
}
|
||||
tests := []testInput{
|
||||
{
|
||||
name: "children",
|
||||
input: `children...`,
|
||||
},
|
||||
{
|
||||
name: "function",
|
||||
input: `components.Spread()...`,
|
||||
},
|
||||
{
|
||||
name: "alternative variable",
|
||||
input: `components...`,
|
||||
},
|
||||
{
|
||||
name: "index",
|
||||
input: `groups[0]...`,
|
||||
},
|
||||
{
|
||||
name: "map",
|
||||
input: `components["name"]...`,
|
||||
},
|
||||
{
|
||||
name: "map func key",
|
||||
input: `components[getKey(ctx)]...`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
for i, suffix := range suffixes {
|
||||
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, Expression))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var funcTests = []testInput{
|
||||
{
|
||||
name: "void func",
|
||||
input: `myfunc()`,
|
||||
},
|
||||
{
|
||||
name: "receiver func",
|
||||
input: `(r recv) myfunc()`,
|
||||
},
|
||||
}
|
||||
|
||||
func FuzzFuncs(f *testing.F) {
|
||||
prefix := "func "
|
||||
suffixes := []string{
|
||||
"",
|
||||
"}",
|
||||
" }",
|
||||
"}</a>\n}\nvar x = []struct {}{}",
|
||||
}
|
||||
for _, test := range funcTests {
|
||||
for _, suffix := range suffixes {
|
||||
f.Add(prefix + test.input + suffix)
|
||||
}
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
_, _, err := Func(s)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFunc(t *testing.T) {
|
||||
prefix := "func "
|
||||
suffixes := []string{
|
||||
"",
|
||||
"}",
|
||||
"}\nvar x = []struct {}{}",
|
||||
"}\nfunc secondFunc() {}",
|
||||
}
|
||||
for _, test := range funcTests {
|
||||
for i, suffix := range suffixes {
|
||||
t.Run(fmt.Sprintf("%s_%d", test.name, i), func(t *testing.T) {
|
||||
name, expr, err := Func(prefix + test.input + suffix)
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse slice args: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(test.input, expr); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
if diff := cmp.Diff("myfunc", name); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testInput struct {
|
||||
name string
|
||||
input string
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
type extractor func(content string) (start, end int, err error)
|
||||
|
||||
func run(test testInput, prefix, suffix string, e extractor) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
src := prefix + test.input + suffix
|
||||
start, end, err := e(src)
|
||||
if test.expectedErr == nil && err != nil {
|
||||
t.Fatalf("expected nil error got error type %T: %v", err, err)
|
||||
}
|
||||
if test.expectedErr != nil && err == nil {
|
||||
t.Fatalf("expected err %q, got %v", test.expectedErr.Error(), err)
|
||||
}
|
||||
if test.expectedErr != nil && err != nil && test.expectedErr.Error() != err.Error() {
|
||||
t.Fatalf("expected err %q, got %q", test.expectedErr.Error(), err.Error())
|
||||
}
|
||||
actual := src[start:end]
|
||||
if diff := cmp.Diff(test.input, actual); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
}
|
105
templ/parser/v2/goexpression/parsebench_test.go
Normal file
105
templ/parser/v2/goexpression/parsebench_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package goexpression
|
||||
|
||||
import "testing"
|
||||
|
||||
var testStringExpression = `"this string expression" }
|
||||
<div>
|
||||
But afterwards, it keeps searching.
|
||||
<div>
|
||||
|
||||
<div>
|
||||
But that's not right, we can stop searching. It won't find anything valid.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Certainly not later in the file.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
It's going to try all the tokens.
|
||||
)}]@<+.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
It's going to try all the tokens.
|
||||
)}]@<+.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
It's going to try all the tokens.
|
||||
)}]@<+.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
It's going to try all the tokens.
|
||||
)}]@<+.
|
||||
</div>
|
||||
`
|
||||
|
||||
func BenchmarkExpression(b *testing.B) {
|
||||
// Baseline...
|
||||
// BenchmarkExpression-10 6484 184862 ns/op
|
||||
// Updated...
|
||||
// BenchmarkExpression-10 3942538 279.6 ns/op
|
||||
for n := 0; n < b.N; n++ {
|
||||
start, end, err := Expression(testStringExpression)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if start != 0 || end != 24 {
|
||||
b.Fatalf("expected 0, 24, got %d, %d", start, end)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var testTemplExpression = `templates.CallMethod(map[string]any{
|
||||
"name": "this string expression",
|
||||
})
|
||||
|
||||
<div>
|
||||
But afterwards, it keeps searching.
|
||||
<div>
|
||||
|
||||
<div>
|
||||
But that's not right, we can stop searching. It won't find anything valid.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Certainly not later in the file.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
It's going to try all the tokens.
|
||||
)}]@<+.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
It's going to try all the tokens.
|
||||
)}]@<+.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
It's going to try all the tokens.
|
||||
)}]@<+.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
It's going to try all the tokens.
|
||||
)}]@<+.
|
||||
</div>
|
||||
`
|
||||
|
||||
func BenchmarkTemplExpression(b *testing.B) {
|
||||
// BenchmarkTemplExpression-10 2694 431934 ns/op
|
||||
// Updated...
|
||||
// BenchmarkTemplExpression-10 1339399 897.6 ns/op
|
||||
for n := 0; n < b.N; n++ {
|
||||
start, end, err := TemplExpression(testTemplExpression)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if start != 0 || end != 74 {
|
||||
b.Fatalf("expected 0, 74, got %d, %d", start, end)
|
||||
}
|
||||
}
|
||||
}
|
180
templ/parser/v2/goexpression/scanner.go
Normal file
180
templ/parser/v2/goexpression/scanner.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package goexpression
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
type Stack[T any] []T
|
||||
|
||||
func (s *Stack[T]) Push(v T) {
|
||||
*s = append(*s, v)
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Pop() (v T) {
|
||||
if len(*s) == 0 {
|
||||
return v
|
||||
}
|
||||
v = (*s)[len(*s)-1]
|
||||
*s = (*s)[:len(*s)-1]
|
||||
return v
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Peek() (v T) {
|
||||
if len(*s) == 0 {
|
||||
return v
|
||||
}
|
||||
return (*s)[len(*s)-1]
|
||||
}
|
||||
|
||||
var goTokenOpenToClose = map[token.Token]token.Token{
|
||||
token.LPAREN: token.RPAREN,
|
||||
token.LBRACE: token.RBRACE,
|
||||
token.LBRACK: token.RBRACK,
|
||||
}
|
||||
|
||||
var goTokenCloseToOpen = map[token.Token]token.Token{
|
||||
token.RPAREN: token.LPAREN,
|
||||
token.RBRACE: token.LBRACE,
|
||||
token.RBRACK: token.LBRACK,
|
||||
}
|
||||
|
||||
type ErrUnbalanced struct {
|
||||
Token token.Token
|
||||
}
|
||||
|
||||
func (e ErrUnbalanced) Error() string {
|
||||
return fmt.Sprintf("unbalanced '%s'", e.Token)
|
||||
}
|
||||
|
||||
func NewExpressionParser() *ExpressionParser {
|
||||
return &ExpressionParser{
|
||||
Stack: make(Stack[token.Token], 0),
|
||||
Previous: token.PERIOD,
|
||||
Fns: make(Stack[int], 0),
|
||||
}
|
||||
}
|
||||
|
||||
type ExpressionParser struct {
|
||||
Stack Stack[token.Token]
|
||||
End int
|
||||
Previous token.Token
|
||||
Fns Stack[int] // Stack of function depths.
|
||||
}
|
||||
|
||||
func (ep *ExpressionParser) setEnd(pos token.Pos, tok token.Token, lit string) {
|
||||
ep.End = int(pos) + len(tokenString(tok, lit)) - 1
|
||||
}
|
||||
|
||||
func (ep *ExpressionParser) hasSpaceBeforeCurrentToken(pos token.Pos) bool {
|
||||
return (int(pos) - 1) > ep.End
|
||||
}
|
||||
|
||||
func (ep *ExpressionParser) isTopLevel() bool {
|
||||
return len(ep.Fns) == 0 && len(ep.Stack) == 0
|
||||
}
|
||||
|
||||
func (ep *ExpressionParser) Insert(
|
||||
pos token.Pos,
|
||||
tok token.Token,
|
||||
lit string,
|
||||
) (stop bool, err error) {
|
||||
defer func() {
|
||||
ep.Previous = tok
|
||||
}()
|
||||
|
||||
// If we've reach the end of the file, terminate reading.
|
||||
if tok == token.EOF {
|
||||
// If the EOF was reached, but we're not at the top level, we must have an unbalanced expression.
|
||||
if !ep.isTopLevel() {
|
||||
return true, ErrUnbalanced{ep.Stack.Pop()}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Handle function literals e.g. func() { fmt.Println("Hello") }
|
||||
// By pushing the current depth onto the stack, we prevent stopping
|
||||
// until we've closed the function.
|
||||
if tok == token.FUNC {
|
||||
ep.Fns.Push(len(ep.Stack))
|
||||
ep.setEnd(pos, tok, lit)
|
||||
return false, nil
|
||||
}
|
||||
// If we're opening a pair, we don't stop until we've closed it.
|
||||
if _, isOpener := goTokenOpenToClose[tok]; isOpener {
|
||||
// If we're at an open brace, at the top level, where a space has been used, stop.
|
||||
if tok == token.LBRACE && ep.isTopLevel() {
|
||||
// Previous was paren, e.g. () {
|
||||
if ep.Previous == token.RPAREN {
|
||||
return true, nil
|
||||
}
|
||||
// Previous was ident that isn't a type.
|
||||
// In `name {`, `name` is considered to be a variable.
|
||||
// In `name{`, `name` is considered to be a type name.
|
||||
if ep.Previous == token.IDENT && ep.hasSpaceBeforeCurrentToken(pos) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
ep.Stack.Push(tok)
|
||||
ep.setEnd(pos, tok, lit)
|
||||
return false, nil
|
||||
}
|
||||
if opener, isCloser := goTokenCloseToOpen[tok]; isCloser {
|
||||
if len(ep.Stack) == 0 {
|
||||
// We've got a close token, but there's nothing to close, so we must be done.
|
||||
return true, nil
|
||||
}
|
||||
actual := ep.Stack.Pop()
|
||||
if !isCloser {
|
||||
return false, ErrUnbalanced{tok}
|
||||
}
|
||||
if actual != opener {
|
||||
return false, ErrUnbalanced{tok}
|
||||
}
|
||||
if tok == token.RBRACE {
|
||||
// If we're closing a function, pop the function depth.
|
||||
if len(ep.Stack) == ep.Fns.Peek() {
|
||||
ep.Fns.Pop()
|
||||
}
|
||||
}
|
||||
ep.setEnd(pos, tok, lit)
|
||||
return false, nil
|
||||
}
|
||||
// If we're in a function literal slice, or pair, we allow anything until we close it.
|
||||
if len(ep.Fns) > 0 || len(ep.Stack) > 0 {
|
||||
ep.setEnd(pos, tok, lit)
|
||||
return false, nil
|
||||
}
|
||||
// We allow an ident to follow a period or a closer.
|
||||
// e.g. "package.name", "typeName{field: value}.name()".
|
||||
// or "call().name", "call().name()".
|
||||
// But not "package .name" or "typeName{field: value} .name()".
|
||||
if tok == token.IDENT && (ep.Previous == token.PERIOD || isCloser(ep.Previous)) {
|
||||
if isCloser(ep.Previous) && ep.hasSpaceBeforeCurrentToken(pos) {
|
||||
// This token starts later than the last ending, which means
|
||||
// there's a space.
|
||||
return true, nil
|
||||
}
|
||||
ep.setEnd(pos, tok, lit)
|
||||
return false, nil
|
||||
}
|
||||
if tok == token.PERIOD && (ep.Previous == token.IDENT || isCloser(ep.Previous)) {
|
||||
ep.setEnd(pos, tok, lit)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// No match, so stop.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func tokenString(tok token.Token, lit string) string {
|
||||
if tok.IsKeyword() || tok.IsOperator() {
|
||||
return tok.String()
|
||||
}
|
||||
return lit
|
||||
}
|
||||
|
||||
func isCloser(tok token.Token) bool {
|
||||
_, ok := goTokenCloseToOpen[tok]
|
||||
return ok
|
||||
}
|
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzCaseDefault/3c6f43d3ec8a900b
vendored
Normal file
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzCaseDefault/3c6f43d3ec8a900b
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("default0")
|
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzCaseDefault/986e7bc325c7890c
vendored
Normal file
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzCaseDefault/986e7bc325c7890c
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("default:{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{`0\r000000")
|
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzCaseDefault/d8a9a4cd9fc8cb11
vendored
Normal file
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzCaseDefault/d8a9a4cd9fc8cb11
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("default")
|
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzExpression/ac5d99902f5e7914
vendored
Normal file
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzExpression/ac5d99902f5e7914
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("#")
|
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzFuncs/46c9ed6c9d427bd2
vendored
Normal file
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzFuncs/46c9ed6c9d427bd2
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("func")
|
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzIf/7a174efc13e3fdd6
vendored
Normal file
2
templ/parser/v2/goexpression/testdata/fuzz/FuzzIf/7a174efc13e3fdd6
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("(")
|
Reference in New Issue
Block a user