From 68b574acb93f20740454b88e98bd021cef52527b Mon Sep 17 00:00:00 2001 From: pjw Date: Fri, 7 Jan 2022 19:06:25 -0500 Subject: [PATCH] internal/template: identify template files by the templateExtensions option Make the language id (sent from the client) 'gotmpl' equivalent to 'tmpl' Wherever a view is known, use its options to determine which files are template files. Whenever the client sends an explicit languageID, use that. Partially fixes golang/vscode-go#1957 Change-Id: I04cd630d6c6c80e0a78c2fafb6ddc1166ce86829 Reviewed-on: https://go-review.googlesource.com/c/tools/+/376854 Trust: Peter Weinberger Reviewed-by: Robert Findley --- internal/lsp/cache/cache.go | 5 ---- internal/lsp/cache/load.go | 4 ++-- internal/lsp/cache/mod.go | 2 +- internal/lsp/cache/parse.go | 4 +++- internal/lsp/cache/session.go | 1 - internal/lsp/cache/snapshot.go | 23 ++++++++++++++----- internal/lsp/cache/view.go | 20 ++++++++++++++++ internal/lsp/cmd/cmd.go | 2 +- internal/lsp/code_action.go | 7 +++--- internal/lsp/code_lens.go | 2 +- internal/lsp/completion.go | 2 +- internal/lsp/definition.go | 2 +- internal/lsp/diagnostics.go | 2 +- internal/lsp/format.go | 2 +- internal/lsp/general.go | 3 ++- internal/lsp/highlight.go | 2 +- internal/lsp/hover.go | 2 +- internal/lsp/link.go | 2 +- internal/lsp/lsp_test.go | 5 ++-- internal/lsp/references.go | 2 +- internal/lsp/semantic.go | 5 ++-- internal/lsp/source/source_test.go | 3 +-- internal/lsp/source/util.go | 37 +++++++++++++++++------------- internal/lsp/source/view.go | 11 ++++----- internal/lsp/symbols.go | 2 +- internal/lsp/workspace.go | 2 +- 26 files changed, 93 insertions(+), 61 deletions(-) diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go index be03e638e0..ac670b573e 100644 --- a/internal/lsp/cache/cache.go +++ b/internal/lsp/cache/cache.go @@ -168,10 +168,6 @@ func (h *fileHandle) URI() span.URI { return h.uri } -func (h *fileHandle) Kind() source.FileKind { - return source.DetectLanguage("", h.uri.Filename()) -} - func (h *fileHandle) Hash() string { return h.hash } @@ -180,7 +176,6 @@ func (h *fileHandle) FileIdentity() source.FileIdentity { return source.FileIdentity{ URI: h.uri, Hash: h.hash, - Kind: h.Kind(), } } diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go index c5b5a3da46..84e8245d9e 100644 --- a/internal/lsp/cache/load.go +++ b/internal/lsp/cache/load.go @@ -57,7 +57,7 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...interf uri := span.URI(scope) // Don't try to load a file that doesn't exist. fh := s.FindFile(uri) - if fh == nil || fh.Kind() != source.Go { + if fh == nil || s.View().FileKind(uri) != source.Go { continue } query = append(query, fmt.Sprintf("file=%s", uri.Filename())) @@ -264,7 +264,7 @@ func (s *snapshot) applyCriticalErrorToFiles(ctx context.Context, msg string, fi for _, fh := range files { // Place the diagnostics on the package or module declarations. var rng protocol.Range - switch fh.Kind() { + switch s.view.FileKind(fh.URI()) { case source.Go: if pgf, err := s.ParseGo(ctx, fh, source.ParseHeader); err == nil { pkgDecl := span.NewRange(s.FileSet(), pgf.File.Package, pgf.File.Name.End()) diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go index a915d052c0..f881a0241b 100644 --- a/internal/lsp/cache/mod.go +++ b/internal/lsp/cache/mod.go @@ -164,7 +164,7 @@ func (mwh *modWhyHandle) why(ctx context.Context, snapshot *snapshot) (map[strin } func (s *snapshot) ModWhy(ctx context.Context, fh source.FileHandle) (map[string]string, error) { - if fh.Kind() != source.Mod { + if s.View().FileKind(fh.URI()) != source.Mod { return nil, fmt.Errorf("%s is not a go.mod file", fh.URI()) } if handle := s.getModWhyHandle(fh.URI()); handle != nil { diff --git a/internal/lsp/cache/parse.go b/internal/lsp/cache/parse.go index 0de0f70d41..e761373fae 100644 --- a/internal/lsp/cache/parse.go +++ b/internal/lsp/cache/parse.go @@ -13,6 +13,7 @@ import ( "go/scanner" "go/token" "go/types" + "path/filepath" "reflect" "strconv" "strings" @@ -246,7 +247,8 @@ func parseGo(ctx context.Context, fset *token.FileSet, fh source.FileHandle, mod ctx, done := event.Start(ctx, "cache.parseGo", tag.File.Of(fh.URI().Filename())) defer done() - if fh.Kind() != source.Go { + ext := filepath.Ext(fh.URI().Filename()) + if ext != ".go" && ext != "" { // files generated by cgo have no extension return &parseGoData{err: errors.Errorf("cannot parse non-Go file %s", fh.URI())} } src, err := fh.Read() diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go index a65b8fe89c..e906eccce9 100644 --- a/internal/lsp/cache/session.go +++ b/internal/lsp/cache/session.go @@ -62,7 +62,6 @@ func (o *overlay) FileIdentity() source.FileIdentity { return source.FileIdentity{ URI: o.uri, Hash: o.hash, - Kind: o.kind, } } diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go index 53f97f4d7f..1b4ef4682e 100644 --- a/internal/lsp/cache/snapshot.go +++ b/internal/lsp/cache/snapshot.go @@ -159,7 +159,8 @@ func (s *snapshot) ModFiles() []span.URI { } func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle { - if len(s.view.Options().TemplateExtensions) == 0 { + opts := s.view.Options().TemplateExtensions + if len(opts) == 0 { return nil } @@ -168,8 +169,18 @@ func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle { s.mu.Lock() defer s.mu.Unlock() + isin := func(s string, a []string) bool { + for _, x := range a { + if x == s || "."+x == s { + return true + } + } + return false + } + for k, x := range s.files { - if strings.HasSuffix(filepath.Ext(k.Filename()), "tmpl") { + suffix := filepath.Ext(k.Filename()) + if isin(suffix, opts) { ans[k] = x } } @@ -516,8 +527,8 @@ func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode if err != nil { return nil, err } - if fh.Kind() != source.Go { - return nil, fmt.Errorf("no packages for non-Go file %s", uri) + if kind := s.view.FileKind(fh.FileIdentity().URI); kind != source.Go { + return nil, fmt.Errorf("no packages for non-Go file %s (%v)", uri, kind) } knownIDs, err := s.getOrLoadIDsForURI(ctx, uri) if err != nil { @@ -780,7 +791,7 @@ func (s *snapshot) getWorkspacePkgPath(id PackageID) PackagePath { return s.workspacePackages[id] } -const fileExtensions = "go,mod,sum,work,tmpl" +const fileExtensions = "go,mod,sum,work" func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} { extensions := fileExtensions @@ -1574,7 +1585,7 @@ func (s *snapshot) orphanedFiles() []source.VersionedFileHandle { var files []source.VersionedFileHandle for uri, fh := range s.files { // Don't try to reload metadata for go.mod files. - if fh.Kind() != source.Go { + if s.view.FileKind(uri) != source.Go { continue } // If the URI doesn't belong to this view, then it's not in a workspace diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index fcff02ade8..b4fe38e64f 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -241,6 +241,26 @@ func (v *View) Options() *source.Options { return v.options } +func (v *View) FileKind(URI span.URI) source.FileKind { + got := filepath.Ext(URI.Filename()) + switch got { + case ".go": + return source.Go + case ".mod": + return source.Mod + case ".sum": + return source.Sum + } + exts := v.Options().TemplateExtensions + for _, ext := range exts { + if got == ext || got == "."+ext { + return source.Tmpl + } + } + // and now what? This should never happen, but it does for cgo before go1.15 + return source.Go +} + func minorOptionsChange(a, b *source.Options) bool { // Check if any of the settings that modify our understanding of files have been changed if !reflect.DeepEqual(a.Env, b.Env) { diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go index 8245e12999..06a33a1fe8 100644 --- a/internal/lsp/cmd/cmd.go +++ b/internal/lsp/cmd/cmd.go @@ -497,7 +497,7 @@ func (c *connection) AddFile(ctx context.Context, uri span.URI) *cmdFile { p := &protocol.DidOpenTextDocumentParams{ TextDocument: protocol.TextDocumentItem{ URI: protocol.URIFromSpanURI(uri), - LanguageID: source.DetectLanguage("", file.uri.Filename()).String(), + LanguageID: "go", Version: 1, Text: string(file.mapper.Content), }, diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go index 526d279bb9..45d2d6e013 100644 --- a/internal/lsp/code_action.go +++ b/internal/lsp/code_action.go @@ -30,9 +30,10 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara uri := fh.URI() // Determine the supported actions for this file kind. - supportedCodeActions, ok := snapshot.View().Options().SupportedCodeActions[fh.Kind()] + kind := snapshot.View().FileKind(uri) + supportedCodeActions, ok := snapshot.View().Options().SupportedCodeActions[kind] if !ok { - return nil, fmt.Errorf("no supported code actions for %v file kind", fh.Kind()) + return nil, fmt.Errorf("no supported code actions for %v file kind", kind) } // The Only field of the context specifies which code actions the client wants. @@ -67,7 +68,7 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara } var codeActions []protocol.CodeAction - switch fh.Kind() { + switch kind { case source.Mod: if diagnostics := params.Context.Diagnostics; len(diagnostics) > 0 { diags, err := mod.DiagnosticsForMod(ctx, snapshot, fh) diff --git a/internal/lsp/code_lens.go b/internal/lsp/code_lens.go index 6e371fcc36..345a21dae5 100644 --- a/internal/lsp/code_lens.go +++ b/internal/lsp/code_lens.go @@ -23,7 +23,7 @@ func (s *Server) codeLens(ctx context.Context, params *protocol.CodeLensParams) return nil, err } var lenses map[command.Command]source.LensFunc - switch fh.Kind() { + switch snapshot.View().FileKind(fh.URI()) { case source.Mod: lenses = mod.LensFuncs() case source.Go: diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index 4523d34e2a..4e904059d6 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -27,7 +27,7 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara } var candidates []completion.CompletionItem var surrounding *completion.Selection - switch fh.Kind() { + switch snapshot.View().FileKind(fh.URI()) { case source.Go: candidates, surrounding, err = completion.Completion(ctx, snapshot, fh, params.Position, params.Context) case source.Mod: diff --git a/internal/lsp/definition.go b/internal/lsp/definition.go index ab4aaed104..b55f26d4fa 100644 --- a/internal/lsp/definition.go +++ b/internal/lsp/definition.go @@ -18,7 +18,7 @@ func (s *Server) definition(ctx context.Context, params *protocol.DefinitionPara if !ok { return nil, err } - if fh.Kind() == source.Tmpl { + if snapshot.View().FileKind(fh.URI()) == source.Tmpl { return template.Definition(snapshot, fh, params.Position) } ident, err := source.Identifier(ctx, snapshot, fh, params.Position) diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index f9e1a47bd6..64379afc5a 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -416,7 +416,7 @@ func (s *Server) showCriticalErrorStatus(ctx context.Context, snapshot source.Sn // If they cannot and the workspace is not otherwise unloaded, it also surfaces // a warning, suggesting that the user check the file for build tags. func (s *Server) checkForOrphanedFile(ctx context.Context, snapshot source.Snapshot, fh source.VersionedFileHandle) *source.Diagnostic { - if fh.Kind() != source.Go { + if snapshot.View().FileKind(fh.URI()) != source.Go { return nil } // builtin files won't have a package, but they are never orphaned. diff --git a/internal/lsp/format.go b/internal/lsp/format.go index 62b25d8101..467bcce412 100644 --- a/internal/lsp/format.go +++ b/internal/lsp/format.go @@ -18,7 +18,7 @@ func (s *Server) formatting(ctx context.Context, params *protocol.DocumentFormat if !ok { return nil, err } - switch fh.Kind() { + switch snapshot.View().FileKind(fh.URI()) { case source.Mod: return mod.Format(ctx, snapshot, fh) case source.Go: diff --git a/internal/lsp/general.go b/internal/lsp/general.go index a946c80d2b..7f2389388e 100644 --- a/internal/lsp/general.go +++ b/internal/lsp/general.go @@ -466,7 +466,8 @@ func (s *Server) beginFileRequest(ctx context.Context, pURI protocol.DocumentURI release() return nil, nil, false, func() {}, err } - if expectKind != source.UnknownKind && fh.Kind() != expectKind { + kind := snapshot.View().FileKind(fh.URI()) + if expectKind != source.UnknownKind && kind != expectKind { // Wrong kind of file. Nothing to do. release() return nil, nil, false, func() {}, nil diff --git a/internal/lsp/highlight.go b/internal/lsp/highlight.go index a350dd54de..71bd7d92dd 100644 --- a/internal/lsp/highlight.go +++ b/internal/lsp/highlight.go @@ -21,7 +21,7 @@ func (s *Server) documentHighlight(ctx context.Context, params *protocol.Documen return nil, err } - if fh.Kind() == source.Tmpl { + if snapshot.View().FileKind(fh.URI()) == source.Tmpl { return template.Highlight(ctx, snapshot, fh, params.Position) } diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go index 1e118bc3a8..9125c23bc9 100644 --- a/internal/lsp/hover.go +++ b/internal/lsp/hover.go @@ -19,7 +19,7 @@ func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*prot if !ok { return nil, err } - switch fh.Kind() { + switch snapshot.View().FileKind(fh.URI()) { case source.Mod: return mod.Hover(ctx, snapshot, fh, params.Position) case source.Go: diff --git a/internal/lsp/link.go b/internal/lsp/link.go index 87692fa4d6..d76f1d0659 100644 --- a/internal/lsp/link.go +++ b/internal/lsp/link.go @@ -30,7 +30,7 @@ func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLink if !ok { return nil, err } - switch fh.Kind() { + switch snapshot.View().FileKind(fh.URI()) { case source.Mod: links, err = modLinks(ctx, snapshot, fh) case source.Go: diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index 8a6673012c..ccfe2c9aa0 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -71,8 +71,7 @@ func testLSP(t *testing.T, datum *tests.Data) { var modifications []source.FileModification for filename, content := range datum.Config.Overlay { - kind := source.DetectLanguage("", filename) - if kind != source.Go { + if filepath.Ext(filename) != ".go" { continue } modifications = append(modifications, source.FileModification{ @@ -187,7 +186,7 @@ func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests } func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) { - if source.DetectLanguage("", uri.Filename()) != source.Mod { + if !strings.HasSuffix(uri.Filename(), "go.mod") { return } got, err := r.server.codeLens(r.ctx, &protocol.CodeLensParams{ diff --git a/internal/lsp/references.go b/internal/lsp/references.go index d8f2f1e283..b5bb00f691 100644 --- a/internal/lsp/references.go +++ b/internal/lsp/references.go @@ -18,7 +18,7 @@ func (s *Server) references(ctx context.Context, params *protocol.ReferenceParam if !ok { return nil, err } - if fh.Kind() == source.Tmpl { + if snapshot.View().FileKind(fh.URI()) == source.Tmpl { return template.References(ctx, snapshot, fh, params) } references, err := source.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration) diff --git a/internal/lsp/semantic.go b/internal/lsp/semantic.go index c3ea15c6b1..3925ce3dec 100644 --- a/internal/lsp/semantic.go +++ b/internal/lsp/semantic.go @@ -70,7 +70,8 @@ func (s *Server) computeSemanticTokens(ctx context.Context, td protocol.TextDocu // the client won't remember the wrong answer return nil, errors.Errorf("semantictokens are disabled") } - if fh.Kind() == source.Tmpl { + kind := snapshot.View().FileKind(fh.URI()) + if kind == source.Tmpl { // this is a little cumbersome to avoid both exporting 'encoded' and its methods // and to avoid import cycles e := &encoded{ @@ -87,7 +88,7 @@ func (s *Server) computeSemanticTokens(ctx context.Context, td protocol.TextDocu } return template.SemanticTokens(ctx, snapshot, fh.URI(), add, data) } - if fh.Kind() != source.Go { + if kind != source.Go { return nil, nil } pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckFull, source.WidestPackage) diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index d0e6b8a2eb..4a2c43fcf3 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -66,8 +66,7 @@ func testSource(t *testing.T, datum *tests.Data) { var modifications []source.FileModification for filename, content := range datum.Config.Overlay { - kind := source.DetectLanguage("", filename) - if kind != source.Go { + if filepath.Ext(filename) != ".go" { continue } modifications = append(modifications, source.FileModification{ diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go index 9500eeed0d..c9b4878264 100644 --- a/internal/lsp/source/util.go +++ b/internal/lsp/source/util.go @@ -162,34 +162,39 @@ func posToMappedRange(snapshot Snapshot, pkg Package, pos, end token.Pos) (Mappe var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`) func DetectLanguage(langID, filename string) FileKind { - switch langID { - case "go": - return Go - case "go.mod": - return Mod - case "go.sum": - return Sum - case "tmpl": - return Tmpl + // use the langID if the client sent it + if langID != "" { + switch langID { + case "go": + return Go + case "go.mod": + return Mod + case "go.sum": + return Sum + case "tmpl", "gotmpl": + return Tmpl + default: + return UnknownKind + } } - // Fallback to detecting the language based on the file extension. + // Detect the language based on the file extension. switch ext := filepath.Ext(filename); ext { case ".mod": return Mod case ".sum": return Sum + case ".go": + return Go default: - if strings.HasSuffix(ext, "tmpl") { - // .tmpl, .gotmpl, etc - return Tmpl - } - // It's a Go file, or we shouldn't be seeing it + // (for instance, before go1.15 cgo files had no extension) return Go } } func (k FileKind) String() string { switch k { + case Go: + return "go" case Mod: return "go.mod" case Sum: @@ -197,7 +202,7 @@ func (k FileKind) String() string { case Tmpl: return "tmpl" default: - return "go" + return fmt.Sprintf("unk%d", k) } } diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index 2285e5a3ac..50648565dd 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -266,6 +266,9 @@ type View interface { // RegisterModuleUpgrades registers that upgrades exist for the given modules. RegisterModuleUpgrades(upgrades map[string]string) + + // FileKind returns the type of a file + FileKind(uri span.URI) FileKind } // A FileSource maps uris to FileHandles. This abstraction exists both for @@ -498,7 +501,6 @@ type VersionedFileIdentity struct { // FileHandle represents a handle to a specific version of a single file. type FileHandle interface { URI() span.URI - Kind() FileKind // FileIdentity returns a FileIdentity for the file, even if there was an // error reading it. @@ -516,17 +518,14 @@ type FileIdentity struct { // Identifier represents a unique identifier for the file's content. Hash string - - // Kind is the file's kind. - Kind FileKind } func (id FileIdentity) String() string { - return fmt.Sprintf("%s%s%s", id.URI, id.Hash, id.Kind) + 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, or sum. +// It can be one of Go,mod, Sum, or Tmpl. type FileKind int const ( diff --git a/internal/lsp/symbols.go b/internal/lsp/symbols.go index 5bde1bdd47..49566e041e 100644 --- a/internal/lsp/symbols.go +++ b/internal/lsp/symbols.go @@ -24,7 +24,7 @@ func (s *Server) documentSymbol(ctx context.Context, params *protocol.DocumentSy return []interface{}{}, err } var docSymbols []protocol.DocumentSymbol - if fh.Kind() == source.Tmpl { + if snapshot.View().FileKind(fh.URI()) == source.Tmpl { docSymbols, err = template.DocumentSymbols(snapshot, fh) } else { docSymbols, err = source.DocumentSymbols(ctx, snapshot, fh) diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go index 02feae5338..c239942f29 100644 --- a/internal/lsp/workspace.go +++ b/internal/lsp/workspace.go @@ -79,8 +79,8 @@ func (s *Server) didChangeConfiguration(ctx context.Context, _ *protocol.DidChan if err != nil { return err } - snapshot, release := view.Snapshot(ctx) go func() { + snapshot, release := view.Snapshot(ctx) defer release() s.diagnoseDetached(snapshot) }()