Exemple #1
0
func TestDecorationsRefs(t *testing.T) {
	d := tbl.Decorations[1]

	st := tbl.Construct(t)
	reply, err := st.Decorations(ctx, &xpb.DecorationsRequest{
		Location:   &xpb.Location{Ticket: d.FileTicket},
		References: true,
		Filter:     []string{"**"},
	})
	testutil.FatalOnErrT(t, "DecorationsRequest error: %v", err)

	if len(reply.SourceText) != 0 {
		t.Errorf("Unexpected source text: %q", string(d.SourceText))
	}
	if reply.Encoding != "" {
		t.Errorf("Unexpected encoding: %q", d.Encoding)
	}

	expected := refs(xrefs.NewNormalizer(d.SourceText), d.Decoration)
	if !reflect.DeepEqual(expected, reply.Reference) {
		t.Fatalf("Expected references %v; found %v", expected, reply.Reference)
	}

	expectedNodes := nodeInfos(tbl.Nodes[7:13])

	sort.Sort(byNodeTicket(expectedNodes))
	sort.Sort(byNodeTicket(reply.Node))

	if err := testutil.DeepEqual(expectedNodes, reply.Node); err != nil {
		t.Fatal(err)
	}
}
Exemple #2
0
// Decorations implements part of the xrefs.Interface.
func (d *DB) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) {
	if d.selectText == nil {
		var err error
		d.selectText, err = d.Prepare("SELECT text, text_encoding FROM Nodes WHERE ticket = $1;")
		if err != nil {
			return nil, fmt.Errorf("error preparing selectText statement: %v", err)
		}
	}

	// TODO(schroederc): dirty buffers
	// TODO(schroederc): span locations

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

	decor := &xpb.DecorationsReply{Location: req.Location}

	r := d.selectText.QueryRow(fileTicket)
	var text []byte
	var textEncoding sql.NullString
	if err := r.Scan(&text, &textEncoding); err != nil {
		return nil, err
	}
	norm := xrefs.NewNormalizer(text)

	if req.SourceText {
		decor.SourceText = text
		decor.Encoding = textEncoding.String
	}

	if req.References {
		var err error
		decor.Reference, err = d.scanReferences(fileTicket, norm)
		if err != nil {
			return nil, err
		}

		if len(req.Filter) > 0 && len(decor.Reference) > 0 {
			nodeTickets := stringset.New()
			for _, r := range decor.Reference {
				nodeTickets.Add(r.TargetTicket)
			}

			nodes, err := d.Nodes(ctx, &xpb.NodesRequest{
				Ticket: nodeTickets.Slice(),
				Filter: req.Filter,
			})
			if err != nil {
				return nil, fmt.Errorf("error filtering nodes:%v", err)
			}
			decor.Nodes = nodes.Nodes
		}
	}

	return decor, nil
}
Exemple #3
0
func normalizedPoint(text []byte) int {
	p := xrefs.NewNormalizer(text).Point(&xpb.Location_Point{
		ByteOffset:   max(*offset, 0),
		LineNumber:   max(*lineNumber, 0),
		ColumnOffset: max(*columnOffset, 0),
	})
	return int(p.ByteOffset)
}
Exemple #4
0
func TestDecorationsDirtyBuffer(t *testing.T) {
	d := tbl.Decorations[1]

	st := tbl.Construct(t)
	dirty := []byte(`(defn map [f coll]
  (if (seq coll)
    []
    (cons (f (first coll)) (map f (rest coll)))))
`)
	reply, err := st.Decorations(ctx, &xpb.DecorationsRequest{
		Location:    &xpb.Location{Ticket: d.FileTicket},
		DirtyBuffer: dirty,
		References:  true,
		Filter:      []string{"**"},
	})
	testutil.FatalOnErrT(t, "DecorationsRequest error: %v", err)

	if len(reply.SourceText) != 0 {
		t.Errorf("Unexpected source text: %q", string(d.SourceText))
	}
	if reply.Encoding != "" {
		t.Errorf("Unexpected encoding: %q", d.Encoding)
	}

	p := xrefs.NewPatcher(d.SourceText, dirty)
	norm := xrefs.NewNormalizer(dirty)
	var expected []*xpb.DecorationsReply_Reference
	for _, d := range d.Decoration {
		if _, _, exists := p.Patch(d.Anchor.StartOffset, d.Anchor.EndOffset); exists {
			expected = append(expected, decorationToReference(norm, d))
		}
	}
	if !reflect.DeepEqual(expected, reply.Reference) {
		t.Fatalf("Expected references %v; found %v", expected, reply.Reference)
	}

	// These are a subset of the anchor nodes in tbl.Decorations[1].  tbl.Nodes[8]
	// and tbl.Nodes[10] are missing because [8] was an anchor in the edited
	// region and [10] was its target.
	expectedNodes := nodeInfos([]*srvpb.Node{
		tbl.Nodes[7], tbl.Nodes[9], tbl.Nodes[11], tbl.Nodes[12],
	})

	// Ensure patching affects the anchor node facts
	mapFacts(expectedNodes[2], map[string]string{
		"/kythe/loc/start": "48",
		"/kythe/loc/end":   "52",
	})

	sort.Sort(byNodeTicket(expectedNodes))
	sort.Sort(byNodeTicket(reply.Node))

	if !reflect.DeepEqual(expectedNodes, reply.Node) {
		t.Fatalf("Expected nodes %v; found %v", expected, reply.Node)
	}
}
Exemple #5
0
func displayReferences(decor *xpb.DecorationsReply) error {
	if *displayJSON {
		return json.NewEncoder(out).Encode(decor)
	}

	nodes := xrefs.NodesMap(decor.Node)

	// TODO(schroederc): return anchor locations from server
	norm := xrefs.NewNormalizer(decor.SourceText)

	for _, ref := range decor.Reference {
		nodeKind := factValue(nodes, ref.TargetTicket, schema.NodeKindFact, "UNKNOWN")
		subkind := factValue(nodes, ref.TargetTicket, schema.SubkindFact, "")
		startOffset := factValue(nodes, ref.SourceTicket, schema.AnchorStartFact, "_")
		endOffset := factValue(nodes, ref.SourceTicket, schema.AnchorEndFact, "_")

		// ignore errors (locations will be 0)
		s, _ := strconv.Atoi(startOffset)
		e, _ := strconv.Atoi(endOffset)

		loc, err := norm.Location(&xpb.Location{
			Kind:  xpb.Location_SPAN,
			Start: &xpb.Location_Point{ByteOffset: int32(s)},
			End:   &xpb.Location_Point{ByteOffset: int32(e)},
		})
		if err != nil {
			return fmt.Errorf("error normalizing reference location for anchor %q: %v", ref.SourceTicket, err)
		}

		r := strings.NewReplacer(
			"@source@", ref.SourceTicket,
			"@target@", ref.TargetTicket,
			"@edgeKind@", ref.Kind,
			"@nodeKind@", nodeKind,
			"@subkind@", subkind,
			"@^offset@", startOffset,
			"@^line@", strconv.Itoa(int(loc.Start.LineNumber)),
			"@^col@", strconv.Itoa(int(loc.Start.ColumnOffset)),
			"@$offset@", endOffset,
			"@$line@", strconv.Itoa(int(loc.End.LineNumber)),
			"@$col@", strconv.Itoa(int(loc.End.ColumnOffset)),
		)
		if _, err := r.WriteString(out, refFormat+"\n"); err != nil {
			return err
		}
	}

	return nil
}
Exemple #6
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 #7
0
func (d *DB) createCrossRefsTable() error {
	if _, err := d.Exec(createCrossReferencesTable); err != nil {
		return fmt.Errorf("error creating cross-references table: %v", err)
	}

	txn, err := d.Begin()
	if err != nil {
		return fmt.Errorf("error creating transaction: %v", err)
	}

	copyXRef, err := txn.Prepare(pq.CopyIn("crossreferences", "ticket", "kind", "file_ticket", "anchor_ticket", "proto"))
	if err != nil {
		return fmt.Errorf("error preparing CrossReferences copy statement: %v", err)
	}

	rs, err := d.Query(`SELECT decor.target_ticket, decor.kind, decor.file_ticket, decor.anchor_ticket, anchor.start_offset, anchor.end_offset, anchor.snippet_start, anchor.snippet_end
    FROM Decorations decor
    JOIN Nodes anchor ON anchor.ticket = decor.anchor_ticket
    ORDER BY file_ticket;`)
	if err != nil {
		return fmt.Errorf("error creating decorations query: %v", err)
	}

	queryFile, err := d.Prepare("SELECT text, text_encoding FROM Nodes WHERE ticket = $1;")
	if err != nil {
		return fmt.Errorf("error preparing file query: %v", err)
	}

	var (
		file     srvpb.File
		raw      srvpb.RawAnchor
		norm     *xrefs.Normalizer
		lastFile string
	)
	for rs.Next() {
		var ticket, kind string
		var snippetStart, snippetEnd sql.NullInt64
		if err := rs.Scan(&ticket, &kind, &file.Ticket, &raw.Ticket, &raw.StartOffset, &raw.EndOffset, &snippetStart, &snippetEnd); err != nil {
			return fmt.Errorf("Decorations scan error: %v", err)
		}

		if snippetStart.Valid {
			raw.SnippetStart = int32(snippetStart.Int64)
		} else {
			raw.SnippetStart = 0
		}
		if snippetEnd.Valid {
			raw.SnippetEnd = int32(snippetEnd.Int64)
		} else {
			raw.SnippetEnd = 0
		}

		if lastFile != file.Ticket {
			var textEncoding sql.NullString
			if err := queryFile.QueryRow(file.Ticket).Scan(&file.Text, &textEncoding); err != nil {
				return fmt.Errorf("error looking up file: %v", err)
			}
			file.Encoding = textEncoding.String
			norm = xrefs.NewNormalizer(file.Text)
			lastFile = file.Ticket
		}

		a, err := assemble.ExpandAnchor(&raw, &file, norm, kind)
		if err != nil {
			return fmt.Errorf("error expanding anchor: %v", err)
		}

		rec, err := proto.Marshal(a2a(a, true))
		if err != nil {
			return fmt.Errorf("error marshaling anchor: %v", err)
		}

		if _, err := copyXRef.Exec(ticket, kind, file.Ticket, raw.Ticket, rec); err != nil {
			return fmt.Errorf("copy error: %v", err)
		}
	}
	if _, err := copyXRef.Exec(); err != nil {
		return fmt.Errorf("error flushing CrossReferences: %v", err)
	}
	if err := txn.Commit(); err != nil {
		return fmt.Errorf("transaction commit error: %v", err)
	}

	return nil
}
Exemple #8
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 #9
0
func writeDecorAndRefs(ctx context.Context, opts *Options, edges <-chan *srvpb.Edge, out *servingOutput) error {
	fragments, err := opts.diskSorter(fragmentLesser{}, fragmentMarshaler{})
	if err != nil {
		return err
	}

	log.Println("Writing decoration fragments")
	if err := createDecorationFragments(ctx, edges, fragments); err != nil {
		return err
	}

	log.Println("Writing completed FileDecorations")

	// refSorter stores a *srvpb.CrossReference for each Decoration from fragments
	refSorter, err := opts.diskSorter(refLesser{}, refMarshaler{})
	if err != nil {
		return fmt.Errorf("error creating sorter: %v", err)
	}

	buffer := out.xs.Buffered()
	var (
		curFile string
		file    *srvpb.File
		norm    *xrefs.Normalizer
		decor   *srvpb.FileDecorations
	)
	if err := fragments.Read(func(x interface{}) error {
		df := x.(*decorationFragment)
		fileTicket := df.fileTicket
		fragment := df.decoration

		if decor != nil && curFile != fileTicket {
			if decor.File != nil {
				if err := writeDecor(ctx, buffer, decor); err != nil {
					return err
				}
				file = nil
			}
			decor = nil
		}
		curFile = fileTicket
		if decor == nil {
			decor = &srvpb.FileDecorations{}
		}

		if fragment.File == nil {
			decor.Decoration = append(decor.Decoration, fragment.Decoration...)
			if file == nil {
				return errors.New("missing file for anchors")
			}

			// Reverse each fragment.Decoration to create a *srvpb.CrossReference
			for _, d := range fragment.Decoration {
				cr, err := assemble.CrossReference(file, norm, d)
				if err != nil {
					return fmt.Errorf("error assembling cross-reference: %v", err)
				}
				if err := refSorter.Add(cr); err != nil {
					return fmt.Errorf("error adding CrossReference to sorter: %v", err)
				}
			}
		} else {
			decor.File = fragment.File
			file = fragment.File
			norm = xrefs.NewNormalizer(file.Text)
		}

		return nil
	}); err != nil {
		return fmt.Errorf("error reading decoration fragments: %v", err)
	}

	if decor != nil && decor.File != nil {
		if err := writeDecor(ctx, buffer, decor); err != nil {
			return err
		}
	}

	log.Println("Writing CrossReferences")

	xb := &assemble.CrossReferencesBuilder{
		MaxPageSize: opts.MaxPageSize,
		Output: func(ctx context.Context, s *srvpb.PagedCrossReferences) error {
			return buffer.Put(ctx, xsrv.CrossReferencesKey(s.SourceTicket), s)
		},
		OutputPage: func(ctx context.Context, p *srvpb.PagedCrossReferences_Page) error {
			return buffer.Put(ctx, xsrv.CrossReferencesPageKey(p.PageKey), p)
		},
	}
	var curTicket string
	if err := refSorter.Read(func(i interface{}) error {
		cr := i.(*srvpb.CrossReference)

		if curTicket != cr.Referent.Ticket {
			curTicket = cr.Referent.Ticket
			if err := xb.StartSet(ctx, curTicket); err != nil {
				return fmt.Errorf("error starting cross-references set: %v", err)
			}
		}

		g := &srvpb.PagedCrossReferences_Group{
			Kind:   cr.TargetAnchor.Kind,
			Anchor: []*srvpb.ExpandedAnchor{cr.TargetAnchor},
		}
		if err := xb.AddGroup(ctx, g); err != nil {
			return fmt.Errorf("error adding cross-reference: %v", err)
		}

		return nil
	}); err != nil {
		return fmt.Errorf("error reading xrefs: %v", err)
	}

	if err := xb.Flush(ctx); err != nil {
		return fmt.Errorf("error flushing cross-references: %v", err)
	}

	return buffer.Flush(ctx)
}
Exemple #10
0
func completeAnchors(ctx context.Context, xs xrefs.NodesEdgesService, retrieveText bool, files map[string]*fileNode, edgeKind string, anchors []string) ([]*xpb.CrossReferencesReply_RelatedAnchor, error) {
	edgeKind = schema.Canonicalize(edgeKind)

	// AllEdges is relatively safe because each anchor will have very few parents (almost always 1)
	reply, err := xrefs.AllEdges(ctx, xs, &xpb.EdgesRequest{
		Ticket: anchors,
		Kind:   []string{schema.ChildOfEdge},
		Filter: []string{
			schema.NodeKindFact,
			schema.AnchorLocFilter,
			schema.SnippetLocFilter,
		},
	})
	if err != nil {
		return nil, err
	}
	nodes := xrefs.NodesMap(reply.Nodes)

	var result []*xpb.CrossReferencesReply_RelatedAnchor
	for ticket, es := range reply.EdgeSets {
		if nodeKind := string(nodes[ticket][schema.NodeKindFact]); nodeKind != schema.AnchorKind {
			log.Printf("Found non-anchor target to %q edge: %q (kind: %q)", edgeKind, ticket, nodeKind)
			continue
		}

		// Parse anchor location start/end facts
		so, eo, err := getSpan(nodes[ticket], schema.AnchorStartFact, schema.AnchorEndFact)
		if err != nil {
			log.Printf("Invalid anchor span for %q: %v", ticket, err)
			continue
		}

		// For each file parent to the anchor, add an Anchor to the result.
		for kind, g := range es.Groups {
			if kind != schema.ChildOfEdge {
				continue
			}

			for _, edge := range g.Edge {
				parent := edge.TargetTicket
				if parentKind := string(nodes[parent][schema.NodeKindFact]); parentKind != schema.FileKind {
					log.Printf("Found non-file parent to anchor: %q (kind: %q)", parent, parentKind)
					continue
				}

				a := &xpb.Anchor{
					Ticket: ticket,
					Kind:   edgeKind,
					Parent: parent,
				}

				file, ok := files[a.Parent]
				if !ok {
					nReply, err := xs.Nodes(ctx, &xpb.NodesRequest{Ticket: []string{a.Parent}})
					if err != nil {
						return nil, fmt.Errorf("error getting file contents for %q: %v", a.Parent, err)
					}
					nMap := xrefs.NodesMap(nReply.Nodes)
					text := nMap[a.Parent][schema.TextFact]
					file = &fileNode{
						text:     text,
						encoding: string(nMap[a.Parent][schema.TextEncodingFact]),
						norm:     xrefs.NewNormalizer(text),
					}
					files[a.Parent] = file
				}

				a.Start, a.End, err = normalizeSpan(file.norm, int32(so), int32(eo))
				if err != nil {
					log.Printf("Invalid anchor span %q in file %q: %v", ticket, a.Parent, err)
					continue
				}

				if retrieveText && a.Start.ByteOffset < a.End.ByteOffset {
					a.Text, err = text.ToUTF8(file.encoding, file.text[a.Start.ByteOffset:a.End.ByteOffset])
					if err != nil {
						log.Printf("Error decoding anchor text: %v", err)
					}
				}

				if snippetStart, snippetEnd, err := getSpan(nodes[ticket], schema.SnippetStartFact, schema.SnippetEndFact); err == nil {
					startPoint, endPoint, err := normalizeSpan(file.norm, int32(snippetStart), int32(snippetEnd))
					if err != nil {
						log.Printf("Invalid snippet span %q in file %q: %v", ticket, a.Parent, err)
					} else {
						a.Snippet, err = text.ToUTF8(file.encoding, file.text[startPoint.ByteOffset:endPoint.ByteOffset])
						if err != nil {
							log.Printf("Error decoding snippet text: %v", err)
						}
						a.SnippetStart = startPoint
						a.SnippetEnd = endPoint
					}
				}

				// fallback to a line-based snippet if the indexer did not provide its own snippet offsets
				if a.Snippet == "" {
					a.SnippetStart = &xpb.Location_Point{
						ByteOffset: a.Start.ByteOffset - a.Start.ColumnOffset,
						LineNumber: a.Start.LineNumber,
					}
					nextLine := file.norm.Point(&xpb.Location_Point{LineNumber: a.Start.LineNumber + 1})
					a.SnippetEnd = &xpb.Location_Point{
						ByteOffset:   nextLine.ByteOffset - 1,
						LineNumber:   a.Start.LineNumber,
						ColumnOffset: a.Start.ColumnOffset + (nextLine.ByteOffset - a.Start.ByteOffset - 1),
					}
					a.Snippet, err = text.ToUTF8(file.encoding, file.text[a.SnippetStart.ByteOffset:a.SnippetEnd.ByteOffset])
					if err != nil {
						log.Printf("Error decoding snippet text: %v", err)
					}
				}

				result = append(result, &xpb.CrossReferencesReply_RelatedAnchor{Anchor: a})
			}

			break // we've handled the only /kythe/edge/childof group
		}
	}

	return result, nil
}
Exemple #11
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 #12
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 #13
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 #14
0
func displayDecorations(text []byte, decor *xpb.DecorationsReply) error {
	if *displayJSON {
		return json.NewEncoder(out).Encode(decor)
	}

	nodes := xrefs.NodesMap(decor.Node)

	if text == nil {
		text = decor.SourceText
	}
	norm := xrefs.NewNormalizer(text)

	for _, ref := range decor.Reference {
		nodeKind := factValue(nodes, ref.TargetTicket, schema.NodeKindFact, "UNKNOWN")
		subkind := factValue(nodes, ref.TargetTicket, schema.SubkindFact, "")

		var loc *xpb.Location
		if ref.AnchorStart != nil && ref.AnchorEnd != nil {
			loc = &xpb.Location{
				Kind:  xpb.Location_SPAN,
				Start: ref.AnchorStart,
				End:   ref.AnchorEnd,
			}
		} else {
			// TODO(schroederc): remove this backwards-compatibility branch

			startOffset := factValue(nodes, ref.SourceTicket, schema.AnchorStartFact, "_")
			endOffset := factValue(nodes, ref.SourceTicket, schema.AnchorEndFact, "_")

			// ignore errors (locations will be 0)
			s, _ := strconv.Atoi(startOffset)
			e, _ := strconv.Atoi(endOffset)

			var err error
			loc, err = norm.Location(&xpb.Location{
				Kind:  xpb.Location_SPAN,
				Start: &xpb.Location_Point{ByteOffset: int32(s)},
				End:   &xpb.Location_Point{ByteOffset: int32(e)},
			})
			if err != nil {
				return fmt.Errorf("error normalizing reference location for anchor %q: %v", ref.SourceTicket, err)
			}
		}

		r := strings.NewReplacer(
			"@source@", ref.SourceTicket,
			"@target@", ref.TargetTicket,
			"@edgeKind@", ref.Kind,
			"@nodeKind@", nodeKind,
			"@subkind@", subkind,
			"@^offset@", itoa(loc.Start.ByteOffset),
			"@^line@", itoa(loc.Start.LineNumber),
			"@^col@", itoa(loc.Start.ColumnOffset),
			"@$offset@", itoa(loc.End.ByteOffset),
			"@$line@", itoa(loc.End.LineNumber),
			"@$col@", itoa(loc.End.ColumnOffset),
		)
		if _, err := r.WriteString(out, refFormat+"\n"); err != nil {
			return err
		}
	}

	return nil
}