@@ -77,6 +77,19 @@ func (f Fixer) fixIssuesInFile(filePath string, issues []result.Issue) error {
7777 return errors .Wrapf (err , "failed to make file %s" , tmpFileName )
7878 }
7979
80+ // merge multiple issues per line into one issue
81+ issuesPerLine := map [int ][]result.Issue {}
82+ for _ , i := range issues {
83+ issuesPerLine [i .Line ()] = append (issuesPerLine [i .Line ()], i )
84+ }
85+
86+ issues = issues [:0 ] // reuse the same memory
87+ for line , lineIssues := range issuesPerLine {
88+ if mergedIssue := f .mergeLineIssues (line , lineIssues , origFileLines ); mergedIssue != nil {
89+ issues = append (issues , * mergedIssue )
90+ }
91+ }
92+
8093 issues = f .findNotIntersectingIssues (issues )
8194
8295 if err = f .writeFixedFile (origFileLines , issues , tmpOutFile ); err != nil {
@@ -94,9 +107,76 @@ func (f Fixer) fixIssuesInFile(filePath string, issues []result.Issue) error {
94107 return nil
95108}
96109
110+ //nolint:gocyclo
111+ func (f Fixer ) mergeLineIssues (lineNum int , lineIssues []result.Issue , origFileLines [][]byte ) * result.Issue {
112+ origLine := origFileLines [lineNum - 1 ] // lineNum is 1-based
113+
114+ if len (lineIssues ) == 1 && lineIssues [0 ].Replacement .Inline == nil {
115+ return & lineIssues [0 ]
116+ }
117+
118+ // check issues first
119+ for _ , i := range lineIssues {
120+ if i .LineRange != nil {
121+ f .log .Infof ("Line %d has multiple issues but at least one of them is ranged: %#v" , lineNum , lineIssues )
122+ return & lineIssues [0 ]
123+ }
124+
125+ r := i .Replacement
126+ if r .Inline == nil || len (r .NewLines ) != 0 || r .NeedOnlyDelete {
127+ f .log .Infof ("Line %d has multiple issues but at least one of them isn't inline: %#v" , lineNum , lineIssues )
128+ return & lineIssues [0 ]
129+ }
130+
131+ if r .Inline .StartCol < 0 || r .Inline .Length <= 0 || r .Inline .StartCol + r .Inline .Length > len (origLine ) {
132+ f .log .Warnf ("Line %d (%q) has invalid inline fix: %#v, %#v" , lineNum , origLine , i , r .Inline )
133+ return nil
134+ }
135+ }
136+
137+ return f .applyInlineFixes (lineIssues , origLine , lineNum )
138+ }
139+
140+ func (f Fixer ) applyInlineFixes (lineIssues []result.Issue , origLine []byte , lineNum int ) * result.Issue {
141+ sort .Slice (lineIssues , func (i , j int ) bool {
142+ return lineIssues [i ].Replacement .Inline .StartCol < lineIssues [j ].Replacement .Inline .StartCol
143+ })
144+
145+ var newLineBuf bytes.Buffer
146+ newLineBuf .Grow (len (origLine ))
147+
148+ //nolint:misspell
149+ // example: origLine="it's becouse of them", StartCol=5, Length=7, NewString="because"
150+
151+ curOrigLinePos := 0
152+ for _ , i := range lineIssues {
153+ fix := i .Replacement .Inline
154+ if fix .StartCol < curOrigLinePos {
155+ f .log .Warnf ("Line %d has multiple intersecting issues: %#v" , lineNum , lineIssues )
156+ return nil
157+ }
158+
159+ if curOrigLinePos != fix .StartCol {
160+ newLineBuf .Write (origLine [curOrigLinePos :fix .StartCol ])
161+ }
162+ newLineBuf .WriteString (fix .NewString )
163+ curOrigLinePos = fix .StartCol + fix .Length
164+ }
165+ if curOrigLinePos != len (origLine ) {
166+ newLineBuf .Write (origLine [curOrigLinePos :])
167+ }
168+
169+ mergedIssue := lineIssues [0 ] // use text from the first issue (it's not really used)
170+ mergedIssue .Replacement = & result.Replacement {
171+ NewLines : []string {newLineBuf .String ()},
172+ }
173+ return & mergedIssue
174+ }
175+
97176func (f Fixer ) findNotIntersectingIssues (issues []result.Issue ) []result.Issue {
98177 sort .SliceStable (issues , func (i , j int ) bool {
99- return issues [i ].Line () < issues [j ].Line () //nolint:scopelint
178+ a , b := issues [i ], issues [j ] //nolint:scopelint
179+ return a .Line () < b .Line ()
100180 })
101181
102182 var ret []result.Issue
0 commit comments