196 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			196 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2013 The Go Authors. All rights reserved.
 | 
						|
// Use of this source code is governed by a BSD-style
 | 
						|
// license that can be found in the LICENSE file.
 | 
						|
 | 
						|
package astutil_test
 | 
						|
 | 
						|
// This file defines tests of PathEnclosingInterval.
 | 
						|
 | 
						|
// TODO(adonovan): exhaustive tests that run over the whole input
 | 
						|
// tree, not just handcrafted examples.
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"go/ast"
 | 
						|
	"go/parser"
 | 
						|
	"go/token"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"golang.org/x/tools/go/ast/astutil"
 | 
						|
)
 | 
						|
 | 
						|
// pathToString returns a string containing the concrete types of the
 | 
						|
// nodes in path.
 | 
						|
func pathToString(path []ast.Node) string {
 | 
						|
	var buf bytes.Buffer
 | 
						|
	fmt.Fprint(&buf, "[")
 | 
						|
	for i, n := range path {
 | 
						|
		if i > 0 {
 | 
						|
			fmt.Fprint(&buf, " ")
 | 
						|
		}
 | 
						|
		fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
 | 
						|
	}
 | 
						|
	fmt.Fprint(&buf, "]")
 | 
						|
	return buf.String()
 | 
						|
}
 | 
						|
 | 
						|
// findInterval parses input and returns the [start, end) positions of
 | 
						|
// the first occurrence of substr in input.  f==nil indicates failure;
 | 
						|
// an error has already been reported in that case.
 | 
						|
//
 | 
						|
func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
 | 
						|
	f, err := parser.ParseFile(fset, "<input>", input, 0)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("parse error: %s", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	i := strings.Index(input, substr)
 | 
						|
	if i < 0 {
 | 
						|
		t.Errorf("%q is not a substring of input", substr)
 | 
						|
		f = nil
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	filePos := fset.File(f.Package)
 | 
						|
	return f, filePos.Pos(i), filePos.Pos(i + len(substr))
 | 
						|
}
 | 
						|
 | 
						|
// Common input for following tests.
 | 
						|
const input = `
 | 
						|
// Hello.
 | 
						|
package main
 | 
						|
import "fmt"
 | 
						|
func f() {}
 | 
						|
func main() {
 | 
						|
	z := (x + y) // add them
 | 
						|
        f() // NB: ExprStmt and its CallExpr have same Pos/End
 | 
						|
}
 | 
						|
`
 | 
						|
 | 
						|
func TestPathEnclosingInterval_Exact(t *testing.T) {
 | 
						|
	// For the exact tests, we check that a substring is mapped to
 | 
						|
	// the canonical string for the node it denotes.
 | 
						|
	tests := []struct {
 | 
						|
		substr string // first occurrence of this string indicates interval
 | 
						|
		node   string // complete text of expected containing node
 | 
						|
	}{
 | 
						|
		{"package",
 | 
						|
			input[11 : len(input)-1]},
 | 
						|
		{"\npack",
 | 
						|
			input[11 : len(input)-1]},
 | 
						|
		{"main",
 | 
						|
			"main"},
 | 
						|
		{"import",
 | 
						|
			"import \"fmt\""},
 | 
						|
		{"\"fmt\"",
 | 
						|
			"\"fmt\""},
 | 
						|
		{"\nfunc f() {}\n",
 | 
						|
			"func f() {}"},
 | 
						|
		{"x ",
 | 
						|
			"x"},
 | 
						|
		{" y",
 | 
						|
			"y"},
 | 
						|
		{"z",
 | 
						|
			"z"},
 | 
						|
		{" + ",
 | 
						|
			"x + y"},
 | 
						|
		{" :=",
 | 
						|
			"z := (x + y)"},
 | 
						|
		{"x + y",
 | 
						|
			"x + y"},
 | 
						|
		{"(x + y)",
 | 
						|
			"(x + y)"},
 | 
						|
		{" (x + y) ",
 | 
						|
			"(x + y)"},
 | 
						|
		{" (x + y) // add",
 | 
						|
			"(x + y)"},
 | 
						|
		{"func",
 | 
						|
			"func f() {}"},
 | 
						|
		{"func f() {}",
 | 
						|
			"func f() {}"},
 | 
						|
		{"\nfun",
 | 
						|
			"func f() {}"},
 | 
						|
		{" f",
 | 
						|
			"f"},
 | 
						|
	}
 | 
						|
	for _, test := range tests {
 | 
						|
		f, start, end := findInterval(t, new(token.FileSet), input, test.substr)
 | 
						|
		if f == nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		path, exact := astutil.PathEnclosingInterval(f, start, end)
 | 
						|
		if !exact {
 | 
						|
			t.Errorf("PathEnclosingInterval(%q) not exact", test.substr)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if len(path) == 0 {
 | 
						|
			if test.node != "" {
 | 
						|
				t.Errorf("PathEnclosingInterval(%q).path: got [], want %q",
 | 
						|
					test.substr, test.node)
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if got := input[path[0].Pos():path[0].End()]; got != test.node {
 | 
						|
			t.Errorf("PathEnclosingInterval(%q): got %q, want %q (path was %s)",
 | 
						|
				test.substr, got, test.node, pathToString(path))
 | 
						|
			continue
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestPathEnclosingInterval_Paths(t *testing.T) {
 | 
						|
	// For these tests, we check only the path of the enclosing
 | 
						|
	// node, but not its complete text because it's often quite
 | 
						|
	// large when !exact.
 | 
						|
	tests := []struct {
 | 
						|
		substr string // first occurrence of this string indicates interval
 | 
						|
		path   string // the pathToString(),exact of the expected path
 | 
						|
	}{
 | 
						|
		{"// add",
 | 
						|
			"[BlockStmt FuncDecl File],false"},
 | 
						|
		{"(x + y",
 | 
						|
			"[ParenExpr AssignStmt BlockStmt FuncDecl File],false"},
 | 
						|
		{"x +",
 | 
						|
			"[BinaryExpr ParenExpr AssignStmt BlockStmt FuncDecl File],false"},
 | 
						|
		{"z := (x",
 | 
						|
			"[AssignStmt BlockStmt FuncDecl File],false"},
 | 
						|
		{"func f",
 | 
						|
			"[FuncDecl File],false"},
 | 
						|
		{"func f()",
 | 
						|
			"[FuncDecl File],false"},
 | 
						|
		{" f()",
 | 
						|
			"[FuncDecl File],false"},
 | 
						|
		{"() {}",
 | 
						|
			"[FuncDecl File],false"},
 | 
						|
		{"// Hello",
 | 
						|
			"[File],false"},
 | 
						|
		{" f",
 | 
						|
			"[Ident FuncDecl File],true"},
 | 
						|
		{"func ",
 | 
						|
			"[FuncDecl File],true"},
 | 
						|
		{"mai",
 | 
						|
			"[Ident File],true"},
 | 
						|
		{"f() // NB",
 | 
						|
			"[CallExpr ExprStmt BlockStmt FuncDecl File],true"},
 | 
						|
	}
 | 
						|
	for _, test := range tests {
 | 
						|
		f, start, end := findInterval(t, new(token.FileSet), input, test.substr)
 | 
						|
		if f == nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		path, exact := astutil.PathEnclosingInterval(f, start, end)
 | 
						|
		if got := fmt.Sprintf("%s,%v", pathToString(path), exact); got != test.path {
 | 
						|
			t.Errorf("PathEnclosingInterval(%q): got %q, want %q",
 | 
						|
				test.substr, got, test.path)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |