Exemple #1
0
// CrossReferences implements part of the xrefs.Service interface.
func (t *tableImpl) CrossReferences(ctx context.Context, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) {
	tickets, err := xrefs.FixTickets(req.Ticket)
	if err != nil {
		return nil, err
	}

	stats := refStats{
		max: int(req.PageSize),
	}
	if stats.max < 0 {
		return nil, fmt.Errorf("invalid page_size: %d", req.PageSize)
	} else if stats.max == 0 {
		stats.max = defaultPageSize
	} else if stats.max > maxPageSize {
		stats.max = maxPageSize
	}

	var edgesPageToken string
	if req.PageToken != "" {
		rec, err := base64.StdEncoding.DecodeString(req.PageToken)
		if err != nil {
			return nil, fmt.Errorf("invalid page_token: %q", req.PageToken)
		}
		var t ipb.PageToken
		if err := proto.Unmarshal(rec, &t); err != nil || t.Index < 0 {
			return nil, fmt.Errorf("invalid page_token: %q", req.PageToken)
		}
		stats.skip = int(t.Index)
		edgesPageToken = t.SecondaryToken
	}
	pageToken := stats.skip

	reply := &xpb.CrossReferencesReply{
		CrossReferences: make(map[string]*xpb.CrossReferencesReply_CrossReferenceSet, len(req.Ticket)),
		Nodes:           make(map[string]*xpb.NodeInfo, len(req.Ticket)),

		Total: &xpb.CrossReferencesReply_Total{},
	}
	var nextToken *ipb.PageToken

	wantMoreCrossRefs := edgesPageToken == "" &&
		(req.DefinitionKind != xpb.CrossReferencesRequest_NO_DEFINITIONS ||
			req.DeclarationKind != xpb.CrossReferencesRequest_NO_DECLARATIONS ||
			req.ReferenceKind != xpb.CrossReferencesRequest_NO_REFERENCES ||
			req.DocumentationKind != xpb.CrossReferencesRequest_NO_DOCUMENTATION ||
			req.CallerKind != xpb.CrossReferencesRequest_NO_CALLERS)

	for _, ticket := range tickets {
		// TODO(schroederc): retrieve PagedCrossReferences in parallel
		cr, err := t.crossReferences(ctx, ticket)
		if err == table.ErrNoSuchKey {
			log.Println("Missing CrossReferences:", ticket)
			continue
		} else if err != nil {
			return nil, fmt.Errorf("error looking up cross-references for ticket %q: %v", ticket, err)
		}

		crs := &xpb.CrossReferencesReply_CrossReferenceSet{
			Ticket: ticket,
		}

		if req.ExperimentalSignatures {
			crs.DisplayName, err = xrefs.SlowSignature(ctx, t, ticket)
			if err != nil {
				log.Printf("WARNING: error looking up signature for ticket %q: %v", ticket, err)
			}
		}

		for _, grp := range cr.Group {
			switch {
			case xrefs.IsDefKind(req.DefinitionKind, grp.Kind, cr.Incomplete):
				reply.Total.Definitions += int64(len(grp.Anchor))
				if wantMoreCrossRefs {
					stats.addAnchors(&crs.Definition, grp.Anchor, req.AnchorText)
				}
			case xrefs.IsDeclKind(req.DeclarationKind, grp.Kind, cr.Incomplete):
				reply.Total.Declarations += int64(len(grp.Anchor))
				if wantMoreCrossRefs {
					stats.addAnchors(&crs.Declaration, grp.Anchor, req.AnchorText)
				}
			case xrefs.IsDocKind(req.DocumentationKind, grp.Kind):
				reply.Total.Documentation += int64(len(grp.Anchor))
				if wantMoreCrossRefs {
					stats.addAnchors(&crs.Documentation, grp.Anchor, req.AnchorText)
				}
			case xrefs.IsRefKind(req.ReferenceKind, grp.Kind):
				reply.Total.References += int64(len(grp.Anchor))
				if wantMoreCrossRefs {
					stats.addAnchors(&crs.Reference, grp.Anchor, req.AnchorText)
				}
			}
		}

		if wantMoreCrossRefs && req.CallerKind != xpb.CrossReferencesRequest_NO_CALLERS {
			anchors, err := xrefs.SlowCallersForCrossReferences(ctx, t, req.CallerKind == xpb.CrossReferencesRequest_OVERRIDE_CALLERS, req.ExperimentalSignatures, ticket)
			if err != nil {
				return nil, fmt.Errorf("error in SlowCallersForCrossReferences: %v", err)
			}
			reply.Total.Callers += int64(len(anchors))
			stats.addRelatedAnchors(&crs.Caller, anchors, req.AnchorText)
		}

		for _, idx := range cr.PageIndex {
			switch {
			case xrefs.IsDefKind(req.DefinitionKind, idx.Kind, cr.Incomplete):
				reply.Total.Definitions += int64(idx.Count)
				if wantMoreCrossRefs && !stats.skipPage(idx) {
					p, err := t.crossReferencesPage(ctx, idx.PageKey)
					if err != nil {
						return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey)
					}
					stats.addAnchors(&crs.Definition, p.Group.Anchor, req.AnchorText)
				}
			case xrefs.IsDeclKind(req.DeclarationKind, idx.Kind, cr.Incomplete):
				reply.Total.Declarations += int64(idx.Count)
				if wantMoreCrossRefs && !stats.skipPage(idx) {
					p, err := t.crossReferencesPage(ctx, idx.PageKey)
					if err != nil {
						return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey)
					}
					stats.addAnchors(&crs.Declaration, p.Group.Anchor, req.AnchorText)
				}
			case xrefs.IsDocKind(req.DocumentationKind, idx.Kind):
				reply.Total.Documentation += int64(idx.Count)
				if wantMoreCrossRefs && !stats.skipPage(idx) {
					p, err := t.crossReferencesPage(ctx, idx.PageKey)
					if err != nil {
						return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey)
					}
					stats.addAnchors(&crs.Documentation, p.Group.Anchor, req.AnchorText)
				}
			case xrefs.IsRefKind(req.ReferenceKind, idx.Kind):
				reply.Total.References += int64(idx.Count)
				if wantMoreCrossRefs && !stats.skipPage(idx) {
					p, err := t.crossReferencesPage(ctx, idx.PageKey)
					if err != nil {
						return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey)
					}
					stats.addAnchors(&crs.Reference, p.Group.Anchor, req.AnchorText)
				}
			}
		}

		if len(crs.Declaration) > 0 || len(crs.Definition) > 0 || len(crs.Reference) > 0 || len(crs.Documentation) > 0 || len(crs.Caller) > 0 {
			reply.CrossReferences[crs.Ticket] = crs
		}
	}

	if pageToken+stats.total != sumTotalCrossRefs(reply.Total) && stats.total != 0 {
		nextToken = &ipb.PageToken{Index: int32(pageToken + stats.total)}
	}

	if len(req.Filter) > 0 {
		er, err := t.edges(ctx, edgesRequest{
			Tickets:   tickets,
			Filters:   req.Filter,
			Kinds:     func(kind string) bool { return !schema.IsAnchorEdge(kind) },
			PageToken: edgesPageToken,
			TotalOnly: (stats.max <= stats.total),
			PageSize:  stats.max - stats.total,
		})
		if err != nil {
			return nil, fmt.Errorf("error getting related nodes: %v", err)
		}
		reply.Total.RelatedNodesByRelation = er.TotalEdgesByKind
		for ticket, es := range er.EdgeSets {
			nodes := stringset.New()
			crs, ok := reply.CrossReferences[ticket]
			if !ok {
				crs = &xpb.CrossReferencesReply_CrossReferenceSet{
					Ticket: ticket,
				}
			}
			for kind, g := range es.Groups {
				for _, edge := range g.Edge {
					nodes.Add(edge.TargetTicket)
					crs.RelatedNode = append(crs.RelatedNode, &xpb.CrossReferencesReply_RelatedNode{
						RelationKind: kind,
						Ticket:       edge.TargetTicket,
						Ordinal:      edge.Ordinal,
					})
				}
			}
			if len(nodes) > 0 {
				for ticket, n := range er.Nodes {
					if nodes.Contains(ticket) {
						reply.Nodes[ticket] = n
					}
				}
			}

			if !ok && len(crs.RelatedNode) > 0 {
				reply.CrossReferences[ticket] = crs
			}
		}

		if er.NextPageToken != "" {
			nextToken = &ipb.PageToken{SecondaryToken: er.NextPageToken}
		}
	}

	if nextToken != nil {
		rec, err := proto.Marshal(nextToken)
		if err != nil {
			return nil, fmt.Errorf("internal error: error marshalling page token: %v", err)
		}
		reply.NextPageToken = base64.StdEncoding.EncodeToString(rec)
	}

	if req.NodeDefinitions {
		nodeTickets := make([]string, 0, len(reply.Nodes))
		for ticket := range reply.Nodes {
			nodeTickets = append(nodeTickets, ticket)
		}

		// TODO(schroederc): cache this in the serving data
		defs, err := xrefs.SlowDefinitions(t, ctx, nodeTickets)
		if err != nil {
			return nil, fmt.Errorf("error retrieving node definitions: %v", err)
		}

		reply.DefinitionLocations = make(map[string]*xpb.Anchor, len(defs))
		for ticket, def := range defs {
			node, ok := reply.Nodes[ticket]
			if !ok {
				panic(fmt.Sprintf("extra definition returned for unknown node %q: %v", ticket, def))
			}
			node.Definition = 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, 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
}