blob: 640a32e7110b920ed61a175b9d31f836fc52bcba [file] [log] [blame]
// Copyright 2018 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 source
import (
"bytes"
"context"
"crypto/sha256"
"errors"
"fmt"
"go/ast"
"go/scanner"
"go/token"
"go/types"
"io"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/types/objectpath"
"golang.org/x/tools/gopls/internal/govulncheck"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/safetoken"
"golang.org/x/tools/gopls/internal/lsp/source/methodsets"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/event/label"
"golang.org/x/tools/internal/event/tag"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/internal/packagesinternal"
)
// A GlobalSnapshotID uniquely identifies a snapshot within this process and
// increases monotonically with snapshot creation time.
//
// We use a distinct integral type for global IDs to help enforce correct
// usage.
type GlobalSnapshotID uint64
// Snapshot represents the current state for the given view.
type Snapshot interface {
// SequenceID is the sequence id of this snapshot within its containing
// view.
//
// Relative to their view sequence ids are monotonically increasing, but this
// does not hold globally: when new views are created their initial snapshot
// has sequence ID 0. For operations that span multiple views, use global
// IDs.
SequenceID() uint64
// GlobalID is a globally unique identifier for this snapshot. Global IDs are
// monotonic: subsequent snapshots will have higher global ID, though
// subsequent snapshots in a view may not have adjacent global IDs.
GlobalID() GlobalSnapshotID
// View returns the View associated with this snapshot.
View() View
// BackgroundContext returns a context used for all background processing
// on behalf of this snapshot.
BackgroundContext() context.Context
// Fileset returns the Fileset used to parse all the Go files in this snapshot.
//
// If the files are known to belong to a specific Package, use
// Package.FileSet instead. (We plan to eliminate the
// Snapshot's cache of parsed files, and thus the need for a
// snapshot-wide FileSet.)
FileSet() *token.FileSet
// ValidBuildConfiguration returns true if there is some error in the
// user's workspace. In particular, if they are both outside of a module
// and their GOPATH.
ValidBuildConfiguration() bool
// FindFile returns the FileHandle for the given URI, if it is already
// in the given snapshot.
FindFile(uri span.URI) FileHandle
// GetFile returns the FileHandle for a given URI, initializing it if it is
// not already part of the snapshot.
GetFile(ctx context.Context, uri span.URI) (FileHandle, error)
// AwaitInitialized waits until the snapshot's view is initialized.
AwaitInitialized(ctx context.Context)
// IsOpen returns whether the editor currently has a file open.
IsOpen(uri span.URI) bool
// IgnoredFile reports if a file would be ignored by a `go list` of the whole
// workspace.
IgnoredFile(uri span.URI) bool
// Templates returns the .tmpl files
Templates() map[span.URI]FileHandle
// ParseGo returns the parsed AST for the file.
// If the file is not available, returns nil and an error.
// Position information is added to FileSet().
ParseGo(ctx context.Context, fh FileHandle, mode ParseMode) (*ParsedGoFile, error)
// Analyze runs the specified analyzers on the given package at this snapshot.
Analyze(ctx context.Context, id PackageID, analyzers []*Analyzer) ([]*Diagnostic, error)
// RunGoCommandPiped runs the given `go` command, writing its output
// to stdout and stderr. Verb, Args, and WorkingDir must be specified.
//
// RunGoCommandPiped runs the command serially using gocommand.RunPiped,
// enforcing that this command executes exclusively to other commands on the
// server.
RunGoCommandPiped(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error
// RunGoCommandDirect runs the given `go` command. Verb, Args, and
// WorkingDir must be specified.
RunGoCommandDirect(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error)
// RunGoCommands runs a series of `go` commands that updates the go.mod
// and go.sum file for wd, and returns their updated contents.
RunGoCommands(ctx context.Context, allowNetwork bool, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) (bool, []byte, []byte, error)
// RunProcessEnvFunc runs fn with the process env for this snapshot's view.
// Note: the process env contains cached module and filesystem state.
RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error
// ModFiles are the go.mod files enclosed in the snapshot's view and known
// to the snapshot.
ModFiles() []span.URI
// ParseMod is used to parse go.mod files.
ParseMod(ctx context.Context, fh FileHandle) (*ParsedModule, error)
// ModWhy returns the results of `go mod why` for the module specified by
// the given go.mod file.
ModWhy(ctx context.Context, fh FileHandle) (map[string]string, error)
// ModTidy returns the results of `go mod tidy` for the module specified by
// the given go.mod file.
ModTidy(ctx context.Context, pm *ParsedModule) (*TidiedModule, error)
// ModVuln returns import vulnerability analysis for the given go.mod URI.
// Concurrent requests are combined into a single command.
ModVuln(ctx context.Context, modURI span.URI) (*govulncheck.Result, error)
// GoModForFile returns the URI of the go.mod file for the given URI.
GoModForFile(uri span.URI) span.URI
// WorkFile, if non-empty, is the go.work file for the workspace.
WorkFile() span.URI
// ParseWork is used to parse go.work files.
ParseWork(ctx context.Context, fh FileHandle) (*ParsedWorkFile, error)
// BuiltinFile returns information about the special builtin package.
BuiltinFile(ctx context.Context) (*ParsedGoFile, error)
// IsBuiltin reports whether uri is part of the builtin package.
IsBuiltin(ctx context.Context, uri span.URI) bool
// ReverseDependencies returns a new mapping whose entries are
// the ID and Metadata of each package in the workspace that
// directly or transitively depend on the package denoted by id,
// excluding id itself.
ReverseDependencies(ctx context.Context, id PackageID, transitive bool) (map[PackageID]*Metadata, error)
// CachedImportPaths returns all the imported packages loaded in this
// snapshot, indexed by their package path (not import path, despite the name)
// and checked in TypecheckWorkspace mode.
//
// To reduce latency, it does not wait for type-checking to complete.
// It is intended for use only in completions.
CachedImportPaths(ctx context.Context) (map[PackagePath]Package, error)
// ActiveMetadata returns a new, unordered slice containing
// metadata for all packages considered 'active' in the workspace.
//
// In normal memory mode, this is all workspace packages. In degraded memory
// mode, this is just the reverse transitive closure of open packages.
ActiveMetadata(ctx context.Context) ([]*Metadata, error)
// AllMetadata returns a new unordered array of metadata for all packages in the workspace.
AllMetadata(ctx context.Context) ([]*Metadata, error)
// Symbols returns all symbols in the snapshot.
Symbols(ctx context.Context) map[span.URI][]Symbol
// Metadata returns the metadata for the specified package,
// or nil if it was not found.
Metadata(id PackageID) *Metadata
// MetadataForFile returns a new slice containing metadata for each
// package containing the Go file identified by uri, ordered by the
// number of CompiledGoFiles (i.e. "narrowest" to "widest" package).
// The result may include tests and intermediate test variants of
// importable packages.
// It returns an error if the context was cancelled.
MetadataForFile(ctx context.Context, uri span.URI) ([]*Metadata, error)
// TypeCheck parses and type-checks the specified packages,
// and returns them in the same order as the ids.
TypeCheck(ctx context.Context, mode TypecheckMode, ids ...PackageID) ([]Package, error)
// GetCriticalError returns any critical errors in the workspace.
//
// A nil result may mean success, or context cancellation.
GetCriticalError(ctx context.Context) *CriticalError
}
// SnapshotLabels returns a new slice of labels that should be used for events
// related to a snapshot.
func SnapshotLabels(snapshot Snapshot) []label.Label {
return []label.Label{tag.Snapshot.Of(snapshot.SequenceID()), tag.Directory.Of(snapshot.View().Folder())}
}
// PackageForFile is a convenience function that selects a package to
// which this file belongs (narrowest or widest), type-checks it in
// the requested mode (full or workspace), and returns it, along with
// the parse tree of that file.
//
// Type-checking is expensive. Call snapshot.ParseGo if all you need
// is a parse tree, or snapshot.MetadataForFile if you only need metadata.
func PackageForFile(ctx context.Context, snapshot Snapshot, uri span.URI, mode TypecheckMode, pkgSel PackageSelector) (Package, *ParsedGoFile, error) {
metas, err := snapshot.MetadataForFile(ctx, uri)
if err != nil {
return nil, nil, err
}
if len(metas) == 0 {
return nil, nil, fmt.Errorf("no package metadata for file %s", uri)
}
switch pkgSel {
case NarrowestPackage:
metas = metas[:1]
case WidestPackage:
metas = metas[len(metas)-1:]
}
pkgs, err := snapshot.TypeCheck(ctx, mode, metas[0].ID)
if err != nil {
return nil, nil, err
}
pkg := pkgs[0]
pgf, err := pkg.File(uri)
if err != nil {
return nil, nil, err // "can't happen"
}
return pkg, pgf, err
}
// PackageSelector sets how a package is selected out from a set of packages
// containing a given file.
type PackageSelector int
const (
// NarrowestPackage picks the "narrowest" package for a given file.
// By "narrowest" package, we mean the package with the fewest number of
// files that includes the given file. This solves the problem of test
// variants, as the test will have more files than the non-test package.
NarrowestPackage PackageSelector = iota
// WidestPackage returns the Package containing the most files.
// This is useful for something like diagnostics, where we'd prefer to
// offer diagnostics for as many files as possible.
WidestPackage
)
// InvocationFlags represents the settings of a particular go command invocation.
// It is a mode, plus a set of flag bits.
type InvocationFlags int
const (
// Normal is appropriate for commands that might be run by a user and don't
// deliberately modify go.mod files, e.g. `go test`.
Normal InvocationFlags = iota
// WriteTemporaryModFile is for commands that need information from a
// modified version of the user's go.mod file, e.g. `go mod tidy` used to
// generate diagnostics.
WriteTemporaryModFile
// LoadWorkspace is for packages.Load, and other operations that should
// consider the whole workspace at once.
LoadWorkspace
// AllowNetwork is a flag bit that indicates the invocation should be
// allowed to access the network.
AllowNetwork InvocationFlags = 1 << 10
)
func (m InvocationFlags) Mode() InvocationFlags {
return m & (AllowNetwork - 1)
}
func (m InvocationFlags) AllowNetwork() bool {
return m&AllowNetwork != 0
}
// View represents a single workspace.
// This is the level at which we maintain configuration like working directory
// and build tags.
type View interface {
// Name returns the name this view was constructed with.
Name() string
// Folder returns the folder with which this view was created.
Folder() span.URI
// Options returns a copy of the Options for this view.
Options() *Options
// Snapshot returns the current snapshot for the view, and a
// release function that must be called when the Snapshot is
// no longer needed.
//
// If the view is shut down, the resulting error will be non-nil, and the
// release function need not be called.
Snapshot() (Snapshot, func(), error)
// IsGoPrivatePath reports whether target is a private import path, as identified
// by the GOPRIVATE environment variable.
IsGoPrivatePath(path string) bool
// ModuleUpgrades returns known module upgrades for the dependencies of
// modfile.
ModuleUpgrades(modfile span.URI) map[string]string
// RegisterModuleUpgrades registers that upgrades exist for the given modules
// required by modfile.
RegisterModuleUpgrades(modfile span.URI, upgrades map[string]string)
// ClearModuleUpgrades clears all upgrades for the modules in modfile.
ClearModuleUpgrades(modfile span.URI)
// Vulnerabilites returns known vulnerabilities for the given modfile.
// TODO(suzmue): replace command.Vuln with a different type, maybe
// https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck/govulnchecklib#Summary?
Vulnerabilities(modfile ...span.URI) map[span.URI]*govulncheck.Result
// SetVulnerabilities resets the list of vulnerabilites that exists for the given modules
// required by modfile.
SetVulnerabilities(modfile span.URI, vulncheckResult *govulncheck.Result)
// FileKind returns the type of a file.
//
// We can't reliably deduce the kind from the file name alone,
// as some editors can be told to interpret a buffer as
// language different from the file name heuristic, e.g. that
// an .html file actually contains Go "html/template" syntax,
// or even that a .go file contains Python.
FileKind(FileHandle) FileKind
// GoVersion returns the configured Go version for this view.
GoVersion() int
// GoVersionString returns the go version string configured for this view.
// Unlike [GoVersion], this encodes the minor version and commit hash information.
GoVersionString() string
}
// A FileSource maps uris to FileHandles. This abstraction exists both for
// testability, and so that algorithms can be run equally on session and
// snapshot files.
type FileSource interface {
// GetFile returns the FileHandle for a given URI.
GetFile(ctx context.Context, uri span.URI) (FileHandle, error)
}
// A ParsedGoFile contains the results of parsing a Go file.
type ParsedGoFile struct {
URI span.URI
Mode ParseMode
File *ast.File
Tok *token.File
// Source code used to build the AST. It may be different from the
// actual content of the file if we have fixed the AST.
Src []byte
Fixed bool
Mapper *protocol.Mapper // may map fixed Src, not file content
ParseErr scanner.ErrorList
}
// -- go/token domain convenience helpers --
// PositionPos returns the token.Pos of protocol position p within the file.
func (pgf *ParsedGoFile) PositionPos(p protocol.Position) (token.Pos, error) {
offset, err := pgf.Mapper.PositionOffset(p)
if err != nil {
return token.NoPos, err
}
return safetoken.Pos(pgf.Tok, offset)
}
// PosRange returns a protocol Range for the token.Pos interval in this file.
func (pgf *ParsedGoFile) PosRange(start, end token.Pos) (protocol.Range, error) {
return pgf.Mapper.PosRange(pgf.Tok, start, end)
}
// PosLocation returns a protocol Location for the token.Pos interval in this file.
func (pgf *ParsedGoFile) PosLocation(start, end token.Pos) (protocol.Location, error) {
return pgf.Mapper.PosLocation(pgf.Tok, start, end)
}
// NodeRange returns a protocol Range for the ast.Node interval in this file.
func (pgf *ParsedGoFile) NodeRange(node ast.Node) (protocol.Range, error) {
return pgf.Mapper.NodeRange(pgf.Tok, node)
}
// PosMappedRange returns a MappedRange for the token.Pos interval in this file.
// A MappedRange can be converted to any other form.
func (pgf *ParsedGoFile) PosMappedRange(startPos, endPos token.Pos) (protocol.MappedRange, error) {
start, end, err := safetoken.Offsets(pgf.Tok, startPos, endPos)
if err != nil {
return protocol.MappedRange{}, nil
}
return pgf.Mapper.OffsetMappedRange(start, end)
}
// RangeToSpanRange parses a protocol Range back into the go/token domain.
func (pgf *ParsedGoFile) RangeToTokenRange(r protocol.Range) (safetoken.Range, error) {
start, end, err := pgf.Mapper.RangeOffsets(r)
if err != nil {
return safetoken.Range{}, err
}
return safetoken.NewRange(pgf.Tok, pgf.Tok.Pos(start), pgf.Tok.Pos(end)), nil
}
// A ParsedModule contains the results of parsing a go.mod file.
type ParsedModule struct {
URI span.URI
File *modfile.File
Mapper *protocol.Mapper
ParseErrors []*Diagnostic
}
// A ParsedWorkFile contains the results of parsing a go.work file.
type ParsedWorkFile struct {
URI span.URI
File *modfile.WorkFile
Mapper *protocol.Mapper
ParseErrors []*Diagnostic
}
// A TidiedModule contains the results of running `go mod tidy` on a module.
type TidiedModule struct {
// Diagnostics representing changes made by `go mod tidy`.
Diagnostics []*Diagnostic
// The bytes of the go.mod file after it was tidied.
TidiedContent []byte
}
// Metadata represents package metadata retrieved from go/packages.
type Metadata struct {
ID PackageID
PkgPath PackagePath
Name PackageName
GoFiles []span.URI
CompiledGoFiles []span.URI
ForTest PackagePath // package path under test, or ""
TypesSizes types.Sizes
Errors []packages.Error
DepsByImpPath map[ImportPath]PackageID // may contain dups; empty ID => missing
DepsByPkgPath map[PackagePath]PackageID // values are unique and non-empty
Module *packages.Module
DepsErrors []*packagesinternal.PackageError
// Config is the *packages.Config associated with the loaded package.
Config *packages.Config
}
// IsIntermediateTestVariant reports whether the given package is an
// intermediate test variant, e.g. "net/http [net/url.test]".
//
// Such test variants arise when an x_test package (in this case net/url_test)
// imports a package (in this case net/http) that itself imports the the
// non-x_test package (in this case net/url).
//
// This is done so that the forward transitive closure of net/url_test has
// only one package for the "net/url" import.
// The intermediate test variant exists to hold the test variant import:
//
// net/url_test [net/url.test]
//
// | "net/http" -> net/http [net/url.test]
// | "net/url" -> net/url [net/url.test]
// | ...
//
// net/http [net/url.test]
//
// | "net/url" -> net/url [net/url.test]
// | ...
//
// This restriction propagates throughout the import graph of net/http: for
// every package imported by net/http that imports net/url, there must be an
// intermediate test variant that instead imports "net/url [net/url.test]".
//
// As one can see from the example of net/url and net/http, intermediate test
// variants can result in many additional packages that are essentially (but
// not quite) identical. For this reason, we filter these variants wherever
// possible.
func (m *Metadata) IsIntermediateTestVariant() bool {
return m.ForTest != "" && m.ForTest != m.PkgPath && m.ForTest+"_test" != m.PkgPath
}
// RemoveIntermediateTestVariants removes intermediate test variants, modifying the array.
func RemoveIntermediateTestVariants(metas []*Metadata) []*Metadata {
res := metas[:0]
for _, m := range metas {
if !m.IsIntermediateTestVariant() {
res = append(res, m)
}
}
return res
}
var ErrViewExists = errors.New("view already exists for session")
// FileModification represents a modification to a file.
type FileModification struct {
URI span.URI
Action FileAction
// OnDisk is true if a watched file is changed on disk.
// If true, Version will be -1 and Text will be nil.
OnDisk bool
// Version will be -1 and Text will be nil when they are not supplied,
// specifically on textDocument/didClose and for on-disk changes.
Version int32
Text []byte
// LanguageID is only sent from the language client on textDocument/didOpen.
LanguageID string
}
type FileAction int
const (
UnknownFileAction = FileAction(iota)
Open
Change
Close
Save
Create
Delete
InvalidateMetadata
)
func (a FileAction) String() string {
switch a {
case Open:
return "Open"
case Change:
return "Change"
case Close:
return "Close"
case Save:
return "Save"
case Create:
return "Create"
case Delete:
return "Delete"
case InvalidateMetadata:
return "InvalidateMetadata"
default:
return "Unknown"
}
}
var ErrTmpModfileUnsupported = errors.New("-modfile is unsupported for this Go version")
var ErrNoModOnDisk = errors.New("go.mod file is not on disk")
func IsNonFatalGoModError(err error) bool {
return err == ErrTmpModfileUnsupported || err == ErrNoModOnDisk
}
// ParseMode controls the content of the AST produced when parsing a source file.
type ParseMode int
const (
// ParseHeader specifies that the main package declaration and imports are needed.
// This is the mode used when attempting to examine the package graph structure.
ParseHeader ParseMode = iota
// ParseExported specifies that the package is used only as a dependency,
// and only its exported declarations are needed. More may be included if
// necessary to avoid type errors.
ParseExported
// ParseFull specifies the full AST is needed.
// This is used for files of direct interest where the entire contents must
// be considered.
ParseFull
)
// AllParseModes contains all possible values of ParseMode.
// It is used for cache invalidation on a file content change.
var AllParseModes = []ParseMode{ParseHeader, ParseExported, ParseFull}
// TypecheckMode controls what kind of parsing should be done (see ParseMode)
// while type checking a package.
type TypecheckMode int
const (
// TypecheckFull means to use ParseFull.
TypecheckFull TypecheckMode = iota
// TypecheckWorkspace means to use ParseFull for workspace packages, and
// ParseExported for others.
TypecheckWorkspace
)
// A FileHandle is an interface to files tracked by the LSP session, which may
// be either files read from disk, or open in the editor session (overlays).
type FileHandle interface {
// URI is the URI for this file handle.
// TODO(rfindley): this is not actually well-defined. In some cases, there
// may be more than one URI that resolve to the same FileHandle. Which one is
// this?
URI() span.URI
// FileIdentity returns a FileIdentity for the file, even if there was an
// error reading it.
FileIdentity() FileIdentity
// Saved reports whether the file has the same content on disk.
// For on-disk files, this is trivially true.
Saved() bool
// Version returns the file version, as defined by the LSP client.
// For on-disk file handles, Version returns 0.
Version() int32
// Read reads the contents of a file.
// If the file is not available, returns a nil slice and an error.
Read() ([]byte, error)
}
// A Hash is a cryptographic digest of the contents of a file.
// (Although at 32B it is larger than a 16B string header, it is smaller
// and has better locality than the string header + 64B of hex digits.)
type Hash [sha256.Size]byte
// HashOf returns the hash of some data.
func HashOf(data []byte) Hash {
return Hash(sha256.Sum256(data))
}
// Hashf returns the hash of a printf-formatted string.
func Hashf(format string, args ...interface{}) Hash {
// Although this looks alloc-heavy, it is faster than using
// Fprintf on sha256.New() because the allocations don't escape.
return HashOf([]byte(fmt.Sprintf(format, args...)))
}
// String returns the digest as a string of hex digits.
func (h Hash) String() string {
return fmt.Sprintf("%64x", [sha256.Size]byte(h))
}
// Less returns true if the given hash is less than the other.
func (h Hash) Less(other Hash) bool {
return bytes.Compare(h[:], other[:]) < 0
}
// XORWith updates *h to *h XOR h2.
func (h *Hash) XORWith(h2 Hash) {
// Small enough that we don't need crypto/subtle.XORBytes.
for i := range h {
h[i] ^= h2[i]
}
}
// FileIdentity uniquely identifies a file at a version from a FileSystem.
type FileIdentity struct {
URI span.URI
Hash Hash // digest of file contents
}
func (id FileIdentity) String() string {
return fmt.Sprintf("%s%s", id.URI, id.Hash)
}
// FileKind describes the kind of the file in question.
// It can be one of Go,mod, Sum, or Tmpl.
type FileKind int
const (
// UnknownKind is a file type we don't know about.
UnknownKind = FileKind(iota)
// Go is a normal go source file.
Go
// Mod is a go.mod file.
Mod
// Sum is a go.sum file.
Sum
// Tmpl is a template file.
Tmpl
// Work is a go.work file.
Work
)
// Analyzer represents a go/analysis analyzer with some boolean properties
// that let the user know how to use the analyzer.
type Analyzer struct {
Analyzer *analysis.Analyzer
// Enabled reports whether the analyzer is enabled. This value can be
// configured per-analysis in user settings. For staticcheck analyzers,
// the value of the Staticcheck setting overrides this field.
//
// Most clients should use the IsEnabled method.
Enabled bool
// Fix is the name of the suggested fix name used to invoke the suggested
// fixes for the analyzer. It is non-empty if we expect this analyzer to
// provide its fix separately from its diagnostics. That is, we should apply
// the analyzer's suggested fixes through a Command, not a TextEdit.
Fix string
// ActionKind is the kind of code action this analyzer produces. If
// unspecified the type defaults to quickfix.
ActionKind []protocol.CodeActionKind
// Severity is the severity set for diagnostics reported by this
// analyzer. If left unset it defaults to Warning.
Severity protocol.DiagnosticSeverity
}
func (a *Analyzer) String() string { return a.Analyzer.String() }
// IsEnabled reports whether this analyzer is enabled by the given options.
func (a Analyzer) IsEnabled(options *Options) bool {
// Staticcheck analyzers can only be enabled when staticcheck is on.
if _, ok := options.StaticcheckAnalyzers[a.Analyzer.Name]; ok {
if !options.Staticcheck {
return false
}
}
if enabled, ok := options.Analyses[a.Analyzer.Name]; ok {
return enabled
}
return a.Enabled
}
// Declare explicit types for package paths, names, and IDs to ensure that we
// never use an ID where a path belongs, and vice versa. If we confused these,
// it would result in confusing errors because package IDs often look like
// package paths.
type (
PackageID string // go list's unique identifier for a package (e.g. "vendor/example.com/foo [vendor/example.com/bar.test]")
PackagePath string // name used to prefix linker symbols (e.g. "vendor/example.com/foo")
PackageName string // identifier in 'package' declaration (e.g. "foo")
ImportPath string // path that appears in an import declaration (e.g. "example.com/foo")
)
// Package represents a Go package that has been parsed and type-checked.
// It maintains only the relevant fields of a *go/packages.Package.
type Package interface {
// Metadata:
ID() PackageID
Name() PackageName
PkgPath() PackagePath
GetTypesSizes() types.Sizes
ForTest() string
Version() *module.Version
// Results of parsing:
FileSet() *token.FileSet
ParseMode() ParseMode
CompiledGoFiles() []*ParsedGoFile // (borrowed)
File(uri span.URI) (*ParsedGoFile, error)
GetSyntax() []*ast.File // (borrowed)
HasListOrParseErrors() bool
// Results of type checking:
GetTypes() *types.Package
GetTypesInfo() *types.Info
DirectDep(path PackagePath) (Package, error)
ResolveImportPath(path ImportPath) (Package, error)
Imports() []Package // new slice of all direct dependencies, unordered
HasTypeErrors() bool
DiagnosticsForFile(uri span.URI) []*Diagnostic // new array of list/parse/type errors
ReferencesTo(PackagePath, objectpath.Path) []protocol.Location // new sorted array of xrefs
MethodSetsIndex() *methodsets.Index
}
// A CriticalError is a workspace-wide error that generally prevents gopls from
// functioning correctly. In the presence of critical errors, other diagnostics
// in the workspace may not make sense.
type CriticalError struct {
// MainError is the primary error. Must be non-nil.
MainError error
// Diagnostics contains any supplemental (structured) diagnostics.
Diagnostics []*Diagnostic
}
// An Diagnostic corresponds to an LSP Diagnostic.
// https://microsoft.github.io/language-server-protocol/specification#diagnostic
type Diagnostic struct {
URI span.URI
Range protocol.Range
Severity protocol.DiagnosticSeverity
Code string
CodeHref string
// Source is a human-readable description of the source of the error.
// Diagnostics generated by an analysis.Analyzer set it to Analyzer.Name.
Source DiagnosticSource
Message string
Tags []protocol.DiagnosticTag
Related []RelatedInformation
// Fields below are used internally to generate quick fixes. They aren't
// part of the LSP spec and don't leave the server.
SuggestedFixes []SuggestedFix
}
func (d *Diagnostic) String() string {
return fmt.Sprintf("%v: %s", d.Range, d.Message)
}
type DiagnosticSource string
const (
UnknownError DiagnosticSource = "<Unknown source>"
ListError DiagnosticSource = "go list"
ParseError DiagnosticSource = "syntax"
TypeError DiagnosticSource = "compiler"
ModTidyError DiagnosticSource = "go mod tidy"
OptimizationDetailsError DiagnosticSource = "optimizer details"
UpgradeNotification DiagnosticSource = "upgrade available"
Vulncheck DiagnosticSource = "govulncheck"
TemplateError DiagnosticSource = "template"
WorkFileError DiagnosticSource = "go.work file"
)
func AnalyzerErrorKind(name string) DiagnosticSource {
return DiagnosticSource(name)
}