Exemple #1
0
// Decorations implements part of the xrefs Service interface.
func (t *tableImpl) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) {
	if req.GetLocation() == nil || req.GetLocation().Ticket == "" {
		return nil, errors.New("missing location")
	}

	ticket, err := kytheuri.Fix(req.GetLocation().Ticket)
	if err != nil {
		return nil, fmt.Errorf("invalid ticket %q: %v", req.GetLocation().Ticket, err)
	}

	decor, err := t.fileDecorations(ctx, ticket)
	if err == table.ErrNoSuchKey {
		return nil, xrefs.ErrDecorationsNotFound
	} else if err != nil {
		return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err)
	}

	text := decor.File.Text
	if len(req.DirtyBuffer) > 0 {
		text = req.DirtyBuffer
	}
	norm := xrefs.NewNormalizer(text)

	loc, err := norm.Location(req.GetLocation())
	if err != nil {
		return nil, err
	}

	reply := &xpb.DecorationsReply{Location: loc}

	if req.SourceText {
		reply.Encoding = decor.File.Encoding
		if loc.Kind == xpb.Location_FILE {
			reply.SourceText = text
		} else {
			reply.SourceText = text[loc.Start.ByteOffset:loc.End.ByteOffset]
		}
	}

	if req.References {
		patterns := xrefs.ConvertFilters(req.Filter)

		var patcher *xrefs.Patcher
		if len(req.DirtyBuffer) > 0 {
			patcher = xrefs.NewPatcher(decor.File.Text, req.DirtyBuffer)
		}

		// The span with which to constrain the set of returned anchor references.
		var startBoundary, endBoundary int32
		spanKind := req.SpanKind
		if loc.Kind == xpb.Location_FILE {
			startBoundary = 0
			endBoundary = int32(len(text))
			spanKind = xpb.DecorationsRequest_WITHIN_SPAN
		} else {
			startBoundary = loc.Start.ByteOffset
			endBoundary = loc.End.ByteOffset
		}

		reply.Reference = make([]*xpb.DecorationsReply_Reference, 0, len(decor.Decoration))
		reply.Nodes = make(map[string]*xpb.NodeInfo)

		seenTarget := stringset.New()

		// Reference.TargetTicket -> NodeInfo (superset of reply.Nodes)
		var nodes map[string]*xpb.NodeInfo
		if len(patterns) > 0 {
			nodes = make(map[string]*xpb.NodeInfo)
			for _, n := range decor.Target {
				nodes[n.Ticket] = nodeToInfo(patterns, n)
			}
		}

		// Reference.TargetTicket -> []Reference set
		var refs map[string][]*xpb.DecorationsReply_Reference
		// ExpandedAnchor.Ticket -> ExpandedAnchor
		var defs map[string]*srvpb.ExpandedAnchor
		if req.TargetDefinitions {
			refs = make(map[string][]*xpb.DecorationsReply_Reference)
			reply.DefinitionLocations = make(map[string]*xpb.Anchor)

			defs = make(map[string]*srvpb.ExpandedAnchor)
			for _, def := range decor.TargetDefinitions {
				defs[def.Ticket] = def
			}
		}

		for _, d := range decor.Decoration {
			start, end, exists := patcher.Patch(d.Anchor.StartOffset, d.Anchor.EndOffset)
			// Filter non-existent anchor.  Anchors can no longer exist if we were
			// given a dirty buffer and the anchor was inside a changed region.
			if exists {
				if xrefs.InSpanBounds(spanKind, start, end, startBoundary, endBoundary) {
					d.Anchor.StartOffset = start
					d.Anchor.EndOffset = end

					r := decorationToReference(norm, d)
					if req.TargetDefinitions {
						if def, ok := defs[d.TargetDefinition]; ok {
							reply.DefinitionLocations[d.TargetDefinition] = a2a(def, false).Anchor
						} else {
							refs[r.TargetTicket] = append(refs[r.TargetTicket], r)
						}
					} else {
						r.TargetDefinition = ""
					}

					reply.Reference = append(reply.Reference, r)

					if !seenTarget.Contains(r.TargetTicket) && nodes != nil {
						reply.Nodes[r.TargetTicket] = nodes[r.TargetTicket]
						seenTarget.Add(r.TargetTicket)
					}
				}
			}
		}

		// Only compute target definitions if the serving data doesn't contain any
		// TODO(schroederc): remove this once serving data is always populated
		if req.TargetDefinitions && len(defs) == 0 {
			targetTickets := make([]string, 0, len(refs))
			for ticket := range refs {
				targetTickets = append(targetTickets, ticket)
			}

			defs, err := xrefs.SlowDefinitions(t, ctx, targetTickets)
			if err != nil {
				return nil, fmt.Errorf("error retrieving target definitions: %v", err)
			}

			reply.DefinitionLocations = make(map[string]*xpb.Anchor, len(defs))
			for tgt, def := range defs {
				for _, ref := range refs[tgt] {
					if def.Ticket != ref.SourceTicket {
						ref.TargetDefinition = def.Ticket
						if _, ok := reply.DefinitionLocations[def.Ticket]; !ok {
							reply.DefinitionLocations[def.Ticket] = def
						}
					}
				}
			}
		}
	}

	return reply, nil
}
Exemple #2
0
// Decorations implements part of the xrefs Service interface.
func (t *tableImpl) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) {
	if req.GetLocation() == nil || req.GetLocation().Ticket == "" {
		return nil, errors.New("missing location")
	}

	ticket, err := kytheuri.Fix(req.GetLocation().Ticket)
	if err != nil {
		return nil, fmt.Errorf("invalid ticket %q: %v", req.GetLocation().Ticket, err)
	}

	decor, err := t.fileDecorations(ctx, ticket)
	if err == table.ErrNoSuchKey {
		return nil, fmt.Errorf("decorations not found for file %q", ticket)
	} else if err != nil {
		return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err)
	}

	text := decor.SourceText
	if len(req.DirtyBuffer) > 0 {
		text = req.DirtyBuffer
	}
	norm := xrefs.NewNormalizer(text)

	loc, err := norm.Location(req.GetLocation())
	if err != nil {
		return nil, err
	}

	reply := &xpb.DecorationsReply{Location: loc}

	if req.SourceText {
		reply.Encoding = decor.Encoding
		if loc.Kind == xpb.Location_FILE {
			reply.SourceText = text
		} else {
			reply.SourceText = text[loc.Start.ByteOffset:loc.End.ByteOffset]
		}
	}

	if req.References {
		// Set of node tickets for which to retrieve facts.  These are the nodes
		// used in the returned references (both anchor sources and node targets).
		nodeTickets := stringset.New()

		var patcher *xrefs.Patcher
		var offsetMapping map[string]span // Map from anchor ticket to patched span
		if len(req.DirtyBuffer) > 0 {
			patcher = xrefs.NewPatcher(decor.SourceText, req.DirtyBuffer)
			offsetMapping = make(map[string]span)
		}

		// The span with which to constrain the set of returned anchor references.
		var startBoundary, endBoundary int32
		if loc.Kind == xpb.Location_FILE {
			startBoundary = 0
			endBoundary = int32(len(text))
		} else {
			startBoundary = loc.Start.ByteOffset
			endBoundary = loc.End.ByteOffset
		}

		reply.Reference = make([]*xpb.DecorationsReply_Reference, 0, len(decor.Decoration))
		for _, d := range decor.Decoration {
			start, end, exists := patcher.Patch(d.Anchor.StartOffset, d.Anchor.EndOffset)
			// Filter non-existent anchor.  Anchors can no longer exist if we were
			// given a dirty buffer and the anchor was inside a changed region.
			if exists {
				if start >= startBoundary && end <= endBoundary {
					if offsetMapping != nil {
						// Save the patched span to update the corresponding facts of the
						// anchor node in reply.Node.
						offsetMapping[d.Anchor.Ticket] = span{start, end}
					}
					reply.Reference = append(reply.Reference, decorationToReference(norm, d))
					nodeTickets.Add(d.Anchor.Ticket)
					nodeTickets.Add(d.TargetTicket)
				}
			}
		}

		// Only request Nodes when there are fact filters given.
		if len(req.Filter) > 0 {
			// Retrieve facts for all nodes referenced in the file decorations.
			nodesReply, err := t.Nodes(ctx, &xpb.NodesRequest{
				Ticket: nodeTickets.Slice(),
				Filter: req.Filter,
			})
			if err != nil {
				return nil, fmt.Errorf("error getting nodes: %v", err)
			}
			reply.Node = nodesReply.Node
		}

		// Patch anchor node facts in reply to match dirty buffer
		if len(offsetMapping) > 0 {
			for _, n := range reply.Node {
				if span, ok := offsetMapping[n.Ticket]; ok {
					for _, f := range n.Fact {
						switch f.Name {
						case schema.AnchorStartFact:
							f.Value = []byte(strconv.Itoa(int(span.start)))
						case schema.AnchorEndFact:
							f.Value = []byte(strconv.Itoa(int(span.end)))
						}
					}
				}
			}
		}
	}

	return reply, nil
}
Exemple #3
0
// Decorations implements part of the Service interface.
func (g *GraphStoreService) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) {
	if len(req.DirtyBuffer) > 0 {
		return nil, errors.New("UNIMPLEMENTED: dirty buffers")
	} else if req.GetLocation() == nil {
		// TODO(schroederc): allow empty location when given dirty buffer
		return nil, errors.New("missing location")
	}

	fileVName, err := kytheuri.ToVName(req.Location.Ticket)
	if err != nil {
		return nil, fmt.Errorf("invalid file ticket %q: %v", req.Location.Ticket, err)
	}

	text, encoding, err := getSourceText(ctx, g.gs, fileVName)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve file text: %v", err)
	}
	norm := xrefs.NewNormalizer(text)

	loc, err := norm.Location(req.GetLocation())
	if err != nil {
		return nil, err
	}

	reply := &xpb.DecorationsReply{
		Location: loc,
		Nodes:    make(map[string]*xpb.NodeInfo),
	}

	// Handle DecorationsRequest.SourceText switch
	if req.SourceText {
		if loc.Kind == xpb.Location_FILE {
			reply.SourceText = text
		} else {
			reply.SourceText = text[loc.Start.ByteOffset:loc.End.ByteOffset]
		}
		reply.Encoding = encoding
	}

	// Handle DecorationsRequest.References switch
	if req.References {
		// Traverse the following chain of edges:
		//   file --%/kythe/edge/childof-> []anchor --forwardEdgeKind-> []target
		//
		// Add []anchor and []target nodes to reply.Nodes
		// Add all {anchor, forwardEdgeKind, target} tuples to reply.Reference

		patterns := xrefs.ConvertFilters(req.Filter)

		children, err := getEdges(ctx, g.gs, fileVName, func(e *spb.Entry) bool {
			return e.EdgeKind == revChildOfEdgeKind
		})
		if err != nil {
			return nil, fmt.Errorf("failed to retrieve file children: %v", err)
		}

		targetSet := stringset.New()
		for _, edge := range children {
			anchor := edge.Target
			ticket := kytheuri.ToString(anchor)
			anchorNodeReply, err := g.Nodes(ctx, &xpb.NodesRequest{
				Ticket: []string{ticket},
			})
			if err != nil {
				return nil, fmt.Errorf("failure getting reference source node: %v", err)
			} else if len(anchorNodeReply.Nodes) != 1 {
				return nil, fmt.Errorf("found %d nodes for {%+v}", len(anchorNodeReply.Nodes), anchor)
			}

			node, ok := xrefs.NodesMap(anchorNodeReply.Nodes)[ticket]
			if !ok {
				return nil, fmt.Errorf("failed to find info for node %q", ticket)
			} else if string(node[schema.NodeKindFact]) != schema.AnchorKind {
				// Skip child if it isn't an anchor node
				continue
			}

			anchorStart, err := strconv.Atoi(string(node[schema.AnchorStartFact]))
			if err != nil {
				log.Printf("Invalid anchor start offset %q for node %q: %v", node[schema.AnchorStartFact], ticket, err)
				continue
			}
			anchorEnd, err := strconv.Atoi(string(node[schema.AnchorEndFact]))
			if err != nil {
				log.Printf("Invalid anchor end offset %q for node %q: %v", node[schema.AnchorEndFact], ticket, err)
				continue
			}

			if loc.Kind == xpb.Location_SPAN {
				// Check if anchor fits within/around requested source text window
				if !xrefs.InSpanBounds(req.SpanKind, int32(anchorStart), int32(anchorEnd), loc.Start.ByteOffset, loc.End.ByteOffset) {
					continue
				} else if anchorStart > anchorEnd {
					log.Printf("Invalid anchor offset span %d:%d", anchorStart, anchorEnd)
					continue
				}
			}

			targets, err := getEdges(ctx, g.gs, anchor, func(e *spb.Entry) bool {
				return schema.EdgeDirection(e.EdgeKind) == schema.Forward && e.EdgeKind != schema.ChildOfEdge
			})
			if err != nil {
				return nil, fmt.Errorf("failed to retrieve targets of anchor %v: %v", anchor, err)
			}
			if len(targets) == 0 {
				log.Printf("Anchor missing forward edges: {%+v}", anchor)
				continue
			}

			if node := filterNode(patterns, anchorNodeReply.Nodes[ticket]); node != nil {
				reply.Nodes[ticket] = node
			}
			for _, edge := range targets {
				targetTicket := kytheuri.ToString(edge.Target)
				targetSet.Add(targetTicket)
				reply.Reference = append(reply.Reference, &xpb.DecorationsReply_Reference{
					SourceTicket: ticket,
					Kind:         edge.Kind,
					TargetTicket: targetTicket,
					AnchorStart:  norm.ByteOffset(int32(anchorStart)),
					AnchorEnd:    norm.ByteOffset(int32(anchorEnd)),
				})
			}
		}
		sort.Sort(bySpan(reply.Reference))

		// Only request Nodes when there are fact filters given.
		if len(req.Filter) > 0 {
			// Ensure returned nodes are not duplicated.
			for ticket := range reply.Nodes {
				targetSet.Remove(ticket)
			}

			// Batch request all Reference target nodes
			nodesReply, err := g.Nodes(ctx, &xpb.NodesRequest{
				Ticket: targetSet.Slice(),
				Filter: req.Filter,
			})
			if err != nil {
				return nil, fmt.Errorf("failure getting reference target nodes: %v", err)
			}
			for ticket, node := range nodesReply.Nodes {
				reply.Nodes[ticket] = node
			}
		}
	}

	return reply, nil
}
Exemple #4
0
// Decorations implements part of the xrefs Service interface.
func (t *tableImpl) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) {
	if req.GetLocation() == nil || req.GetLocation().Ticket == "" {
		return nil, errors.New("missing location")
	}

	ticket, err := kytheuri.Fix(req.GetLocation().Ticket)
	if err != nil {
		return nil, fmt.Errorf("invalid ticket %q: %v", req.GetLocation().Ticket, err)
	}

	decor, err := t.fileDecorations(ctx, ticket)
	if err == table.ErrNoSuchKey {
		return nil, fmt.Errorf("decorations not found for file %q", ticket)
	} else if err != nil {
		return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err)
	}

	text := decor.SourceText
	if len(req.DirtyBuffer) > 0 {
		text = req.DirtyBuffer
	}
	norm := xrefs.NewNormalizer(text)

	loc, err := norm.Location(req.GetLocation())
	if err != nil {
		return nil, err
	}

	reply := &xpb.DecorationsReply{Location: loc}

	if req.SourceText {
		reply.Encoding = decor.Encoding
		if loc.Kind == xpb.Location_FILE {
			reply.SourceText = text
		} else {
			reply.SourceText = text[loc.Start.ByteOffset:loc.End.ByteOffset]
		}
	}

	if req.References {
		patterns := xrefs.ConvertFilters(req.Filter)
		nodeTickets := stringset.New()

		var patcher *xrefs.Patcher
		var offsetMapping map[string]span // Map from anchor ticket to patched span
		if len(req.DirtyBuffer) > 0 {
			patcher = xrefs.NewPatcher(decor.SourceText, req.DirtyBuffer)
			offsetMapping = make(map[string]span)
		}

		// The span with which to constrain the set of returned anchor references.
		var startBoundary, endBoundary int32
		if loc.Kind == xpb.Location_FILE {
			startBoundary = 0
			endBoundary = int32(len(text))
		} else {
			startBoundary = loc.Start.ByteOffset
			endBoundary = loc.End.ByteOffset
		}

		reply.Reference = make([]*xpb.DecorationsReply_Reference, 0, len(decor.Decoration))
		for _, d := range decor.Decoration {
			start, end, exists := patcher.Patch(d.Anchor.StartOffset, d.Anchor.EndOffset)
			// Filter non-existent anchor.  Anchors can no longer exist if we were
			// given a dirty buffer and the anchor was inside a changed region.
			if exists {
				if start >= startBoundary && end <= endBoundary {
					if offsetMapping != nil {
						// Save the patched span to update the corresponding facts of the
						// anchor node in reply.Node.
						offsetMapping[d.Anchor.Ticket] = span{start, end}
					}
					reply.Reference = append(reply.Reference, decorationToReference(norm, d))

					if len(patterns) > 0 && !nodeTickets.Contains(d.Target.Ticket) {
						nodeTickets.Add(d.Target.Ticket)
						reply.Node = append(reply.Node, nodeToInfo(patterns, d.Target))
					}
				}
			}
		}
	}

	return reply, nil
}
Exemple #5
0
// Decorations implements part of the xrefs Service interface.
func (t *Table) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) {
	if len(req.DirtyBuffer) > 0 {
		log.Println("TODO: implement DecorationsRequest.DirtyBuffer")
		return nil, errors.New("dirty buffers unimplemented")
	} else if req.GetLocation() == nil {
		// TODO(schroederc): allow empty location when given dirty buffer
		return nil, errors.New("missing location")
	}

	var decor srvpb.FileDecorations
	ticket := req.GetLocation().Ticket
	if err := t.Lookup(DecorationsKey(ticket), &decor); err == table.ErrNoSuchKey {
		return nil, fmt.Errorf("decorations not found for file %q", ticket)
	} else if err != nil {
		return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err)
	}

	loc, err := xrefs.NewNormalizer(decor.SourceText).Location(req.GetLocation())
	if err != nil {
		return nil, err
	}

	reply := &xpb.DecorationsReply{Location: loc}

	if req.SourceText {
		reply.Encoding = decor.Encoding
		if loc.Kind == xpb.Location_FILE {
			reply.SourceText = decor.SourceText
		} else {
			reply.SourceText = decor.SourceText[loc.Start.ByteOffset:loc.End.ByteOffset]
		}
	}

	if req.References {
		nodeTickets := stringset.New()
		if loc.Kind == xpb.Location_FILE {
			reply.Reference = make([]*xpb.DecorationsReply_Reference, len(decor.Decoration))
			for i, d := range decor.Decoration {
				reply.Reference[i] = decorationToReference(d)
				nodeTickets.Add(d.Anchor.Ticket)
				nodeTickets.Add(d.TargetTicket)
			}
		} else {
			for _, d := range decor.Decoration {
				if d.Anchor.StartOffset >= loc.Start.ByteOffset && d.Anchor.EndOffset <= loc.End.ByteOffset {
					reply.Reference = append(reply.Reference, decorationToReference(d))
					nodeTickets.Add(d.Anchor.Ticket)
					nodeTickets.Add(d.TargetTicket)
				}
			}
		}

		nodesReply, err := t.Nodes(ctx, &xpb.NodesRequest{Ticket: nodeTickets.Slice()})
		if err != nil {
			return nil, fmt.Errorf("error getting nodes: %v", err)
		}
		reply.Node = nodesReply.Node
	}

	return reply, nil
}
Exemple #6
0
// Decorations implements part of the xrefs.Service interface.
func (db *DB) Decorations(req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) {
	if req.GetLocation() == nil {
		return nil, errors.New("missing location")
	} else if req.Location.Kind != xpb.Location_FILE {
		return nil, fmt.Errorf("%s location kind unimplemented", req.Location.Kind)
	} else if len(req.DirtyBuffer) != 0 {
		return nil, errors.New("dirty buffers unimplemented")
	}

	fileTicket := req.Location.Ticket

	edgesReply, err := db.Edges(&xpb.EdgesRequest{
		Ticket: []string{fileTicket},
		Kind:   []string{revChildOfEdge},
		Filter: []string{schema.NodeKindFact, schema.TextFact, schema.TextEncodingFact},
	})
	if err != nil {
		return nil, err
	}

	reply := &xpb.DecorationsReply{
		Location: &xpb.Location{
			Ticket: fileTicket,
		},
	}

	nodes := xrefs.NodesMap(edgesReply.Node)

	if req.SourceText {
		if nodes[fileTicket] == nil {
			nodesReply, err := db.Nodes(&xpb.NodesRequest{Ticket: []string{fileTicket}})
			if err != nil {
				return nil, err
			}
			nodes = xrefs.NodesMap(nodesReply.Node)
		}
		reply.SourceText = nodes[fileTicket][schema.TextFact]
		reply.Encoding = string(nodes[fileTicket][schema.TextEncodingFact])
	}

	nodeTickets := stringset.New()

	if req.References {
		// Traverse the following chain of edges:
		//   file --%/kythe/edge/childof-> []anchor --forwardEdgeKind-> []target
		edges := xrefs.EdgesMap(edgesReply.EdgeSet)

		for _, anchor := range edges[fileTicket][revChildOfEdge] {
			if string(nodes[anchor][schema.NodeKindFact]) != schema.AnchorKind {
				continue
			}

			uri, err := kytheuri.Parse(anchor)
			if err != nil {
				return nil, fmt.Errorf("invalid anchor ticket found %q: %v", anchor, err)
			}
			rows, err := db.anchorEdgesStmt.Query(schema.ChildOfEdge, uri.Signature, uri.Corpus, uri.Root, uri.Path, uri.Language)
			if err != nil {
				return nil, fmt.Errorf("anchor %q edge query error: %v", anchor, err)
			}

			for rows.Next() {
				var target kytheuri.URI
				var kind string
				if err := rows.Scan(&target.Signature, &target.Corpus, &target.Root, &target.Path, &target.Language, &kind); err != nil {
					return nil, fmt.Errorf("anchor %q edge scan error: %v", anchor, err)
				}
				ticket := target.String()
				reply.Reference = append(reply.Reference, &xpb.DecorationsReply_Reference{
					SourceTicket: anchor,
					Kind:         kind,
					TargetTicket: ticket,
				})
				nodeTickets.Add(anchor, ticket)
			}
		}
	}

	nodesReply, err := db.Nodes(&xpb.NodesRequest{Ticket: nodeTickets.Slice()})
	if err != nil {
		return nil, err
	}
	reply.Node = nodesReply.Node

	return reply, nil
}