1
0
mirror of https://github.com/robertkrimen/otto synced 2025-10-12 20:27:30 +08:00
otto/ast/walk_test.go
Steven Hartland 98effe01d8
chore: update ci versions (#519)
Update go, golangci-lint and action versions.

Address new lint failures flagged by updated golangci-lint version.
2024-04-13 17:05:50 +01:00

158 lines
3.0 KiB
Go

package ast_test
import (
"testing"
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/parser"
"github.com/stretchr/testify/require"
)
type walker struct {
seen map[ast.Node]struct{}
source string
stack []ast.Node
shift file.Idx
duplicate int
newExpressionIdx1 file.Idx
}
// push and pop below are to prove the symmetry of Enter/Exit calls
func (w *walker) push(n ast.Node) {
w.stack = append(w.stack, n)
}
func (w *walker) pop(n ast.Node) {
size := len(w.stack)
if size <= 0 {
panic("pop of empty stack")
}
if toPop := w.stack[size-1]; toPop != n {
panic("pop: nodes do not equal")
}
w.stack[size-1] = nil
w.stack = w.stack[:size-1]
}
func (w *walker) Enter(n ast.Node) ast.Visitor {
w.push(n)
if _, ok := w.seen[n]; ok {
// Skip items we've already seen which occurs due to declarations.
w.duplicate++
return w
}
w.seen[n] = struct{}{}
switch t := n.(type) {
case *ast.Identifier:
if t != nil {
idx := n.Idx0() + w.shift - 1
s := w.source[:idx] + "IDENT_" + w.source[idx:]
w.source = s
w.shift += 6
}
case *ast.VariableExpression:
if t != nil {
idx := n.Idx0() + w.shift - 1
s := w.source[:idx] + "VAR_" + w.source[idx:]
w.source = s
w.shift += 4
}
case *ast.NewExpression:
w.newExpressionIdx1 = n.Idx1()
}
return w
}
func (w *walker) Exit(n ast.Node) {
w.pop(n)
}
func TestVisitorRewrite(t *testing.T) {
source := `var b = function() {
test();
try {} catch(e) {}
var test = "test(); var test = 1"
} // test`
program, err := parser.ParseFile(nil, "", source, 0)
require.NoError(t, err)
w := &walker{
source: source,
seen: make(map[ast.Node]struct{}),
}
ast.Walk(w, program)
xformed := `var VAR_b = function() {
IDENT_test();
try {} catch(IDENT_e) {}
var VAR_test = "test(); var test = 1"
} // test`
require.Equal(t, xformed, w.source)
require.Empty(t, w.stack)
require.Zero(t, w.duplicate)
}
func Test_issue261(t *testing.T) {
tests := map[string]struct {
code string
want file.Idx
}{
"no-parenthesis": {
code: `var i = new Image;`,
want: 18,
},
"no-args": {
code: `var i = new Image();`,
want: 20,
},
"two-args": {
code: `var i = new Image(1, 2);`,
want: 24,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
prog, err := parser.ParseFile(nil, "", tt.code, 0)
require.NoError(t, err)
w := &walker{
source: tt.code,
seen: make(map[ast.Node]struct{}),
}
ast.Walk(w, prog)
require.Equal(t, tt.want, w.newExpressionIdx1)
require.Empty(t, w.stack)
require.Zero(t, w.duplicate)
})
}
}
func TestBadStatement(t *testing.T) {
source := `
var abc;
break; do {
} while(true);
`
program, err := parser.ParseFile(nil, "", source, 0)
require.ErrorContains(t, err, "Illegal break statement")
w := &walker{
source: source,
seen: make(map[ast.Node]struct{}),
}
require.NotPanics(t, func() {
ast.Walk(w, program)
})
}