// 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 ( "context" "fmt" "go/ast" "go/token" "go/types" "sort" "strings" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" "golang.org/x/tools/internal/imports" "golang.org/x/tools/internal/lsp/diff" "golang.org/x/tools/internal/span" ) // FileIdentity uniquely identifies a file at a version from a FileSystem. type FileIdentity struct { URI span.URI Version string } func (identity FileIdentity) String() string { return fmt.Sprintf("%s%s", identity.URI, identity.Version) } // FileHandle represents a handle to a specific version of a single file from // a specific file system. type FileHandle interface { // FileSystem returns the file system this handle was acquired from. FileSystem() FileSystem // Identity returns the FileIdentity for the file. Identity() FileIdentity // Kind returns the FileKind for the file. Kind() FileKind // Read reads the contents of a file and returns it along with its hash value. // If the file is not available, returns a nil slice and an error. Read(ctx context.Context) ([]byte, string, error) } // FileSystem is the interface to something that provides file contents. type FileSystem interface { // GetFile returns a handle for the specified file. GetFile(uri span.URI) FileHandle } // FileKind describes the kind of the file in question. // It can be one of Go, mod, or sum. type FileKind int const ( Go = FileKind(iota) Mod Sum ) // TokenHandle represents a handle to the *token.File for a file. type TokenHandle interface { // File returns a file handle for which to get the *token.File. File() FileHandle // Token returns the *token.File for the file. Token(ctx context.Context) (*token.File, error) } // ParseGoHandle represents a handle to the AST for a file. type ParseGoHandle interface { // File returns a file handle for which to get the AST. File() FileHandle // Mode returns the parse mode of this handle. Mode() ParseMode // Parse returns the parsed AST for the file. // If the file is not available, returns nil and an error. Parse(ctx context.Context) (*ast.File, error) // Cached returns the AST for this handle, if it has already been stored. Cached(ctx context.Context) (*ast.File, error) } // 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 public symbols are needed, but things like // private symbols and function bodies are not. // This mode is used for things where a package is being consumed only as a // dependency. ParseExported // ParseFull specifies the full AST is needed. // This is used for files of direct interest where the entire contents must // be considered. ParseFull ) // CheckPackageHandle represents a handle to a specific version of a package. // It is uniquely defined by the file handles that make up the package. type CheckPackageHandle interface { // ParseGoHandle returns a ParseGoHandle for which to get the package. Files() []ParseGoHandle // Config is the *packages.Config that the package metadata was loaded with. Config() *packages.Config // Check returns the type-checked Package for the CheckPackageHandle. Check(ctx context.Context) (Package, error) // Cached returns the Package for the CheckPackageHandle if it has already been stored. Cached(ctx context.Context) (Package, error) } // Cache abstracts the core logic of dealing with the environment from the // higher level logic that processes the information to produce results. // The cache provides access to files and their contents, so the source // package does not directly access the file system. // A single cache is intended to be process wide, and is the primary point of // sharing between all consumers. // A cache may have many active sessions at any given time. type Cache interface { // A FileSystem that reads file contents from external storage. FileSystem // NewSession creates a new Session manager and returns it. NewSession(ctx context.Context) Session // FileSet returns the shared fileset used by all files in the system. FileSet() *token.FileSet // TokenHandle returns a TokenHandle for the given file handle. TokenHandle(fh FileHandle) TokenHandle // ParseGoHandle returns a ParseGoHandle for the given file handle. ParseGoHandle(fh FileHandle, mode ParseMode) ParseGoHandle } // Session represents a single connection from a client. // This is the level at which things like open files are maintained on behalf // of the client. // A session may have many active views at any given time. type Session interface { // NewView creates a new View and returns it. NewView(ctx context.Context, name string, folder span.URI) View // Cache returns the cache that created this session. Cache() Cache // View returns a view with a mathing name, if the session has one. View(name string) View // ViewOf returns a view corresponding to the given URI. ViewOf(uri span.URI) View // Views returns the set of active views built by this session. Views() []View // Shutdown the session and all views it has created. Shutdown(ctx context.Context) // A FileSystem prefers the contents from overlays, and falls back to the // content from the underlying cache if no overlay is present. FileSystem // DidOpen is invoked each time a file is opened in the editor. DidOpen(ctx context.Context, uri span.URI, kind FileKind, text []byte) // DidSave is invoked each time an open file is saved in the editor. DidSave(uri span.URI) // DidClose is invoked each time an open file is closed in the editor. DidClose(uri span.URI) // IsOpen can be called to check if the editor has a file currently open. IsOpen(uri span.URI) bool // Called to set the effective contents of a file from this session. SetOverlay(uri span.URI, data []byte) (wasFirstChange bool) } // View represents a single workspace. // This is the level at which we maintain configuration like working directory // and build tags. type View interface { // Session returns the session that created this view. Session() Session // Name returns the name this view was constructed with. Name() string // Folder returns the root folder for this view. Folder() span.URI // BuiltinPackage returns the ast for the special "builtin" package. BuiltinPackage() *ast.Package // GetFile returns the file object for a given uri. GetFile(ctx context.Context, uri span.URI) (File, error) // Called to set the effective contents of a file from this view. SetContent(ctx context.Context, uri span.URI, content []byte) (wasFirstChange bool, err error) // BackgroundContext returns a context used for all background processing // on behalf of this view. BackgroundContext() context.Context // Env returns the current set of environment overrides on this view. Env() []string // SetEnv is used to adjust the environment applied to the view. SetEnv([]string) // SetBuildFlags is used to adjust the build flags applied to the view. SetBuildFlags([]string) // Shutdown closes this view, and detaches it from it's session. Shutdown(ctx context.Context) // Ignore returns true if this file should be ignored by this view. Ignore(span.URI) bool Config(ctx context.Context) *packages.Config // RunProcessEnvFunc runs fn with the process env for this view inserted into opts. // Note: the process env contains cached module and filesystem state. RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error, opts *imports.Options) error } // File represents a source file of any type. type File interface { URI() span.URI View() View Handle(ctx context.Context) FileHandle FileSet() *token.FileSet GetToken(ctx context.Context) (*token.File, error) } // GoFile represents a Go source file that has been type-checked. type GoFile interface { File // GetAST returns the AST for the file, at or above the given mode. GetAST(ctx context.Context, mode ParseMode) (*ast.File, error) // GetCachedPackage returns the cached package for the file, if any. GetCachedPackage(ctx context.Context) (Package, error) // GetPackage returns the CheckPackageHandle for the package that this file belongs to. GetCheckPackageHandle(ctx context.Context) (CheckPackageHandle, error) // GetPackages returns the CheckPackageHandles of the packages that this file belongs to. GetCheckPackageHandles(ctx context.Context) ([]CheckPackageHandle, error) // GetPackage returns the CheckPackageHandle for the package that this file belongs to. GetPackage(ctx context.Context) (Package, error) // GetPackages returns the CheckPackageHandles of the packages that this file belongs to. GetPackages(ctx context.Context) ([]Package, error) // GetActiveReverseDeps returns the active files belonging to the reverse // dependencies of this file's package. GetActiveReverseDeps(ctx context.Context) []GoFile } type ModFile interface { File } type SumFile interface { File } // Package represents a Go package that has been type-checked. It maintains // only the relevant fields of a *go/packages.Package. type Package interface { ID() string PkgPath() string GetHandles() []ParseGoHandle GetSyntax(context.Context) []*ast.File GetErrors() []packages.Error GetTypes() *types.Package GetTypesInfo() *types.Info GetTypesSizes() types.Sizes IsIllTyped() bool GetDiagnostics() []Diagnostic SetDiagnostics(diags []Diagnostic) // GetImport returns the CheckPackageHandle for a package imported by this package. GetImport(ctx context.Context, pkgPath string) (Package, error) // GetActionGraph returns the action graph for the given package. GetActionGraph(ctx context.Context, a *analysis.Analyzer) (*Action, error) } // TextEdit represents a change to a section of a document. // The text within the specified span should be replaced by the supplied new text. type TextEdit struct { Span span.Span NewText string } // DiffToEdits converts from a sequence of diff operations to a sequence of // source.TextEdit func DiffToEdits(uri span.URI, ops []*diff.Op) []TextEdit { edits := make([]TextEdit, 0, len(ops)) for _, op := range ops { s := span.New(uri, span.NewPoint(op.I1+1, 1, 0), span.NewPoint(op.I2+1, 1, 0)) switch op.Kind { case diff.Delete: // Delete: unformatted[i1:i2] is deleted. edits = append(edits, TextEdit{Span: s}) case diff.Insert: // Insert: formatted[j1:j2] is inserted at unformatted[i1:i1]. if content := strings.Join(op.Content, ""); content != "" { edits = append(edits, TextEdit{Span: s, NewText: content}) } } } return edits } func EditsToDiff(edits []TextEdit) []*diff.Op { iToJ := 0 ops := make([]*diff.Op, len(edits)) for i, edit := range edits { i1 := edit.Span.Start().Line() - 1 i2 := edit.Span.End().Line() - 1 kind := diff.Insert if edit.NewText == "" { kind = diff.Delete } ops[i] = &diff.Op{ Kind: kind, Content: diff.SplitLines(edit.NewText), I1: i1, I2: i2, J1: i1 + iToJ, } if kind == diff.Insert { iToJ += len(ops[i].Content) } else { iToJ -= i2 - i1 } } return ops } func sortTextEdits(d []TextEdit) { // Use a stable sort to maintain the order of edits inserted at the same position. sort.SliceStable(d, func(i int, j int) bool { return span.Compare(d[i].Span, d[j].Span) < 0 }) }