| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package work |
| |
| import ( |
| "context" |
| "fmt" |
| "os" |
| "path/filepath" |
| |
| "golang.org/x/mod/modfile" |
| "golang.org/x/tools/gopls/internal/file" |
| "golang.org/x/tools/gopls/internal/lsp/cache" |
| "golang.org/x/tools/gopls/internal/lsp/protocol" |
| "golang.org/x/tools/internal/event" |
| ) |
| |
| func Diagnose(ctx context.Context, snapshot *cache.Snapshot) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { |
| ctx, done := event.Start(ctx, "work.Diagnostics", snapshot.Labels()...) |
| defer done() |
| |
| reports := map[protocol.DocumentURI][]*cache.Diagnostic{} |
| uri := snapshot.WorkFile() |
| if uri == "" { |
| return nil, nil |
| } |
| fh, err := snapshot.ReadFile(ctx, uri) |
| if err != nil { |
| return nil, err |
| } |
| reports[fh.URI()] = []*cache.Diagnostic{} |
| diagnostics, err := diagnoseOne(ctx, snapshot, fh) |
| if err != nil { |
| return nil, err |
| } |
| for _, d := range diagnostics { |
| fh, err := snapshot.ReadFile(ctx, d.URI) |
| if err != nil { |
| return nil, err |
| } |
| reports[fh.URI()] = append(reports[fh.URI()], d) |
| } |
| |
| return reports, nil |
| } |
| |
| func diagnoseOne(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]*cache.Diagnostic, error) { |
| pw, err := snapshot.ParseWork(ctx, fh) |
| if err != nil { |
| if pw == nil || len(pw.ParseErrors) == 0 { |
| return nil, err |
| } |
| return pw.ParseErrors, nil |
| } |
| |
| // Add diagnostic if a directory does not contain a module. |
| var diagnostics []*cache.Diagnostic |
| for _, use := range pw.File.Use { |
| rng, err := pw.Mapper.OffsetRange(use.Syntax.Start.Byte, use.Syntax.End.Byte) |
| if err != nil { |
| return nil, err |
| } |
| |
| modfh, err := snapshot.ReadFile(ctx, modFileURI(pw, use)) |
| if err != nil { |
| return nil, err |
| } |
| if _, err := modfh.Content(); err != nil && os.IsNotExist(err) { |
| diagnostics = append(diagnostics, &cache.Diagnostic{ |
| URI: fh.URI(), |
| Range: rng, |
| Severity: protocol.SeverityError, |
| Source: cache.WorkFileError, |
| Message: fmt.Sprintf("directory %v does not contain a module", use.Path), |
| }) |
| } |
| } |
| return diagnostics, nil |
| } |
| |
| func modFileURI(pw *cache.ParsedWorkFile, use *modfile.Use) protocol.DocumentURI { |
| workdir := filepath.Dir(pw.URI.Path()) |
| |
| modroot := filepath.FromSlash(use.Path) |
| if !filepath.IsAbs(modroot) { |
| modroot = filepath.Join(workdir, modroot) |
| } |
| |
| return protocol.URIFromPath(filepath.Join(modroot, "go.mod")) |
| } |