Skip to content

Commit 171fdb2

Browse files
Merge pull request #36 from firelizzard18/fix
Implement fixer
2 parents 663afa4 + 3dd4d40 commit 171fdb2

File tree

3 files changed

+244
-2
lines changed

3 files changed

+244
-2
lines changed

analyzer.go

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ type Analyzer struct {
5252
template string
5353
}
5454

55-
func (a *Analyzer) Analyze(target *Target) Issue {
55+
func (a *Analyzer) Analyze(target *Target) (i Issue) {
5656
if a.template == "" {
5757
return NewIssue("Missed template for check")
5858
}
@@ -74,6 +74,16 @@ func (a *Analyzer) Analyze(target *Target) Issue {
7474
offset.Position += 3
7575
}
7676
}
77+
defer func() {
78+
if i == nil {
79+
return
80+
}
81+
fix, ok := a.generateFix(i, file, header)
82+
if !ok {
83+
return
84+
}
85+
i = NewIssueWithFix(i.Message(), i.Location(), fix)
86+
}()
7787
header = strings.TrimSpace(header)
7888
if header == "" {
7989
return NewIssue("Missed header for check")
@@ -144,3 +154,93 @@ func New(options ...Option) *Analyzer {
144154
}
145155
return a
146156
}
157+
158+
func (a *Analyzer) generateFix(i Issue, file *ast.File, header string) (Fix, bool) {
159+
var expect string
160+
t := NewReader(a.template)
161+
for !t.Done() {
162+
ch := t.Peek()
163+
if ch == '{' {
164+
f := a.values[a.readField(t)]
165+
if f == nil {
166+
return Fix{}, false
167+
}
168+
if f.Calculate(a.values) != nil {
169+
return Fix{}, false
170+
}
171+
expect += f.Get()
172+
continue
173+
}
174+
175+
expect += string(ch)
176+
t.Next()
177+
}
178+
179+
fix := Fix{Expected: strings.Split(expect, "\n")}
180+
if !(len(file.Comments) > 0 && file.Comments[0].Pos() < file.Package) {
181+
for i := range fix.Expected {
182+
fix.Expected[i] = "// " + fix.Expected[i]
183+
}
184+
return fix, true
185+
}
186+
187+
actual := file.Comments[0].List[0].Text
188+
if !strings.HasPrefix(actual, "/*") {
189+
for i := range fix.Expected {
190+
fix.Expected[i] = "// " + fix.Expected[i]
191+
}
192+
for _, c := range file.Comments[0].List {
193+
fix.Actual = append(fix.Actual, c.Text)
194+
}
195+
i = NewIssueWithFix(i.Message(), i.Location(), fix)
196+
return fix, true
197+
}
198+
199+
gets := func(i int, end bool) string {
200+
if i < 0 {
201+
return header
202+
}
203+
if end {
204+
return header[i+1:]
205+
}
206+
return header[:i]
207+
}
208+
start := strings.Index(actual, gets(strings.IndexByte(header, '\n'), false))
209+
if start < 0 {
210+
return Fix{}, false // Should be impossible
211+
}
212+
nl := strings.LastIndexByte(actual[:start], '\n')
213+
if nl >= 0 {
214+
fix.Actual = strings.Split(actual[:nl], "\n")
215+
fix.Expected = append(fix.Actual, fix.Expected...)
216+
actual = actual[nl+1:]
217+
start -= nl + 1
218+
}
219+
220+
prefix := actual[:start]
221+
if nl < 0 {
222+
fix.Expected[0] = prefix + fix.Expected[0]
223+
} else {
224+
n := len(fix.Actual)
225+
for i := range fix.Expected[n:] {
226+
fix.Expected[n+i] = prefix + fix.Expected[n+i]
227+
}
228+
}
229+
230+
last := gets(strings.LastIndexByte(header, '\n'), true)
231+
end := strings.Index(actual, last)
232+
if end < 0 {
233+
return Fix{}, false // Should be impossible
234+
}
235+
236+
trailing := actual[end+len(last):]
237+
if i := strings.IndexRune(trailing, '\n'); i < 0 {
238+
fix.Expected[len(fix.Expected)-1] += trailing
239+
} else {
240+
fix.Expected[len(fix.Expected)-1] += trailing[:i]
241+
fix.Expected = append(fix.Expected, strings.Split(trailing[i+1:], "\n")...)
242+
}
243+
244+
fix.Actual = append(fix.Actual, strings.Split(actual, "\n")...)
245+
return fix, true
246+
}

analyzer_test.go

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
package goheader_test
1818

1919
import (
20+
"fmt"
2021
"go/ast"
2122
"go/parser"
2223
"go/token"
2324
"io/ioutil"
2425
"os"
2526
"path"
2627
"testing"
28+
"time"
2729

2830
goheader "github.com/denis-tingaikin/go-header"
2931
"github.com/stretchr/testify/require"
@@ -54,7 +56,7 @@ func TestAnalyzer_YearRangeValue_ShouldWorkWithComplexVariables(t *testing.T) {
5456
RawValue: "{{year-range }} B",
5557
}
5658
var a = goheader.New(goheader.WithTemplate("A {{ my-val }}"), goheader.WithValues(vals))
57-
require.Nil(t, a.Analyze(header(`A 2000-2022 B`)))
59+
require.Nil(t, a.Analyze(header(fmt.Sprintf("A 2000-%v B", time.Now().Year()))))
5860
}
5961

6062
func TestAnalyzer_Analyze1(t *testing.T) {
@@ -185,3 +187,124 @@ See the License for the specific language governing permissions and
185187
limitations under the License.`))
186188
require.Nil(t, issue)
187189
}
190+
191+
func TestFix(t *testing.T) {
192+
const pkg = `
193+
194+
// Package foo
195+
package foo
196+
197+
func Foo() { println("Foo") }
198+
`
199+
200+
analyze := func(header string) goheader.Issue {
201+
a := goheader.New(
202+
goheader.WithTemplate(`{{ MY COMPANY }}
203+
SPDX-License-Identifier: Foo`),
204+
goheader.WithValues(map[string]goheader.Value{
205+
"MY COMPANY": &goheader.ConstValue{
206+
RawValue: "mycompany.com",
207+
},
208+
}))
209+
210+
fset := token.NewFileSet()
211+
file, err := parser.ParseFile(fset, "foo.go", header+pkg, parser.ParseComments)
212+
require.NoError(t, err)
213+
214+
issue := a.Analyze(&goheader.Target{
215+
File: file,
216+
Path: t.TempDir(),
217+
})
218+
require.NotNil(t, issue)
219+
require.NotNil(t, issue.Fix())
220+
return issue
221+
}
222+
223+
t.Run("Line comment", func(t *testing.T) {
224+
issue := analyze(`// mycompany.net
225+
// SPDX-License-Identifier: Foo`)
226+
227+
require.Equal(t, []string{
228+
"// mycompany.net",
229+
"// SPDX-License-Identifier: Foo",
230+
}, issue.Fix().Actual)
231+
require.Equal(t, []string{
232+
"// mycompany.com",
233+
"// SPDX-License-Identifier: Foo",
234+
}, issue.Fix().Expected)
235+
})
236+
237+
t.Run("Block comment 1", func(t *testing.T) {
238+
issue := analyze(`/* mycompany.net
239+
SPDX-License-Identifier: Foo */`)
240+
241+
require.Equal(t, []string{
242+
"/* mycompany.net",
243+
"SPDX-License-Identifier: Foo */",
244+
}, issue.Fix().Actual)
245+
require.Equal(t, []string{
246+
"/* mycompany.com",
247+
"SPDX-License-Identifier: Foo */",
248+
}, issue.Fix().Expected)
249+
})
250+
251+
t.Run("Block comment 2", func(t *testing.T) {
252+
issue := analyze(`/*
253+
mycompany.net
254+
SPDX-License-Identifier: Foo */`)
255+
256+
require.Equal(t, []string{
257+
"/*",
258+
"mycompany.net",
259+
"SPDX-License-Identifier: Foo */",
260+
}, issue.Fix().Actual)
261+
require.Equal(t, []string{
262+
"/*",
263+
"mycompany.com",
264+
"SPDX-License-Identifier: Foo */",
265+
}, issue.Fix().Expected)
266+
})
267+
268+
t.Run("Block comment 3", func(t *testing.T) {
269+
issue := analyze(`/* mycompany.net
270+
SPDX-License-Identifier: Foo
271+
*/`)
272+
273+
require.Equal(t, []string{
274+
"/* mycompany.net",
275+
"SPDX-License-Identifier: Foo",
276+
"*/",
277+
}, issue.Fix().Actual)
278+
require.Equal(t, []string{
279+
"/* mycompany.com",
280+
"SPDX-License-Identifier: Foo",
281+
"*/",
282+
}, issue.Fix().Expected)
283+
})
284+
285+
t.Run("Block comment 4", func(t *testing.T) {
286+
issue := analyze(`/*
287+
288+
mycompany.net
289+
SPDX-License-Identifier: Foo
290+
291+
*/`)
292+
293+
require.Equal(t, []string{
294+
"/*",
295+
"",
296+
"mycompany.net",
297+
"SPDX-License-Identifier: Foo",
298+
"",
299+
"*/",
300+
}, issue.Fix().Actual)
301+
require.Equal(t, []string{
302+
"/*",
303+
"",
304+
"mycompany.com",
305+
"SPDX-License-Identifier: Foo",
306+
"",
307+
"*/",
308+
}, issue.Fix().Expected)
309+
})
310+
}

issue.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@ package goheader
1919
type Issue interface {
2020
Location() Location
2121
Message() string
22+
Fix() *Fix
2223
}
2324

2425
type issue struct {
2526
msg string
2627
location Location
28+
fix *Fix
29+
}
30+
31+
type Fix struct {
32+
Actual []string
33+
Expected []string
2734
}
2835

2936
func (i *issue) Location() Location {
@@ -34,13 +41,25 @@ func (i *issue) Message() string {
3441
return i.msg
3542
}
3643

44+
func (i *issue) Fix() *Fix {
45+
return i.fix
46+
}
47+
3748
func NewIssueWithLocation(msg string, location Location) Issue {
3849
return &issue{
3950
msg: msg,
4051
location: location,
4152
}
4253
}
4354

55+
func NewIssueWithFix(msg string, location Location, fix Fix) Issue {
56+
return &issue{
57+
msg: msg,
58+
location: location,
59+
fix: &fix,
60+
}
61+
}
62+
4463
func NewIssue(msg string) Issue {
4564
return &issue{
4665
msg: msg,

0 commit comments

Comments
 (0)