kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/test/services/xrefs/xrefs.go (about) 1 /* 2 * Copyright 2017 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package xrefs provides test utilities for the xrefs.Service. 18 // 19 // This package includes the Atomizer which allows for XRefService proto 20 // response messages to be shattered in GraphStore entries. By indexing, 21 // post-processing, and serving source code with verifier 22 // (http://www.kythe.io/docs/kythe-verifier.html) goals, using the Atomizer to 23 // shatter the served DecorationsReply and CrossReferencesReply messages, and 24 // feeding the resulting entries into the verifier, one can write integration 25 // tests between the Kythe indexers and Kythe server. 26 package xrefs // import "kythe.io/kythe/go/test/services/xrefs" 27 28 import ( 29 "context" 30 "fmt" 31 "path/filepath" 32 "strings" 33 34 "kythe.io/kythe/go/util/kytheuri" 35 "kythe.io/kythe/go/util/log" 36 "kythe.io/kythe/go/util/markedsource" 37 "kythe.io/kythe/go/util/schema/edges" 38 "kythe.io/kythe/go/util/schema/facts" 39 "kythe.io/kythe/go/util/schema/nodes" 40 41 "google.golang.org/protobuf/proto" 42 43 cpb "kythe.io/kythe/proto/common_go_proto" 44 spb "kythe.io/kythe/proto/storage_go_proto" 45 xpb "kythe.io/kythe/proto/xref_go_proto" 46 ) 47 48 // An Atomizer shatters XRefService proto replies into equivalent GraphStore 49 // entries. The resulting composition of the emitted entries reflects a Kythe 50 // graph following the Kythe schema in most respects. In particular, the 51 // emitted graph approximates an input Kythe graph that was used to derive the 52 // XRefService replies (through post-processing and serving). 53 type Atomizer func(context.Context, *spb.Entry) error 54 55 type atomizerPanic struct{ error } 56 57 // catchErrors recovers from a possible panic. If the panic was caused by a 58 // atomizerPanic, the underlying error will replace the ret error. 59 // Otherwise, the panic error will directly replace the ret error. If 60 // recovering from a panic and *ret != nil, the original ret error will be 61 // logged as a warning. 62 func (a Atomizer) catchErrors(ret *error) { 63 if err := recover(); err != nil { 64 if pe, ok := err.(atomizerPanic); ok { 65 if *ret != nil { 66 log.Warningf("%v", *ret) 67 } 68 *ret = pe.error 69 } else { 70 panic(err) 71 } 72 } 73 } 74 75 func (a Atomizer) emit(ctx context.Context, entry *spb.Entry) { 76 if err := a(ctx, entry); err != nil { 77 // Instead of checking for errors throughout the code, panic with a special 78 // error that will be caught in the top-level methods by 79 // Atomizer.catchErrors. 80 panic(atomizerPanic{err}) 81 } 82 } 83 84 // Decorations emits a file node for the reply's Location, emits an anchor node 85 // for each reference along with an edge to its target, and emits all node 86 // facts directly. 87 func (a Atomizer) Decorations(ctx context.Context, decor *xpb.DecorationsReply) (ret error) { 88 defer a.catchErrors(&ret) 89 file := a.parseTicket(decor.Location.Ticket) 90 a.emitFact(ctx, file, facts.NodeKind, []byte(nodes.File)) 91 a.emitFact(ctx, file, facts.Text, decor.SourceText) 92 a.emitFact(ctx, file, facts.TextEncoding, []byte(decor.Encoding)) 93 for _, ref := range decor.Reference { 94 anchor := a.anchorNode(ctx, decor.Location.Ticket, ref.Span, nil) 95 a.emitEdge(ctx, anchor, ref.Kind, a.parseTicket(ref.TargetTicket)) 96 } 97 for ticket, node := range decor.Nodes { 98 for name, val := range node.Facts { 99 a.emitFact(ctx, a.parseTicket(ticket), name, val) 100 } 101 } 102 return nil 103 } 104 105 // CrossReferences emits anchor nodes for all RelatedAnchors along with edges to 106 // their targets, emits edges for each RelatedNode, and emits all node facts 107 // directly. 108 // TODO(schroederc): handle Callers 109 func (a Atomizer) CrossReferences(ctx context.Context, xrefs *xpb.CrossReferencesReply) (ret error) { 110 defer a.catchErrors(&ret) 111 for _, xrs := range xrefs.CrossReferences { 112 src := a.parseTicket(xrs.Ticket) 113 if xrs.MarkedSource != nil { 114 rec, err := proto.Marshal(xrs.MarkedSource) 115 if err != nil { 116 return err 117 } 118 a.emitFact(ctx, src, facts.Code, rec) 119 120 rendered := markedsource.Render(xrs.MarkedSource) 121 a.emitFact(ctx, src, facts.Code+"/rendered", []byte(rendered)) 122 ident := markedsource.RenderSimpleIdentifier(xrs.MarkedSource, markedsource.PlaintextContent, nil) 123 a.emitFact(ctx, src, facts.Code+"/rendered/identifier", []byte(ident)) 124 params := markedsource.RenderSimpleParams(xrs.MarkedSource, markedsource.PlaintextContent, nil) 125 if len(params) > 0 { 126 a.emitFact(ctx, src, facts.Code+"/rendered/params", []byte(strings.Join(params, ","))) 127 } 128 } 129 a.emitAnchors(ctx, src, xrs.Definition) 130 a.emitAnchors(ctx, src, xrs.Declaration) 131 a.emitAnchors(ctx, src, xrs.Reference) 132 a.emitAnchors(ctx, src, xrs.Caller) 133 for _, rn := range xrs.RelatedNode { 134 if rn.RelationKind == edges.Param || rn.Ordinal != 0 { 135 a.emitOrdinal(ctx, src, rn.RelationKind, a.parseTicket(rn.Ticket), rn.Ordinal) 136 } else { 137 a.emitEdge(ctx, src, rn.RelationKind, a.parseTicket(rn.Ticket)) 138 } 139 } 140 } 141 for ticket, node := range xrefs.Nodes { 142 for name, val := range node.Facts { 143 a.emitFact(ctx, a.parseTicket(ticket), name, val) 144 } 145 } 146 return nil 147 } 148 149 func (a Atomizer) parseTicket(ticket string) *spb.VName { 150 uri, err := kytheuri.Parse(ticket) 151 if err != nil { 152 panic(atomizerPanic{err}) 153 } 154 return uri.VName() 155 } 156 157 // anchorURI synthesizes an anchor URI from a span and its parent file. 158 func (a Atomizer) anchorURI(fileTicket string, span *cpb.Span) *kytheuri.URI { 159 uri, err := kytheuri.Parse(fileTicket) 160 if err != nil { 161 panic(atomizerPanic{err}) 162 } 163 uri.Signature = fmt.Sprintf("a[%d,%d)", span.GetStart().GetByteOffset(), span.GetEnd().GetByteOffset()) 164 // The language doesn't have to exactly match the schema; just use the file's 165 // extension as an approximation. 166 uri.Language = strings.TrimPrefix(filepath.Ext(uri.Path), ".") 167 return uri 168 } 169 170 func (a Atomizer) anchorNode(ctx context.Context, parentFile string, span *cpb.Span, snippet *cpb.Span) *spb.VName { 171 anchor := a.anchorURI(parentFile, span).VName() 172 a.emitFact(ctx, anchor, facts.NodeKind, []byte(nodes.Anchor)) 173 a.emitFact(ctx, anchor, facts.AnchorStart, []byte(fmt.Sprintf("%d", span.Start.ByteOffset))) 174 a.emitFact(ctx, anchor, facts.AnchorEnd, []byte(fmt.Sprintf("%d", span.End.ByteOffset))) 175 if snippet != nil { 176 a.emitFact(ctx, anchor, facts.SnippetStart, []byte(fmt.Sprintf("%d", snippet.Start.ByteOffset))) 177 a.emitFact(ctx, anchor, facts.SnippetEnd, []byte(fmt.Sprintf("%d", snippet.End.ByteOffset))) 178 } 179 return anchor 180 } 181 182 func (a Atomizer) emitFact(ctx context.Context, src *spb.VName, fact string, value []byte) { 183 a.emit(ctx, &spb.Entry{ 184 Source: src, 185 FactName: fact, 186 FactValue: value, 187 }) 188 } 189 190 func (a Atomizer) emitOrdinal(ctx context.Context, src *spb.VName, kind string, tgt *spb.VName, ordinal int32) { 191 a.emitEdge(ctx, src, fmt.Sprintf("%s.%d", kind, ordinal), tgt) 192 } 193 194 func (a Atomizer) emitEdge(ctx context.Context, src *spb.VName, kind string, tgt *spb.VName) { 195 a.emit(ctx, &spb.Entry{ 196 Source: src, 197 FactName: "/", 198 EdgeKind: kind, 199 Target: tgt, 200 }) 201 } 202 203 func (a Atomizer) emitAnchors(ctx context.Context, n *spb.VName, ras []*xpb.CrossReferencesReply_RelatedAnchor) { 204 for _, ra := range ras { 205 anchor := a.anchorNode(ctx, ra.Anchor.Parent, ra.Anchor.Span, ra.Anchor.SnippetSpan) 206 a.emitEdge(ctx, anchor, ra.Anchor.Kind, n) 207 } 208 }