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] = ¬SortedImport{ 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 }