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  }