From defcaca81e391e51bd7835a6cec6c4bc67badeac Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Mon, 18 Jul 2022 01:20:48 -0500 Subject: [PATCH] :sparkles: Add auto complete comments with current value, template, and line num --- internal/visitor/find_args.go | 88 +++++++++++++++++++++++------- internal/visitor/find_args_test.go | 46 ++++++++-------- 2 files changed, 90 insertions(+), 44 deletions(-) diff --git a/internal/visitor/find_args.go b/internal/visitor/find_args.go index 0cb1bc7..5f82164 100644 --- a/internal/visitor/find_args.go +++ b/internal/visitor/find_args.go @@ -1,11 +1,15 @@ package visitor import ( + "fmt" "github.com/clevyr/go-yampl/internal/config" "github.com/clevyr/go-yampl/internal/node" template2 "github.com/clevyr/go-yampl/internal/template" "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/token" "regexp" + "strconv" + "strings" "text/template" "text/template/parse" ) @@ -14,39 +18,81 @@ var fieldRe = regexp.MustCompile(`\.([A-Za-z_.]+)`) func NewFindArgs(conf config.Config) FindArgs { return FindArgs{ - conf: conf, - valMap: make(map[string]struct{}), + conf: conf, + matchMap: make(map[string]MatchSlice), } } +type Match struct { + Value any + Template string + Position *token.Position +} + +func (m Match) String() string { + val := fmt.Sprintf("%v", m.Value) + maxLen := 33 + if len(val) > maxLen { + val = val[:maxLen-3] + "..." + } + var result string + if m.Position != nil { + result += "line " + strconv.Itoa(m.Position.Line) + ": " + } + result += fmt.Sprintf("%s %#v", val, m.Template) + result = strings.ReplaceAll(result, "\n", " ") + return result +} + +type MatchSlice []Match + +func (v MatchSlice) String() string { + var s []string + for _, match := range v { + s = append(s, match.String()) + } + return strings.Join(s, "; ") +} + type FindArgs struct { - conf config.Config - valMap map[string]struct{} - err error + conf config.Config + matchMap map[string]MatchSlice + err error } -func (v *FindArgs) Visit(n ast.Node) ast.Visitor { - if comment := node.GetCommentTmpl(v.conf.Prefix, n); comment != "" { +func (visitor *FindArgs) Visit(n ast.Node) ast.Visitor { + if comment := node.GetCommentTmpl(visitor.conf.Prefix, n); comment != "" { tmpl, err := template.New(""). Funcs(template2.FuncMap()). - Delims(v.conf.LeftDelim, v.conf.RightDelim). + Delims(visitor.conf.LeftDelim, visitor.conf.RightDelim). Option("missingkey=zero"). Parse(comment) if err != nil { - v.err = err + visitor.err = err return nil } for _, field := range listTemplFields(tmpl) { - matches := fieldRe.FindStringSubmatch(field) - if matches != nil { - for _, match := range matches[1:] { - v.valMap[match] = struct{}{} + if tokens := fieldRe.FindStringSubmatch(field); tokens != nil { + for _, tok := range tokens[1:] { + match := Match{ + Template: comment, + Position: n.GetToken().Position, + } + switch n := n.(type) { + case *ast.LiteralNode: + match.Value = n.Value.String() + default: + if scalar, ok := n.(ast.ScalarNode); ok { + match.Value = scalar.GetValue() + } + } + visitor.matchMap[tok] = append(visitor.matchMap[tok], match) } } } } - return v + return visitor } func listTemplFields(t *template.Template) []string { @@ -66,11 +112,11 @@ func listNodeFields(node parse.Node, res []string) []string { return res } -func (v FindArgs) Values() []string { - result := make([]string, 0, len(v.valMap)) +func (visitor FindArgs) Values() []string { + result := make([]string, 0, len(visitor.matchMap)) outer: - for k := range v.valMap { - for kconf := range v.conf.Values { + for k, v := range visitor.matchMap { + for kconf := range visitor.conf.Values { if k == kconf { continue outer } @@ -80,11 +126,11 @@ outer: continue outer } } - result = append(result, k+"=") + result = append(result, fmt.Sprintf("%s=\t%v", k, v)) } return result } -func (v FindArgs) Error() error { - return v.err +func (visitor FindArgs) Error() error { + return visitor.err } diff --git a/internal/visitor/find_args_test.go b/internal/visitor/find_args_test.go index 47b3e42..591dc93 100644 --- a/internal/visitor/find_args_test.go +++ b/internal/visitor/find_args_test.go @@ -11,9 +11,9 @@ import ( func TestFindArgs_Error(t *testing.T) { type fields struct { - conf config.Config - valMap map[string]struct{} - err error + conf config.Config + matchMap map[string]MatchSlice + err error } tests := []struct { name string @@ -26,9 +26,9 @@ func TestFindArgs_Error(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := FindArgs{ - conf: tt.fields.conf, - valMap: tt.fields.valMap, - err: tt.fields.err, + conf: tt.fields.conf, + matchMap: tt.fields.matchMap, + err: tt.fields.err, } if err := v.Error(); (err != nil) != tt.wantErr { t.Errorf("Error() error = %v, wantErr %v", err, tt.wantErr) @@ -39,26 +39,26 @@ func TestFindArgs_Error(t *testing.T) { func TestFindArgs_Values(t *testing.T) { type fields struct { - conf config.Config - valMap map[string]struct{} - err error + conf config.Config + matchMap map[string]MatchSlice + err error } tests := []struct { name string fields fields want []string }{ - {"simple", fields{valMap: map[string]struct{}{"a": {}}}, []string{"a="}}, - {"nested", fields{valMap: map[string]struct{}{"a.b": {}}}, []string{"a.b="}}, - {"duplicate", fields{conf: config.Config{Values: map[string]any{"b": "b"}}, valMap: map[string]struct{}{"b": {}}}, []string{}}, - {"reserved", fields{valMap: map[string]struct{}{"Value": {}}}, []string{}}, + {name: "simple", fields: fields{matchMap: map[string]MatchSlice{"a": []Match{}}}, want: []string{"a=\t"}}, + {"nested", fields{matchMap: map[string]MatchSlice{"a.b": []Match{}}}, []string{"a.b=\t"}}, + {"duplicate", fields{conf: config.Config{Values: map[string]any{"b": "b"}}, matchMap: map[string]MatchSlice{"b": []Match{{}}}}, []string{}}, + {"reserved", fields{matchMap: map[string]MatchSlice{"Value": []Match{}}}, []string{}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := FindArgs{ - conf: tt.fields.conf, - valMap: tt.fields.valMap, - err: tt.fields.err, + conf: tt.fields.conf, + matchMap: tt.fields.matchMap, + err: tt.fields.err, } if got := v.Values(); !reflect.DeepEqual(got, tt.want) { t.Errorf("Values() = %v, want %v", got, tt.want) @@ -75,11 +75,11 @@ func TestFindArgs_Visit(t *testing.T) { name string v map[string]struct{} source string - want map[string]struct{} + wantLen int wantErr bool }{ - {"simple", make(map[string]struct{}), "a #yampl {{ .a }}", map[string]struct{}{"a": {}}, false}, - {"invalid template", make(map[string]struct{}), "a #yampl {{", map[string]struct{}{}, true}, + {"simple", make(map[string]struct{}), "a #yampl {{ .a }}", 1, false}, + {"invalid template", make(map[string]struct{}), "a #yampl {{", 0, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -91,9 +91,9 @@ func TestFindArgs_Visit(t *testing.T) { return } - got := v.valMap - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Visitor() = %v, want %v", got, tt.want) + got := v.matchMap + if len(got) != tt.wantLen { + t.Errorf("Visitor() len = %v, want len %v", len(got), tt.wantLen) } }) } @@ -108,7 +108,7 @@ func TestNewFindArgs(t *testing.T) { args args want FindArgs }{ - {"default", args{conf: config.Config{}}, FindArgs{conf: config.Config{}, valMap: make(map[string]struct{})}}, + {"default", args{conf: config.Config{}}, FindArgs{conf: config.Config{}, matchMap: make(map[string]MatchSlice)}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {