// 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 template import ( "log" "sort" "strings" "testing" "golang.org/x/tools/gopls/internal/protocol" ) func init() { log.SetFlags(log.Lshortfile) } type tparse struct { marked string // ^ shows where to ask for completions. (The user just typed the following character.) wanted []string // expected completions } // Test completions in templates that parse enough (if completion needs symbols) // Seen characters up to the ^ func TestParsed(t *testing.T) { var tests = []tparse{ {"{{x}}{{12. xx^", nil}, // https://github.com/golang/go/issues/50430 {``, nil}, {"{{i^f}}", []string{"index", "if"}}, {"{{if .}}{{e^ {{end}}", []string{"eq", "end}}", "else", "end"}}, {"{{foo}}{{f^", []string{"foo"}}, {"{{$^}}", []string{"$"}}, {"{{$x:=4}}{{$^", []string{"$x"}}, {"{{$x:=4}}{{$ ^ ", []string{}}, {"{{len .Modified}}{{.^Mo", []string{"Modified"}}, {"{{len .Modified}}{{.mf^", []string{"Modified"}}, {"{{$^ }}", []string{"$"}}, {"{{$a =3}}{{$^", []string{"$a"}}, // .two is not good here: fix someday {`{{.Modified}}{{.^{{if $.one.two}}xxx{{end}}`, []string{"Modified", "one", "two"}}, {`{{.Modified}}{{.o^{{if $.one.two}}xxx{{end}}`, []string{"one"}}, {"{{.Modiifed}}{{.one.t^{{if $.one.two}}xxx{{end}}", []string{"two"}}, {`{{block "foo" .}}{{i^`, []string{"index", "if"}}, {"{{in^{{Internal}}", []string{"index", "Internal", "if"}}, // simple number has no completions {"{{4^e", []string{}}, // simple string has no completions {"{{`e^", []string{}}, {"{{`No i^", []string{}}, // example of why go/scanner is used {"{{xavier}}{{12. x^", []string{"xavier"}}, } for _, tx := range tests { c := testCompleter(t, tx) var v []string if c != nil { ans, _ := c.complete() for _, a := range ans.Items { v = append(v, a.Label) } } if len(v) != len(tx.wanted) { t.Errorf("%q: got %q, wanted %q %d,%d", tx.marked, v, tx.wanted, len(v), len(tx.wanted)) continue } sort.Strings(tx.wanted) sort.Strings(v) for i := 0; i < len(v); i++ { if tx.wanted[i] != v[i] { t.Errorf("%q at %d: got %v, wanted %v", tx.marked, i, v, tx.wanted) break } } } } func testCompleter(t *testing.T, tx tparse) *completer { t.Helper() // seen chars up to ^ col := strings.Index(tx.marked, "^") buf := strings.Replace(tx.marked, "^", "", 1) p := parseBuffer([]byte(buf)) pos := protocol.Position{Line: 0, Character: uint32(col)} if p.ParseErr != nil { log.Printf("%q: %v", tx.marked, p.ParseErr) } offset := inTemplate(p, pos) if offset == -1 { return nil } syms := make(map[string]symbol) filterSyms(syms, p.symbols) c := &completer{ p: p, pos: protocol.Position{Line: 0, Character: uint32(col)}, offset: offset + len(Left), ctx: protocol.CompletionContext{TriggerKind: protocol.Invoked}, syms: syms, } return c }