github.com/v2fly/tools@v0.100.0/internal/lsp/source/fix.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package source
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/token"
    12  	"go/types"
    13  
    14  	"github.com/v2fly/tools/go/analysis"
    15  	"github.com/v2fly/tools/internal/lsp/analysis/fillstruct"
    16  	"github.com/v2fly/tools/internal/lsp/analysis/undeclaredname"
    17  	"github.com/v2fly/tools/internal/lsp/protocol"
    18  	"github.com/v2fly/tools/internal/span"
    19  	errors "golang.org/x/xerrors"
    20  )
    21  
    22  // SuggestedFixFunc is a function used to get the suggested fixes for a given
    23  // gopls command, some of which are provided by go/analysis.Analyzers. Some of
    24  // the analyzers in internal/lsp/analysis are not efficient enough to include
    25  // suggested fixes with their diagnostics, so we have to compute them
    26  // separately. Such analyzers should provide a function with a signature of
    27  // SuggestedFixFunc.
    28  type SuggestedFixFunc func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error)
    29  
    30  const (
    31  	FillStruct      = "fill_struct"
    32  	UndeclaredName  = "undeclared_name"
    33  	ExtractVariable = "extract_variable"
    34  	ExtractFunction = "extract_function"
    35  )
    36  
    37  // suggestedFixes maps a suggested fix command id to its handler.
    38  var suggestedFixes = map[string]SuggestedFixFunc{
    39  	FillStruct:      fillstruct.SuggestedFix,
    40  	UndeclaredName:  undeclaredname.SuggestedFix,
    41  	ExtractVariable: extractVariable,
    42  	ExtractFunction: extractFunction,
    43  }
    44  
    45  func SuggestedFixFromCommand(cmd protocol.Command, kind protocol.CodeActionKind) SuggestedFix {
    46  	return SuggestedFix{
    47  		Title:      cmd.Title,
    48  		Command:    &cmd,
    49  		ActionKind: kind,
    50  	}
    51  }
    52  
    53  // ApplyFix applies the command's suggested fix to the given file and
    54  // range, returning the resulting edits.
    55  func ApplyFix(ctx context.Context, fix string, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) ([]protocol.TextDocumentEdit, error) {
    56  	handler, ok := suggestedFixes[fix]
    57  	if !ok {
    58  		return nil, fmt.Errorf("no suggested fix function for %s", fix)
    59  	}
    60  	fset, rng, src, file, m, pkg, info, err := getAllSuggestedFixInputs(ctx, snapshot, fh, pRng)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	suggestion, err := handler(fset, rng, src, file, pkg, info)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	if suggestion == nil {
    69  		return nil, nil
    70  	}
    71  
    72  	var edits []protocol.TextDocumentEdit
    73  	for _, edit := range suggestion.TextEdits {
    74  		rng := span.NewRange(fset, edit.Pos, edit.End)
    75  		spn, err := rng.Span()
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  		clRng, err := m.Range(spn)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  		edits = append(edits, protocol.TextDocumentEdit{
    84  			TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
    85  				Version: fh.Version(),
    86  				TextDocumentIdentifier: protocol.TextDocumentIdentifier{
    87  					URI: protocol.URIFromSpanURI(fh.URI()),
    88  				},
    89  			},
    90  			Edits: []protocol.TextEdit{
    91  				{
    92  					Range:   clRng,
    93  					NewText: string(edit.NewText),
    94  				},
    95  			},
    96  		})
    97  	}
    98  	return edits, nil
    99  }
   100  
   101  // getAllSuggestedFixInputs is a helper function to collect all possible needed
   102  // inputs for an AppliesFunc or SuggestedFixFunc.
   103  func getAllSuggestedFixInputs(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) (*token.FileSet, span.Range, []byte, *ast.File, *protocol.ColumnMapper, *types.Package, *types.Info, error) {
   104  	pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
   105  	if err != nil {
   106  		return nil, span.Range{}, nil, nil, nil, nil, nil, errors.Errorf("getting file for Identifier: %w", err)
   107  	}
   108  	rng, err := pgf.Mapper.RangeToSpanRange(pRng)
   109  	if err != nil {
   110  		return nil, span.Range{}, nil, nil, nil, nil, nil, err
   111  	}
   112  	return snapshot.FileSet(), rng, pgf.Src, pgf.File, pgf.Mapper, pkg.GetTypes(), pkg.GetTypesInfo(), nil
   113  }