From 7ba8adfc74a19ea62faccf0674c58062dc41bd22 Mon Sep 17 00:00:00 2001 From: kesonan Date: Thu, 11 Jan 2024 23:50:53 +0800 Subject: [PATCH] fix(goctl)/new parser (#3834) Co-authored-by: keson --- tools/goctl/model/sql/gen/gen_test.go | 5 +- tools/goctl/pkg/parser/api/parser/api.go | 14 ++- tools/goctl/pkg/parser/api/parser/filter.go | 13 +++ tools/goctl/pkg/parser/api/parser/parser.go | 3 + .../pkg/parser/api/parser/parser_test.go | 100 ++++++++++++++++++ .../parser/api/parser/testdata/example.api | 23 ++++ .../api/parser/testdata/service_test.api | 15 +++ tools/goctl/pkg/parser/api/scanner/scanner.go | 3 +- 8 files changed, 171 insertions(+), 5 deletions(-) diff --git a/tools/goctl/model/sql/gen/gen_test.go b/tools/goctl/model/sql/gen/gen_test.go index 676e044c..721a7e38 100644 --- a/tools/goctl/model/sql/gen/gen_test.go +++ b/tools/goctl/model/sql/gen/gen_test.go @@ -192,7 +192,10 @@ func Test_genPublicModel(t *testing.T) { code, err := g.genModelCustom(*tables[0], false) assert.NoError(t, err) assert.True(t, strings.Contains(code, "package model")) - assert.True(t, strings.Contains(code, "TestUserModel interface {\n\t\ttestUserModel\n\t}\n")) + assert.True(t, strings.Contains(code, ` TestUserModel interface { + testUserModel + withSession(session sqlx.Session) TestUserModel + }`)) assert.True(t, strings.Contains(code, "customTestUserModel struct {\n\t\t*defaultTestUserModel\n\t}\n")) assert.True(t, strings.Contains(code, "func NewTestUserModel(conn sqlx.SqlConn) TestUserModel {")) } diff --git a/tools/goctl/pkg/parser/api/parser/api.go b/tools/goctl/pkg/parser/api/parser/api.go index 10b5a1a4..6fc5584b 100644 --- a/tools/goctl/pkg/parser/api/parser/api.go +++ b/tools/goctl/pkg/parser/api/parser/api.go @@ -12,6 +12,11 @@ import ( "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token" ) +const ( + atServerGroupKey = "group:" + atServerPrefixKey = "prefix:" +) + // API is the parsed api file. type API struct { Filename string @@ -127,10 +132,13 @@ func (api *API) checkServiceStmt() error { } else { serviceName[name] = name } - var group = api.getAtServerValue(v.AtServerStmt, "prefix") + var ( + prefix = api.getAtServerValue(v.AtServerStmt, atServerPrefixKey) + group = api.getAtServerValue(v.AtServerStmt, atServerGroupKey) + ) for _, item := range v.Routes { - handlerChecker.check(item.AtHandler.Name) - path := fmt.Sprintf("[%s]:%s", group, item.Route.Format("")) + handlerChecker.checkNodeWithPrefix(group, item.AtHandler.Name) + path := fmt.Sprintf("[%s]:%s", prefix, item.Route.Format("")) pathChecker.check(ast.NewTokenNode(token.Token{ Text: path, Position: item.Route.Pos(), diff --git a/tools/goctl/pkg/parser/api/parser/filter.go b/tools/goctl/pkg/parser/api/parser/filter.go index 166ce07e..322260a0 100644 --- a/tools/goctl/pkg/parser/api/parser/filter.go +++ b/tools/goctl/pkg/parser/api/parser/filter.go @@ -1,6 +1,8 @@ package parser import ( + "fmt" + "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/ast" "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/placeholder" ) @@ -21,6 +23,17 @@ func (b *filterBuilder) check(nodes ...*ast.TokenNode) { } } +func (b *filterBuilder) checkNodeWithPrefix(prefix string, nodes ...*ast.TokenNode) { + for _, node := range nodes { + joinText:=fmt.Sprintf("%s/%s",prefix,node.Token.Text) + if _, ok := b.m[joinText]; ok { + b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName)) + } else { + b.m[joinText] = placeholder.PlaceHolder + } + } +} + func (b *filterBuilder) error() error { return b.errorManager.error() } diff --git a/tools/goctl/pkg/parser/api/parser/parser.go b/tools/goctl/pkg/parser/api/parser/parser.go index 5876697e..5cbc2943 100644 --- a/tools/goctl/pkg/parser/api/parser/parser.go +++ b/tools/goctl/pkg/parser/api/parser/parser.go @@ -385,6 +385,9 @@ func (p *Parser) parsePathExpr() *ast.PathExpr { } values = append(values, p.curTok) + if p.peekTokenIs(token.LPAREN, token.Returns, token.AT_DOC, token.AT_HANDLER, token.SEMICOLON, token.RBRACE){ + break + } if p.notExpectPeekTokenGotComment(p.curTokenNode().PeekFirstLeadingComment(), token.COLON, token.IDENT, token.INT) { return nil } diff --git a/tools/goctl/pkg/parser/api/parser/parser_test.go b/tools/goctl/pkg/parser/api/parser/parser_test.go index 9fac7a5f..c37b03a7 100644 --- a/tools/goctl/pkg/parser/api/parser/parser_test.go +++ b/tools/goctl/pkg/parser/api/parser/parser_test.go @@ -521,6 +521,19 @@ func TestParser_Parse_service(t *testing.T) { LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}), RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}), Routes: []*ast.ServiceItemStmt{ + { + AtHandler: &ast.AtHandlerStmt{ + AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}), + Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "root"}), + }, + Route: &ast.RouteStmt{ + Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "get"}), + Path: &ast.PathExpr{Value: ast.NewTokenNode(token.Token{ + Type: token.PATH, + Text: "/", + })}, + }, + }, { AtHandler: &ast.AtHandlerStmt{ AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}), @@ -557,6 +570,93 @@ func TestParser_Parse_service(t *testing.T) { LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}), RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}), Routes: []*ast.ServiceItemStmt{ + { + AtDoc: &ast.AtDocLiteralStmt{ + AtDoc: ast.NewTokenNode(token.Token{Type: token.AT_DOC, Text: "@doc"}), + Value: ast.NewTokenNode(token.Token{Type: token.STRING, Text: `"bar"`}), + }, + AtHandler: &ast.AtHandlerStmt{ + AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}), + Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "root"}), + }, + Route: &ast.RouteStmt{ + Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "get"}), + Path: &ast.PathExpr{ + Value: ast.NewTokenNode(token.Token{ + Type: token.PATH, + Text: "/", + }), + }, + Request: &ast.BodyStmt{ + LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}), + Body: &ast.BodyExpr{ + Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}), + }, + RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}), + }, + }, + }, + { + AtDoc: &ast.AtDocLiteralStmt{ + AtDoc: ast.NewTokenNode(token.Token{Type: token.AT_DOC, Text: "@doc"}), + Value: ast.NewTokenNode(token.Token{Type: token.STRING, Text: `"bar"`}), + }, + AtHandler: &ast.AtHandlerStmt{ + AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}), + Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "root2"}), + }, + Route: &ast.RouteStmt{ + Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "get"}), + Path: &ast.PathExpr{ + Value: ast.NewTokenNode(token.Token{ + Type: token.PATH, + Text: "/", + }), + }, + Returns: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "returns"}), + Response: &ast.BodyStmt{ + LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}), + Body: &ast.BodyExpr{ + Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}), + }, + RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}), + }, + }, + }, + { + AtDoc: &ast.AtDocLiteralStmt{ + AtDoc: ast.NewTokenNode(token.Token{Type: token.AT_DOC, Text: "@doc"}), + Value: ast.NewTokenNode(token.Token{Type: token.STRING, Text: `"bar"`}), + }, + AtHandler: &ast.AtHandlerStmt{ + AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}), + Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "root3"}), + }, + Route: &ast.RouteStmt{ + Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "get"}), + Path: &ast.PathExpr{ + Value: ast.NewTokenNode(token.Token{ + Type: token.PATH, + Text: "/", + }), + }, + Request: &ast.BodyStmt{ + LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}), + Body: &ast.BodyExpr{ + Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}), + }, + RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}), + }, + Returns: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "returns"}), + Response: &ast.BodyStmt{ + LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}), + Body: &ast.BodyExpr{ + Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Bar"}), + }, + RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}), + }, + }, + }, { AtDoc: &ast.AtDocLiteralStmt{ AtDoc: ast.NewTokenNode(token.Token{Type: token.AT_DOC, Text: "@doc"}), diff --git a/tools/goctl/pkg/parser/api/parser/testdata/example.api b/tools/goctl/pkg/parser/api/parser/testdata/example.api index 2a1f8d3c..97ff1dd8 100644 --- a/tools/goctl/pkg/parser/api/parser/testdata/example.api +++ b/tools/goctl/pkg/parser/api/parser/testdata/example.api @@ -93,6 +93,12 @@ type ( NestDemoResp2 { *Nest `json:"nest"` } + RootReq{ + + } + RootResp{ + + } ) @server ( @@ -124,6 +130,23 @@ service example { prefix: /v1/v2 timeout: 100ms ) +service example { + @doc ( + desc: "path demo" + ) + @handler postPath + post /example/path (PostPathReq) returns (PostPathResp) + + @handler root + post / (RootReq) returns (RootResp) +} + +@server ( + group: path2 + middleware: Path + prefix: /v1/v3 + timeout: 100ms +) service example { @doc ( desc: "path demo" diff --git a/tools/goctl/pkg/parser/api/parser/testdata/service_test.api b/tools/goctl/pkg/parser/api/parser/testdata/service_test.api index f967e731..7d9323d3 100644 --- a/tools/goctl/pkg/parser/api/parser/testdata/service_test.api +++ b/tools/goctl/pkg/parser/api/parser/testdata/service_test.api @@ -1,4 +1,7 @@ service foo { + @handler root + get / + @handler bar get /ping @@ -7,6 +10,18 @@ service foo { } service bar { + @doc "bar" + @handler root + get / (Foo) + + @doc "bar" + @handler root2 + get / returns (Foo) + + @doc "bar" + @handler root3 + get / (Foo) returns (Bar) + @doc "bar" @handler foo get /foo/:bar (Foo) diff --git a/tools/goctl/pkg/parser/api/scanner/scanner.go b/tools/goctl/pkg/parser/api/scanner/scanner.go index 87767c17..0b86a79d 100644 --- a/tools/goctl/pkg/parser/api/scanner/scanner.go +++ b/tools/goctl/pkg/parser/api/scanner/scanner.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token" + "github.com/zeromicro/go-zero/tools/goctl/util/pathx" ) const ( @@ -650,7 +651,7 @@ func NewScanner(filename string, src interface{}) (*Scanner, error) { } func readData(filename string, src interface{}) ([]byte, error) { - if strings.HasSuffix(filename, ".api") { + if strings.HasSuffix(filename, ".api") &&pathx.FileExists(filename){ data, err := os.ReadFile(filename) if err != nil { return nil, err