Bläddra i källkod

Add ChangesXPather

Signed-off-by: Vadim Markovtsev <vadim@sourced.tech>
Vadim Markovtsev 7 år sedan
förälder
incheckning
781d6fbc53
2 ändrade filer med 136 tillägg och 0 borttagningar
  1. 67 0
      changes_xpather.go
  2. 69 0
      changes_xpather_test.go

+ 67 - 0
changes_xpather.go

@@ -0,0 +1,67 @@
+package hercules
+
+import (
+	"log"
+
+	"github.com/minio/highwayhash"
+	"gopkg.in/bblfsh/sdk.v1/uast"
+	"gopkg.in/bblfsh/client-go.v2/tools"
+	"gopkg.in/src-d/go-git.v4/plumbing"
+)
+
+// ChangesXPather extracts changed UAST nodes from files changed in the current commit.
+type ChangesXPather struct {
+	XPath string
+}
+
+var hashKey = []byte{
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+	16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+}
+
+// Extract returns the list of new or changed UAST nodes filtered by XPath.
+func (xpather ChangesXPather) Extract(changes []UASTChange) []*uast.Node {
+	result := []*uast.Node{}
+	for _, change := range changes {
+		if change.After == nil {
+			continue
+		}
+		oldNodes := xpather.filter(change.Before, change.Change.From.TreeEntry.Hash)
+		newNodes := xpather.filter(change.After, change.Change.To.TreeEntry.Hash)
+		oldHashes := xpather.hash(oldNodes)
+		newHashes := xpather.hash(newNodes)
+		// remove any untouched nodes
+		for hash := range oldHashes {
+			delete(newHashes, hash)
+		}
+		// there can be hash collisions; we ignore them
+		for _, node := range newHashes {
+			result = append(result, node)
+		}
+	}
+	return result
+}
+
+func (xpather ChangesXPather) filter(root *uast.Node, origin plumbing.Hash) []*uast.Node {
+	if root != nil {
+		nodes, err := tools.Filter(root, xpather.XPath)
+		if err != nil {
+			log.Printf("libuast filter error on object %s: %v", origin.String(), err)
+			return []*uast.Node{}
+		}
+		return nodes
+	}
+	return []*uast.Node{}
+}
+
+func (xpather ChangesXPather) hash(nodes []*uast.Node) map[uint64]*uast.Node {
+	result := map[uint64]*uast.Node{}
+	for _, node := range nodes {
+		buffer, err := node.Marshal()
+		if err != nil {
+			panic(err)
+		}
+		result[highwayhash.Sum64(buffer, hashKey)] = node
+	}
+	return result
+}

+ 69 - 0
changes_xpather_test.go

@@ -0,0 +1,69 @@
+package hercules
+
+import (
+	"io/ioutil"
+	"log"
+	"testing"
+
+	"gopkg.in/bblfsh/client-go.v2"
+	"gopkg.in/src-d/go-git.v4/plumbing"
+	"gopkg.in/bblfsh/sdk.v1/uast"
+	"gopkg.in/src-d/go-git.v4/plumbing/object"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestChangesXPatherExtractChanged(t *testing.T) {
+	client, err := bblfsh.NewClient("0.0.0.0:9432")
+	if err != nil {
+		log.Panicf("Failed to connect to the Babelfish server at 0.0.0.0:9432: %v", err)
+	}
+
+	hash1 := "a98a6940eb4cfb1eb635c3232485a75c4b63fff3"
+	hash2 := "42457dc695fa73ec9621b47832d5711f6325410d"
+	root1 := parseBlobFromTestRepo(hash1, "burndown.go", client)
+	root2 := parseBlobFromTestRepo(hash2, "burndown.go", client)
+	gitChange := fakeChangeForName("burndown.go", hash1, hash2)
+	uastChanges := []UASTChange{
+		{Before: root1, After: root2, Change: gitChange},
+		{Before: nil, After: root2, Change: gitChange},
+		{Before: root1, After: nil, Change: gitChange},
+	}
+	xpather := ChangesXPather{XPath: "//*[@roleComment]"}
+	nodes := xpather.Extract(uastChanges)
+	assert.True(t, len(nodes) > 0)
+}
+
+func parseBlobFromTestRepo(hash, name string, client *bblfsh.Client) *uast.Node {
+	blob, err := testRepository.BlobObject(plumbing.NewHash(hash))
+	if err != nil {
+		panic(err)
+	}
+	reader, err := blob.Reader()
+	if err != nil {
+		panic(err)
+	}
+	defer reader.Close()
+	data, err := ioutil.ReadAll(reader)
+	if err != nil {
+		panic(err)
+	}
+	request := client.NewParseRequest()
+	request.Content(string(data))
+	request.Filename(name)
+	response, err := request.Do()
+	if err != nil {
+		panic(err)
+	}
+	return response.UAST
+}
+
+func fakeChangeForName(name string, hashFrom string, hashTo string) *object.Change {
+	return &object.Change{
+		From: object.ChangeEntry{Name: name, TreeEntry: object.TreeEntry{
+			Name: name, Hash: plumbing.NewHash(hashFrom),
+		}},
+		To: object.ChangeEntry{Name: name, TreeEntry: object.TreeEntry{
+			Name: name, Hash: plumbing.NewHash(hashTo),
+		}},
+	}
+}