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

     1  package rules
     2  
     3  import (
     4  	"sort"
     5  
     6  	"github.com/yoheimuta/go-protoparser/v4/parser"
     7  
     8  	"github.com/yoheimuta/protolint/linter/report"
     9  	"github.com/yoheimuta/protolint/linter/rule"
    10  	"github.com/yoheimuta/protolint/linter/visitor"
    11  )
    12  
    13  // ImportsSortedRule enforces sorted imports.
    14  type ImportsSortedRule struct {
    15  	RuleWithSeverity
    16  	fixMode bool
    17  }
    18  
    19  // NewImportsSortedRule creates a new ImportsSortedRule.
    20  func NewImportsSortedRule(
    21  	severity rule.Severity,
    22  	fixMode bool,
    23  ) ImportsSortedRule {
    24  	return ImportsSortedRule{
    25  		RuleWithSeverity: RuleWithSeverity{severity: severity},
    26  		fixMode:          fixMode,
    27  	}
    28  }
    29  
    30  // ID returns the ID of this rule.
    31  func (r ImportsSortedRule) ID() string {
    32  	return "IMPORTS_SORTED"
    33  }
    34  
    35  // Purpose returns the purpose of this rule.
    36  func (r ImportsSortedRule) Purpose() string {
    37  	return "Enforces sorted imports."
    38  }
    39  
    40  // IsOfficial decides whether or not this rule belongs to the official guide.
    41  func (r ImportsSortedRule) IsOfficial() bool {
    42  	return true
    43  }
    44  
    45  // Apply applies the rule to the proto.
    46  func (r ImportsSortedRule) Apply(
    47  	proto *parser.Proto,
    48  ) ([]report.Failure, error) {
    49  	base, err := visitor.NewBaseFixableVisitor(r.ID(), true, proto, string(r.Severity()))
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	v := &importsSortedVisitor{
    55  		BaseFixableVisitor: base,
    56  		fixMode:            r.fixMode,
    57  		sorter:             new(importSorter),
    58  	}
    59  	return visitor.RunVisitor(v, proto, r.ID())
    60  }
    61  
    62  type importsSortedVisitor struct {
    63  	*visitor.BaseFixableVisitor
    64  	fixMode bool
    65  	sorter  *importSorter
    66  }
    67  
    68  func (v importsSortedVisitor) VisitImport(i *parser.Import) (next bool) {
    69  	v.sorter.add(i)
    70  	return false
    71  }
    72  
    73  func (v importsSortedVisitor) Finally() error {
    74  	notSorted := v.sorter.notSortedImports()
    75  
    76  	v.Fixer.ReplaceAll(func(lines []string) []string {
    77  		var fixedLines []string
    78  
    79  		for i, line := range lines {
    80  			if invalid, ok := notSorted[i+1]; ok {
    81  				v.AddFailuref(
    82  					invalid.Meta.Pos,
    83  					`Imports are not sorted.`,
    84  				)
    85  				line = lines[invalid.sortedLine-1]
    86  			}
    87  			fixedLines = append(fixedLines, line)
    88  		}
    89  		return fixedLines
    90  	})
    91  	if !v.fixMode {
    92  		return nil
    93  	}
    94  	return v.BaseFixableVisitor.Finally()
    95  }
    96  
    97  type notSortedImport struct {
    98  	*parser.Import
    99  	sortedLine int
   100  }
   101  
   102  type importGroup []*parser.Import
   103  
   104  func (g importGroup) isContiguous(i *parser.Import) bool {
   105  	last := g[len(g)-1]
   106  	return i.Meta.Pos.Line-last.Meta.Pos.Line == 1
   107  }
   108  
   109  func (g importGroup) sorted() importGroup {
   110  	var s importGroup
   111  	s = append(s, g...)
   112  	sort.Slice(s, func(i, j int) bool { return s[i].Location < s[j].Location })
   113  	return s
   114  }
   115  
   116  func (g importGroup) notSortedImports() map[int]*notSortedImport {
   117  	is := make(map[int]*notSortedImport)
   118  	s := g.sorted()
   119  
   120  	for idx, i := range g {
   121  		sorted := s[idx]
   122  		if i.Location != sorted.Location {
   123  			is[i.Meta.Pos.Line] = &notSortedImport{
   124  				Import:     i,
   125  				sortedLine: sorted.Meta.Pos.Line,
   126  			}
   127  		}
   128  	}
   129  	return is
   130  }
   131  
   132  type importSorter struct {
   133  	groups []*importGroup
   134  }
   135  
   136  func (s *importSorter) add(i *parser.Import) {
   137  	for _, g := range s.groups {
   138  		if g.isContiguous(i) {
   139  			*g = append(*g, i)
   140  			return
   141  		}
   142  	}
   143  	s.groups = append(s.groups, &importGroup{i})
   144  }
   145  
   146  func (s *importSorter) notSortedImports() map[int]*notSortedImport {
   147  	is := make(map[int]*notSortedImport)
   148  	for _, g := range s.groups {
   149  		ps := g.notSortedImports()
   150  		for line, p := range ps {
   151  			is[line] = p
   152  		}
   153  	}
   154  	return is
   155  }