Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ bar = [

If the file is `-`, the tool will read from stdin and write to stdout.

You can select a mode with the `--mode` flag:
- `fix` (default) sorts files in place.
- `lint` prints JSON findings and exits non-zero if anything is out of order.
- `diff` prints a unified diff instead of rewriting files and exits non-zero if anything would change.

#### pre-commit

You can run keep-sorted automatically by adding this repository to your
Expand Down
61 changes: 50 additions & 11 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"strconv"
"strings"

"github.com/aymanbagabas/go-udiff"
Comment thread
JeffFaer marked this conversation as resolved.
"github.com/google/keep-sorted/keepsorted"
"github.com/rs/zerolog/log"
flag "github.com/spf13/pflag"
Expand Down Expand Up @@ -84,6 +85,7 @@ var (
operations = map[string]operation{
"lint": lint,
"fix": fix,
"diff": diff,
}
)

Expand Down Expand Up @@ -228,18 +230,9 @@ func fix(fixer *keepsorted.Fixer, filenames []string, modifiedLines []keepsorted
}
}

for _, warn := range warnings {
if len(warnings) > 0 {
ok = false
log := log.Warn()
if warn.Path != stdin {
log = log.Str("file", warn.Path)
}
if warn.Lines.Start == warn.Lines.End {
log = log.Int("line", warn.Lines.Start)
} else {
log = log.Ints("[start,end]", []int{warn.Lines.Start, warn.Lines.End})
}
log.Msg(warn.Message)
logWarnings(warnings)
}
}

Expand Down Expand Up @@ -269,6 +262,52 @@ func lint(fixer *keepsorted.Fixer, filenames []string, modifiedLines []keepsorte
return false, nil
}

func diff(fixer *keepsorted.Fixer, filenames []string, modifiedLines []keepsorted.LineRange) (ok bool, err error) {
ok = true

for _, fn := range filenames {
contents, err := read(fn)
if err != nil {
return false, err
}
want, alreadyFixed, warnings := fixer.Fix(fn, contents, modifiedLines)
if !alreadyFixed {
ok = false
inName := fn
outName := fn
if fn == stdin {
inName = "stdin"
outName = "stdout"
}
if _, err := os.Stdout.WriteString(udiff.Unified(inName, outName, contents, want)); err != nil {
return false, err
}
}

if len(warnings) > 0 {
ok = false
logWarnings(warnings)
}
}

return ok, nil
}

func logWarnings(warnings []*keepsorted.Finding) {
for _, warn := range warnings {
log := log.Warn()
if warn.Path != stdin {
log = log.Str("file", warn.Path)
}
if warn.Lines.Start == warn.Lines.End {
log = log.Int("line", warn.Lines.Start)
} else {
log = log.Ints("[start,end]", []int{warn.Lines.Start, warn.Lines.End})
}
log.Msg(warn.Message)
}
}

