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 { result.LeadingCommentGroup = append(result.LeadingCommentGroup, node.LeadingCommentGroup...) } 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) }