Repository: robertkrimen/otto
Pull request #473 (2022-11-29 13:31:27)

fix(ast): invalid idx1 for new expression
Fix invalid Idx1() return for NewExpression when there are no parenthesis.


Patch:
diff --git a/ast/node.go b/ast/node.go
index 80372c3..ad98198 100644
--- a/ast/node.go
+++ b/ast/node.go
@@ -464,14 +464,19 @@ func (self *DotExpression) Idx1() file.Idx         { return self.Identifier.Idx1
 func (self *EmptyExpression) Idx1() file.Idx       { return self.End }
 func (self *FunctionLiteral) Idx1() file.Idx       { return self.Body.Idx1() }
 func (self *Identifier) Idx1() file.Idx            { return file.Idx(int(self.Idx) + len(self.Name)) }
-func (self *NewExpression) Idx1() file.Idx         { return self.RightParenthesis + 1 }
-func (self *NullLiteral) Idx1() file.Idx           { return file.Idx(int(self.Idx) + 4) } // "null"
-func (self *NumberLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
-func (self *ObjectLiteral) Idx1() file.Idx         { return self.RightBrace }
-func (self *RegExpLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
-func (self *SequenceExpression) Idx1() file.Idx    { return self.Sequence[0].Idx1() }
-func (self *StringLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
-func (self *ThisExpression) Idx1() file.Idx        { return self.Idx + 4 }
+func (self *NewExpression) Idx1() file.Idx {
+	if self.RightParenthesis > 0 {
+		return self.RightParenthesis + 1
+	}
+	return self.Callee.Idx1()
+}
+func (self *NullLiteral) Idx1() file.Idx        { return file.Idx(int(self.Idx) + 4) } // "null"
+func (self *NumberLiteral) Idx1() file.Idx      { return file.Idx(int(self.Idx) + len(self.Literal)) }
+func (self *ObjectLiteral) Idx1() file.Idx      { return self.RightBrace }
+func (self *RegExpLiteral) Idx1() file.Idx      { return file.Idx(int(self.Idx) + len(self.Literal)) }
+func (self *SequenceExpression) Idx1() file.Idx { return self.Sequence[0].Idx1() }
+func (self *StringLiteral) Idx1() file.Idx      { return file.Idx(int(self.Idx) + len(self.Literal)) }
+func (self *ThisExpression) Idx1() file.Idx     { return self.Idx + 4 }
 func (self *UnaryExpression) Idx1() file.Idx {
 	if self.Postfix {
 		return self.Operand.Idx1() + 2 // ++ --


Test patch:
diff --git a/ast/walk_test.go b/ast/walk_test.go
index ad0e2f2..f063a18 100644
--- a/ast/walk_test.go
+++ b/ast/walk_test.go
@@ -10,11 +10,12 @@ import (
 )
 
 type walker struct {
-	stack     []ast.Node
-	source    string
-	shift     file.Idx
-	seen      map[ast.Node]struct{}
-	duplicate int
+	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
@@ -48,17 +49,23 @@ func (w *walker) Enter(n ast.Node) ast.Visitor {
 
 	w.seen[n] = struct{}{}
 
-	if id, ok := n.(*ast.Identifier); ok && id != nil {
-		idx := n.Idx0() + w.shift - 1
-		s := w.source[:idx] + "IDENT_" + w.source[idx:]
-		w.source = s
-		w.shift += 6
-	}
-	if v, ok := n.(*ast.VariableExpression); ok && v != nil {
-		idx := n.Idx0() + w.shift - 1
-		s := w.source[:idx] + "VAR_" + w.source[idx:]
-		w.source = s
-		w.shift += 4
+	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
@@ -93,3 +100,40 @@ func TestVisitorRewrite(t *testing.T) {
 	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)
+		})
+	}
+}