github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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/powerman/golang-tools/go/analysis" 15 "github.com/powerman/golang-tools/internal/lsp/analysis/fillstruct" 16 "github.com/powerman/golang-tools/internal/lsp/analysis/undeclaredname" 17 "github.com/powerman/golang-tools/internal/lsp/protocol" 18 "github.com/powerman/golang-tools/internal/span" 19 errors "golang.org/x/xerrors" 20 ) 21 22 type ( 23 // SuggestedFixFunc is a function used to get the suggested fixes for a given 24 // gopls command, some of which are provided by go/analysis.Analyzers. Some of 25 // the analyzers in internal/lsp/analysis are not efficient enough to include 26 // suggested fixes with their diagnostics, so we have to compute them 27 // separately. Such analyzers should provide a function with a signature of 28 // SuggestedFixFunc. 29 SuggestedFixFunc func(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) (*analysis.SuggestedFix, error) 30 singleFileFixFunc func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) 31 ) 32 33 const ( 34 FillStruct = "fill_struct" 35 StubMethods = "stub_methods" 36 UndeclaredName = "undeclared_name" 37 ExtractVariable = "extract_variable" 38 ExtractFunction = "extract_function" 39 ExtractMethod = "extract_method" 40 ) 41 42 // suggestedFixes maps a suggested fix command id to its handler. 43 var suggestedFixes = map[string]SuggestedFixFunc{ 44 FillStruct: singleFile(fillstruct.SuggestedFix), 45 UndeclaredName: singleFile(undeclaredname.SuggestedFix), 46 ExtractVariable: singleFile(extractVariable), 47 ExtractFunction: singleFile(extractFunction), 48 ExtractMethod: singleFile(extractMethod), 49 StubMethods: stubSuggestedFixFunc, 50 } 51 52 // singleFile calls analyzers that expect inputs for a single file 53 func singleFile(sf singleFileFixFunc) SuggestedFixFunc { 54 return func(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) (*analysis.SuggestedFix, error) { 55 fset, rng, src, file, pkg, info, err := getAllSuggestedFixInputs(ctx, snapshot, fh, pRng) 56 if err != nil { 57 return nil, err 58 } 59 return sf(fset, rng, src, file, pkg, info) 60 } 61 } 62 63 func SuggestedFixFromCommand(cmd protocol.Command, kind protocol.CodeActionKind) SuggestedFix { 64 return SuggestedFix{ 65 Title: cmd.Title, 66 Command: &cmd, 67 ActionKind: kind, 68 } 69 } 70 71 // ApplyFix applies the command's suggested fix to the given file and 72 // range, returning the resulting edits. 73 func ApplyFix(ctx context.Context, fix string, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) ([]protocol.TextDocumentEdit, error) { 74 handler, ok := suggestedFixes[fix] 75 if !ok { 76 return nil, fmt.Errorf("no suggested fix function for %s", fix) 77 } 78 suggestion, err := handler(ctx, snapshot, fh, pRng) 79 if err != nil { 80 return nil, err 81 } 82 if suggestion == nil { 83 return nil, nil 84 } 85 fset := snapshot.FileSet() 86 editsPerFile := map[span.URI]*protocol.TextDocumentEdit{} 87 for _, edit := range suggestion.TextEdits { 88 spn, err := span.NewRange(fset, edit.Pos, edit.End).Span() 89 if err != nil { 90 return nil, err 91 } 92 fh, err := snapshot.GetVersionedFile(ctx, spn.URI()) 93 if err != nil { 94 return nil, err 95 } 96 te, ok := editsPerFile[spn.URI()] 97 if !ok { 98 te = &protocol.TextDocumentEdit{ 99 TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{ 100 Version: fh.Version(), 101 TextDocumentIdentifier: protocol.TextDocumentIdentifier{ 102 URI: protocol.URIFromSpanURI(fh.URI()), 103 }, 104 }, 105 } 106 editsPerFile[spn.URI()] = te 107 } 108 _, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage) 109 if err != nil { 110 return nil, err 111 } 112 rng, err := pgf.Mapper.Range(spn) 113 if err != nil { 114 return nil, err 115 } 116 te.Edits = append(te.Edits, protocol.TextEdit{ 117 Range: rng, 118 NewText: string(edit.NewText), 119 }) 120 } 121 var edits []protocol.TextDocumentEdit 122 for _, edit := range editsPerFile { 123 edits = append(edits, *edit) 124 } 125 return edits, nil 126 } 127 128 // getAllSuggestedFixInputs is a helper function to collect all possible needed 129 // inputs for an AppliesFunc or SuggestedFixFunc. 130 func getAllSuggestedFixInputs(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) (*token.FileSet, span.Range, []byte, *ast.File, *types.Package, *types.Info, error) { 131 pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage) 132 if err != nil { 133 return nil, span.Range{}, nil, nil, nil, nil, errors.Errorf("getting file for Identifier: %w", err) 134 } 135 rng, err := pgf.Mapper.RangeToSpanRange(pRng) 136 if err != nil { 137 return nil, span.Range{}, nil, nil, nil, nil, err 138 } 139 return snapshot.FileSet(), rng, pgf.Src, pgf.File, pkg.GetTypes(), pkg.GetTypesInfo(), nil 140 }