(goctl)fix parser issues (#3930)

master^2
kesonan 9 months ago committed by GitHub
parent a5d2b971a1
commit e08ba2fee8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -84,6 +84,19 @@ func (t *TokenNode) SetLeadingCommentGroup(cg CommentGroup) {
t.LeadingCommentGroup = cg t.LeadingCommentGroup = cg
} }
// RawText returns the node's raw text.
func (t *TokenNode) RawText() string {
text := t.Token.Text
if strings.HasPrefix(text, "`") {
text = strings.TrimPrefix(text, "`")
text = strings.TrimSuffix(text, "`")
} else if strings.HasPrefix(text, `"`) {
text = strings.TrimPrefix(text, `"`)
text = strings.TrimSuffix(text, `"`)
}
return text
}
func (t *TokenNode) HasLeadingCommentGroup() bool { func (t *TokenNode) HasLeadingCommentGroup() bool {
return t.LeadingCommentGroup.Valid() || t.leadingFlag return t.LeadingCommentGroup.Valid() || t.leadingFlag
} }

@ -108,6 +108,8 @@ func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
} }
func (a *Analyzer) convert2Spec() error { func (a *Analyzer) convert2Spec() error {
a.fillInfo()
if err := a.fillTypes(); err != nil { if err := a.fillTypes(); err != nil {
return err return err
} }
@ -128,7 +130,7 @@ func (a *Analyzer) convert2Spec() error {
groups = append(groups, v) groups = append(groups, v)
} }
sort.SliceStable(groups, func(i, j int) bool { sort.SliceStable(groups, func(i, j int) bool {
return groups[i].Annotation.Properties["group"] < groups[j].Annotation.Properties["group"] return groups[i].Annotation.Properties[groupKeyText] < groups[j].Annotation.Properties[groupKeyText]
}) })
a.spec.Service.Groups = groups a.spec.Service.Groups = groups
@ -150,7 +152,11 @@ func (a *Analyzer) convertKV(kv []*ast.KVExpr) map[string]string {
var ret = map[string]string{} var ret = map[string]string{}
for _, v := range kv { for _, v := range kv {
key := strings.TrimSuffix(v.Key.Token.Text, ":") key := strings.TrimSuffix(v.Key.Token.Text, ":")
ret[key] = v.Value.Token.Text if key == summaryKeyText {
ret[key] = v.Value.RawText()
} else {
ret[key] = v.Value.Token.Text
}
} }
return ret return ret
} }
@ -270,6 +276,27 @@ func (a *Analyzer) fillService() error {
return nil return nil
} }
func (a *Analyzer) fillInfo() {
properties := make(map[string]string)
if a.api.info != nil {
for _, kv := range a.api.info.Values {
key := kv.Key.Token.Text
properties[strings.TrimSuffix(key, ":")] = kv.Value.RawText()
}
}
a.spec.Info.Properties = properties
infoKeyValue := make(map[string]string)
for key, value := range properties {
titleKey := strings.Title(strings.TrimSuffix(key, ":"))
infoKeyValue[titleKey] = value
}
a.spec.Info.Title = infoKeyValue[infoTitleKey]
a.spec.Info.Desc = infoKeyValue[infoDescKey]
a.spec.Info.Version = infoKeyValue[infoVersionKey]
a.spec.Info.Author = infoKeyValue[infoAuthorKey]
a.spec.Info.Email = infoKeyValue[infoEmailKey]
}
func (a *Analyzer) fillTypes() error { func (a *Analyzer) fillTypes() error {
for _, item := range a.api.TypeStmt { for _, item := range a.api.TypeStmt {
switch v := (item).(type) { switch v := (item).(type) {

@ -8,14 +8,40 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/assertx" "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/assertx"
) )
func Test_Parse(t *testing.T) { func Test_Parse(t *testing.T) {
t.Run("valid", func(t *testing.T) { t.Run("valid", func(t *testing.T) {
_, err := Parse("./testdata/example.api", nil) apiSpec, err := Parse("./testdata/example.api", nil)
assert.Nil(t, err) assert.Nil(t, err)
ast := assert.New(t)
ast.Equal(spec.Info{
Title: "type title here",
Desc: "type desc here",
Version: "type version here",
Author: "type author here",
Email: "type email here",
Properties: map[string]string{
"title": "type title here",
"desc": "type desc here",
"version": "type version here",
"author": "type author here",
"email": "type email here",
},
}, apiSpec.Info)
ast.True(func() bool {
for _, group := range apiSpec.Service.Groups {
value, ok := group.Annotation.Properties["summary"]
if ok {
return value == "test"
}
}
return false
}())
}) })
t.Run("invalid", func(t *testing.T) { t.Run("invalid", func(t *testing.T) {
data, err := os.ReadFile("./testdata/invalid.api") data, err := os.ReadFile("./testdata/invalid.api")
@ -46,4 +72,9 @@ func Test_Parse(t *testing.T) {
_, err := Parse("./testdata/link_import.api", nil) _, err := Parse("./testdata/link_import.api", nil)
assert.Nil(t, err) assert.Nil(t, err)
}) })
t.Run("duplicate_types", func(t *testing.T) {
_, err := Parse("./testdata/duplicate_type.api", nil)
assertx.Error(t, err)
})
} }

@ -4,12 +4,12 @@ import (
"fmt" "fmt"
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/ast" "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/ast"
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/placeholder" "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
) )
type filterBuilder struct { type filterBuilder struct {
filename string filename string
m map[string]placeholder.Type m map[string]token.Position
checkExprName string checkExprName string
errorManager *errorManager errorManager *errorManager
} }
@ -17,10 +17,10 @@ type filterBuilder struct {
func (b *filterBuilder) check(nodes ...*ast.TokenNode) { func (b *filterBuilder) check(nodes ...*ast.TokenNode) {
for _, node := range nodes { for _, node := range nodes {
fileNodeText := fmt.Sprintf("%s/%s", b.filename, node.Token.Text) fileNodeText := fmt.Sprintf("%s/%s", b.filename, node.Token.Text)
if _, ok := b.m[fileNodeText]; ok { if pos, ok := b.m[fileNodeText]; ok && pos != node.Token.Position {
b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName)) b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName))
} else { } else {
b.m[fileNodeText] = placeholder.PlaceHolder b.m[fileNodeText] = node.Token.Position
} }
} }
} }
@ -28,10 +28,10 @@ func (b *filterBuilder) check(nodes ...*ast.TokenNode) {
func (b *filterBuilder) checkNodeWithPrefix(prefix string, nodes ...*ast.TokenNode) { func (b *filterBuilder) checkNodeWithPrefix(prefix string, nodes ...*ast.TokenNode) {
for _, node := range nodes { for _, node := range nodes {
joinText := fmt.Sprintf("%s/%s", prefix, node.Token.Text) joinText := fmt.Sprintf("%s/%s", prefix, node.Token.Text)
if _, ok := b.m[joinText]; ok { if pos, ok := b.m[joinText]; ok && pos != node.Token.Position {
b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName)) b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName))
} else { } else {
b.m[joinText] = placeholder.PlaceHolder b.m[joinText] = node.Token.Position
} }
} }
} }
@ -51,7 +51,7 @@ func newFilter() *filter {
func (f *filter) addCheckItem(filename, checkExprName string) *filterBuilder { func (f *filter) addCheckItem(filename, checkExprName string) *filterBuilder {
b := &filterBuilder{ b := &filterBuilder{
filename: filename, filename: filename,
m: make(map[string]placeholder.Type), m: make(map[string]token.Position),
checkExprName: checkExprName, checkExprName: checkExprName,
errorManager: newErrorManager(), errorManager: newErrorManager(),
} }

@ -12,7 +12,17 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token" "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
) )
const idAPI = "api" const (
idAPI = "api"
summaryKeyExprText = "summary:"
summaryKeyText = "summary"
groupKeyText = "group"
infoTitleKey = "Title"
infoDescKey = "Desc"
infoVersionKey = "Version"
infoAuthorKey = "Author"
infoEmailKey = "Email"
)
// Parser is the parser for api file. // Parser is the parser for api file.
type Parser struct { type Parser struct {
@ -1134,7 +1144,7 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr {
var valueTok token.Token var valueTok token.Token
var leadingCommentGroup ast.CommentGroup var leadingCommentGroup ast.CommentGroup
if p.notExpectPeekToken(token.QUO, token.DURATION, token.IDENT, token.INT) { if p.notExpectPeekToken(token.QUO, token.DURATION, token.IDENT, token.INT, token.STRING) {
return nil return nil
} }
@ -1144,13 +1154,24 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr {
} }
slashTok := p.curTok slashTok := p.curTok
var pathText = slashTok.Text
if !p.advanceIfPeekTokenIs(token.IDENT) { if !p.advanceIfPeekTokenIs(token.IDENT) {
return nil return nil
} }
pathText += p.curTok.Text
if p.peekTokenIs(token.SUB) { // 解析 abc-efg 格式
if !p.nextToken() {
return nil
}
pathText += p.curTok.Text
if !p.advanceIfPeekTokenIs(token.IDENT) {
return nil
}
pathText += p.curTok.Text
}
idTok := p.curTok
valueTok = token.Token{ valueTok = token.Token{
Text: slashTok.Text + idTok.Text, Text: pathText,
Position: slashTok.Position, Position: slashTok.Position,
} }
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
@ -1170,6 +1191,22 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr {
return nil return nil
} }
valueTok = p.curTok
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
node := ast.NewTokenNode(valueTok)
node.SetLeadingCommentGroup(leadingCommentGroup)
expr.Value = node
return expr
} else if p.peekTokenIs(token.STRING) {
if expr.Key.Token.Text != summaryKeyExprText {
if p.notExpectPeekToken(token.QUO, token.DURATION, token.IDENT, token.INT) {
return nil
}
}
if !p.nextToken() {
return nil
}
valueTok = p.curTok valueTok = p.curTok
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
node := ast.NewTokenNode(valueTok) node := ast.NewTokenNode(valueTok)
@ -1221,13 +1258,25 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr {
} }
slashTok := p.curTok slashTok := p.curTok
var pathText = valueTok.Text
pathText += slashTok.Text
if !p.advanceIfPeekTokenIs(token.IDENT) { if !p.advanceIfPeekTokenIs(token.IDENT) {
return nil return nil
} }
pathText += p.curTok.Text
if p.peekTokenIs(token.SUB) { // 解析 abc-efg 格式
if !p.nextToken() {
return nil
}
pathText += p.curTok.Text
if !p.advanceIfPeekTokenIs(token.IDENT) {
return nil
}
pathText += p.curTok.Text
}
idTok := p.curTok
valueTok = token.Token{ valueTok = token.Token{
Text: valueTok.Text + slashTok.Text + idTok.Text, Text: pathText,
Position: valueTok.Position, Position: valueTok.Position,
} }
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup leadingCommentGroup = p.curTokenNode().LeadingCommentGroup

@ -299,6 +299,11 @@ func TestParser_Parse_atServerStmt(t *testing.T) {
"timeout6:": "10ns", "timeout6:": "10ns",
"timeout7:": "1h10m10s10ms10µs10ns", "timeout7:": "1h10m10s10ms10µs10ns",
"maxBytes:": `1024`, "maxBytes:": `1024`,
"prefix:": "/v1",
"prefix1:": "/v1/v2_test/v2-beta",
"prefix2:": "v1/v2_test/v2-beta",
"prefix3:": "v1/v2_",
"summary:": `"test"`,
} }
p := New("foo.api", atServerTestAPI) p := New("foo.api", atServerTestAPI)
@ -349,6 +354,8 @@ func TestParser_Parse_atServerStmt(t *testing.T) {
`@server(foo:/v1/v2`, `@server(foo:/v1/v2`,
`@server(foo: m1,`, `@server(foo: m1,`,
`@server(foo: m1,)`, `@server(foo: m1,)`,
`@server(foo: v1/v2-)`,
`@server(foo:"test")`,
} }
for _, v := range testData { for _, v := range testData {
p := New("foo.api", v) p := New("foo.api", v)

@ -13,4 +13,9 @@
timeout6: 10ns timeout6: 10ns
timeout7: 1h10m10s10ms10µs10ns timeout7: 1h10m10s10ms10µs10ns
maxBytes: 1024 maxBytes: 1024
prefix: /v1
prefix1: /v1/v2_test/v2-beta
prefix2: v1/v2_test/v2-beta
prefix3: v1/v2_
summary:"test"
) )

