Skip to content

Commit e1bb975

Browse files
committed
Improve fixer performance
By `Prepare`ing the linter before repeated use. ``` 150116720 ns/op 101567417 B/op 2359322 allocs/op // Before 132816578 ns/op 89093239 B/op 2068892 allocs/op // After ``` Signed-off-by: Anders Eknert <anders.eknert@apple.com>
1 parent 415b05f commit e1bb975

4 files changed

Lines changed: 64 additions & 35 deletions

File tree

pkg/fixer/fixer.go

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const (
2626

2727
// Fixer must be instantiated via NewFixer.
2828
type Fixer struct {
29-
registeredFixes map[string]any
29+
registeredFixes map[string]fixes.Fix
3030
onConflictOperation OnConflictOperation
3131
registeredRoots []string
3232
versionsMap map[string]ast.RegoVersion
@@ -35,7 +35,7 @@ type Fixer struct {
3535
// NewFixer instantiates a Fixer.
3636
func NewFixer() *Fixer {
3737
return &Fixer{
38-
registeredFixes: make(map[string]any),
38+
registeredFixes: make(map[string]fixes.Fix),
3939
registeredRoots: make([]string, 0),
4040
onConflictOperation: OnConflictError,
4141
}
@@ -78,17 +78,11 @@ func (f *Fixer) RegisterRoots(roots ...string) *Fixer {
7878
}
7979

8080
func (f *Fixer) GetFixForName(name string) (fixes.Fix, bool) {
81-
fix, ok := f.registeredFixes[name]
82-
if !ok {
83-
return nil, false
81+
if fix, ok := f.registeredFixes[name]; ok {
82+
return fix, true
8483
}
8584

86-
fixInstance, ok := fix.(fixes.Fix)
87-
if !ok {
88-
return nil, false
89-
}
90-
91-
return fixInstance, true
85+
return nil, false
9286
}
9387

9488
func (f *Fixer) Fix(ctx context.Context, l *linter.Linter, fp fileprovider.FileProvider) (*Report, error) {
@@ -119,16 +113,13 @@ func (f *Fixer) FixViolations(
119113
return nil, fmt.Errorf("failed to list files: %w", err)
120114
}
121115

122-
// rangeValCopy may be expensive, but this is not critical enough
123-
// to motivate cluttering the code
124-
//nolint:gocritic
125-
for _, violation := range violations {
126-
fixInstance, ok := f.GetFixForName(violation.Title)
116+
for i := range violations {
117+
fixInstance, ok := f.GetFixForName(violations[i].Title)
127118
if !ok {
128-
return nil, fmt.Errorf("no fix for violation %s", violation.Title)
119+
return nil, fmt.Errorf("no fix for violation %s", violations[i].Title)
129120
}
130121

131-
file := violation.Location.File
122+
file := violations[i].Location.File
132123

133124
fc, err := fp.Get(file)
134125
if err != nil {
@@ -143,7 +134,7 @@ func (f *Fixer) FixViolations(
143134
fixResults, err := fixInstance.Fix(&fixes.FixCandidate{Filename: file, Contents: fc}, &fixes.RuntimeOptions{
144135
BaseDir: util.FindClosestMatchingRoot(abs, f.registeredRoots),
145136
Config: config,
146-
Locations: []report.Location{violation.Location},
137+
Locations: []report.Location{violations[i].Location},
147138
})
148139
if err != nil {
149140
return nil, fmt.Errorf("failed to fix %s: %w", file, err)
@@ -200,6 +191,11 @@ func (f *Fixer) applyLinterFixes(
200191
versionsMap = f.versionsMap
201192
}
202193

194+
li, err := l.WithDisableAll(true).WithEnabledRules(fixableEnabledRules...).Prepare(ctx)
195+
if err != nil {
196+
return fmt.Errorf("failed to prepare linter for fixing: %w", err)
197+
}
198+
203199
for {
204200
fixMadeInIteration := false
205201

@@ -208,7 +204,7 @@ func (f *Fixer) applyLinterFixes(
208204
return fmt.Errorf("failed to create linter input: %w", err)
209205
}
210206

211-
rep, err := l.WithDisableAll(true).WithEnabledRules(fixableEnabledRules...).WithInputModules(&in).Lint(ctx)
207+
rep, err := li.WithInputModules(&in).Lint(ctx)
212208
if err != nil {
213209
return fmt.Errorf("failed to lint before fixing: %w", err)
214210
}
@@ -217,19 +213,18 @@ func (f *Fixer) applyLinterFixes(
217213
break
218214
}
219215

220-
//nolint:gocritic
221-
for _, violation := range rep.Violations {
222-
fixInstance, ok := f.GetFixForName(violation.Title)
216+
for i := range rep.Violations {
217+
fixInstance, ok := f.GetFixForName(rep.Violations[i].Title)
223218
if !ok {
224-
return fmt.Errorf("no fix for violation %s", violation.Title)
219+
return fmt.Errorf("no fix for violation %s", rep.Violations[i].Title)
225220
}
226221

227222
config, err := l.GetConfig()
228223
if err != nil {
229224
return fmt.Errorf("failed to get config: %w", err)
230225
}
231226

232-
file := violation.Location.File
227+
file := rep.Violations[i].Location.File
233228

234229
abs, err := filepath.Abs(file)
235230
if err != nil {
@@ -246,7 +241,7 @@ func (f *Fixer) applyLinterFixes(
246241
fixResults, err := fixInstance.Fix(&fixCandidate, &fixes.RuntimeOptions{
247242
BaseDir: util.FindClosestMatchingRoot(abs, f.registeredRoots),
248243
Config: config,
249-
Locations: []report.Location{violation.Location},
244+
Locations: []report.Location{rep.Violations[i].Location},
250245
})
251246
if err != nil {
252247
return fmt.Errorf("failed to fix %s: %w", file, err)
@@ -259,7 +254,7 @@ func (f *Fixer) applyLinterFixes(
259254
fixResult := fixResults[0]
260255

261256
if fixResult.Rename != nil {
262-
if err = f.handleRename(fp, fixReport, startingFiles, fixResult); err != nil {
257+
if err := f.handleRename(fp, fixReport, startingFiles, fixResult); err != nil {
263258
return err
264259
}
265260

pkg/fixer/fixer_test.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@ deny = true
3434

3535
memfp := fileprovider.NewInMemoryFileProvider(policies)
3636

37-
input, err := memfp.ToInput(map[string]ast.RegoVersion{
38-
mainDir: ast.RegoV1,
39-
})
37+
input, err := memfp.ToInput(map[string]ast.RegoVersion{mainDir: ast.RegoV1})
4038
if err != nil {
4139
t.Fatalf("failed to create input: %v", err)
4240
}
@@ -196,3 +194,39 @@ func TestFixViolations(t *testing.T) {
196194
}
197195
}
198196
}
197+
198+
// 150116720 ns/op 101567417 B/op 2359322 allocs/op
199+
// 132816578 ns/op 89093239 B/op 2068892 allocs/op // Linter.Prepare()
200+
func BenchmarkFixViolations(b *testing.B) {
201+
rootPath := testutil.Must(filepath.Abs(filepath.FromSlash("/root")))(b)
202+
mainDir := filepath.Join(rootPath, "main")
203+
mainRegoFile := filepath.Join(mainDir, "main.rego")
204+
205+
policies := map[string]string{
206+
mainRegoFile: `package test
207+
208+
allow if {
209+
true #no space
210+
}
211+
deny = true
212+
`,
213+
}
214+
215+
memfp := fileprovider.NewInMemoryFileProvider(policies)
216+
217+
input, err := memfp.ToInput(map[string]ast.RegoVersion{mainDir: ast.RegoV1})
218+
if err != nil {
219+
b.Fatalf("failed to create input: %v", err)
220+
}
221+
222+
l := linter.NewLinter().WithEnableAll(true).WithInputModules(&input)
223+
f := NewFixer().RegisterFixes(fixes.NewDefaultFixes()...).RegisterRoots(rootPath).
224+
SetRegoVersionsMap(map[string]ast.RegoVersion{mainDir: ast.RegoV1})
225+
226+
for b.Loop() {
227+
_, err := f.Fix(b.Context(), &l, memfp)
228+
if err != nil {
229+
b.Fatalf("failed to fix: %v", err)
230+
}
231+
}
232+
}

pkg/fixer/fixes/nowhitespacecomment.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func (n *NoWhitespaceComment) Fix(fc *FixCandidate, opts *RuntimeOptions) ([]Fix
2020
fixed := false
2121

2222
for _, loc := range opts.Locations {
23-
if loc.Row > len(lines) || loc.Column > len(lines[loc.Row-1]) || loc.Column < 1 {
23+
if loc.Row < 1 || loc.Column < 1 || loc.Row > len(lines) || loc.Column > len(lines[loc.Row-1]) {
2424
continue
2525
}
2626

pkg/linter/linter.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,10 @@ func (l Linter) Lint(ctx context.Context) (report.Report, error) {
388388
l.stopTimer(regalmetrics.RegalFilterIgnoredModules)
389389
}
390390

391+
if len(l.inputPaths) == 0 && l.inputModules == nil && len(l.overriddenAggregates) == 0 {
392+
return report.Report{}, errors.New("nothing provided to lint")
393+
}
394+
391395
regoReport, err := l.lint(ctx, input)
392396
if err != nil {
393397
return report.Report{}, fmt.Errorf("failed to lint using Rego rules: %w", err)
@@ -531,10 +535,6 @@ func (l Linter) notPrepared() Linter {
531535
}
532536

533537
func (l Linter) validate(conf *config.Config) error {
534-
if len(l.inputPaths) == 0 && l.inputModules == nil && len(l.overriddenAggregates) == 0 {
535-
return errors.New("nothing provided to lint")
536-
}
537-
538538
if l.customRuleError != nil {
539539
return fmt.Errorf("failed to load custom rules: %w", l.customRuleError)
540540
}

0 commit comments

Comments
 (0)