kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/services/cli/command_decor.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 cli 18 19 import ( 20 "context" 21 "errors" 22 "flag" 23 "fmt" 24 "io/ioutil" 25 "strconv" 26 "strings" 27 28 "kythe.io/kythe/go/platform/vfs" 29 "kythe.io/kythe/go/services/graph" 30 "kythe.io/kythe/go/util/flagutil" 31 "kythe.io/kythe/go/util/kytheuri" 32 "kythe.io/kythe/go/util/schema/facts" 33 34 cpb "kythe.io/kythe/proto/common_go_proto" 35 xpb "kythe.io/kythe/proto/xref_go_proto" 36 ) 37 38 var ( 39 // DefaultFileCorpus is the --corpus flag used by source/decor/diagnostics 40 // commands to construct a file ticket from a raw file path. 41 DefaultFileCorpus string 42 43 // DefaultFileRoot is the --root flag used by source/decor/diagnostics commands 44 // to construct a file ticket from a raw file path. 45 DefaultFileRoot string 46 47 // DefaultFilePathPrefix is the --path_prefix flag used by 48 // source/decor/diagnostics commands to construct a file ticket from a raw file 49 // path. 50 DefaultFilePathPrefix string 51 ) 52 53 // baseDecorCommand is a shared base for the source/decor/diagnostics commands 54 type baseDecorCommand struct { 55 baseKytheCommand 56 decorSpan string 57 corpus, root, pathPrefix string 58 buildConfigs flagutil.StringSet 59 60 workspaceURI string 61 } 62 63 func (c *baseDecorCommand) SetFlags(flag *flag.FlagSet) { 64 flag.StringVar(&c.decorSpan, "span", "", 65 `Limit results to this span (e.g. "10-30", "b1462-b1847", "3:5-3:10", "10") 66 Formats: 67 b\d+-b\d+ -- Byte-offsets 68 \d+(:\d+)?-\d+(:\d+)? -- Line offsets with optional column offsets 69 \d+(:\d+)? -- Full line span (with an optional starting column offset)`) 70 flag.StringVar(&c.workspaceURI, "workspace_uri", "", "Workspace URI to patch file decorations") 71 flag.StringVar(&c.corpus, "corpus", DefaultFileCorpus, "File corpus to use if given a raw path") 72 flag.StringVar(&c.root, "root", DefaultFileRoot, "File root to use if given a raw path") 73 flag.StringVar(&c.pathPrefix, "path_prefix", DefaultFilePathPrefix, "File path prefix to use if given a raw path (this is prepended directly to the raw path without any joining slashes)") 74 flag.Var(&c.buildConfigs, "build_config", "CSV set of build configs with which to filter file decorations") 75 } 76 77 func (c baseDecorCommand) fileTicketArg(flag *flag.FlagSet) (string, error) { 78 if flag.NArg() < 0 { 79 return "", errors.New("no file given") 80 } 81 file := flag.Arg(0) 82 if strings.HasPrefix(file, kytheuri.Scheme) { 83 return file, nil 84 } 85 return (&kytheuri.URI{ 86 Corpus: c.corpus, 87 Root: c.root, 88 Path: c.pathPrefix + file, 89 }).String(), nil 90 } 91 92 func (c baseDecorCommand) baseRequest(flag *flag.FlagSet) (*xpb.DecorationsRequest, error) { 93 ticket, err := c.fileTicketArg(flag) 94 if err != nil { 95 return nil, err 96 } 97 req := &xpb.DecorationsRequest{ 98 Location: &xpb.Location{Ticket: ticket}, 99 } 100 span, err := c.spanArg() 101 if err != nil { 102 return nil, err 103 } else if span != nil { 104 req.Location.Kind = xpb.Location_SPAN 105 req.Location.Span = span 106 } 107 req.BuildConfig = c.buildConfigs.Elements() 108 if c.workspaceURI != "" { 109 req.Workspace = &xpb.Workspace{Uri: c.workspaceURI} 110 req.PatchAgainstWorkspace = true 111 } 112 return req, nil 113 } 114 115 func (c baseDecorCommand) spanArg() (*cpb.Span, error) { 116 if c.decorSpan == "" { 117 return nil, nil 118 } 119 span, err := parseSpan(c.decorSpan) 120 if err != nil { 121 return nil, fmt.Errorf("invalid --span %q: %v", c.decorSpan, err) 122 } 123 return span, nil 124 } 125 126 type decorCommand struct { 127 baseDecorCommand 128 targetDefs bool 129 dirtyFile string 130 refFormat string 131 extendsOverrides bool 132 semanticScopes bool 133 } 134 135 func (decorCommand) Name() string { return "decor" } 136 func (decorCommand) Synopsis() string { return "list a file's decorations" } 137 func (decorCommand) Aliases() []string { return []string{"decors"} } 138 func (c *decorCommand) SetFlags(flag *flag.FlagSet) { 139 c.baseDecorCommand.SetFlags(flag) 140 // TODO(schroederc): add option to look for dirty files based on file-ticket path and a directory root 141 flag.StringVar(&c.dirtyFile, "dirty", "", "Send the given file as the dirty buffer for patching references") 142 flag.StringVar(&c.refFormat, "format", "@edgeKind@\t@^line@:@^col@-@$line@:@$col@\t@targetKind@\t@target@\t@targetDef@", 143 `Format for each decoration result. 144 Format Markers: 145 @target@ -- ticket of referenced target node 146 @targetDef@ -- ticket of referenced target's definition 147 @edgeKind@ -- edge kind from anchor node to its referenced target 148 @targetKind@ -- node kind and subkind of referenced target 149 @nodeKind@ -- node kind of referenced target 150 @subkind@ -- subkind of referenced target 151 @^offset@ -- anchor source's starting byte-offset 152 @^line@ -- anchor source's starting line 153 @^col@ -- anchor source's starting column offset 154 @$offset@ -- anchor source's ending byte-offset 155 @$line@ -- anchor source's ending line 156 @$col@ -- anchor source's ending column offset`) 157 flag.BoolVar(&c.targetDefs, "target_definitions", false, "Whether to request definitions (@targetDef@ format marker) for each reference's target") 158 flag.BoolVar(&c.extendsOverrides, "extends_overrides", false, "Whether to request extends/overrides information") 159 flag.BoolVar(&c.semanticScopes, "semantic_scopes", false, "Whether to request semantic scope information") 160 } 161 func (c decorCommand) Run(ctx context.Context, flag *flag.FlagSet, api API) error { 162 req, err := c.baseRequest(flag) 163 if err != nil { 164 return err 165 } 166 req.References = true 167 req.TargetDefinitions = c.targetDefs 168 req.ExtendsOverrides = c.extendsOverrides 169 req.SemanticScopes = c.semanticScopes 170 req.Filter = []string{ 171 facts.NodeKind, 172 facts.Subkind, 173 } 174 if c.dirtyFile != "" { 175 f, err := vfs.Open(ctx, c.dirtyFile) 176 if err != nil { 177 return fmt.Errorf("error opening dirty buffer file at %q: %v", c.dirtyFile, err) 178 } 179 buf, err := ioutil.ReadAll(f) 180 if err != nil { 181 f.Close() 182 return fmt.Errorf("error reading dirty buffer file: %v", err) 183 } else if err := f.Close(); err != nil { 184 return fmt.Errorf("error closing dirty buffer file: %v", err) 185 } 186 req.DirtyBuffer = buf 187 } 188 189 LogRequest(req) 190 reply, err := api.XRefService.Decorations(ctx, req) 191 if err != nil { 192 return err 193 } 194 195 return c.displayDecorations(reply) 196 } 197 198 func (c decorCommand) displayDecorations(decor *xpb.DecorationsReply) error { 199 if DisplayJSON { 200 return PrintJSONMessage(decor) 201 } 202 203 nodes := graph.NodesMap(decor.Nodes) 204 205 for _, ref := range decor.Reference { 206 nodeKind := factValue(nodes, ref.TargetTicket, facts.NodeKind, "UNKNOWN") 207 subkind := factValue(nodes, ref.TargetTicket, facts.Subkind, "") 208 209 tgtKind := nodeKind 210 if subkind != "" { 211 tgtKind += "/" + subkind 212 } 213 214 var targetDef string 215 if ref.TargetDefinition != "" { 216 targetDef = ref.TargetDefinition 217 // TODO(schroederc): fields from decor.DefinitionLocations 218 // TODO(zarko): fields from decor.ExtendsOverrides 219 } 220 221 r := strings.NewReplacer( 222 "@target@", ref.TargetTicket, 223 "@edgeKind@", ref.Kind, 224 "@targetKind@", tgtKind, 225 "@nodeKind@", nodeKind, 226 "@subkind@", subkind, 227 "@^offset@", itoa(ref.Span.Start.ByteOffset), 228 "@^line@", itoa(ref.Span.Start.LineNumber), 229 "@^col@", itoa(ref.Span.Start.ColumnOffset), 230 "@$offset@", itoa(ref.Span.End.ByteOffset), 231 "@$line@", itoa(ref.Span.End.LineNumber), 232 "@$col@", itoa(ref.Span.End.ColumnOffset), 233 "@targetDef@", targetDef, 234 ) 235 if _, err := r.WriteString(out, c.refFormat+"\n"); err != nil { 236 return err 237 } 238 } 239 240 return nil 241 } 242 243 func itoa(n int32) string { return strconv.Itoa(int(n)) } 244 245 func factValue(m map[string]map[string][]byte, ticket, factName, def string) string { 246 if n, ok := m[ticket]; ok { 247 if val, ok := n[factName]; ok { 248 return string(val) 249 } 250 } 251 return def 252 }