mirror of https://github.com/golang/go.git
gopls/internal/regtest: add regression tests for template diagnostics
Add some additional regressions tests for our loading of templates based on language ID and/or the configured templateExtensions. Move these tests to a new regtest package "templates", so that they may be easily run together. Fixes golang/vscode-go#1957 Change-Id: Ic83454725e9aec41b3c1f5202bb68d97cc73c839 Reviewed-on: https://go-review.googlesource.com/c/tools/+/378394 Trust: Robert Findley <rfindley@google.com> Run-TryBot: Robert Findley <rfindley@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Suzy Mueller <suzmue@golang.org>
This commit is contained in:
parent
c4cfc425f2
commit
a222cdb107
|
|
@ -1,75 +0,0 @@
|
|||
// Copyright 2021 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 misc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
. "golang.org/x/tools/internal/lsp/regtest"
|
||||
)
|
||||
|
||||
const filesA = `
|
||||
-- go.mod --
|
||||
module mod.com
|
||||
|
||||
go 1.12
|
||||
-- b.gotmpl --
|
||||
{{define "A"}}goo{{end}}
|
||||
-- a.tmpl --
|
||||
{{template "A"}}
|
||||
`
|
||||
|
||||
func TestSuffixes(t *testing.T) {
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
AllExperiments: true,
|
||||
Settings: map[string]interface{}{
|
||||
"templateExtensions": []string{"tmpl", "gotmpl"},
|
||||
},
|
||||
},
|
||||
).Run(t, filesA, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("a.tmpl")
|
||||
x := env.RegexpSearch("a.tmpl", `A`)
|
||||
file, pos := env.GoToDefinition("a.tmpl", x)
|
||||
refs := env.References(file, pos)
|
||||
if len(refs) != 2 {
|
||||
t.Fatalf("got %v reference(s), want 2", len(refs))
|
||||
}
|
||||
// make sure we got one from b.gotmpl
|
||||
want := env.Sandbox.Workdir.URI("b.gotmpl")
|
||||
if refs[0].URI != want && refs[1].URI != want {
|
||||
t.Errorf("failed to find reference to %s", shorten(want))
|
||||
for i, r := range refs {
|
||||
t.Logf("%d: URI:%s %v", i, shorten(r.URI), r.Range)
|
||||
}
|
||||
}
|
||||
|
||||
content, npos := env.Hover(file, pos)
|
||||
if pos != npos {
|
||||
t.Errorf("pos? got %v, wanted %v", npos, pos)
|
||||
}
|
||||
if content.Value != "template A defined" {
|
||||
t.Errorf("got %s, wanted 'template A defined", content.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// shorten long URIs
|
||||
func shorten(fn protocol.DocumentURI) string {
|
||||
if len(fn) <= 20 {
|
||||
return string(fn)
|
||||
}
|
||||
pieces := strings.Split(string(fn), "/")
|
||||
if len(pieces) < 2 {
|
||||
return string(fn)
|
||||
}
|
||||
j := len(pieces)
|
||||
return pieces[j-2] + "/" + pieces[j-1]
|
||||
}
|
||||
|
||||
// Hover, SemTok, Diagnose with errors
|
||||
// and better coverage
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright 2022 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 template
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/gopls/internal/hooks"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
. "golang.org/x/tools/internal/lsp/regtest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
Main(m, hooks.Options)
|
||||
}
|
||||
|
||||
func TestTemplatesFromExtensions(t *testing.T) {
|
||||
const files = `
|
||||
-- go.mod --
|
||||
module mod.com
|
||||
|
||||
go 1.12
|
||||
-- hello.tmpl --
|
||||
{{range .Planets}}
|
||||
Hello {{}} <-- missing body
|
||||
{{end}}
|
||||
`
|
||||
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"templateExtensions": []string{"tmpl"},
|
||||
},
|
||||
},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
// TODO: can we move this diagnostic onto {{}}?
|
||||
env.Await(env.DiagnosticAtRegexp("hello.tmpl", "()Hello {{}}"))
|
||||
env.WriteWorkspaceFile("hello.tmpl", "{{range .Planets}}\nHello {{.}}\n{{end}}")
|
||||
env.Await(EmptyDiagnostics("hello.tmpl"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplatesFromLangID(t *testing.T) {
|
||||
const files = `
|
||||
-- go.mod --
|
||||
module mod.com
|
||||
|
||||
go 1.12
|
||||
`
|
||||
|
||||
Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.CreateBuffer("hello.tmpl", "")
|
||||
env.Await(
|
||||
OnceMet(
|
||||
env.DoneWithOpen(),
|
||||
NoDiagnostics("hello.tmpl"), // Don't get spurious errors for empty templates.
|
||||
),
|
||||
)
|
||||
env.SetBufferContent("hello.tmpl", "{{range .Planets}}\nHello {{}}\n{{end}}")
|
||||
env.Await(env.DiagnosticAtRegexp("hello.tmpl", "()Hello {{}}"))
|
||||
env.RegexpReplace("hello.tmpl", "{{}}", "{{.}}")
|
||||
env.Await(EmptyOrNoDiagnostics("hello.tmpl"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestClosingTemplatesMakesDiagnosticsDisappear(t *testing.T) {
|
||||
const files = `
|
||||
-- go.mod --
|
||||
module mod.com
|
||||
|
||||
go 1.12
|
||||
-- hello.tmpl --
|
||||
{{range .Planets}}
|
||||
Hello {{}} <-- missing body
|
||||
{{end}}
|
||||
`
|
||||
|
||||
Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("hello.tmpl")
|
||||
env.Await(env.DiagnosticAtRegexp("hello.tmpl", "()Hello {{}}"))
|
||||
// Since we don't have templateExtensions configured, closing hello.tmpl
|
||||
// should make its diagnostics disappear.
|
||||
env.CloseBuffer("hello.tmpl")
|
||||
env.Await(EmptyDiagnostics("hello.tmpl"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultipleSuffixes(t *testing.T) {
|
||||
const files = `
|
||||
-- go.mod --
|
||||
module mod.com
|
||||
|
||||
go 1.12
|
||||
-- b.gotmpl --
|
||||
{{define "A"}}goo{{end}}
|
||||
-- a.tmpl --
|
||||
{{template "A"}}
|
||||
`
|
||||
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"templateExtensions": []string{"tmpl", "gotmpl"},
|
||||
},
|
||||
},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("a.tmpl")
|
||||
x := env.RegexpSearch("a.tmpl", `A`)
|
||||
file, pos := env.GoToDefinition("a.tmpl", x)
|
||||
refs := env.References(file, pos)
|
||||
if len(refs) != 2 {
|
||||
t.Fatalf("got %v reference(s), want 2", len(refs))
|
||||
}
|
||||
// make sure we got one from b.gotmpl
|
||||
want := env.Sandbox.Workdir.URI("b.gotmpl")
|
||||
if refs[0].URI != want && refs[1].URI != want {
|
||||
t.Errorf("failed to find reference to %s", shorten(want))
|
||||
for i, r := range refs {
|
||||
t.Logf("%d: URI:%s %v", i, shorten(r.URI), r.Range)
|
||||
}
|
||||
}
|
||||
|
||||
content, npos := env.Hover(file, pos)
|
||||
if pos != npos {
|
||||
t.Errorf("pos? got %v, wanted %v", npos, pos)
|
||||
}
|
||||
if content.Value != "template A defined" {
|
||||
t.Errorf("got %s, wanted 'template A defined", content.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// shorten long URIs
|
||||
func shorten(fn protocol.DocumentURI) string {
|
||||
if len(fn) <= 20 {
|
||||
return string(fn)
|
||||
}
|
||||
pieces := strings.Split(string(fn), "/")
|
||||
if len(pieces) < 2 {
|
||||
return string(fn)
|
||||
}
|
||||
j := len(pieces)
|
||||
return pieces[j-2] + "/" + pieces[j-1]
|
||||
}
|
||||
|
||||
// Hover, SemTok, Diagnose with errors
|
||||
// and better coverage
|
||||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
|
@ -114,6 +115,14 @@ type EditorConfig struct {
|
|||
// Whether to edit files with windows line endings.
|
||||
WindowsLineEndings bool
|
||||
|
||||
// Map of language ID -> regexp to match, used to set the file type of new
|
||||
// buffers. Applied as an overlay on top of the following defaults:
|
||||
// "go" -> ".*\.go"
|
||||
// "go.mod" -> "go\.mod"
|
||||
// "go.sum" -> "go\.sum"
|
||||
// "gotmpl" -> ".*tmpl"
|
||||
FileAssociations map[string]string
|
||||
|
||||
// Settings holds arbitrary additional settings to apply to the gopls config.
|
||||
// TODO(rfindley): replace existing EditorConfig fields with Settings.
|
||||
Settings map[string]interface{}
|
||||
|
|
@ -378,21 +387,6 @@ func (e *Editor) OpenFile(ctx context.Context, path string) error {
|
|||
return e.createBuffer(ctx, path, false, content)
|
||||
}
|
||||
|
||||
func textDocumentItem(wd *Workdir, buf buffer) protocol.TextDocumentItem {
|
||||
uri := wd.URI(buf.path)
|
||||
languageID := ""
|
||||
if strings.HasSuffix(buf.path, ".go") {
|
||||
// TODO: what about go.mod files? What is their language ID?
|
||||
languageID = "go"
|
||||
}
|
||||
return protocol.TextDocumentItem{
|
||||
URI: uri,
|
||||
LanguageID: languageID,
|
||||
Version: int32(buf.version),
|
||||
Text: buf.text(),
|
||||
}
|
||||
}
|
||||
|
||||
// CreateBuffer creates a new unsaved buffer corresponding to the workdir path,
|
||||
// containing the given textual content.
|
||||
func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
|
||||
|
|
@ -410,7 +404,13 @@ func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, cont
|
|||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.buffers[path] = buf
|
||||
item := textDocumentItem(e.sandbox.Workdir, buf)
|
||||
|
||||
item := protocol.TextDocumentItem{
|
||||
URI: e.sandbox.Workdir.URI(buf.path),
|
||||
LanguageID: e.languageID(buf.path),
|
||||
Version: int32(buf.version),
|
||||
Text: buf.text(),
|
||||
}
|
||||
|
||||
if e.Server != nil {
|
||||
if err := e.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
|
||||
|
|
@ -425,6 +425,29 @@ func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, cont
|
|||
return nil
|
||||
}
|
||||
|
||||
var defaultFileAssociations = map[string]*regexp.Regexp{
|
||||
"go": regexp.MustCompile(`^.*\.go$`), // '$' is important: don't match .gotmpl!
|
||||
"go.mod": regexp.MustCompile(`^go\.mod$`),
|
||||
"go.sum": regexp.MustCompile(`^go\.sum$`),
|
||||
"gotmpl": regexp.MustCompile(`^.*tmpl$`),
|
||||
}
|
||||
|
||||
func (e *Editor) languageID(p string) string {
|
||||
base := path.Base(p)
|
||||
for lang, re := range e.Config.FileAssociations {
|
||||
re := regexp.MustCompile(re)
|
||||
if re.MatchString(base) {
|
||||
return lang
|
||||
}
|
||||
}
|
||||
for lang, re := range defaultFileAssociations {
|
||||
if re.MatchString(base) {
|
||||
return lang
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// lines returns line-ending agnostic line representation of content.
|
||||
func lines(content string) []string {
|
||||
lines := strings.Split(content, "\n")
|
||||
|
|
|
|||
Loading…
Reference in New Issue