github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/work/diagnostics.go (about)

     1  // Copyright 2022 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 work
     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/event"
    15  	"github.com/powerman/golang-tools/internal/lsp/debug/tag"
    16  	"github.com/powerman/golang-tools/internal/lsp/protocol"
    17  	"github.com/powerman/golang-tools/internal/lsp/source"
    18  	"github.com/powerman/golang-tools/internal/span"
    19  )
    20  
    21  func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.VersionedFileIdentity][]*source.Diagnostic, error) {
    22  	ctx, done := event.Start(ctx, "work.Diagnostics", tag.Snapshot.Of(snapshot.ID()))
    23  	defer done()
    24  
    25  	reports := map[source.VersionedFileIdentity][]*source.Diagnostic{}
    26  	uri := snapshot.WorkFile()
    27  	if uri == "" {
    28  		return nil, nil
    29  	}
    30  	fh, err := snapshot.GetVersionedFile(ctx, uri)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  	reports[fh.VersionedFileIdentity()] = []*source.Diagnostic{}
    35  	diagnostics, err := DiagnosticsForWork(ctx, snapshot, fh)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	for _, d := range diagnostics {
    40  		fh, err := snapshot.GetVersionedFile(ctx, d.URI)
    41  		if err != nil {
    42  			return nil, err
    43  		}
    44  		reports[fh.VersionedFileIdentity()] = append(reports[fh.VersionedFileIdentity()], d)
    45  	}
    46  
    47  	return reports, nil
    48  }
    49  
    50  func DiagnosticsForWork(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]*source.Diagnostic, error) {
    51  	pw, err := snapshot.ParseWork(ctx, fh)
    52  	if err != nil {
    53  		if pw == nil || len(pw.ParseErrors) == 0 {
    54  			return nil, err
    55  		}
    56  		return pw.ParseErrors, nil
    57  	}
    58  
    59  	// Add diagnostic if a directory does not contain a module.
    60  	var diagnostics []*source.Diagnostic
    61  	for _, use := range pw.File.Use {
    62  		rng, err := source.LineToRange(pw.Mapper, fh.URI(), use.Syntax.Start, use.Syntax.End)
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  
    67  		modfh, err := snapshot.GetFile(ctx, modFileURI(pw, use))
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  		if _, err := modfh.Read(); err != nil && os.IsNotExist(err) {
    72  			diagnostics = append(diagnostics, &source.Diagnostic{
    73  				URI:      fh.URI(),
    74  				Range:    rng,
    75  				Severity: protocol.SeverityError,
    76  				Source:   source.UnknownError, // Do we need a new source for this?
    77  				Message:  fmt.Sprintf("directory %v does not contain a module", use.Path),
    78  			})
    79  		}
    80  	}
    81  	return diagnostics, nil
    82  }
    83  
    84  func modFileURI(pw *source.ParsedWorkFile, use *modfile.Use) span.URI {
    85  	workdir := filepath.Dir(pw.URI.Filename())
    86  
    87  	modroot := filepath.FromSlash(use.Path)
    88  	if !filepath.IsAbs(modroot) {
    89  		modroot = filepath.Join(workdir, modroot)
    90  	}
    91  
    92  	return span.URIFromPath(filepath.Join(modroot, "go.mod"))
    93  }