github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/mod/code_lens.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 mod
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  
    13  	"golang.org/x/mod/modfile"
    14  	"github.com/powerman/golang-tools/internal/lsp/command"
    15  	"github.com/powerman/golang-tools/internal/lsp/protocol"
    16  	"github.com/powerman/golang-tools/internal/lsp/source"
    17  )
    18  
    19  // LensFuncs returns the supported lensFuncs for go.mod files.
    20  func LensFuncs() map[command.Command]source.LensFunc {
    21  	return map[command.Command]source.LensFunc{
    22  		command.UpgradeDependency: upgradeLenses,
    23  		command.Tidy:              tidyLens,
    24  		command.Vendor:            vendorLens,
    25  	}
    26  }
    27  
    28  func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
    29  	pm, err := snapshot.ParseMod(ctx, fh)
    30  	if err != nil || pm.File == nil {
    31  		return nil, err
    32  	}
    33  	if len(pm.File.Require) == 0 {
    34  		// Nothing to upgrade.
    35  		return nil, nil
    36  	}
    37  	var requires []string
    38  	for _, req := range pm.File.Require {
    39  		requires = append(requires, req.Mod.Path)
    40  	}
    41  	uri := protocol.URIFromSpanURI(fh.URI())
    42  	checkUpgrade, err := command.NewCheckUpgradesCommand("Check for upgrades", command.CheckUpgradesArgs{
    43  		URI:     uri,
    44  		Modules: requires,
    45  	})
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	upgradeTransitive, err := command.NewUpgradeDependencyCommand("Upgrade transitive dependencies", command.DependencyArgs{
    50  		URI:        uri,
    51  		AddRequire: false,
    52  		GoCmdArgs:  []string{"-d", "-u", "-t", "./..."},
    53  	})
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	upgradeDirect, err := command.NewUpgradeDependencyCommand("Upgrade direct dependencies", command.DependencyArgs{
    58  		URI:        uri,
    59  		AddRequire: false,
    60  		GoCmdArgs:  append([]string{"-d"}, requires...),
    61  	})
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	// Put the upgrade code lenses above the first require block or statement.
    66  	rng, err := firstRequireRange(fh, pm)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	return []protocol.CodeLens{
    72  		{Range: rng, Command: checkUpgrade},
    73  		{Range: rng, Command: upgradeTransitive},
    74  		{Range: rng, Command: upgradeDirect},
    75  	}, nil
    76  }
    77  
    78  func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
    79  	pm, err := snapshot.ParseMod(ctx, fh)
    80  	if err != nil || pm.File == nil {
    81  		return nil, err
    82  	}
    83  	uri := protocol.URIFromSpanURI(fh.URI())
    84  	cmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: []protocol.DocumentURI{uri}})
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	rng, err := moduleStmtRange(fh, pm)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	return []protocol.CodeLens{{
    93  		Range:   rng,
    94  		Command: cmd,
    95  	}}, nil
    96  }
    97  
    98  func vendorLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
    99  	pm, err := snapshot.ParseMod(ctx, fh)
   100  	if err != nil || pm.File == nil {
   101  		return nil, err
   102  	}
   103  	if len(pm.File.Require) == 0 {
   104  		// Nothing to vendor.
   105  		return nil, nil
   106  	}
   107  	rng, err := moduleStmtRange(fh, pm)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	title := "Create vendor directory"
   112  	uri := protocol.URIFromSpanURI(fh.URI())
   113  	cmd, err := command.NewVendorCommand(title, command.URIArg{URI: uri})
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	// Change the message depending on whether or not the module already has a
   118  	// vendor directory.
   119  	vendorDir := filepath.Join(filepath.Dir(fh.URI().Filename()), "vendor")
   120  	if info, _ := os.Stat(vendorDir); info != nil && info.IsDir() {
   121  		title = "Sync vendor directory"
   122  	}
   123  	return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
   124  }
   125  
   126  func moduleStmtRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) {
   127  	if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil {
   128  		return protocol.Range{}, fmt.Errorf("no module statement in %s", fh.URI())
   129  	}
   130  	syntax := pm.File.Module.Syntax
   131  	return source.LineToRange(pm.Mapper, fh.URI(), syntax.Start, syntax.End)
   132  }
   133  
   134  // firstRequireRange returns the range for the first "require" in the given
   135  // go.mod file. This is either a require block or an individual require line.
   136  func firstRequireRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) {
   137  	if len(pm.File.Require) == 0 {
   138  		return protocol.Range{}, fmt.Errorf("no requires in the file %s", fh.URI())
   139  	}
   140  	var start, end modfile.Position
   141  	for _, stmt := range pm.File.Syntax.Stmt {
   142  		if b, ok := stmt.(*modfile.LineBlock); ok && len(b.Token) == 1 && b.Token[0] == "require" {
   143  			start, end = b.Span()
   144  			break
   145  		}
   146  	}
   147  
   148  	firstRequire := pm.File.Require[0].Syntax
   149  	if start.Byte == 0 || firstRequire.Start.Byte < start.Byte {
   150  		start, end = firstRequire.Start, firstRequire.End
   151  	}
   152  	return source.LineToRange(pm.Mapper, fh.URI(), start, end)
   153  }