github.com/jwilson-ts/prototool@v1.3.0/internal/format/main_visitor.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package format
    22  
    23  import (
    24  	"fmt"
    25  	"strings"
    26  
    27  	"github.com/emicklei/proto"
    28  	"github.com/uber/prototool/internal/text"
    29  )
    30  
    31  var _ proto.Visitor = &mainVisitor{}
    32  
    33  type mainVisitor struct {
    34  	*baseVisitor
    35  
    36  	isProto2          bool
    37  	haveHitNonComment bool
    38  	parent            proto.Visitee
    39  }
    40  
    41  func newMainVisitor(isProto2 bool) *mainVisitor {
    42  	return &mainVisitor{isProto2: isProto2, baseVisitor: newBaseVisitor()}
    43  }
    44  
    45  func (v *mainVisitor) Do() []*text.Failure {
    46  	return v.Failures
    47  }
    48  
    49  func (v *mainVisitor) VisitMessage(element *proto.Message) {
    50  	v.haveHitNonComment = true
    51  	v.PComment(element.Comment)
    52  	prefix := "message "
    53  	if element.IsExtend {
    54  		prefix = "extend "
    55  	}
    56  	if len(element.Elements) == 0 {
    57  		v.P(prefix, element.Name, " {}")
    58  		v.P()
    59  		return
    60  	}
    61  	v.P(prefix, element.Name, " {")
    62  	v.In()
    63  	originalParent := v.parent
    64  	v.parent = element
    65  	for _, child := range element.Elements {
    66  		child.Accept(v)
    67  	}
    68  	v.parent = originalParent
    69  	v.Out()
    70  	v.P("}")
    71  	if v.parent == nil {
    72  		v.P()
    73  	}
    74  }
    75  
    76  func (v *mainVisitor) VisitService(element *proto.Service) {
    77  	v.haveHitNonComment = true
    78  	v.PComment(element.Comment)
    79  	if len(element.Elements) == 0 {
    80  		v.P("service ", element.Name, " {}")
    81  		v.P()
    82  		return
    83  	}
    84  	v.P("service ", element.Name, " {")
    85  	v.In()
    86  	originalParent := v.parent
    87  	v.parent = element
    88  	for _, child := range element.Elements {
    89  		child.Accept(v)
    90  	}
    91  	v.parent = originalParent
    92  	v.Out()
    93  	v.P("}")
    94  	v.P()
    95  }
    96  
    97  func (v *mainVisitor) VisitSyntax(element *proto.Syntax) {
    98  	// done in first pass visitor
    99  	v.haveHitNonComment = true
   100  }
   101  
   102  func (v *mainVisitor) VisitPackage(element *proto.Package) {
   103  	// done in first pass visitor
   104  	v.haveHitNonComment = true
   105  }
   106  
   107  func (v *mainVisitor) VisitOption(element *proto.Option) {
   108  	v.haveHitNonComment = true
   109  	// file options done in first pass visitor
   110  	if v.parent == nil {
   111  		return
   112  	}
   113  	switch v.parent.(type) {
   114  	case (*proto.Enum):
   115  		v.POptions(element)
   116  	case (*proto.Message):
   117  		v.POptions(element)
   118  	case (*proto.Oneof):
   119  		v.POptions(element)
   120  	case (*proto.Service):
   121  		v.POptions(element)
   122  	default:
   123  		v.AddFailure(element.Position, "unhandled child option")
   124  	}
   125  }
   126  
   127  func (v *mainVisitor) VisitImport(element *proto.Import) {
   128  	// done in first pass visitor
   129  	v.haveHitNonComment = true
   130  }
   131  
   132  func (v *mainVisitor) VisitNormalField(element *proto.NormalField) {
   133  	v.haveHitNonComment = true
   134  	prefix := ""
   135  	if element.Repeated {
   136  		prefix = "repeated "
   137  	}
   138  	if v.isProto2 {
   139  		// technically these are only set if the file is proto2
   140  		// but doing this just to make sure
   141  		if element.Required {
   142  			prefix = "required "
   143  		} else {
   144  			prefix = "optional "
   145  		}
   146  	}
   147  	v.PField(prefix, element.Type, element.Field)
   148  }
   149  
   150  func (v *mainVisitor) VisitEnumField(element *proto.EnumField) {
   151  	v.haveHitNonComment = true
   152  	v.PEnumField(element)
   153  }
   154  
   155  func (v *mainVisitor) VisitEnum(element *proto.Enum) {
   156  	v.haveHitNonComment = true
   157  	v.PComment(element.Comment)
   158  	if len(element.Elements) == 0 {
   159  		v.P("enum ", element.Name, " {}")
   160  		v.P()
   161  		return
   162  	}
   163  	v.P("enum ", element.Name, " {")
   164  	v.In()
   165  	originalParent := v.parent
   166  	v.parent = element
   167  	for _, child := range element.Elements {
   168  		child.Accept(v)
   169  	}
   170  	v.parent = originalParent
   171  	v.Out()
   172  	v.P("}")
   173  	if v.parent == nil {
   174  		v.P()
   175  	}
   176  }
   177  
   178  func (v *mainVisitor) VisitComment(element *proto.Comment) {
   179  	if v.haveHitNonComment {
   180  		v.PComment(element)
   181  		v.P()
   182  	}
   183  }
   184  
   185  func (v *mainVisitor) VisitOneof(element *proto.Oneof) {
   186  	v.haveHitNonComment = true
   187  	v.PComment(element.Comment)
   188  	if len(element.Elements) == 0 {
   189  		// TODO: is this even legal?
   190  		v.P("oneof ", element.Name, " {}")
   191  		return
   192  	}
   193  	v.P("oneof ", element.Name, " {")
   194  	v.In()
   195  	originalParent := v.parent
   196  	v.parent = element
   197  	for _, child := range element.Elements {
   198  		child.Accept(v)
   199  	}
   200  	v.parent = originalParent
   201  	v.Out()
   202  	v.P("}")
   203  }
   204  
   205  func (v *mainVisitor) VisitOneofField(element *proto.OneOfField) {
   206  	v.haveHitNonComment = true
   207  	v.PField("", element.Type, element.Field)
   208  }
   209  
   210  func (v *mainVisitor) VisitReserved(element *proto.Reserved) {
   211  	v.haveHitNonComment = true
   212  	if len(element.Ranges) > 0 && len(element.FieldNames) > 0 {
   213  		v.AddFailure(element.Position, "reserved had both integer ranges and field names which is unexpected")
   214  		return
   215  	}
   216  	v.PComment(element.Comment)
   217  	if len(element.Ranges) > 0 {
   218  		rangeStrings := make([]string, len(element.Ranges))
   219  		for i, r := range element.Ranges {
   220  			rangeStrings[i] = r.SourceRepresentation()
   221  		}
   222  		v.PWithInlineComment(element.InlineComment, "reserved ", strings.Join(rangeStrings, ", "), ";")
   223  		return
   224  	}
   225  	if len(element.FieldNames) > 0 {
   226  		fieldNameStrings := make([]string, len(element.FieldNames))
   227  		for i, fieldName := range element.FieldNames {
   228  			fieldNameStrings[i] = `"` + fieldName + `"`
   229  		}
   230  		v.PWithInlineComment(element.InlineComment, "reserved ", strings.Join(fieldNameStrings, ", "), ";")
   231  	}
   232  }
   233  
   234  func (v *mainVisitor) VisitRPC(element *proto.RPC) {
   235  	v.haveHitNonComment = true
   236  	v.PComment(element.Comment)
   237  	requestStream := ""
   238  	if element.StreamsRequest {
   239  		requestStream = "stream "
   240  	}
   241  	responseStream := ""
   242  	if element.StreamsReturns {
   243  		responseStream = "stream "
   244  	}
   245  	if len(element.Options) == 0 {
   246  		v.PWithInlineComment(element.InlineComment, "rpc ", element.Name, "(", requestStream, element.RequestType, ") returns (", responseStream, element.ReturnsType, ");")
   247  		return
   248  	}
   249  	v.P("rpc ", element.Name, "(", requestStream, element.RequestType, ") returns (", responseStream, element.ReturnsType, ") {")
   250  	v.In()
   251  	v.POptions(element.Options...)
   252  	v.Out()
   253  	v.PWithInlineComment(element.InlineComment, "}")
   254  }
   255  
   256  func (v *mainVisitor) VisitMapField(element *proto.MapField) {
   257  	v.haveHitNonComment = true
   258  	v.PField("", fmt.Sprintf("map<%s, %s>", element.KeyType, element.Type), element.Field)
   259  }
   260  
   261  func (v *mainVisitor) VisitGroup(element *proto.Group) {
   262  	v.haveHitNonComment = true
   263  	v.PComment(element.Comment)
   264  	prefix := ""
   265  	// TODO: required and repeated not handled yet, add when handled
   266  	if element.Optional {
   267  		prefix = "optional "
   268  	}
   269  	if len(element.Elements) == 0 {
   270  		v.P(prefix, "group ", element.Name, " = ", element.Sequence, " {}")
   271  		return
   272  	}
   273  	v.P(prefix, "group ", element.Name, " = ", element.Sequence, " {")
   274  	v.In()
   275  	originalParent := v.parent
   276  	v.parent = element
   277  	for _, child := range element.Elements {
   278  		child.Accept(v)
   279  	}
   280  	v.parent = originalParent
   281  	v.Out()
   282  	v.P("}")
   283  }
   284  
   285  func (v *mainVisitor) VisitExtensions(element *proto.Extensions) {
   286  	v.haveHitNonComment = true
   287  	v.PComment(element.Comment)
   288  	rangeStrings := make([]string, len(element.Ranges))
   289  	for i, r := range element.Ranges {
   290  		rangeStrings[i] = r.SourceRepresentation()
   291  	}
   292  	v.PWithInlineComment(element.InlineComment, "extensions ", strings.Join(rangeStrings, ", "), ";")
   293  }