From 4f9510c6a12d5544b14ed8164f6dbbb3dfeb2a9f Mon Sep 17 00:00:00 2001 From: Ian Cottrell Date: Sat, 4 May 2019 00:04:18 -0400 Subject: [PATCH] internal/lsp: prepare for non go files This abstracts out the concrete file type so that we can support non go files. Change-Id: I7447daa2ce076ec2867de9e59a0dedfe1a0553f5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/175217 Run-TryBot: Ian Cottrell TryBot-Result: Gobot Gobot Reviewed-by: Rebecca Stambler --- internal/lsp/cache/check.go | 48 ++++++++++++------ internal/lsp/cache/file.go | 72 +++++++++++++++++---------- internal/lsp/cache/parse.go | 4 +- internal/lsp/cache/view.go | 52 +++++++++++-------- internal/lsp/code_action.go | 4 +- internal/lsp/completion.go | 2 +- internal/lsp/definition.go | 8 +-- internal/lsp/diagnostics.go | 2 +- internal/lsp/format.go | 2 +- internal/lsp/highlight.go | 2 +- internal/lsp/hover.go | 2 +- internal/lsp/link.go | 2 +- internal/lsp/lsp_test.go | 2 +- internal/lsp/signature_help.go | 2 +- internal/lsp/source/completion.go | 2 +- internal/lsp/source/diagnostics.go | 15 ++++-- internal/lsp/source/format.go | 4 +- internal/lsp/source/highlight.go | 2 +- internal/lsp/source/identifier.go | 14 ++++-- internal/lsp/source/signature_help.go | 2 +- internal/lsp/source/source_test.go | 16 +++--- internal/lsp/source/symbols.go | 2 +- internal/lsp/source/view.go | 20 ++++---- internal/lsp/symbols.go | 2 +- internal/lsp/text_synchronization.go | 2 +- internal/lsp/util.go | 14 +++++- 26 files changed, 189 insertions(+), 110 deletions(-) diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go index 4a9ec802..90eb5a55 100644 --- a/internal/lsp/cache/check.go +++ b/internal/lsp/cache/check.go @@ -14,10 +14,11 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" + "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" ) -func (v *view) parse(ctx context.Context, f *file) ([]packages.Error, error) { +func (v *view) parse(ctx context.Context, file source.File) ([]packages.Error, error) { v.mcache.mu.Lock() defer v.mcache.mu.Unlock() @@ -26,6 +27,11 @@ func (v *view) parse(ctx context.Context, f *file) ([]packages.Error, error) { return nil, err } + f, ok := file.(*goFile) + if !ok { + return nil, fmt.Errorf("not a go file: %v", file.URI()) + } + // If the package for the file has not been invalidated by the application // of the pending changes, there is no need to continue. if f.isPopulated() { @@ -37,7 +43,7 @@ func (v *view) parse(ctx context.Context, f *file) ([]packages.Error, error) { return errs, err } if f.meta == nil { - return nil, fmt.Errorf("no metadata found for %v", f.filename) + return nil, fmt.Errorf("no metadata found for %v", f.filename()) } imp := &importer{ view: v, @@ -56,19 +62,19 @@ func (v *view) parse(ctx context.Context, f *file) ([]packages.Error, error) { // If we still have not found the package for the file, something is wrong. if f.pkg == nil { - return nil, fmt.Errorf("parse: no package found for %v", f.filename) + return nil, fmt.Errorf("parse: no package found for %v", f.filename()) } return nil, nil } -func (v *view) checkMetadata(ctx context.Context, f *file) ([]packages.Error, error) { - if v.reparseImports(ctx, f, f.filename) { +func (v *view) checkMetadata(ctx context.Context, f *goFile) ([]packages.Error, error) { + if v.reparseImports(ctx, f, f.filename()) { cfg := v.config cfg.Mode = packages.LoadImports | packages.NeedTypesSizes - pkgs, err := packages.Load(&cfg, fmt.Sprintf("file=%s", f.filename)) + pkgs, err := packages.Load(&cfg, fmt.Sprintf("file=%s", f.filename())) if len(pkgs) == 0 { if err == nil { - err = fmt.Errorf("no packages found for %s", f.filename) + err = fmt.Errorf("no packages found for %s", f.filename()) } // Return this error as a diagnostic to the user. return []packages.Error{ @@ -84,7 +90,7 @@ func (v *view) checkMetadata(ctx context.Context, f *file) ([]packages.Error, er if len(pkg.Errors) > 0 { return pkg.Errors, fmt.Errorf("package %s has errors, skipping type-checking", pkg.PkgPath) } - v.link(pkg.PkgPath, pkg, nil) + v.link(ctx, pkg.PkgPath, pkg, nil) } } return nil, nil @@ -92,7 +98,7 @@ func (v *view) checkMetadata(ctx context.Context, f *file) ([]packages.Error, er // reparseImports reparses a file's import declarations to determine if they // have changed. -func (v *view) reparseImports(ctx context.Context, f *file, filename string) bool { +func (v *view) reparseImports(ctx context.Context, f *goFile, filename string) bool { if f.meta == nil { return true } @@ -113,7 +119,7 @@ func (v *view) reparseImports(ctx context.Context, f *file, filename string) boo return false } -func (v *view) link(pkgPath string, pkg *packages.Package, parent *metadata) *metadata { +func (v *view) link(ctx context.Context, pkgPath string, pkg *packages.Package, parent *metadata) *metadata { m, ok := v.mcache.packages[pkgPath] if !ok { m = &metadata{ @@ -130,7 +136,12 @@ func (v *view) link(pkgPath string, pkg *packages.Package, parent *metadata) *me m.files = pkg.CompiledGoFiles for _, filename := range m.files { if f, _ := v.getFile(span.FileURI(filename)); f != nil { - f.meta = m + gof, ok := f.(*goFile) + if !ok { + v.Logger().Errorf(ctx, "not a go file: %v", f.URI()) + continue + } + gof.meta = m } } // Connect the import graph. @@ -140,7 +151,7 @@ func (v *view) link(pkgPath string, pkg *packages.Package, parent *metadata) *me } for importPath, importPkg := range pkg.Imports { if _, ok := m.children[importPath]; !ok { - v.link(importPath, importPkg, m) + v.link(ctx, importPath, importPkg, m) } } // Clear out any imports that have been removed. @@ -273,10 +284,15 @@ func (v *view) cachePackage(ctx context.Context, pkg *pkg, meta *metadata) { v.Logger().Errorf(ctx, "no file: %v", err) continue } - f.token = tok - f.ast = file - f.imports = f.ast.Imports - f.pkg = pkg + gof, ok := f.(*goFile) + if !ok { + v.Logger().Errorf(ctx, "not a go file: %v", f.URI()) + continue + } + gof.token = tok + gof.ast = file + gof.imports = gof.ast.Imports + gof.pkg = pkg } v.pcache.mu.Lock() diff --git a/internal/lsp/cache/file.go b/internal/lsp/cache/file.go index 1e69dfe4..3711e96e 100644 --- a/internal/lsp/cache/file.go +++ b/internal/lsp/cache/file.go @@ -16,17 +16,32 @@ import ( "golang.org/x/tools/internal/span" ) -// file holds all the information we know about a file. -type file struct { - uris []span.URI - filename string - basename string +// viewFile extends source.File with helper methods for the view package. +type viewFile interface { + source.File + setContent(content []byte) + filename() string + addURI(uri span.URI) int + isActive() bool +} + +// fileBase holds the common functionality for all files. +// It is intended to be embedded in the file implementations +type fileBase struct { + uris []span.URI + fname string view *view active bool content []byte - ast *ast.File token *token.File +} + +// goFile holds all the information we know about a go file. +type goFile struct { + fileBase + + ast *ast.File pkg *pkg meta *metadata imports []*ast.ImportSpec @@ -36,17 +51,25 @@ func basename(filename string) string { return strings.ToLower(filepath.Base(filename)) } -func (f *file) URI() span.URI { +func (f *fileBase) URI() span.URI { return f.uris[0] } +func (f *fileBase) filename() string { + return f.fname +} + +func (f *fileBase) isActive() bool { + return f.active +} + // View returns the view associated with the file. -func (f *file) View() source.View { +func (f *fileBase) View() source.View { return f.view } // GetContent returns the contents of the file, reading it from file system if needed. -func (f *file) GetContent(ctx context.Context) []byte { +func (f *fileBase) GetContent(ctx context.Context) []byte { f.view.mu.Lock() defer f.view.mu.Unlock() @@ -57,14 +80,13 @@ func (f *file) GetContent(ctx context.Context) []byte { return f.content } -func (f *file) GetFileSet(ctx context.Context) *token.FileSet { +func (f *fileBase) GetFileSet(ctx context.Context) *token.FileSet { return f.view.config.Fset } -func (f *file) GetToken(ctx context.Context) *token.File { +func (f *goFile) GetToken(ctx context.Context) *token.File { f.view.mu.Lock() defer f.view.mu.Unlock() - if f.token == nil || len(f.view.contentChanges) > 0 { if _, err := f.view.parse(ctx, f); err != nil { return nil @@ -73,7 +95,7 @@ func (f *file) GetToken(ctx context.Context) *token.File { return f.token } -func (f *file) GetAST(ctx context.Context) *ast.File { +func (f *goFile) GetAST(ctx context.Context) *ast.File { f.view.mu.Lock() defer f.view.mu.Unlock() @@ -85,7 +107,7 @@ func (f *file) GetAST(ctx context.Context) *ast.File { return f.ast } -func (f *file) GetPackage(ctx context.Context) source.Package { +func (f *goFile) GetPackage(ctx context.Context) source.Package { f.view.mu.Lock() defer f.view.mu.Unlock() @@ -103,7 +125,7 @@ func (f *file) GetPackage(ctx context.Context) source.Package { // read is the internal part of GetContent. It assumes that the caller is // holding the mutex of the file's view. -func (f *file) read(ctx context.Context) { +func (f *fileBase) read(ctx context.Context) { if f.content != nil { if len(f.view.contentChanges) == 0 { return @@ -118,25 +140,25 @@ func (f *file) read(ctx context.Context) { } } // We might have the content saved in an overlay. - if content, ok := f.view.config.Overlay[f.filename]; ok { + if content, ok := f.view.config.Overlay[f.filename()]; ok { f.content = content return } // We don't know the content yet, so read it. - content, err := ioutil.ReadFile(f.filename) + content, err := ioutil.ReadFile(f.filename()) if err != nil { - f.view.Logger().Errorf(ctx, "unable to read file %s: %v", f.filename, err) + f.view.Logger().Errorf(ctx, "unable to read file %s: %v", f.filename(), err) return } f.content = content } // isPopulated returns true if all of the computed fields of the file are set. -func (f *file) isPopulated() bool { +func (f *goFile) isPopulated() bool { return f.ast != nil && f.token != nil && f.pkg != nil && f.meta != nil && f.imports != nil } -func (f *file) GetActiveReverseDeps(ctx context.Context) []source.File { +func (f *goFile) GetActiveReverseDeps(ctx context.Context) []source.GoFile { pkg := f.GetPackage(ctx) if pkg == nil { return nil @@ -149,10 +171,10 @@ func (f *file) GetActiveReverseDeps(ctx context.Context) []source.File { defer f.view.mcache.mu.Unlock() seen := make(map[string]struct{}) // visited packages - results := make(map[*file]struct{}) + results := make(map[*goFile]struct{}) f.view.reverseDeps(ctx, seen, results, pkg.PkgPath()) - files := make([]source.File, 0, len(results)) + files := make([]source.GoFile, 0, len(results)) for rd := range results { if rd == nil { continue @@ -166,7 +188,7 @@ func (f *file) GetActiveReverseDeps(ctx context.Context) []source.File { return files } -func (v *view) reverseDeps(ctx context.Context, seen map[string]struct{}, results map[*file]struct{}, pkgPath string) { +func (v *view) reverseDeps(ctx context.Context, seen map[string]struct{}, results map[*goFile]struct{}, pkgPath string) { if _, ok := seen[pkgPath]; ok { return } @@ -176,8 +198,8 @@ func (v *view) reverseDeps(ctx context.Context, seen map[string]struct{}, result return } for _, filename := range m.files { - if f, err := v.getFile(span.FileURI(filename)); err == nil && f.active { - results[f] = struct{}{} + if f, err := v.getFile(span.FileURI(filename)); err == nil && f.isActive() { + results[f.(*goFile)] = struct{}{} } } for parentPkgPath := range m.parents { diff --git a/internal/lsp/cache/parse.go b/internal/lsp/cache/parse.go index 0cb67e07..b8264b50 100644 --- a/internal/lsp/cache/parse.go +++ b/internal/lsp/cache/parse.go @@ -50,7 +50,9 @@ func (imp *importer) parseFiles(filenames []string) ([]*ast.File, []error) { } var fAST *ast.File if f != nil { - fAST = f.ast + if gof, ok := f.(*goFile); ok { + fAST = gof.ast + } } wg.Add(1) diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index 81170656..15c054e7 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -51,8 +51,8 @@ type view struct { // keep track of files by uri and by basename, a single file may be mapped // to multiple uris, and the same basename may map to multiple files - filesByURI map[span.URI]*file - filesByBase map[string][]*file + filesByURI map[span.URI]viewFile + filesByBase map[string][]viewFile // contentChanges saves the content changes for a given state of the view. // When type information is requested by the view, all of the dirty changes @@ -105,8 +105,8 @@ func NewView(ctx context.Context, log xlog.Logger, name string, folder span.URI, config: *config, name: name, folder: folder, - filesByURI: make(map[span.URI]*file), - filesByBase: make(map[string][]*file), + filesByURI: make(map[span.URI]viewFile), + filesByBase: make(map[string][]viewFile), contentChanges: make(map[span.URI]func()), mcache: &metadataCache{ packages: make(map[string]*metadata), @@ -227,6 +227,10 @@ func (v *view) applyContentChange(uri span.URI, content []byte) { if err != nil { return } + f.setContent(content) +} + +func (f *goFile) setContent(content []byte) { f.content = content // TODO(rstambler): Should we recompute these here? @@ -235,19 +239,19 @@ func (v *view) applyContentChange(uri span.URI, content []byte) { // Remove the package and all of its reverse dependencies from the cache. if f.pkg != nil { - v.remove(f.pkg.pkgPath, map[string]struct{}{}) + f.view.remove(f.pkg.pkgPath, map[string]struct{}{}) } switch { case f.active && content == nil: // The file was active, so we need to forget its content. f.active = false - delete(f.view.config.Overlay, f.filename) + delete(f.view.config.Overlay, f.filename()) f.content = nil case content != nil: // This is an active overlay, so we update the map. f.active = true - f.view.config.Overlay[f.filename] = f.content + f.view.config.Overlay[f.filename()] = f.content } } @@ -270,14 +274,16 @@ func (v *view) remove(pkgPath string, seen map[string]struct{}) { // invalidated package. for _, filename := range m.files { if f, _ := v.findFile(span.FileURI(filename)); f != nil { - f.pkg = nil + if gof, ok := f.(*goFile); ok { + gof.pkg = nil + } } } delete(v.pcache.packages, pkgPath) } // FindFile returns the file if the given URI is already a part of the view. -func (v *view) FindFile(ctx context.Context, uri span.URI) *file { +func (v *view) FindFile(ctx context.Context, uri span.URI) source.File { v.mu.Lock() defer v.mu.Unlock() f, err := v.findFile(uri) @@ -301,7 +307,7 @@ func (v *view) GetFile(ctx context.Context, uri span.URI) (source.File, error) { } // getFile is the unlocked internal implementation of GetFile. -func (v *view) getFile(uri span.URI) (*file, error) { +func (v *view) getFile(uri span.URI) (viewFile, error) { filename, err := uri.Filename() if err != nil { return nil, err @@ -314,9 +320,11 @@ func (v *view) getFile(uri span.URI) (*file, error) { } else if f != nil { return f, nil } - f := &file{ - view: v, - filename: filename, + f := &goFile{ + fileBase: fileBase{ + view: v, + fname: filename, + }, } v.mapFile(uri, f) return f, nil @@ -340,7 +348,7 @@ func (v *view) isIgnored(filename string) bool { // // An error is only returned for an irreparable failure, for example, if the // filename in question does not exist. -func (v *view) findFile(uri span.URI) (*file, error) { +func (v *view) findFile(uri span.URI) (viewFile, error) { if f := v.filesByURI[uri]; f != nil { // a perfect match return f, nil @@ -360,7 +368,7 @@ func (v *view) findFile(uri span.URI) (*file, error) { return nil, nil // the file may exist, return without an error } for _, c := range candidates { - if cStat, err := os.Stat(c.filename); err == nil { + if cStat, err := os.Stat(c.filename()); err == nil { if os.SameFile(pathStat, cStat) { // same file, map it v.mapFile(uri, c) @@ -373,12 +381,16 @@ func (v *view) findFile(uri span.URI) (*file, error) { return nil, nil } -func (v *view) mapFile(uri span.URI, f *file) { - v.filesByURI[uri] = f +func (f *fileBase) addURI(uri span.URI) int { f.uris = append(f.uris, uri) - if f.basename == "" { - f.basename = basename(f.filename) - v.filesByBase[f.basename] = append(v.filesByBase[f.basename], f) + return len(f.uris) +} + +func (v *view) mapFile(uri span.URI, f viewFile) { + v.filesByURI[uri] = f + if f.addURI(uri) == 1 { + basename := basename(f.filename()) + v.filesByBase[basename] = append(v.filesByBase[basename], f) } } diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go index e320f73d..7518e9a7 100644 --- a/internal/lsp/code_action.go +++ b/internal/lsp/code_action.go @@ -17,7 +17,7 @@ import ( func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { uri := span.NewURI(params.TextDocument.URI) view := s.findView(ctx, uri) - _, m, err := newColumnMap(ctx, view, uri) + _, m, err := getSourceFile(ctx, view, uri) if err != nil { return nil, err } @@ -63,7 +63,7 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara } func organizeImports(ctx context.Context, v source.View, s span.Span) ([]protocol.TextEdit, error) { - f, m, err := newColumnMap(ctx, v, s.URI()) + f, m, err := getGoFile(ctx, v, s.URI()) if err != nil { return nil, err } diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index bcc4e0e9..b9d82f43 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -18,7 +18,7 @@ import ( func (s *Server) completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) { uri := span.NewURI(params.TextDocument.URI) view := s.findView(ctx, uri) - f, m, err := newColumnMap(ctx, view, uri) + f, m, err := getGoFile(ctx, view, uri) if err != nil { return nil, err } diff --git a/internal/lsp/definition.go b/internal/lsp/definition.go index 5a1fe948..b0f996cc 100644 --- a/internal/lsp/definition.go +++ b/internal/lsp/definition.go @@ -15,7 +15,7 @@ import ( func (s *Server) definition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { uri := span.NewURI(params.TextDocument.URI) view := s.findView(ctx, uri) - f, m, err := newColumnMap(ctx, view, uri) + f, m, err := getGoFile(ctx, view, uri) if err != nil { return nil, err } @@ -35,7 +35,7 @@ func (s *Server) definition(ctx context.Context, params *protocol.TextDocumentPo if err != nil { return nil, err } - _, decM, err := newColumnMap(ctx, view, decSpan.URI()) + _, decM, err := getSourceFile(ctx, view, decSpan.URI()) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func (s *Server) definition(ctx context.Context, params *protocol.TextDocumentPo func (s *Server) typeDefinition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { uri := span.NewURI(params.TextDocument.URI) view := s.findView(ctx, uri) - f, m, err := newColumnMap(ctx, view, uri) + f, m, err := getGoFile(ctx, view, uri) if err != nil { return nil, err } @@ -69,7 +69,7 @@ func (s *Server) typeDefinition(ctx context.Context, params *protocol.TextDocume if err != nil { return nil, err } - _, identM, err := newColumnMap(ctx, view, identSpan.URI()) + _, identM, err := getSourceFile(ctx, view, identSpan.URI()) if err != nil { return nil, err } diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index 0312d4e0..1d0ac830 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -62,7 +62,7 @@ func (s *Server) publishDiagnostics(ctx context.Context, view source.View, uri s func toProtocolDiagnostics(ctx context.Context, v source.View, diagnostics []source.Diagnostic) ([]protocol.Diagnostic, error) { reports := []protocol.Diagnostic{} for _, diag := range diagnostics { - _, m, err := newColumnMap(ctx, v, diag.Span.URI()) + _, m, err := getSourceFile(ctx, v, diag.Span.URI()) if err != nil { return nil, err } diff --git a/internal/lsp/format.go b/internal/lsp/format.go index f591ae1b..0c672e83 100644 --- a/internal/lsp/format.go +++ b/internal/lsp/format.go @@ -22,7 +22,7 @@ func (s *Server) formatting(ctx context.Context, params *protocol.DocumentFormat // formatRange formats a document with a given range. func formatRange(ctx context.Context, v source.View, s span.Span) ([]protocol.TextEdit, error) { - f, m, err := newColumnMap(ctx, v, s.URI()) + f, m, err := getGoFile(ctx, v, s.URI()) if err != nil { return nil, err } diff --git a/internal/lsp/highlight.go b/internal/lsp/highlight.go index 288587d5..22bb1699 100644 --- a/internal/lsp/highlight.go +++ b/internal/lsp/highlight.go @@ -15,7 +15,7 @@ import ( func (s *Server) documentHighlight(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.DocumentHighlight, error) { uri := span.NewURI(params.TextDocument.URI) view := s.findView(ctx, uri) - f, m, err := newColumnMap(ctx, view, uri) + f, m, err := getGoFile(ctx, view, uri) if err != nil { return nil, err } diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go index 7d9ae2c0..a3d66aa8 100644 --- a/internal/lsp/hover.go +++ b/internal/lsp/hover.go @@ -16,7 +16,7 @@ import ( func (s *Server) hover(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.Hover, error) { uri := span.NewURI(params.TextDocument.URI) view := s.findView(ctx, uri) - f, m, err := newColumnMap(ctx, view, uri) + f, m, err := getGoFile(ctx, view, uri) if err != nil { return nil, err } diff --git a/internal/lsp/link.go b/internal/lsp/link.go index 33e9188b..ecf6e3eb 100644 --- a/internal/lsp/link.go +++ b/internal/lsp/link.go @@ -15,7 +15,7 @@ import ( func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) { uri := span.NewURI(params.TextDocument.URI) view := s.findView(ctx, uri) - f, m, err := newColumnMap(ctx, view, uri) + f, m, err := getGoFile(ctx, view, uri) if err != nil { return nil, err } diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index caa9b4be..04eaa814 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -293,7 +293,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) { } continue } - _, m, err := newColumnMap(ctx, r.server.findView(ctx, uri), uri) + _, m, err := getSourceFile(ctx, r.server.findView(ctx, uri), uri) if err != nil { t.Error(err) } diff --git a/internal/lsp/signature_help.go b/internal/lsp/signature_help.go index 74713212..b28f860b 100644 --- a/internal/lsp/signature_help.go +++ b/internal/lsp/signature_help.go @@ -15,7 +15,7 @@ import ( func (s *Server) signatureHelp(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.SignatureHelp, error) { uri := span.NewURI(params.TextDocument.URI) view := s.findView(ctx, uri) - f, m, err := newColumnMap(ctx, view, uri) + f, m, err := getGoFile(ctx, view, uri) if err != nil { return nil, err } diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index 2a132d58..d654397e 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -200,7 +200,7 @@ func (c *completer) found(obj types.Object, weight float64) { // The prefix is computed based on the preceding identifier and can be used by // the client to score the quality of the completion. For instance, some clients // may tolerate imperfect matches as valid completion results, since users may make typos. -func Completion(ctx context.Context, f File, pos token.Pos) ([]CompletionItem, Prefix, error) { +func Completion(ctx context.Context, f GoFile, pos token.Pos) ([]CompletionItem, Prefix, error) { file := f.GetAST(ctx) pkg := f.GetPackage(ctx) if pkg == nil || pkg.IsIllTyped() { diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go index 032c4aff..23b33b65 100644 --- a/internal/lsp/source/diagnostics.go +++ b/internal/lsp/source/diagnostics.go @@ -55,7 +55,11 @@ func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diag if err != nil { return singleDiagnostic(uri, "no file found for %s", uri), nil } - pkg := f.GetPackage(ctx) + gof, ok := f.(GoFile) + if !ok { + return singleDiagnostic(uri, "%s is not a go file", uri), nil + } + pkg := gof.GetPackage(ctx) if pkg == nil { return singleDiagnostic(uri, "%s is not part of a package", uri), nil } @@ -72,7 +76,7 @@ func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diag } } // Updates to the diagnostics for this package may need to be propagated. - for _, f := range f.GetActiveReverseDeps(ctx) { + for _, f := range gof.GetActiveReverseDeps(ctx) { pkg := f.GetPackage(ctx) if pkg == nil { continue @@ -151,11 +155,16 @@ func analyses(ctx context.Context, v View, pkg Package, reports map[span.URI][]D func pointToSpan(ctx context.Context, v View, spn span.Span) span.Span { // Don't set a range if it's anything other than a type error. - diagFile, err := v.GetFile(ctx, spn.URI()) + f, err := v.GetFile(ctx, spn.URI()) if err != nil { v.Logger().Errorf(ctx, "Could find file for diagnostic: %v", spn.URI()) return spn } + diagFile, ok := f.(GoFile) + if !ok { + v.Logger().Errorf(ctx, "Not a go file: %v", spn.URI()) + return spn + } tok := diagFile.GetToken(ctx) if tok == nil { v.Logger().Errorf(ctx, "Could not find tokens for diagnostic: %v", spn.URI()) diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go index bcc0d7b5..682f0756 100644 --- a/internal/lsp/source/format.go +++ b/internal/lsp/source/format.go @@ -19,7 +19,7 @@ import ( ) // Format formats a file with a given range. -func Format(ctx context.Context, f File, rng span.Range) ([]TextEdit, error) { +func Format(ctx context.Context, f GoFile, rng span.Range) ([]TextEdit, error) { pkg := f.GetPackage(ctx) if hasParseErrors(pkg.GetErrors()) { return nil, fmt.Errorf("%s has parse errors, not formatting", f.URI()) @@ -52,7 +52,7 @@ func hasParseErrors(errors []packages.Error) bool { } // Imports formats a file using the goimports tool. -func Imports(ctx context.Context, f File, rng span.Range) ([]TextEdit, error) { +func Imports(ctx context.Context, f GoFile, rng span.Range) ([]TextEdit, error) { formatted, err := imports.Process(f.GetToken(ctx).Name(), f.GetContent(ctx), nil) if err != nil { return nil, err diff --git a/internal/lsp/source/highlight.go b/internal/lsp/source/highlight.go index 2952d137..8e81139e 100644 --- a/internal/lsp/source/highlight.go +++ b/internal/lsp/source/highlight.go @@ -13,7 +13,7 @@ import ( "golang.org/x/tools/internal/span" ) -func Highlight(ctx context.Context, f File, pos token.Pos) []span.Span { +func Highlight(ctx context.Context, f GoFile, pos token.Pos) []span.Span { fAST := f.GetAST(ctx) fset := f.GetFileSet(ctx) path, _ := astutil.PathEnclosingInterval(fAST, pos, pos) diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go index aaf4696b..6e43fccd 100644 --- a/internal/lsp/source/identifier.go +++ b/internal/lsp/source/identifier.go @@ -20,7 +20,7 @@ import ( type IdentifierInfo struct { Name string Range span.Range - File File + File GoFile Type struct { Range span.Range Object types.Object @@ -37,7 +37,7 @@ type IdentifierInfo struct { // Identifier returns identifier information for a position // in a file, accounting for a potentially incomplete selector. -func Identifier(ctx context.Context, v View, f File, pos token.Pos) (*IdentifierInfo, error) { +func Identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*IdentifierInfo, error) { if result, err := identifier(ctx, v, f, pos); err != nil || result != nil { return result, err } @@ -52,7 +52,7 @@ func Identifier(ctx context.Context, v View, f File, pos token.Pos) (*Identifier } // identifier checks a single position for a potential identifier. -func identifier(ctx context.Context, v View, f File, pos token.Pos) (*IdentifierInfo, error) { +func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*IdentifierInfo, error) { fAST := f.GetAST(ctx) pkg := f.GetPackage(ctx) if pkg == nil || pkg.IsIllTyped() { @@ -128,7 +128,7 @@ func identifier(ctx context.Context, v View, f File, pos token.Pos) (*Identifier return result, nil } -func checkImportSpec(f File, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) { +func checkImportSpec(f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) { // Check if pos is in an *ast.ImportSpec. for _, imp := range fAST.Imports { if imp.Pos() <= pos && pos < imp.End() { @@ -204,10 +204,14 @@ func objToNode(ctx context.Context, v View, obj types.Object, rng span.Range) (a if err != nil { return nil, err } - declFile, err := v.GetFile(ctx, s.URI()) + f, err := v.GetFile(ctx, s.URI()) if err != nil { return nil, err } + declFile, ok := f.(GoFile) + if !ok { + return nil, fmt.Errorf("not a go file %v", s.URI()) + } declAST := declFile.GetAST(ctx) path, _ := astutil.PathEnclosingInterval(declAST, rng.Start, rng.End) if path == nil { diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go index ea5a644c..250d4281 100644 --- a/internal/lsp/source/signature_help.go +++ b/internal/lsp/source/signature_help.go @@ -24,7 +24,7 @@ type ParameterInformation struct { Label string } -func SignatureHelp(ctx context.Context, f File, pos token.Pos) (*SignatureInformation, error) { +func SignatureHelp(ctx context.Context, f GoFile, pos token.Pos) (*SignatureInformation, error) { fAST := f.GetAST(ctx) pkg := f.GetPackage(ctx) if pkg == nil || pkg.IsIllTyped() { diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index 69005c80..b08b1105 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -133,9 +133,9 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests if err != nil { t.Fatalf("failed for %v: %v", src, err) } - tok := f.GetToken(ctx) + tok := f.(source.GoFile).GetToken(ctx) pos := tok.Pos(src.Start().Offset()) - list, prefix, err := source.Completion(ctx, f, pos) + list, prefix, err := source.Completion(ctx, f.(source.GoFile), pos) if err != nil { t.Fatalf("failed for %v: %v", src, err) } @@ -164,7 +164,7 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests } tok := f.GetToken(ctx) pos := tok.Pos(src.Start().Offset()) - list, _, err := source.Completion(ctx, f, pos) + list, _, err := source.Completion(ctx, f.(source.GoFile), pos) if err != nil { t.Fatalf("failed for %v: %v", src, err) } @@ -273,7 +273,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) { if err != nil { t.Fatalf("failed for %v: %v", spn, err) } - edits, err := source.Format(ctx, f, rng) + edits, err := source.Format(ctx, f.(source.GoFile), rng) if err != nil { if gofmted != "" { t.Error(err) @@ -297,7 +297,7 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) { } tok := f.GetToken(ctx) pos := tok.Pos(d.Src.Start().Offset()) - ident, err := source.Identifier(ctx, r.view, f, pos) + ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), pos) if err != nil { t.Fatalf("failed for %v: %v", d.Src, err) } @@ -341,7 +341,7 @@ func (r *runner) Highlight(t *testing.T, data tests.Highlights) { } tok := f.GetToken(ctx) pos := tok.Pos(src.Start().Offset()) - highlights := source.Highlight(ctx, f, pos) + highlights := source.Highlight(ctx, f.(source.GoFile), pos) if len(highlights) != len(locations) { t.Fatalf("got %d highlights for %s, expected %d", len(highlights), name, len(locations)) } @@ -360,7 +360,7 @@ func (r *runner) Symbol(t *testing.T, data tests.Symbols) { if err != nil { t.Fatalf("failed for %v: %v", uri, err) } - symbols := source.DocumentSymbols(ctx, f) + symbols := source.DocumentSymbols(ctx, f.(source.GoFile)) if len(symbols) != len(expectedSymbols) { t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols)) @@ -424,7 +424,7 @@ func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) { } tok := f.GetToken(ctx) pos := tok.Pos(spn.Start().Offset()) - gotSignature, err := source.SignatureHelp(ctx, f, pos) + gotSignature, err := source.SignatureHelp(ctx, f.(source.GoFile), pos) if err != nil { t.Fatalf("failed for %v: %v", spn, err) } diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go index c0dcaf48..f91f8f4d 100644 --- a/internal/lsp/source/symbols.go +++ b/internal/lsp/source/symbols.go @@ -40,7 +40,7 @@ type Symbol struct { Children []Symbol } -func DocumentSymbols(ctx context.Context, f File) []Symbol { +func DocumentSymbols(ctx context.Context, f GoFile) []Symbol { fset := f.GetFileSet(ctx) file := f.GetAST(ctx) pkg := f.GetPackage(ctx) diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index 91c82c0c..fbbf91f3 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -35,22 +35,24 @@ type View interface { Shutdown(ctx context.Context) } -// File represents a Go source file that has been type-checked. It is the input -// to most of the exported functions in this package, as it wraps up the -// building blocks for most queries. Users of the source package can abstract -// the loading of packages into their own caching systems. +// File represents a source file of any type. type File interface { URI() span.URI View() View - GetAST(ctx context.Context) *ast.File - GetFileSet(ctx context.Context) *token.FileSet - GetPackage(ctx context.Context) Package - GetToken(ctx context.Context) *token.File GetContent(ctx context.Context) []byte + GetFileSet(ctx context.Context) *token.FileSet + GetToken(ctx context.Context) *token.File +} + +// GoFile represents a Go source file that has been type-checked. +type GoFile interface { + File + GetAST(ctx context.Context) *ast.File + GetPackage(ctx context.Context) Package // GetActiveReverseDeps returns the active files belonging to the reverse // dependencies of this file's package. - GetActiveReverseDeps(ctx context.Context) []File + GetActiveReverseDeps(ctx context.Context) []GoFile } // Package represents a Go package that has been type-checked. It maintains diff --git a/internal/lsp/symbols.go b/internal/lsp/symbols.go index da57ec24..2ee526aa 100644 --- a/internal/lsp/symbols.go +++ b/internal/lsp/symbols.go @@ -15,7 +15,7 @@ import ( func (s *Server) documentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) { uri := span.NewURI(params.TextDocument.URI) view := s.findView(ctx, uri) - f, m, err := newColumnMap(ctx, view, uri) + f, m, err := getGoFile(ctx, view, uri) if err != nil { return nil, err } diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go index 16677730..5402f23c 100644 --- a/internal/lsp/text_synchronization.go +++ b/internal/lsp/text_synchronization.go @@ -65,7 +65,7 @@ func (s *Server) applyChanges(ctx context.Context, params *protocol.DidChangeTex uri := span.NewURI(params.TextDocument.URI) view := s.findView(ctx, uri) - file, m, err := newColumnMap(ctx, view, uri) + file, m, err := getSourceFile(ctx, view, uri) if err != nil { return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found") } diff --git a/internal/lsp/util.go b/internal/lsp/util.go index 244e2cbb..0d61de5d 100644 --- a/internal/lsp/util.go +++ b/internal/lsp/util.go @@ -46,7 +46,7 @@ func PrintVersionInfo(w io.Writer, verbose bool, markdown bool) { } } -func newColumnMap(ctx context.Context, v source.View, uri span.URI) (source.File, *protocol.ColumnMapper, error) { +func getSourceFile(ctx context.Context, v source.View, uri span.URI) (source.File, *protocol.ColumnMapper, error) { f, err := v.GetFile(ctx, uri) if err != nil { return nil, nil, err @@ -58,3 +58,15 @@ func newColumnMap(ctx context.Context, v source.View, uri span.URI) (source.File m := protocol.NewColumnMapper(f.URI(), f.GetFileSet(ctx), tok, f.GetContent(ctx)) return f, m, nil } + +func getGoFile(ctx context.Context, v source.View, uri span.URI) (source.GoFile, *protocol.ColumnMapper, error) { + f, m, err := getSourceFile(ctx, v, uri) + if err != nil { + return nil, nil, err + } + gof, ok := f.(source.GoFile) + if !ok { + return nil, nil, fmt.Errorf("not a go file %v", f.URI()) + } + return gof, m, nil +}