github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/internal/addon/rules/orderRule.go (about)

     1  package rules
     2  
     3  import (
     4  	"bytes"
     5  
     6  	"github.com/yoheimuta/go-protoparser/v4/parser"
     7  	"github.com/yoheimuta/go-protoparser/v4/parser/meta"
     8  	"github.com/yoheimuta/protolint/linter/report"
     9  	"github.com/yoheimuta/protolint/linter/rule"
    10  	"github.com/yoheimuta/protolint/linter/visitor"
    11  )
    12  
    13  // OrderRule verifies that all files should be ordered in the following manner:
    14  // 1. Syntax
    15  // 2. Package
    16  // 3. Imports (sorted)
    17  // 4. File options
    18  // 5. Everything else
    19  // See https://developers.google.com/protocol-buffers/docs/style#file-structure.
    20  type OrderRule struct {
    21  	RuleWithSeverity
    22  	fixMode bool
    23  }
    24  
    25  // NewOrderRule creates a new OrderRule.
    26  func NewOrderRule(
    27  	severity rule.Severity,
    28  	fixMode bool,
    29  ) OrderRule {
    30  	return OrderRule{
    31  		RuleWithSeverity: RuleWithSeverity{severity: severity},
    32  		fixMode:          fixMode,
    33  	}
    34  }
    35  
    36  // ID returns the ID of this rule.
    37  func (r OrderRule) ID() string {
    38  	return "ORDER"
    39  }
    40  
    41  // Purpose returns the purpose of this rule.
    42  func (r OrderRule) Purpose() string {
    43  	return "Verifies that all files should be ordered in the specific manner."
    44  }
    45  
    46  // IsOfficial decides whether or not this rule belongs to the official guide.
    47  func (r OrderRule) IsOfficial() bool {
    48  	return true
    49  }
    50  
    51  // Apply applies the rule to the proto.
    52  func (r OrderRule) Apply(proto *parser.Proto) ([]report.Failure, error) {
    53  	base, err := visitor.NewBaseFixableVisitor(r.ID(), r.fixMode, proto, string(r.Severity()))
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	v := &orderVisitor{
    59  		BaseFixableVisitor: base,
    60  		state:              initialOrderState,
    61  		machine:            newOrderStateTransition(),
    62  	}
    63  	return visitor.RunVisitor(v, proto, r.ID())
    64  }
    65  
    66  type orderVisitor struct {
    67  	*visitor.BaseFixableVisitor
    68  	state   orderState
    69  	machine orderStateTransition
    70  
    71  	formatter formatter
    72  }
    73  
    74  func (v *orderVisitor) Finally() error {
    75  	if 0 < len(v.Failures()) {
    76  		shouldFixed := true
    77  		v.Fixer.ReplaceContent(func(content []byte) []byte {
    78  			newContent := v.formatter.format(content)
    79  			if bytes.Equal(content, newContent) {
    80  				shouldFixed = false
    81  			}
    82  			return newContent
    83  		})
    84  
    85  		// TODO: BaseFixableVisitor.Finally should run the base Finally first, and then the fixing later.
    86  		if shouldFixed {
    87  			return v.BaseFixableVisitor.Finally()
    88  		}
    89  	}
    90  	return nil
    91  }
    92  
    93  func (v *orderVisitor) VisitSyntax(s *parser.Syntax) bool {
    94  	next := v.machine.transit(v.state, syntaxVisitEvent)
    95  	if next == invalidOrderState {
    96  		v.AddFailuref(s.Meta.Pos, "Syntax should be located at the top. Check if the file is ordered in the correct manner.")
    97  	}
    98  	v.state = syntaxOrderState
    99  	v.formatter.syntax = s
   100  	return false
   101  }
   102  
   103  func (v *orderVisitor) VisitPackage(p *parser.Package) bool {
   104  	next := v.machine.transit(v.state, packageVisitEvent)
   105  	if next == invalidOrderState {
   106  		v.AddFailuref(p.Meta.Pos, "The order of Package is invalid. Check if the file is ordered in the correct manner.")
   107  	}
   108  	v.state = packageOrderState
   109  	v.formatter.pkg = p
   110  	return false
   111  }
   112  
   113  func (v *orderVisitor) VisitImport(i *parser.Import) bool {
   114  	next := v.machine.transit(v.state, importsVisitEvent)
   115  	if next == invalidOrderState {
   116  		v.AddFailuref(i.Meta.Pos, "The order of Import is invalid. Check if the file is ordered in the correct manner.")
   117  	}
   118  	v.state = importsOrderState
   119  	v.formatter.addImports(i)
   120  	return false
   121  }
   122  
   123  func (v *orderVisitor) VisitOption(o *parser.Option) bool {
   124  	next := v.machine.transit(v.state, fileOptionsVisitEvent)
   125  	if next == invalidOrderState {
   126  		v.AddFailuref(o.Meta.Pos, "The order of Option is invalid. Check if the file is ordered in the correct manner.")
   127  	}
   128  	v.state = fileOptionsOrderState
   129  	v.formatter.addOptions(o)
   130  	return false
   131  }
   132  
   133  func (v *orderVisitor) VisitMessage(m *parser.Message) bool {
   134  	v.state = everythingElseOrderState
   135  	v.formatter.addMisc(m)
   136  	return false
   137  }
   138  
   139  func (v *orderVisitor) VisitEnum(e *parser.Enum) bool {
   140  	v.state = everythingElseOrderState
   141  	v.formatter.addMisc(e)
   142  	return false
   143  }
   144  
   145  func (v *orderVisitor) VisitService(s *parser.Service) bool {
   146  	v.state = everythingElseOrderState
   147  	v.formatter.addMisc(s)
   148  	return false
   149  }
   150  
   151  func (v *orderVisitor) VisitExtend(e *parser.Extend) bool {
   152  	v.state = everythingElseOrderState
   153  	v.formatter.addMisc(e)
   154  	return false
   155  }
   156  
   157  func (v *orderVisitor) VisitComment(c *parser.Comment) {
   158  	v.formatter.addComment(c)
   159  }
   160  
   161  // State Checker
   162  type orderState int
   163  
   164  const (
   165  	invalidOrderState orderState = iota
   166  	initialOrderState
   167  	syntaxOrderState
   168  	packageOrderState
   169  	importsOrderState
   170  	fileOptionsOrderState
   171  	everythingElseOrderState
   172  )
   173  
   174  type orderEvent int
   175  
   176  const (
   177  	syntaxVisitEvent orderEvent = iota
   178  	packageVisitEvent
   179  	importsVisitEvent
   180  	fileOptionsVisitEvent
   181  )
   182  
   183  type orderInput struct {
   184  	state orderState
   185  	event orderEvent
   186  }
   187  
   188  type orderStateTransition struct {
   189  	f map[orderInput]orderState
   190  }
   191  
   192  func newOrderStateTransition() orderStateTransition {
   193  	return orderStateTransition{
   194  		f: map[orderInput]orderState{
   195  			{
   196  				state: initialOrderState,
   197  				event: syntaxVisitEvent,
   198  			}: syntaxOrderState,
   199  
   200  			{
   201  				state: initialOrderState,
   202  				event: packageVisitEvent,
   203  			}: packageOrderState,
   204  			{
   205  				state: syntaxOrderState,
   206  				event: packageVisitEvent,
   207  			}: packageOrderState,
   208  
   209  			{
   210  				state: initialOrderState,
   211  				event: importsVisitEvent,
   212  			}: importsOrderState,
   213  			{
   214  				state: syntaxOrderState,
   215  				event: importsVisitEvent,
   216  			}: importsOrderState,
   217  			{
   218  				state: packageOrderState,
   219  				event: importsVisitEvent,
   220  			}: importsOrderState,
   221  			{
   222  				state: importsOrderState,
   223  				event: importsVisitEvent,
   224  			}: importsOrderState,
   225  
   226  			{
   227  				state: initialOrderState,
   228  				event: fileOptionsVisitEvent,
   229  			}: fileOptionsOrderState,
   230  			{
   231  				state: syntaxOrderState,
   232  				event: fileOptionsVisitEvent,
   233  			}: fileOptionsOrderState,
   234  			{
   235  				state: packageOrderState,
   236  				event: fileOptionsVisitEvent,
   237  			}: fileOptionsOrderState,
   238  			{
   239  				state: importsOrderState,
   240  				event: fileOptionsVisitEvent,
   241  			}: fileOptionsOrderState,
   242  			{
   243  				state: fileOptionsOrderState,
   244  				event: fileOptionsVisitEvent,
   245  			}: fileOptionsOrderState,
   246  		},
   247  	}
   248  }
   249  
   250  func (t orderStateTransition) transit(
   251  	state orderState,
   252  	event orderEvent,
   253  ) orderState {
   254  	out, ok := t.f[orderInput{state: state, event: event}]
   255  	if !ok {
   256  		return invalidOrderState
   257  	}
   258  	return out
   259  }
   260  
   261  // Formatter
   262  type indexedVisitee struct {
   263  	index   int
   264  	visitee parser.Visitee
   265  }
   266  
   267  // NOTE: This check is not used at the moment.
   268  // If no one requests to put the same element in a row as much as possible,
   269  // we should delete this wrap struct, indexedVisitee.
   270  func (i indexedVisitee) isContiguous(a indexedVisitee) bool {
   271  	return i.index-a.index == 1
   272  }
   273  
   274  type formatter struct {
   275  	syntax   *parser.Syntax
   276  	pkg      *parser.Package
   277  	imports  []indexedVisitee
   278  	options  []indexedVisitee
   279  	misc     []indexedVisitee
   280  	comments []indexedVisitee
   281  }
   282  
   283  func (f formatter) index() int {
   284  	idx := 0
   285  	if f.syntax != nil {
   286  		idx = 1
   287  	}
   288  	if f.pkg != nil {
   289  		idx++
   290  	}
   291  	return idx + len(f.imports) + len(f.options) + len(f.misc)
   292  }
   293  
   294  func (f *formatter) addImports(t *parser.Import) {
   295  	f.imports = append(f.imports, indexedVisitee{f.index(), t})
   296  }
   297  
   298  func (f *formatter) addOptions(t *parser.Option) {
   299  	f.options = append(f.options, indexedVisitee{f.index(), t})
   300  }
   301  
   302  func (f *formatter) addMisc(t parser.Visitee) {
   303  	f.misc = append(f.misc, indexedVisitee{f.index(), t})
   304  }
   305  
   306  func (f *formatter) addComment(t parser.Visitee) {
   307  	f.comments = append(f.comments, indexedVisitee{f.index(), t})
   308  }
   309  
   310  type line struct {
   311  	startPos meta.Position
   312  	endPos   meta.Position
   313  }
   314  
   315  func newLine(meta meta.Meta, comments []*parser.Comment, inline *parser.Comment) line {
   316  	var l line
   317  	l.startPos = meta.Pos
   318  	if 0 < len(comments) {
   319  		l.startPos = comments[0].Meta.Pos
   320  	}
   321  	l.endPos = meta.LastPos
   322  	if inline != nil {
   323  		l.endPos = inline.Meta.LastPos
   324  	}
   325  	return l
   326  }
   327  
   328  func newVisiteeLine(elm parser.Visitee) line {
   329  	switch e := elm.(type) {
   330  	case *parser.Syntax:
   331  		return newLine(e.Meta, e.Comments, e.InlineComment)
   332  	case *parser.Package:
   333  		return newLine(e.Meta, e.Comments, e.InlineComment)
   334  	case *parser.Import:
   335  		return newLine(e.Meta, e.Comments, e.InlineComment)
   336  	case *parser.Option:
   337  		return newLine(e.Meta, e.Comments, e.InlineComment)
   338  	case *parser.Message:
   339  		return newLine(e.Meta, e.Comments, e.InlineComment)
   340  	case *parser.Extend:
   341  		return newLine(e.Meta, e.Comments, e.InlineComment)
   342  	case *parser.Enum:
   343  		return newLine(e.Meta, e.Comments, e.InlineComment)
   344  	case *parser.Service:
   345  		return newLine(e.Meta, e.Comments, e.InlineComment)
   346  	case *parser.Comment:
   347  		return newLine(e.Meta, []*parser.Comment{}, nil)
   348  	}
   349  	return line{}
   350  }
   351  
   352  func (l line) hasEmptyLine(prev line) bool {
   353  	return 1 < l.startPos.Line-prev.endPos.Line
   354  }
   355  
   356  type writer struct {
   357  	content    []byte
   358  	newContent []byte
   359  }
   360  
   361  func (w *writer) write(l line) {
   362  	w.newContent = append(w.newContent, w.content[l.startPos.Offset:l.endPos.Offset+1]...)
   363  }
   364  
   365  func (w *writer) writeN(l line) {
   366  	w.write(l)
   367  	w.newContent = append(w.newContent, "\n"...)
   368  }
   369  
   370  func (w *writer) writeNN(l line) {
   371  	w.write(l)
   372  	w.newContent = append(w.newContent, "\n\n"...)
   373  }
   374  
   375  func (w *writer) writeOnlyN() {
   376  	w.newContent = append(w.newContent, "\n"...)
   377  }
   378  
   379  func (w *writer) removeLastRedundantN() {
   380  	if bytes.Equal(w.newContent[len(w.newContent)-2:len(w.newContent)], []byte("\n\n")) {
   381  		w.newContent = w.newContent[0 : len(w.newContent)-1]
   382  	}
   383  }
   384  
   385  func (f formatter) format(content []byte) []byte {
   386  	w := writer{content: content}
   387  
   388  	sl := newVisiteeLine(f.syntax)
   389  	w.writeNN(sl)
   390  
   391  	if f.pkg != nil {
   392  		pl := newVisiteeLine(f.pkg)
   393  		w.writeNN(pl)
   394  	}
   395  
   396  	visitees := [][]indexedVisitee{f.imports, f.options, f.misc, f.comments}
   397  	for i, vs := range visitees {
   398  		var ls []line
   399  		for _, elm := range vs {
   400  			ls = append(ls, newVisiteeLine(elm.visitee))
   401  		}
   402  
   403  		for i, l := range ls {
   404  			// There are any empty lines between both ls
   405  			if 0 < i && l.hasEmptyLine(ls[i-1]) {
   406  				w.writeOnlyN()
   407  			}
   408  			w.writeN(l)
   409  		}
   410  
   411  		if 0 < len(ls) && i < len(visitees)-1 {
   412  			w.writeOnlyN()
   413  		}
   414  	}
   415  
   416  	w.removeLastRedundantN()
   417  
   418  	return w.newContent
   419  }