(goctl)fix parser issues (#3930)
This commit is contained in:
@@ -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"
|
||||||
|
)
|
||||||
|
|||||||
8
tools/goctl/pkg/parser/api/parser/testdata/duplicate_type.api
vendored
Normal file
8
tools/goctl/pkg/parser/api/parser/testdata/duplicate_type.api
vendored
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3
tools/goctl/pkg/parser/api/parser/testdata/example_base.api
vendored
Normal file
3
tools/goctl/pkg/parser/api/parser/testdata/example_base.api
vendored
Normal file
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user