Skip to content

Commit

Permalink
Add ability to change param name & fix default values with spaces (#17
Browse files Browse the repository at this point in the history
  • Loading branch information
dizzyfool committed Mar 14, 2023
1 parent ee9c359 commit eaa63d6
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 47 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ go 1.13
require (
github.com/gorilla/websocket v1.4.2
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/common v0.10.0
github.com/smartystreets/goconvey v1.6.4
github.com/thoas/go-funk v0.6.0
golang.org/x/tools v0.0.0-20200729173947-1c30660f9f89
Expand Down
130 changes: 87 additions & 43 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"go/ast"
"go/token"
"path/filepath"
"regexp"
"strconv"
"strings"
"unicode"
Expand All @@ -23,6 +24,10 @@ const (
zenrpcMagicPrefix = "//zenrpc:"
)

var errorCommentRegexp = regexp.MustCompile("^(-?\\d+)\\s*(.*)$")
var returnCommentRegexp = regexp.MustCompile("return\\s*(.*)")
var argumentCommentRegexp = regexp.MustCompile("([^=( ]+)\\s*(\\(\\s*([^ )]+)\\s*\\))?(\\s*=\\s*((`([^`]+)`)|([^ ]+)))?\\s*(.*)")

// PackageInfo represents struct info for XXX_zenrpc.go file generation
type PackageInfo struct {
EntryPoint string
Expand Down Expand Up @@ -531,60 +536,99 @@ func (m *Method) parseComments(doc *ast.CommentGroup, pi *PackageInfo) {
continue
}

// split by magic path and description
fields := strings.Fields(comment.Text)
couple := [...]string{
strings.TrimPrefix(strings.TrimSpace(fields[0]), zenrpcMagicPrefix),
strings.Join(fields[1:], " "),
}

// parse arguments
if args := strings.Split(couple[0], "="); len(args) == 2 {
// default value
// example: "//zenrpc:exp=2 exponent could be empty"

name := args[0]
value := args[1]
line := strings.TrimPrefix(strings.TrimSpace(comment.Text), zenrpcMagicPrefix)
switch parseCommentType(line) {
case "argument":
name, alias, hasDefault, defaultValue, description := parseArgumentComment(line)
for i, a := range m.Args {
if a.Name == name {
m.DefaultValues[name] = DefaultValue{
Name: name,
CapitalName: a.CapitalName,
Type: strings.TrimPrefix(a.Type, "*"), // remove star
Comment: comment.Text,
Value: value,
m.Args[i].Description = description

if hasDefault {
m.DefaultValues[name] = DefaultValue{
Name: name,
CapitalName: a.CapitalName,
Type: strings.TrimPrefix(a.Type, "*"), // remove star
Comment: comment.Text,
Value: defaultValue,
}

m.Args[i].HasDefaultValue = true
}

m.Args[i].HasDefaultValue = true
if len(couple) == 2 {
m.Args[i].Description = couple[1]
if alias != "" {
m.Args[i].JsonName = alias
}

break
}
}
} else if couple[0] == "return" {
// description for return
// example: "//zenrpc:return operation result"
case "return":
m.SMDReturn.Description = parseReturnComment(line)
case "error":
code, description := parseErrorComment(line)
m.Errors = append(m.Errors, SMDError{code, description})
}
}
}

m.SMDReturn.Description = couple[1]
} else if i, err := strconv.Atoi(couple[0]); err == nil {
// error code
// example: "//zenrpc:-32603 divide by zero"
func parseCommentType(line string) string {
if strings.HasPrefix(line, "return") {
return "return"
}

m.Errors = append(m.Errors, SMDError{i, couple[1]})
} else {
// description for argument without default value
// example: "//zenrpc:id person id"
if errorCommentRegexp.MatchString(line) {
return "error"
}

for i, a := range m.Args {
if a.Name == couple[0] {
m.Args[i].Description = couple[1]
break
}
}
}
return "argument"
}

func parseReturnComment(line string) string {
matches := returnCommentRegexp.FindStringSubmatch(line)
if len(matches) < 2 {
return ""
}

return matches[1]
}

func parseErrorComment(line string) (int, string) {
matches := errorCommentRegexp.FindStringSubmatch(line)
if len(matches) < 3 {
// should not be here
return 0, ""
}

code, err := strconv.Atoi(matches[1])
if err != nil {
return 0, ""
}

return code, matches[2]
}

func parseArgumentComment(line string) (name, alias string, hasDefault bool, defaultValue, description string) {
matches := argumentCommentRegexp.FindStringSubmatch(line)

if len(matches) < 10 {
return
}

// name index = 1
name = matches[1]
// alias index = 3
alias = matches[3]
// has default index = 4
hasDefault = matches[4] != ""
// default index = 5
defaultValue = matches[5]
// default quoted index = 7 can override non quoted string
if matches[7] != "" {
defaultValue = matches[7]
}
// description index = 9
description = strings.TrimSpace(matches[9])

return
}

func parseCommentGroup(doc *ast.CommentGroup) string {
Expand Down
194 changes: 194 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package parser

import "testing"

func Test_parseArgumentComment(t *testing.T) {
tests := []struct {
test string
line string
wantName string
wantAlias string
wantHasDefault bool
wantDefaultValue string
wantDescription string
}{
{
test: "should parse only name",
line: "var",
wantName: "var",
},
{
test: "should parse only name with spaces",
line: " var ",
wantName: "var",
},
{
test: "should parse name with alias",
line: "var(alias)",
wantName: "var",
wantAlias: "alias",
},
{
test: "should parse name with alias with spaces",
line: "var ( alias )",
wantName: "var",
wantAlias: "alias",
},
{
test: "should parse name with alias and default",
line: "var(alias)=default",
wantName: "var",
wantAlias: "alias",
wantHasDefault: true,
wantDefaultValue: "default",
},
{
test: "should parse name with alias and default with spaces",
line: "var(alias) = default",
wantName: "var",
wantAlias: "alias",
wantHasDefault: true,
wantDefaultValue: "default",
},
{
test: "should parse name with alias and quoted default",
line: "var(alias)=`default`",
wantName: "var",
wantAlias: "alias",
wantHasDefault: true,
wantDefaultValue: "default",
},
{
test: "should parse name with alias and quoted default with spaces",
line: "var(alias)= `defa ult ` ",
wantName: "var",
wantAlias: "alias",
wantHasDefault: true,
wantDefaultValue: "defa ult ",
},
{
test: "should parse name with alias, quoted default with spaces and description",
line: "var(alias)= `defa ult ` description ",
wantName: "var",
wantAlias: "alias",
wantHasDefault: true,
wantDefaultValue: "defa ult ",
wantDescription: "description",
},
{
test: "should parse name and description",
line: "var description",
wantName: "var",
wantHasDefault: false,
wantDescription: "description",
},
{
test: "should parse name and default",
line: "var=default",
wantName: "var",
wantHasDefault: true,
wantDefaultValue: "default",
},
{
test: "should parse name and default and description",
line: "var=default",
wantName: "var",
wantHasDefault: true,
wantDefaultValue: "default",
},
{
test: "should parse name and quoted default",
line: "var=`default`",
wantName: "var",
wantHasDefault: true,
wantDefaultValue: "default",
},
{
test: "should parse name and quoted default and description",
line: "var=`default` description",
wantName: "var",
wantHasDefault: true,
wantDefaultValue: "default",
wantDescription: "description",
},
{
test: "should parse name and alias and description",
line: "var(alias) description",
wantName: "var",
wantAlias: "alias",
wantDescription: "description",
},
}

for _, tt := range tests {
t.Run(tt.test, func(t *testing.T) {
gotName, gotAlias, gotHasDefault, gotDefaultValue, gotDescription := parseArgumentComment(tt.line)
if gotName != tt.wantName {
t.Errorf("parseArgumentComment() gotName = %v, want %v", gotName, tt.wantName)
}
if gotAlias != tt.wantAlias {
t.Errorf("parseArgumentComment() gotAlias = %v, want %v", gotAlias, tt.wantAlias)
}
if gotHasDefault != tt.wantHasDefault {
t.Errorf("parseArgumentComment() gotHasDefault = %v, want %v", gotHasDefault, tt.wantHasDefault)
}
if gotDefaultValue != tt.wantDefaultValue {
t.Errorf("parseArgumentComment() gotDefaultValue = %v, want %v", gotDefaultValue, tt.wantDefaultValue)
}
if gotDescription != tt.wantDescription {
t.Errorf("parseArgumentComment() gotDefaultValue = %v, want %v", gotDefaultValue, tt.wantDefaultValue)
}
})
}
}

func Test_parseCommentType(t *testing.T) {
tests := []struct {
test string
line string
want string
}{
{
test: "should detect return",
line: "return result",
want: "return",
},
{
test: "should detect return without description",
line: "return",
want: "return",
},
{
test: "should detect error",
line: "0 description",
want: "error",
},
{
test: "should detect error with negative code",
line: "-100 description",
want: "error",
},
{
test: "should detect error without description",
line: "-100",
want: "error",
},
{
test: "should detect argument",
line: "var(alias)",
want: "argument",
},
{
test: "should detect argument with numbers",
line: "var100=100 description",
want: "argument",
},
}
for _, tt := range tests {
t.Run(tt.test, func(t *testing.T) {
if got := parseCommentType(tt.line); got != tt.want {
t.Errorf("parseCommentType() = %v, want %v", got, tt.want)
}
})
}
}
6 changes: 6 additions & 0 deletions testdata/phonebook.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,9 @@ func (pb *PhoneBook) Save(p Person, replace *bool) (id uint64, err *zenrpc.Error

return pb.id, nil
}

// Prints message
//zenrpc:str(type)=`"hello world"`
func (pb *PhoneBook) Echo(str string) string {
return str
}
Loading

0 comments on commit eaa63d6

Please sign in to comment.