internal/lsp: enable template processing and add templateFiles option

Move template processing out of experimental and enable it. Users
can set the option 'templateSupport' to false to disable it.
Also, add a new flag 'templateExtensions', defaulting to ['tmpl','gotmpl']
to let the user provide a list of extensions of files
to be considered as template files.

Change-Id: I724387738c1632256999cda304d6cf9fa48ed91d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/362241
Run-TryBot: Peter Weinberger <pjw@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Peter Weinberger <pjw@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
pjw 2021-11-08 07:04:31 -05:00 committed by Peter Weinberger
parent e900012391
commit fc3ed20887
7 changed files with 131 additions and 36 deletions

View File

@ -72,6 +72,20 @@ Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-pro
Default: `["-node_modules"]`.
#### **templateSupport** *bool*
templateSupport can be used to turn off support for template files.
Default: `true`.
#### **templateExtensions** *[]string*
templateExtensions gives the extensions of file names that are treateed
as template files. (The extension
is the part of the file name after the final dot.)
Default: `["tmpl","gotmpl"]`.
#### **memoryMode** *enum*
**This setting is experimental and may be deleted.**
@ -112,15 +126,6 @@ for multi-module workspaces.
Default: `false`.
#### **experimentalTemplateSupport** *bool*
**This setting is experimental and may be deleted.**
experimentalTemplateSupport opts into the experimental support
for template files.
Default: `false`.
#### **experimentalPackageCacheKey** *bool*
**This setting is experimental and may be deleted.**

View File

@ -159,7 +159,7 @@ func (s *snapshot) ModFiles() []span.URI {
}
func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle {
if !s.view.Options().ExperimentalTemplateSupport {
if !s.view.Options().TemplateSupport {
return nil
}
@ -783,12 +783,15 @@ func (s *snapshot) getWorkspacePkgPath(id PackageID) PackagePath {
const fileExtensions = "go,mod,sum,work,tmpl"
func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
extensions := fileExtensions
for _, ext := range s.View().Options().TemplateExtensions {
extensions += "," + ext
}
// Work-around microsoft/vscode#100870 by making sure that we are,
// at least, watching the user's entire workspace. This will still be
// applied to every folder in the workspace.
patterns := map[string]struct{}{
fmt.Sprintf("**/*.{%s}", fileExtensions): {},
"**/*.*tmpl": {},
fmt.Sprintf("**/*.{%s}", extensions): {},
}
dirs := s.workspace.dirs(ctx, s)
for _, dir := range dirs {
@ -802,7 +805,7 @@ func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]stru
// TODO(rstambler): If microsoft/vscode#3025 is resolved before
// microsoft/vscode#101042, we will need a work-around for Windows
// drive letter casing.
patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, fileExtensions)] = struct{}{}
patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, extensions)] = struct{}{}
}
// Some clients do not send notifications for changes to directories that

View File

