kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/services/cli/commands_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 cli
    18  
    19  import (
    20  	"context"
    21  	"flag"
    22  	"fmt"
    23  	"strings"
    24  
    25  	"kythe.io/kythe/go/util/flagutil"
    26  	"kythe.io/kythe/go/util/kytheuri"
    27  	"kythe.io/kythe/go/util/log"
    28  	"kythe.io/kythe/go/util/markedsource"
    29  	"kythe.io/kythe/go/util/schema/edges"
    30  	"kythe.io/kythe/go/util/schema/facts"
    31  
    32  	cpb "kythe.io/kythe/proto/common_go_proto"
    33  	xpb "kythe.io/kythe/proto/xref_go_proto"
    34  )
    35  
    36  type xrefsCommand struct {
    37  	baseKytheCommand
    38  	nodeFilters  flagutil.StringList
    39  	buildConfigs flagutil.StringSet
    40  
    41  	workspaceURI string
    42  
    43  	pageToken string
    44  	pageSize  int
    45  
    46  	defKind    string
    47  	declKind   string
    48  	refKind    string
    49  	callerKind string
    50  
    51  	semanticScopes  bool
    52  	relatedNodes    bool
    53  	nodeDefinitions bool
    54  	anchorText      bool
    55  
    56  	resolvedPathFilters flagutil.StringList
    57  
    58  	excludeGenerated bool
    59  
    60  	totalsOnly bool
    61  }
    62  
    63  func (xrefsCommand) Name() string     { return "xrefs" }
    64  func (xrefsCommand) Synopsis() string { return "retrieve cross-references for the given node" }
    65  func (xrefsCommand) Usage() string    { return "" }
    66  func (c *xrefsCommand) SetFlags(flag *flag.FlagSet) {
    67  	flag.StringVar(&c.defKind, "definitions", "binding", "Kind of definitions to return (kinds: all, binding, full, or none)")
    68  	flag.StringVar(&c.declKind, "declarations", "all", "Kind of declarations to return (kinds: all or none)")
    69  	flag.StringVar(&c.refKind, "references", "noncall", "Kind of references to return (kinds: all, noncall, call, or none)")
    70  	flag.StringVar(&c.callerKind, "callers", "direct", "Kind of callers to return (kinds: direct, overrides, or none)")
    71  	flag.StringVar(&c.workspaceURI, "workspace_uri", "", "Workspace URI to patch cross-references")
    72  	flag.BoolVar(&c.relatedNodes, "related_nodes", true, "Whether to request related nodes")
    73  	flag.Var(&c.nodeFilters, "filters", "CSV list of additional fact filters to use when requesting related nodes")
    74  	flag.Var(&c.resolvedPathFilters, "resolved_path_filters", "CSV list of additional resolved path filters to use")
    75  	flag.Var(&c.buildConfigs, "build_config", "CSV set of build configs with which to filter file decorations")
    76  	flag.BoolVar(&c.nodeDefinitions, "node_definitions", false, "Whether to request definition locations for related nodes")
    77  	flag.BoolVar(&c.anchorText, "anchor_text", false, "Whether to request text for anchors")
    78  	flag.BoolVar(&c.semanticScopes, "semantic_scopes", false, "Whether to include semantic scopes")
    79  	flag.BoolVar(&c.excludeGenerated, "exclude_generated", false, "Whether to exclude anchors with non-empty roots")
    80  
    81  	flag.BoolVar(&c.totalsOnly, "totals_only", false, "Only output total count of xrefs")
    82  
    83  	flag.StringVar(&c.pageToken, "page_token", "", "CrossReferences page token")
    84  	flag.IntVar(&c.pageSize, "page_size", 0, "Maximum number of cross-references returned (0 lets the service use a sensible default)")
    85  }
    86  func (c xrefsCommand) Run(ctx context.Context, flag *flag.FlagSet, api API) error {
    87  	req := &xpb.CrossReferencesRequest{
    88  		Ticket:        flag.Args(),
    89  		PageToken:     c.pageToken,
    90  		PageSize:      int32(c.pageSize),
    91  		Snippets:      xpb.SnippetsKind_DEFAULT,
    92  		TotalsQuality: xpb.CrossReferencesRequest_APPROXIMATE_TOTALS,
    93  
    94  		SemanticScopes:  c.semanticScopes,
    95  		AnchorText:      c.anchorText,
    96  		NodeDefinitions: c.nodeDefinitions,
    97  
    98  		CorpusPathFilters: &xpb.CorpusPathFilters{},
    99  	}
   100  	if c.workspaceURI != "" {
   101  		req.Workspace = &xpb.Workspace{Uri: c.workspaceURI}
   102  		req.PatchAgainstWorkspace = true
   103  	}
   104  	if c.excludeGenerated {
   105  		req.CorpusPathFilters.Filter = append(req.CorpusPathFilters.Filter, &xpb.CorpusPathFilter{
   106  			Type: xpb.CorpusPathFilter_EXCLUDE,
   107  			Root: ".+",
   108  		})
   109  	}
   110  	for _, f := range c.resolvedPathFilters {
   111  		req.CorpusPathFilters.Filter = append(req.CorpusPathFilters.Filter, &xpb.CorpusPathFilter{
   112  			Type:         xpb.CorpusPathFilter_INCLUDE_ONLY,
   113  			ResolvedPath: f,
   114  		})
   115  	}
   116  	if c.relatedNodes {
   117  		req.Filter = []string{facts.NodeKind, facts.Subkind}
   118  		if len(c.nodeFilters) > 0 && (len(c.nodeFilters) != 1 || c.nodeFilters[0] != "") {
   119  			req.Filter = append(req.Filter, c.nodeFilters...)
   120  		}
   121  	}
   122  	req.BuildConfig = c.buildConfigs.Elements()
   123  	switch c.defKind {
   124  	case "all":
   125  		req.DefinitionKind = xpb.CrossReferencesRequest_ALL_DEFINITIONS
   126  	case "none":
   127  		req.DefinitionKind = xpb.CrossReferencesRequest_NO_DEFINITIONS
   128  	case "binding":
   129  		req.DefinitionKind = xpb.CrossReferencesRequest_BINDING_DEFINITIONS
   130  	case "full":
   131  		req.DefinitionKind = xpb.CrossReferencesRequest_FULL_DEFINITIONS
   132  	default:
   133  		return fmt.Errorf("unknown definition kind: %q", c.defKind)
   134  	}
   135  	switch c.declKind {
   136  	case "all":
   137  		req.DeclarationKind = xpb.CrossReferencesRequest_ALL_DECLARATIONS
   138  	case "none":
   139  		req.DeclarationKind = xpb.CrossReferencesRequest_NO_DECLARATIONS
   140  	default:
   141  		return fmt.Errorf("unknown declaration kind: %q", c.declKind)
   142  	}
   143  	switch c.refKind {
   144  	case "all":
   145  		req.ReferenceKind = xpb.CrossReferencesRequest_ALL_REFERENCES
   146  	case "noncall":
   147  		req.ReferenceKind = xpb.CrossReferencesRequest_NON_CALL_REFERENCES
   148  	case "call":
   149  		req.ReferenceKind = xpb.CrossReferencesRequest_CALL_REFERENCES
   150  	case "none":
   151  		req.ReferenceKind = xpb.CrossReferencesRequest_NO_REFERENCES
   152  	default:
   153  		return fmt.Errorf("unknown reference kind: %q", c.refKind)
   154  	}
   155  	switch c.callerKind {
   156  	case "direct":
   157  		req.CallerKind = xpb.CrossReferencesRequest_DIRECT_CALLERS
   158  	case "overrides":
   159  		req.CallerKind = xpb.CrossReferencesRequest_OVERRIDE_CALLERS
   160  	case "none":
   161  		req.CallerKind = xpb.CrossReferencesRequest_NO_CALLERS
   162  	default:
   163  		return fmt.Errorf("unknown caller kind: %q", c.callerKind)
   164  	}
   165  	LogRequest(req)
   166  	reply, err := api.XRefService.CrossReferences(ctx, req)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	if reply.NextPageToken != "" {
   171  		defer log.InfoContextf(ctx, "Next page token: %s", reply.NextPageToken)
   172  	}
   173  	return c.displayXRefs(reply)
   174  }
   175  
   176  func (c xrefsCommand) displayXRefs(reply *xpb.CrossReferencesReply) error {
   177  	if DisplayJSON {
   178  		return PrintJSONMessage(reply)
   179  	}
   180  
   181  	fmt.Fprintf(out, "Totals:\n%s\n\n", reply.GetTotal())
   182  
   183  	if c.totalsOnly {
   184  		return nil
   185  	}
   186  
   187  	for _, xr := range reply.CrossReferences {
   188  		var sig string
   189  		if xr.MarkedSource != nil {
   190  			sig = showSignature(xr.MarkedSource) + " "
   191  		}
   192  		if _, err := fmt.Fprintf(out, "Cross-References for %s%s\n", sig, xr.Ticket); err != nil {
   193  			return err
   194  		}
   195  		if err := displayRelatedAnchors("Definitions", xr.Definition); err != nil {
   196  			return err
   197  		}
   198  		if err := displayRelatedAnchors("Declarations", xr.Declaration); err != nil {
   199  			return err
   200  		}
   201  		if err := displayRelatedAnchors("References", xr.Reference); err != nil {
   202  			return err
   203  		}
   204  		if err := displayRelatedAnchors("Callers", xr.Caller); err != nil {
   205  			return err
   206  		}
   207  		if len(xr.RelatedNode) > 0 {
   208  			if _, err := fmt.Fprintln(out, "  Related Nodes:"); err != nil {
   209  				return err
   210  			}
   211  			for _, n := range xr.RelatedNode {
   212  				var nodeKind, subkind string
   213  				if node, ok := reply.Nodes[n.Ticket]; ok {
   214  					for name, value := range node.Facts {
   215  						switch name {
   216  						case facts.NodeKind:
   217  							nodeKind = string(value)
   218  						case facts.Subkind:
   219  							subkind = string(value)
   220  						}
   221  					}
   222  				}
   223  				if nodeKind == "" {
   224  					nodeKind = "UNKNOWN"
   225  				} else if subkind != "" {
   226  					nodeKind += "/" + subkind
   227  				}
   228  				var ordinal string
   229  				if edges.OrdinalKind(n.RelationKind) || n.Ordinal != 0 {
   230  					ordinal = fmt.Sprintf(".%d", n.Ordinal)
   231  				}
   232  				if _, err := fmt.Fprintf(out, "    %s %s%s [%s]\n", n.Ticket, n.RelationKind, ordinal, nodeKind); err != nil {
   233  					return err
   234  				}
   235  			}
   236  		}
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  func displayRelatedAnchors(kind string, anchors []*xpb.CrossReferencesReply_RelatedAnchor) error {
   243  	if len(anchors) == 0 {
   244  		return nil
   245  	}
   246  
   247  	if _, err := fmt.Fprintf(out, "  %s:\n", kind); err != nil {
   248  		return err
   249  	}
   250  
   251  	for _, a := range anchors {
   252  		pURI, err := kytheuri.Parse(a.Anchor.Parent)
   253  		if err != nil {
   254  			return err
   255  		}
   256  		if _, err := fmt.Fprintf(out, "    %s\t", pURI.Path); err != nil {
   257  			return err
   258  		}
   259  		if a.MarkedSource != nil {
   260  			if _, err := fmt.Fprintf(out, "%s\t", showSignature(a.MarkedSource)); err != nil {
   261  				return err
   262  			}
   263  		}
   264  		if _, err := fmt.Fprintf(out, "    [%d:%d-%d:%d %s)\n      %q\n",
   265  			a.GetAnchor().GetSpan().GetStart().GetLineNumber(), a.GetAnchor().GetSpan().GetStart().GetColumnOffset(),
   266  			a.GetAnchor().GetSpan().GetEnd().GetLineNumber(), a.GetAnchor().GetSpan().GetEnd().GetColumnOffset(),
   267  			a.GetAnchor().GetKind(), string(a.GetAnchor().GetSnippet())); err != nil {
   268  			return err
   269  		}
   270  		for _, site := range a.Site {
   271  			if _, err := fmt.Fprintf(out, "      [%d:%d-%d-%d %s)\n        %q\n",
   272  				site.GetSpan().GetStart().GetLineNumber(), site.GetSpan().GetStart().GetColumnOffset(),
   273  				site.GetSpan().GetEnd().GetLineNumber(), site.GetSpan().GetEnd().GetColumnOffset(),
   274  				site.GetKind(), string(site.GetSnippet())); err != nil {
   275  				return err
   276  			}
   277  		}
   278  	}
   279  
   280  	return nil
   281  }
   282  
   283  func showSignature(signature *cpb.MarkedSource) string {
   284  	if signature == nil {
   285  		return "(nil)"
   286  	}
   287  	ident := markedsource.RenderSimpleIdentifier(signature, markedsource.PlaintextContent, nil)
   288  	params := markedsource.RenderSimpleParams(signature, markedsource.PlaintextContent, nil)
   289  	if len(params) == 0 {
   290  		return ident
   291  	}
   292  	return fmt.Sprintf("%s(%s)", ident, strings.Join(params, ","))
   293  }