Ejemplo n.º 1
0
func readEdges(ctx context.Context, es xrefs.NodesEdgesService, files []string, edges chan<- *xpb.EdgesReply, filters []string, kinds []string) error {
	var eErr error
	for _, file := range files {
		if eErr == nil {
			reply, err := xrefs.AllEdges(ctx, es, &xpb.EdgesRequest{
				Ticket: []string{file},
				Filter: filters,
				Kind:   kinds,
			})
			if err != nil {
				eErr = err
				continue
			}
			if len(reply.EdgeSet) == 0 {
				// File does not have any decorations, but we still want the source text/encoding.
				nodeReply, err := es.Nodes(ctx, &xpb.NodesRequest{
					Ticket: []string{file},
					Filter: filters,
				})
				if err != nil {
					return fmt.Errorf("error getting file node: %v", err)
				}
				reply.Node = nodeReply.Node
			}
			edges <- reply
		}
	}
	return eErr
}
Ejemplo n.º 2
0
func getDecorations(ctx context.Context, es xrefs.EdgesService, anchor *xpb.NodeInfo) ([]*srvpb.FileDecorations_Decoration, error) {
	var (
		isAnchor   bool
		start, end int
		err        error
	)
	for _, f := range anchor.Fact {
		switch f.Name {
		case schema.NodeKindFact:
			if string(f.Value) == schema.AnchorKind {
				isAnchor = true
			}
		case schema.AnchorStartFact:
			start, err = strconv.Atoi(string(f.Value))
			if err != nil {
				return nil, fmt.Errorf("invalid anchor %q start offset: %q", anchor.Ticket, string(f.Value))
			}
		case schema.AnchorEndFact:
			end, err = strconv.Atoi(string(f.Value))
			if err != nil {
				return nil, fmt.Errorf("invalid anchor %q end offset: %q", anchor.Ticket, string(f.Value))
			}
		}
	}
	if !isAnchor {
		return nil, nil
	} else if start > end {
		log.Printf("Invalid anchor span %d:%d for %q", start, end, anchor.Ticket)
		return nil, nil
	}

	edges, err := xrefs.AllEdges(ctx, es, &xpb.EdgesRequest{Ticket: []string{anchor.Ticket}})
	if err != nil {
		return nil, err
	}
	if len(edges.EdgeSet) != 1 {
		return nil, fmt.Errorf("invalid number of EdgeSets returned for anchor: %d", len(edges.EdgeSet))
	}

	a := &srvpb.FileDecorations_Decoration_Anchor{
		Ticket:      anchor.Ticket,
		StartOffset: int32(start),
		EndOffset:   int32(end),
	}
	var ds []*srvpb.FileDecorations_Decoration
	for _, grp := range edges.EdgeSet[0].Group {
		if schema.EdgeDirection(grp.Kind) == schema.Forward && grp.Kind != schema.ChildOfEdge {
			for _, target := range grp.TargetTicket {
				ds = append(ds, &srvpb.FileDecorations_Decoration{
					Anchor:       a,
					Kind:         grp.Kind,
					TargetTicket: target,
				})
			}
		}
	}
	return ds, nil
}
Ejemplo n.º 3
0
func completeDefinition(defAnchor string) (*definition, error) {
	parentReply, err := xrefs.AllEdges(ctx, xs, &xpb.EdgesRequest{
		Ticket: []string{defAnchor},
		Kind:   []string{schema.ChildOfEdge},
		Filter: []string{schema.NodeKindFact, schema.AnchorLocFilter},
	})
	if err != nil {
		return nil, err
	}

	parentNodes := xrefs.NodesMap(parentReply.Node)
	var files []string
	for _, parent := range xrefs.EdgesMap(parentReply.EdgeSet)[defAnchor][schema.ChildOfEdge] {
		if string(parentNodes[parent][schema.NodeKindFact]) == schema.FileKind {
			files = append(files, parent)
		}
	}

	if len(files) == 0 {
		return nil, nil
	} else if len(files) > 1 {
		return nil, fmt.Errorf("anchor has multiple file parents %q: %v", defAnchor, files)
	}

	vName, err := kytheuri.Parse(files[0])
	if err != nil {
		return nil, err
	}
	start, end := parseAnchorSpan(parentNodes[defAnchor])

	return &definition{
		File:  vName.VName(),
		Start: start,
		End:   end,
	}, nil
}
Ejemplo n.º 4
0
Archivo: xrefs.go Proyecto: bzz/kythe
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
}
Ejemplo n.º 5
0
func main() {
	flag.Parse()
	if flag.NArg() > 0 {
		flagutil.UsageErrorf("unknown non-flag argument(s): %v", flag.Args())
	} else if *offset < 0 && (*lineNumber < 0 || *columnOffset < 0) {
		flagutil.UsageError("non-negative --offset (or --line and --column) required")
	} else if *signature == "" && *path == "" {
		flagutil.UsageError("must provide at least --path or --signature")
	}

	defer (*apiFlag).Close()
	xs, idx = *apiFlag, *apiFlag

	relPath := *path
	if *localRepoRoot != "NONE" {
		if _, err := os.Stat(relPath); err == nil {
			absPath, err := filepath.Abs(relPath)
			if err != nil {
				log.Fatal(err)
			}
			if *dirtyBuffer == "" {
				*dirtyBuffer = absPath
			}

			kytheRoot := *localRepoRoot
			if kytheRoot == "" {
				kytheRoot = findKytheRoot(filepath.Dir(absPath))
			}
			if kytheRoot != "" {
				relPath, err = filepath.Rel(filepath.Join(kytheRoot, *root), absPath)
				if err != nil {
					log.Fatal(err)
				}
			}
		}
	}
	partialFile := &spb.VName{
		Signature: *signature,
		Corpus:    *corpus,
		Root:      *root,
		Path:      relPath,
		Language:  *language,
	}
	reply, err := idx.Search(ctx, &spb.SearchRequest{
		Partial: partialFile,
		Fact:    fileFacts,
	})
	if err != nil {
		log.Fatalf("Error locating file {%v}: %v", partialFile, err)
	}
	if len(reply.Ticket) == 0 {
		log.Fatalf("Could not locate file {%v}", partialFile)
	} else if len(reply.Ticket) > 1 {
		log.Fatalf("Ambiguous file {%v}; multiple results: %v", partialFile, reply.Ticket)
	}

	fileTicket := reply.Ticket[0]
	text := readDirtyBuffer(ctx)
	decor, err := xs.Decorations(ctx, &xpb.DecorationsRequest{
		// TODO(schroederc): limit Location to a SPAN around *offset
		Location:    &xpb.Location{Ticket: fileTicket},
		References:  true,
		SourceText:  true,
		DirtyBuffer: text,
		Filter: []string{
			schema.NodeKindFact,
			schema.SubkindFact,
			schema.AnchorLocFilter, // TODO(schroederc): remove once backwards-compatibility fix below is removed
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	if text == nil {
		text = decor.SourceText
	}
	nodes := xrefs.NodesMap(decor.Node)

	// Normalize point within source text
	point := normalizedPoint(text)

	en := json.NewEncoder(os.Stdout)
	for _, ref := range decor.Reference {
		var start, end int
		if ref.AnchorStart == nil || ref.AnchorEnd == nil {
			// TODO(schroederc): remove this backwards-compatibility fix
			start, end = parseAnchorSpan(nodes[ref.SourceTicket])
		} else {
			start, end = int(ref.AnchorStart.ByteOffset), int(ref.AnchorEnd.ByteOffset)
		}

		if start <= point && point < end {
			var r reference
			r.Span.Start = start
			r.Span.End = end
			r.Span.Text = string(text[start:end])
			r.Kind = strings.TrimPrefix(ref.Kind, schema.EdgePrefix)
			r.Node.Ticket = ref.TargetTicket

			node := nodes[ref.TargetTicket]
			r.Node.Kind = string(node[schema.NodeKindFact])
			r.Node.Subkind = string(node[schema.SubkindFact])

			if eReply, err := xrefs.AllEdges(ctx, xs, &xpb.EdgesRequest{
				Ticket: []string{ref.TargetTicket},
				Kind:   []string{schema.NamedEdge, schema.TypedEdge, definedAtEdge, definedBindingAtEdge},
			}); err != nil {
				log.Printf("WARNING: error getting edges for %q: %v", ref.TargetTicket, err)
			} else {
				edges := xrefs.EdgesMap(eReply.EdgeSet)[ref.TargetTicket]
				for _, name := range edges[schema.NamedEdge] {
					if uri, err := kytheuri.Parse(name); err != nil {
						log.Printf("WARNING: named node ticket (%q) could not be parsed: %v", name, err)
					} else {
						r.Node.Names = append(r.Node.Names, uri.Signature)
					}
				}

				if typed := edges[schema.TypedEdge]; len(typed) > 0 {
					r.Node.Typed = typed[0]
				}

				if !*skipDefinitions {
					defs := edges[definedAtEdge]
					if len(defs) == 0 {
						defs = edges[definedBindingAtEdge]
					}
					for _, defAnchor := range defs {
						def, err := completeDefinition(defAnchor)
						if err != nil {
							log.Printf("WARNING: failed to complete definition for %q: %v", defAnchor, err)
						} else {
							r.Node.Definitions = append(r.Node.Definitions, def)
						}
					}
				}
			}

			if err := en.Encode(r); err != nil {
				log.Fatal(err)
			}
		}
	}
}
Ejemplo n.º 6
0
func main() {
	flag.Parse()
	if flag.NArg() > 0 {
		flagutil.UsageErrorf("unknown non-flag argument(s): %v", flag.Args())
	} else if *offset < 0 && (*lineNumber < 0 || *columnOffset < 0) {
		flagutil.UsageError("non-negative --offset (or --line and --column) required")
	} else if *path == "" {
		flagutil.UsageError("must provide --path")
	}

	defer (*apiFlag).Close()
	xs = *apiFlag

	relPath := *path
	if *localRepoRoot != "NONE" {
		if _, err := os.Stat(relPath); err == nil {
			absPath, err := filepath.Abs(relPath)
			if err != nil {
				log.Fatal(err)
			}
			if *dirtyBuffer == "" {
				*dirtyBuffer = absPath
			}

			kytheRoot := *localRepoRoot
			if kytheRoot == "" {
				kytheRoot = findKytheRoot(filepath.Dir(absPath))
			}
			if kytheRoot != "" {
				relPath, err = filepath.Rel(filepath.Join(kytheRoot, *root), absPath)
				if err != nil {
					log.Fatal(err)
				}
			}
		}
	}

	fileTicket := (&kytheuri.URI{Corpus: *corpus, Root: *root, Path: relPath}).String()
	point := &xpb.Location_Point{
		ByteOffset:   int32(*offset),
		LineNumber:   int32(*lineNumber),
		ColumnOffset: int32(*columnOffset),
	}
	dirtyBuffer := readDirtyBuffer(ctx)
	decor, err := xs.Decorations(ctx, &xpb.DecorationsRequest{
		Location: &xpb.Location{
			Ticket: fileTicket,
			Kind:   xpb.Location_SPAN,
			Start:  point,
			End:    point,
		},
		SpanKind:    xpb.DecorationsRequest_AROUND_SPAN,
		References:  true,
		SourceText:  true,
		DirtyBuffer: dirtyBuffer,
		Filter: []string{
			facts.NodeKind,
			facts.Subkind,
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	nodes := xrefs.NodesMap(decor.Nodes)

	en := json.NewEncoder(os.Stdout)
	for _, ref := range decor.Reference {
		start, end := int(ref.AnchorStart.ByteOffset), int(ref.AnchorEnd.ByteOffset)

		var r reference
		r.Span.Start = start
		r.Span.End = end
		if len(dirtyBuffer) > 0 {
			r.Span.Text = string(dirtyBuffer[start:end])
		} // TODO(schroederc): add option to get anchor text from DecorationsReply
		r.Kind = strings.TrimPrefix(ref.Kind, edges.Prefix)
		r.Node.Ticket = ref.TargetTicket

		node := nodes[ref.TargetTicket]
		r.Node.Kind = string(node[facts.NodeKind])
		r.Node.Subkind = string(node[facts.Subkind])

		// TODO(schroederc): use CrossReferences method
		if eReply, err := xrefs.AllEdges(ctx, xs, &gpb.EdgesRequest{
			Ticket: []string{ref.TargetTicket},
			Kind:   []string{edges.Named, edges.Typed, definedAtEdge, definedBindingAtEdge},
		}); err != nil {
			log.Printf("WARNING: error getting edges for %q: %v", ref.TargetTicket, err)
		} else {
			matching := xrefs.EdgesMap(eReply.EdgeSets)[ref.TargetTicket]
			for name := range matching[edges.Named] {
				if uri, err := kytheuri.Parse(name); err != nil {
					log.Printf("WARNING: named node ticket (%q) could not be parsed: %v", name, err)
				} else {
					r.Node.Names = append(r.Node.Names, uri.Signature)
				}
			}

			for typed := range matching[edges.Typed] {
				r.Node.Typed = typed
				break
			}

			if !*skipDefinitions {
				defs := matching[definedAtEdge]
				if len(defs) == 0 {
					defs = matching[definedBindingAtEdge]
				}
				for defAnchor := range defs {
					def, err := completeDefinition(defAnchor)
					if err != nil {
						log.Printf("WARNING: failed to complete definition for %q: %v", defAnchor, err)
					} else {
						r.Node.Definitions = append(r.Node.Definitions, def)
					}
				}
			}
		}

		if err := en.Encode(r); err != nil {
			log.Fatal(err)
		}
	}
}