func read(fn string) (string, error) {
if fn == stdin {
b, err := io.ReadAll(os.Stdin)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ go 1.23.1

require (
github.com/Workiva/go-datastructures v1.0.53
github.com/aymanbagabas/go-udiff v0.3.1
github.com/bluekeyes/go-gitdiff v0.8.1
github.com/google/go-cmp v0.5.8
github.com/mattn/go-isatty v0.0.20
github.com/rs/zerolog v1.31.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/bluekeyes/go-gitdiff v0.8.1 h1:lL1GofKMywO17c0lgQmJYcKek5+s8X6tXVNOLxy4smI=
github.com/bluekeyes/go-gitdiff v0.8.1/go.mod h1:WWAk1Mc6EgWarCrPFO+xeYlujPu98VuLW3Tu+B/85AE=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
Expand Down
49 changes: 44 additions & 5 deletions goldens/golden_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package golden_test

import (
"bytes"
"errors"
"fmt"
"io"
Expand All @@ -27,6 +28,7 @@ import (
"strings"
"testing"

"github.com/bluekeyes/go-gitdiff/gitdiff"
"github.com/google/go-cmp/cmp"
)

Expand Down Expand Up @@ -68,7 +70,7 @@ func TestGoldens(t *testing.T) {
t.Run(tc, func(t *testing.T) {
t.Parallel()
inFile := filepath.Join(dir, tc+".in")
in, err := os.Open(inFile)
in, err := os.ReadFile(inFile)
if err != nil {
t.Fatalf("Could not open .in file: %v", err)
}
Expand All @@ -91,7 +93,7 @@ func TestGoldens(t *testing.T) {
wantErr = []byte(strings.ReplaceAll(string(wantErr), "\r\n", "\n"))
wantErr = []byte(strings.ReplaceAll(string(wantErr), "\r", "\n"))

gotOut, gotErr, exitCode, err := runKeepSorted(in)
gotOut, gotErr, exitCode, err := runKeepSorted(bytes.NewReader(in), "fix")
if err != nil {
t.Errorf("Had trouble running keep-sorted: %v", err)
}
Expand All @@ -104,7 +106,9 @@ func TestGoldens(t *testing.T) {
needsRegen <- inFile
}

gotOut2, _, exitCode2, err := runKeepSorted(strings.NewReader(gotOut))
testDiffMode(t, in, wantOut)

gotOut2, _, exitCode2, err := runKeepSorted(strings.NewReader(gotOut), "fix")
if err != nil {
t.Errorf("Had trouble running keep-sorted on keep-sorted output: %v", err)
}
Expand All @@ -129,13 +133,48 @@ func TestGoldens(t *testing.T) {
}
}

func testDiffMode(t *testing.T, in []byte, wantOut []byte) {
t.Run("diff", func(t *testing.T) {
t.Parallel()
gotDiff, _, _, err := runKeepSorted(bytes.NewReader(in), "diff")
if err != nil {
t.Fatalf("Had trouble running keep-sorted --mode diff: %v", err)
}
files, _, err := gitdiff.Parse(strings.NewReader(gotDiff))
if err != nil {
t.Fatalf("Had trouble parsing diff: %v", err)
}
if len(files) != 1 {
t.Fatalf("Exactly one file is expected in diff, got %d", len(files))
}
var b strings.Builder
err = gitdiff.Apply(&b, bytes.NewReader(in), files[0])
if err != nil {
t.Fatalf("Had trouble applying diff: %v", err)
}
Comment on lines +143 to +154
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this just be ubdiff.ApplyUnified(gotDiff, in)?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are talking about aymanbagabas/go-udiff@v0.4.1, it fails to apply unified with context lines, see https://github.com/aymanbagabas/go-udiff/blob/v0.4.1/unified.go#L301

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. I'm not a huge fan of adding a second dependency just to apply diffs, even if it is test-only...

I can think of a couple different options here:

  • Edit the upstream to handle context lines correctly. It seems like it would be relatively easy to add support for that, although I'm not sure if the gotools package really needs that functionality which might make it a harder sell. And this option will take longer than the next option...
  • We could change our interaction with udiff to make it configurable from this test
    • Instead of using udiff.Unified, we would use udiff.Lines to calculate []Edits and use udiff.ToUnifiedDiff which has a configurable contextLines parameter
    • We add a hidden flag that this test can use to override the number of context lines to 0. That lets udiff.ApplyUnified work. There's already some precedent for hidden, test-only flags
  • Find some other package that both calculates and applies diffs. Although I do like how the udiff package is just a mirror of a gotools package...

if diff := cmp.Diff(string(wantOut), b.String()); diff != "" {
t.Fatalf("Diff applied to the input didn't match expected out:\n%s", diff)
}
})
t.Run("diff after fix", func(t *testing.T) {
t.Parallel()
gotDiff, _, _, err := runKeepSorted(bytes.NewReader(wantOut), "diff")
if err != nil {
t.Fatalf("Had trouble running keep-sorted --mode diff: %v", err)
}
if gotDiff != "" {
t.Errorf("Non-empty diff produced:\n%s", gotDiff)
}
})
}

func showTopLevel(dir string) (string, error) {
b, err := exec.Command("git", "-C", dir, "rev-parse", "--show-toplevel").Output()
return strings.TrimSpace(string(b)), err
}

func runKeepSorted(stdin io.Reader) (stdout, stderr string, exitCode int, err error) {
cmd := exec.Command("go", "run", gitDir, "--id=keep-sorted-test", "--omit-timestamps", "-")
func runKeepSorted(stdin io.Reader, mode string) (stdout, stderr string, exitCode int, err error) {
cmd := exec.Command("go", "run", gitDir, "--id=keep-sorted-test", "--mode="+mode, "--omit-timestamps", "-")
cmd.Stdin = stdin
outPipe, err := cmd.StdoutPipe()
if err != nil {
Expand Down
Loading