@ -332,10 +332,25 @@ func (s *snapshot) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Optio
return s.view.importsState.runProcessEnvFunc(ctx, s, fn)
}
// separated out from its sole use in locateTemplatFiles for testability
func fileHasExtension(path string, suffixes []string) bool {
ext := filepath.Ext(path)
if ext != "" && ext[0] == '.' {
ext = ext[1:]
}
for _, s := range suffixes {
if s != "" && ext == s {
return true
}
}
return false
}
func (s *snapshot) locateTemplateFiles(ctx context.Context) {
if !s.view.Options().ExperimentalTemplateSupport {
if !s.view.Options().TemplateSupport {
return
}
suffixes := s.view.Options().TemplateExtensions
dir := s.workspace.root.Filename()
searched := 0
// Change to WalkDir when we move up to 1.16
@ -343,7 +358,7 @@ func (s *snapshot) locateTemplateFiles(ctx context.Context) {
if err != nil {
return err
}
if strings.HasSuffix(filepath.Ext(path), "tmpl") && !pathExcludedByFilter(path, dir, s.view.gomodcache, s.view.options) &&
if fileHasExtension(path, suffixes) && !pathExcludedByFilter(path, dir, s.view.gomodcache, s.view.options) &&
!fi.IsDir() {
k := span.URIFromPath(path)
fh, err := s.GetVersionedFile(ctx, k)

View File

@ -172,3 +172,44 @@ func TestFilters(t *testing.T) {
}
}
}
func TestSuffixes(t *testing.T) {
type file struct {
path string
want bool
}
type cases struct {
option []string
files []file
}
tests := []cases{
{[]string{"tmpl", "gotmpl"}, []file{ // default
{"foo", false},
{"foo.tmpl", true},
{"foo.gotmpl", true},
{"tmpl", false},
{"tmpl.go", false}},
},
{[]string{"tmpl", "gotmpl", "html", "gohtml"}, []file{
{"foo.gotmpl", true},
{"foo.html", true},
{"foo.gohtml", true},
{"html", false}},
},
{[]string{"tmpl", "gotmpl", ""}, []file{ // possible user mistake
{"foo.gotmpl", true},
{"foo.go", false},
{"foo", false}},
},
}
for _, a := range tests {
suffixes := a.option
for _, b := range a.files {
got := fileHasExtension(b.path, suffixes)
if got != b.want {
t.Errorf("got %v, want %v, option %q, file %q (%+v)",
got, b.want, a.option, b.path, b)
}
}
}
}

View File

@ -44,6 +44,32 @@ var GeneratedAPIJSON = &APIJSON{
Status: "",
Hierarchy: "build",
},
{
Name: "templateSupport",
Type: "bool",
Doc: "templateSupport can be used to turn off support for template files.\n",
EnumKeys: EnumKeys{
ValueType: "",
Keys: nil,
},
EnumValues: nil,
Default: "true",
Status: "",
Hierarchy: "build",
},
{
Name: "templateExtensions",
Type: "[]string",
Doc: "templateExtensions gives the extensions of file names that are treateed\nas template files. (The extension\nis the part of the file name after the final dot.)\n",
EnumKeys: EnumKeys{
ValueType: "",
Keys: nil,
},
EnumValues: nil,
Default: "[\"tmpl\",\"gotmpl\"]",
Status: "",
Hierarchy: "build",
},
{
Name: "memoryMode",
Type: "enum",
@ -92,19 +118,6 @@ var GeneratedAPIJSON = &APIJSON{
Status: "experimental",
Hierarchy: "build",
},
{
Name: "experimentalTemplateSupport",
Type: "bool",
Doc: "experimentalTemplateSupport opts into the experimental support\nfor template files.\n",
EnumKeys: EnumKeys{
ValueType: "",
Keys: nil,
},
EnumValues: nil,
Default: "false",
Status: "experimental",
Hierarchy: "build",
},
{
Name: "experimentalPackageCacheKey",
Type: "bool",

View File

@ -114,6 +114,8 @@ func DefaultOptions() *Options {
ExperimentalPackageCacheKey: true,
MemoryMode: ModeNormal,
DirectoryFilters: []string{"-node_modules"},
TemplateSupport: true,
TemplateExtensions: []string{"tmpl", "gotmpl"},
},
UIOptions: UIOptions{
DiagnosticOptions: DiagnosticOptions{
@ -231,6 +233,14 @@ type BuildOptions struct {
// Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`
DirectoryFilters []string
// TemplateSupport can be used to turn off support for template files.
TemplateSupport bool
// TemplateExtensions gives the extensions of file names that are treateed
// as template files. (The extension
// is the part of the file name after the final dot.)
TemplateExtensions []string
// MemoryMode controls the tradeoff `gopls` makes between memory usage and
// correctness.
//
@ -249,10 +259,6 @@ type BuildOptions struct {
// for multi-module workspaces.
ExperimentalWorkspaceModule bool `status:"experimental"`
// ExperimentalTemplateSupport opts into the experimental support
// for template files.
ExperimentalTemplateSupport bool `status:"experimental"`
// ExperimentalPackageCacheKey controls whether to use a coarser cache key
// for package type information to increase cache hits. This setting removes
// the user's environment, build flags, and working directory from the cache
@ -747,7 +753,6 @@ func (o *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer, enabled bool, sev
func (o *Options) EnableAllExperiments() {
o.SemanticTokens = true
o.ExperimentalPostfixCompletions = true
o.ExperimentalTemplateSupport = true
o.ExperimentalUseInvalidMetadata = true
o.ExperimentalWatchedFileDelay = 50 * time.Millisecond
}
@ -935,8 +940,21 @@ func (o *Options) set(name string, value interface{}, seen map[string]struct{})
case "experimentalWorkspaceModule":
result.setBool(&o.ExperimentalWorkspaceModule)
case "experimentalTemplateSupport":
result.setBool(&o.ExperimentalTemplateSupport)
case "experimentalTemplateSupport", // remove after June 2022
"templateSupport":
if name == "experimentalTemplateSupport" {
result.State = OptionDeprecated
result.Replacement = "templateSupport"
}
result.setBool(&o.TemplateSupport)
case "templateExtensions":
iexts, ok := value.([]string)
if !ok {
result.errorf("invalid type %T, expect []string", value)
break
}
o.TemplateExtensions = iexts
case "experimentalDiagnosticsDelay", "diagnosticsDelay":
if name == "experimentalDiagnosticsDelay" {

View File

@ -66,7 +66,7 @@ func Diagnose(f source.VersionedFileHandle) []*source.Diagnostic {
}
func skipTemplates(s source.Snapshot) bool {
return !s.View().Options().ExperimentalTemplateSupport
return !s.View().Options().TemplateSupport
}
// Definition finds the definitions of the symbol at loc. It