github.com/nikron/prototool@v1.3.0/internal/format/first_pass_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  	"sort"
    25  
    26  	"github.com/emicklei/proto"
    27  	"github.com/uber/prototool/internal/protostrs"
    28  	"github.com/uber/prototool/internal/text"
    29  )
    30  
    31  var _ proto.Visitor = &firstPassVisitor{}
    32  
    33  type firstPassVisitor struct {
    34  	*baseVisitor
    35  
    36  	Syntax  *proto.Syntax
    37  	Package *proto.Package
    38  	Options []*proto.Option
    39  	Imports []*proto.Import
    40  
    41  	haveHitNonComment bool
    42  
    43  	filename                 string
    44  	fix                      bool
    45  	goPackageOption          *proto.Option
    46  	javaMultipleFilesOption  *proto.Option
    47  	javaOuterClassnameOption *proto.Option
    48  	javaPackageOption        *proto.Option
    49  }
    50  
    51  func newFirstPassVisitor(filename string, fix bool) *firstPassVisitor {
    52  	return &firstPassVisitor{baseVisitor: newBaseVisitor(), filename: filename, fix: fix}
    53  }
    54  
    55  func (v *firstPassVisitor) Do() []*text.Failure {
    56  	if v.Syntax != nil {
    57  		v.PComment(v.Syntax.Comment)
    58  		if v.Syntax.Comment != nil {
    59  			// special case, we add a newline in between the first comment and syntax
    60  			// to separate licenses, file descriptions, etc.
    61  			v.P()
    62  		}
    63  		v.PWithInlineComment(v.Syntax.InlineComment, `syntax = "`, v.Syntax.Value, `";`)
    64  		v.P()
    65  	}
    66  	if v.Package != nil {
    67  		v.PComment(v.Package.Comment)
    68  		v.PWithInlineComment(v.Package.InlineComment, `package `, v.Package.Name, `;`)
    69  		v.P()
    70  	}
    71  	if v.fix && v.Package != nil {
    72  		if v.goPackageOption == nil {
    73  			v.goPackageOption = &proto.Option{Name: "go_package"}
    74  		}
    75  		if v.javaMultipleFilesOption == nil {
    76  			v.javaMultipleFilesOption = &proto.Option{Name: "java_multiple_files"}
    77  		}
    78  		if v.javaOuterClassnameOption == nil {
    79  			v.javaOuterClassnameOption = &proto.Option{Name: "java_outer_classname"}
    80  		}
    81  		if v.javaPackageOption == nil {
    82  			v.javaPackageOption = &proto.Option{Name: "java_package"}
    83  		}
    84  		v.goPackageOption.Constant = proto.Literal{
    85  			Source:   protostrs.GoPackage(v.Package.Name),
    86  			IsString: true,
    87  		}
    88  		v.javaMultipleFilesOption.Constant = proto.Literal{
    89  			Source: "true",
    90  		}
    91  		v.javaOuterClassnameOption.Constant = proto.Literal{
    92  			Source:   protostrs.JavaOuterClassname(v.filename),
    93  			IsString: true,
    94  		}
    95  		v.javaPackageOption.Constant = proto.Literal{
    96  			Source:   protostrs.JavaPackage(v.Package.Name),
    97  			IsString: true,
    98  		}
    99  		v.Options = append(
   100  			v.Options,
   101  			v.goPackageOption,
   102  			v.javaMultipleFilesOption,
   103  			v.javaOuterClassnameOption,
   104  			v.javaPackageOption,
   105  		)
   106  	}
   107  	if len(v.Options) > 0 {
   108  		v.POptions(v.Options...)
   109  		v.P()
   110  	}
   111  	if len(v.Imports) > 0 {
   112  		v.PImports(v.Imports)
   113  		v.P()
   114  	}
   115  	return v.Failures
   116  }
   117  
   118  func (v *firstPassVisitor) VisitMessage(element *proto.Message) {
   119  	v.haveHitNonComment = true
   120  }
   121  
   122  func (v *firstPassVisitor) VisitService(element *proto.Service) {
   123  	v.haveHitNonComment = true
   124  }
   125  
   126  func (v *firstPassVisitor) VisitSyntax(element *proto.Syntax) {
   127  	v.haveHitNonComment = true
   128  	if v.Syntax != nil {
   129  		v.AddFailure(element.Position, "duplicate syntax specified")
   130  		return
   131  	}
   132  	v.Syntax = element
   133  }
   134  
   135  func (v *firstPassVisitor) VisitPackage(element *proto.Package) {
   136  	v.haveHitNonComment = true
   137  	if v.Package != nil {
   138  		v.AddFailure(element.Position, "duplicate package specified")
   139  		return
   140  	}
   141  	v.Package = element
   142  }
   143  
   144  func (v *firstPassVisitor) VisitOption(element *proto.Option) {
   145  	// this will only hit file options since we don't do any
   146  	// visiting of children in this visitor
   147  	v.haveHitNonComment = true
   148  	if v.fix {
   149  		switch element.Name {
   150  		case "go_package":
   151  			v.goPackageOption = element
   152  			return
   153  		case "java_multiple_files":
   154  			v.javaMultipleFilesOption = element
   155  			return
   156  		case "java_outer_classname":
   157  			v.javaOuterClassnameOption = element
   158  			return
   159  		case "java_package":
   160  			v.javaPackageOption = element
   161  			return
   162  		}
   163  	}
   164  	v.Options = append(v.Options, element)
   165  }
   166  
   167  func (v *firstPassVisitor) VisitImport(element *proto.Import) {
   168  	v.haveHitNonComment = true
   169  	v.Imports = append(v.Imports, element)
   170  }
   171  
   172  func (v *firstPassVisitor) VisitNormalField(element *proto.NormalField) {
   173  	v.haveHitNonComment = true
   174  }
   175  
   176  func (v *firstPassVisitor) VisitEnumField(element *proto.EnumField) {
   177  	v.haveHitNonComment = true
   178  }
   179  
   180  func (v *firstPassVisitor) VisitEnum(element *proto.Enum) {
   181  	v.haveHitNonComment = true
   182  }
   183  
   184  func (v *firstPassVisitor) VisitComment(element *proto.Comment) {
   185  	// We only print file-level comments before syntax, package, file-level options,
   186  	// or package if they are at the top of the file
   187  	if !v.haveHitNonComment {
   188  		v.PComment(element)
   189  		v.P()
   190  	}
   191  }
   192  
   193  func (v *firstPassVisitor) VisitOneof(element *proto.Oneof) {
   194  	v.haveHitNonComment = true
   195  }
   196  
   197  func (v *firstPassVisitor) VisitOneofField(element *proto.OneOfField) {
   198  	v.haveHitNonComment = true
   199  }
   200  
   201  func (v *firstPassVisitor) VisitReserved(element *proto.Reserved) {
   202  	v.haveHitNonComment = true
   203  }
   204  
   205  func (v *firstPassVisitor) VisitRPC(element *proto.RPC) {
   206  	v.haveHitNonComment = true
   207  }
   208  
   209  func (v *firstPassVisitor) VisitMapField(element *proto.MapField) {
   210  	v.haveHitNonComment = true
   211  }
   212  
   213  func (v *firstPassVisitor) VisitGroup(element *proto.Group) {
   214  	v.haveHitNonComment = true
   215  }
   216  
   217  func (v *firstPassVisitor) VisitExtensions(element *proto.Extensions) {
   218  	v.haveHitNonComment = true
   219  }
   220  
   221  func (v *firstPassVisitor) PImports(imports []*proto.Import) {
   222  	if len(imports) == 0 {
   223  		return
   224  	}
   225  	sort.Slice(imports, func(i int, j int) bool { return imports[i].Filename < imports[j].Filename })
   226  	for _, i := range imports {
   227  		v.PComment(i.Comment)
   228  		// kind can be "weak", "public", or empty
   229  		// if weak or public, just print it out but with a space afterwards
   230  		// otherwise do not print anything
   231  		// https://developers.google.com/protocol-buffers/docs/reference/proto3-spec#import_statement
   232  		kind := i.Kind
   233  		if kind != "" {
   234  			kind = kind + " "
   235  		}
   236  		v.PWithInlineComment(i.InlineComment, `import `, kind, `"`, i.Filename, `";`)
   237  	}
   238  }