github.com/tiagovtristao/plz@v13.4.0+incompatible/tools/build_langserver/langserver/rename.go (about)

     1  package langserver
     2  
     3  import (
     4  	"context"
     5  	"core"
     6  	"encoding/json"
     7  
     8  	"github.com/sourcegraph/jsonrpc2"
     9  
    10  	"github.com/thought-machine/please/src/parse/asp"
    11  	"github.com/thought-machine/please/tools/build_langserver/lsp"
    12  )
    13  
    14  const renameMethod = "textDocument/rename"
    15  
    16  func (h *LsHandler) handleRename(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error) {
    17  	if req.Params == nil {
    18  		return nil, nil
    19  	}
    20  
    21  	var params lsp.RenameParams
    22  	if err := json.Unmarshal(*req.Params, &params); err != nil {
    23  		return nil, err
    24  	}
    25  
    26  	documentURI, err := getURIAndHandleErrors(params.TextDocument.URI, renameMethod)
    27  	if err != nil {
    28  		return nil, err
    29  	}
    30  
    31  	edits, err := h.getRenameEdits(ctx, params.NewName, documentURI, params.Position)
    32  	if err != nil {
    33  		log.Warning("error occurred trying to get the rename edits from %s", documentURI)
    34  	}
    35  
    36  	return edits, nil
    37  }
    38  
    39  func (h *LsHandler) getRenameEdits(ctx context.Context, newName string,
    40  	uri lsp.DocumentURI, pos lsp.Position) (*lsp.WorkspaceEdit, error) {
    41  
    42  	renamingLabel, err := h.getRenamingLabel(ctx, uri, pos)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	h.mu.Lock()
    48  	defer h.mu.Unlock()
    49  
    50  	doc, ok := h.workspace.documents[uri]
    51  	if !ok {
    52  		log.Info("document %s is not opened", uri)
    53  		return nil, nil
    54  	}
    55  	workSpaceEdit := &lsp.WorkspaceEdit{
    56  		Changes: make(map[lsp.DocumentURI][]lsp.TextEdit),
    57  	}
    58  	// Fill in workSpaceEdit.Changes first
    59  	for _, label := range renamingLabel.RevDeps {
    60  		buildLabel, err := h.analyzer.BuildLabelFromCoreBuildLabel(ctx, label)
    61  		if err != nil {
    62  			// In the case of error, we still return the current available locs
    63  			return workSpaceEdit, nil
    64  		}
    65  		labelURI := lsp.DocumentURI("file://" + buildLabel.Path)
    66  		edits := getEditsFromLabel(buildLabel, renamingLabel, newName)
    67  
    68  		_, ok := workSpaceEdit.Changes[labelURI]
    69  		if ok {
    70  			workSpaceEdit.Changes[uri] = append(workSpaceEdit.Changes[uri], edits...)
    71  		} else {
    72  			workSpaceEdit.Changes[uri] = edits
    73  		}
    74  	}
    75  
    76  	for k, v := range workSpaceEdit.Changes {
    77  		docChange := lsp.TextDocumentEdit{
    78  			TextDocument: lsp.VersionedTextDocumentIdentifier{
    79  				TextDocumentIdentifier: &lsp.TextDocumentIdentifier{URI: k},
    80  				Version:                doc.version,
    81  			},
    82  			Edits: v,
    83  		}
    84  		workSpaceEdit.DocumentChanges = append(workSpaceEdit.DocumentChanges, docChange)
    85  	}
    86  
    87  	return workSpaceEdit, nil
    88  }
    89  
    90  // getRenamingLabel returns a BuildLabel object with RevDeps being defined
    91  func (h *LsHandler) getRenamingLabel(ctx context.Context, uri lsp.DocumentURI, pos lsp.Position) (*BuildLabel, error) {
    92  
    93  	h.mu.Lock()
    94  	defer h.mu.Unlock()
    95  
    96  	def, err := h.analyzer.BuildDefsFromPos(ctx, uri, pos)
    97  	if def == nil || err != nil || !isPosAtNameArg(def, pos) {
    98  		return nil, err
    99  	}
   100  
   101  	coreLabel, err := getCoreBuildLabel(def, uri)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	renamingLabel, err := h.analyzer.BuildLabelFromCoreBuildLabel(ctx, coreLabel)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	revDeps, err := h.analyzer.RevDepsFromCoreBuildLabel(coreLabel, uri)
   111  	if err != nil {
   112  		log.Info("error occurred computing the reverse dependency of %s: %s", coreLabel.String(), err)
   113  		return nil, err
   114  	}
   115  
   116  	renamingLabel.RevDeps = revDeps
   117  
   118  	return renamingLabel, nil
   119  }
   120  
   121  func getEditsFromLabel(depLabel *BuildLabel, renaminglabel *BuildLabel, newName string) (edits []lsp.TextEdit) {
   122  	if !isBuildDefValid(depLabel.BuildDef) {
   123  		return nil
   124  	}
   125  
   126  	// Get the label string based on whether it's relative
   127  	newLabelStr := core.NewBuildLabel(renaminglabel.PackageName, newName).String()
   128  	oldLabelStr := renaminglabel.String()
   129  	if renaminglabel.Path == depLabel.Path {
   130  		newLabelStr = ":" + newName
   131  		oldLabelStr = ":" + renaminglabel.Name
   132  	}
   133  
   134  	// Walk through the all the arguments from the call, and find string values that is the same as the old label string
   135  	callback := func(astStruct interface{}) interface{} {
   136  		if expr, ok := astStruct.(asp.Expression); ok && expr.Val != nil {
   137  			if expr.Val.String != "" && TrimQuotes(expr.Val.String) == oldLabelStr {
   138  				pos := aspPositionToLsp(expr.Pos)
   139  				// Add 1 to the character, as we would be changing from inside of the quotes
   140  				pos.Character = pos.Character + 1
   141  				edit := lsp.TextEdit{
   142  					Range:   getNameRange(pos, oldLabelStr),
   143  					NewText: newLabelStr,
   144  				}
   145  				edits = append(edits, edit)
   146  			}
   147  		}
   148  
   149  		return nil
   150  	}
   151  
   152  	asp.WalkAST(depLabel.BuildDef.Action.Call.Arguments, callback)
   153  
   154  	return edits
   155  }
   156  
   157  func isPosAtNameArg(def *BuildDef, pos lsp.Position) bool {
   158  
   159  	if !isBuildDefValid(def) {
   160  		return false
   161  	}
   162  
   163  	args := def.Action.Call.Arguments
   164  
   165  	for _, arg := range args {
   166  		if arg.Name == "name" && withInRange(arg.Value.Pos, arg.Value.EndPos, pos) {
   167  			return true
   168  		}
   169  	}
   170  
   171  	return false
   172  }
   173  
   174  func isBuildDefValid(def *BuildDef) bool {
   175  	if def.Action == nil || def.Action.Call == nil ||
   176  		def.Action.Call.Arguments == nil || len(def.Action.Call.Arguments) == 0 {
   177  
   178  		return false
   179  	}
   180  
   181  	return true
   182  }