golang.org/x/tools/gopls@v0.15.3/internal/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  	"golang.org/x/tools/gopls/internal/cache"
    15  	"golang.org/x/tools/gopls/internal/file"
    16  	"golang.org/x/tools/gopls/internal/protocol"
    17  	"golang.org/x/tools/internal/event"
    18  )
    19  
    20  func Diagnostics(ctx context.Context, snapshot *cache.Snapshot) (map[protocol.DocumentURI][]*cache.Diagnostic, error) {
    21  	ctx, done := event.Start(ctx, "work.Diagnostics", snapshot.Labels()...)
    22  	defer done()
    23  
    24  	reports := map[protocol.DocumentURI][]*cache.Diagnostic{}
    25  	uri := snapshot.View().GoWork()
    26  	if uri == "" {
    27  		return nil, nil
    28  	}
    29  	fh, err := snapshot.ReadFile(ctx, uri)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	reports[fh.URI()] = []*cache.Diagnostic{}
    34  	diagnostics, err := diagnoseOne(ctx, snapshot, fh)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  	for _, d := range diagnostics {
    39  		fh, err := snapshot.ReadFile(ctx, d.URI)
    40  		if err != nil {
    41  			return nil, err
    42  		}
    43  		reports[fh.URI()] = append(reports[fh.URI()], d)
    44  	}
    45  
    46  	return reports, nil
    47  }
    48  
    49  func diagnoseOne(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]*cache.Diagnostic, error) {
    50  	pw, err := snapshot.ParseWork(ctx, fh)
    51  	if err != nil {
    52  		if pw == nil || len(pw.ParseErrors) == 0 {
    53  			return nil, err
    54  		}
    55  		return pw.ParseErrors, nil
    56  	}
    57  
    58  	// Add diagnostic if a directory does not contain a module.
    59  	var diagnostics []*cache.Diagnostic
    60  	for _, use := range pw.File.Use {
    61  		rng, err := pw.Mapper.OffsetRange(use.Syntax.Start.Byte, use.Syntax.End.Byte)
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  
    66  		modfh, err := snapshot.ReadFile(ctx, modFileURI(pw, use))
    67  		if err != nil {
    68  			return nil, err
    69  		}
    70  		if _, err := modfh.Content(); err != nil && os.IsNotExist(err) {
    71  			diagnostics = append(diagnostics, &cache.Diagnostic{
    72  				URI:      fh.URI(),
    73  				Range:    rng,
    74  				Severity: protocol.SeverityError,
    75  				Source:   cache.WorkFileError,
    76  				Message:  fmt.Sprintf("directory %v does not contain a module", use.Path),
    77  			})
    78  		}
    79  	}
    80  	return diagnostics, nil
    81  }
    82  
    83  func modFileURI(pw *cache.ParsedWorkFile, use *modfile.Use) protocol.DocumentURI {
    84  	workdir := filepath.Dir(pw.URI.Path())
    85  
    86  	modroot := filepath.FromSlash(use.Path)
    87  	if !filepath.IsAbs(modroot) {
    88  		modroot = filepath.Join(workdir, modroot)
    89  	}
    90  
    91  	return protocol.URIFromPath(filepath.Join(modroot, "go.mod"))
    92  }