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 <pjw@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
pjw 2022-01-07 19:06:25 -05:00 committed by Peter Weinberger
parent d7a4bb4f6a
commit 68b574acb9
26 changed files with 93 additions and 61 deletions

View File

@ -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(),
}
}

View File

@ -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())

View File

@ -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 {

View File

@ -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()

View File

@ -62,7 +62,6 @@ func (o *overlay) FileIdentity() source.FileIdentity {
return source.FileIdentity{
URI: o.uri,
Hash: o.hash,
Kind: o.kind,
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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),
},

View File

@ -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)

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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)
}

View File

@ -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:

View File

@ -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:

View File

@ -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{

View File

@ -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)

View File

@ -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)

View File

@ -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{

View File

@ -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)
}
}

View File

@ -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 (

View File

@ -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)

View File

@ -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)
}()