mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-13 18:00:00 +08:00
feat(goctl): Add api parser (#2585)
This commit is contained in:
@@ -13,8 +13,11 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/zeromicro/go-zero/core/errorx"
|
"github.com/zeromicro/go-zero/core/errorx"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/parser"
|
"github.com/zeromicro/go-zero/tools/goctl/api/parser"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/util"
|
"github.com/zeromicro/go-zero/tools/goctl/api/util"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
|
||||||
|
apiF "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/format"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
|
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,6 +93,10 @@ func apiFormatReader(reader io.Reader, filename string, skipCheckDeclare bool) e
|
|||||||
|
|
||||||
// ApiFormatByPath format api from file path
|
// ApiFormatByPath format api from file path
|
||||||
func ApiFormatByPath(apiFilePath string, skipCheckDeclare bool) error {
|
func ApiFormatByPath(apiFilePath string, skipCheckDeclare bool) error {
|
||||||
|
if env.UseExperimental() {
|
||||||
|
return apiF.File(apiFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
data, err := os.ReadFile(apiFilePath)
|
data, err := os.ReadFile(apiFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func sweep() error {
|
|||||||
|
|
||||||
tm := time.Unix(seconds, 0)
|
tm := time.Unix(seconds, 0)
|
||||||
if tm.Before(keepTime) {
|
if tm.Before(keepTime) {
|
||||||
if err := os.Remove(fpath); err != nil {
|
if err := os.RemoveAll(fpath); err != nil {
|
||||||
fmt.Println(aurora.Red(fmt.Sprintf("failed to remove file: %s", fpath)))
|
fmt.Println(aurora.Red(fmt.Sprintf("failed to remove file: %s", fpath)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/tools/goctl/api/parser/g4/ast"
|
"github.com/zeromicro/go-zero/tools/goctl/api/parser/g4/ast"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/parser/g4/gen/api"
|
"github.com/zeromicro/go-zero/tools/goctl/api/parser/g4/gen/api"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
|
||||||
|
apiParser "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
type parser struct {
|
type parser struct {
|
||||||
@@ -17,6 +19,10 @@ type parser struct {
|
|||||||
|
|
||||||
// Parse parses the api file
|
// Parse parses the api file
|
||||||
func Parse(filename string) (*spec.ApiSpec, error) {
|
func Parse(filename string) (*spec.ApiSpec, error) {
|
||||||
|
if env.UseExperimental() {
|
||||||
|
return apiParser.Parse(filename, "")
|
||||||
|
}
|
||||||
|
|
||||||
astParser := ast.NewParser(ast.WithParserPrefix(filepath.Base(filename)), ast.WithParserDebug())
|
astParser := ast.NewParser(ast.WithParserPrefix(filepath.Base(filename)), ast.WithParserDebug())
|
||||||
parsedApi, err := astParser.Parse(filename)
|
parsedApi, err := astParser.Parse(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
5
tools/goctl/api/parser/testdata/test.api
vendored
5
tools/goctl/api/parser/testdata/test.api
vendored
@@ -11,6 +11,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// service doc
|
// service doc
|
||||||
|
@server(
|
||||||
|
group: test
|
||||||
|
middleware: m1,m2
|
||||||
|
prefix: v1
|
||||||
|
)
|
||||||
service greet-api {
|
service greet-api {
|
||||||
// handler doc
|
// handler doc
|
||||||
@handler GreetHandler // handler comment
|
@handler GreetHandler // handler comment
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ type (
|
|||||||
|
|
||||||
// ApiSpec describes an api file
|
// ApiSpec describes an api file
|
||||||
ApiSpec struct {
|
ApiSpec struct {
|
||||||
Info Info
|
Info Info // Deprecated: useless expression
|
||||||
Syntax ApiSyntax
|
Syntax ApiSyntax // Deprecated: useless expression
|
||||||
Imports []Import
|
Imports []Import // Deprecated: useless expression
|
||||||
Types []Type
|
Types []Type
|
||||||
Service Service
|
Service Service
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,7 @@ type (
|
|||||||
|
|
||||||
// Route describes api route
|
// Route describes api route
|
||||||
Route struct {
|
Route struct {
|
||||||
|
// Deprecated: Use Service AtServer instead.
|
||||||
AtServerAnnotation Annotation
|
AtServerAnnotation Annotation
|
||||||
Method string
|
Method string
|
||||||
Path string
|
Path string
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// BuildVersion is the version of goctl.
|
// BuildVersion is the version of goctl.
|
||||||
const BuildVersion = "1.5.0"
|
const BuildVersion = "1.5.1"
|
||||||
|
|
||||||
var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5}
|
var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5}
|
||||||
|
|
||||||
|
|||||||
16
tools/goctl/pkg/env/env.go
vendored
16
tools/goctl/pkg/env/env.go
vendored
@@ -25,11 +25,14 @@ const (
|
|||||||
GoctlDebug = "GOCTL_DEBUG"
|
GoctlDebug = "GOCTL_DEBUG"
|
||||||
GoctlCache = "GOCTL_CACHE"
|
GoctlCache = "GOCTL_CACHE"
|
||||||
GoctlVersion = "GOCTL_VERSION"
|
GoctlVersion = "GOCTL_VERSION"
|
||||||
|
GoctlExperimental = "GOCTL_EXPERIMENTAL"
|
||||||
ProtocVersion = "PROTOC_VERSION"
|
ProtocVersion = "PROTOC_VERSION"
|
||||||
ProtocGenGoVersion = "PROTOC_GEN_GO_VERSION"
|
ProtocGenGoVersion = "PROTOC_GEN_GO_VERSION"
|
||||||
ProtocGenGoGRPCVersion = "PROTO_GEN_GO_GRPC_VERSION"
|
ProtocGenGoGRPCVersion = "PROTO_GEN_GO_GRPC_VERSION"
|
||||||
|
|
||||||
envFileDir = "env"
|
envFileDir = "env"
|
||||||
|
ExperimentalOn = "on"
|
||||||
|
ExperimentalOff = "off"
|
||||||
)
|
)
|
||||||
|
|
||||||
// init initializes the goctl environment variables, the environment variables of the function are set in order,
|
// init initializes the goctl environment variables, the environment variables of the function are set in order,
|
||||||
@@ -56,6 +59,8 @@ func init() {
|
|||||||
if value := existsEnv.GetStringOr(GoctlCache, ""); value != "" {
|
if value := existsEnv.GetStringOr(GoctlCache, ""); value != "" {
|
||||||
goctlEnv.SetKV(GoctlCache, value)
|
goctlEnv.SetKV(GoctlCache, value)
|
||||||
}
|
}
|
||||||
|
experimental:=existsEnv.GetOr(GoctlExperimental,ExperimentalOff)
|
||||||
|
goctlEnv.SetKV(GoctlExperimental,experimental)
|
||||||
}
|
}
|
||||||
if !goctlEnv.HasKey(GoctlHome) {
|
if !goctlEnv.HasKey(GoctlHome) {
|
||||||
goctlEnv.SetKV(GoctlHome, defaultGoctlHome)
|
goctlEnv.SetKV(GoctlHome, defaultGoctlHome)
|
||||||
@@ -69,7 +74,12 @@ func init() {
|
|||||||
goctlEnv.SetKV(GoctlCache, cacheDir)
|
goctlEnv.SetKV(GoctlCache, cacheDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !goctlEnv.HasKey(GoctlExperimental){
|
||||||
|
goctlEnv.SetKV(GoctlExperimental, ExperimentalOff)
|
||||||
|
}
|
||||||
|
|
||||||
goctlEnv.SetKV(GoctlVersion, version.BuildVersion)
|
goctlEnv.SetKV(GoctlVersion, version.BuildVersion)
|
||||||
|
|
||||||
protocVer, _ := protoc.Version()
|
protocVer, _ := protoc.Version()
|
||||||
goctlEnv.SetKV(ProtocVersion, protocVer)
|
goctlEnv.SetKV(ProtocVersion, protocVer)
|
||||||
|
|
||||||
@@ -92,6 +102,10 @@ func GetOr(key, def string) string {
|
|||||||
return goctlEnv.GetStringOr(key, def)
|
return goctlEnv.GetStringOr(key, def)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UseExperimental() bool {
|
||||||
|
return GetOr(GoctlExperimental, ExperimentalOff) == ExperimentalOn
|
||||||
|
}
|
||||||
|
|
||||||
func readEnv(goctlHome string) *sortedmap.SortedMap {
|
func readEnv(goctlHome string) *sortedmap.SortedMap {
|
||||||
envFile := filepath.Join(goctlHome, envFileDir)
|
envFile := filepath.Join(goctlHome, envFileDir)
|
||||||
data, err := os.ReadFile(envFile)
|
data, err := os.ReadFile(envFile)
|
||||||
|
|||||||
32
tools/goctl/pkg/parser/api/assertx/error.go
Normal file
32
tools/goctl/pkg/parser/api/assertx/error.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package assertx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorOrigin is used to assert error and print source and error.
|
||||||
|
func ErrorOrigin(t *testing.T, source string, err ...error) {
|
||||||
|
if len(err) == 0 {
|
||||||
|
t.Fatalf("expected errors, got 0 error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, e := range err {
|
||||||
|
fmt.Printf("<%s>: %v\n", source, e)
|
||||||
|
assert.Error(t, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is used to assert error.
|
||||||
|
func Error(t *testing.T, err ...error) {
|
||||||
|
if len(err) == 0 {
|
||||||
|
t.Fatalf("expected errors, got 0 error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, e := range err {
|
||||||
|
fmt.Println(e)
|
||||||
|
assert.Error(t, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
223
tools/goctl/pkg/parser/api/ast/ast.go
Normal file
223
tools/goctl/pkg/parser/api/ast/ast.go
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Node represents a node in the AST.
|
||||||
|
type Node interface {
|
||||||
|
// Pos returns the position of the first character belonging to the node.
|
||||||
|
Pos() token.Position
|
||||||
|
// End returns the position of the first character immediately after the node.
|
||||||
|
End() token.Position
|
||||||
|
// Format returns the node's text after format.
|
||||||
|
Format(...string) string
|
||||||
|
// HasHeadCommentGroup returns true if the node has head comment group.
|
||||||
|
HasHeadCommentGroup() bool
|
||||||
|
// HasLeadingCommentGroup returns true if the node has leading comment group.
|
||||||
|
HasLeadingCommentGroup() bool
|
||||||
|
// CommentGroup returns the node's head comment group and leading comment group.
|
||||||
|
CommentGroup() (head, leading CommentGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stmt represents a statement in the AST.
|
||||||
|
type Stmt interface {
|
||||||
|
Node
|
||||||
|
stmtNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expr represents an expression in the AST.
|
||||||
|
type Expr interface {
|
||||||
|
Node
|
||||||
|
exprNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AST represents a parsed API file.
|
||||||
|
type AST struct {
|
||||||
|
Filename string
|
||||||
|
Stmts []Stmt
|
||||||
|
readPosition int
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenNode represents a token node in the AST.
|
||||||
|
type TokenNode struct {
|
||||||
|
// HeadCommentGroup are the comments in prev lines.
|
||||||
|
HeadCommentGroup CommentGroup
|
||||||
|
// Token is the token of the node.
|
||||||
|
Token token.Token
|
||||||
|
// LeadingCommentGroup are the tail comments in same line.
|
||||||
|
LeadingCommentGroup CommentGroup
|
||||||
|
|
||||||
|
// headFlag and leadingFlag is a comment flag only used in transfer another Node to TokenNode,
|
||||||
|
// headFlag's value is true do not represent HeadCommentGroup is not empty,
|
||||||
|
// leadingFlag's values is true do not represent LeadingCommentGroup is not empty.
|
||||||
|
headFlag, leadingFlag bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTokenNode creates and returns a new TokenNode.
|
||||||
|
func NewTokenNode(tok token.Token) *TokenNode {
|
||||||
|
return &TokenNode{Token: tok}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmptyString returns true if the node is empty string.
|
||||||
|
func (t *TokenNode) IsEmptyString() bool {
|
||||||
|
return t.Equal("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZeroString returns true if the node is zero string.
|
||||||
|
func (t *TokenNode) IsZeroString() bool {
|
||||||
|
return t.Equal(`""`) || t.Equal("``")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if the node's text is equal to the given text.
|
||||||
|
func (t *TokenNode) Equal(s string) bool {
|
||||||
|
return t.Token.Text == s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLeadingCommentGroup sets the node's leading comment group.
|
||||||
|
func (t *TokenNode) SetLeadingCommentGroup(cg CommentGroup) {
|
||||||
|
t.LeadingCommentGroup = cg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenNode) HasLeadingCommentGroup() bool {
|
||||||
|
return t.LeadingCommentGroup.Valid() || t.leadingFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenNode) HasHeadCommentGroup() bool {
|
||||||
|
return t.HeadCommentGroup.Valid() || t.headFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenNode) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return t.HeadCommentGroup, t.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeekFirstLeadingComment returns the first leading comment of the node.
|
||||||
|
func (t *TokenNode) PeekFirstLeadingComment() *CommentStmt {
|
||||||
|
if len(t.LeadingCommentGroup) > 0 {
|
||||||
|
return t.LeadingCommentGroup[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeekFirstHeadComment returns the first head comment of the node.
|
||||||
|
func (t *TokenNode) PeekFirstHeadComment() *CommentStmt {
|
||||||
|
if len(t.HeadCommentGroup) > 0 {
|
||||||
|
return t.HeadCommentGroup[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenNode) Format(prefix ...string) string {
|
||||||
|
p := peekOne(prefix)
|
||||||
|
var textList []string
|
||||||
|
for _, v := range t.HeadCommentGroup {
|
||||||
|
textList = append(textList, v.Format(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenText = p + t.Token.Text
|
||||||
|
var validLeadingCommentGroup CommentGroup
|
||||||
|
for _, e := range t.LeadingCommentGroup {
|
||||||
|
if util.IsEmptyStringOrWhiteSpace(e.Comment.Text) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
validLeadingCommentGroup = append(validLeadingCommentGroup, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(validLeadingCommentGroup) > 0 {
|
||||||
|
tokenText = tokenText + WhiteSpace + t.LeadingCommentGroup.Join(WhiteSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
textList = append(textList, tokenText)
|
||||||
|
return strings.Join(textList, NewLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenNode) Pos() token.Position {
|
||||||
|
if len(t.HeadCommentGroup) > 0 {
|
||||||
|
return t.PeekFirstHeadComment().Pos()
|
||||||
|
}
|
||||||
|
return t.Token.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenNode) End() token.Position {
|
||||||
|
if len(t.LeadingCommentGroup) > 0 {
|
||||||
|
return t.LeadingCommentGroup[len(t.LeadingCommentGroup)-1].End()
|
||||||
|
}
|
||||||
|
return t.Token.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format formats the AST.
|
||||||
|
func (a *AST) Format(w io.Writer) {
|
||||||
|
fw := NewWriter(w)
|
||||||
|
defer fw.Flush()
|
||||||
|
for idx, e := range a.Stmts {
|
||||||
|
if e.Format() == NilIndent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fw.Write(withNode(e))
|
||||||
|
fw.NewLine()
|
||||||
|
switch e.(type) {
|
||||||
|
case *SyntaxStmt:
|
||||||
|
fw.NewLine()
|
||||||
|
case *ImportGroupStmt:
|
||||||
|
fw.NewLine()
|
||||||
|
case *ImportLiteralStmt:
|
||||||
|
if idx < len(a.Stmts)-1 {
|
||||||
|
_, ok := a.Stmts[idx+1].(*ImportLiteralStmt)
|
||||||
|
if !ok {
|
||||||
|
fw.NewLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *InfoStmt:
|
||||||
|
fw.NewLine()
|
||||||
|
case *ServiceStmt:
|
||||||
|
fw.NewLine()
|
||||||
|
case *TypeGroupStmt:
|
||||||
|
fw.NewLine()
|
||||||
|
case *TypeLiteralStmt:
|
||||||
|
fw.NewLine()
|
||||||
|
case *CommentStmt:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatForUnitTest formats the AST for unit test.
|
||||||
|
func (a *AST) FormatForUnitTest(w io.Writer) {
|
||||||
|
fw := NewWriter(w)
|
||||||
|
defer fw.Flush()
|
||||||
|
for _, e := range a.Stmts {
|
||||||
|
text := e.Format()
|
||||||
|
if text == NilIndent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fw.WriteText(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print prints the AST.
|
||||||
|
func (a *AST) Print() {
|
||||||
|
_ = Print(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyntaxError represents a syntax error.
|
||||||
|
func SyntaxError(pos token.Position, format string, v ...interface{}) error {
|
||||||
|
return fmt.Errorf("syntax error: %s %s", pos.String(), fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DuplicateStmtError represents a duplicate statement error.
|
||||||
|
func DuplicateStmtError(pos token.Position, msg string) error {
|
||||||
|
return fmt.Errorf("duplicate declaration: %s %s", pos.String(), msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func peekOne(list []string) string {
|
||||||
|
if len(list) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return list[0]
|
||||||
|
}
|
||||||
75
tools/goctl/pkg/parser/api/ast/comment.go
Normal file
75
tools/goctl/pkg/parser/api/ast/comment.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommentGroup represents a list of comments.
|
||||||
|
type CommentGroup []*CommentStmt
|
||||||
|
|
||||||
|
// List returns the list of comments.
|
||||||
|
func (cg CommentGroup) List() []string {
|
||||||
|
var list = make([]string, 0, len(cg))
|
||||||
|
for _, v := range cg {
|
||||||
|
comment := v.Comment.Text
|
||||||
|
if util.IsEmptyStringOrWhiteSpace(comment) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
list = append(list, comment)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// String joins and returns the comment text.
|
||||||
|
func (cg CommentGroup) String() string {
|
||||||
|
return cg.Join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join joins the comments with the given separator.
|
||||||
|
func (cg CommentGroup) Join(sep string) string {
|
||||||
|
if !cg.Valid() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
list := cg.List()
|
||||||
|
return strings.Join(list, sep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns true if the comment is valid.
|
||||||
|
func (cg CommentGroup) Valid() bool {
|
||||||
|
return len(cg) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommentStmt represents a comment statement.
|
||||||
|
type CommentStmt struct {
|
||||||
|
// Comment is the comment token.
|
||||||
|
Comment token.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommentStmt) HasHeadCommentGroup() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommentStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommentStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommentStmt) stmtNode() {}
|
||||||
|
|
||||||
|
func (c *CommentStmt) Pos() token.Position {
|
||||||
|
return c.Comment.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommentStmt) End() token.Position {
|
||||||
|
return c.Comment.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommentStmt) Format(prefix ...string) string {
|
||||||
|
return peekOne(prefix) + c.Comment.Text
|
||||||
|
}
|
||||||
111
tools/goctl/pkg/parser/api/ast/importstatement.go
Normal file
111
tools/goctl/pkg/parser/api/ast/importstatement.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
|
||||||
|
// ImportStmt represents an import statement.
|
||||||
|
type ImportStmt interface {
|
||||||
|
Stmt
|
||||||
|
importNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportLiteralStmt represents an import literal statement.
|
||||||
|
type ImportLiteralStmt struct {
|
||||||
|
// Import is the import token.
|
||||||
|
Import *TokenNode
|
||||||
|
// Value is the import value.
|
||||||
|
Value *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportLiteralStmt) HasHeadCommentGroup() bool {
|
||||||
|
return i.Import.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportLiteralStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return i.Value.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportLiteralStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return i.Import.HeadCommentGroup, i.Value.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportLiteralStmt) Format(prefix ...string) (result string) {
|
||||||
|
if i.Value.IsZeroString() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
w := NewBufferWriter()
|
||||||
|
importNode := transferTokenNode(i.Import, ignoreLeadingComment(), withTokenNodePrefix(prefix...))
|
||||||
|
w.Write(withNode(importNode, i.Value), withMode(ModeExpectInSameLine))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportLiteralStmt) End() token.Position {
|
||||||
|
return i.Value.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportLiteralStmt) importNode() {}
|
||||||
|
|
||||||
|
func (i *ImportLiteralStmt) Pos() token.Position {
|
||||||
|
return i.Import.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportLiteralStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type ImportGroupStmt struct {
|
||||||
|
// Import is the import token.
|
||||||
|
Import *TokenNode
|
||||||
|
// LParen is the left parenthesis token.
|
||||||
|
LParen *TokenNode
|
||||||
|
// Values is the import values.
|
||||||
|
Values []*TokenNode
|
||||||
|
// RParen is the right parenthesis token.
|
||||||
|
RParen *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportGroupStmt) HasHeadCommentGroup() bool {
|
||||||
|
return i.Import.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportGroupStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return i.RParen.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportGroupStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return i.Import.HeadCommentGroup, i.RParen.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportGroupStmt) Format(prefix ...string) string {
|
||||||
|
var textList []string
|
||||||
|
for _, v := range i.Values {
|
||||||
|
if v.IsZeroString() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
textList = append(textList, v.Format(Indent))
|
||||||
|
}
|
||||||
|
if len(textList) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
importNode := transferTokenNode(i.Import, ignoreLeadingComment(), withTokenNodePrefix(prefix...))
|
||||||
|
w := NewBufferWriter()
|
||||||
|
w.Write(withNode(importNode, i.LParen), expectSameLine())
|
||||||
|
w.NewLine()
|
||||||
|
for _, v := range i.Values {
|
||||||
|
node := transferTokenNode(v, withTokenNodePrefix(peekOne(prefix)+Indent))
|
||||||
|
w.Write(withNode(node), expectSameLine())
|
||||||
|
w.NewLine()
|
||||||
|
}
|
||||||
|
w.Write(withNode(transferTokenNode(i.RParen, withTokenNodePrefix(prefix...))))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportGroupStmt) End() token.Position {
|
||||||
|
return i.RParen.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportGroupStmt) importNode() {}
|
||||||
|
|
||||||
|
func (i *ImportGroupStmt) Pos() token.Position {
|
||||||
|
return i.Import.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImportGroupStmt) stmtNode() {}
|
||||||
65
tools/goctl/pkg/parser/api/ast/infostatement.go
Normal file
65
tools/goctl/pkg/parser/api/ast/infostatement.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
|
||||||
|
// InfoStmt is the info statement.
|
||||||
|
type InfoStmt struct {
|
||||||
|
// Info is the info keyword.
|
||||||
|
Info *TokenNode
|
||||||
|
// LParen is the left parenthesis.
|
||||||
|
LParen *TokenNode
|
||||||
|
// Values is the info values.
|
||||||
|
Values []*KVExpr
|
||||||
|
// RParen is the right parenthesis.
|
||||||
|
RParen *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InfoStmt) HasHeadCommentGroup() bool {
|
||||||
|
return i.Info.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InfoStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return i.RParen.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InfoStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return i.Info.HeadCommentGroup, i.RParen.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InfoStmt) Format(prefix ...string) string {
|
||||||
|
if len(i.Values) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var textList []string
|
||||||
|
for _, v := range i.Values {
|
||||||
|
if v.Value.IsZeroString() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
textList = append(textList, v.Format(Indent))
|
||||||
|
}
|
||||||
|
if len(textList) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
w := NewBufferWriter()
|
||||||
|
infoNode := transferTokenNode(i.Info, withTokenNodePrefix(prefix...), ignoreLeadingComment())
|
||||||
|
w.Write(withNode(infoNode, i.LParen))
|
||||||
|
w.NewLine()
|
||||||
|
for _, v := range i.Values {
|
||||||
|
node := transferTokenNode(v.Key, withTokenNodePrefix(peekOne(prefix)+Indent), ignoreLeadingComment())
|
||||||
|
w.Write(withNode(node, v.Value), expectIndentInfix(), expectSameLine())
|
||||||
|
w.NewLine()
|
||||||
|
}
|
||||||
|
w.Write(withNode(transferTokenNode(i.RParen, withTokenNodePrefix(prefix...))))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InfoStmt) End() token.Position {
|
||||||
|
return i.RParen.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InfoStmt) Pos() token.Position {
|
||||||
|
return i.Info.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InfoStmt) stmtNode() {}
|
||||||
39
tools/goctl/pkg/parser/api/ast/kvexpression.go
Normal file
39
tools/goctl/pkg/parser/api/ast/kvexpression.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
|
||||||
|
// KVExpr is a key value expression.
|
||||||
|
type KVExpr struct {
|
||||||
|
// Key is the key of the key value expression.
|
||||||
|
Key *TokenNode
|
||||||
|
// Value is the value of the key value expression.
|
||||||
|
Value *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *KVExpr) HasHeadCommentGroup() bool {
|
||||||
|
return i.Key.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *KVExpr) HasLeadingCommentGroup() bool {
|
||||||
|
return i.Value.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *KVExpr) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return i.Key.HeadCommentGroup, i.Value.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *KVExpr) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
w.Write(withNode(i.Key, i.Value), withPrefix(prefix...), withInfix(Indent), withRawText())
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *KVExpr) End() token.Position {
|
||||||
|
return i.Value.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *KVExpr) Pos() token.Position {
|
||||||
|
return i.Key.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *KVExpr) exprNode() {}
|
||||||
237
tools/goctl/pkg/parser/api/ast/print.go
Normal file
237
tools/goctl/pkg/parser/api/ast/print.go
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/token"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
apitoken "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A FieldFilter may be provided to Fprint to control the output.
|
||||||
|
type FieldFilter func(name string, value reflect.Value) bool
|
||||||
|
|
||||||
|
// NotNilFilter returns true for field values that are not nil,
|
||||||
|
// it returns false otherwise.
|
||||||
|
func NotNilFilter(_ string, v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
|
||||||
|
return !v.IsNil()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint prints the value of x to the writer w.
|
||||||
|
func Fprint(w io.Writer, x interface{}, f FieldFilter) error {
|
||||||
|
return fprint(w, x, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fprint(w io.Writer, x interface{}, f FieldFilter) (err error) {
|
||||||
|
// setup printer
|
||||||
|
p := printer{
|
||||||
|
output: w,
|
||||||
|
filter: f,
|
||||||
|
ptrmap: make(map[interface{}]int),
|
||||||
|
last: '\n', // force printing of line number on first line
|
||||||
|
}
|
||||||
|
|
||||||
|
// install error handler
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
err = e.(localError).err // re-panics if it's not a localError
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// print x
|
||||||
|
if x == nil {
|
||||||
|
p.printf("nil\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.print(reflect.ValueOf(x))
|
||||||
|
p.printf("\n")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Print(x interface{}) error {
|
||||||
|
return Fprint(os.Stdout, x, NotNilFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
type printer struct {
|
||||||
|
output io.Writer
|
||||||
|
filter FieldFilter
|
||||||
|
ptrmap map[interface{}]int // *T -> line number
|
||||||
|
prefixIndent int // current indentation level
|
||||||
|
last byte // the last byte processed by Write
|
||||||
|
line int // current line number
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefixIndent = []byte(". ")
|
||||||
|
|
||||||
|
// Write implements io.Writer.
|
||||||
|
func (p *printer) Write(data []byte) (n int, err error) {
|
||||||
|
var m int
|
||||||
|
for i, b := range data {
|
||||||
|
// invariant: data[0:n] has been written
|
||||||
|
if b == '\n' {
|
||||||
|
m, err = p.output.Write(data[n : i+1])
|
||||||
|
n += m
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.line++
|
||||||
|
} else if p.last == '\n' {
|
||||||
|
_, err = fmt.Fprintf(p.output, "%6d ", p.line)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for j := p.prefixIndent; j > 0; j-- {
|
||||||
|
_, err = p.output.Write(prefixIndent)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.last = b
|
||||||
|
}
|
||||||
|
if len(data) > n {
|
||||||
|
m, err = p.output.Write(data[n:])
|
||||||
|
n += m
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// localError wraps locally caught errors so we can distinguish
|
||||||
|
// them from genuine panics which we don't want to return as errors.
|
||||||
|
type localError struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// printf is a convenience wrapper that takes care of print errors.
|
||||||
|
func (p *printer) printf(format string, args ...interface{}) {
|
||||||
|
if _, err := fmt.Fprintf(p, format, args...); err != nil {
|
||||||
|
panic(localError{err})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation note: Print is written for AST nodes but could be
|
||||||
|
// used to print arbitrary data structures; such a version should
|
||||||
|
// probably be in a different package.
|
||||||
|
//
|
||||||
|
// Note: This code detects (some) cycles created via pointers but
|
||||||
|
// not cycles that are created via slices or maps containing the
|
||||||
|
// same slice or map. Code for general data structures probably
|
||||||
|
// should catch those as well.
|
||||||
|
|
||||||
|
func (p *printer) print(x reflect.Value) {
|
||||||
|
if !NotNilFilter("", x) {
|
||||||
|
p.printf("nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x.Kind() {
|
||||||
|
case reflect.Interface:
|
||||||
|
p.print(x.Elem())
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
p.printf("%s (len = %d) {", x.Type(), x.Len())
|
||||||
|
if x.Len() > 0 {
|
||||||
|
p.prefixIndent++
|
||||||
|
p.printf("\n")
|
||||||
|
for _, key := range x.MapKeys() {
|
||||||
|
p.print(key)
|
||||||
|
p.printf(": ")
|
||||||
|
p.print(x.MapIndex(key))
|
||||||
|
p.printf("\n")
|
||||||
|
}
|
||||||
|
p.prefixIndent--
|
||||||
|
}
|
||||||
|
p.printf("}")
|
||||||
|
|
||||||
|
case reflect.Pointer:
|
||||||
|
p.printf("*")
|
||||||
|
// type-checked ASTs may contain cycles - use ptrmap
|
||||||
|
// to keep track of objects that have been printed
|
||||||
|
// already and print the respective line number instead
|
||||||
|
ptr := x.Interface()
|
||||||
|
if line, exists := p.ptrmap[ptr]; exists {
|
||||||
|
p.printf("(obj @ %d)", line)
|
||||||
|
} else {
|
||||||
|
p.ptrmap[ptr] = p.line
|
||||||
|
p.print(x.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
p.printf("%s {", x.Type())
|
||||||
|
if x.Len() > 0 {
|
||||||
|
p.prefixIndent++
|
||||||
|
p.printf("\n")
|
||||||
|
for i, n := 0, x.Len(); i < n; i++ {
|
||||||
|
p.printf("%d: ", i)
|
||||||
|
p.print(x.Index(i))
|
||||||
|
p.printf("\n")
|
||||||
|
}
|
||||||
|
p.prefixIndent--
|
||||||
|
}
|
||||||
|
p.printf("}")
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if s, ok := x.Interface().([]byte); ok {
|
||||||
|
p.printf("%#q", s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.printf("%s (len = %d) {", x.Type(), x.Len())
|
||||||
|
if x.Len() > 0 {
|
||||||
|
p.prefixIndent++
|
||||||
|
p.printf("\n")
|
||||||
|
for i, n := 0, x.Len(); i < n; i++ {
|
||||||
|
p.printf("%d: ", i)
|
||||||
|
p.print(x.Index(i))
|
||||||
|
p.printf("\n")
|
||||||
|
}
|
||||||
|
p.prefixIndent--
|
||||||
|
}
|
||||||
|
p.printf("}")
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
if val, ok := x.Interface().(apitoken.Position); ok {
|
||||||
|
p.printf(val.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t := x.Type()
|
||||||
|
p.printf("%s {", t)
|
||||||
|
p.prefixIndent++
|
||||||
|
first := true
|
||||||
|
for i, n := 0, t.NumField(); i < n; i++ {
|
||||||
|
// exclude non-exported fields because their
|
||||||
|
// values cannot be accessed via reflection
|
||||||
|
if name := t.Field(i).Name; token.IsExported(name) {
|
||||||
|
value := x.Field(i)
|
||||||
|
if p.filter == nil || p.filter(name, value) {
|
||||||
|
if first {
|
||||||
|
p.printf("\n")
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
p.printf("%s: ", name)
|
||||||
|
p.print(value)
|
||||||
|
p.printf("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.prefixIndent--
|
||||||
|
p.printf("}")
|
||||||
|
|
||||||
|
default:
|
||||||
|
v := x.Interface()
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
// print strings in quotes
|
||||||
|
p.printf("%q", v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// default
|
||||||
|
p.printf("%v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
577
tools/goctl/pkg/parser/api/ast/servicestatement.go
Normal file
577
tools/goctl/pkg/parser/api/ast/servicestatement.go
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
|
||||||
|
// AtServerStmt represents @server statement.
|
||||||
|
type AtServerStmt struct {
|
||||||
|
// AtServer is the @server token.
|
||||||
|
AtServer *TokenNode
|
||||||
|
// LParen is the left parenthesis token.
|
||||||
|
LParen *TokenNode
|
||||||
|
// Values is the key-value pairs.
|
||||||
|
Values []*KVExpr
|
||||||
|
// RParen is the right parenthesis token.
|
||||||
|
RParen *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtServerStmt) HasHeadCommentGroup() bool {
|
||||||
|
return a.AtServer.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtServerStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return a.RParen.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtServerStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return a.AtServer.HeadCommentGroup, a.RParen.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtServerStmt) Format(prefix ...string) string {
|
||||||
|
if len(a.Values) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var textList []string
|
||||||
|
for _, v := range a.Values {
|
||||||
|
if v.Value.IsZeroString() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
textList = append(textList, v.Format())
|
||||||
|
}
|
||||||
|
if len(textList) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
w := NewBufferWriter()
|
||||||
|
atServerNode := transferTokenNode(a.AtServer, withTokenNodePrefix(prefix...), ignoreLeadingComment())
|
||||||
|
w.Write(withNode(atServerNode, a.LParen), expectSameLine())
|
||||||
|
w.NewLine()
|
||||||
|
for _, v := range a.Values {
|
||||||
|
node := transferTokenNode(v.Key, withTokenNodePrefix(peekOne(prefix)+Indent), ignoreLeadingComment())
|
||||||
|
w.Write(withNode(node, v.Value), expectIndentInfix(), expectSameLine())
|
||||||
|
w.NewLine()
|
||||||
|
}
|
||||||
|
w.Write(withNode(transferTokenNode(a.RParen, withTokenNodePrefix(prefix...))))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtServerStmt) End() token.Position {
|
||||||
|
return a.RParen.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtServerStmt) Pos() token.Position {
|
||||||
|
return a.AtServer.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtServerStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type AtDocStmt interface {
|
||||||
|
Stmt
|
||||||
|
atDocNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtDocLiteralStmt struct {
|
||||||
|
AtDoc *TokenNode
|
||||||
|
Value *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocLiteralStmt) HasHeadCommentGroup() bool {
|
||||||
|
return a.AtDoc.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocLiteralStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return a.Value.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocLiteralStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return a.AtDoc.HeadCommentGroup, a.Value.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocLiteralStmt) Format(prefix ...string) string {
|
||||||
|
if a.Value.IsZeroString() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
w := NewBufferWriter()
|
||||||
|
atDocNode := transferTokenNode(a.AtDoc, withTokenNodePrefix(prefix...), ignoreLeadingComment())
|
||||||
|
valueNode := transferTokenNode(a.Value, ignoreHeadComment())
|
||||||
|
w.Write(withNode(atDocNode, valueNode), expectSameLine())
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocLiteralStmt) End() token.Position {
|
||||||
|
return a.Value.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocLiteralStmt) atDocNode() {}
|
||||||
|
|
||||||
|
func (a *AtDocLiteralStmt) Pos() token.Position {
|
||||||
|
return a.AtDoc.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocLiteralStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type AtDocGroupStmt struct {
|
||||||
|
AtDoc *TokenNode
|
||||||
|
LParen *TokenNode
|
||||||
|
Values []*KVExpr
|
||||||
|
RParen *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocGroupStmt) HasHeadCommentGroup() bool {
|
||||||
|
return a.AtDoc.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocGroupStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return a.RParen.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocGroupStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return a.AtDoc.HeadCommentGroup, a.RParen.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocGroupStmt) Format(prefix ...string) string {
|
||||||
|
if len(a.Values) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var textList []string
|
||||||
|
for _, v := range a.Values {
|
||||||
|
if v.Value.IsZeroString() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
textList = append(textList, v.Format(peekOne(prefix)+Indent))
|
||||||
|
}
|
||||||
|
if len(textList) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
w := NewBufferWriter()
|
||||||
|
atDocNode := transferTokenNode(a.AtDoc, withTokenNodePrefix(prefix...), ignoreLeadingComment())
|
||||||
|
w.Write(withNode(atDocNode, a.LParen), expectSameLine())
|
||||||
|
w.NewLine()
|
||||||
|
for _, v := range a.Values {
|
||||||
|
node := transferTokenNode(v.Key, withTokenNodePrefix(peekOne(prefix)+Indent), ignoreLeadingComment())
|
||||||
|
w.Write(withNode(node, v.Value), expectIndentInfix(), expectSameLine())
|
||||||
|
w.NewLine()
|
||||||
|
}
|
||||||
|
w.Write(withNode(transferTokenNode(a.RParen, withTokenNodePrefix(prefix...))))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocGroupStmt) End() token.Position {
|
||||||
|
return a.RParen.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocGroupStmt) atDocNode() {}
|
||||||
|
|
||||||
|
func (a *AtDocGroupStmt) Pos() token.Position {
|
||||||
|
return a.AtDoc.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtDocGroupStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type ServiceStmt struct {
|
||||||
|
AtServerStmt *AtServerStmt
|
||||||
|
Service *TokenNode
|
||||||
|
Name *ServiceNameExpr
|
||||||
|
LBrace *TokenNode
|
||||||
|
Routes []*ServiceItemStmt
|
||||||
|
RBrace *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceStmt) HasHeadCommentGroup() bool {
|
||||||
|
if s.AtServerStmt != nil {
|
||||||
|
return s.AtServerStmt.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
return s.Service.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return s.RBrace.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
if s.AtServerStmt != nil {
|
||||||
|
head, _ = s.AtServerStmt.CommentGroup()
|
||||||
|
return head, s.RBrace.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
return s.Service.HeadCommentGroup, s.RBrace.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceStmt) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
if s.AtServerStmt != nil {
|
||||||
|
text := s.AtServerStmt.Format()
|
||||||
|
if len(text) > 0 {
|
||||||
|
w.WriteText(text)
|
||||||
|
w.NewLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serviceNode := transferTokenNode(s.Service, withTokenNodePrefix(prefix...))
|
||||||
|
w.Write(withNode(serviceNode, s.Name, s.LBrace), expectSameLine())
|
||||||
|
if len(s.Routes) == 0 {
|
||||||
|
w.Write(withNode(transferTokenNode(s.RBrace, withTokenNodePrefix(prefix...))))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
w.NewLine()
|
||||||
|
for idx, route := range s.Routes {
|
||||||
|
routeNode := transfer2TokenNode(route, false, withTokenNodePrefix(peekOne(prefix)+Indent))
|
||||||
|
w.Write(withNode(routeNode))
|
||||||
|
if idx < len(s.Routes)-1 {
|
||||||
|
w.NewLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write(withNode(transferTokenNode(s.RBrace, withTokenNodePrefix(prefix...))))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceStmt) End() token.Position {
|
||||||
|
return s.RBrace.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceStmt) Pos() token.Position {
|
||||||
|
if s.AtServerStmt != nil {
|
||||||
|
return s.AtServerStmt.Pos()
|
||||||
|
}
|
||||||
|
return s.Service.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type ServiceNameExpr struct {
|
||||||
|
Name *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceNameExpr) HasHeadCommentGroup() bool {
|
||||||
|
return s.Name.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceNameExpr) HasLeadingCommentGroup() bool {
|
||||||
|
return s.Name.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceNameExpr) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return s.Name.HeadCommentGroup, s.Name.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceNameExpr) Format(...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
w.WriteText(s.Name.Format())
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceNameExpr) End() token.Position {
|
||||||
|
return s.Name.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceNameExpr) Pos() token.Position {
|
||||||
|
return s.Name.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceNameExpr) exprNode() {}
|
||||||
|
|
||||||
|
type AtHandlerStmt struct {
|
||||||
|
AtHandler *TokenNode
|
||||||
|
Name *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtHandlerStmt) HasHeadCommentGroup() bool {
|
||||||
|
return a.AtHandler.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtHandlerStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return a.Name.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtHandlerStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return a.AtHandler.HeadCommentGroup, a.Name.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtHandlerStmt) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
atDocNode := transferTokenNode(a.AtHandler, withTokenNodePrefix(prefix...), ignoreLeadingComment())
|
||||||
|
nameNode := transferTokenNode(a.Name, ignoreHeadComment())
|
||||||
|
w.Write(withNode(atDocNode, nameNode), expectSameLine())
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtHandlerStmt) End() token.Position {
|
||||||
|
return a.Name.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtHandlerStmt) Pos() token.Position {
|
||||||
|
return a.AtHandler.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AtHandlerStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type ServiceItemStmt struct {
|
||||||
|
AtDoc AtDocStmt
|
||||||
|
AtHandler *AtHandlerStmt
|
||||||
|
Route *RouteStmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceItemStmt) HasHeadCommentGroup() bool {
|
||||||
|
if s.AtDoc != nil {
|
||||||
|
return s.AtDoc.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
return s.AtHandler.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceItemStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return s.Route.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceItemStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
_, leading = s.Route.CommentGroup()
|
||||||
|
if s.AtDoc != nil {
|
||||||
|
head, _ = s.AtDoc.CommentGroup()
|
||||||
|
return head, leading
|
||||||
|
}
|
||||||
|
head, _ = s.AtHandler.CommentGroup()
|
||||||
|
return head, leading
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceItemStmt) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
if s.AtDoc != nil {
|
||||||
|
w.WriteText(s.AtDoc.Format(prefix...))
|
||||||
|
w.NewLine()
|
||||||
|
}
|
||||||
|
w.WriteText(s.AtHandler.Format(prefix...))
|
||||||
|
w.NewLine()
|
||||||
|
routeNode := transfer2TokenNode(s.Route, false, withTokenNodePrefix(prefix...))
|
||||||
|
w.Write(withNode(routeNode))
|
||||||
|
w.NewLine()
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceItemStmt) End() token.Position {
|
||||||
|
return s.Route.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceItemStmt) Pos() token.Position {
|
||||||
|
if s.AtDoc != nil {
|
||||||
|
return s.AtDoc.Pos()
|
||||||
|
}
|
||||||
|
return s.AtHandler.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceItemStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type RouteStmt struct {
|
||||||
|
Method *TokenNode
|
||||||
|
Path *PathExpr
|
||||||
|
Request *BodyStmt
|
||||||
|
Returns *TokenNode
|
||||||
|
Response *BodyStmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RouteStmt) HasHeadCommentGroup() bool {
|
||||||
|
return r.Method.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RouteStmt) HasLeadingCommentGroup() bool {
|
||||||
|
if r.Response != nil {
|
||||||
|
return r.Response.HasLeadingCommentGroup()
|
||||||
|
} else if r.Returns != nil {
|
||||||
|
return r.Returns.HasLeadingCommentGroup()
|
||||||
|
} else if r.Request != nil {
|
||||||
|
return r.Request.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
return r.Path.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RouteStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
head, _ = r.Method.CommentGroup()
|
||||||
|
if r.Response != nil {
|
||||||
|
_, leading = r.Response.CommentGroup()
|
||||||
|
} else if r.Returns != nil {
|
||||||
|
_, leading = r.Returns.CommentGroup()
|
||||||
|
} else if r.Request != nil {
|
||||||
|
_, leading = r.Request.CommentGroup()
|
||||||
|
}
|
||||||
|
return head, leading
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RouteStmt) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
methodNode := transferTokenNode(r.Method, withTokenNodePrefix(prefix...), ignoreLeadingComment())
|
||||||
|
if r.Response != nil {
|
||||||
|
if r.Response.Body == nil {
|
||||||
|
r.Response.RParen = transferTokenNode(r.Response.RParen, ignoreHeadComment())
|
||||||
|
if r.Request != nil {
|
||||||
|
w.Write(withNode(methodNode, r.Path, r.Request), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(methodNode, r.Path), expectSameLine())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.Response.RParen = transferTokenNode(r.Response.RParen, ignoreHeadComment())
|
||||||
|
if r.Request != nil {
|
||||||
|
w.Write(withNode(methodNode, r.Path, r.Request, r.Returns, r.Response), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(methodNode, r.Path, r.Returns, r.Response), expectSameLine())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if r.Request != nil {
|
||||||
|
r.Request.RParen = transferTokenNode(r.Request.RParen, ignoreHeadComment())
|
||||||
|
w.Write(withNode(methodNode, r.Path, r.Request), expectSameLine())
|
||||||
|
} else {
|
||||||
|
pathNode := transferTokenNode(r.Path.Value, ignoreHeadComment())
|
||||||
|
w.Write(withNode(methodNode, pathNode), expectSameLine())
|
||||||
|
}
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RouteStmt) End() token.Position {
|
||||||
|
if r.Response != nil {
|
||||||
|
return r.Response.End()
|
||||||
|
}
|
||||||
|
if r.Returns != nil {
|
||||||
|
return r.Returns.Pos()
|
||||||
|
}
|
||||||
|
if r.Request != nil {
|
||||||
|
return r.Request.End()
|
||||||
|
}
|
||||||
|
return r.Path.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RouteStmt) Pos() token.Position {
|
||||||
|
return r.Method.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RouteStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type PathExpr struct {
|
||||||
|
Value *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PathExpr) HasHeadCommentGroup() bool {
|
||||||
|
return p.Value.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PathExpr) HasLeadingCommentGroup() bool {
|
||||||
|
return p.Value.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PathExpr) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return p.Value.CommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PathExpr) Format(prefix ...string) string {
|
||||||
|
pathNode := transferTokenNode(p.Value, ignoreComment())
|
||||||
|
return pathNode.Format(prefix...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PathExpr) End() token.Position {
|
||||||
|
return p.Value.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PathExpr) Pos() token.Position {
|
||||||
|
return p.Value.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PathExpr) exprNode() {}
|
||||||
|
|
||||||
|
type BodyStmt struct {
|
||||||
|
LParen *TokenNode
|
||||||
|
Body *BodyExpr
|
||||||
|
RParen *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BodyStmt) HasHeadCommentGroup() bool {
|
||||||
|
return b.LParen.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BodyStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return b.RParen.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BodyStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return b.LParen.HeadCommentGroup, b.RParen.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BodyStmt) Format(...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
if b.Body == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
w.Write(withNode(b.LParen, b.Body, b.RParen), withInfix(NilIndent), expectSameLine())
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BodyStmt) End() token.Position {
|
||||||
|
return b.RParen.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BodyStmt) Pos() token.Position {
|
||||||
|
return b.LParen.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BodyStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type BodyExpr struct {
|
||||||
|
LBrack *TokenNode
|
||||||
|
RBrack *TokenNode
|
||||||
|
Star *TokenNode
|
||||||
|
Value *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BodyExpr) HasHeadCommentGroup() bool {
|
||||||
|
if e.LBrack != nil {
|
||||||
|
return e.LBrack.HasHeadCommentGroup()
|
||||||
|
} else if e.Star != nil {
|
||||||
|
return e.Star.HasHeadCommentGroup()
|
||||||
|
} else {
|
||||||
|
return e.Value.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BodyExpr) HasLeadingCommentGroup() bool {
|
||||||
|
return e.Value.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BodyExpr) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
if e.LBrack != nil {
|
||||||
|
head = e.LBrack.HeadCommentGroup
|
||||||
|
} else if e.Star != nil {
|
||||||
|
head = e.Star.HeadCommentGroup
|
||||||
|
} else {
|
||||||
|
head = e.Value.HeadCommentGroup
|
||||||
|
}
|
||||||
|
return head, e.Value.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BodyExpr) End() token.Position {
|
||||||
|
return e.Value.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BodyExpr) Format(...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
if e.LBrack != nil {
|
||||||
|
lbrackNode := transferTokenNode(e.LBrack, ignoreComment())
|
||||||
|
rbrackNode := transferTokenNode(e.RBrack, ignoreComment())
|
||||||
|
if e.Star != nil {
|
||||||
|
starNode := transferTokenNode(e.Star, ignoreComment())
|
||||||
|
w.Write(withNode(lbrackNode, rbrackNode, starNode, e.Value), withInfix(NilIndent), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(lbrackNode, rbrackNode, e.Value), withInfix(NilIndent), expectSameLine())
|
||||||
|
}
|
||||||
|
} else if e.Star != nil {
|
||||||
|
starNode := transferTokenNode(e.Star, ignoreComment())
|
||||||
|
w.Write(withNode(starNode, e.Value), withInfix(NilIndent), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(e.Value))
|
||||||
|
}
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BodyExpr) Pos() token.Position {
|
||||||
|
if e.LBrack != nil {
|
||||||
|
return e.LBrack.Pos()
|
||||||
|
}
|
||||||
|
if e.Star != nil {
|
||||||
|
return e.Star.Pos()
|
||||||
|
}
|
||||||
|
return e.Value.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BodyExpr) exprNode() {}
|
||||||
44
tools/goctl/pkg/parser/api/ast/syntaxstatement.go
Normal file
44
tools/goctl/pkg/parser/api/ast/syntaxstatement.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
|
||||||
|
// SyntaxStmt represents a syntax statement.
|
||||||
|
type SyntaxStmt struct {
|
||||||
|
// Syntax is the syntax token.
|
||||||
|
Syntax *TokenNode
|
||||||
|
// Assign is the assign token.
|
||||||
|
Assign *TokenNode
|
||||||
|
// Value is the syntax value.
|
||||||
|
Value *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SyntaxStmt) HasHeadCommentGroup() bool {
|
||||||
|
return s.Syntax.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SyntaxStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return s.Value.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SyntaxStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return s.Syntax.HeadCommentGroup, s.Syntax.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SyntaxStmt) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
syntaxNode := transferTokenNode(s.Syntax,
|
||||||
|
withTokenNodePrefix(prefix...), ignoreLeadingComment())
|
||||||
|
assignNode := transferTokenNode(s.Assign, ignoreLeadingComment())
|
||||||
|
w.Write(withNode(syntaxNode, assignNode, s.Value), withPrefix(prefix...), expectSameLine())
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SyntaxStmt) End() token.Position {
|
||||||
|
return s.Value.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SyntaxStmt) Pos() token.Position {
|
||||||
|
return s.Syntax.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SyntaxStmt) stmtNode() {}
|
||||||
797
tools/goctl/pkg/parser/api/ast/typestatement.go
Normal file
797
tools/goctl/pkg/parser/api/ast/typestatement.go
Normal file
@@ -0,0 +1,797 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
|
||||||
|
/*******************TypeStmt Begin********************/
|
||||||
|
|
||||||
|
// TypeStmt is the interface for type statement.
|
||||||
|
type TypeStmt interface {
|
||||||
|
Stmt
|
||||||
|
typeNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeLiteralStmt is the type statement for type literal.
|
||||||
|
type TypeLiteralStmt struct {
|
||||||
|
// Type is the type keyword.
|
||||||
|
Type *TokenNode
|
||||||
|
// Expr is the type expression.
|
||||||
|
Expr *TypeExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeLiteralStmt) HasHeadCommentGroup() bool {
|
||||||
|
return t.Type.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeLiteralStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return t.Expr.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeLiteralStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
_, leading = t.Expr.CommentGroup()
|
||||||
|
return t.Type.HeadCommentGroup, leading
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeLiteralStmt) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
w.Write(withNode(t.Type, t.Expr), withPrefix(prefix...), expectSameLine())
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeLiteralStmt) End() token.Position {
|
||||||
|
return t.Expr.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeLiteralStmt) Pos() token.Position {
|
||||||
|
return t.Type.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeLiteralStmt) stmtNode() {}
|
||||||
|
func (t *TypeLiteralStmt) typeNode() {}
|
||||||
|
|
||||||
|
// TypeGroupStmt is the type statement for type group.
|
||||||
|
type TypeGroupStmt struct {
|
||||||
|
// Type is the type keyword.
|
||||||
|
Type *TokenNode
|
||||||
|
// LParen is the left parenthesis.
|
||||||
|
LParen *TokenNode
|
||||||
|
// ExprList is the type expression list.
|
||||||
|
ExprList []*TypeExpr
|
||||||
|
// RParen is the right parenthesis.
|
||||||
|
RParen *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeGroupStmt) HasHeadCommentGroup() bool {
|
||||||
|
return t.Type.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeGroupStmt) HasLeadingCommentGroup() bool {
|
||||||
|
return t.RParen.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeGroupStmt) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return t.Type.HeadCommentGroup, t.RParen.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeGroupStmt) Format(prefix ...string) string {
|
||||||
|
if len(t.ExprList) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
w := NewBufferWriter()
|
||||||
|
typeNode := transferTokenNode(t.Type, withTokenNodePrefix(prefix...))
|
||||||
|
w.Write(withNode(typeNode, t.LParen), expectSameLine())
|
||||||
|
w.NewLine()
|
||||||
|
for _, e := range t.ExprList {
|
||||||
|
w.Write(withNode(e), withPrefix(peekOne(prefix)+Indent))
|
||||||
|
w.NewLine()
|
||||||
|
}
|
||||||
|
w.WriteText(t.RParen.Format(prefix...))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeGroupStmt) End() token.Position {
|
||||||
|
return t.RParen.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeGroupStmt) Pos() token.Position {
|
||||||
|
return t.Type.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypeGroupStmt) stmtNode() {}
|
||||||
|
func (t *TypeGroupStmt) typeNode() {}
|
||||||
|
|
||||||
|
/*******************TypeStmt End********************/
|
||||||
|
|
||||||
|
/*******************TypeExpr Begin********************/
|
||||||
|
|
||||||
|
// TypeExpr is the type expression.
|
||||||
|
type TypeExpr struct {
|
||||||
|
// Name is the type name.
|
||||||
|
Name *TokenNode
|
||||||
|
// Assign is the assign operator.
|
||||||
|
Assign *TokenNode
|
||||||
|
// DataType is the data type.
|
||||||
|
DataType DataType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TypeExpr) HasHeadCommentGroup() bool {
|
||||||
|
return e.Name.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TypeExpr) HasLeadingCommentGroup() bool {
|
||||||
|
return e.DataType.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TypeExpr) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
_, leading = e.DataType.CommentGroup()
|
||||||
|
return e.Name.HeadCommentGroup, leading
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TypeExpr) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
nameNode := transferTokenNode(e.Name, withTokenNodePrefix(prefix...))
|
||||||
|
dataTypeNode := transfer2TokenNode(e.DataType, false, withTokenNodePrefix(prefix...))
|
||||||
|
if e.Assign != nil {
|
||||||
|
w.Write(withNode(nameNode, e.Assign, dataTypeNode), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(nameNode, dataTypeNode), expectSameLine())
|
||||||
|
}
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TypeExpr) End() token.Position {
|
||||||
|
return e.DataType.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TypeExpr) Pos() token.Position {
|
||||||
|
return e.Name.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TypeExpr) exprNode() {}
|
||||||
|
|
||||||
|
func (e *TypeExpr) isStruct() bool {
|
||||||
|
return e.DataType.ContainsStruct()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************TypeExpr Begin********************/
|
||||||
|
|
||||||
|
/*******************Elem Begin********************/
|
||||||
|
|
||||||
|
// ElemExpr is the element expression.
|
||||||
|
type ElemExpr struct {
|
||||||
|
// Name is the field element name.
|
||||||
|
Name []*TokenNode
|
||||||
|
// DataType is the field data type.
|
||||||
|
DataType DataType
|
||||||
|
// Tag is the field tag.
|
||||||
|
Tag *TokenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAnonymous returns true if the element is anonymous.
|
||||||
|
func (e *ElemExpr) IsAnonymous() bool {
|
||||||
|
return len(e.Name) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ElemExpr) HasHeadCommentGroup() bool {
|
||||||
|
if e.IsAnonymous() {
|
||||||
|
return e.DataType.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
return e.Name[0].HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ElemExpr) HasLeadingCommentGroup() bool {
|
||||||
|
if e.Tag != nil {
|
||||||
|
return e.Tag.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
return e.DataType.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ElemExpr) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
if e.Tag != nil {
|
||||||
|
leading = e.Tag.LeadingCommentGroup
|
||||||
|
} else {
|
||||||
|
_, leading = e.DataType.CommentGroup()
|
||||||
|
}
|
||||||
|
if e.IsAnonymous() {
|
||||||
|
head, _ := e.DataType.CommentGroup()
|
||||||
|
return head, leading
|
||||||
|
}
|
||||||
|
return e.Name[0].HeadCommentGroup, leading
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ElemExpr) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
var nameNodeList []*TokenNode
|
||||||
|
for idx, n := range e.Name {
|
||||||
|
if idx == 0 {
|
||||||
|
nameNodeList = append(nameNodeList,
|
||||||
|
transferTokenNode(n, ignoreLeadingComment()))
|
||||||
|
} else if idx < len(e.Name)-1 {
|
||||||
|
nameNodeList = append(nameNodeList,
|
||||||
|
transferTokenNode(n, ignoreLeadingComment(), ignoreHeadComment()))
|
||||||
|
} else {
|
||||||
|
nameNodeList = append(nameNodeList, transferTokenNode(n, ignoreHeadComment()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataTypeOption []tokenNodeOption
|
||||||
|
if e.DataType.ContainsStruct() {
|
||||||
|
dataTypeOption = append(dataTypeOption, withTokenNodePrefix(peekOne(prefix)+Indent))
|
||||||
|
} else {
|
||||||
|
dataTypeOption = append(dataTypeOption, withTokenNodePrefix(prefix...))
|
||||||
|
}
|
||||||
|
dataTypeNode := transfer2TokenNode(e.DataType, false, dataTypeOption...)
|
||||||
|
if len(nameNodeList) > 0 {
|
||||||
|
nameNode := transferNilInfixNode(nameNodeList,
|
||||||
|
withTokenNodePrefix(prefix...), withTokenNodeInfix(", "))
|
||||||
|
if e.Tag != nil {
|
||||||
|
w.Write(withNode(nameNode, dataTypeNode, e.Tag), expectIndentInfix(), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(nameNode, dataTypeNode), expectIndentInfix(), expectSameLine())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if e.Tag != nil {
|
||||||
|
w.Write(withNode(dataTypeNode, e.Tag), expectIndentInfix(), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(dataTypeNode), expectIndentInfix(), expectSameLine())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ElemExpr) End() token.Position {
|
||||||
|
if e.Tag != nil {
|
||||||
|
return e.Tag.End()
|
||||||
|
}
|
||||||
|
return e.DataType.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ElemExpr) Pos() token.Position {
|
||||||
|
if len(e.Name) > 0 {
|
||||||
|
return e.Name[0].Pos()
|
||||||
|
}
|
||||||
|
return token.IllegalPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ElemExpr) exprNode() {}
|
||||||
|
|
||||||
|
/*******************Elem End********************/
|
||||||
|
|
||||||
|
/*******************ElemExprList Begin********************/
|
||||||
|
|
||||||
|
// ElemExprList is the element expression list.
|
||||||
|
type ElemExprList []*ElemExpr
|
||||||
|
|
||||||
|
/*******************ElemExprList Begin********************/
|
||||||
|
|
||||||
|
/*******************DataType Begin********************/
|
||||||
|
|
||||||
|
// DataType represents the data type.
|
||||||
|
type DataType interface {
|
||||||
|
Expr
|
||||||
|
dataTypeNode()
|
||||||
|
// CanEqual returns true if the data type can be equal.
|
||||||
|
CanEqual() bool
|
||||||
|
// ContainsStruct returns true if the data type contains struct.
|
||||||
|
ContainsStruct() bool
|
||||||
|
// RawText returns the raw text of the data type.
|
||||||
|
RawText() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyDataType is the any data type.
|
||||||
|
type AnyDataType struct {
|
||||||
|
// Any is the any token node.
|
||||||
|
Any *TokenNode
|
||||||
|
isChild bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AnyDataType) HasHeadCommentGroup() bool {
|
||||||
|
return t.Any.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AnyDataType) HasLeadingCommentGroup() bool {
|
||||||
|
return t.Any.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AnyDataType) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return t.Any.HeadCommentGroup, t.Any.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AnyDataType) Format(prefix ...string) string {
|
||||||
|
return t.Any.Format(prefix...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AnyDataType) End() token.Position {
|
||||||
|
return t.Any.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AnyDataType) RawText() string {
|
||||||
|
return t.Any.Token.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AnyDataType) ContainsStruct() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AnyDataType) Pos() token.Position {
|
||||||
|
return t.Any.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AnyDataType) exprNode() {}
|
||||||
|
|
||||||
|
func (t *AnyDataType) dataTypeNode() {}
|
||||||
|
|
||||||
|
func (t *AnyDataType) CanEqual() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayDataType is the array data type.
|
||||||
|
type ArrayDataType struct {
|
||||||
|
// LB is the left bracket token node.
|
||||||
|
LBrack *TokenNode
|
||||||
|
// Len is the array length.
|
||||||
|
Length *TokenNode
|
||||||
|
// RB is the right bracket token node.
|
||||||
|
RBrack *TokenNode
|
||||||
|
// DataType is the array data type.
|
||||||
|
DataType DataType
|
||||||
|
isChild bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ArrayDataType) HasHeadCommentGroup() bool {
|
||||||
|
return t.LBrack.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ArrayDataType) HasLeadingCommentGroup() bool {
|
||||||
|
return t.DataType.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ArrayDataType) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
_, leading = t.DataType.CommentGroup()
|
||||||
|
return t.LBrack.HeadCommentGroup, leading
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ArrayDataType) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
lbrack := transferTokenNode(t.LBrack, ignoreLeadingComment())
|
||||||
|
lengthNode := transferTokenNode(t.Length, ignoreLeadingComment())
|
||||||
|
rbrack := transferTokenNode(t.RBrack, ignoreHeadComment())
|
||||||
|
var dataType *TokenNode
|
||||||
|
var options []tokenNodeOption
|
||||||
|
options = append(options, withTokenNodePrefix(prefix...))
|
||||||
|
if t.isChild {
|
||||||
|
options = append(options, ignoreComment())
|
||||||
|
} else {
|
||||||
|
options = append(options, ignoreHeadComment())
|
||||||
|
}
|
||||||
|
|
||||||
|
dataType = transfer2TokenNode(t.DataType, false, options...)
|
||||||
|
node := transferNilInfixNode([]*TokenNode{lbrack, lengthNode, rbrack, dataType})
|
||||||
|
w.Write(withNode(node))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ArrayDataType) End() token.Position {
|
||||||
|
return t.DataType.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ArrayDataType) RawText() string {
|
||||||
|
return t.Format("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ArrayDataType) ContainsStruct() bool {
|
||||||
|
return t.DataType.ContainsStruct()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ArrayDataType) CanEqual() bool {
|
||||||
|
return t.DataType.CanEqual()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ArrayDataType) Pos() token.Position {
|
||||||
|
return t.LBrack.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ArrayDataType) exprNode() {}
|
||||||
|
func (t *ArrayDataType) dataTypeNode() {}
|
||||||
|
|
||||||
|
// BaseDataType is a common id type which contains bool, uint8, uint16, uint32,
|
||||||
|
// uint64, int8, int16, int32, int64, float32, float64, complex64, complex128,
|
||||||
|
// string, int, uint, uintptr, byte, rune, any.
|
||||||
|
type BaseDataType struct {
|
||||||
|
// Base is the base token node.
|
||||||
|
Base *TokenNode
|
||||||
|
isChild bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseDataType) HasHeadCommentGroup() bool {
|
||||||
|
return t.Base.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseDataType) HasLeadingCommentGroup() bool {
|
||||||
|
return t.Base.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseDataType) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return t.Base.HeadCommentGroup, t.Base.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseDataType) Format(prefix ...string) string {
|
||||||
|
return t.Base.Format(prefix...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseDataType) End() token.Position {
|
||||||
|
return t.Base.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseDataType) RawText() string {
|
||||||
|
return t.Base.Token.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseDataType) ContainsStruct() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseDataType) CanEqual() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseDataType) Pos() token.Position {
|
||||||
|
return t.Base.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseDataType) exprNode() {}
|
||||||
|
func (t *BaseDataType) dataTypeNode() {}
|
||||||
|
|
||||||
|
// InterfaceDataType is the interface data type.
|
||||||
|
type InterfaceDataType struct {
|
||||||
|
// Interface is the interface token node.
|
||||||
|
Interface *TokenNode
|
||||||
|
isChild bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) HasHeadCommentGroup() bool {
|
||||||
|
return t.Interface.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) HasLeadingCommentGroup() bool {
|
||||||
|
return t.Interface.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return t.Interface.HeadCommentGroup, t.Interface.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) Format(prefix ...string) string {
|
||||||
|
return t.Interface.Format(prefix...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) End() token.Position {
|
||||||
|
return t.Interface.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) RawText() string {
|
||||||
|
return t.Interface.Token.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) ContainsStruct() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) CanEqual() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) Pos() token.Position {
|
||||||
|
return t.Interface.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) exprNode() {}
|
||||||
|
|
||||||
|
func (t *InterfaceDataType) dataTypeNode() {}
|
||||||
|
|
||||||
|
// MapDataType is the map data type.
|
||||||
|
type MapDataType struct {
|
||||||
|
// Map is the map token node.
|
||||||
|
Map *TokenNode
|
||||||
|
// Lbrack is the left bracket token node.
|
||||||
|
LBrack *TokenNode
|
||||||
|
// Key is the map key data type.
|
||||||
|
Key DataType
|
||||||
|
// Rbrack is the right bracket token node.
|
||||||
|
RBrack *TokenNode
|
||||||
|
// Value is the map value data type.
|
||||||
|
Value DataType
|
||||||
|
isChild bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MapDataType) HasHeadCommentGroup() bool {
|
||||||
|
return t.Map.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MapDataType) HasLeadingCommentGroup() bool {
|
||||||
|
return t.Value.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MapDataType) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
_, leading = t.Value.CommentGroup()
|
||||||
|
return t.Map.HeadCommentGroup, leading
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MapDataType) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
mapNode := transferTokenNode(t.Map, ignoreLeadingComment())
|
||||||
|
lbrack := transferTokenNode(t.LBrack, ignoreLeadingComment())
|
||||||
|
rbrack := transferTokenNode(t.RBrack, ignoreComment())
|
||||||
|
var keyOption, valueOption []tokenNodeOption
|
||||||
|
keyOption = append(keyOption, ignoreComment())
|
||||||
|
valueOption = append(valueOption, withTokenNodePrefix(prefix...))
|
||||||
|
|
||||||
|
if t.isChild {
|
||||||
|
valueOption = append(valueOption, ignoreComment())
|
||||||
|
} else {
|
||||||
|
valueOption = append(valueOption, ignoreHeadComment())
|
||||||
|
}
|
||||||
|
|
||||||
|
keyDataType := transfer2TokenNode(t.Key, true, keyOption...)
|
||||||
|
valueDataType := transfer2TokenNode(t.Value, false, valueOption...)
|
||||||
|
node := transferNilInfixNode([]*TokenNode{mapNode, lbrack, keyDataType, rbrack, valueDataType})
|
||||||
|
w.Write(withNode(node))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MapDataType) End() token.Position {
|
||||||
|
return t.Value.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MapDataType) RawText() string {
|
||||||
|
return t.Format("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MapDataType) ContainsStruct() bool {
|
||||||
|
return t.Key.ContainsStruct() || t.Value.ContainsStruct()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MapDataType) CanEqual() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MapDataType) Pos() token.Position {
|
||||||
|
return t.Map.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MapDataType) exprNode() {}
|
||||||
|
func (t *MapDataType) dataTypeNode() {}
|
||||||
|
|
||||||
|
// PointerDataType is the pointer data type.
|
||||||
|
type PointerDataType struct {
|
||||||
|
// Star is the star token node.
|
||||||
|
Star *TokenNode
|
||||||
|
// DataType is the pointer data type.
|
||||||
|
DataType DataType
|
||||||
|
isChild bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PointerDataType) HasHeadCommentGroup() bool {
|
||||||
|
return t.Star.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PointerDataType) HasLeadingCommentGroup() bool {
|
||||||
|
return t.DataType.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PointerDataType) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
_, leading = t.DataType.CommentGroup()
|
||||||
|
return t.Star.HeadCommentGroup, leading
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PointerDataType) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
star := transferTokenNode(t.Star, ignoreLeadingComment(), withTokenNodePrefix(prefix...))
|
||||||
|
var dataTypeOption []tokenNodeOption
|
||||||
|
dataTypeOption = append(dataTypeOption, ignoreHeadComment())
|
||||||
|
dataType := transfer2TokenNode(t.DataType, false, dataTypeOption...)
|
||||||
|
node := transferNilInfixNode([]*TokenNode{star, dataType})
|
||||||
|
w.Write(withNode(node))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PointerDataType) End() token.Position {
|
||||||
|
return t.DataType.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PointerDataType) RawText() string {
|
||||||
|
return t.Format("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PointerDataType) ContainsStruct() bool {
|
||||||
|
return t.DataType.ContainsStruct()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PointerDataType) CanEqual() bool {
|
||||||
|
return t.DataType.CanEqual()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PointerDataType) Pos() token.Position {
|
||||||
|
return t.Star.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PointerDataType) exprNode() {}
|
||||||
|
func (t *PointerDataType) dataTypeNode() {}
|
||||||
|
|
||||||
|
// SliceDataType is the slice data type.
|
||||||
|
type SliceDataType struct {
|
||||||
|
// Lbrack is the left bracket token node.
|
||||||
|
LBrack *TokenNode
|
||||||
|
// Rbrack is the right bracket token node.
|
||||||
|
RBrack *TokenNode
|
||||||
|
// DataType is the slice data type.
|
||||||
|
DataType DataType
|
||||||
|
isChild bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SliceDataType) HasHeadCommentGroup() bool {
|
||||||
|
return t.LBrack.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SliceDataType) HasLeadingCommentGroup() bool {
|
||||||
|
return t.DataType.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SliceDataType) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
_, leading = t.DataType.CommentGroup()
|
||||||
|
return t.LBrack.HeadCommentGroup, leading
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SliceDataType) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
lbrack := transferTokenNode(t.LBrack, ignoreLeadingComment())
|
||||||
|
rbrack := transferTokenNode(t.RBrack, ignoreHeadComment())
|
||||||
|
dataType := transfer2TokenNode(t.DataType, false, withTokenNodePrefix(prefix...), ignoreHeadComment())
|
||||||
|
node := transferNilInfixNode([]*TokenNode{lbrack, rbrack, dataType})
|
||||||
|
w.Write(withNode(node))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SliceDataType) End() token.Position {
|
||||||
|
return t.DataType.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SliceDataType) RawText() string {
|
||||||
|
return t.Format("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SliceDataType) ContainsStruct() bool {
|
||||||
|
return t.DataType.ContainsStruct()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SliceDataType) CanEqual() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SliceDataType) Pos() token.Position {
|
||||||
|
return t.LBrack.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SliceDataType) exprNode() {}
|
||||||
|
func (t *SliceDataType) dataTypeNode() {}
|
||||||
|
|
||||||
|
// StructDataType is the structure data type.
|
||||||
|
type StructDataType struct {
|
||||||
|
// Lbrace is the left brace token node.
|
||||||
|
LBrace *TokenNode
|
||||||
|
// Elements is the structure elements.
|
||||||
|
Elements ElemExprList
|
||||||
|
// Rbrace is the right brace token node.
|
||||||
|
RBrace *TokenNode
|
||||||
|
isChild bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StructDataType) HasHeadCommentGroup() bool {
|
||||||
|
return t.LBrace.HasHeadCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StructDataType) HasLeadingCommentGroup() bool {
|
||||||
|
return t.RBrace.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StructDataType) CommentGroup() (head, leading CommentGroup) {
|
||||||
|
return t.LBrace.HeadCommentGroup, t.RBrace.LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StructDataType) Format(prefix ...string) string {
|
||||||
|
w := NewBufferWriter()
|
||||||
|
if len(t.Elements) == 0 {
|
||||||
|
lbrace := transferTokenNode(t.LBrace, withTokenNodePrefix(prefix...), ignoreLeadingComment())
|
||||||
|
rbrace := transferTokenNode(t.RBrace, ignoreHeadComment())
|
||||||
|
brace := transferNilInfixNode([]*TokenNode{lbrace, rbrace})
|
||||||
|
w.Write(withNode(brace), expectSameLine())
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
w.WriteText(t.LBrace.Format(NilIndent))
|
||||||
|
w.NewLine()
|
||||||
|
for _, e := range t.Elements {
|
||||||
|
var nameNodeList []*TokenNode
|
||||||
|
if len(e.Name) > 0 {
|
||||||
|
for idx, n := range e.Name {
|
||||||
|
if idx == 0 {
|
||||||
|
nameNodeList = append(nameNodeList,
|
||||||
|
transferTokenNode(n, withTokenNodePrefix(peekOne(prefix)+Indent), ignoreLeadingComment()))
|
||||||
|
} else if idx < len(e.Name)-1 {
|
||||||
|
nameNodeList = append(nameNodeList,
|
||||||
|
transferTokenNode(n, ignoreLeadingComment(), ignoreHeadComment()))
|
||||||
|
} else {
|
||||||
|
nameNodeList = append(nameNodeList, transferTokenNode(n, ignoreHeadComment()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var dataTypeOption []tokenNodeOption
|
||||||
|
if e.DataType.ContainsStruct() || e.IsAnonymous() {
|
||||||
|
dataTypeOption = append(dataTypeOption, withTokenNodePrefix(peekOne(prefix)+Indent))
|
||||||
|
} else {
|
||||||
|
dataTypeOption = append(dataTypeOption, withTokenNodePrefix(prefix...))
|
||||||
|
}
|
||||||
|
dataTypeNode := transfer2TokenNode(e.DataType, false, dataTypeOption...)
|
||||||
|
if len(nameNodeList) > 0 {
|
||||||
|
nameNode := transferNilInfixNode(nameNodeList, withTokenNodeInfix(", "))
|
||||||
|
if e.Tag != nil {
|
||||||
|
if e.DataType.ContainsStruct() {
|
||||||
|
w.Write(withNode(nameNode, dataTypeNode, e.Tag), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(nameNode, e.DataType, e.Tag), expectIndentInfix(), expectSameLine())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if e.DataType.ContainsStruct() {
|
||||||
|
w.Write(withNode(nameNode, dataTypeNode), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(nameNode, e.DataType), expectIndentInfix(), expectSameLine())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if e.Tag != nil {
|
||||||
|
if e.DataType.ContainsStruct() {
|
||||||
|
w.Write(withNode(dataTypeNode, e.Tag), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(e.DataType, e.Tag), expectIndentInfix(), expectSameLine())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if e.DataType.ContainsStruct() {
|
||||||
|
w.Write(withNode(dataTypeNode), expectSameLine())
|
||||||
|
} else {
|
||||||
|
w.Write(withNode(dataTypeNode), expectIndentInfix(), expectSameLine())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.NewLine()
|
||||||
|
}
|
||||||
|
w.WriteText(t.RBrace.Format(prefix...))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StructDataType) End() token.Position {
|
||||||
|
return t.RBrace.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StructDataType) RawText() string {
|
||||||
|
return t.Format("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StructDataType) ContainsStruct() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StructDataType) CanEqual() bool {
|
||||||
|
for _, v := range t.Elements {
|
||||||
|
if !v.DataType.CanEqual() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StructDataType) Pos() token.Position {
|
||||||
|
return t.LBrace.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StructDataType) exprNode() {}
|
||||||
|
func (t *StructDataType) dataTypeNode() {}
|
||||||
|
|
||||||
|
/*******************DataType End********************/
|
||||||
403
tools/goctl/pkg/parser/api/ast/writer.go
Normal file
403
tools/goctl/pkg/parser/api/ast/writer.go
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
NilIndent = ""
|
||||||
|
WhiteSpace = " "
|
||||||
|
Indent = "\t"
|
||||||
|
NewLine = "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ WriteMode = 1 << iota
|
||||||
|
// ModeAuto is the default mode, which will automatically
|
||||||
|
//determine whether to write a newline.
|
||||||
|
ModeAuto
|
||||||
|
|
||||||
|
// ModeExpectInSameLine will write in the same line.
|
||||||
|
ModeExpectInSameLine
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option func(o *option)
|
||||||
|
|
||||||
|
type option struct {
|
||||||
|
prefix string
|
||||||
|
infix string
|
||||||
|
mode WriteMode
|
||||||
|
nodes []Node
|
||||||
|
rawText bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokenNodeOption func(o *tokenNodeOpt)
|
||||||
|
type tokenNodeOpt struct {
|
||||||
|
prefix string
|
||||||
|
infix string
|
||||||
|
ignoreHeadComment bool
|
||||||
|
ignoreLeadingComment bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMode is the mode of writing.
|
||||||
|
type WriteMode int
|
||||||
|
|
||||||
|
// Writer is the writer of ast.
|
||||||
|
type Writer struct {
|
||||||
|
tw *tabwriter.Writer
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func transfer2TokenNode(node Node, isChild bool, opt ...tokenNodeOption) *TokenNode {
|
||||||
|
option := new(tokenNodeOpt)
|
||||||
|
for _, o := range opt {
|
||||||
|
o(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
var copyOpt = append([]tokenNodeOption(nil), opt...)
|
||||||
|
var tn *TokenNode
|
||||||
|
switch val := node.(type) {
|
||||||
|
case *AnyDataType:
|
||||||
|
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
|
||||||
|
tn = transferTokenNode(val.Any, copyOpt...)
|
||||||
|
if option.ignoreHeadComment {
|
||||||
|
tn.HeadCommentGroup = nil
|
||||||
|
}
|
||||||
|
if option.ignoreLeadingComment {
|
||||||
|
tn.LeadingCommentGroup = nil
|
||||||
|
}
|
||||||
|
val.isChild = isChild
|
||||||
|
val.Any = tn
|
||||||
|
case *ArrayDataType:
|
||||||
|
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
|
||||||
|
tn = transferTokenNode(val.LBrack, copyOpt...)
|
||||||
|
if option.ignoreHeadComment {
|
||||||
|
tn.HeadCommentGroup = nil
|
||||||
|
}
|
||||||
|
if option.ignoreLeadingComment {
|
||||||
|
tn.LeadingCommentGroup = nil
|
||||||
|
}
|
||||||
|
val.isChild = isChild
|
||||||
|
val.LBrack = tn
|
||||||
|
case *BaseDataType:
|
||||||
|
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
|
||||||
|
tn = transferTokenNode(val.Base, copyOpt...)
|
||||||
|
if option.ignoreHeadComment {
|
||||||
|
tn.HeadCommentGroup = nil
|
||||||
|
}
|
||||||
|
if option.ignoreLeadingComment {
|
||||||
|
tn.LeadingCommentGroup = nil
|
||||||
|
}
|
||||||
|
val.isChild = isChild
|
||||||
|
val.Base = tn
|
||||||
|
case *InterfaceDataType:
|
||||||
|
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
|
||||||
|
tn = transferTokenNode(val.Interface, copyOpt...)
|
||||||
|
if option.ignoreHeadComment {
|
||||||
|
tn.HeadCommentGroup = nil
|
||||||
|
}
|
||||||
|
if option.ignoreLeadingComment {
|
||||||
|
tn.LeadingCommentGroup = nil
|
||||||
|
}
|
||||||
|
val.isChild = isChild
|
||||||
|
val.Interface = tn
|
||||||
|
case *MapDataType:
|
||||||
|
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
|
||||||
|
tn = transferTokenNode(val.Map, copyOpt...)
|
||||||
|
if option.ignoreHeadComment {
|
||||||
|
tn.HeadCommentGroup = nil
|
||||||
|
}
|
||||||
|
if option.ignoreLeadingComment {
|
||||||
|
tn.LeadingCommentGroup = nil
|
||||||
|
}
|
||||||
|
val.isChild = isChild
|
||||||
|
val.Map = tn
|
||||||
|
case *PointerDataType:
|
||||||
|
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
|
||||||
|
tn = transferTokenNode(val.Star, copyOpt...)
|
||||||
|
if option.ignoreHeadComment {
|
||||||
|
tn.HeadCommentGroup = nil
|
||||||
|
}
|
||||||
|
if option.ignoreLeadingComment {
|
||||||
|
tn.LeadingCommentGroup = nil
|
||||||
|
}
|
||||||
|
val.isChild = isChild
|
||||||
|
val.Star = tn
|
||||||
|
case *SliceDataType:
|
||||||
|
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
|
||||||
|
tn = transferTokenNode(val.LBrack, copyOpt...)
|
||||||
|
if option.ignoreHeadComment {
|
||||||
|
tn.HeadCommentGroup = nil
|
||||||
|
}
|
||||||
|
if option.ignoreLeadingComment {
|
||||||
|
tn.LeadingCommentGroup = nil
|
||||||
|
}
|
||||||
|
val.isChild = isChild
|
||||||
|
val.LBrack = tn
|
||||||
|
case *StructDataType:
|
||||||
|
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
|
||||||
|
tn = transferTokenNode(val.LBrace, copyOpt...)
|
||||||
|
if option.ignoreHeadComment {
|
||||||
|
tn.HeadCommentGroup = nil
|
||||||
|
}
|
||||||
|
if option.ignoreLeadingComment {
|
||||||
|
tn.LeadingCommentGroup = nil
|
||||||
|
}
|
||||||
|
val.isChild = isChild
|
||||||
|
val.LBrace = tn
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TokenNode{
|
||||||
|
headFlag: node.HasHeadCommentGroup(),
|
||||||
|
leadingFlag: node.HasLeadingCommentGroup(),
|
||||||
|
Token: token.Token{
|
||||||
|
Text: node.Format(option.prefix),
|
||||||
|
Position: node.Pos(),
|
||||||
|
},
|
||||||
|
LeadingCommentGroup: CommentGroup{
|
||||||
|
{
|
||||||
|
token.Token{Position: node.End()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferNilInfixNode(nodes []*TokenNode, opt ...tokenNodeOption) *TokenNode {
|
||||||
|
result := &TokenNode{}
|
||||||
|
var option = new(tokenNodeOpt)
|
||||||
|
for _, o := range opt {
|
||||||
|
o(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
var list []string
|
||||||
|
for _, n := range nodes {
|
||||||
|
list = append(list, n.Token.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Token = token.Token{
|
||||||
|
Text: option.prefix + strings.Join(list, option.infix),
|
||||||
|
Position: nodes[0].Pos(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !option.ignoreHeadComment {
|
||||||
|
result.HeadCommentGroup = nodes[0].HeadCommentGroup
|
||||||
|
}
|
||||||
|
if !option.ignoreLeadingComment {
|
||||||
|
result.LeadingCommentGroup = nodes[len(nodes)-1].LeadingCommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferTokenNode(node *TokenNode, opt ...tokenNodeOption) *TokenNode {
|
||||||
|
result := &TokenNode{}
|
||||||
|
var option = new(tokenNodeOpt)
|
||||||
|
for _, o := range opt {
|
||||||
|
o(option)
|
||||||
|
}
|
||||||
|
result.Token = token.Token{
|
||||||
|
Type: node.Token.Type,
|
||||||
|
Text: option.prefix + node.Token.Text,
|
||||||
|
Position: node.Token.Position,
|
||||||
|
}
|
||||||
|
if !option.ignoreHeadComment {
|
||||||
|
for _, v := range node.HeadCommentGroup {
|
||||||
|
result.HeadCommentGroup = append(result.HeadCommentGroup,
|
||||||
|
&CommentStmt{Comment: token.Token{
|
||||||
|
Type: v.Comment.Type,
|
||||||
|
Text: option.prefix + v.Comment.Text,
|
||||||
|
Position: v.Comment.Position,
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !option.ignoreLeadingComment {
|
||||||
|
for _, v := range node.LeadingCommentGroup {
|
||||||
|
result.LeadingCommentGroup = append(result.LeadingCommentGroup, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ignoreHeadComment() tokenNodeOption {
|
||||||
|
return func(o *tokenNodeOpt) {
|
||||||
|
o.ignoreHeadComment = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ignoreLeadingComment() tokenNodeOption {
|
||||||
|
return func(o *tokenNodeOpt) {
|
||||||
|
o.ignoreLeadingComment = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ignoreComment() tokenNodeOption {
|
||||||
|
return func(o *tokenNodeOpt) {
|
||||||
|
o.ignoreHeadComment = true
|
||||||
|
o.ignoreLeadingComment = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withTokenNodePrefix(prefix ...string) tokenNodeOption {
|
||||||
|
return func(o *tokenNodeOpt) {
|
||||||
|
for _, p := range prefix {
|
||||||
|
o.prefix = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func withTokenNodeInfix(infix string) tokenNodeOption {
|
||||||
|
return func(o *tokenNodeOpt) {
|
||||||
|
o.infix = infix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectSameLine() Option {
|
||||||
|
return func(o *option) {
|
||||||
|
o.mode = ModeExpectInSameLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectIndentInfix() Option {
|
||||||
|
return func(o *option) {
|
||||||
|
o.infix = Indent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withNode(nodes ...Node) Option {
|
||||||
|
return func(o *option) {
|
||||||
|
o.nodes = nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withMode(mode WriteMode) Option {
|
||||||
|
return func(o *option) {
|
||||||
|
o.mode = mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withPrefix(prefix ...string) Option {
|
||||||
|
return func(o *option) {
|
||||||
|
for _, p := range prefix {
|
||||||
|
o.prefix = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withInfix(infix string) Option {
|
||||||
|
return func(o *option) {
|
||||||
|
o.infix = infix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withRawText() Option {
|
||||||
|
return func(o *option) {
|
||||||
|
o.rawText = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter returns a new Writer.
|
||||||
|
func NewWriter(writer io.Writer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
tw: tabwriter.NewWriter(writer, 1, 8, 1, ' ', tabwriter.TabIndent),
|
||||||
|
writer: writer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBufferWriter returns a new buffer Writer.
|
||||||
|
func NewBufferWriter() *Writer {
|
||||||
|
writer := bytes.NewBuffer(nil)
|
||||||
|
return &Writer{
|
||||||
|
tw: tabwriter.NewWriter(writer, 1, 8, 1, ' ', tabwriter.TabIndent),
|
||||||
|
writer: writer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string of the buffer.
|
||||||
|
func (w *Writer) String() string {
|
||||||
|
buffer, ok := w.writer.(*bytes.Buffer)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flushes the buffer.
|
||||||
|
func (w *Writer) Flush() {
|
||||||
|
_ = w.tw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLine writes a new line.
|
||||||
|
func (w *Writer) NewLine() {
|
||||||
|
_, _ = fmt.Fprint(w.tw, NewLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the node.
|
||||||
|
func (w *Writer) Write(opts ...Option) {
|
||||||
|
if len(opts) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var opt = new(option)
|
||||||
|
opt.mode = ModeAuto
|
||||||
|
opt.prefix = NilIndent
|
||||||
|
opt.infix = WhiteSpace
|
||||||
|
for _, v := range opts {
|
||||||
|
v(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.write(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteText writes the text.
|
||||||
|
func (w *Writer) WriteText(text string) {
|
||||||
|
_, _ = fmt.Fprintf(w.tw, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) write(opt *option) {
|
||||||
|
if len(opt.nodes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var textList []string
|
||||||
|
line := opt.nodes[0].End().Line
|
||||||
|
for idx, node := range opt.nodes {
|
||||||
|
mode := opt.mode
|
||||||
|
preIdx := idx - 1
|
||||||
|
var preNodeHasLeading bool
|
||||||
|
if preIdx > -1 && preIdx < len(opt.nodes) {
|
||||||
|
preNode := opt.nodes[preIdx]
|
||||||
|
preNodeHasLeading = preNode.HasLeadingCommentGroup()
|
||||||
|
}
|
||||||
|
if node.HasHeadCommentGroup() || preNodeHasLeading {
|
||||||
|
mode = ModeAuto
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == ModeAuto && node.Pos().Line > line {
|
||||||
|
textList = append(textList, NewLine)
|
||||||
|
}
|
||||||
|
line = node.End().Line
|
||||||
|
if util.TrimWhiteSpace(node.Format()) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
textList = append(textList, node.Format(opt.prefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
text := strings.Join(textList, opt.infix)
|
||||||
|
text = strings.ReplaceAll(text, " \n", "\n")
|
||||||
|
text = strings.ReplaceAll(text, "\n ", "\n")
|
||||||
|
if opt.rawText {
|
||||||
|
_, _ = fmt.Fprint(w.writer, text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprint(w.tw, text)
|
||||||
|
}
|
||||||
45
tools/goctl/pkg/parser/api/format/format.go
Normal file
45
tools/goctl/pkg/parser/api/format/format.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File formats the api file.
|
||||||
|
func File(filename string) error {
|
||||||
|
data, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
if err := Source(data, buffer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(filename, buffer.Bytes(), 0666)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source formats the api source.
|
||||||
|
func Source(source []byte, w io.Writer) error {
|
||||||
|
p := parser.New("", source)
|
||||||
|
result := p.Parse()
|
||||||
|
if err := p.CheckErrors(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Format(w)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatForUnitTest(source []byte, w io.Writer) error {
|
||||||
|
p := parser.New("", source)
|
||||||
|
result := p.Parse()
|
||||||
|
if err := p.CheckErrors(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.FormatForUnitTest(w)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
1510
tools/goctl/pkg/parser/api/format/format_test.go
Normal file
1510
tools/goctl/pkg/parser/api/format/format_test.go
Normal file
File diff suppressed because it is too large
Load Diff
36
tools/goctl/pkg/parser/api/format/testdata/expected_service.api
vendored
Normal file
36
tools/goctl/pkg/parser/api/format/testdata/expected_service.api
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// server foo
|
||||||
|
@server ( // server
|
||||||
|
// key-value form
|
||||||
|
key1: value1
|
||||||
|
key2: value2
|
||||||
|
jwt: Auth // enable jwt
|
||||||
|
prefix: /v1 // the route prefix
|
||||||
|
)
|
||||||
|
// service foo
|
||||||
|
service foo { // foo
|
||||||
|
// example1
|
||||||
|
@doc "example1"
|
||||||
|
@handler example1 // handler declare
|
||||||
|
get /path/example1 // no body
|
||||||
|
|
||||||
|
@doc ( // doc group
|
||||||
|
key1: "value1"
|
||||||
|
key11: "value11"
|
||||||
|
key111: "value111"
|
||||||
|
)
|
||||||
|
@handler example2 // handler example2
|
||||||
|
get /path/example2/:id // path arg
|
||||||
|
|
||||||
|
@doc "example3"
|
||||||
|
@handler example3
|
||||||
|
get /path/example3/:id (Foo) // no response
|
||||||
|
|
||||||
|
@doc "example4"
|
||||||
|
@handler example4
|
||||||
|
post /path/example4/a-b returns (Foo) // no request
|
||||||
|
|
||||||
|
@doc "example5"
|
||||||
|
@handler example5
|
||||||
|
// example5
|
||||||
|
post /path/example5/a-b (Foo) returns (Bar) // do not comment between path and body
|
||||||
|
}
|
||||||
37
tools/goctl/pkg/parser/api/format/testdata/expected_type_struct_group.api
vendored
Normal file
37
tools/goctl/pkg/parser/api/format/testdata/expected_type_struct_group.api
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*aa*/
|
||||||
|
type (
|
||||||
|
/*bb*/
|
||||||
|
T /*cc*/ { // T.bg
|
||||||
|
// Name head1
|
||||||
|
/*Name head2*/
|
||||||
|
Name string `json:"name"` // name
|
||||||
|
Age int `json:"age"` // age
|
||||||
|
Extra
|
||||||
|
Address string
|
||||||
|
Hobby []{
|
||||||
|
Name string // hobby.name
|
||||||
|
Rate string
|
||||||
|
} `json:"hobby"` // hobby
|
||||||
|
Child { // child.bg
|
||||||
|
Name string `json:"name"` // child.name
|
||||||
|
Gender int `json:"gender"` // child.gender
|
||||||
|
Birthday string `json:"birthday"` // child.birthday
|
||||||
|
Desc string // son.desc
|
||||||
|
Son { // son.bg
|
||||||
|
Name string `json:"name"` // son.name
|
||||||
|
Gender int `json:"gender"` // son.gender
|
||||||
|
Birthday string `json:"birthday"` // son.birthday
|
||||||
|
Desc string // son.desc
|
||||||
|
Hobby []{
|
||||||
|
Name string // hobby.name
|
||||||
|
Description string
|
||||||
|
// Map
|
||||||
|
Map map[string]{
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age string `json:"age"`
|
||||||
|
} `json:"map"`
|
||||||
|
} `json:"hobby"` // hobby
|
||||||
|
} // son.end
|
||||||
|
} // child.end
|
||||||
|
} // T.end
|
||||||
|
)
|
||||||
34
tools/goctl/pkg/parser/api/format/testdata/expected_type_struct_lit.api
vendored
Normal file
34
tools/goctl/pkg/parser/api/format/testdata/expected_type_struct_lit.api
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*aa*/
|
||||||
|
type /*bb*/ T /*cc*/ { // T.bg
|
||||||
|
// Name head1
|
||||||
|
/*Name head2*/
|
||||||
|
Name string `json:"name"` // name
|
||||||
|
Age int `json:"age"` // age
|
||||||
|
Extra
|
||||||
|
Address string
|
||||||
|
Hobby []{
|
||||||
|
Name string // hobby.name
|
||||||
|
Rate string
|
||||||
|
} `json:"hobby"` // hobby
|
||||||
|
Child { // child.bg
|
||||||
|
Name string `json:"name"` // child.name
|
||||||
|
Gender int `json:"gender"` // child.gender
|
||||||
|
Birthday string `json:"birthday"` // child.birthday
|
||||||
|
Desc string // son.desc
|
||||||
|
Son { // son.bg
|
||||||
|
Name string `json:"name"` // son.name
|
||||||
|
Gender int `json:"gender"` // son.gender
|
||||||
|
Birthday string `json:"birthday"` // son.birthday
|
||||||
|
Desc string // son.desc
|
||||||
|
Hobby []{
|
||||||
|
Name string // hobby.name
|
||||||
|
Description string
|
||||||
|
// Map
|
||||||
|
Map map[string]{
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age string `json:"age"`
|
||||||
|
} `json:"map"`
|
||||||
|
} `json:"hobby"` // hobby
|
||||||
|
} // son.end
|
||||||
|
} // child.end
|
||||||
|
} // T.end
|
||||||
154
tools/goctl/pkg/parser/api/format/testdata/test_format.api
vendored
Normal file
154
tools/goctl/pkg/parser/api/format/testdata/test_format.api
vendored
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
import "example_base1.api"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"example_base2.api"
|
||||||
|
)
|
||||||
|
|
||||||
|
info (
|
||||||
|
title: "type title here"
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
email: "type email here"
|
||||||
|
version: "type version here"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetFormReq {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Age int `form:"age"`
|
||||||
|
Hobbits []string `form:"hobbits"`
|
||||||
|
Any any `form:"any"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetFormREsp {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbits []string `json:"hobbits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
PostFormReq {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Age int `form:"age"`
|
||||||
|
Hobbits []string `form:"hobbits"`
|
||||||
|
}
|
||||||
|
PostFormResp {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbits []string `json:"hobbits"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
PostJsonReq {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbits []string `json:"hobbits"`
|
||||||
|
}
|
||||||
|
PostJsonResp {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbits []string `json:"hobbits"`
|
||||||
|
Extra map[string]string `json:"extra"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
PostPathReq {
|
||||||
|
Id string `path:"id"`
|
||||||
|
}
|
||||||
|
PostPathResp {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbits []string `json:"hobbits"`
|
||||||
|
Extra map[string]string `json:"extra"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
DemoOfArrayReq {
|
||||||
|
In string `json:"in"`
|
||||||
|
}
|
||||||
|
DemoOfArrayResp {
|
||||||
|
Out string `json:"out"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Nest {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
NestDemoReq {
|
||||||
|
Nest *Nest `json:"nest"`
|
||||||
|
}
|
||||||
|
NestDemoResp {
|
||||||
|
Nest []*Nest `json:"nest"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: form
|
||||||
|
)
|
||||||
|
service example {
|
||||||
|
@handler getForm
|
||||||
|
get /example/form (GetFormReq) returns (GetFormREsp)
|
||||||
|
|
||||||
|
@handler postForm
|
||||||
|
post /example/form (PostFormReq) returns (PostFormResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: json
|
||||||
|
jwt: Auth
|
||||||
|
)
|
||||||
|
service example {
|
||||||
|
@doc "json demo"
|
||||||
|
@handler postJson
|
||||||
|
post /example/json (PostJsonReq) returns (PostJsonResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: path
|
||||||
|
middleware: Path
|
||||||
|
prefix: /v1/v2
|
||||||
|
)
|
||||||
|
service example {
|
||||||
|
@doc (
|
||||||
|
desc: "path demo"
|
||||||
|
)
|
||||||
|
@handler postPath
|
||||||
|
post /example/path (PostPathReq) returns (PostPathResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: array
|
||||||
|
prefix: /array
|
||||||
|
)
|
||||||
|
service example {
|
||||||
|
@doc (
|
||||||
|
desc: "array response demo"
|
||||||
|
)
|
||||||
|
@handler getArray
|
||||||
|
post /example/array (DemoOfArrayReq) returns ([]DemoOfArrayResp)
|
||||||
|
|
||||||
|
@doc (
|
||||||
|
desc: "array pointer response demo"
|
||||||
|
)
|
||||||
|
@handler getArrayPointer
|
||||||
|
post /example/array/pointer (DemoOfArrayReq) returns ([]*DemoOfArrayResp)
|
||||||
|
|
||||||
|
@doc (
|
||||||
|
desc: "array base response demo"
|
||||||
|
)
|
||||||
|
@handler getArrayBase
|
||||||
|
post /example/array/pointer (DemoOfArrayReq) returns ([]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
service example {
|
||||||
|
@handler nestDemo
|
||||||
|
post /example/nest (NestDemoReq) returns (NestDemoResp)
|
||||||
|
}
|
||||||
|
|
||||||
35
tools/goctl/pkg/parser/api/format/testdata/test_service.api
vendored
Normal file
35
tools/goctl/pkg/parser/api/format/testdata/test_service.api
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// server foo
|
||||||
|
@server(// server
|
||||||
|
// key-value form
|
||||||
|
key1:value1
|
||||||
|
key2:value2
|
||||||
|
jwt:Auth // enable jwt
|
||||||
|
prefix: /v1 // the route prefix
|
||||||
|
)
|
||||||
|
// service foo
|
||||||
|
service foo{// foo
|
||||||
|
// example1
|
||||||
|
@doc "example1"
|
||||||
|
@handler example1 // handler declare
|
||||||
|
get /path/example1// no body
|
||||||
|
@doc( // doc group
|
||||||
|
key1:"value1"
|
||||||
|
key11:"value11"
|
||||||
|
key111:"value111"
|
||||||
|
)
|
||||||
|
@handler example2 // handler example2
|
||||||
|
get /path/example2/:id// path arg
|
||||||
|
@doc
|
||||||
|
"example3"
|
||||||
|
@handler
|
||||||
|
example3
|
||||||
|
get /path/example3/:id
|
||||||
|
( Foo )// no response
|
||||||
|
@doc "example4"
|
||||||
|
@handler example4
|
||||||
|
post /path/example4/a-b returns ( Foo )// no request
|
||||||
|
@doc "example5"
|
||||||
|
@handler example5
|
||||||
|
// example5
|
||||||
|
post /path/example5/a-b ( Foo ) returns ( Bar ) // do not comment between path and body
|
||||||
|
}
|
||||||
34
tools/goctl/pkg/parser/api/format/testdata/test_type_struct_group.api
vendored
Normal file
34
tools/goctl/pkg/parser/api/format/testdata/test_type_struct_group.api
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*aa*/type (
|
||||||
|
/*bb*/T /*cc*/{// T.bg
|
||||||
|
// Name head1
|
||||||
|
/*Name head2*/Name string `json:"name"`// name
|
||||||
|
Age int `json:"age"` // age
|
||||||
|
Extra
|
||||||
|
Address string
|
||||||
|
Hobby []{
|
||||||
|
Name string // hobby.name
|
||||||
|
Rate string
|
||||||
|
} `json:"hobby"` // hobby
|
||||||
|
Child {// child.bg
|
||||||
|
Name string `json:"name"`// child.name
|
||||||
|
Gender int `json:"gender"`// child.gender
|
||||||
|
Birthday string `json:"birthday"`// child.birthday
|
||||||
|
Desc string // son.desc
|
||||||
|
Son {// son.bg
|
||||||
|
Name string `json:"name"`// son.name
|
||||||
|
Gender int `json:"gender"`// son.gender
|
||||||
|
Birthday string `json:"birthday"`// son.birthday
|
||||||
|
Desc string // son.desc
|
||||||
|
Hobby []{
|
||||||
|
Name string // hobby.name
|
||||||
|
Description string
|
||||||
|
// Map
|
||||||
|
Map map[string]{
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age string `json:"age"`
|
||||||
|
}`json:"map"`
|
||||||
|
} `json:"hobby"` // hobby
|
||||||
|
}// son.end
|
||||||
|
}// child.end
|
||||||
|
}// T.end
|
||||||
|
)
|
||||||
32
tools/goctl/pkg/parser/api/format/testdata/test_type_struct_lit.api
vendored
Normal file
32
tools/goctl/pkg/parser/api/format/testdata/test_type_struct_lit.api
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*aa*/type /*bb*/T /*cc*/{// T.bg
|
||||||
|
// Name head1
|
||||||
|
/*Name head2*/Name string `json:"name"`// name
|
||||||
|
Age int `json:"age"` // age
|
||||||
|
Extra
|
||||||
|
Address string
|
||||||
|
Hobby []{
|
||||||
|
Name string // hobby.name
|
||||||
|
Rate string
|
||||||
|
} `json:"hobby"` // hobby
|
||||||
|
Child {// child.bg
|
||||||
|
Name string `json:"name"`// child.name
|
||||||
|
Gender int `json:"gender"`// child.gender
|
||||||
|
Birthday string `json:"birthday"`// child.birthday
|
||||||
|
Desc string // son.desc
|
||||||
|
Son {// son.bg
|
||||||
|
Name string `json:"name"`// son.name
|
||||||
|
Gender int `json:"gender"`// son.gender
|
||||||
|
Birthday string `json:"birthday"`// son.birthday
|
||||||
|
Desc string // son.desc
|
||||||
|
Hobby []{
|
||||||
|
Name string // hobby.name
|
||||||
|
Description string
|
||||||
|
// Map
|
||||||
|
Map map[string]{
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age string `json:"age"`
|
||||||
|
}`json:"map"`
|
||||||
|
} `json:"hobby"` // hobby
|
||||||
|
}// son.end
|
||||||
|
}// child.end
|
||||||
|
}// T.end
|
||||||
412
tools/goctl/pkg/parser/api/parser/analyzer.go
Normal file
412
tools/goctl/pkg/parser/api/parser/analyzer.go
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Analyzer analyzes the ast and converts it to spec.
|
||||||
|
type Analyzer struct {
|
||||||
|
api *API
|
||||||
|
spec *spec.ApiSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
|
||||||
|
isLiteralType := func(dt ast.DataType) bool {
|
||||||
|
_, ok := dt.(*ast.BaseDataType)
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
_, ok = dt.(*ast.AnyDataType)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
switch v := (in).(type) {
|
||||||
|
case *ast.BaseDataType:
|
||||||
|
raw := v.RawText()
|
||||||
|
if IsBaseType(raw) {
|
||||||
|
return spec.PrimitiveType{
|
||||||
|
RawName: raw,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return spec.DefineStruct{RawName: raw}, nil
|
||||||
|
case *ast.AnyDataType:
|
||||||
|
return nil, ast.SyntaxError(v.Pos(), "unsupported any type")
|
||||||
|
case *ast.StructDataType:
|
||||||
|
// TODO(keson) feature: can be extended
|
||||||
|
case *ast.InterfaceDataType:
|
||||||
|
return spec.InterfaceType{RawName: v.RawText()}, nil
|
||||||
|
case *ast.MapDataType:
|
||||||
|
if !isLiteralType(v.Key) {
|
||||||
|
return nil, ast.SyntaxError(v.Pos(), "expected literal type, got <%T>", v)
|
||||||
|
}
|
||||||
|
if !v.Key.CanEqual() {
|
||||||
|
return nil, ast.SyntaxError(v.Pos(), "map key <%T> must be equal data type", v)
|
||||||
|
}
|
||||||
|
value, err := a.astTypeToSpec(v.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return spec.MapType{
|
||||||
|
RawName: v.RawText(),
|
||||||
|
Key: v.RawText(),
|
||||||
|
Value: value,
|
||||||
|
}, nil
|
||||||
|
case *ast.PointerDataType:
|
||||||
|
raw := v.DataType.RawText()
|
||||||
|
if IsBaseType(raw) {
|
||||||
|
return spec.PointerType{RawName: v.RawText(), Type: spec.PrimitiveType{RawName: raw}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := a.astTypeToSpec(v.DataType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return spec.PointerType{
|
||||||
|
RawName: v.RawText(),
|
||||||
|
Type: value,
|
||||||
|
}, nil
|
||||||
|
case *ast.ArrayDataType:
|
||||||
|
if v.Length.Token.Type == token.ELLIPSIS {
|
||||||
|
return nil, ast.SyntaxError(v.Pos(), "Array: unsupported dynamic length")
|
||||||
|
}
|
||||||
|
value, err := a.astTypeToSpec(v.DataType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return spec.ArrayType{
|
||||||
|
RawName: v.RawText(),
|
||||||
|
Value: value,
|
||||||
|
}, nil
|
||||||
|
case *ast.SliceDataType:
|
||||||
|
value, err := a.astTypeToSpec(v.DataType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return spec.ArrayType{
|
||||||
|
RawName: v.RawText(),
|
||||||
|
Value: value,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ast.SyntaxError(in.Pos(), "unsupported type <%T>", in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) convert2Spec() error {
|
||||||
|
if err := a.fillTypes(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.fillService()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) convertAtDoc(atDoc ast.AtDocStmt) spec.AtDoc {
|
||||||
|
var ret spec.AtDoc
|
||||||
|
switch val := atDoc.(type) {
|
||||||
|
case *ast.AtDocLiteralStmt:
|
||||||
|
ret.Text = val.Value.Token.Text
|
||||||
|
case *ast.AtDocGroupStmt:
|
||||||
|
ret.Properties = a.convertKV(val.Values)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) convertKV(kv []*ast.KVExpr) map[string]string {
|
||||||
|
var ret = map[string]string{}
|
||||||
|
for _, v := range kv {
|
||||||
|
key := strings.TrimSuffix(v.Key.Token.Text, ":")
|
||||||
|
ret[key] = v.Value.Token.Text
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) fieldToMember(field *ast.ElemExpr) (spec.Member, error) {
|
||||||
|
var name []string
|
||||||
|
for _, v := range field.Name {
|
||||||
|
name = append(name, v.Token.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
tp, err := a.astTypeToSpec(field.DataType)
|
||||||
|
if err != nil {
|
||||||
|
return spec.Member{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
head, leading := field.CommentGroup()
|
||||||
|
m := spec.Member{
|
||||||
|
Name: strings.Join(name, ", "),
|
||||||
|
Type: tp,
|
||||||
|
Docs: head.List(),
|
||||||
|
Comment: leading.String(),
|
||||||
|
IsInline: field.IsAnonymous(),
|
||||||
|
}
|
||||||
|
if field.Tag != nil {
|
||||||
|
m.Tag = field.Tag.Token.Text
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) fillRouteType(route *spec.Route) error {
|
||||||
|
if route.RequestType != nil {
|
||||||
|
switch route.RequestType.(type) {
|
||||||
|
case spec.DefineStruct:
|
||||||
|
tp, err := a.findDefinedType(route.RequestType.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
route.RequestType = tp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if route.ResponseType != nil {
|
||||||
|
switch route.ResponseType.(type) {
|
||||||
|
case spec.DefineStruct:
|
||||||
|
tp, err := a.findDefinedType(route.ResponseType.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
route.ResponseType = tp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) fillService() error {
|
||||||
|
var groups []spec.Group
|
||||||
|
for _, item := range a.api.ServiceStmts {
|
||||||
|
var group spec.Group
|
||||||
|
if item.AtServerStmt != nil {
|
||||||
|
group.Annotation.Properties = a.convertKV(item.AtServerStmt.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, astRoute := range item.Routes {
|
||||||
|
head, leading := astRoute.CommentGroup()
|
||||||
|
route := spec.Route{
|
||||||
|
Method: astRoute.Route.Method.Token.Text,
|
||||||
|
Path: astRoute.Route.Path.Format(""),
|
||||||
|
Doc: head.List(),
|
||||||
|
Comment: leading.List(),
|
||||||
|
}
|
||||||
|
if astRoute.AtDoc != nil {
|
||||||
|
route.AtDoc = a.convertAtDoc(astRoute.AtDoc)
|
||||||
|
}
|
||||||
|
if astRoute.AtHandler != nil {
|
||||||
|
route.AtDoc = a.convertAtDoc(astRoute.AtDoc)
|
||||||
|
route.Handler = astRoute.AtHandler.Name.Token.Text
|
||||||
|
head, leading := astRoute.AtHandler.CommentGroup()
|
||||||
|
route.HandlerDoc = head.List()
|
||||||
|
route.HandlerComment = leading.List()
|
||||||
|
}
|
||||||
|
|
||||||
|
if astRoute.Route.Request != nil && astRoute.Route.Request.Body != nil {
|
||||||
|
requestType, err := a.getType(astRoute.Route.Request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
route.RequestType = requestType
|
||||||
|
}
|
||||||
|
if astRoute.Route.Response != nil && astRoute.Route.Response.Body != nil {
|
||||||
|
responseType, err := a.getType(astRoute.Route.Response)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
route.ResponseType = responseType
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.fillRouteType(&route); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Routes = append(group.Routes, route)
|
||||||
|
|
||||||
|
name := item.Name.Format("")
|
||||||
|
if len(a.spec.Service.Name) > 0 && a.spec.Service.Name != name {
|
||||||
|
return ast.SyntaxError(item.Name.Pos(), "multiple service names defined <%s> and <%s>", name, a.spec.Service.Name)
|
||||||
|
}
|
||||||
|
a.spec.Service.Name = name
|
||||||
|
}
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.spec.Service.Groups = groups
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) fillTypes() error {
|
||||||
|
for _, item := range a.api.TypeStmt {
|
||||||
|
switch v := (item).(type) {
|
||||||
|
case *ast.TypeLiteralStmt:
|
||||||
|
err := a.fillTypeExpr(v.Expr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case *ast.TypeGroupStmt:
|
||||||
|
for _, expr := range v.ExprList {
|
||||||
|
err := a.fillTypeExpr(expr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var types []spec.Type
|
||||||
|
for _, item := range a.spec.Types {
|
||||||
|
switch v := (item).(type) {
|
||||||
|
case spec.DefineStruct:
|
||||||
|
var members []spec.Member
|
||||||
|
for _, member := range v.Members {
|
||||||
|
switch v := member.Type.(type) {
|
||||||
|
case spec.DefineStruct:
|
||||||
|
tp, err := a.findDefinedType(v.RawName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
member.Type = tp
|
||||||
|
}
|
||||||
|
members = append(members, member)
|
||||||
|
}
|
||||||
|
v.Members = members
|
||||||
|
types = append(types, v)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown type %+v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.spec.Types = types
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) fillTypeExpr(expr *ast.TypeExpr) error {
|
||||||
|
head, _ := expr.CommentGroup()
|
||||||
|
switch val := expr.DataType.(type) {
|
||||||
|
case *ast.StructDataType:
|
||||||
|
var members []spec.Member
|
||||||
|
for _, item := range val.Elements {
|
||||||
|
m, err := a.fieldToMember(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
members = append(members, m)
|
||||||
|
}
|
||||||
|
a.spec.Types = append(a.spec.Types, spec.DefineStruct{
|
||||||
|
RawName: expr.Name.Token.Text,
|
||||||
|
Members: members,
|
||||||
|
Docs: head.List(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return ast.SyntaxError(expr.Pos(), "expected <struct> expr, got <%T>", expr.DataType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) findDefinedType(name string) (spec.Type, error) {
|
||||||
|
for _, item := range a.spec.Types {
|
||||||
|
if _, ok := item.(spec.DefineStruct); ok {
|
||||||
|
if item.Name() == name {
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("type %s not defined", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) getType(expr *ast.BodyStmt) (spec.Type, error) {
|
||||||
|
body := expr.Body
|
||||||
|
var tp spec.Type
|
||||||
|
var err error
|
||||||
|
var rawText = body.Format("")
|
||||||
|
if IsBaseType(body.Value.Token.Text) {
|
||||||
|
tp = spec.PrimitiveType{RawName: body.Value.Token.Text}
|
||||||
|
} else {
|
||||||
|
tp, err = a.findDefinedType(body.Value.Token.Text)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if body.LBrack != nil {
|
||||||
|
if body.Star != nil {
|
||||||
|
return spec.PointerType{
|
||||||
|
RawName: rawText,
|
||||||
|
Type: tp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return spec.ArrayType{
|
||||||
|
RawName: rawText,
|
||||||
|
Value: tp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if body.Star != nil {
|
||||||
|
return spec.PointerType{
|
||||||
|
RawName: rawText,
|
||||||
|
Type: tp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return tp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the given file and returns the parsed spec.
|
||||||
|
func Parse(filename string, src interface{}) (*spec.ApiSpec, error) {
|
||||||
|
p := New(filename, src)
|
||||||
|
ast := p.Parse()
|
||||||
|
if err := p.CheckErrors(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var importManager = make(map[string]placeholder.Type)
|
||||||
|
importManager[ast.Filename] = placeholder.PlaceHolder
|
||||||
|
api, err := convert2API(ast, importManager)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new(spec.ApiSpec)
|
||||||
|
analyzer := Analyzer{
|
||||||
|
api: api,
|
||||||
|
spec: result,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = analyzer.convert2Spec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var kind = map[string]placeholder.Type{
|
||||||
|
"bool": placeholder.PlaceHolder,
|
||||||
|
"int": placeholder.PlaceHolder,
|
||||||
|
"int8": placeholder.PlaceHolder,
|
||||||
|
"int16": placeholder.PlaceHolder,
|
||||||
|
"int32": placeholder.PlaceHolder,
|
||||||
|
"int64": placeholder.PlaceHolder,
|
||||||
|
"uint": placeholder.PlaceHolder,
|
||||||
|
"uint8": placeholder.PlaceHolder,
|
||||||
|
"uint16": placeholder.PlaceHolder,
|
||||||
|
"uint32": placeholder.PlaceHolder,
|
||||||
|
"uint64": placeholder.PlaceHolder,
|
||||||
|
"uintptr": placeholder.PlaceHolder,
|
||||||
|
"float32": placeholder.PlaceHolder,
|
||||||
|
"float64": placeholder.PlaceHolder,
|
||||||
|
"complex64": placeholder.PlaceHolder,
|
||||||
|
"complex128": placeholder.PlaceHolder,
|
||||||
|
"string": placeholder.PlaceHolder,
|
||||||
|
"byte": placeholder.PlaceHolder,
|
||||||
|
"rune": placeholder.PlaceHolder,
|
||||||
|
"any": placeholder.PlaceHolder,
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBaseType returns true if the given type is a base type.
|
||||||
|
func IsBaseType(text string) bool {
|
||||||
|
_, ok := kind[text]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
45
tools/goctl/pkg/parser/api/parser/analyzer_test.go
Normal file
45
tools/goctl/pkg/parser/api/parser/analyzer_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/assertx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Parse(t *testing.T) {
|
||||||
|
t.Run("valid", func(t *testing.T) {
|
||||||
|
_, err := Parse("./testdata/example.api", nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
t.Run("invalid", func(t *testing.T) {
|
||||||
|
data, err := ioutil.ReadFile("./testdata/invalid.api")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
splits := bytes.Split(data, []byte("-----"))
|
||||||
|
var testFile []string
|
||||||
|
for idx, split := range splits {
|
||||||
|
replacer := strings.NewReplacer(" ", "", "\t", "", "\n", "", "\r", "", "\f", "")
|
||||||
|
r := replacer.Replace(string(split))
|
||||||
|
if len(r) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filename := filepath.Join(t.TempDir(), fmt.Sprintf("invalid%d.api", idx))
|
||||||
|
err := ioutil.WriteFile(filename, split, 0666)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
testFile = append(testFile, filename)
|
||||||
|
}
|
||||||
|
for _, v := range testFile {
|
||||||
|
_, err := Parse(v, nil)
|
||||||
|
assertx.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("circleImport", func(t *testing.T) {
|
||||||
|
_, err := Parse("./testdata/base.api", nil)
|
||||||
|
assertx.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
311
tools/goctl/pkg/parser/api/parser/api.go
Normal file
311
tools/goctl/pkg/parser/api/parser/api.go
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// API is the parsed api file.
|
||||||
|
type API struct {
|
||||||
|
Filename string
|
||||||
|
Syntax *ast.SyntaxStmt
|
||||||
|
info *ast.InfoStmt // Info block does not participate in code generation.
|
||||||
|
importStmt []ast.ImportStmt // ImportStmt block does not participate in code generation.
|
||||||
|
TypeStmt []ast.TypeStmt
|
||||||
|
ServiceStmts []*ast.ServiceStmt
|
||||||
|
importManager map[string]placeholder.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert2API(a *ast.AST, importManager map[string]placeholder.Type) (*API, error) {
|
||||||
|
var api = new(API)
|
||||||
|
api.importManager = make(map[string]placeholder.Type)
|
||||||
|
api.Filename = a.Filename
|
||||||
|
for k, v := range importManager {
|
||||||
|
api.importManager[k] = v
|
||||||
|
}
|
||||||
|
one := a.Stmts[0]
|
||||||
|
syntax, ok := one.(*ast.SyntaxStmt)
|
||||||
|
if !ok {
|
||||||
|
return nil, ast.SyntaxError(one.Pos(), "expected syntax statement, got <%T>", one)
|
||||||
|
}
|
||||||
|
api.Syntax = syntax
|
||||||
|
|
||||||
|
for i := 1; i < len(a.Stmts); i++ {
|
||||||
|
one := a.Stmts[i]
|
||||||
|
switch val := one.(type) {
|
||||||
|
case *ast.SyntaxStmt:
|
||||||
|
return nil, ast.DuplicateStmtError(val.Pos(), "duplicate syntax statement")
|
||||||
|
case *ast.InfoStmt:
|
||||||
|
if api.info != nil {
|
||||||
|
return nil, ast.DuplicateStmtError(val.Pos(), "duplicate info statement")
|
||||||
|
}
|
||||||
|
api.info = val
|
||||||
|
case ast.ImportStmt:
|
||||||
|
api.importStmt = append(api.importStmt, val)
|
||||||
|
case ast.TypeStmt:
|
||||||
|
api.TypeStmt = append(api.TypeStmt, val)
|
||||||
|
case *ast.ServiceStmt:
|
||||||
|
api.ServiceStmts = append(api.ServiceStmts, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := api.SelfCheck(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) checkImportStmt() error {
|
||||||
|
f := newFilter()
|
||||||
|
b := f.addCheckItem("import value expression")
|
||||||
|
for _, v := range api.importStmt {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case *ast.ImportLiteralStmt:
|
||||||
|
b.check(val.Value)
|
||||||
|
case *ast.ImportGroupStmt:
|
||||||
|
b.check(val.Values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) checkInfoStmt() error {
|
||||||
|
if api.info == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f := newFilter()
|
||||||
|
b := f.addCheckItem("info key expression")
|
||||||
|
for _, v := range api.info.Values {
|
||||||
|
b.check(v.Key)
|
||||||
|
}
|
||||||
|
return f.error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) checkServiceStmt() error {
|
||||||
|
f := newFilter()
|
||||||
|
serviceNameChecker := f.addCheckItem("service name expression")
|
||||||
|
handlerChecker := f.addCheckItem("handler expression")
|
||||||
|
pathChecker := f.addCheckItem("path expression")
|
||||||
|
var serviceName = map[string]string{}
|
||||||
|
for _, v := range api.ServiceStmts {
|
||||||
|
name := strings.TrimSuffix(v.Name.Format(""), "-api")
|
||||||
|
if sn, ok := serviceName[name]; ok {
|
||||||
|
if sn != name {
|
||||||
|
serviceNameChecker.errorManager.add(ast.SyntaxError(v.Name.Pos(), "multiple service name"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serviceName[name] = name
|
||||||
|
}
|
||||||
|
var group = api.getAtServerValue(v.AtServerStmt, "prefix")
|
||||||
|
for _, item := range v.Routes {
|
||||||
|
handlerChecker.check(item.AtHandler.Name)
|
||||||
|
path := fmt.Sprintf("[%s]:%s", group, item.Route.Format(""))
|
||||||
|
pathChecker.check(ast.NewTokenNode(token.Token{
|
||||||
|
Text: path,
|
||||||
|
Position: item.Route.Pos(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) checkTypeStmt() error {
|
||||||
|
f := newFilter()
|
||||||
|
b := f.addCheckItem("type expression")
|
||||||
|
for _, v := range api.TypeStmt {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case *ast.TypeLiteralStmt:
|
||||||
|
b.check(val.Expr.Name)
|
||||||
|
case *ast.TypeGroupStmt:
|
||||||
|
for _, expr := range val.ExprList {
|
||||||
|
b.check(expr.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) checkTypeDeclareContext() error {
|
||||||
|
var typeMap = map[string]placeholder.Type{}
|
||||||
|
for _, v := range api.TypeStmt {
|
||||||
|
switch tp := v.(type) {
|
||||||
|
case *ast.TypeLiteralStmt:
|
||||||
|
typeMap[tp.Expr.Name.Token.Text] = placeholder.PlaceHolder
|
||||||
|
case *ast.TypeGroupStmt:
|
||||||
|
for _, v := range tp.ExprList {
|
||||||
|
typeMap[v.Name.Token.Text] = placeholder.PlaceHolder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.checkTypeContext(typeMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) checkTypeContext(declareContext map[string]placeholder.Type) error {
|
||||||
|
var em = newErrorManager()
|
||||||
|
for _, v := range api.TypeStmt {
|
||||||
|
switch tp := v.(type) {
|
||||||
|
case *ast.TypeLiteralStmt:
|
||||||
|
em.add(api.checkTypeExprContext(declareContext, tp.Expr.DataType))
|
||||||
|
case *ast.TypeGroupStmt:
|
||||||
|
for _, v := range tp.ExprList {
|
||||||
|
em.add(api.checkTypeExprContext(declareContext, v.DataType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return em.error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) checkTypeExprContext(declareContext map[string]placeholder.Type, tp ast.DataType) error {
|
||||||
|
switch val := tp.(type) {
|
||||||
|
case *ast.ArrayDataType:
|
||||||
|
return api.checkTypeExprContext(declareContext, val.DataType)
|
||||||
|
case *ast.BaseDataType:
|
||||||
|
if IsBaseType(val.Base.Token.Text) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, ok := declareContext[val.Base.Token.Text]
|
||||||
|
if !ok {
|
||||||
|
return ast.SyntaxError(val.Base.Pos(), "unresolved type <%s>", val.Base.Token.Text)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case *ast.MapDataType:
|
||||||
|
var manager = newErrorManager()
|
||||||
|
manager.add(api.checkTypeExprContext(declareContext, val.Key))
|
||||||
|
manager.add(api.checkTypeExprContext(declareContext, val.Value))
|
||||||
|
return manager.error()
|
||||||
|
case *ast.PointerDataType:
|
||||||
|
return api.checkTypeExprContext(declareContext, val.DataType)
|
||||||
|
case *ast.SliceDataType:
|
||||||
|
return api.checkTypeExprContext(declareContext, val.DataType)
|
||||||
|
case *ast.StructDataType:
|
||||||
|
var manager = newErrorManager()
|
||||||
|
for _, e := range val.Elements {
|
||||||
|
manager.add(api.checkTypeExprContext(declareContext, e.DataType))
|
||||||
|
}
|
||||||
|
return manager.error()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) getAtServerValue(atServer *ast.AtServerStmt, key string) string {
|
||||||
|
if atServer == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range atServer.Values {
|
||||||
|
if val.Key.Token.Text == key {
|
||||||
|
return val.Value.Token.Text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) mergeAPI(in *API) error {
|
||||||
|
for k, v := range in.importManager {
|
||||||
|
api.importManager[k] = v
|
||||||
|
}
|
||||||
|
if api.Syntax.Value.Format() != in.Syntax.Value.Format() {
|
||||||
|
return ast.SyntaxError(in.Syntax.Value.Pos(),
|
||||||
|
"multiple syntax value expression, expected <%s>, got <%s>",
|
||||||
|
api.Syntax.Value.Format(),
|
||||||
|
in.Syntax.Value.Format(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
api.TypeStmt = append(api.TypeStmt, in.TypeStmt...)
|
||||||
|
api.ServiceStmts = append(api.ServiceStmts, in.ServiceStmts...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) parseImportedAPI(imports []ast.ImportStmt) ([]*API, error) {
|
||||||
|
var list []*API
|
||||||
|
if len(imports) == 0 {
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var importValueSet = map[string]token.Token{}
|
||||||
|
for _, imp := range imports {
|
||||||
|
switch val := imp.(type) {
|
||||||
|
case *ast.ImportLiteralStmt:
|
||||||
|
importValueSet[strings.ReplaceAll(val.Value.Token.Text, `"`, "")] = val.Value.Token
|
||||||
|
case *ast.ImportGroupStmt:
|
||||||
|
for _, v := range val.Values {
|
||||||
|
importValueSet[strings.ReplaceAll(v.Token.Text, `"`, "")] = v.Token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Dir(api.Filename)
|
||||||
|
for impPath, tok := range importValueSet {
|
||||||
|
if !filepath.IsAbs(impPath) {
|
||||||
|
impPath = filepath.Join(dir, impPath)
|
||||||
|
}
|
||||||
|
// import cycle check
|
||||||
|
if _, ok := api.importManager[impPath]; ok {
|
||||||
|
return nil, ast.SyntaxError(tok.Position, "import circle not allowed")
|
||||||
|
} else {
|
||||||
|
api.importManager[impPath] = placeholder.PlaceHolder
|
||||||
|
}
|
||||||
|
|
||||||
|
p := New(impPath, "")
|
||||||
|
ast := p.Parse()
|
||||||
|
if err := p.CheckErrors(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nestedApi, err := convert2API(ast, api.importManager)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = nestedApi.parseReverse(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
list = append(list, nestedApi)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) parseReverse() error {
|
||||||
|
list, err := api.parseImportedAPI(api.importStmt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, e := range list {
|
||||||
|
if err = api.mergeAPI(e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) SelfCheck() error {
|
||||||
|
if err := api.parseReverse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := api.checkImportStmt(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := api.checkInfoStmt(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := api.checkTypeStmt(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := api.checkServiceStmt(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return api.checkTypeDeclareContext()
|
||||||
|
}
|
||||||
28
tools/goctl/pkg/parser/api/parser/error.go
Normal file
28
tools/goctl/pkg/parser/api/parser/error.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorManager struct {
|
||||||
|
errors []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newErrorManager() *errorManager {
|
||||||
|
return &errorManager{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorManager) add(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.errors = append(e.errors, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorManager) error() error {
|
||||||
|
if len(e.errors)==0{
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf(strings.Join(e.errors, "\n"))
|
||||||
|
}
|
||||||
55
tools/goctl/pkg/parser/api/parser/filter.go
Normal file
55
tools/goctl/pkg/parser/api/parser/filter.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/ast"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/placeholder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type filterBuilder struct {
|
||||||
|
m map[string]placeholder.Type
|
||||||
|
checkExprName string
|
||||||
|
errorManager *errorManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *filterBuilder) check(nodes ...*ast.TokenNode) {
|
||||||
|
for _, node := range nodes {
|
||||||
|
if _, ok := b.m[node.Token.Text]; ok {
|
||||||
|
b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName))
|
||||||
|
} else {
|
||||||
|
b.m[node.Token.Text] = placeholder.PlaceHolder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *filterBuilder) error() error {
|
||||||
|
return b.errorManager.error()
|
||||||
|
}
|
||||||
|
|
||||||
|
type filter struct {
|
||||||
|
builders []*filterBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFilter() *filter {
|
||||||
|
return &filter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filter) addCheckItem(checkExprName string) *filterBuilder {
|
||||||
|
b := &filterBuilder{
|
||||||
|
m: make(map[string]placeholder.Type),
|
||||||
|
checkExprName: checkExprName,
|
||||||
|
errorManager: newErrorManager(),
|
||||||
|
}
|
||||||
|
f.builders = append(f.builders, b)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filter) error() error {
|
||||||
|
if len(f.builders) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var errorManager = newErrorManager()
|
||||||
|
for _, b := range f.builders {
|
||||||
|
errorManager.add(b.error())
|
||||||
|
}
|
||||||
|
return errorManager.error()
|
||||||
|
}
|
||||||
1506
tools/goctl/pkg/parser/api/parser/parser.go
Normal file
1506
tools/goctl/pkg/parser/api/parser/parser.go
Normal file
File diff suppressed because it is too large
Load Diff
1484
tools/goctl/pkg/parser/api/parser/parser_test.go
Normal file
1484
tools/goctl/pkg/parser/api/parser/parser_test.go
Normal file
File diff suppressed because it is too large
Load Diff
5
tools/goctl/pkg/parser/api/parser/testdata/atdoc_group_test.api
vendored
Normal file
5
tools/goctl/pkg/parser/api/parser/testdata/atdoc_group_test.api
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@doc(
|
||||||
|
foo: "foo"
|
||||||
|
bar: "bar"
|
||||||
|
baz: ""
|
||||||
|
)
|
||||||
3
tools/goctl/pkg/parser/api/parser/testdata/atdoc_literal_test.api
vendored
Normal file
3
tools/goctl/pkg/parser/api/parser/testdata/atdoc_literal_test.api
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@doc ""
|
||||||
|
@doc "foo"
|
||||||
|
@doc "bar"
|
||||||
3
tools/goctl/pkg/parser/api/parser/testdata/athandler_test.api
vendored
Normal file
3
tools/goctl/pkg/parser/api/parser/testdata/athandler_test.api
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@handler foo
|
||||||
|
@handler foo1
|
||||||
|
@handler _bar
|
||||||
16
tools/goctl/pkg/parser/api/parser/testdata/atserver_test.api
vendored
Normal file
16
tools/goctl/pkg/parser/api/parser/testdata/atserver_test.api
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@server(
|
||||||
|
foo: bar
|
||||||
|
bar: baz
|
||||||
|
baz: foo
|
||||||
|
qux: /v1
|
||||||
|
quux: /v1/v2
|
||||||
|
middleware: M1,M2
|
||||||
|
timeout1: 1h
|
||||||
|
timeout2: 10m
|
||||||
|
timeout3: 10s
|
||||||
|
timeout4: 10ms
|
||||||
|
timeout5: 10µs
|
||||||
|
timeout6: 10ns
|
||||||
|
timeout7: 1h10m10s10ms10µs10ns
|
||||||
|
maxBytes: 1024
|
||||||
|
)
|
||||||
11
tools/goctl/pkg/parser/api/parser/testdata/base.api
vendored
Normal file
11
tools/goctl/pkg/parser/api/parser/testdata/base.api
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
import "base1.api"
|
||||||
|
info (
|
||||||
|
title: "type title here"
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
email: "type email here"
|
||||||
|
version: "type version here"
|
||||||
|
)
|
||||||
|
|
||||||
11
tools/goctl/pkg/parser/api/parser/testdata/base1.api
vendored
Normal file
11
tools/goctl/pkg/parser/api/parser/testdata/base1.api
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
import "base2.api"
|
||||||
|
info (
|
||||||
|
title: "type title here"
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
email: "type email here"
|
||||||
|
version: "type version here"
|
||||||
|
)
|
||||||
|
|
||||||
11
tools/goctl/pkg/parser/api/parser/testdata/base2.api
vendored
Normal file
11
tools/goctl/pkg/parser/api/parser/testdata/base2.api
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
import "base.api"
|
||||||
|
info (
|
||||||
|
title: "type title here"
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
email: "type email here"
|
||||||
|
version: "type version here"
|
||||||
|
)
|
||||||
|
|
||||||
4
tools/goctl/pkg/parser/api/parser/testdata/comment_test.api
vendored
Normal file
4
tools/goctl/pkg/parser/api/parser/testdata/comment_test.api
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// foo
|
||||||
|
// bar
|
||||||
|
/*foo*/
|
||||||
|
/*bar*/ //baz
|
||||||
167
tools/goctl/pkg/parser/api/parser/testdata/example.api
vendored
Normal file
167
tools/goctl/pkg/parser/api/parser/testdata/example.api
vendored
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
import "example_base1.api"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"example_base2.api"
|
||||||
|
)
|
||||||
|
|
||||||
|
info (
|
||||||
|
title: "type title here"
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
email: "type email here"
|
||||||
|
version: "type version here"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetFormReq {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Age int `form:"age"`
|
||||||
|
Hobbits []string `form:"hobbits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetFormREsp {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbits []string `json:"hobbits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
PostFormReq {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Age int `form:"age"`
|
||||||
|
Hobbits []string `form:"hobbits"`
|
||||||
|
}
|
||||||
|
PostFormResp {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbits []string `json:"hobbits"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
PostJsonReq {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbits []string `json:"hobbits"`
|
||||||
|
}
|
||||||
|
PostJsonResp {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbits []string `json:"hobbits"`
|
||||||
|
Extra map[string]string `json:"extra"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
PostPathReq {
|
||||||
|
Id string `path:"id"`
|
||||||
|
}
|
||||||
|
PostPathResp {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbits []string `json:"hobbits"`
|
||||||
|
Hobbits2 [2]string `json:"hobbits2"`
|
||||||
|
Extra map[string]string `json:"extra"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
DemoOfArrayReq {
|
||||||
|
In string `json:"in"`
|
||||||
|
}
|
||||||
|
DemoOfArrayResp {
|
||||||
|
Out string `json:"out"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Nest {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
NestDemoReq1 {
|
||||||
|
Nest *Nest `json:"nest"`
|
||||||
|
}
|
||||||
|
NestDemoResp1 {
|
||||||
|
Nest []*Nest `json:"nest"`
|
||||||
|
}
|
||||||
|
NestDemoReq2 {
|
||||||
|
*Nest
|
||||||
|
}
|
||||||
|
NestDemoResp2 {
|
||||||
|
*Nest `json:"nest"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: form
|
||||||
|
timeout: 3s
|
||||||
|
)
|
||||||
|
service example {
|
||||||
|
@handler getForm
|
||||||
|
get /example/form (GetFormReq) returns (GetFormREsp)
|
||||||
|
|
||||||
|
@handler postForm
|
||||||
|
post /example/form (PostFormReq) returns (PostFormResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: json
|
||||||
|
jwt: Auth
|
||||||
|
timeout: 3m
|
||||||
|
)
|
||||||
|
service example {
|
||||||
|
@doc "json demo"
|
||||||
|
@handler postJson
|
||||||
|
post /example/json (PostJsonReq) returns (PostJsonResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: path
|
||||||
|
middleware: Path
|
||||||
|
prefix: /v1/v2
|
||||||
|
timeout: 100ms
|
||||||
|
)
|
||||||
|
service example {
|
||||||
|
@doc (
|
||||||
|
desc: "path demo"
|
||||||
|
)
|
||||||
|
@handler postPath
|
||||||
|
post /example/path (PostPathReq) returns (PostPathResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: array
|
||||||
|
prefix: /array
|
||||||
|
maxBytes: 1024
|
||||||
|
)
|
||||||
|
service example {
|
||||||
|
@doc (
|
||||||
|
desc: "array response demo"
|
||||||
|
)
|
||||||
|
@handler getArray
|
||||||
|
post /example/array (DemoOfArrayReq) returns ([]DemoOfArrayResp)
|
||||||
|
|
||||||
|
@doc (
|
||||||
|
desc: "array pointer response demo"
|
||||||
|
)
|
||||||
|
@handler getArrayPointer
|
||||||
|
post /example/array/pointer (DemoOfArrayReq) returns ([]*DemoOfArrayResp)
|
||||||
|
|
||||||
|
@doc (
|
||||||
|
desc: "array base response demo"
|
||||||
|
)
|
||||||
|
@handler getArrayBase
|
||||||
|
post /example/array/base (DemoOfArrayReq) returns ([]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
service example {
|
||||||
|
@handler nestDemo1
|
||||||
|
post /example/nest (NestDemoReq1) returns (NestDemoResp1)
|
||||||
|
|
||||||
|
@handler nestDemo2
|
||||||
|
post /example/nest2 (NestDemoReq2) returns (NestDemoResp2)
|
||||||
|
}
|
||||||
|
|
||||||
12
tools/goctl/pkg/parser/api/parser/testdata/example_base1.api
vendored
Normal file
12
tools/goctl/pkg/parser/api/parser/testdata/example_base1.api
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info(
|
||||||
|
title: "type title here"
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
email: "type email here"
|
||||||
|
version: "type version here"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseReq1{}
|
||||||
|
type BaseResp1{}
|
||||||
12
tools/goctl/pkg/parser/api/parser/testdata/example_base2.api
vendored
Normal file
12
tools/goctl/pkg/parser/api/parser/testdata/example_base2.api
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info(
|
||||||
|
title: "type title here"
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
email: "type email here"
|
||||||
|
version: "type version here"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseReq2{}
|
||||||
|
type BaseResp2{}
|
||||||
5
tools/goctl/pkg/parser/api/parser/testdata/import_group_test.api
vendored
Normal file
5
tools/goctl/pkg/parser/api/parser/testdata/import_group_test.api
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import (
|
||||||
|
""
|
||||||
|
"foo"
|
||||||
|
"bar"
|
||||||
|
)
|
||||||
3
tools/goctl/pkg/parser/api/parser/testdata/import_literal_test.api
vendored
Normal file
3
tools/goctl/pkg/parser/api/parser/testdata/import_literal_test.api
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import ""
|
||||||
|
import "foo"
|
||||||
|
import "bar"
|
||||||
7
tools/goctl/pkg/parser/api/parser/testdata/info_test.api
vendored
Normal file
7
tools/goctl/pkg/parser/api/parser/testdata/info_test.api
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info(
|
||||||
|
title: "type title here"
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
email: "type email here"
|
||||||
|
version: "type version here"
|
||||||
|
)
|
||||||
136
tools/goctl/pkg/parser/api/parser/testdata/invalid.api
vendored
Normal file
136
tools/goctl/pkg/parser/api/parser/testdata/invalid.api
vendored
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// test case: expected syntax statement
|
||||||
|
info ()
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: duplicate syntax statement
|
||||||
|
syntax = "v1"
|
||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: duplicate info statement
|
||||||
|
syntax = "v1"
|
||||||
|
info()
|
||||||
|
info()
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: duplicate type
|
||||||
|
syntax = "v1"
|
||||||
|
type Foo{}
|
||||||
|
type Foo{}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: duplicate type
|
||||||
|
syntax = "v1"
|
||||||
|
type Baz{}
|
||||||
|
type (
|
||||||
|
Baz{}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: multiple service name
|
||||||
|
syntax = "v1"
|
||||||
|
service foo{
|
||||||
|
@handler foo
|
||||||
|
get /foo
|
||||||
|
}
|
||||||
|
service bar{
|
||||||
|
@handler foo
|
||||||
|
get /foo
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: duplicate handler
|
||||||
|
syntax = "v1"
|
||||||
|
service foo{
|
||||||
|
@handler foo
|
||||||
|
get /foo
|
||||||
|
@handler foo
|
||||||
|
get /bar
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: duplicate path
|
||||||
|
syntax = "v1"
|
||||||
|
service foo{
|
||||||
|
@handler foo
|
||||||
|
get /foo
|
||||||
|
@handler bar
|
||||||
|
get /foo
|
||||||
|
@handler qux
|
||||||
|
get /v1/baz
|
||||||
|
}
|
||||||
|
|
||||||
|
@server(
|
||||||
|
prefix: /v1
|
||||||
|
)
|
||||||
|
service foo{
|
||||||
|
@handler qux
|
||||||
|
get /baz
|
||||||
|
@handler quux
|
||||||
|
get /baz
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: type declare context
|
||||||
|
syntax = "v1"
|
||||||
|
type Foo {
|
||||||
|
Bar Bar `json:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: map key expected literal type
|
||||||
|
syntax = "v1"
|
||||||
|
type Foo {
|
||||||
|
Bar map[[]int]string `json:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: map key expected literal type
|
||||||
|
syntax = "v1"
|
||||||
|
type Foo {
|
||||||
|
Bar map[[]int]string `json:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: map key expected literal type
|
||||||
|
syntax = "v1"
|
||||||
|
type Foo {
|
||||||
|
Bar *map[[]int]string `json:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: map valu expected literal type
|
||||||
|
syntax = "v1"
|
||||||
|
type Foo {
|
||||||
|
Bar *map[string]{} `json:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: invalid slice
|
||||||
|
syntax = "v1"
|
||||||
|
type Foo {
|
||||||
|
Bar []map[[]int]string `json:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: array
|
||||||
|
syntax = "v1"
|
||||||
|
type Foo {
|
||||||
|
Bar [...]int `json:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: any
|
||||||
|
syntax = "v1"
|
||||||
|
type Foo {
|
||||||
|
Bar any `json:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
-----
|
||||||
|
// test case: unresolved type
|
||||||
|
syntax = "v1"
|
||||||
|
service example {
|
||||||
|
@handler nestDemo
|
||||||
|
post /example/nest (NestDemoReq) returns (NestDemoResp)
|
||||||
|
}
|
||||||
37
tools/goctl/pkg/parser/api/parser/testdata/service_test.api
vendored
Normal file
37
tools/goctl/pkg/parser/api/parser/testdata/service_test.api
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
service foo {
|
||||||
|
@handler bar
|
||||||
|
get /ping
|
||||||
|
|
||||||
|
@handler bar
|
||||||
|
get /ping;
|
||||||
|
}
|
||||||
|
|
||||||
|
service bar {
|
||||||
|
@doc "bar"
|
||||||
|
@handler foo
|
||||||
|
get /foo/:bar (Foo)
|
||||||
|
|
||||||
|
@doc "bar"
|
||||||
|
@handler foo
|
||||||
|
get /foo/:bar (Foo) returns ();
|
||||||
|
|
||||||
|
@handler foo
|
||||||
|
get /foo/:bar returns (Foo)
|
||||||
|
|
||||||
|
@handler foo
|
||||||
|
get /foo/:bar () returns (Foo);
|
||||||
|
}
|
||||||
|
|
||||||
|
service baz-api {
|
||||||
|
@handler foo
|
||||||
|
post /foo/:bar/foo-bar-baz (Foo) returns (*Bar)
|
||||||
|
|
||||||
|
@handler foo
|
||||||
|
post /foo/:bar/foo-bar-baz (Foo) returns (*Bar);
|
||||||
|
|
||||||
|
@handler bar
|
||||||
|
post /foo ([]Foo) returns ([]*Bar)
|
||||||
|
|
||||||
|
@handler bar
|
||||||
|
post /foo ([]Foo) returns ([]*Bar);
|
||||||
|
}
|
||||||
160
tools/goctl/pkg/parser/api/parser/testdata/test.api
vendored
Normal file
160
tools/goctl/pkg/parser/api/parser/testdata/test.api
vendored
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
// aaaa
|
||||||
|
|
||||||
|
/*bb*/ syntax /*cc*/ = /*dd*/ "v1" /*syntax doc*/ // syntax stmt
|
||||||
|
// bbb
|
||||||
|
|
||||||
|
info ( // info stmt
|
||||||
|
title: "type title here" // title expr
|
||||||
|
/*ee*/
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
email: "type email here"
|
||||||
|
version: "type version here"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliasInt int
|
||||||
|
type AliasString = string
|
||||||
|
type AliasArray [2]int8
|
||||||
|
type AliasArray2 [...]int8
|
||||||
|
type AliasSlice []int8
|
||||||
|
type AliasMap map[string]int
|
||||||
|
type Any interface{}
|
||||||
|
type AliasMapKeyStruct map[{
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Bar {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Bar {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Bar {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]int
|
||||||
|
type AliasMapValueStruct map[string]{
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Bar {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Bar {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Bar {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type Foo {
|
||||||
|
Bar {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Bar {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Bar {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar {
|
||||||
|
Base int `json:"base"`
|
||||||
|
Array1 [2]int `json:"array1"`
|
||||||
|
Array2 [...]int `json:"array2"`
|
||||||
|
Slice []int `json:"slice"`
|
||||||
|
Map1 map[string]int `json:"map1"`
|
||||||
|
Map2 map[string]*int `json:"map2"`
|
||||||
|
Map3 map[string][]int `json:"map3"`
|
||||||
|
Map4 map[string][]*int `json:"map4"`
|
||||||
|
Map5 map[string][2]int `json:"map5"`
|
||||||
|
Map6 map[string][...]int `json:"map6"`
|
||||||
|
Interface interface{} `json:"interface"`
|
||||||
|
Any any `json:"any"`
|
||||||
|
Foo Foo `json:"foo"`
|
||||||
|
Baz {
|
||||||
|
F1 int `json:"f1"`
|
||||||
|
F2 int `json:"f2"`
|
||||||
|
} `json:"baz"`
|
||||||
|
Qux *string `json:"qux"`
|
||||||
|
Quux bool `json:"quux"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
GroupAliasInt int
|
||||||
|
GroupAliasString = string
|
||||||
|
GroupAliasArray [2]int8
|
||||||
|
GroupAliasArray2 [...]int8
|
||||||
|
GroupAliasSlice []int8
|
||||||
|
GroupAliasMap map[string]int
|
||||||
|
GroupAny interface{}
|
||||||
|
GroupFoo {}
|
||||||
|
GroupBar {
|
||||||
|
Base int `json:"base"`
|
||||||
|
Array1 [2]int `json:"array1"`
|
||||||
|
Array2 [...]int `json:"array2"`
|
||||||
|
Slice []int `json:"slice"`
|
||||||
|
Map1 map[string]int `json:"map1"`
|
||||||
|
Map2 map[string]*int `json:"map2"`
|
||||||
|
Map3 map[string][]int `json:"map3"`
|
||||||
|
Map4 map[string][]*int `json:"map4"`
|
||||||
|
Map5 map[string][2]int `json:"map5"`
|
||||||
|
Map6 map[string][...]int `json:"map6"`
|
||||||
|
Interface interface{} `json:"interface"`
|
||||||
|
Any any `json:"any"`
|
||||||
|
Foo Foo `json:"foo"`
|
||||||
|
Baz {
|
||||||
|
F1 int `json:"f1"`
|
||||||
|
F2 int `json:"f2"`
|
||||||
|
} `json:"baz"`
|
||||||
|
Qux *string `json:"qux"`
|
||||||
|
Quux bool `json:"quux"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@server ()
|
||||||
|
service test {
|
||||||
|
@handler foo
|
||||||
|
get /test/foo
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
jwt: Auth
|
||||||
|
group: Group1
|
||||||
|
)
|
||||||
|
service test {
|
||||||
|
@doc "ping"
|
||||||
|
@handler foo
|
||||||
|
get /test/foo
|
||||||
|
@doc (
|
||||||
|
key1: "value1"
|
||||||
|
key2: "value2"
|
||||||
|
)
|
||||||
|
@handler bar
|
||||||
|
get /test/foo (Foo)
|
||||||
|
@handler baz
|
||||||
|
post /test/foo/baz returns (Bar)
|
||||||
|
@handler qux
|
||||||
|
post /test/foo/baz/:qux (Foo) returns (Bar)
|
||||||
|
@handler quux
|
||||||
|
post /test/foo/baz/:qux/qu-ux (Foo) returns (Bar)
|
||||||
|
@handler foobar
|
||||||
|
post /foo/bar (*Foo) returns ([]Bar)
|
||||||
|
@handler barbaz
|
||||||
|
post /bar/baz ([]*Foo) returns ([]int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminal
|
||||||
|
// terminal2
|
||||||
|
/*
|
||||||
|
kkk
|
||||||
|
*/
|
||||||
|
|
||||||
50
tools/goctl/pkg/parser/api/parser/testdata/test_format.api
vendored
Normal file
50
tools/goctl/pkg/parser/api/parser/testdata/test_format.api
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// format api demo
|
||||||
|
syntax ="v1" // dd
|
||||||
|
|
||||||
|
info()
|
||||||
|
info(foo:"")
|
||||||
|
info(foo:""
|
||||||
|
bar: ""
|
||||||
|
quux: "")
|
||||||
|
info(foo:""
|
||||||
|
bar: ""
|
||||||
|
quux: ""
|
||||||
|
)
|
||||||
|
// info statement
|
||||||
|
// info statement
|
||||||
|
info (// Info bloack
|
||||||
|
title: "type title here" // title comment
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
/*aaa*/
|
||||||
|
/*
|
||||||
|
bbb
|
||||||
|
*/
|
||||||
|
email: "type email here" // eamil comment
|
||||||
|
/*aaa*/version:/*bbb*/ "type version here"// version comment
|
||||||
|
)
|
||||||
|
|
||||||
|
import ""
|
||||||
|
import "aaa"
|
||||||
|
import"bb"
|
||||||
|
import "cc"
|
||||||
|
import()
|
||||||
|
import(
|
||||||
|
)
|
||||||
|
import (
|
||||||
|
|
||||||
|
)
|
||||||
|
import ("aa")
|
||||||
|
import ("aa" "bb")
|
||||||
|
import ("aa"
|
||||||
|
"bb"
|
||||||
|
)
|
||||||
|
import ("aa"
|
||||||
|
"bb")
|
||||||
|
import (
|
||||||
|
|
||||||
|
"aa"
|
||||||
|
|
||||||
|
"bb"
|
||||||
|
|
||||||
|
)
|
||||||
7
tools/goctl/pkg/parser/api/placeholder/placeholder.go
Normal file
7
tools/goctl/pkg/parser/api/placeholder/placeholder.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package placeholder
|
||||||
|
|
||||||
|
// Type is the placeholder type.
|
||||||
|
type Type struct{}
|
||||||
|
|
||||||
|
// PlaceHolder is the placeholder.
|
||||||
|
var PlaceHolder Type
|
||||||
667
tools/goctl/pkg/parser/api/scanner/scanner.go
Normal file
667
tools/goctl/pkg/parser/api/scanner/scanner.go
Normal file
@@ -0,0 +1,667 @@
|
|||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
initMode mode = iota
|
||||||
|
|
||||||
|
// document mode bg
|
||||||
|
documentHalfOpen
|
||||||
|
documentOpen
|
||||||
|
documentHalfClose
|
||||||
|
documentClose
|
||||||
|
// document mode end
|
||||||
|
|
||||||
|
// string mode bg
|
||||||
|
stringOpen
|
||||||
|
stringClose
|
||||||
|
// string mode end
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
var missingInput = errors.New("missing input")
|
||||||
|
|
||||||
|
type mode int
|
||||||
|
|
||||||
|
// Scanner is a lexical scanner.
|
||||||
|
type Scanner struct {
|
||||||
|
filename string
|
||||||
|
size int
|
||||||
|
|
||||||
|
data []rune
|
||||||
|
position int // current position in input (points to current char)
|
||||||
|
readPosition int // current reading position in input (after current char)
|
||||||
|
ch rune
|
||||||
|
|
||||||
|
lines []int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextToken returns the next token.
|
||||||
|
func (s *Scanner) NextToken() (token.Token, error) {
|
||||||
|
s.skipWhiteSpace()
|
||||||
|
switch s.ch {
|
||||||
|
case '/':
|
||||||
|
peekOne := s.peekRune()
|
||||||
|
switch peekOne {
|
||||||
|
case '/':
|
||||||
|
return s.scanLineComment(), nil
|
||||||
|
case '*':
|
||||||
|
return s.scanDocument()
|
||||||
|
default:
|
||||||
|
return s.newToken(token.QUO), nil
|
||||||
|
}
|
||||||
|
case '-':
|
||||||
|
return s.newToken(token.SUB), nil
|
||||||
|
case '*':
|
||||||
|
return s.newToken(token.MUL), nil
|
||||||
|
case '(':
|
||||||
|
return s.newToken(token.LPAREN), nil
|
||||||
|
case '[':
|
||||||
|
return s.newToken(token.LBRACK), nil
|
||||||
|
case '{':
|
||||||
|
return s.newToken(token.LBRACE), nil
|
||||||
|
case ',':
|
||||||
|
return s.newToken(token.COMMA), nil
|
||||||
|
case '.':
|
||||||
|
position := s.position
|
||||||
|
peekOne := s.peekRune()
|
||||||
|
if peekOne != '.' {
|
||||||
|
return s.newToken(token.DOT), nil
|
||||||
|
}
|
||||||
|
s.readRune()
|
||||||
|
peekOne = s.peekRune()
|
||||||
|
if peekOne != '.' {
|
||||||
|
return s.newToken(token.DOT), nil
|
||||||
|
}
|
||||||
|
s.readRune()
|
||||||
|
s.readRune()
|
||||||
|
return token.Token{
|
||||||
|
Type: token.ELLIPSIS,
|
||||||
|
Text: "...",
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}, nil
|
||||||
|
case ')':
|
||||||
|
return s.newToken(token.RPAREN), nil
|
||||||
|
case ']':
|
||||||
|
return s.newToken(token.RBRACK), nil
|
||||||
|
case '}':
|
||||||
|
return s.newToken(token.RBRACE), nil
|
||||||
|
case ';':
|
||||||
|
return s.newToken(token.SEMICOLON), nil
|
||||||
|
case ':':
|
||||||
|
return s.newToken(token.COLON), nil
|
||||||
|
case '=':
|
||||||
|
return s.newToken(token.ASSIGN), nil
|
||||||
|
case '@':
|
||||||
|
return s.scanAt()
|
||||||
|
case '"':
|
||||||
|
return s.scanString('"', token.STRING)
|
||||||
|
case '`':
|
||||||
|
return s.scanString('`', token.RAW_STRING)
|
||||||
|
case 0:
|
||||||
|
return token.EofToken, nil
|
||||||
|
default:
|
||||||
|
if s.isIdentifierLetter(s.ch) {
|
||||||
|
return s.scanIdent(), nil
|
||||||
|
}
|
||||||
|
if s.isDigit(s.ch) {
|
||||||
|
return s.scanIntOrDuration(), nil
|
||||||
|
}
|
||||||
|
tok := token.NewIllegalToken(s.ch, s.newPosition(s.position))
|
||||||
|
s.readRune()
|
||||||
|
return tok, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) newToken(tp token.Type) token.Token {
|
||||||
|
tok := token.Token{
|
||||||
|
Type: tp,
|
||||||
|
Text: string(s.ch),
|
||||||
|
Position: s.positionAt(),
|
||||||
|
}
|
||||||
|
s.readRune()
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) readRune() {
|
||||||
|
if s.readPosition >= s.size {
|
||||||
|
s.ch = 0
|
||||||
|
} else {
|
||||||
|
s.ch = s.data[s.readPosition]
|
||||||
|
}
|
||||||
|
s.position = s.readPosition
|
||||||
|
s.readPosition += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) peekRune() rune {
|
||||||
|
if s.readPosition >= s.size {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return s.data[s.readPosition]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanString(delim rune, tp token.Type) (token.Token, error) {
|
||||||
|
position := s.position
|
||||||
|
var stringMode = initMode
|
||||||
|
for {
|
||||||
|
switch s.ch {
|
||||||
|
case delim:
|
||||||
|
switch stringMode {
|
||||||
|
case initMode:
|
||||||
|
stringMode = stringOpen
|
||||||
|
case stringOpen:
|
||||||
|
stringMode = stringClose
|
||||||
|
s.readRune()
|
||||||
|
return token.Token{
|
||||||
|
Type: tp,
|
||||||
|
Text: string(s.data[position:s.position]),
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
switch stringMode {
|
||||||
|
case initMode: // assert: dead code
|
||||||
|
return token.ErrorToken, s.assertExpected(token.EOF, tp)
|
||||||
|
case stringOpen:
|
||||||
|
return token.ErrorToken, s.assertExpectedString(token.EOF.String(), string(delim))
|
||||||
|
case stringClose: // assert: dead code
|
||||||
|
return token.Token{
|
||||||
|
Type: tp,
|
||||||
|
Text: string(s.data[position:s.position]),
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanAt() (token.Token, error) {
|
||||||
|
position := s.position
|
||||||
|
peek := s.peekRune()
|
||||||
|
if !s.isLetter(peek) {
|
||||||
|
if peek == 0 {
|
||||||
|
return token.NewIllegalToken(s.ch, s.positionAt()), nil
|
||||||
|
}
|
||||||
|
return token.ErrorToken, s.assertExpectedString(string(peek), token.IDENT.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
s.readRune()
|
||||||
|
letters := s.scanLetterSet()
|
||||||
|
switch letters {
|
||||||
|
case "handler":
|
||||||
|
return token.Token{
|
||||||
|
Type: token.AT_HANDLER,
|
||||||
|
Text: "@handler",
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}, nil
|
||||||
|
case "server":
|
||||||
|
return token.Token{
|
||||||
|
Type: token.AT_SERVER,
|
||||||
|
Text: "@server",
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}, nil
|
||||||
|
case "doc":
|
||||||
|
return token.Token{
|
||||||
|
Type: token.AT_DOC,
|
||||||
|
Text: "@doc",
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
|
||||||
|
return token.ErrorToken, s.assertExpectedString(
|
||||||
|
"@"+letters,
|
||||||
|
token.AT_DOC.String(),
|
||||||
|
token.AT_HANDLER.String(),
|
||||||
|
token.AT_SERVER.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanIntOrDuration() token.Token {
|
||||||
|
position := s.position
|
||||||
|
for s.isDigit(s.ch) {
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
switch s.ch {
|
||||||
|
case 'n', 'µ', 'm', 's', 'h':
|
||||||
|
return s.scanDuration(position)
|
||||||
|
default:
|
||||||
|
return token.Token{
|
||||||
|
Type: token.INT,
|
||||||
|
Text: string(s.data[position:s.position]),
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanDuration scans a duration literal, for example "1ns", "1µs", "1ms", "1s", "1m", "1h".
|
||||||
|
func (s *Scanner) scanDuration(bgPos int) token.Token {
|
||||||
|
switch s.ch {
|
||||||
|
case 'n':
|
||||||
|
return s.scanNanosecond(bgPos)
|
||||||
|
case 'µ':
|
||||||
|
return s.scanMicrosecond(bgPos)
|
||||||
|
case 'm':
|
||||||
|
return s.scanMillisecondOrMinute(bgPos)
|
||||||
|
case 's':
|
||||||
|
return s.scanSecond(bgPos)
|
||||||
|
case 'h':
|
||||||
|
return s.scanHour(bgPos)
|
||||||
|
default:
|
||||||
|
return s.illegalToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanNanosecond(bgPos int) token.Token {
|
||||||
|
s.readRune()
|
||||||
|
if s.ch != 's' {
|
||||||
|
return s.illegalToken()
|
||||||
|
}
|
||||||
|
s.readRune()
|
||||||
|
return token.Token{
|
||||||
|
Type: token.DURATION,
|
||||||
|
Text: string(s.data[bgPos:s.position]),
|
||||||
|
Position: s.newPosition(bgPos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanMicrosecond(bgPos int) token.Token {
|
||||||
|
s.readRune()
|
||||||
|
if s.ch != 's' {
|
||||||
|
return s.illegalToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.readRune()
|
||||||
|
if !s.isDigit(s.ch) {
|
||||||
|
return token.Token{
|
||||||
|
Type: token.DURATION,
|
||||||
|
Text: string(s.data[bgPos:s.position]),
|
||||||
|
Position: s.newPosition(bgPos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for s.isDigit(s.ch) {
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ch != 'n' {
|
||||||
|
return s.illegalToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.scanNanosecond(bgPos)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanMillisecondOrMinute(bgPos int) token.Token {
|
||||||
|
s.readRune()
|
||||||
|
if s.ch != 's' { // minute
|
||||||
|
if s.ch == 0 || !s.isDigit(s.ch) {
|
||||||
|
return token.Token{
|
||||||
|
Type: token.DURATION,
|
||||||
|
Text: string(s.data[bgPos:s.position]),
|
||||||
|
Position: s.newPosition(bgPos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.scanMinute(bgPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.scanMillisecond(bgPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanMillisecond(bgPos int) token.Token {
|
||||||
|
s.readRune()
|
||||||
|
if !s.isDigit(s.ch) {
|
||||||
|
return token.Token{
|
||||||
|
Type: token.DURATION,
|
||||||
|
Text: string(s.data[bgPos:s.position]),
|
||||||
|
Position: s.newPosition(bgPos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for s.isDigit(s.ch) {
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s.ch {
|
||||||
|
case 'n':
|
||||||
|
return s.scanNanosecond(bgPos)
|
||||||
|
case 'µ':
|
||||||
|
return s.scanMicrosecond(bgPos)
|
||||||
|
default:
|
||||||
|
return s.illegalToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanSecond(bgPos int) token.Token {
|
||||||
|
s.readRune()
|
||||||
|
if !s.isDigit(s.ch) {
|
||||||
|
return token.Token{
|
||||||
|
Type: token.DURATION,
|
||||||
|
Text: string(s.data[bgPos:s.position]),
|
||||||
|
Position: s.newPosition(bgPos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for s.isDigit(s.ch) {
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s.ch {
|
||||||
|
case 'n':
|
||||||
|
return s.scanNanosecond(bgPos)
|
||||||
|
case 'µ':
|
||||||
|
return s.scanMicrosecond(bgPos)
|
||||||
|
case 'm':
|
||||||
|
s.readRune()
|
||||||
|
if s.ch != 's' {
|
||||||
|
return s.illegalToken()
|
||||||
|
}
|
||||||
|
return s.scanMillisecond(bgPos)
|
||||||
|
default:
|
||||||
|
return s.illegalToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanMinute(bgPos int) token.Token {
|
||||||
|
if !s.isDigit(s.ch) {
|
||||||
|
return token.Token{
|
||||||
|
Type: token.DURATION,
|
||||||
|
Text: string(s.data[bgPos:s.position]),
|
||||||
|
Position: s.newPosition(bgPos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for s.isDigit(s.ch) {
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s.ch {
|
||||||
|
case 'n':
|
||||||
|
return s.scanNanosecond(bgPos)
|
||||||
|
case 'µ':
|
||||||
|
return s.scanMicrosecond(bgPos)
|
||||||
|
case 'm':
|
||||||
|
s.readRune()
|
||||||
|
if s.ch != 's' {
|
||||||
|
return s.illegalToken()
|
||||||
|
}
|
||||||
|
return s.scanMillisecond(bgPos)
|
||||||
|
case 's':
|
||||||
|
return s.scanSecond(bgPos)
|
||||||
|
default:
|
||||||
|
return s.illegalToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanHour(bgPos int) token.Token {
|
||||||
|
s.readRune()
|
||||||
|
if !s.isDigit(s.ch) {
|
||||||
|
return token.Token{
|
||||||
|
Type: token.DURATION,
|
||||||
|
Text: string(s.data[bgPos:s.position]),
|
||||||
|
Position: s.newPosition(bgPos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for s.isDigit(s.ch) {
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s.ch {
|
||||||
|
case 'n':
|
||||||
|
return s.scanNanosecond(bgPos)
|
||||||
|
case 'µ':
|
||||||
|
return s.scanMicrosecond(bgPos)
|
||||||
|
case 'm':
|
||||||
|
return s.scanMillisecondOrMinute(bgPos)
|
||||||
|
case 's':
|
||||||
|
return s.scanSecond(bgPos)
|
||||||
|
default:
|
||||||
|
return s.illegalToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) illegalToken() token.Token {
|
||||||
|
tok := token.NewIllegalToken(s.ch, s.newPosition(s.position))
|
||||||
|
s.readRune()
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanIdent() token.Token {
|
||||||
|
position := s.position
|
||||||
|
for s.isIdentifierLetter(s.ch) || s.isDigit(s.ch) {
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
ident := string(s.data[position:s.position])
|
||||||
|
|
||||||
|
if s.ch == ':' {
|
||||||
|
s.readRune()
|
||||||
|
return token.Token{
|
||||||
|
Type: token.KEY,
|
||||||
|
Text: string(s.data[position:s.position]),
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ident == "interface" && s.ch == '{' && s.peekRune() == '}' {
|
||||||
|
s.readRune()
|
||||||
|
s.readRune()
|
||||||
|
return token.Token{
|
||||||
|
Type: token.ANY,
|
||||||
|
Text: string(s.data[position:s.position]),
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Token{
|
||||||
|
Type: token.IDENT,
|
||||||
|
Text: ident,
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanLetterSet() string {
|
||||||
|
position := s.position
|
||||||
|
for s.isLetter(s.ch) {
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
return string(s.data[position:s.position])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanLineComment() token.Token {
|
||||||
|
position := s.position
|
||||||
|
for s.ch != '\n' && s.ch != 0 {
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
return token.Token{
|
||||||
|
Type: token.COMMENT,
|
||||||
|
Text: string(s.data[position:s.position]),
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanDocument() (token.Token, error) {
|
||||||
|
position := s.position
|
||||||
|
var documentMode = initMode
|
||||||
|
for {
|
||||||
|
switch s.ch {
|
||||||
|
case '*':
|
||||||
|
switch documentMode {
|
||||||
|
case documentHalfOpen:
|
||||||
|
documentMode = documentOpen // /*
|
||||||
|
case documentOpen, documentHalfClose:
|
||||||
|
documentMode = documentHalfClose // (?m)\/\*\*+
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
switch documentMode {
|
||||||
|
case initMode, documentHalfOpen: // assert: dead code
|
||||||
|
return token.ErrorToken, s.assertExpected(token.EOF, token.MUL)
|
||||||
|
case documentOpen:
|
||||||
|
return token.ErrorToken, s.assertExpected(token.EOF, token.MUL)
|
||||||
|
case documentHalfClose:
|
||||||
|
return token.ErrorToken, s.assertExpected(token.EOF, token.QUO)
|
||||||
|
}
|
||||||
|
case '/':
|
||||||
|
switch documentMode {
|
||||||
|
case initMode: // /
|
||||||
|
documentMode = documentHalfOpen
|
||||||
|
case documentHalfOpen: // assert: dead code
|
||||||
|
return token.ErrorToken, s.assertExpected(token.QUO, token.MUL)
|
||||||
|
case documentHalfClose:
|
||||||
|
documentMode = documentClose // /*\*+*/
|
||||||
|
s.readRune()
|
||||||
|
tok := token.Token{
|
||||||
|
Type: token.DOCUMENT,
|
||||||
|
Text: string(s.data[position:s.position]),
|
||||||
|
Position: s.newPosition(position),
|
||||||
|
}
|
||||||
|
return tok, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) assertExpected(actual token.Type, expected ...token.Type) error {
|
||||||
|
var expects []string
|
||||||
|
for _, v := range expected {
|
||||||
|
expects = append(expects, fmt.Sprintf("'%s'", v.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprint(s.positionAt().String(), " ", fmt.Sprintf(
|
||||||
|
"expected %s, got '%s'",
|
||||||
|
strings.Join(expects, " | "),
|
||||||
|
actual.String(),
|
||||||
|
))
|
||||||
|
return errors.New(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) assertExpectedString(actual string, expected ...string) error {
|
||||||
|
var expects []string
|
||||||
|
for _, v := range expected {
|
||||||
|
expects = append(expects, fmt.Sprintf("'%s'", v))
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprint(s.positionAt().String(), " ", fmt.Sprintf(
|
||||||
|
"expected %s, got '%s'",
|
||||||
|
strings.Join(expects, " | "),
|
||||||
|
actual,
|
||||||
|
))
|
||||||
|
return errors.New(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) positionAt() token.Position {
|
||||||
|
return s.newPosition(s.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) newPosition(position int) token.Position {
|
||||||
|
line := s.lineCount()
|
||||||
|
return token.Position{
|
||||||
|
Filename: s.filename,
|
||||||
|
Line: line,
|
||||||
|
Column: position - s.lines[line-1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) lineCount() int {
|
||||||
|
return len(s.lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) skipWhiteSpace() {
|
||||||
|
for s.isWhiteSpace(s.ch) {
|
||||||
|
s.readRune()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) isDigit(b rune) bool {
|
||||||
|
return b >= '0' && b <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) isLetter(b rune) bool {
|
||||||
|
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) isIdentifierLetter(b rune) bool {
|
||||||
|
if s.isLetter(b) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return b == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) isWhiteSpace(b rune) bool {
|
||||||
|
if b == '\n' {
|
||||||
|
s.lines = append(s.lines, s.position)
|
||||||
|
}
|
||||||
|
return b == ' ' || b == '\t' || b == '\r' || b == '\f' || b == '\v' || b == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustNewScanner returns a new scanner for the given filename and data.
|
||||||
|
func MustNewScanner(filename string, src interface{}) *Scanner {
|
||||||
|
sc, err := NewScanner(filename, src)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScanner returns a new scanner for the given filename and data.
|
||||||
|
func NewScanner(filename string, src interface{}) (*Scanner, error) {
|
||||||
|
data, err := readData(filename, src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil, missingInput
|
||||||
|
}
|
||||||
|
|
||||||
|
var runeList []rune
|
||||||
|
for _, r := range string(data) {
|
||||||
|
runeList = append(runeList, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = filepath.Base(filename)
|
||||||
|
s := &Scanner{
|
||||||
|
filename: filename,
|
||||||
|
size: len(runeList),
|
||||||
|
data: runeList,
|
||||||
|
lines: []int{-1},
|
||||||
|
readPosition: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.readRune()
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readData(filename string, src interface{}) ([]byte, error) {
|
||||||
|
data, err := ioutil.ReadFile(filename)
|
||||||
|
if err == nil {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
data = append(data, v...)
|
||||||
|
case *bytes.Buffer:
|
||||||
|
data = v.Bytes()
|
||||||
|
case string:
|
||||||
|
data = []byte(v)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported type: %T", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
1490
tools/goctl/pkg/parser/api/scanner/scanner_test.go
Normal file
1490
tools/goctl/pkg/parser/api/scanner/scanner_test.go
Normal file
File diff suppressed because it is too large
Load Diff
28
tools/goctl/pkg/parser/api/scanner/test.api
Normal file
28
tools/goctl/pkg/parser/api/scanner/test.api
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info(
|
||||||
|
title: "type title here"
|
||||||
|
desc: "type desc here"
|
||||||
|
author: "type author here"
|
||||||
|
email: "type email here"
|
||||||
|
version: "type version here"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type request {
|
||||||
|
// TODO: add members here and delete this comment
|
||||||
|
}
|
||||||
|
|
||||||
|
type response {
|
||||||
|
// TODO: add members here and delete this comment
|
||||||
|
}
|
||||||
|
|
||||||
|
@server(
|
||||||
|
jwt: Auth
|
||||||
|
group: template
|
||||||
|
)
|
||||||
|
service template {
|
||||||
|
@doc "foo" /*foo*/
|
||||||
|
@handler handlerName // TODO: replace handler name and delete this comment
|
||||||
|
get /users/id/:userId (request) returns (response)
|
||||||
|
}
|
||||||
21
tools/goctl/pkg/parser/api/token/position.go
Normal file
21
tools/goctl/pkg/parser/api/token/position.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// IllegalPosition is a position that is not valid.
|
||||||
|
var IllegalPosition = Position{}
|
||||||
|
|
||||||
|
// Position represents a rune position in the source code.
|
||||||
|
type Position struct {
|
||||||
|
Filename string
|
||||||
|
Line int
|
||||||
|
Column int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the position.
|
||||||
|
func (p Position) String() string {
|
||||||
|
if len(p.Filename) == 0 {
|
||||||
|
return fmt.Sprint(p.Line, ":", p.Column)
|
||||||
|
}
|
||||||
|
return fmt.Sprint(p.Filename, " ", p.Line, ":", p.Column)
|
||||||
|
}
|
||||||
357
tools/goctl/pkg/parser/api/token/token.go
Normal file
357
tools/goctl/pkg/parser/api/token/token.go
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/placeholder"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Syntax = "syntax"
|
||||||
|
Info = "info"
|
||||||
|
Service = "service"
|
||||||
|
Returns = "returns"
|
||||||
|
Any = "any"
|
||||||
|
TypeKeyword = "type"
|
||||||
|
MapKeyword = "map"
|
||||||
|
ImportKeyword = "import"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type is the type of token.
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
// EofToken is the end of file token.
|
||||||
|
var EofToken = Token{Type: EOF}
|
||||||
|
|
||||||
|
// ErrorToken is the error token.
|
||||||
|
var ErrorToken = Token{Type: error}
|
||||||
|
|
||||||
|
// Token is the token of a rune.
|
||||||
|
type Token struct {
|
||||||
|
Type Type
|
||||||
|
Text string
|
||||||
|
Position Position
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fork forks token for a given Type.
|
||||||
|
func (t Token) Fork(tp Type) Token {
|
||||||
|
return Token{
|
||||||
|
Type: tp,
|
||||||
|
Text: t.Text,
|
||||||
|
Position: t.Position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmptyString returns true if the token is empty string.
|
||||||
|
func (t Token) IsEmptyString() bool {
|
||||||
|
if t.Type != STRING && t.Type != RAW_STRING {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
text := util.TrimWhiteSpace(t.Text)
|
||||||
|
return text == `""` || text == "``"
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsComment returns true if the token is comment.
|
||||||
|
func (t Token) IsComment() bool {
|
||||||
|
return t.IsType(COMMENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDocument returns true if the token is document.
|
||||||
|
func (t Token) IsDocument() bool {
|
||||||
|
return t.IsType(DOCUMENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsType returns true if the token is the given type.
|
||||||
|
func (t Token) IsType(tp Type) bool {
|
||||||
|
return t.Type == tp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line returns the line number of the token.
|
||||||
|
func (t Token) Line() int {
|
||||||
|
return t.Position.Line
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string of the token.
|
||||||
|
func (t Token) String() string {
|
||||||
|
if t == ErrorToken {
|
||||||
|
return t.Type.String()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %s %s", t.Position.String(), t.Type.String(), t.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns true if the token is valid.
|
||||||
|
func (t Token) Valid() bool {
|
||||||
|
return t.Type != token_bg
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsKeyword returns true if the token is keyword.
|
||||||
|
func (t Token) IsKeyword() bool {
|
||||||
|
return golang_keyword_beg < t.Type && t.Type < golang_keyword_end
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBaseType returns true if the token is base type.
|
||||||
|
func (t Token) IsBaseType() bool {
|
||||||
|
_, ok := baseDataType[t.Text]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHttpMethod returns true if the token is http method.
|
||||||
|
func (t Token) IsHttpMethod() bool {
|
||||||
|
_, ok := httpMethod[t.Text]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is returns true if the token text is one of the given list.
|
||||||
|
func (t Token) Is(text ...string) bool {
|
||||||
|
for _, v := range text {
|
||||||
|
if t.Text == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
token_bg Type = iota
|
||||||
|
error
|
||||||
|
ILLEGAL
|
||||||
|
EOF
|
||||||
|
COMMENT
|
||||||
|
DOCUMENT
|
||||||
|
|
||||||
|
literal_beg
|
||||||
|
IDENT // main
|
||||||
|
INT // 123
|
||||||
|
DURATION // 3s,3ms
|
||||||
|
STRING // "abc"
|
||||||
|
RAW_STRING // `abc`
|
||||||
|
PATH // `abc`
|
||||||
|
KEY // `abc:`
|
||||||
|
literal_end
|
||||||
|
|
||||||
|
operator_beg
|
||||||
|
SUB // -
|
||||||
|
MUL // *
|
||||||
|
QUO // /
|
||||||
|
ASSIGN // =
|
||||||
|
|
||||||
|
LPAREN // (
|
||||||
|
LBRACK // [
|
||||||
|
LBRACE // {
|
||||||
|
COMMA // ,
|
||||||
|
DOT // .
|
||||||
|
|
||||||
|
RPAREN // )
|
||||||
|
RBRACE // }
|
||||||
|
RBRACK // ]
|
||||||
|
SEMICOLON // ;
|
||||||
|
COLON // :
|
||||||
|
ELLIPSIS
|
||||||
|
operator_end
|
||||||
|
|
||||||
|
golang_keyword_beg
|
||||||
|
BREAK
|
||||||
|
CASE
|
||||||
|
CHAN
|
||||||
|
CONST
|
||||||
|
CONTINUE
|
||||||
|
|
||||||
|
DEFAULT
|
||||||
|
DEFER
|
||||||
|
ELSE
|
||||||
|
FALLTHROUGH
|
||||||
|
FOR
|
||||||
|
|
||||||
|
FUNC
|
||||||
|
GO
|
||||||
|
GOTO
|
||||||
|
IF
|
||||||
|
IMPORT
|
||||||
|
|
||||||
|
INTERFACE
|
||||||
|
MAP
|
||||||
|
PACKAGE
|
||||||
|
RANGE
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
STRUCT
|
||||||
|
SWITCH
|
||||||
|
TYPE
|
||||||
|
VAR
|
||||||
|
golang_keyword_end
|
||||||
|
|
||||||
|
api_keyword_bg
|
||||||
|
AT_DOC
|
||||||
|
AT_HANDLER
|
||||||
|
AT_SERVER
|
||||||
|
ANY
|
||||||
|
|
||||||
|
api_keyword_end
|
||||||
|
token_end
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns the string of the token type.
|
||||||
|
func (t Type) String() string {
|
||||||
|
if t >= token_bg && t < token_end {
|
||||||
|
return tokens[t]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokens = [...]string{
|
||||||
|
ILLEGAL: "ILLEGAL",
|
||||||
|
|
||||||
|
EOF: "EOF",
|
||||||
|
COMMENT: "COMMENT",
|
||||||
|
DOCUMENT: "DOCUMENT",
|
||||||
|
|
||||||
|
IDENT: "IDENT",
|
||||||
|
INT: "INT",
|
||||||
|
DURATION: "DURATION",
|
||||||
|
STRING: "STRING",
|
||||||
|
RAW_STRING: "RAW_STRING",
|
||||||
|
PATH: "PATH",
|
||||||
|
KEY: "KEY",
|
||||||
|
|
||||||
|
SUB: "-",
|
||||||
|
MUL: "*",
|
||||||
|
QUO: "/",
|
||||||
|
ASSIGN: "=",
|
||||||
|
|
||||||
|
LPAREN: "(",
|
||||||
|
LBRACK: "[",
|
||||||
|
LBRACE: "{",
|
||||||
|
COMMA: ",",
|
||||||
|
DOT: ".",
|
||||||
|
|
||||||
|
RPAREN: ")",
|
||||||
|
RBRACK: "]",
|
||||||
|
RBRACE: "}",
|
||||||
|
SEMICOLON: ";",
|
||||||
|
COLON: ":",
|
||||||
|
ELLIPSIS: "...",
|
||||||
|
|
||||||
|
BREAK: "break",
|
||||||
|
CASE: "case",
|
||||||
|
CHAN: "chan",
|
||||||
|
CONST: "const",
|
||||||
|
CONTINUE: "continue",
|
||||||
|
|
||||||
|
DEFAULT: "default",
|
||||||
|
DEFER: "defer",
|
||||||
|
ELSE: "else",
|
||||||
|
FALLTHROUGH: "fallthrough",
|
||||||
|
FOR: "for",
|
||||||
|
|
||||||
|
FUNC: "func",
|
||||||
|
GO: "go",
|
||||||
|
GOTO: "goto",
|
||||||
|
IF: "if",
|
||||||
|
IMPORT: "import",
|
||||||
|
|
||||||
|
INTERFACE: "interface",
|
||||||
|
MAP: "map",
|
||||||
|
PACKAGE: "package",
|
||||||
|
RANGE: "range",
|
||||||
|
RETURN: "return",
|
||||||
|
|
||||||
|
SELECT: "select",
|
||||||
|
STRUCT: "struct",
|
||||||
|
SWITCH: "switch",
|
||||||
|
TYPE: "type",
|
||||||
|
VAR: "var",
|
||||||
|
|
||||||
|
AT_DOC: "@doc",
|
||||||
|
AT_HANDLER: "@handler",
|
||||||
|
AT_SERVER: "@server",
|
||||||
|
ANY: "interface{}",
|
||||||
|
}
|
||||||
|
|
||||||
|
// HttpMethods returns the http methods.
|
||||||
|
var HttpMethods = []interface{}{"get", "head", "post", "put", "patch", "delete", "connect", "options", "trace"}
|
||||||
|
|
||||||
|
var httpMethod = map[string]placeholder.Type{
|
||||||
|
"get": placeholder.PlaceHolder,
|
||||||
|
"head": placeholder.PlaceHolder,
|
||||||
|
"post": placeholder.PlaceHolder,
|
||||||
|
"put": placeholder.PlaceHolder,
|
||||||
|
"patch": placeholder.PlaceHolder,
|
||||||
|
"delete": placeholder.PlaceHolder,
|
||||||
|
"connect": placeholder.PlaceHolder,
|
||||||
|
"options": placeholder.PlaceHolder,
|
||||||
|
"trace": placeholder.PlaceHolder,
|
||||||
|
}
|
||||||
|
|
||||||
|
var keywords = map[string]Type{
|
||||||
|
// golang_keyword_bg
|
||||||
|
"break": BREAK,
|
||||||
|
"case": CASE,
|
||||||
|
"chan": CHAN,
|
||||||
|
"const": CONST,
|
||||||
|
"continue": CONTINUE,
|
||||||
|
|
||||||
|
"default": DEFAULT,
|
||||||
|
"defer": DEFER,
|
||||||
|
"else": ELSE,
|
||||||
|
"fallthrough": FALLTHROUGH,
|
||||||
|
"for": FOR,
|
||||||
|
|
||||||
|
"func": FUNC,
|
||||||
|
"go": GO,
|
||||||
|
"goto": GOTO,
|
||||||
|
"if": IF,
|
||||||
|
"import": IMPORT,
|
||||||
|
|
||||||
|
"interface": INTERFACE,
|
||||||
|
"map": MAP,
|
||||||
|
"package": PACKAGE,
|
||||||
|
"range": RANGE,
|
||||||
|
"return": RETURN,
|
||||||
|
|
||||||
|
"select": SELECT,
|
||||||
|
"struct": STRUCT,
|
||||||
|
"switch": SWITCH,
|
||||||
|
"type": TYPE,
|
||||||
|
"var": VAR,
|
||||||
|
// golang_keyword_end
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseDataType = map[string]placeholder.Type{
|
||||||
|
"bool": placeholder.PlaceHolder,
|
||||||
|
"uint8": placeholder.PlaceHolder,
|
||||||
|
"uint16": placeholder.PlaceHolder,
|
||||||
|
"uint32": placeholder.PlaceHolder,
|
||||||
|
"uint64": placeholder.PlaceHolder,
|
||||||
|
"int8": placeholder.PlaceHolder,
|
||||||
|
"int16": placeholder.PlaceHolder,
|
||||||
|
"int32": placeholder.PlaceHolder,
|
||||||
|
"int64": placeholder.PlaceHolder,
|
||||||
|
"float32": placeholder.PlaceHolder,
|
||||||
|
"float64": placeholder.PlaceHolder,
|
||||||
|
"complex64": placeholder.PlaceHolder,
|
||||||
|
"complex128": placeholder.PlaceHolder,
|
||||||
|
"string": placeholder.PlaceHolder,
|
||||||
|
"int": placeholder.PlaceHolder,
|
||||||
|
"uint": placeholder.PlaceHolder,
|
||||||
|
"uintptr": placeholder.PlaceHolder,
|
||||||
|
"byte": placeholder.PlaceHolder,
|
||||||
|
"rune": placeholder.PlaceHolder,
|
||||||
|
"any": placeholder.PlaceHolder,
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupKeyword returns the keyword type if the given ident is keyword.
|
||||||
|
func LookupKeyword(ident string) (Type, bool) {
|
||||||
|
tp, ok := keywords[ident]
|
||||||
|
return tp, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIllegalToken returns a new illegal token.
|
||||||
|
func NewIllegalToken(b rune, pos Position) Token {
|
||||||
|
return Token{
|
||||||
|
Type: ILLEGAL,
|
||||||
|
Text: string(b),
|
||||||
|
Position: pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -111,3 +111,13 @@ func isGolangKeyword(s string) bool {
|
|||||||
_, ok := goKeyword[s]
|
_, ok := goKeyword[s]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TrimWhiteSpace(s string) string {
|
||||||
|
r := strings.NewReplacer(" ", "", "\t", "", "\n", "", "\f", "", "\r", "")
|
||||||
|
return r.Replace(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsEmptyStringOrWhiteSpace(s string) bool {
|
||||||
|
v := TrimWhiteSpace(s)
|
||||||
|
return len(v) == 0
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user