@ -0,0 +1,8 @@
syntax = "v1"
type Example{
A string
}
type Example{
B string
}

@ -188,3 +188,17 @@ service example {
post /example/nest2 (NestDemoReq2) returns (NestDemoResp2) post /example/nest2 (NestDemoReq2) returns (NestDemoResp2)
} }
@server (
group: /g1/g2_test/g2_beta
prefix: /v1/v2_test/v2-beta
summary: "test"
)
service example {
@handler nestDemo1
post /a/b_c/d-e/:f/123/g (NestDemoReq1) returns (NestDemoResp1)
@handler nestDemo2
post /example/nest2 (NestDemoReq2) returns (NestDemoResp2)
}

@ -0,0 +1,3 @@
syntax = "v1"
type Base{}

@ -1,5 +1,7 @@
syntax = "v1" syntax = "v1"
import "example_base.api"
info( info(
title: "type title here" title: "type title here"
desc: "type desc here" desc: "type desc here"

@ -1,5 +1,7 @@
syntax = "v1" syntax = "v1"
import "example_base.api"
info( info(
title: "type title here" title: "type title here"
desc: "type desc here" desc: "type desc here"

@ -5,7 +5,6 @@ import (
"go/build" "go/build"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -51,12 +50,7 @@ func TestRpcGenerate(t *testing.T) {
err = g.Generate(ctx) err = g.Generate(ctx)
assert.Nil(t, err) assert.Nil(t, err)
_, err = execx.Run("go test "+projectName, projectDir) _, err = execx.Run("go test "+projectName, projectDir)
if err != nil { assert.Error(t, err)
assert.True(t, func() bool {
return strings.Contains(err.Error(),
"not in GOROOT") || strings.Contains(err.Error(), "cannot find package")
}())
}
}) })
// case go mod // case go mod

Loading…
Cancel
Save