github.com/v2fly/tools@v0.100.0/internal/lsp/mod/diagnostics.go (about)

     1  // Copyright 2019 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 provides core features related to go.mod file
     6  // handling for use by Go editors and tools.
     7  package mod
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  
    13  	"github.com/v2fly/tools/internal/event"
    14  	"github.com/v2fly/tools/internal/lsp/command"
    15  	"github.com/v2fly/tools/internal/lsp/debug/tag"
    16  	"github.com/v2fly/tools/internal/lsp/protocol"
    17  	"github.com/v2fly/tools/internal/lsp/source"
    18  )
    19  
    20  func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.VersionedFileIdentity][]*source.Diagnostic, error) {
    21  	ctx, done := event.Start(ctx, "mod.Diagnostics", tag.Snapshot.Of(snapshot.ID()))
    22  	defer done()
    23  
    24  	reports := map[source.VersionedFileIdentity][]*source.Diagnostic{}
    25  	for _, uri := range snapshot.ModFiles() {
    26  		fh, err := snapshot.GetVersionedFile(ctx, uri)
    27  		if err != nil {
    28  			return nil, err
    29  		}
    30  		reports[fh.VersionedFileIdentity()] = []*source.Diagnostic{}
    31  		diagnostics, err := DiagnosticsForMod(ctx, snapshot, fh)
    32  		if err != nil {
    33  			return nil, err
    34  		}
    35  		for _, d := range diagnostics {
    36  			fh, err := snapshot.GetVersionedFile(ctx, d.URI)
    37  			if err != nil {
    38  				return nil, err
    39  			}
    40  			reports[fh.VersionedFileIdentity()] = append(reports[fh.VersionedFileIdentity()], d)
    41  		}
    42  	}
    43  	return reports, nil
    44  }
    45  
    46  func DiagnosticsForMod(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]*source.Diagnostic, error) {
    47  	pm, err := snapshot.ParseMod(ctx, fh)
    48  	if err != nil {
    49  		if pm == nil || len(pm.ParseErrors) == 0 {
    50  			return nil, err
    51  		}
    52  		return pm.ParseErrors, nil
    53  	}
    54  
    55  	var diagnostics []*source.Diagnostic
    56  
    57  	// Add upgrade quick fixes for individual modules if we know about them.
    58  	upgrades := snapshot.View().ModuleUpgrades()
    59  	for _, req := range pm.File.Require {
    60  		ver, ok := upgrades[req.Mod.Path]
    61  		if !ok || req.Mod.Version == ver {
    62  			continue
    63  		}
    64  		rng, err := lineToRange(pm.Mapper, fh.URI(), req.Syntax.Start, req.Syntax.End)
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  		// Upgrade to the exact version we offer the user, not the most recent.
    69  		title := fmt.Sprintf("Upgrade to %v", ver)
    70  		cmd, err := command.NewUpgradeDependencyCommand(title, command.DependencyArgs{
    71  			URI:        protocol.URIFromSpanURI(fh.URI()),
    72  			AddRequire: false,
    73  			GoCmdArgs:  []string{req.Mod.Path + "@" + ver},
    74  		})
    75  		if err != nil {
    76  			return nil, err
    77  		}
    78  		diagnostics = append(diagnostics, &source.Diagnostic{
    79  			URI:            fh.URI(),
    80  			Range:          rng,
    81  			Severity:       protocol.SeverityInformation,
    82  			Source:         source.UpgradeNotification,
    83  			Message:        fmt.Sprintf("%v can be upgraded", req.Mod.Path),
    84  			SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)},
    85  		})
    86  	}
    87  
    88  	// Packages in the workspace can contribute diagnostics to go.mod files.
    89  	wspkgs, err := snapshot.WorkspacePackages(ctx)
    90  	if err != nil && !source.IsNonFatalGoModError(err) {
    91  		event.Error(ctx, "diagnosing go.mod", err)
    92  	}
    93  	if err == nil {
    94  		for _, pkg := range wspkgs {
    95  			pkgDiagnostics, err := snapshot.DiagnosePackage(ctx, pkg)
    96  			if err != nil {
    97  				return nil, err
    98  			}
    99  			diagnostics = append(diagnostics, pkgDiagnostics[fh.URI()]...)
   100  		}
   101  	}
   102  
   103  	tidied, err := snapshot.ModTidy(ctx, pm)
   104  	if err != nil && !source.IsNonFatalGoModError(err) {
   105  		event.Error(ctx, "diagnosing go.mod", err)
   106  	}
   107  	if err == nil {
   108  		for _, d := range tidied.Diagnostics {
   109  			if d.URI != fh.URI() {
   110  				continue
   111  			}
   112  			diagnostics = append(diagnostics, d)
   113  		}
   114  	}
   115  	return diagnostics, nil
   116  }