mirror of
https://github.com/robertkrimen/otto
synced 2025-10-12 20:27:30 +08:00
158 lines
3.0 KiB
Go
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 {
|
|
stack []ast.Node
|
|
source string
|
|
shift file.Idx
|
|
seen map[ast.Node]struct{}
|
|
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.Len(t, w.stack, 0)
|
|
require.Equal(t, w.duplicate, 0)
|
|
}
|
|
|
|
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.Len(t, w.stack, 0)
|
|
require.Equal(t, w.duplicate, 0)
|
|
})
|
|
}
|
|
}
|
|
|
|
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)
|
|
})
|
|
}
|