├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── internal ├── govendor │ ├── version.txt │ ├── go │ │ ├── doc │ │ │ └── comment │ │ │ │ ├── std.go │ │ │ │ ├── doc.go │ │ │ │ ├── html.go │ │ │ │ ├── markdown.go │ │ │ │ ├── print.go │ │ │ │ └── text.go │ │ ├── printer │ │ │ ├── comment.go │ │ │ └── gobuild.go │ │ └── format │ │ │ ├── format.go │ │ │ └── internal.go │ └── diff │ │ └── diff.go └── version │ └── version.go ├── .gitattributes ├── format ├── testdata │ └── fuzz │ │ └── FuzzFormat │ │ ├── 948d1d5be3c838b207d345a3ac57e97bb3b77788cb5039a65994967490c49baa │ │ └── 18c862f09f82fe57f536e7ab4b1bd63daecc2cba8189530bb0eb77b8cef6f798 ├── format_test.go ├── fuzz_test.go ├── rewrite.go └── simplify.go ├── testdata ├── script │ ├── missing-import.txtar │ ├── simplify.txtar │ ├── deprecated-flags.txtar │ ├── cgo.txtar │ ├── diff.txtar │ ├── workspaces.txtar │ ├── short-decl.txtar │ ├── gomod.txtar │ ├── assignment-newlines.txtar │ ├── generated.txtar │ ├── block-empty.txtar │ ├── decls-separated.txtar │ ├── octal-literals.txtar │ ├── clothe-returns.txtar │ ├── decl-group-single.txtar │ ├── decl-group-many.txtar │ ├── composite-literals-leading-lines.txtar │ ├── typeparams.txtar │ ├── block-single.txtar │ ├── newline-errcheck.txtar │ ├── composite-multiline.txtar │ ├── func-merge-parameters.txtar │ ├── comment-spaced.txtar │ ├── ignore.txtar │ ├── short-case.txtar │ ├── interface.txtar │ ├── diagnose.txtar │ ├── std-imports.txtar │ ├── linedirectives.txtar │ ├── long-lines.txtar │ └── func-newlines.txtar └── gofumpt-external │ ├── go.mod │ ├── main.go │ └── go.sum ├── doc.go ├── go.mod ├── main_test.go ├── LICENSE ├── LICENSE.google ├── go.sum ├── gen_govendor.go ├── ulimit_linux_test.go ├── internal.go ├── CHANGELOG.md ├── README.md └── gofmt.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mvdan 2 | -------------------------------------------------------------------------------- /internal/govendor/version.txt: -------------------------------------------------------------------------------- 1 | go1.25.0 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # To prevent CRLF breakages on Windows for fragile files, like testdata. 2 | * -text 3 | -------------------------------------------------------------------------------- /format/testdata/fuzz/FuzzFormat/948d1d5be3c838b207d345a3ac57e97bb3b77788cb5039a65994967490c49baa: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("package A\nvar A A\nvar A A") 3 | int8(18) 4 | bool(false) 5 | -------------------------------------------------------------------------------- /testdata/script/missing-import.txtar: -------------------------------------------------------------------------------- 1 | # A missing import shouldn't matter nor be fixed by gofumpt. 2 | exec gofumpt foo.go 3 | cmp stdout foo.go 4 | 5 | -- foo.go -- 6 | package p 7 | 8 | var _ bytes.Buffer 9 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | // gofumpt enforces a stricter format than gofmt, while being backwards compatible. 5 | package main 6 | -------------------------------------------------------------------------------- /format/testdata/fuzz/FuzzFormat/18c862f09f82fe57f536e7ab4b1bd63daecc2cba8189530bb0eb77b8cef6f798: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("package A\nfunc A000000000(A000000000000,\nA00000000)(){\"\"}") 3 | int8(62) 4 | bool(true) 5 | -------------------------------------------------------------------------------- /testdata/gofumpt-external/go.mod: -------------------------------------------------------------------------------- 1 | module test/gofumpt-external 2 | 3 | go 1.24.0 4 | 5 | require mvdan.cc/gofumpt v0.8.1-0.20250831111522-b5fd2eb6e821 6 | 7 | require ( 8 | github.com/google/go-cmp v0.7.0 // indirect 9 | golang.org/x/tools v0.36.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /testdata/gofumpt-external/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "mvdan.cc/gofumpt/format" 8 | ) 9 | 10 | func main() { 11 | orig, err := io.ReadAll(os.Stdin) 12 | if err != nil { 13 | panic(err) 14 | } 15 | formatted, err := format.Source(orig, format.Options{ 16 | LangVersion: "go1.16", 17 | }) 18 | if err != nil { 19 | panic(err) 20 | } 21 | os.Stdout.Write(formatted) 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mvdan.cc/gofumpt 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/go-quicktest/qt v1.101.0 7 | github.com/rogpeppe/go-internal v1.14.1 8 | golang.org/x/mod v0.29.0 9 | golang.org/x/sync v0.17.0 10 | golang.org/x/sys v0.37.0 11 | golang.org/x/tools v0.38.0 12 | ) 13 | 14 | require ( 15 | github.com/google/go-cmp v0.7.0 // indirect 16 | github.com/kr/pretty v0.3.1 // indirect 17 | github.com/kr/text v0.2.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /testdata/script/simplify.txtar: -------------------------------------------------------------------------------- 1 | # gofumpt changes -s to default to true. 2 | exec gofumpt foo.go 3 | cmp stdout foo.go.golden 4 | 5 | -- foo.go -- 6 | package p 7 | 8 | const () 9 | 10 | const ( 11 | // Comment 12 | ) 13 | 14 | type () 15 | 16 | type ( 17 | // Comment 18 | ) 19 | 20 | var () 21 | 22 | var ( 23 | // Comment 24 | ) 25 | 26 | var _ = [][]int{[]int{1}} 27 | -- foo.go.golden -- 28 | package p 29 | 30 | const ( 31 | // Comment 32 | ) 33 | 34 | type ( 35 | // Comment 36 | ) 37 | 38 | var ( 39 | // Comment 40 | ) 41 | 42 | var _ = [][]int{{1}} 43 | -------------------------------------------------------------------------------- /testdata/script/deprecated-flags.txtar: -------------------------------------------------------------------------------- 1 | cp foo.orig.go foo.go 2 | ! exec gofumpt -w -r foo foo.go 3 | stderr 'the rewrite flag is no longer available; use "gofmt -r" instead\n' 4 | cmp foo.orig.go foo.go 5 | 6 | exec gofumpt -w -s foo.go 7 | stderr 'warning: -s is deprecated as it is always enabled\n' 8 | cmp foo.go foo.go.golden 9 | 10 | exec gofumpt -d foo.go.golden 11 | ! stdout . 12 | 13 | -- foo.orig.go -- 14 | package p 15 | 16 | func f() { 17 | 18 | println("foo") 19 | 20 | } 21 | -- foo.go.golden -- 22 | package p 23 | 24 | func f() { 25 | println("foo") 26 | } 27 | -------------------------------------------------------------------------------- /testdata/script/cgo.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | import "C" 11 | import "os" 12 | 13 | import `C` 14 | import "os" 15 | 16 | import "C" 17 | import ( 18 | "io" 19 | "utf8" 20 | ) 21 | 22 | import `C` 23 | import ( 24 | "io" 25 | "utf8" 26 | ) 27 | 28 | -- foo.go.golden -- 29 | package p 30 | 31 | import "C" 32 | import "os" 33 | 34 | import "C" 35 | import "os" 36 | 37 | import "C" 38 | import ( 39 | "io" 40 | "utf8" 41 | ) 42 | 43 | import "C" 44 | import ( 45 | "io" 46 | "utf8" 47 | ) 48 | -------------------------------------------------------------------------------- /testdata/script/diff.txtar: -------------------------------------------------------------------------------- 1 | # gofumpt fails with -d if there is a diff. 2 | 3 | exec gofumpt -d good.go 4 | ! stdout . 5 | ! stderr . 6 | 7 | ! exec gofumpt -d bad.go 8 | cmp stdout bad.go.diff 9 | ! stderr . 10 | 11 | -- good.go -- 12 | package p 13 | 14 | func f() { 15 | println("well formatted") 16 | } 17 | -- bad.go -- 18 | package p 19 | 20 | func f() { 21 | println("not well formatted") 22 | } 23 | -- bad.go.diff -- 24 | diff bad.go.orig bad.go 25 | --- bad.go.orig 26 | +++ bad.go 27 | @@ -1,5 +1,5 @@ 28 | package p 29 | 30 | func f() { 31 | -println("not well formatted") 32 | + println("not well formatted") 33 | } 34 | -------------------------------------------------------------------------------- /testdata/script/workspaces.txtar: -------------------------------------------------------------------------------- 1 | # Whether we run gofumpt from inside or outside a module, 2 | # we should always use the information from its go.mod. 3 | # We also test that we don't get confused by the presence of go.work. 4 | 5 | exec gofumpt a/go112.go 6 | cmp stdout a/go113.go 7 | 8 | cd a 9 | exec gofumpt go112.go 10 | cmp stdout go113.go 11 | 12 | -- go.work -- 13 | go 1.18 14 | use ./a 15 | use ./b 16 | -- a/go.mod -- 17 | module a 18 | go 1.18 19 | -- a/a.go -- 20 | package a 21 | -- a/go112.go -- 22 | package main 23 | 24 | const x = 0777 25 | -- a/go113.go -- 26 | package main 27 | 28 | const x = 0o777 29 | -- b/go.mod -- 30 | module b 31 | go 1.18 32 | -- b/b.go -- 33 | package b 34 | -------------------------------------------------------------------------------- /format/format_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package format_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/go-quicktest/qt" 10 | 11 | "mvdan.cc/gofumpt/format" 12 | ) 13 | 14 | func TestSourceIncludesSimplify(t *testing.T) { 15 | t.Parallel() 16 | 17 | in := []byte(` 18 | package p 19 | 20 | var () 21 | 22 | func f() { 23 | for _ = range v { 24 | } 25 | } 26 | `[1:]) 27 | want := []byte(` 28 | package p 29 | 30 | func f() { 31 | for range v { 32 | } 33 | } 34 | `[1:]) 35 | got, err := format.Source(in, format.Options{}) 36 | qt.Assert(t, qt.IsNil(err)) 37 | qt.Assert(t, qt.Equals(string(got), string(want))) 38 | } 39 | -------------------------------------------------------------------------------- /testdata/script/short-decl.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | var global = x 11 | 12 | func f() { 13 | var local = x 14 | var local2, local3 = x, y 15 | 16 | var onlyType T 17 | 18 | var typeAndVar T = x 19 | 20 | var _ = unused 21 | 22 | var ( 23 | aligned = x 24 | vars = y 25 | here = y 26 | ) 27 | } 28 | -- foo.go.golden -- 29 | package p 30 | 31 | var global = x 32 | 33 | func f() { 34 | local := x 35 | local2, local3 := x, y 36 | 37 | var onlyType T 38 | 39 | var typeAndVar T = x 40 | 41 | _ = unused 42 | 43 | var ( 44 | aligned = x 45 | vars = y 46 | here = y 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /testdata/script/gomod.txtar: -------------------------------------------------------------------------------- 1 | # Test various edge cases with go.mod files. 2 | 3 | exec gofumpt toolchain-stable/a.go 4 | stdout '//gofumpt:diagnose.* -lang=go1.21' 5 | 6 | exec gofumpt toolchain-unstable/a.go 7 | stdout '//gofumpt:diagnose.* -lang=go1.21' 8 | 9 | exec gofumpt missing-go-directive/a.go 10 | stdout '//gofumpt:diagnose.* -lang=go1.16' 11 | 12 | -- toolchain-stable/go.mod -- 13 | module a 14 | 15 | go 1.21.2 16 | -- toolchain-stable/a.go -- 17 | package a 18 | 19 | //gofumpt:diagnose 20 | 21 | -- toolchain-unstable/go.mod -- 22 | module a 23 | 24 | go 1.21rc3 25 | -- toolchain-unstable/a.go -- 26 | package a 27 | 28 | //gofumpt:diagnose 29 | 30 | -- missing-go-directive/go.mod -- 31 | module a 32 | 33 | -- missing-go-directive/a.go -- 34 | package a 35 | 36 | //gofumpt:diagnose 37 | -------------------------------------------------------------------------------- /testdata/script/assignment-newlines.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | func f() { 11 | foo := 12 | 13 | 14 | "bar" 15 | 16 | foo := 17 | "bar" 18 | 19 | _, _ = 20 | 0, 21 | 1 22 | 23 | _, _ = 0, 24 | 1 25 | 26 | _ = 27 | ` 28 | foo 29 | ` 30 | 31 | _ = /* inline */ 32 | "foo" 33 | 34 | _ = // inline 35 | "foo" 36 | } 37 | 38 | -- foo.go.golden -- 39 | package p 40 | 41 | func f() { 42 | foo := "bar" 43 | 44 | foo := "bar" 45 | 46 | _, _ = 0, 47 | 1 48 | 49 | _, _ = 0, 50 | 1 51 | 52 | _ = ` 53 | foo 54 | ` 55 | 56 | _ = /* inline */ "foo" 57 | 58 | _ = // inline 59 | "foo" 60 | } 61 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [1.24.x, 1.25.x] 8 | os: [ubuntu-latest, macos-latest, windows-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - uses: actions/checkout@v5 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - run: go test ./... 16 | - run: go test -race ./... 17 | 18 | # Static checks from this point forward. Only run on one Go version and on 19 | # Linux, since it's the fastest platform, and the tools behave the same. 20 | - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.25.x' 21 | run: diff <(echo -n) <(gofmt -s -d .) 22 | - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.25.x' 23 | run: go vet ./... 24 | -------------------------------------------------------------------------------- /internal/govendor/go/doc/comment/std.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Code generated by 'go generate' DO NOT EDIT. 6 | //disabled go:generate ./mkstd.sh 7 | 8 | package comment 9 | 10 | var stdPkgs = []string{ 11 | "bufio", 12 | "bytes", 13 | "cmp", 14 | "context", 15 | "crypto", 16 | "embed", 17 | "encoding", 18 | "errors", 19 | "expvar", 20 | "flag", 21 | "fmt", 22 | "hash", 23 | "html", 24 | "image", 25 | "io", 26 | "iter", 27 | "log", 28 | "maps", 29 | "math", 30 | "mime", 31 | "net", 32 | "os", 33 | "path", 34 | "plugin", 35 | "reflect", 36 | "regexp", 37 | "runtime", 38 | "slices", 39 | "sort", 40 | "strconv", 41 | "strings", 42 | "structs", 43 | "sync", 44 | "syscall", 45 | "testing", 46 | "time", 47 | "unicode", 48 | "unique", 49 | "unsafe", 50 | "weak", 51 | } 52 | -------------------------------------------------------------------------------- /testdata/script/generated.txtar: -------------------------------------------------------------------------------- 1 | # Explicitly given generated files are formatted with our rules. 2 | exec gofumpt foo.go 3 | cmp stdout foo.go.golden 4 | 5 | # stdin is still considered an explicit file. 6 | stdin foo.go 7 | exec gofumpt 8 | cmp stdout foo.go.golden 9 | 10 | # Implicitly walked generated files get formatted without the added rules. 11 | exec gofumpt -l . 12 | stdout -count=1 '^badgofmt.go$' 13 | ! stdout '^foo.go$' 14 | ! stderr . 15 | 16 | -- badgofmt.go -- 17 | // Code generated by foo. DO NOT EDIT. 18 | 19 | package foo 20 | 21 | func f() { 22 | println("body") 23 | } 24 | -- foo.go -- 25 | // foo is a package about bar. 26 | 27 | // Code generated by foo. DO NOT EDIT. 28 | 29 | package foo 30 | 31 | func f() { 32 | 33 | println("body") 34 | 35 | } 36 | -- foo.go.golden -- 37 | // foo is a package about bar. 38 | 39 | // Code generated by foo. DO NOT EDIT. 40 | 41 | package foo 42 | 43 | func f() { 44 | println("body") 45 | } 46 | -------------------------------------------------------------------------------- /testdata/script/block-empty.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | func f() { 11 | if true { 12 | // lone comment 13 | } 14 | { 15 | 16 | } 17 | 18 | { 19 | 20 | // lone comment 21 | 22 | } 23 | 24 | type S struct { 25 | 26 | 27 | // lone comment 28 | 29 | 30 | } 31 | 32 | type I interface { 33 | 34 | 35 | // lone comment 36 | 37 | 38 | } 39 | 40 | 41 | } 42 | 43 | type SOut struct { 44 | 45 | // lone comment 46 | 47 | } 48 | 49 | type IOut interface { 50 | 51 | 52 | // lone comment 53 | 54 | 55 | } 56 | -- foo.go.golden -- 57 | package p 58 | 59 | func f() { 60 | if true { 61 | // lone comment 62 | } 63 | { 64 | } 65 | 66 | { 67 | // lone comment 68 | } 69 | 70 | type S struct { 71 | // lone comment 72 | } 73 | 74 | type I interface { 75 | // lone comment 76 | } 77 | } 78 | 79 | type SOut struct { 80 | // lone comment 81 | } 82 | 83 | type IOut interface { 84 | // lone comment 85 | } 86 | -------------------------------------------------------------------------------- /testdata/script/decls-separated.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | func f1() { println("single line") } 11 | func f2() { println("single line") } 12 | 13 | func f3() { 14 | println("multiline") 15 | } 16 | func f4() { 17 | println("multiline") 18 | } 19 | 20 | // l1 is a var. 21 | var l1 = []int{ 22 | 1, 2, 23 | } 24 | // l2 is a var. 25 | var l2 = []int{ 26 | 3, 4, 27 | } 28 | 29 | var ( 30 | s3 = ` 31 | ok if grouped together 32 | ` 33 | s4 = ` 34 | ok if grouped together 35 | ` 36 | ) 37 | var _ = "ok if either isn't multiline" 38 | -- foo.go.golden -- 39 | package p 40 | 41 | func f1() { println("single line") } 42 | func f2() { println("single line") } 43 | 44 | func f3() { 45 | println("multiline") 46 | } 47 | 48 | func f4() { 49 | println("multiline") 50 | } 51 | 52 | // l1 is a var. 53 | var l1 = []int{ 54 | 1, 2, 55 | } 56 | 57 | // l2 is a var. 58 | var l2 = []int{ 59 | 3, 4, 60 | } 61 | 62 | var ( 63 | s3 = ` 64 | ok if grouped together 65 | ` 66 | s4 = ` 67 | ok if grouped together 68 | ` 69 | ) 70 | var _ = "ok if either isn't multiline" 71 | -------------------------------------------------------------------------------- /testdata/script/octal-literals.txtar: -------------------------------------------------------------------------------- 1 | # Initially, the Go language version is too low. 2 | exec gofumpt -l . 3 | ! stdout . 4 | 5 | # We can give an explicitly newer version. 6 | exec gofumpt -lang=go1.13 -l . 7 | stdout -count=1 'foo\.go' 8 | stdout -count=1 'nested[/\\]nested\.go' 9 | 10 | # If we bump the version in go.mod, it should be picked up. 11 | exec go mod edit -go=1.13 12 | exec gofumpt -l . 13 | stdout -count=1 'foo\.go' 14 | ! stdout 'nested' 15 | 16 | # Ensure we produce the output we expect, and that it's stable. 17 | exec gofumpt foo.go 18 | cmp stdout foo.go.golden 19 | exec gofumpt -d foo.go.golden 20 | ! stdout . 21 | 22 | # We can give an explicitly older version, too 23 | exec gofumpt -lang=go1.0 -l . 24 | ! stdout . 25 | 26 | -- go.mod -- 27 | module test 28 | 29 | go 1.12 30 | -- foo.go -- 31 | package p 32 | 33 | const ( 34 | i = 0 35 | j = 022 36 | k = 0o_7_5_5 37 | l = 1022 38 | ) 39 | -- foo.go.golden -- 40 | package p 41 | 42 | const ( 43 | i = 0 44 | j = 0o22 45 | k = 0o_7_5_5 46 | l = 1022 47 | ) 48 | -- nested/go.mod -- 49 | module nested 50 | 51 | go 1.11 52 | -- nested/nested.go -- 53 | package p 54 | 55 | const ( 56 | i = 0 57 | j = 022 58 | k = 0o_7_5_5 59 | l = 1022 60 | ) 61 | -------------------------------------------------------------------------------- /testdata/gofumpt-external/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= 2 | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= 3 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 4 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 5 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 6 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 7 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 8 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 9 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 10 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 11 | golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= 12 | golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 13 | mvdan.cc/gofumpt v0.8.1-0.20250831111522-b5fd2eb6e821 h1:VDqVZ5gLA1oZPVHY9UkEoD6NVP1785uI+y4u/9V8Bvo= 14 | mvdan.cc/gofumpt v0.8.1-0.20250831111522-b5fd2eb6e821/go.mod h1:3xYtNemnKiXaTh6R4VtlqDATFwBbdXI8lJvH/4qk7mw= 15 | -------------------------------------------------------------------------------- /testdata/script/clothe-returns.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -d foo.go.golden 2 | ! stdout . 3 | 4 | exec gofumpt -extra=clothe_returns -w foo.go 5 | cmp foo.go foo.go.golden 6 | 7 | exec gofumpt -extra=clothe_returns -d foo.go.golden 8 | ! stdout . 9 | 10 | -- foo.go -- 11 | package p 12 | 13 | func foo() (err error) { 14 | if true { 15 | return 16 | } 17 | if false { 18 | return func() (err2 error) { 19 | return 20 | } 21 | } 22 | return 23 | } 24 | 25 | func bar() (_ int, err error) { 26 | return 27 | } 28 | 29 | func baz() (a, b, c int) { 30 | return 31 | } 32 | 33 | func qux() (file string, b int, err error) { 34 | if err == nil { 35 | return 36 | } 37 | 38 | // A comment 39 | return 40 | } 41 | 42 | // quux does quuxy things 43 | func quux() {} 44 | -- foo.go.golden -- 45 | package p 46 | 47 | func foo() (err error) { 48 | if true { 49 | return err 50 | } 51 | if false { 52 | return func() (err2 error) { 53 | return err2 54 | } 55 | } 56 | return err 57 | } 58 | 59 | func bar() (_ int, err error) { 60 | return 61 | } 62 | 63 | func baz() (a, b, c int) { 64 | return a, b, c 65 | } 66 | 67 | func qux() (file string, b int, err error) { 68 | if err == nil { 69 | return file, b, err 70 | } 71 | 72 | // A comment 73 | return file, b, err 74 | } 75 | 76 | // quux does quuxy things 77 | func quux() {} 78 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/json" 8 | "flag" 9 | "os/exec" 10 | "path/filepath" 11 | "testing" 12 | 13 | "github.com/go-quicktest/qt" 14 | 15 | "github.com/rogpeppe/go-internal/gotooltest" 16 | "github.com/rogpeppe/go-internal/testscript" 17 | ) 18 | 19 | func TestMain(m *testing.M) { 20 | testscript.Main(m, map[string]func(){ 21 | "gofumpt": main, 22 | }) 23 | } 24 | 25 | var update = flag.Bool("u", false, "update testscript output files") 26 | 27 | func TestScript(t *testing.T) { 28 | t.Parallel() 29 | 30 | var goEnv struct { 31 | GOCACHE string 32 | GOMODCACHE string 33 | GOMOD string 34 | } 35 | out, err := exec.Command("go", "env", "-json").CombinedOutput() 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | if err := json.Unmarshal(out, &goEnv); err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | p := testscript.Params{ 44 | Dir: filepath.Join("testdata", "script"), 45 | UpdateScripts: *update, 46 | RequireExplicitExec: true, 47 | Setup: func(env *testscript.Env) error { 48 | env.Setenv("GOCACHE", goEnv.GOCACHE) 49 | env.Setenv("GOMODCACHE", goEnv.GOMODCACHE) 50 | env.Setenv("GOMOD_DIR", filepath.Dir(goEnv.GOMOD)) 51 | return nil 52 | }, 53 | } 54 | err = gotooltest.Setup(&p) 55 | qt.Assert(t, qt.IsNil(err)) 56 | testscript.Run(t, p) 57 | } 58 | -------------------------------------------------------------------------------- /internal/govendor/go/doc/comment/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package comment implements parsing and reformatting of Go doc comments, 7 | (documentation comments), which are comments that immediately precede 8 | a top-level declaration of a package, const, func, type, or var. 9 | 10 | Go doc comment syntax is a simplified subset of Markdown that supports 11 | links, headings, paragraphs, lists (without nesting), and preformatted text blocks. 12 | The details of the syntax are documented at https://go.dev/doc/comment. 13 | 14 | To parse the text associated with a doc comment (after removing comment markers), 15 | use a [Parser]: 16 | 17 | var p comment.Parser 18 | doc := p.Parse(text) 19 | 20 | The result is a [*Doc]. 21 | To reformat it as a doc comment, HTML, Markdown, or plain text, 22 | use a [Printer]: 23 | 24 | var pr comment.Printer 25 | os.Stdout.Write(pr.Text(doc)) 26 | 27 | The [Parser] and [Printer] types are structs whose fields can be 28 | modified to customize the operations. 29 | For details, see the documentation for those types. 30 | 31 | Use cases that need additional control over reformatting can 32 | implement their own logic by inspecting the parsed syntax itself. 33 | See the documentation for [Doc], [Block], [Text] for an overview 34 | and links to additional types. 35 | */ 36 | package comment 37 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package version 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "runtime" 10 | "runtime/debug" 11 | ) 12 | 13 | const ourModulePath = "mvdan.cc/gofumpt" 14 | 15 | func findModule(info *debug.BuildInfo, modulePath string) *debug.Module { 16 | if info.Main.Path == modulePath { 17 | return &info.Main 18 | } 19 | for _, dep := range info.Deps { 20 | if dep.Path == modulePath { 21 | return dep 22 | } 23 | } 24 | return nil 25 | } 26 | 27 | func gofumptVersion() string { 28 | info, ok := debug.ReadBuildInfo() 29 | if !ok { 30 | return "(no build info)" 31 | } 32 | // Note that gofumpt may be used as a library via the format package, 33 | // so we cannot assume it is the main module in the build. 34 | mod := findModule(info, ourModulePath) 35 | if mod == nil { 36 | return "(module not found)" 37 | } 38 | if mod.Replace != nil { 39 | mod = mod.Replace 40 | } 41 | return mod.Version 42 | } 43 | 44 | func goVersion() string { 45 | // For the tests, as we don't want the Go version to change over time. 46 | if testVersion := os.Getenv("GO_VERSION_TEST"); testVersion != "" { 47 | return testVersion 48 | } 49 | return runtime.Version() 50 | } 51 | 52 | func String(injected string) string { 53 | if injected != "" { 54 | return fmt.Sprintf("%s (%s)", injected, goVersion()) 55 | } 56 | return fmt.Sprintf("%s (%s)", gofumptVersion(), goVersion()) 57 | } 58 | -------------------------------------------------------------------------------- /testdata/script/decl-group-single.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w f1.go f2.go 2 | cmp f1.go f1.go.golden 3 | cmp f2.go f2.go.golden 4 | 5 | exec gofumpt -d f1.go.golden f2.go.golden 6 | ! stdout . 7 | 8 | -- f1.go -- 9 | package p 10 | 11 | import "non-grouped" 12 | 13 | import ( 14 | "grouped" 15 | ) 16 | 17 | var single = "foo" 18 | 19 | var ( 20 | // verbose is verbose. 21 | verbose = "bar" 22 | ) 23 | 24 | // This entire block has a comment. 25 | var ( 26 | groupComment = "bar" 27 | ) 28 | 29 | var ( 30 | multiple1 string 31 | multiple2 string 32 | ) 33 | 34 | const ( 35 | first = iota 36 | ) 37 | 38 | var ( 39 | multiline = []string{ 40 | "foo", 41 | "bar", 42 | } 43 | ) 44 | 45 | var ( 46 | foo = "foo" 47 | // bar = "bar" 48 | // baz = "baz" 49 | ) 50 | -- f1.go.golden -- 51 | package p 52 | 53 | import "non-grouped" 54 | 55 | import ( 56 | "grouped" 57 | ) 58 | 59 | var single = "foo" 60 | 61 | // verbose is verbose. 62 | var verbose = "bar" 63 | 64 | // This entire block has a comment. 65 | var ( 66 | groupComment = "bar" 67 | ) 68 | 69 | var ( 70 | multiple1 string 71 | multiple2 string 72 | ) 73 | 74 | const ( 75 | first = iota 76 | ) 77 | 78 | var multiline = []string{ 79 | "foo", 80 | "bar", 81 | } 82 | 83 | var foo = "foo" 84 | 85 | // bar = "bar" 86 | // baz = "baz" 87 | -- f2.go -- 88 | package p 89 | 90 | func _() { 91 | var ( 92 | _ int 93 | ) 94 | } 95 | -- f2.go.golden -- 96 | package p 97 | 98 | func _() { 99 | var _ int 100 | } 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Daniel Martí. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of the copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSE.google: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /testdata/script/decl-group-many.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | var single = "foo" 11 | var another = "bar" 12 | 13 | const one = 'q' 14 | const two = 'w' 15 | const three = 'e' 16 | const four = 'r' 17 | 18 | var not = 'a' 19 | 20 | var v1 = 's' 21 | //go:embed hello.txt 22 | var v2 = 'd' 23 | 24 | var v1 = 's' 25 | // comment line 1 26 | // comment line 2 27 | var v2 = 'd' 28 | 29 | var v1 = "mixed" 30 | const c1 = "mixed" 31 | 32 | //go:embed hello.txt 33 | var v1 = 's' 34 | var v2 = 'd' 35 | var v3 = 'd' 36 | 37 | // comment 38 | var v1 = 's' 39 | var v2 = 'd' 40 | /* comment */ 41 | var v3 = 'd' 42 | 43 | const inline1 = "s1" // c1 44 | const inline2 = "s2" // c2 45 | const inline3 = "s3" // c3 46 | -- foo.go.golden -- 47 | package p 48 | 49 | var ( 50 | single = "foo" 51 | another = "bar" 52 | ) 53 | 54 | const ( 55 | one = 'q' 56 | two = 'w' 57 | three = 'e' 58 | four = 'r' 59 | ) 60 | 61 | var not = 'a' 62 | 63 | var v1 = 's' 64 | 65 | //go:embed hello.txt 66 | var v2 = 'd' 67 | 68 | var ( 69 | v1 = 's' 70 | // comment line 1 71 | // comment line 2 72 | v2 = 'd' 73 | ) 74 | 75 | var v1 = "mixed" 76 | 77 | const c1 = "mixed" 78 | 79 | //go:embed hello.txt 80 | var v1 = 's' 81 | 82 | var ( 83 | v2 = 'd' 84 | v3 = 'd' 85 | ) 86 | 87 | // comment 88 | var ( 89 | v1 = 's' 90 | v2 = 'd' 91 | /* comment */ 92 | v3 = 'd' 93 | ) 94 | 95 | const ( 96 | inline1 = "s1" // c1 97 | inline2 = "s2" // c2 98 | inline3 = "s3" // c3 99 | ) 100 | -------------------------------------------------------------------------------- /testdata/script/composite-literals-leading-lines.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | var _ = []string{ 11 | 12 | 13 | "foo", 14 | } 15 | 16 | var _ = []string{ 17 | 18 | "foo", 19 | } 20 | 21 | var _ = []string{ 22 | 23 | // joint comment 24 | "foo", 25 | } 26 | 27 | var _ = []string{ 28 | // separate comment 29 | 30 | "foo", 31 | } 32 | 33 | var _ = map[string]string{ 34 | 35 | 36 | "foo": "bar", 37 | } 38 | 39 | var _ = map[string]string{ 40 | 41 | "foo": "bar", 42 | } 43 | 44 | var _ = map[string]string{ 45 | 46 | // joint comment 47 | "foo": "bar", 48 | } 49 | 50 | var _ = map[string]string{ 51 | // separate comment 52 | 53 | "foo": "bar", 54 | } 55 | 56 | var _ = map[string]string{ 57 | /* 58 | joint comment 59 | */ 60 | "foo": "bar", 61 | } 62 | 63 | -- foo.go.golden -- 64 | package p 65 | 66 | var _ = []string{ 67 | "foo", 68 | } 69 | 70 | var _ = []string{ 71 | "foo", 72 | } 73 | 74 | var _ = []string{ 75 | // joint comment 76 | "foo", 77 | } 78 | 79 | var _ = []string{ 80 | // separate comment 81 | 82 | "foo", 83 | } 84 | 85 | var _ = map[string]string{ 86 | "foo": "bar", 87 | } 88 | 89 | var _ = map[string]string{ 90 | "foo": "bar", 91 | } 92 | 93 | var _ = map[string]string{ 94 | // joint comment 95 | "foo": "bar", 96 | } 97 | 98 | var _ = map[string]string{ 99 | // separate comment 100 | 101 | "foo": "bar", 102 | } 103 | 104 | var _ = map[string]string{ 105 | /* 106 | joint comment 107 | */ 108 | "foo": "bar", 109 | } 110 | -------------------------------------------------------------------------------- /testdata/script/typeparams.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt foo.go 2 | cmp stdout foo.go.golden 3 | 4 | -- go.mod -- 5 | module test 6 | 7 | go 1.18 8 | -- foo.go -- 9 | package p 10 | 11 | func Foo[A, B any](x A, y B) {} 12 | 13 | type Vector[T any] []T 14 | 15 | var v Vector[int ] 16 | 17 | type PredeclaredSignedInteger interface { 18 | int | int8 | int16 | int32 | int64 19 | } 20 | 21 | type StringableSignedInteger interface { 22 | 23 | ~int | ~int8 | ~int16 | ~int32 | ~int64 24 | 25 | String() string 26 | 27 | } 28 | 29 | type CombineEmbeds interface { 30 | fmt.Stringer 31 | comparable | io.Reader 32 | 33 | Foo() 34 | } 35 | 36 | func Caller() { 37 | Foo[int,int](1,2) 38 | } 39 | 40 | func Issue235[K interface { 41 | comparable 42 | constraints.Ordered 43 | }, V any](m map[K]V) []K { 44 | keys := maps.Keys(m) 45 | slices.Sort(keys) 46 | return keys 47 | } 48 | 49 | func multilineParams[V any](p1 V, 50 | p2 V) { 51 | 52 | println("body") 53 | 54 | } 55 | -- foo.go.golden -- 56 | package p 57 | 58 | func Foo[A, B any](x A, y B) {} 59 | 60 | type Vector[T any] []T 61 | 62 | var v Vector[int] 63 | 64 | type PredeclaredSignedInteger interface { 65 | int | int8 | int16 | int32 | int64 66 | } 67 | 68 | type StringableSignedInteger interface { 69 | ~int | ~int8 | ~int16 | ~int32 | ~int64 70 | 71 | String() string 72 | } 73 | 74 | type CombineEmbeds interface { 75 | fmt.Stringer 76 | comparable | io.Reader 77 | 78 | Foo() 79 | } 80 | 81 | func Caller() { 82 | Foo[int, int](1, 2) 83 | } 84 | 85 | func Issue235[K interface { 86 | comparable 87 | constraints.Ordered 88 | }, V any](m map[K]V) []K { 89 | keys := maps.Keys(m) 90 | slices.Sort(keys) 91 | return keys 92 | } 93 | 94 | func multilineParams[V any](p1 V, 95 | p2 V, 96 | ) { 97 | println("body") 98 | } 99 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= 3 | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= 4 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 5 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 6 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 7 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 8 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 9 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 10 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 11 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 12 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 13 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 14 | golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= 15 | golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= 16 | golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= 17 | golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 18 | golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= 19 | golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 20 | golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= 21 | golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= 22 | -------------------------------------------------------------------------------- /testdata/script/block-single.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | func f() { 11 | if true { 12 | 13 | println() 14 | } 15 | 16 | for true { 17 | println() 18 | 19 | } 20 | 21 | { 22 | 23 | 24 | println(1, 2, 25 | 3, 4, `foo 26 | bar`) 27 | 28 | 29 | } 30 | 31 | { 32 | 33 | // comment directly before 34 | println() 35 | 36 | // comment after 37 | 38 | } 39 | 40 | { 41 | 42 | // comment before 43 | 44 | println() 45 | // comment directly after 46 | 47 | } 48 | 49 | // For readability; the empty line helps separate the multi-line 50 | // condition from the body. 51 | if true && 52 | true { 53 | 54 | println() 55 | } 56 | for true && 57 | true { 58 | 59 | println() 60 | } 61 | if true && 62 | true { 63 | 64 | // documented single statement 65 | println() 66 | } 67 | } 68 | -- foo.go.golden -- 69 | package p 70 | 71 | func f() { 72 | if true { 73 | println() 74 | } 75 | 76 | for true { 77 | println() 78 | } 79 | 80 | { 81 | println(1, 2, 82 | 3, 4, `foo 83 | bar`) 84 | } 85 | 86 | { 87 | // comment directly before 88 | println() 89 | 90 | // comment after 91 | } 92 | 93 | { 94 | // comment before 95 | 96 | println() 97 | // comment directly after 98 | } 99 | 100 | // For readability; the empty line helps separate the multi-line 101 | // condition from the body. 102 | if true && 103 | true { 104 | 105 | println() 106 | } 107 | for true && 108 | true { 109 | 110 | println() 111 | } 112 | if true && 113 | true { 114 | 115 | // documented single statement 116 | println() 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /testdata/script/newline-errcheck.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | var Do1 func() error 11 | 12 | var Do2 func() (int, error) 13 | 14 | func f() { 15 | n1, err := Do2() 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | if n2, err := Do2(); err != nil { 21 | panic(err) 22 | } 23 | 24 | n3, err := Do2() 25 | 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | select { 31 | default: 32 | err := Do1() 33 | 34 | if err != nil { 35 | panic(err) 36 | } 37 | } 38 | 39 | n4, err := Do2() 40 | 41 | if err != nil && err.Error() == "complex condition" { 42 | panic(err) 43 | } 44 | 45 | err1 := Do1() 46 | 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | { 52 | if err != nil { 53 | panic(err) 54 | } 55 | } 56 | 57 | n5, err = Do2() 58 | 59 | if err != nil { 60 | panic(err) 61 | } 62 | } 63 | -- foo.go.golden -- 64 | package p 65 | 66 | var Do1 func() error 67 | 68 | var Do2 func() (int, error) 69 | 70 | func f() { 71 | n1, err := Do2() 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | if n2, err := Do2(); err != nil { 77 | panic(err) 78 | } 79 | 80 | n3, err := Do2() 81 | if err != nil { 82 | panic(err) 83 | } 84 | 85 | select { 86 | default: 87 | err := Do1() 88 | if err != nil { 89 | panic(err) 90 | } 91 | } 92 | 93 | n4, err := Do2() 94 | 95 | if err != nil && err.Error() == "complex condition" { 96 | panic(err) 97 | } 98 | 99 | err1 := Do1() 100 | 101 | if err != nil { 102 | panic(err) 103 | } 104 | 105 | { 106 | if err != nil { 107 | panic(err) 108 | } 109 | } 110 | 111 | n5, err = Do2() 112 | if err != nil { 113 | panic(err) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /format/fuzz_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package format 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "go/scanner" 10 | "path/filepath" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/go-quicktest/qt" 15 | "golang.org/x/tools/txtar" 16 | ) 17 | 18 | func FuzzFormat(f *testing.F) { 19 | // Initialize the corpus with the Go files from our test scripts. 20 | paths, err := filepath.Glob(filepath.Join("..", "testdata", "script", "*.txtar")) 21 | qt.Assert(f, qt.IsNil(err)) 22 | qt.Assert(f, qt.Not(qt.HasLen(paths, 0))) 23 | for _, path := range paths { 24 | archive, err := txtar.ParseFile(path) 25 | qt.Assert(f, qt.IsNil(err)) 26 | for _, file := range archive.Files { 27 | f.Logf("adding %s from %s", file.Name, path) 28 | if strings.HasSuffix(file.Name, ".go") || strings.Contains(file.Name, ".go.") { 29 | f.Add(string(file.Data), int8(18), false) // -lang=go1.18 30 | f.Add(string(file.Data), int8(1), false) // -lang=go1.1 31 | f.Add(string(file.Data), int8(18), true) // -lang=go1.18 -extra 32 | } 33 | } 34 | } 35 | 36 | f.Fuzz(func(t *testing.T, src string, 37 | majorVersion int8, // Empty version if negative, 1.N otherwise. 38 | extraRules bool, 39 | ) { 40 | // TODO: also fuzz Options.ModulePath 41 | opts := Options{ExtraRules: extraRules} 42 | if majorVersion >= 0 { 43 | opts.LangVersion = fmt.Sprintf("go1.%d", majorVersion) 44 | } 45 | 46 | orig := []byte(src) 47 | formatted, err := Source(orig, opts) 48 | if errors.As(err, &scanner.ErrorList{}) { 49 | return // invalid syntax from parsing 50 | } 51 | qt.Assert(t, qt.IsNil(err)) 52 | _ = formatted 53 | 54 | // TODO: verify that the result is idempotent 55 | 56 | // TODO: verify that, if the input was valid Go 1.N syntax, 57 | // so is the output (how? go/parser lacks an option) 58 | 59 | // TODO: check calling format.Node directly as well 60 | 61 | qt.Assert(t, qt.Equals(string(orig), src), 62 | qt.Commentf("input source bytes were modified")) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /testdata/script/composite-multiline.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | var _ = []int{} 11 | 12 | var _ = []int{ 13 | } 14 | 15 | var _ = []int{1, 2, 16 | 3, 4} 17 | 18 | var _ = []int{ 19 | 1, 2, 3, 4} 20 | 21 | var _ = [][]string{{ 22 | "no need for more newlines", 23 | "if wrapping a single expression", 24 | }} 25 | 26 | var _ = []string{` 27 | no need for newlines 28 | `, ` 29 | if no elements are surrounded by newlines 30 | `} 31 | 32 | var _ = []struct{ a int }{ 33 | { // consistent 34 | a: 1, 35 | }, 36 | { 37 | a: 2, 38 | }, { // inconsistent 39 | a: 3, 40 | }, 41 | } 42 | 43 | var _ = []struct{ a int }{{ 44 | a: 1, 45 | }, { 46 | a: 2, 47 | }, { 48 | a: 3, 49 | }} 50 | 51 | var _ interface{ 52 | } 53 | 54 | func _(struct{ 55 | }) 56 | 57 | var _ = []interface { 58 | }{1, 2, 3} 59 | 60 | func _( 61 | ) 62 | 63 | type T struct { 64 | Foo // comment 65 | Bar struct { // comment 66 | } 67 | } 68 | -- foo.go.golden -- 69 | package p 70 | 71 | var _ = []int{} 72 | 73 | var _ = []int{} 74 | 75 | var _ = []int{ 76 | 1, 2, 77 | 3, 4, 78 | } 79 | 80 | var _ = []int{ 81 | 1, 2, 3, 4, 82 | } 83 | 84 | var _ = [][]string{{ 85 | "no need for more newlines", 86 | "if wrapping a single expression", 87 | }} 88 | 89 | var _ = []string{` 90 | no need for newlines 91 | `, ` 92 | if no elements are surrounded by newlines 93 | `} 94 | 95 | var _ = []struct{ a int }{ 96 | { // consistent 97 | a: 1, 98 | }, 99 | { 100 | a: 2, 101 | }, 102 | { // inconsistent 103 | a: 3, 104 | }, 105 | } 106 | 107 | var _ = []struct{ a int }{{ 108 | a: 1, 109 | }, { 110 | a: 2, 111 | }, { 112 | a: 3, 113 | }} 114 | 115 | var _ interface{} 116 | 117 | func _(struct{}) 118 | 119 | var _ = []interface{}{1, 2, 3} 120 | 121 | func _() 122 | 123 | type T struct { 124 | Foo // comment 125 | Bar struct { // comment 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /gen_govendor.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | //go:build ignore 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "encoding/json" 11 | "io" 12 | "os" 13 | "os/exec" 14 | "path" 15 | "path/filepath" 16 | "strings" 17 | ) 18 | 19 | var ( 20 | modulePath = "mvdan.cc/gofumpt" 21 | vendorDir = filepath.Join("internal", "govendor") 22 | ) 23 | 24 | // All the packages which affect the formatting behavior. 25 | var toVendor = []string{ 26 | "go/format", 27 | "go/printer", 28 | "go/doc/comment", 29 | "internal/diff", 30 | } 31 | 32 | func main() { 33 | catch(os.RemoveAll(vendorDir)) 34 | 35 | catch(os.MkdirAll(vendorDir, 0o777)) 36 | out, err := exec.Command("go", "env", "GOVERSION").Output() 37 | catch(err) 38 | catch(os.WriteFile(filepath.Join(vendorDir, "version.txt"), out, 0o666)) 39 | 40 | oldnew := []string{ 41 | "//go:generate", "//disabled go:generate", 42 | } 43 | for _, pkgPath := range toVendor { 44 | oldnew = append(oldnew, pkgPath, path.Join(modulePath, vendorDir, pkgPath)) 45 | } 46 | replacer := strings.NewReplacer(oldnew...) 47 | 48 | listArgs := append([]string{"list", "-json"}, toVendor...) 49 | out, err = exec.Command("go", listArgs...).Output() 50 | catch(err) 51 | 52 | type Package struct { 53 | Dir string 54 | ImportPath string 55 | GoFiles []string 56 | } 57 | dec := json.NewDecoder(bytes.NewReader(out)) 58 | for { 59 | var pkg Package 60 | err := dec.Decode(&pkg) 61 | if err == io.EOF { 62 | break 63 | } 64 | catch(err) 65 | 66 | // Otherwise we can't import it. 67 | dstPkg := strings.TrimPrefix(pkg.ImportPath, "internal/") 68 | 69 | dstDir := filepath.Join(vendorDir, filepath.FromSlash(dstPkg)) 70 | catch(os.MkdirAll(dstDir, 0o777)) 71 | // TODO: if the packages start using build tags like GOOS or GOARCH, 72 | // we will need to vendor IgnoredGoFiles as well. 73 | for _, goFile := range pkg.GoFiles { 74 | srcBytes, err := os.ReadFile(filepath.Join(pkg.Dir, goFile)) 75 | catch(err) 76 | 77 | src := replacer.Replace(string(srcBytes)) 78 | 79 | dst := filepath.Join(dstDir, goFile) 80 | catch(os.WriteFile(dst, []byte(src), 0o666)) 81 | } 82 | } 83 | } 84 | 85 | func catch(err error) { 86 | if err != nil { 87 | panic(err) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /testdata/script/func-merge-parameters.txtar: -------------------------------------------------------------------------------- 1 | # By default, this rule isn't enabled. 2 | exec gofumpt foo.go 3 | cmp stdout foo.go 4 | 5 | exec gofumpt -extra=group_params foo.go 6 | cmp stdout foo.go.golden 7 | 8 | exec gofumpt -d foo.go.golden 9 | exec gofumpt -extra=group_params -d foo.go.golden 10 | ! stdout . 11 | 12 | -- foo.go -- 13 | package p 14 | 15 | type f func(x int, y int) int 16 | 17 | type i interface { 18 | add(x int, y int) 19 | } 20 | 21 | type s struct { 22 | x int 23 | y int 24 | } 25 | 26 | func mergeAdjacent(x int, y int) {} 27 | 28 | func mergeThreeAdjacent(x int, y int, z int) {} 29 | 30 | func mergeOneWithTwo(x, y int, z int) {} 31 | 32 | func mergeTwoWithOne(x int, y, z int) {} 33 | 34 | func mergeWithComment( 35 | x int, y int, // comment 36 | ) 37 | 38 | func mergeAllSyntax(x chan []*foo.Bar, y chan []*foo.Bar) {} 39 | 40 | func dontMergeAnonymousParams(int, int) {} 41 | 42 | func dontMergeMultipleLines( 43 | x int, 44 | y int, 45 | ) { 46 | } 47 | 48 | func dontMergeMultipleLines2( 49 | x, 50 | y int, 51 | z int, 52 | ) { 53 | } 54 | 55 | func dontMergeDifferentKinds(format string, args ...string) {} 56 | 57 | func dontMergeDifferentTypesReturn() (n int, err error) {} 58 | 59 | func mergeIgnoresPositions(a func(x int), b func( 60 | x int)) { 61 | } 62 | 63 | type xint int 64 | 65 | func dontMergeDistinguishedTypes(a func(x int), b func(xint)) {} 66 | -- foo.go.golden -- 67 | package p 68 | 69 | type f func(x, y int) int 70 | 71 | type i interface { 72 | add(x, y int) 73 | } 74 | 75 | type s struct { 76 | x int 77 | y int 78 | } 79 | 80 | func mergeAdjacent(x, y int) {} 81 | 82 | func mergeThreeAdjacent(x, y, z int) {} 83 | 84 | func mergeOneWithTwo(x, y, z int) {} 85 | 86 | func mergeTwoWithOne(x, y, z int) {} 87 | 88 | func mergeWithComment( 89 | x, y int, // comment 90 | ) 91 | 92 | func mergeAllSyntax(x, y chan []*foo.Bar) {} 93 | 94 | func dontMergeAnonymousParams(int, int) {} 95 | 96 | func dontMergeMultipleLines( 97 | x int, 98 | y int, 99 | ) { 100 | } 101 | 102 | func dontMergeMultipleLines2( 103 | x, 104 | y int, 105 | z int, 106 | ) { 107 | } 108 | 109 | func dontMergeDifferentKinds(format string, args ...string) {} 110 | 111 | func dontMergeDifferentTypesReturn() (n int, err error) {} 112 | 113 | func mergeIgnoresPositions(a, b func(x int), 114 | ) { 115 | } 116 | 117 | type xint int 118 | 119 | func dontMergeDistinguishedTypes(a func(x int), b func(xint)) {} 120 | -------------------------------------------------------------------------------- /testdata/script/comment-spaced.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | //go:build tag 9 | // +build tag 10 | 11 | package p 12 | 13 | //go:generate some command 14 | 15 | //go:unknowndirective 16 | 17 | //lint:disablefoo 18 | 19 | //go-sumtype:decl Foo 20 | 21 | //nolint 22 | 23 | //nolint // explanation 24 | 25 | //nolint:somelinter // explanation 26 | 27 | //NOSONAR 28 | 29 | //NOSONAR // explanation 30 | 31 | //noinspection ALL 32 | 33 | //noinspection foo,bar 34 | 35 | //#nosec 36 | 37 | //#nosec G000 G999 -- explanation 38 | 39 | //not actually: a directive 40 | 41 | //https://just.one/url 42 | 43 | //TODO: do something 44 | 45 | //export CgoFunc 46 | 47 | //extern open 48 | func c_open(name *byte, mode int, perm int) int 49 | 50 | //line 123 51 | 52 | //sys Unlink(path string) (err error) 53 | 54 | //sysnb Getpid() (pid int) 55 | 56 | //foo:bar_one 57 | 58 | //foo:bar-two 59 | 60 | //foo:bar01 61 | 62 | //foo:barOne 63 | 64 | //foo:barTwo 65 | 66 | //foo is foo. 67 | type foo int 68 | 69 | // comment with a tab. 70 | 71 | // comment with many spaces 72 | 73 | //comment group 74 | //123 numbers too 75 | 76 | // comment group 77 | //123 numbers too 78 | 79 | //{ 80 | //this is probably code 81 | //} 82 | 83 | //////////// 84 | // ascii art 85 | //---------- 86 | 87 | // 88 | -- foo.go.golden -- 89 | //go:build tag 90 | // +build tag 91 | 92 | package p 93 | 94 | //go:generate some command 95 | 96 | //go:unknowndirective 97 | 98 | //lint:disablefoo 99 | 100 | //go-sumtype:decl Foo 101 | 102 | //nolint 103 | 104 | //nolint // explanation 105 | 106 | //nolint:somelinter // explanation 107 | 108 | //NOSONAR 109 | 110 | //NOSONAR // explanation 111 | 112 | //noinspection ALL 113 | 114 | //noinspection foo,bar 115 | 116 | //#nosec 117 | 118 | //#nosec G000 G999 -- explanation 119 | 120 | // not actually: a directive 121 | 122 | // https://just.one/url 123 | 124 | // TODO: do something 125 | 126 | //export CgoFunc 127 | 128 | //extern open 129 | func c_open(name *byte, mode int, perm int) int 130 | 131 | //line 123 132 | 133 | //sys Unlink(path string) (err error) 134 | 135 | //sysnb Getpid() (pid int) 136 | 137 | //foo:bar_one 138 | 139 | //foo:bar-two 140 | 141 | //foo:bar01 142 | 143 | //foo:barOne 144 | 145 | //foo:barTwo 146 | 147 | // foo is foo. 148 | type foo int 149 | 150 | // comment with a tab. 151 | 152 | // comment with many spaces 153 | 154 | // comment group 155 | // 123 numbers too 156 | 157 | // comment group 158 | // 123 numbers too 159 | 160 | //{ 161 | //this is probably code 162 | //} 163 | 164 | //////////// 165 | // ascii art 166 | //---------- 167 | 168 | // 169 | -------------------------------------------------------------------------------- /testdata/script/ignore.txtar: -------------------------------------------------------------------------------- 1 | # format explicit dirs 2 | exec gofumpt -l vendor testdata module/ignore/dir 3 | cmpenv stdout explicit-dirs.stdout 4 | ! stderr . 5 | 6 | # format explicit files 7 | exec gofumpt -l vendor/foo/foo.go testdata/foo/foo.go module/ignore/dir/ignore.go 8 | cmpenv stdout explicit-files.stdout 9 | ! stderr . 10 | 11 | # ignore implicit dirs via fs walking 12 | exec gofumpt -l . 13 | cmpenv stdout implicit-dot.stdout 14 | ! stderr . 15 | 16 | # format explicit dirs without clean paths 17 | exec gofumpt -l $WORK//vendor ./testdata/./ $WORK/module/ignore//dir/. 18 | cmpenv stdout explicit-dirs-unclean.stdout 19 | ! stderr . 20 | 21 | -- explicit-dirs.stdout -- 22 | vendor${/}bar${/}bar.go 23 | vendor${/}foo${/}foo.go 24 | testdata${/}bar${/}bar.go 25 | testdata${/}foo${/}foo.go 26 | module${/}ignore${/}dir${/}ignore.go 27 | module${/}ignore${/}dir${/}subdir${/}ignore.go 28 | -- explicit-files.stdout -- 29 | vendor${/}foo${/}foo.go 30 | testdata${/}foo${/}foo.go 31 | module${/}ignore${/}dir${/}ignore.go 32 | -- implicit-dot.stdout -- 33 | ignore${/}dir${/}outsidemodule.go 34 | module${/}ignore${/}dirsuffix${/}nomatch.go 35 | module${/}ignore${/}nomatch.go 36 | module${/}ignoreroot${/}nomatch.go 37 | module${/}prefixignore${/}dir${/}nomatch.go 38 | module${/}subdir${/}ignoreroot${/}dir${/}notatroot.go 39 | -- explicit-dirs-unclean.stdout -- 40 | ${WORK}${/}vendor${/}bar${/}bar.go 41 | ${WORK}${/}vendor${/}foo${/}foo.go 42 | testdata${/}bar${/}bar.go 43 | testdata${/}foo${/}foo.go 44 | ${WORK}${/}module${/}ignore${/}dir${/}ignore.go 45 | ${WORK}${/}module${/}ignore${/}dir${/}subdir${/}ignore.go 46 | -- module/go.mod -- 47 | module test 48 | 49 | ignore ( 50 | ignore/dir 51 | ./ignoreroot/dir 52 | ) 53 | -- ignore/dir/outsidemodule.go -- 54 | package p; func badlyformatted() {} 55 | -- module/ignoreroot/dir/ignore.go -- 56 | package p; func badlyformatted() {} 57 | -- module/ignoreroot/dir/subdir/ignore.go -- 58 | package p; func badlyformatted() {} 59 | -- module/ignore/dir/ignore.go -- 60 | package p; func badlyformatted() {} 61 | -- module/ignore/dir/subdir/ignore.go -- 62 | package p; func badlyformatted() {} 63 | -- module/ignore/dirsuffix/nomatch.go -- 64 | package p; func badlyformatted() {} 65 | -- module/prefixignore/dir/nomatch.go -- 66 | package p; func badlyformatted() {} 67 | -- module/subdir/ignoreroot/dir/notatroot.go -- 68 | package p; func badlyformatted() {} 69 | -- module/subdir/ignore/dir/ignore.go -- 70 | package p; func badlyformatted() {} 71 | -- module/ignore/nomatch.go -- 72 | package p; func badlyformatted() {} 73 | -- module/ignoreroot/nomatch.go -- 74 | package p; func badlyformatted() {} 75 | -- vendor/foo/foo.go -- 76 | package p; func badlyformatted() {} 77 | -- vendor/bar/bar.go -- 78 | package p; func badlyformatted() {} 79 | -- testdata/foo/foo.go -- 80 | package p; func badlyformatted() {} 81 | -- testdata/bar/bar.go -- 82 | package p; func badlyformatted() {} 83 | -------------------------------------------------------------------------------- /testdata/script/short-case.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | func f(r rune) { 11 | switch r { 12 | case 'a', 13 | 'b', 14 | 'c': 15 | 16 | case 'd', 'e', 'f': 17 | 18 | case 'a', 'b', 19 | 'c': 20 | 21 | case 'v', 'e', 'r', 'y', 'l', 'o', 'n', 'g', 22 | 'l', 'i', 's', 't', '.', '.', '.': 23 | 24 | // before 25 | case 'a', 26 | 'b': // inline 27 | // after 28 | 29 | case 'a', // middle 30 | 'b': 31 | 32 | case 'a', 'b', 'c', 'd', 'e', 'f', 33 | 'g': // very very long inline comment at the end 34 | 35 | case 'a', 'b', 'c', 36 | 'd': // short comment 37 | } 38 | { 39 | { 40 | { 41 | { 42 | { 43 | switch r { 44 | case 'i', 'n', 'd', 'e', 45 | 'n', 't', 'e', 'd': 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | func s(x int) { 55 | switch x { 56 | case 57 | shortConstant1, 58 | shortConstant2: 59 | // A comment. 60 | fmt.Println(x) 61 | case 62 | shortConstant3, 63 | shortConstant4: 64 | // Do nothing. 65 | default: 66 | // Another comment. 67 | fmt.Println(x * 2) 68 | } 69 | } 70 | 71 | func s(x int) { 72 | switch x { 73 | case 74 | longerConstantName1, 75 | longerConstantName2: 76 | // A comment. 77 | fmt.Println(x) 78 | case 79 | longerConstantName3, 80 | longerConstantName4: 81 | // Do nothing. 82 | default: 83 | // Another comment. 84 | fmt.Println(x * 2) 85 | } 86 | } 87 | -- foo.go.golden -- 88 | package p 89 | 90 | func f(r rune) { 91 | switch r { 92 | case 'a', 'b', 'c': 93 | 94 | case 'd', 'e', 'f': 95 | 96 | case 'a', 'b', 'c': 97 | 98 | case 'v', 'e', 'r', 'y', 'l', 'o', 'n', 'g', 99 | 'l', 'i', 's', 't', '.', '.', '.': 100 | 101 | // before 102 | case 'a', 'b': // inline 103 | // after 104 | 105 | case 'a', // middle 106 | 'b': 107 | 108 | case 'a', 'b', 'c', 'd', 'e', 'f', 109 | 'g': // very very long inline comment at the end 110 | 111 | case 'a', 'b', 'c', 'd': // short comment 112 | } 113 | { 114 | { 115 | { 116 | { 117 | { 118 | switch r { 119 | case 'i', 'n', 'd', 'e', 120 | 'n', 't', 'e', 'd': 121 | } 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | 129 | func s(x int) { 130 | switch x { 131 | case shortConstant1, shortConstant2: 132 | // A comment. 133 | fmt.Println(x) 134 | case shortConstant3, shortConstant4: 135 | // Do nothing. 136 | default: 137 | // Another comment. 138 | fmt.Println(x * 2) 139 | } 140 | } 141 | 142 | func s(x int) { 143 | switch x { 144 | case 145 | longerConstantName1, 146 | longerConstantName2: 147 | // A comment. 148 | fmt.Println(x) 149 | case 150 | longerConstantName3, 151 | longerConstantName4: 152 | // Do nothing. 153 | default: 154 | // Another comment. 155 | fmt.Println(x * 2) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /ulimit_linux_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "strconv" 13 | "testing" 14 | 15 | "github.com/go-quicktest/qt" 16 | "golang.org/x/sys/unix" 17 | ) 18 | 19 | func init() { 20 | // Here rather than in TestMain, to reuse the unix build tag. 21 | if limit := os.Getenv("TEST_WITH_FILE_LIMIT"); limit != "" { 22 | n, err := strconv.ParseUint(limit, 10, 64) 23 | if err != nil { 24 | panic(err) 25 | } 26 | rlimit := unix.Rlimit{Cur: n, Max: n} 27 | if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil { 28 | panic(err) 29 | } 30 | main() 31 | } 32 | } 33 | 34 | func TestWithLowOpenFileLimit(t *testing.T) { 35 | if testing.Short() { 36 | t.Skip("this test creates thousands of files") 37 | } 38 | // Safe to run in parallel, as we only change the limit for child processes. 39 | t.Parallel() 40 | 41 | tempDir := t.TempDir() 42 | testBinary, err := os.Executable() 43 | qt.Assert(t, qt.IsNil(err)) 44 | 45 | const ( 46 | // Enough directories to run into the ulimit. 47 | // Enough number of files in total to run into the ulimit. 48 | numberDirs = 500 49 | numberFilesPerDir = 20 50 | numberFilesTotal = numberDirs * numberFilesPerDir 51 | ) 52 | t.Logf("writing %d tiny Go files", numberFilesTotal) 53 | var allGoFiles []string 54 | for i := range numberDirs { 55 | // Prefix "p", so the package name is a valid identifier. 56 | // Add one go.mod file per directory as well, 57 | // which will help catch data races when loading module info. 58 | dirName := fmt.Sprintf("p%03d", i) 59 | dirPath := filepath.Join(tempDir, dirName) 60 | err := os.MkdirAll(dirPath, 0o777) 61 | qt.Assert(t, qt.IsNil(err)) 62 | 63 | err = os.WriteFile(filepath.Join(dirPath, "go.mod"), 64 | fmt.Appendf(nil, "module %s\n\ngo 1.16", dirName), 0o666) 65 | qt.Assert(t, qt.IsNil(err)) 66 | 67 | for j := range numberFilesPerDir { 68 | filePath := filepath.Join(dirPath, fmt.Sprintf("%03d.go", j)) 69 | err := os.WriteFile(filePath, 70 | // Extra newlines so that "-l" prints all paths. 71 | fmt.Appendf(nil, "package %s\n\n\n", dirName), 0o666) 72 | qt.Assert(t, qt.IsNil(err)) 73 | allGoFiles = append(allGoFiles, filePath) 74 | } 75 | } 76 | if len(allGoFiles) != numberFilesTotal { 77 | panic("allGoFiles doesn't have the expected number of files?") 78 | } 79 | runGofmt := func(paths ...string) { 80 | t.Logf("running with %d paths", len(paths)) 81 | cmd := exec.Command(testBinary, append([]string{"-l"}, paths...)...) 82 | // 256 is a relatively common low limit, e.g. on Mac. 83 | cmd.Env = append(os.Environ(), "TEST_WITH_FILE_LIMIT=256") 84 | out, err := cmd.Output() 85 | var stderr []byte 86 | if err, _ := err.(*exec.ExitError); err != nil { 87 | stderr = err.Stderr 88 | } 89 | qt.Assert(t, qt.IsNil(err), qt.Commentf("stderr:\n%s", stderr)) 90 | qt.Assert(t, qt.Equals(bytes.Count(out, []byte("\n")), len(allGoFiles))) 91 | } 92 | runGofmt(tempDir) 93 | runGofmt(allGoFiles...) 94 | } 95 | -------------------------------------------------------------------------------- /testdata/script/interface.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | type i1 interface { 11 | 12 | a(x int) int 13 | 14 | 15 | 16 | b(x int) int 17 | 18 | c(x int) int 19 | 20 | D() 21 | 22 | E() 23 | 24 | f() 25 | } 26 | 27 | type i2 interface { 28 | 29 | // comment for a 30 | a(x int) int 31 | 32 | // comment between a and b 33 | 34 | // comment for b 35 | b(x int) int 36 | 37 | // comment between b and c 38 | 39 | c(x int) int 40 | 41 | d(x int) int 42 | 43 | // comment for e 44 | e(x int) int 45 | 46 | } 47 | 48 | type i3 interface { 49 | a(x int) int 50 | 51 | // standalone comment 52 | 53 | b(x int) int 54 | } 55 | 56 | type leadingLine1 interface { 57 | 58 | 59 | a(x int) int 60 | } 61 | 62 | type leadingLine2 interface { 63 | 64 | a(x int) int 65 | } 66 | 67 | type leadingLine3 interface { 68 | 69 | // comment 70 | a(x int) int 71 | } 72 | 73 | type leadingLine4 interface { 74 | // comment 75 | 76 | a(x int) int 77 | } 78 | 79 | type leadingLine5 interface { 80 | // comment 81 | 82 | // comment for a 83 | a(x int) int 84 | } 85 | 86 | type leadingLine6 interface { 87 | 88 | // comment 89 | 90 | // comment for a 91 | a(x int) int 92 | } 93 | 94 | type leadingLine7 interface { 95 | 96 | 97 | // comment 98 | 99 | // comment for a 100 | a(x int) int 101 | } 102 | 103 | type leadingLine8 interface { 104 | // comment 105 | } 106 | 107 | type ii1 interface { 108 | DoA() 109 | DoB() 110 | 111 | UndoA() 112 | UndoB() 113 | } 114 | -- foo.go.golden -- 115 | package p 116 | 117 | type i1 interface { 118 | a(x int) int 119 | 120 | b(x int) int 121 | 122 | c(x int) int 123 | 124 | D() 125 | 126 | E() 127 | 128 | f() 129 | } 130 | 131 | type i2 interface { 132 | // comment for a 133 | a(x int) int 134 | 135 | // comment between a and b 136 | 137 | // comment for b 138 | b(x int) int 139 | 140 | // comment between b and c 141 | 142 | c(x int) int 143 | 144 | d(x int) int 145 | 146 | // comment for e 147 | e(x int) int 148 | } 149 | 150 | type i3 interface { 151 | a(x int) int 152 | 153 | // standalone comment 154 | 155 | b(x int) int 156 | } 157 | 158 | type leadingLine1 interface { 159 | a(x int) int 160 | } 161 | 162 | type leadingLine2 interface { 163 | a(x int) int 164 | } 165 | 166 | type leadingLine3 interface { 167 | // comment 168 | a(x int) int 169 | } 170 | 171 | type leadingLine4 interface { 172 | // comment 173 | 174 | a(x int) int 175 | } 176 | 177 | type leadingLine5 interface { 178 | // comment 179 | 180 | // comment for a 181 | a(x int) int 182 | } 183 | 184 | type leadingLine6 interface { 185 | // comment 186 | 187 | // comment for a 188 | a(x int) int 189 | } 190 | 191 | type leadingLine7 interface { 192 | // comment 193 | 194 | // comment for a 195 | a(x int) int 196 | } 197 | 198 | type leadingLine8 interface { 199 | // comment 200 | } 201 | 202 | type ii1 interface { 203 | DoA() 204 | DoB() 205 | 206 | UndoA() 207 | UndoB() 208 | } 209 | -------------------------------------------------------------------------------- /testdata/script/diagnose.txtar: -------------------------------------------------------------------------------- 1 | # Note that `go test` binaries don't get stamped with versions, 2 | # so the local tests below end up with a "devel" gofumpt version. 3 | env GO_VERSION_TEST=go1.18.29 4 | 5 | exec gofumpt foo.go 6 | cmp stdout foo.go.golden 7 | 8 | exec gofumpt outdated.go 9 | cmp stdout foo.go.golden 10 | 11 | exec gofumpt -extra foo.go 12 | cmp stdout foo.go.golden-extra 13 | 14 | exec gofumpt -extra=false foo.go 15 | cmp stdout foo.go.golden-extra-false 16 | 17 | exec gofumpt -extra=group_params foo.go 18 | cmp stdout foo.go.golden-extra-group-params 19 | 20 | exec gofumpt -lang=go1 foo.go 21 | cmp stdout foo.go.golden-lang 22 | 23 | exec gofumpt -d nochange.go 24 | ! stdout . 25 | 26 | exec gofumpt -d foo.go.golden 27 | ! stdout . 28 | 29 | exec gofumpt -d -extra foo.go.golden-extra 30 | ! stdout . 31 | 32 | [short] stop 'the rest of this test builds gofumpt binaries' 33 | 34 | # We want a published version of gofumpt on the public module proxies, 35 | # because that's the only way that its module version will be included. 36 | # Using a directory replace directive will not work. 37 | # This means that any change in how gofumpt reports its own version 38 | # will require two pull requests, the second one updating the test script. 39 | # We could consider using go-internal/goproxytest, but then we would need to 40 | # manually run something like go-internal/cmd/txtar-addmod reguarly. 41 | # Or teach goproxytest to serve a mock version of gofumpt from its local checkout. 42 | # Either way, both are relatively overkill for now. 43 | # Update this pseudo-version to master from time to time as needed. 44 | env GOBIN=${WORK}/bin 45 | env GOFUMPT_PUBLISHED_VERSION=v0.8.1-0.20250831111522-b5fd2eb6e821 46 | 47 | # TODO: update these once the library fix hits master 48 | 49 | # gofumpt as the main binary with a real module version. 50 | go install mvdan.cc/gofumpt@${GOFUMPT_PUBLISHED_VERSION} 51 | exec ${GOBIN}/gofumpt foo.go 52 | cmp stdout foo.go.golden-released 53 | 54 | # gofumpt as a library with a real module version. 55 | cd ${GOMOD_DIR}/testdata/gofumpt-external 56 | go install . 57 | cd ${WORK} 58 | stdin foo.go 59 | exec ${GOBIN}/gofumpt-external 60 | cmp stdout foo.go.golden-external 61 | 62 | -- go.mod -- 63 | module test 64 | 65 | go 1.16 66 | -- foo.go -- 67 | package p 68 | 69 | //gofumpt:diagnose 70 | -- outdated.go -- 71 | package p 72 | 73 | //gofumpt:diagnose v0.1.0 74 | -- nochange.go -- 75 | package p 76 | 77 | //gofumpt:diagnosefoobar 78 | -- foo.go.golden -- 79 | package p 80 | 81 | //gofumpt:diagnose version: (devel) (go1.18.29) flags: -lang=go1.16 -modpath=test 82 | -- foo.go.golden-extra -- 83 | package p 84 | 85 | //gofumpt:diagnose version: (devel) (go1.18.29) flags: -lang=go1.16 -modpath=test -extra=group_params,clothe_returns 86 | -- foo.go.golden-extra-false -- 87 | package p 88 | 89 | //gofumpt:diagnose version: (devel) (go1.18.29) flags: -lang=go1.16 -modpath=test 90 | -- foo.go.golden-extra-group-params -- 91 | package p 92 | 93 | //gofumpt:diagnose version: (devel) (go1.18.29) flags: -lang=go1.16 -modpath=test -extra=group_params 94 | -- foo.go.golden-lang -- 95 | package p 96 | 97 | //gofumpt:diagnose version: (devel) (go1.18.29) flags: -lang=go1 -modpath=test 98 | -- foo.go.golden-released -- 99 | package p 100 | 101 | //gofumpt:diagnose version: v0.8.1-0.20250831111522-b5fd2eb6e821 (go1.18.29) flags: -lang=go1.16 -modpath=test 102 | -- foo.go.golden-external -- 103 | package p 104 | 105 | //gofumpt:diagnose version: v0.8.1-0.20250831111522-b5fd2eb6e821 (go1.18.29) flags: -lang=go1.16 -modpath= 106 | -------------------------------------------------------------------------------- /format/rewrite.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package format 6 | 7 | import ( 8 | "go/ast" 9 | "go/token" 10 | "reflect" 11 | "unicode" 12 | "unicode/utf8" 13 | ) 14 | 15 | // Values/types for special cases. 16 | var ( 17 | identType = reflect.TypeOf((*ast.Ident)(nil)) 18 | objectPtrType = reflect.TypeOf((*ast.Object)(nil)) 19 | positionType = reflect.TypeOf(token.NoPos) 20 | callExprType = reflect.TypeOf((*ast.CallExpr)(nil)) 21 | ) 22 | 23 | func isWildcard(s string) bool { 24 | rune, size := utf8.DecodeRuneInString(s) 25 | return size == len(s) && unicode.IsLower(rune) 26 | } 27 | 28 | // match reports whether pattern matches val, 29 | // recording wildcard submatches in m. 30 | // If m == nil, match checks whether pattern == val. 31 | func match(m map[string]reflect.Value, pattern, val reflect.Value) bool { 32 | // Wildcard matches any expression. If it appears multiple 33 | // times in the pattern, it must match the same expression 34 | // each time. 35 | if m != nil && pattern.IsValid() && pattern.Type() == identType { 36 | name := pattern.Interface().(*ast.Ident).Name 37 | if isWildcard(name) && val.IsValid() { 38 | // wildcards only match valid (non-nil) expressions. 39 | if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() { 40 | if old, ok := m[name]; ok { 41 | return match(nil, old, val) 42 | } 43 | m[name] = val 44 | return true 45 | } 46 | } 47 | } 48 | 49 | // Otherwise, pattern and val must match recursively. 50 | if !pattern.IsValid() || !val.IsValid() { 51 | return !pattern.IsValid() && !val.IsValid() 52 | } 53 | if pattern.Type() != val.Type() { 54 | return false 55 | } 56 | 57 | // Special cases. 58 | switch pattern.Type() { 59 | case identType: 60 | // For identifiers, only the names need to match 61 | // (and none of the other *ast.Object information). 62 | // This is a common case, handle it all here instead 63 | // of recursing down any further via reflection. 64 | p := pattern.Interface().(*ast.Ident) 65 | v := val.Interface().(*ast.Ident) 66 | return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name 67 | case objectPtrType, positionType: 68 | // object pointers and token positions always match 69 | return true 70 | case callExprType: 71 | // For calls, the Ellipsis fields (token.Position) must 72 | // match since that is how f(x) and f(x...) are different. 73 | // Check them here but fall through for the remaining fields. 74 | p := pattern.Interface().(*ast.CallExpr) 75 | v := val.Interface().(*ast.CallExpr) 76 | if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() { 77 | return false 78 | } 79 | } 80 | 81 | p := reflect.Indirect(pattern) 82 | v := reflect.Indirect(val) 83 | if !p.IsValid() || !v.IsValid() { 84 | return !p.IsValid() && !v.IsValid() 85 | } 86 | 87 | switch p.Kind() { 88 | case reflect.Slice: 89 | if p.Len() != v.Len() { 90 | return false 91 | } 92 | for i := range p.Len() { 93 | if !match(m, p.Index(i), v.Index(i)) { 94 | return false 95 | } 96 | } 97 | return true 98 | 99 | case reflect.Struct: 100 | for i := range p.NumField() { 101 | if !match(m, p.Field(i), v.Field(i)) { 102 | return false 103 | } 104 | } 105 | return true 106 | 107 | case reflect.Interface: 108 | return match(m, p.Elem(), v.Elem()) 109 | } 110 | 111 | // Handle token integers, etc. 112 | return p.Interface() == v.Interface() 113 | } 114 | -------------------------------------------------------------------------------- /testdata/script/std-imports.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- go.mod -- 8 | module nodomainmod/mod1 9 | 10 | go 1.16 11 | -- foo.go -- 12 | package p 13 | 14 | import ( 15 | "io" 16 | "io/ioutil" // if the user keeps them in the top group, obey that 17 | _ "io/ioutil" 18 | 19 | _ "image/png" 20 | 21 | "bufio" // the above is for a side effect; this one has a comment 22 | ) 23 | 24 | import ( 25 | "os" 26 | 27 | "foo.local/one" 28 | 29 | bytes_ "bytes" 30 | 31 | "io" 32 | ) 33 | 34 | import ( 35 | "foo.local/two" 36 | 37 | "fmt" 38 | ) 39 | 40 | // If they are in order, but with extra newlines, join them. 41 | import ( 42 | "more" 43 | 44 | "std" 45 | ) 46 | 47 | // We need to split std vs non-std in this case too. 48 | import ( 49 | "foo.local" 50 | "foo.local/three" 51 | math "math" 52 | ) 53 | 54 | import ( 55 | "x" 56 | // don't mess up this comment 57 | "y" 58 | // or many 59 | // of them 60 | "z" 61 | ) 62 | 63 | // This used to crash gofumpt, as there's no space to insert an extra newline. 64 | import ( 65 | "std" 66 | "non.std/pkg" 67 | ) 68 | 69 | // All of the extra imports below are known to not belong in std. 70 | // For example/ and test/, see https://golang.org/issue/37641. 71 | import ( 72 | "io" 73 | 74 | "example/foo" 75 | "internal/bar" 76 | "test/baz" 77 | ) 78 | 79 | import ( 80 | "io" 81 | 82 | "nodomainmod" 83 | "nodomainmod/mod1/pkg1" 84 | "nodomainmod/mod2" 85 | "nodomainmodextra" 86 | ) 87 | 88 | import ( 89 | "io" 90 | 91 | "nodomainother/mod.withdot/pkg1" 92 | ) 93 | 94 | // TODO: fix issue 225. 95 | import ( 96 | "path/filepath" 97 | "time" 98 | "github.com/tinkerbell/tink/pkg/apis/core/v1alpha1" 99 | "k8s.io/apimachinery/pkg/types" 100 | "sigs.k8s.io/yaml" 101 | ) 102 | -- foo.go.golden -- 103 | package p 104 | 105 | import ( 106 | "io" 107 | "io/ioutil" // if the user keeps them in the top group, obey that 108 | _ "io/ioutil" 109 | 110 | _ "image/png" 111 | 112 | "bufio" // the above is for a side effect; this one has a comment 113 | ) 114 | 115 | import ( 116 | "io" 117 | "os" 118 | 119 | "foo.local/one" 120 | 121 | bytes_ "bytes" 122 | ) 123 | 124 | import ( 125 | "fmt" 126 | 127 | "foo.local/two" 128 | ) 129 | 130 | // If they are in order, but with extra newlines, join them. 131 | import ( 132 | "more" 133 | "std" 134 | ) 135 | 136 | // We need to split std vs non-std in this case too. 137 | import ( 138 | math "math" 139 | 140 | "foo.local" 141 | "foo.local/three" 142 | ) 143 | 144 | import ( 145 | "x" 146 | // don't mess up this comment 147 | "y" 148 | // or many 149 | // of them 150 | "z" 151 | ) 152 | 153 | // This used to crash gofumpt, as there's no space to insert an extra newline. 154 | import ( 155 | "std" 156 | 157 | "non.std/pkg" 158 | ) 159 | 160 | // All of the extra imports below are known to not belong in std. 161 | // For example/ and test/, see https://golang.org/issue/37641. 162 | import ( 163 | "internal/bar" 164 | "io" 165 | 166 | "example/foo" 167 | 168 | "test/baz" 169 | ) 170 | 171 | import ( 172 | "io" 173 | "nodomainmodextra" 174 | 175 | "nodomainmod" 176 | "nodomainmod/mod1/pkg1" 177 | "nodomainmod/mod2" 178 | ) 179 | 180 | import ( 181 | "io" 182 | "nodomainother/mod.withdot/pkg1" 183 | ) 184 | 185 | // TODO: fix issue 225. 186 | import ( 187 | "path/filepath" 188 | "time" 189 | 190 | "github.com/tinkerbell/tink/pkg/apis/core/v1alpha1" 191 | "k8s.io/apimachinery/pkg/types" 192 | 193 | "sigs.k8s.io/yaml" 194 | ) 195 | -------------------------------------------------------------------------------- /testdata/script/linedirectives.txtar: -------------------------------------------------------------------------------- 1 | # Line directives can throw off our use of MergeLines. 2 | # We should ignore them entirely when calculating line numbers. 3 | # The file below is borrowed from Go's test/dwarf/linedirectives.go. 4 | 5 | exec gofumpt -w foo.go 6 | cmp foo.go foo.go.golden 7 | 8 | -- foo.go -- 9 | // Copyright 2011 The Go Authors. All rights reserved. 10 | // Use of this source code is governed by a BSD-style 11 | // license that can be found in the LICENSE file. 12 | 13 | //line foo/bar.y:4 14 | package main 15 | //line foo/bar.y:60 16 | func main() { 17 | //line foo/bar.y:297 18 | f, l := 0, 0 19 | //line yacctab:1 20 | f, l = 1, 1 21 | //line yaccpar:1 22 | f, l = 2, 1 23 | //line foo/bar.y:82 24 | f, l = 3, 82 25 | //line foo/bar.y:90 26 | f, l = 3, 90 27 | //line foo/bar.y:92 28 | f, l = 3, 92 29 | //line foo/bar.y:100 30 | f, l = 3, 100 31 | //line foo/bar.y:104 32 | l = 104 33 | //line foo/bar.y:112 34 | l = 112 35 | //line foo/bar.y:117 36 | l = 117 37 | //line foo/bar.y:121 38 | l = 121 39 | //line foo/bar.y:125 40 | l = 125 41 | //line foo/bar.y:133 42 | l = 133 43 | //line foo/bar.y:146 44 | l = 146 45 | //line foo/bar.y:148 46 | //line foo/bar.y:153 47 | //line foo/bar.y:155 48 | l = 155 49 | //line foo/bar.y:160 50 | 51 | //line foo/bar.y:164 52 | //line foo/bar.y:173 53 | 54 | //line foo/bar.y:178 55 | //line foo/bar.y:180 56 | //line foo/bar.y:185 57 | //line foo/bar.y:195 58 | //line foo/bar.y:197 59 | //line foo/bar.y:202 60 | //line foo/bar.y:204 61 | //line foo/bar.y:208 62 | //line foo/bar.y:211 63 | //line foo/bar.y:213 64 | //line foo/bar.y:215 65 | //line foo/bar.y:217 66 | //line foo/bar.y:221 67 | //line foo/bar.y:229 68 | //line foo/bar.y:236 69 | //line foo/bar.y:238 70 | //line foo/bar.y:240 71 | //line foo/bar.y:244 72 | //line foo/bar.y:249 73 | //line foo/bar.y:253 74 | //line foo/bar.y:257 75 | //line foo/bar.y:262 76 | //line foo/bar.y:267 77 | //line foo/bar.y:272 78 | if l == f { 79 | //line foo/bar.y:277 80 | panic("aie!") 81 | //line foo/bar.y:281 82 | } 83 | //line foo/bar.y:285 84 | return 85 | //line foo/bar.y:288 86 | //line foo/bar.y:290 87 | } 88 | //line foo/bar.y:293 89 | //line foo/bar.y:295 90 | -- foo.go.golden -- 91 | // Copyright 2011 The Go Authors. All rights reserved. 92 | // Use of this source code is governed by a BSD-style 93 | // license that can be found in the LICENSE file. 94 | 95 | //line foo/bar.y:4 96 | package main 97 | 98 | //line foo/bar.y:60 99 | func main() { 100 | //line foo/bar.y:297 101 | f, l := 0, 0 102 | //line yacctab:1 103 | f, l = 1, 1 104 | //line yaccpar:1 105 | f, l = 2, 1 106 | //line foo/bar.y:82 107 | f, l = 3, 82 108 | //line foo/bar.y:90 109 | f, l = 3, 90 110 | //line foo/bar.y:92 111 | f, l = 3, 92 112 | //line foo/bar.y:100 113 | f, l = 3, 100 114 | //line foo/bar.y:104 115 | l = 104 116 | //line foo/bar.y:112 117 | l = 112 118 | //line foo/bar.y:117 119 | l = 117 120 | //line foo/bar.y:121 121 | l = 121 122 | //line foo/bar.y:125 123 | l = 125 124 | //line foo/bar.y:133 125 | l = 133 126 | //line foo/bar.y:146 127 | l = 146 128 | //line foo/bar.y:148 129 | //line foo/bar.y:153 130 | //line foo/bar.y:155 131 | l = 155 132 | //line foo/bar.y:160 133 | 134 | //line foo/bar.y:164 135 | //line foo/bar.y:173 136 | 137 | //line foo/bar.y:178 138 | //line foo/bar.y:180 139 | //line foo/bar.y:185 140 | //line foo/bar.y:195 141 | //line foo/bar.y:197 142 | //line foo/bar.y:202 143 | //line foo/bar.y:204 144 | //line foo/bar.y:208 145 | //line foo/bar.y:211 146 | //line foo/bar.y:213 147 | //line foo/bar.y:215 148 | //line foo/bar.y:217 149 | //line foo/bar.y:221 150 | //line foo/bar.y:229 151 | //line foo/bar.y:236 152 | //line foo/bar.y:238 153 | //line foo/bar.y:240 154 | //line foo/bar.y:244 155 | //line foo/bar.y:249 156 | //line foo/bar.y:253 157 | //line foo/bar.y:257 158 | //line foo/bar.y:262 159 | //line foo/bar.y:267 160 | //line foo/bar.y:272 161 | if l == f { 162 | //line foo/bar.y:277 163 | panic("aie!") 164 | //line foo/bar.y:281 165 | } 166 | //line foo/bar.y:285 167 | return 168 | //line foo/bar.y:288 169 | //line foo/bar.y:290 170 | } 171 | 172 | //line foo/bar.y:293 173 | //line foo/bar.y:295 174 | -------------------------------------------------------------------------------- /internal/govendor/go/doc/comment/html.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package comment 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "strconv" 11 | ) 12 | 13 | // An htmlPrinter holds the state needed for printing a [Doc] as HTML. 14 | type htmlPrinter struct { 15 | *Printer 16 | tight bool 17 | } 18 | 19 | // HTML returns an HTML formatting of the [Doc]. 20 | // See the [Printer] documentation for ways to customize the HTML output. 21 | func (p *Printer) HTML(d *Doc) []byte { 22 | hp := &htmlPrinter{Printer: p} 23 | var out bytes.Buffer 24 | for _, x := range d.Content { 25 | hp.block(&out, x) 26 | } 27 | return out.Bytes() 28 | } 29 | 30 | // block prints the block x to out. 31 | func (p *htmlPrinter) block(out *bytes.Buffer, x Block) { 32 | switch x := x.(type) { 33 | default: 34 | fmt.Fprintf(out, "?%T", x) 35 | 36 | case *Paragraph: 37 | if !p.tight { 38 | out.WriteString("

") 39 | } 40 | p.text(out, x.Text) 41 | out.WriteString("\n") 42 | 43 | case *Heading: 44 | out.WriteString("") 53 | p.text(out, x.Text) 54 | out.WriteString("\n") 57 | 58 | case *Code: 59 | out.WriteString("

")
 60 | 		p.escape(out, x.Text)
 61 | 		out.WriteString("
\n") 62 | 63 | case *List: 64 | kind := "ol>\n" 65 | if x.Items[0].Number == "" { 66 | kind = "ul>\n" 67 | } 68 | out.WriteString("<") 69 | out.WriteString(kind) 70 | next := "1" 71 | for _, item := range x.Items { 72 | out.WriteString("") 83 | p.tight = !x.BlankBetween() 84 | for _, blk := range item.Content { 85 | p.block(out, blk) 86 | } 87 | p.tight = false 88 | } 89 | out.WriteString("= 0; i-- { 99 | if b[i] < '9' { 100 | b[i]++ 101 | return string(b) 102 | } 103 | b[i] = '0' 104 | } 105 | return "1" + string(b) 106 | } 107 | 108 | // text prints the text sequence x to out. 109 | func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) { 110 | for _, t := range x { 111 | switch t := t.(type) { 112 | case Plain: 113 | p.escape(out, string(t)) 114 | case Italic: 115 | out.WriteString("") 116 | p.escape(out, string(t)) 117 | out.WriteString("") 118 | case *Link: 119 | out.WriteString(``) 122 | p.text(out, t.Text) 123 | out.WriteString("") 124 | case *DocLink: 125 | url := p.docLinkURL(t) 126 | if url != "" { 127 | out.WriteString(``) 130 | } 131 | p.text(out, t.Text) 132 | if url != "" { 133 | out.WriteString("") 134 | } 135 | } 136 | } 137 | } 138 | 139 | // escape prints s to out as plain text, 140 | // escaping < & " ' and > to avoid being misinterpreted 141 | // in larger HTML constructs. 142 | func (p *htmlPrinter) escape(out *bytes.Buffer, s string) { 143 | start := 0 144 | for i := 0; i < len(s); i++ { 145 | switch s[i] { 146 | case '<': 147 | out.WriteString(s[start:i]) 148 | out.WriteString("<") 149 | start = i + 1 150 | case '&': 151 | out.WriteString(s[start:i]) 152 | out.WriteString("&") 153 | start = i + 1 154 | case '"': 155 | out.WriteString(s[start:i]) 156 | out.WriteString(""") 157 | start = i + 1 158 | case '\'': 159 | out.WriteString(s[start:i]) 160 | out.WriteString("'") 161 | start = i + 1 162 | case '>': 163 | out.WriteString(s[start:i]) 164 | out.WriteString(">") 165 | start = i + 1 166 | } 167 | } 168 | out.WriteString(s[start:]) 169 | } 170 | -------------------------------------------------------------------------------- /internal/govendor/go/printer/comment.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package printer 6 | 7 | import ( 8 | "go/ast" 9 | "strings" 10 | 11 | "mvdan.cc/gofumpt/internal/govendor/go/doc/comment" 12 | ) 13 | 14 | // formatDocComment reformats the doc comment list, 15 | // returning the canonical formatting. 16 | func formatDocComment(list []*ast.Comment) []*ast.Comment { 17 | // Extract comment text (removing comment markers). 18 | var kind, text string 19 | var directives []*ast.Comment 20 | if len(list) == 1 && strings.HasPrefix(list[0].Text, "/*") { 21 | kind = "/*" 22 | text = list[0].Text 23 | if !strings.Contains(text, "\n") || allStars(text) { 24 | // Single-line /* .. */ comment in doc comment position, 25 | // or multiline old-style comment like 26 | // /* 27 | // * Comment 28 | // * text here. 29 | // */ 30 | // Should not happen, since it will not work well as a 31 | // doc comment, but if it does, just ignore: 32 | // reformatting it will only make the situation worse. 33 | return list 34 | } 35 | text = text[2 : len(text)-2] // cut /* and */ 36 | } else if strings.HasPrefix(list[0].Text, "//") { 37 | kind = "//" 38 | var b strings.Builder 39 | for _, c := range list { 40 | after, found := strings.CutPrefix(c.Text, "//") 41 | if !found { 42 | return list 43 | } 44 | // Accumulate //go:build etc lines separately. 45 | if isDirective(after) { 46 | directives = append(directives, c) 47 | continue 48 | } 49 | b.WriteString(strings.TrimPrefix(after, " ")) 50 | b.WriteString("\n") 51 | } 52 | text = b.String() 53 | } else { 54 | // Not sure what this is, so leave alone. 55 | return list 56 | } 57 | 58 | if text == "" { 59 | return list 60 | } 61 | 62 | // Parse comment and reformat as text. 63 | var p comment.Parser 64 | d := p.Parse(text) 65 | 66 | var pr comment.Printer 67 | text = string(pr.Comment(d)) 68 | 69 | // For /* */ comment, return one big comment with text inside. 70 | slash := list[0].Slash 71 | if kind == "/*" { 72 | c := &ast.Comment{ 73 | Slash: slash, 74 | Text: "/*\n" + text + "*/", 75 | } 76 | return []*ast.Comment{c} 77 | } 78 | 79 | // For // comment, return sequence of // lines. 80 | var out []*ast.Comment 81 | for text != "" { 82 | var line string 83 | line, text, _ = strings.Cut(text, "\n") 84 | if line == "" { 85 | line = "//" 86 | } else if strings.HasPrefix(line, "\t") { 87 | line = "//" + line 88 | } else { 89 | line = "// " + line 90 | } 91 | out = append(out, &ast.Comment{ 92 | Slash: slash, 93 | Text: line, 94 | }) 95 | } 96 | if len(directives) > 0 { 97 | out = append(out, &ast.Comment{ 98 | Slash: slash, 99 | Text: "//", 100 | }) 101 | for _, c := range directives { 102 | out = append(out, &ast.Comment{ 103 | Slash: slash, 104 | Text: c.Text, 105 | }) 106 | } 107 | } 108 | return out 109 | } 110 | 111 | // isDirective reports whether c is a comment directive. 112 | // See go.dev/issue/37974. 113 | // This code is also in go/ast. 114 | func isDirective(c string) bool { 115 | // "//line " is a line directive. 116 | // "//extern " is for gccgo. 117 | // "//export " is for cgo. 118 | // (The // has been removed.) 119 | if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { 120 | return true 121 | } 122 | 123 | // "//[a-z0-9]+:[a-z0-9]" 124 | // (The // has been removed.) 125 | colon := strings.Index(c, ":") 126 | if colon <= 0 || colon+1 >= len(c) { 127 | return false 128 | } 129 | for i := 0; i <= colon+1; i++ { 130 | if i == colon { 131 | continue 132 | } 133 | b := c[i] 134 | if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { 135 | return false 136 | } 137 | } 138 | return true 139 | } 140 | 141 | // allStars reports whether text is the interior of an 142 | // old-style /* */ comment with a star at the start of each line. 143 | func allStars(text string) bool { 144 | for i := 0; i < len(text); i++ { 145 | if text[i] == '\n' { 146 | j := i + 1 147 | for j < len(text) && (text[j] == ' ' || text[j] == '\t') { 148 | j++ 149 | } 150 | if j < len(text) && text[j] != '*' { 151 | return false 152 | } 153 | } 154 | } 155 | return true 156 | } 157 | -------------------------------------------------------------------------------- /internal/govendor/go/format/format.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package format implements standard formatting of Go source. 6 | // 7 | // Note that formatting of Go source code changes over time, so tools relying on 8 | // consistent formatting should execute a specific version of the gofmt binary 9 | // instead of using this package. That way, the formatting will be stable, and 10 | // the tools won't need to be recompiled each time gofmt changes. 11 | // 12 | // For example, pre-submit checks that use this package directly would behave 13 | // differently depending on what Go version each developer uses, causing the 14 | // check to be inherently fragile. 15 | package format 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "go/ast" 21 | "go/parser" 22 | "go/token" 23 | "io" 24 | 25 | "mvdan.cc/gofumpt/internal/govendor/go/printer" 26 | ) 27 | 28 | // Keep these in sync with cmd/gofmt/gofmt.go. 29 | const ( 30 | tabWidth = 8 31 | printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers 32 | 33 | // printerNormalizeNumbers means to canonicalize number literal prefixes 34 | // and exponents while printing. See https://golang.org/doc/go1.13#gofmt. 35 | // 36 | // This value is defined in mvdan.cc/gofumpt/internal/govendor/go/printer specifically for mvdan.cc/gofumpt/internal/govendor/go/format and cmd/gofmt. 37 | printerNormalizeNumbers = 1 << 30 38 | ) 39 | 40 | var config = printer.Config{Mode: printerMode, Tabwidth: tabWidth} 41 | 42 | const parserMode = parser.ParseComments | parser.SkipObjectResolution 43 | 44 | // Node formats node in canonical gofmt style and writes the result to dst. 45 | // 46 | // The node type must be *[ast.File], *[printer.CommentedNode], [][ast.Decl], 47 | // [][ast.Stmt], or assignment-compatible to [ast.Expr], [ast.Decl], [ast.Spec], 48 | // or [ast.Stmt]. Node does not modify node. Imports are not sorted for 49 | // nodes representing partial source files (for instance, if the node is 50 | // not an *[ast.File] or a *[printer.CommentedNode] not wrapping an *[ast.File]). 51 | // 52 | // The function may return early (before the entire result is written) 53 | // and return a formatting error, for instance due to an incorrect AST. 54 | func Node(dst io.Writer, fset *token.FileSet, node any) error { 55 | // Determine if we have a complete source file (file != nil). 56 | var file *ast.File 57 | var cnode *printer.CommentedNode 58 | switch n := node.(type) { 59 | case *ast.File: 60 | file = n 61 | case *printer.CommentedNode: 62 | if f, ok := n.Node.(*ast.File); ok { 63 | file = f 64 | cnode = n 65 | } 66 | } 67 | 68 | // Sort imports if necessary. 69 | if file != nil && hasUnsortedImports(file) { 70 | // Make a copy of the AST because ast.SortImports is destructive. 71 | // TODO(gri) Do this more efficiently. 72 | var buf bytes.Buffer 73 | err := config.Fprint(&buf, fset, file) 74 | if err != nil { 75 | return err 76 | } 77 | file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode) 78 | if err != nil { 79 | // We should never get here. If we do, provide good diagnostic. 80 | return fmt.Errorf("format.Node internal error (%s)", err) 81 | } 82 | ast.SortImports(fset, file) 83 | 84 | // Use new file with sorted imports. 85 | node = file 86 | if cnode != nil { 87 | node = &printer.CommentedNode{Node: file, Comments: cnode.Comments} 88 | } 89 | } 90 | 91 | return config.Fprint(dst, fset, node) 92 | } 93 | 94 | // Source formats src in canonical gofmt style and returns the result 95 | // or an (I/O or syntax) error. src is expected to be a syntactically 96 | // correct Go source file, or a list of Go declarations or statements. 97 | // 98 | // If src is a partial source file, the leading and trailing space of src 99 | // is applied to the result (such that it has the same leading and trailing 100 | // space as src), and the result is indented by the same amount as the first 101 | // line of src containing code. Imports are not sorted for partial source files. 102 | func Source(src []byte) ([]byte, error) { 103 | fset := token.NewFileSet() 104 | file, sourceAdj, indentAdj, err := parse(fset, "", src, true) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | if sourceAdj == nil { 110 | // Complete source file. 111 | // TODO(gri) consider doing this always. 112 | ast.SortImports(fset, file) 113 | } 114 | 115 | return format(fset, file, sourceAdj, indentAdj, src, config) 116 | } 117 | 118 | func hasUnsortedImports(file *ast.File) bool { 119 | for _, d := range file.Decls { 120 | d, ok := d.(*ast.GenDecl) 121 | if !ok || d.Tok != token.IMPORT { 122 | // Not an import declaration, so we're done. 123 | // Imports are always first. 124 | return false 125 | } 126 | if d.Lparen.IsValid() { 127 | // For now assume all grouped imports are unsorted. 128 | // TODO(gri) Should check if they are sorted already. 129 | return true 130 | } 131 | // Ungrouped imports are sorted by default. 132 | } 133 | return false 134 | } 135 | -------------------------------------------------------------------------------- /internal/govendor/go/doc/comment/markdown.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package comment 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "strings" 11 | ) 12 | 13 | // An mdPrinter holds the state needed for printing a Doc as Markdown. 14 | type mdPrinter struct { 15 | *Printer 16 | headingPrefix string 17 | raw bytes.Buffer 18 | } 19 | 20 | // Markdown returns a Markdown formatting of the Doc. 21 | // See the [Printer] documentation for ways to customize the Markdown output. 22 | func (p *Printer) Markdown(d *Doc) []byte { 23 | mp := &mdPrinter{ 24 | Printer: p, 25 | headingPrefix: strings.Repeat("#", p.headingLevel()) + " ", 26 | } 27 | 28 | var out bytes.Buffer 29 | for i, x := range d.Content { 30 | if i > 0 { 31 | out.WriteByte('\n') 32 | } 33 | mp.block(&out, x) 34 | } 35 | return out.Bytes() 36 | } 37 | 38 | // block prints the block x to out. 39 | func (p *mdPrinter) block(out *bytes.Buffer, x Block) { 40 | switch x := x.(type) { 41 | default: 42 | fmt.Fprintf(out, "?%T", x) 43 | 44 | case *Paragraph: 45 | p.text(out, x.Text) 46 | out.WriteString("\n") 47 | 48 | case *Heading: 49 | out.WriteString(p.headingPrefix) 50 | p.text(out, x.Text) 51 | if id := p.headingID(x); id != "" { 52 | out.WriteString(" {#") 53 | out.WriteString(id) 54 | out.WriteString("}") 55 | } 56 | out.WriteString("\n") 57 | 58 | case *Code: 59 | md := x.Text 60 | for md != "" { 61 | var line string 62 | line, md, _ = strings.Cut(md, "\n") 63 | if line != "" { 64 | out.WriteString("\t") 65 | out.WriteString(line) 66 | } 67 | out.WriteString("\n") 68 | } 69 | 70 | case *List: 71 | loose := x.BlankBetween() 72 | for i, item := range x.Items { 73 | if i > 0 && loose { 74 | out.WriteString("\n") 75 | } 76 | if n := item.Number; n != "" { 77 | out.WriteString(" ") 78 | out.WriteString(n) 79 | out.WriteString(". ") 80 | } else { 81 | out.WriteString(" - ") // SP SP - SP 82 | } 83 | for i, blk := range item.Content { 84 | const fourSpace = " " 85 | if i > 0 { 86 | out.WriteString("\n" + fourSpace) 87 | } 88 | p.text(out, blk.(*Paragraph).Text) 89 | out.WriteString("\n") 90 | } 91 | } 92 | } 93 | } 94 | 95 | // text prints the text sequence x to out. 96 | func (p *mdPrinter) text(out *bytes.Buffer, x []Text) { 97 | p.raw.Reset() 98 | p.rawText(&p.raw, x) 99 | line := bytes.TrimSpace(p.raw.Bytes()) 100 | if len(line) == 0 { 101 | return 102 | } 103 | switch line[0] { 104 | case '+', '-', '*', '#': 105 | // Escape what would be the start of an unordered list or heading. 106 | out.WriteByte('\\') 107 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 108 | i := 1 109 | for i < len(line) && '0' <= line[i] && line[i] <= '9' { 110 | i++ 111 | } 112 | if i < len(line) && (line[i] == '.' || line[i] == ')') { 113 | // Escape what would be the start of an ordered list. 114 | out.Write(line[:i]) 115 | out.WriteByte('\\') 116 | line = line[i:] 117 | } 118 | } 119 | out.Write(line) 120 | } 121 | 122 | // rawText prints the text sequence x to out, 123 | // without worrying about escaping characters 124 | // that have special meaning at the start of a Markdown line. 125 | func (p *mdPrinter) rawText(out *bytes.Buffer, x []Text) { 126 | for _, t := range x { 127 | switch t := t.(type) { 128 | case Plain: 129 | p.escape(out, string(t)) 130 | case Italic: 131 | out.WriteString("*") 132 | p.escape(out, string(t)) 133 | out.WriteString("*") 134 | case *Link: 135 | out.WriteString("[") 136 | p.rawText(out, t.Text) 137 | out.WriteString("](") 138 | out.WriteString(t.URL) 139 | out.WriteString(")") 140 | case *DocLink: 141 | url := p.docLinkURL(t) 142 | if url != "" { 143 | out.WriteString("[") 144 | } 145 | p.rawText(out, t.Text) 146 | if url != "" { 147 | out.WriteString("](") 148 | url = strings.ReplaceAll(url, "(", "%28") 149 | url = strings.ReplaceAll(url, ")", "%29") 150 | out.WriteString(url) 151 | out.WriteString(")") 152 | } 153 | } 154 | } 155 | } 156 | 157 | // escape prints s to out as plain text, 158 | // escaping special characters to avoid being misinterpreted 159 | // as Markdown markup sequences. 160 | func (p *mdPrinter) escape(out *bytes.Buffer, s string) { 161 | start := 0 162 | for i := 0; i < len(s); i++ { 163 | switch s[i] { 164 | case '\n': 165 | // Turn all \n into spaces, for a few reasons: 166 | // - Avoid introducing paragraph breaks accidentally. 167 | // - Avoid the need to reindent after the newline. 168 | // - Avoid problems with Markdown renderers treating 169 | // every mid-paragraph newline as a
. 170 | out.WriteString(s[start:i]) 171 | out.WriteByte(' ') 172 | start = i + 1 173 | continue 174 | case '`', '_', '*', '[', '<', '\\': 175 | // Not all of these need to be escaped all the time, 176 | // but is valid and easy to do so. 177 | // We assume the Markdown is being passed to a 178 | // Markdown renderer, not edited by a person, 179 | // so it's fine to have escapes that are not strictly 180 | // necessary in some cases. 181 | out.WriteString(s[start:i]) 182 | out.WriteByte('\\') 183 | out.WriteByte(s[i]) 184 | start = i + 1 185 | } 186 | } 187 | out.WriteString(s[start:]) 188 | } 189 | -------------------------------------------------------------------------------- /internal/govendor/go/printer/gobuild.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package printer 6 | 7 | import ( 8 | "go/build/constraint" 9 | "slices" 10 | "text/tabwriter" 11 | ) 12 | 13 | func (p *printer) fixGoBuildLines() { 14 | if len(p.goBuild)+len(p.plusBuild) == 0 { 15 | return 16 | } 17 | 18 | // Find latest possible placement of //go:build and // +build comments. 19 | // That's just after the last blank line before we find a non-comment. 20 | // (We'll add another blank line after our comment block.) 21 | // When we start dropping // +build comments, we can skip over /* */ comments too. 22 | // Note that we are processing tabwriter input, so every comment 23 | // begins and ends with a tabwriter.Escape byte. 24 | // And some newlines have turned into \f bytes. 25 | insert := 0 26 | for pos := 0; ; { 27 | // Skip leading space at beginning of line. 28 | blank := true 29 | for pos < len(p.output) && (p.output[pos] == ' ' || p.output[pos] == '\t') { 30 | pos++ 31 | } 32 | // Skip over // comment if any. 33 | if pos+3 < len(p.output) && p.output[pos] == tabwriter.Escape && p.output[pos+1] == '/' && p.output[pos+2] == '/' { 34 | blank = false 35 | for pos < len(p.output) && !isNL(p.output[pos]) { 36 | pos++ 37 | } 38 | } 39 | // Skip over \n at end of line. 40 | if pos >= len(p.output) || !isNL(p.output[pos]) { 41 | break 42 | } 43 | pos++ 44 | 45 | if blank { 46 | insert = pos 47 | } 48 | } 49 | 50 | // If there is a //go:build comment before the place we identified, 51 | // use that point instead. (Earlier in the file is always fine.) 52 | if len(p.goBuild) > 0 && p.goBuild[0] < insert { 53 | insert = p.goBuild[0] 54 | } else if len(p.plusBuild) > 0 && p.plusBuild[0] < insert { 55 | insert = p.plusBuild[0] 56 | } 57 | 58 | var x constraint.Expr 59 | switch len(p.goBuild) { 60 | case 0: 61 | // Synthesize //go:build expression from // +build lines. 62 | for _, pos := range p.plusBuild { 63 | y, err := constraint.Parse(p.commentTextAt(pos)) 64 | if err != nil { 65 | x = nil 66 | break 67 | } 68 | if x == nil { 69 | x = y 70 | } else { 71 | x = &constraint.AndExpr{X: x, Y: y} 72 | } 73 | } 74 | case 1: 75 | // Parse //go:build expression. 76 | x, _ = constraint.Parse(p.commentTextAt(p.goBuild[0])) 77 | } 78 | 79 | var block []byte 80 | if x == nil { 81 | // Don't have a valid //go:build expression to treat as truth. 82 | // Bring all the lines together but leave them alone. 83 | // Note that these are already tabwriter-escaped. 84 | for _, pos := range p.goBuild { 85 | block = append(block, p.lineAt(pos)...) 86 | } 87 | for _, pos := range p.plusBuild { 88 | block = append(block, p.lineAt(pos)...) 89 | } 90 | } else { 91 | block = append(block, tabwriter.Escape) 92 | block = append(block, "//go:build "...) 93 | block = append(block, x.String()...) 94 | block = append(block, tabwriter.Escape, '\n') 95 | if len(p.plusBuild) > 0 { 96 | lines, err := constraint.PlusBuildLines(x) 97 | if err != nil { 98 | lines = []string{"// +build error: " + err.Error()} 99 | } 100 | for _, line := range lines { 101 | block = append(block, tabwriter.Escape) 102 | block = append(block, line...) 103 | block = append(block, tabwriter.Escape, '\n') 104 | } 105 | } 106 | } 107 | block = append(block, '\n') 108 | 109 | // Build sorted list of lines to delete from remainder of output. 110 | toDelete := append(p.goBuild, p.plusBuild...) 111 | slices.Sort(toDelete) 112 | 113 | // Collect output after insertion point, with lines deleted, into after. 114 | var after []byte 115 | start := insert 116 | for _, end := range toDelete { 117 | if end < start { 118 | continue 119 | } 120 | after = appendLines(after, p.output[start:end]) 121 | start = end + len(p.lineAt(end)) 122 | } 123 | after = appendLines(after, p.output[start:]) 124 | if n := len(after); n >= 2 && isNL(after[n-1]) && isNL(after[n-2]) { 125 | after = after[:n-1] 126 | } 127 | 128 | p.output = p.output[:insert] 129 | p.output = append(p.output, block...) 130 | p.output = append(p.output, after...) 131 | } 132 | 133 | // appendLines is like append(x, y...) 134 | // but it avoids creating doubled blank lines, 135 | // which would not be gofmt-standard output. 136 | // It assumes that only whole blocks of lines are being appended, 137 | // not line fragments. 138 | func appendLines(x, y []byte) []byte { 139 | if len(y) > 0 && isNL(y[0]) && // y starts in blank line 140 | (len(x) == 0 || len(x) >= 2 && isNL(x[len(x)-1]) && isNL(x[len(x)-2])) { // x is empty or ends in blank line 141 | y = y[1:] // delete y's leading blank line 142 | } 143 | return append(x, y...) 144 | } 145 | 146 | func (p *printer) lineAt(start int) []byte { 147 | pos := start 148 | for pos < len(p.output) && !isNL(p.output[pos]) { 149 | pos++ 150 | } 151 | if pos < len(p.output) { 152 | pos++ 153 | } 154 | return p.output[start:pos] 155 | } 156 | 157 | func (p *printer) commentTextAt(start int) string { 158 | if start < len(p.output) && p.output[start] == tabwriter.Escape { 159 | start++ 160 | } 161 | pos := start 162 | for pos < len(p.output) && p.output[pos] != tabwriter.Escape && !isNL(p.output[pos]) { 163 | pos++ 164 | } 165 | return string(p.output[start:pos]) 166 | } 167 | 168 | func isNL(b byte) bool { 169 | return b == '\n' || b == '\f' 170 | } 171 | -------------------------------------------------------------------------------- /format/simplify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package format 6 | 7 | import ( 8 | "go/ast" 9 | "go/token" 10 | "reflect" 11 | ) 12 | 13 | type simplifier struct{} 14 | 15 | func (s simplifier) Visit(node ast.Node) ast.Visitor { 16 | switch n := node.(type) { 17 | case *ast.CompositeLit: 18 | // array, slice, and map composite literals may be simplified 19 | outer := n 20 | var keyType, eltType ast.Expr 21 | switch typ := outer.Type.(type) { 22 | case *ast.ArrayType: 23 | eltType = typ.Elt 24 | case *ast.MapType: 25 | keyType = typ.Key 26 | eltType = typ.Value 27 | } 28 | 29 | if eltType != nil { 30 | var ktyp reflect.Value 31 | if keyType != nil { 32 | ktyp = reflect.ValueOf(keyType) 33 | } 34 | typ := reflect.ValueOf(eltType) 35 | for i, x := range outer.Elts { 36 | px := &outer.Elts[i] 37 | // look at value of indexed/named elements 38 | if t, ok := x.(*ast.KeyValueExpr); ok { 39 | if keyType != nil { 40 | s.simplifyLiteral(ktyp, keyType, t.Key, &t.Key) 41 | } 42 | x = t.Value 43 | px = &t.Value 44 | } 45 | s.simplifyLiteral(typ, eltType, x, px) 46 | } 47 | // node was simplified - stop walk (there are no subnodes to simplify) 48 | return nil 49 | } 50 | 51 | case *ast.SliceExpr: 52 | // a slice expression of the form: s[a:len(s)] 53 | // can be simplified to: s[a:] 54 | // if s is "simple enough" (for now we only accept identifiers) 55 | // 56 | // Note: This may not be correct because len may have been redeclared in 57 | // the same package. However, this is extremely unlikely and so far 58 | // (April 2022, after years of supporting this rewrite feature) 59 | // has never come up, so let's keep it working as is (see also #15153). 60 | // 61 | // Also note that this code used to use go/ast's object tracking, 62 | // which was removed in exchange for go/parser.Mode.SkipObjectResolution. 63 | // False positives are extremely unlikely as described above, 64 | // and go/ast's object tracking is incomplete in any case. 65 | if n.Max != nil { 66 | // - 3-index slices always require the 2nd and 3rd index 67 | break 68 | } 69 | if s, _ := n.X.(*ast.Ident); s != nil { 70 | // the array/slice object is a single identifier 71 | if call, _ := n.High.(*ast.CallExpr); call != nil && len(call.Args) == 1 && !call.Ellipsis.IsValid() { 72 | // the high expression is a function call with a single argument 73 | if fun, _ := call.Fun.(*ast.Ident); fun != nil && fun.Name == "len" { 74 | // the function called is "len" 75 | if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Name == s.Name { 76 | // the len argument is the array/slice object 77 | n.High = nil 78 | } 79 | } 80 | } 81 | } 82 | // Note: We could also simplify slice expressions of the form s[0:b] to s[:b] 83 | // but we leave them as is since sometimes we want to be very explicit 84 | // about the lower bound. 85 | // An example where the 0 helps: 86 | // x, y, z := b[0:2], b[2:4], b[4:6] 87 | // An example where it does not: 88 | // x, y := b[:n], b[n:] 89 | 90 | case *ast.RangeStmt: 91 | // - a range of the form: for x, _ = range v {...} 92 | // can be simplified to: for x = range v {...} 93 | // - a range of the form: for _ = range v {...} 94 | // can be simplified to: for range v {...} 95 | if isBlank(n.Value) { 96 | n.Value = nil 97 | } 98 | if isBlank(n.Key) && n.Value == nil { 99 | n.Key = nil 100 | } 101 | } 102 | 103 | return s 104 | } 105 | 106 | func (s simplifier) simplifyLiteral(typ reflect.Value, astType, x ast.Expr, px *ast.Expr) { 107 | ast.Walk(s, x) // simplify x 108 | 109 | // if the element is a composite literal and its literal type 110 | // matches the outer literal's element type exactly, the inner 111 | // literal type may be omitted 112 | if inner, ok := x.(*ast.CompositeLit); ok { 113 | if match(nil, typ, reflect.ValueOf(inner.Type)) { 114 | inner.Type = nil 115 | } 116 | } 117 | // if the outer literal's element type is a pointer type *T 118 | // and the element is & of a composite literal of type T, 119 | // the inner &T may be omitted. 120 | if ptr, ok := astType.(*ast.StarExpr); ok { 121 | if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND { 122 | if inner, ok := addr.X.(*ast.CompositeLit); ok { 123 | if match(nil, reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) { 124 | inner.Type = nil // drop T 125 | *px = inner // drop & 126 | } 127 | } 128 | } 129 | } 130 | } 131 | 132 | func isBlank(x ast.Expr) bool { 133 | ident, ok := x.(*ast.Ident) 134 | return ok && ident.Name == "_" 135 | } 136 | 137 | func simplify(f *ast.File) { 138 | // remove empty declarations such as "const ()", etc 139 | removeEmptyDeclGroups(f) 140 | 141 | var s simplifier 142 | ast.Walk(s, f) 143 | } 144 | 145 | func removeEmptyDeclGroups(f *ast.File) { 146 | i := 0 147 | for _, d := range f.Decls { 148 | if g, ok := d.(*ast.GenDecl); !ok || !isEmpty(f, g) { 149 | f.Decls[i] = d 150 | i++ 151 | } 152 | } 153 | f.Decls = f.Decls[:i] 154 | } 155 | 156 | func isEmpty(f *ast.File, g *ast.GenDecl) bool { 157 | if g.Doc != nil || g.Specs != nil { 158 | return false 159 | } 160 | 161 | for _, c := range f.Comments { 162 | // if there is a comment in the declaration, it is not considered empty 163 | if g.Pos() <= c.Pos() && c.End() <= g.End() { 164 | return false 165 | } 166 | } 167 | 168 | return true 169 | } 170 | -------------------------------------------------------------------------------- /internal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // TODO(gri): This file and the file src/go/format/internal.go are 6 | // the same (but for this comment and the package name). Do not modify 7 | // one without the other. Determine if we can factor out functionality 8 | // in a public API. See also #11844 for context. 9 | 10 | package main 11 | 12 | import ( 13 | "bytes" 14 | "go/ast" 15 | "go/parser" 16 | "go/token" 17 | "strings" 18 | 19 | "mvdan.cc/gofumpt/internal/govendor/go/printer" 20 | ) 21 | 22 | // parse parses src, which was read from the named file, 23 | // as a Go source file, declaration, or statement list. 24 | func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( 25 | file *ast.File, 26 | sourceAdj func(src []byte, indent int) []byte, 27 | indentAdj int, 28 | err error, 29 | ) { 30 | // Try as whole source file. 31 | file, err = parser.ParseFile(fset, filename, src, parserMode) 32 | // If there's no error, return. If the error is that the source file didn't begin with a 33 | // package line and source fragments are ok, fall through to 34 | // try as a source fragment. Stop and return on any other error. 35 | if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") { 36 | return file, sourceAdj, indentAdj, err 37 | } 38 | 39 | // If this is a declaration list, make it a source file 40 | // by inserting a package clause. 41 | // Insert using a ';', not a newline, so that the line numbers 42 | // in psrc match the ones in src. 43 | psrc := append([]byte("package p;"), src...) 44 | file, err = parser.ParseFile(fset, filename, psrc, parserMode) 45 | if err == nil { 46 | sourceAdj = func(src []byte, indent int) []byte { 47 | // Remove the package clause. 48 | // Gofmt has turned the ';' into a '\n'. 49 | src = src[indent+len("package p\n"):] 50 | return bytes.TrimSpace(src) 51 | } 52 | return file, sourceAdj, indentAdj, err 53 | } 54 | // If the error is that the source file didn't begin with a 55 | // declaration, fall through to try as a statement list. 56 | // Stop and return on any other error. 57 | if !strings.Contains(err.Error(), "expected declaration") { 58 | return file, sourceAdj, indentAdj, err 59 | } 60 | 61 | // If this is a statement list, make it a source file 62 | // by inserting a package clause and turning the list 63 | // into a function body. This handles expressions too. 64 | // Insert using a ';', not a newline, so that the line numbers 65 | // in fsrc match the ones in src. Add an extra '\n' before the '}' 66 | // to make sure comments are flushed before the '}'. 67 | fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '\n', '}') 68 | file, err = parser.ParseFile(fset, filename, fsrc, parserMode) 69 | if err == nil { 70 | sourceAdj = func(src []byte, indent int) []byte { 71 | // Cap adjusted indent to zero. 72 | if indent < 0 { 73 | indent = 0 74 | } 75 | // Remove the wrapping. 76 | // Gofmt has turned the "; " into a "\n\n". 77 | // There will be two non-blank lines with indent, hence 2*indent. 78 | src = src[2*indent+len("package p\n\nfunc _() {"):] 79 | // Remove only the "}\n" suffix: remaining whitespaces will be trimmed anyway 80 | src = src[:len(src)-len("}\n")] 81 | return bytes.TrimSpace(src) 82 | } 83 | // Gofmt has also indented the function body one level. 84 | // Adjust that with indentAdj. 85 | indentAdj = -1 86 | } 87 | 88 | // Succeeded, or out of options. 89 | return file, sourceAdj, indentAdj, err 90 | } 91 | 92 | // format formats the given package file originally obtained from src 93 | // and adjusts the result based on the original source via sourceAdj 94 | // and indentAdj. 95 | func format( 96 | fset *token.FileSet, 97 | file *ast.File, 98 | sourceAdj func(src []byte, indent int) []byte, 99 | indentAdj int, 100 | src []byte, 101 | cfg printer.Config, 102 | ) ([]byte, error) { 103 | if sourceAdj == nil { 104 | // Complete source file. 105 | var buf bytes.Buffer 106 | err := cfg.Fprint(&buf, fset, file) 107 | if err != nil { 108 | return nil, err 109 | } 110 | return buf.Bytes(), nil 111 | } 112 | 113 | // Partial source file. 114 | // Determine and prepend leading space. 115 | i, j := 0, 0 116 | for j < len(src) && isSpace(src[j]) { 117 | if src[j] == '\n' { 118 | i = j + 1 // byte offset of last line in leading space 119 | } 120 | j++ 121 | } 122 | var res []byte 123 | res = append(res, src[:i]...) 124 | 125 | // Determine and prepend indentation of first code line. 126 | // Spaces are ignored unless there are no tabs, 127 | // in which case spaces count as one tab. 128 | indent := 0 129 | hasSpace := false 130 | for _, b := range src[i:j] { 131 | switch b { 132 | case ' ': 133 | hasSpace = true 134 | case '\t': 135 | indent++ 136 | } 137 | } 138 | if indent == 0 && hasSpace { 139 | indent = 1 140 | } 141 | for range indent { 142 | res = append(res, '\t') 143 | } 144 | 145 | // Format the source. 146 | // Write it without any leading and trailing space. 147 | cfg.Indent = indent + indentAdj 148 | var buf bytes.Buffer 149 | err := cfg.Fprint(&buf, fset, file) 150 | if err != nil { 151 | return nil, err 152 | } 153 | out := sourceAdj(buf.Bytes(), cfg.Indent) 154 | 155 | // If the adjusted output is empty, the source 156 | // was empty but (possibly) for white space. 157 | // The result is the incoming source. 158 | if len(out) == 0 { 159 | return src, nil 160 | } 161 | 162 | // Otherwise, append output to leading space. 163 | res = append(res, out...) 164 | 165 | // Determine and append trailing space. 166 | i = len(src) 167 | for i > 0 && isSpace(src[i-1]) { 168 | i-- 169 | } 170 | return append(res, src[i:]...), nil 171 | } 172 | 173 | // isSpace reports whether the byte is a space character. 174 | // isSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'. 175 | func isSpace(b byte) bool { 176 | return b == ' ' || b == '\t' || b == '\n' || b == '\r' 177 | } 178 | -------------------------------------------------------------------------------- /internal/govendor/go/format/internal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // TODO(gri): This file and the file src/cmd/gofmt/internal.go are 6 | // the same (but for this comment and the package name). Do not modify 7 | // one without the other. Determine if we can factor out functionality 8 | // in a public API. See also #11844 for context. 9 | 10 | package format 11 | 12 | import ( 13 | "bytes" 14 | "go/ast" 15 | "go/parser" 16 | "go/token" 17 | "strings" 18 | 19 | "mvdan.cc/gofumpt/internal/govendor/go/printer" 20 | ) 21 | 22 | // parse parses src, which was read from the named file, 23 | // as a Go source file, declaration, or statement list. 24 | func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( 25 | file *ast.File, 26 | sourceAdj func(src []byte, indent int) []byte, 27 | indentAdj int, 28 | err error, 29 | ) { 30 | // Try as whole source file. 31 | file, err = parser.ParseFile(fset, filename, src, parserMode) 32 | // If there's no error, return. If the error is that the source file didn't begin with a 33 | // package line and source fragments are ok, fall through to 34 | // try as a source fragment. Stop and return on any other error. 35 | if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") { 36 | return file, sourceAdj, indentAdj, err 37 | } 38 | 39 | // If this is a declaration list, make it a source file 40 | // by inserting a package clause. 41 | // Insert using a ';', not a newline, so that the line numbers 42 | // in psrc match the ones in src. 43 | psrc := append([]byte("package p;"), src...) 44 | file, err = parser.ParseFile(fset, filename, psrc, parserMode) 45 | if err == nil { 46 | sourceAdj = func(src []byte, indent int) []byte { 47 | // Remove the package clause. 48 | // Gofmt has turned the ';' into a '\n'. 49 | src = src[indent+len("package p\n"):] 50 | return bytes.TrimSpace(src) 51 | } 52 | return file, sourceAdj, indentAdj, err 53 | } 54 | // If the error is that the source file didn't begin with a 55 | // declaration, fall through to try as a statement list. 56 | // Stop and return on any other error. 57 | if !strings.Contains(err.Error(), "expected declaration") { 58 | return file, sourceAdj, indentAdj, err 59 | } 60 | 61 | // If this is a statement list, make it a source file 62 | // by inserting a package clause and turning the list 63 | // into a function body. This handles expressions too. 64 | // Insert using a ';', not a newline, so that the line numbers 65 | // in fsrc match the ones in src. Add an extra '\n' before the '}' 66 | // to make sure comments are flushed before the '}'. 67 | fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '\n', '}') 68 | file, err = parser.ParseFile(fset, filename, fsrc, parserMode) 69 | if err == nil { 70 | sourceAdj = func(src []byte, indent int) []byte { 71 | // Cap adjusted indent to zero. 72 | if indent < 0 { 73 | indent = 0 74 | } 75 | // Remove the wrapping. 76 | // Gofmt has turned the "; " into a "\n\n". 77 | // There will be two non-blank lines with indent, hence 2*indent. 78 | src = src[2*indent+len("package p\n\nfunc _() {"):] 79 | // Remove only the "}\n" suffix: remaining whitespaces will be trimmed anyway 80 | src = src[:len(src)-len("}\n")] 81 | return bytes.TrimSpace(src) 82 | } 83 | // Gofmt has also indented the function body one level. 84 | // Adjust that with indentAdj. 85 | indentAdj = -1 86 | } 87 | 88 | // Succeeded, or out of options. 89 | return file, sourceAdj, indentAdj, err 90 | } 91 | 92 | // format formats the given package file originally obtained from src 93 | // and adjusts the result based on the original source via sourceAdj 94 | // and indentAdj. 95 | func format( 96 | fset *token.FileSet, 97 | file *ast.File, 98 | sourceAdj func(src []byte, indent int) []byte, 99 | indentAdj int, 100 | src []byte, 101 | cfg printer.Config, 102 | ) ([]byte, error) { 103 | if sourceAdj == nil { 104 | // Complete source file. 105 | var buf bytes.Buffer 106 | err := cfg.Fprint(&buf, fset, file) 107 | if err != nil { 108 | return nil, err 109 | } 110 | return buf.Bytes(), nil 111 | } 112 | 113 | // Partial source file. 114 | // Determine and prepend leading space. 115 | i, j := 0, 0 116 | for j < len(src) && isSpace(src[j]) { 117 | if src[j] == '\n' { 118 | i = j + 1 // byte offset of last line in leading space 119 | } 120 | j++ 121 | } 122 | var res []byte 123 | res = append(res, src[:i]...) 124 | 125 | // Determine and prepend indentation of first code line. 126 | // Spaces are ignored unless there are no tabs, 127 | // in which case spaces count as one tab. 128 | indent := 0 129 | hasSpace := false 130 | for _, b := range src[i:j] { 131 | switch b { 132 | case ' ': 133 | hasSpace = true 134 | case '\t': 135 | indent++ 136 | } 137 | } 138 | if indent == 0 && hasSpace { 139 | indent = 1 140 | } 141 | for i := 0; i < indent; i++ { 142 | res = append(res, '\t') 143 | } 144 | 145 | // Format the source. 146 | // Write it without any leading and trailing space. 147 | cfg.Indent = indent + indentAdj 148 | var buf bytes.Buffer 149 | err := cfg.Fprint(&buf, fset, file) 150 | if err != nil { 151 | return nil, err 152 | } 153 | out := sourceAdj(buf.Bytes(), cfg.Indent) 154 | 155 | // If the adjusted output is empty, the source 156 | // was empty but (possibly) for white space. 157 | // The result is the incoming source. 158 | if len(out) == 0 { 159 | return src, nil 160 | } 161 | 162 | // Otherwise, append output to leading space. 163 | res = append(res, out...) 164 | 165 | // Determine and append trailing space. 166 | i = len(src) 167 | for i > 0 && isSpace(src[i-1]) { 168 | i-- 169 | } 170 | return append(res, src[i:]...), nil 171 | } 172 | 173 | // isSpace reports whether the byte is a space character. 174 | // isSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'. 175 | func isSpace(b byte) bool { 176 | return b == ' ' || b == '\t' || b == '\n' || b == '\r' 177 | } 178 | -------------------------------------------------------------------------------- /testdata/script/long-lines.txtar: -------------------------------------------------------------------------------- 1 | cp foo.go foo.go.orig 2 | 3 | exec gofumpt -w foo.go 4 | cmp foo.go foo.go.orig 5 | 6 | env GOFUMPT_SPLIT_LONG_LINES=on 7 | exec gofumpt -w foo.go 8 | cmp foo.go foo.go.golden 9 | 10 | exec gofumpt -d foo.go.golden 11 | ! stdout . 12 | 13 | -- foo.go -- 14 | package p 15 | 16 | func _() { 17 | if err := f(argument1, argument2, argument3, argument4, argument5, argument6, argument7, argument8, argument9, argument10); err != nil { 18 | panic(err) 19 | } 20 | 21 | // Tiny arguments to ensure the length calculation is right. 22 | if err := f(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); err != nil { 23 | panic(err) 24 | } 25 | 26 | // These wouldn't take significantly less horizontal space if split. 27 | f(x, "one single very very very very very very very very very very very very very very very very long literal") 28 | if err := f(x, "one single very very very very very very very very very very very very very very very very long literal"); err != nil { 29 | panic(err) 30 | } 31 | { 32 | { 33 | { 34 | { 35 | println("first", "one single very very very very very very very very very very very very very long literal") 36 | } 37 | } 38 | } 39 | } 40 | 41 | // Allow splitting at the start of sub-lists too. 42 | if err := f(argument1, argument2, argument3, argument4, someComplex{argument5, argument6, argument7, argument8, argument9, argument10}); err != nil { 43 | panic(err) 44 | } 45 | if err := f(argument1, argument2, argument3, argument4, &someComplex{argument5, argument6, argument7, argument8, argument9, argument10}); err != nil { 46 | panic(err) 47 | } 48 | if err := f(argument1, argument2, argument3, argument4, []someSlice{argument5, argument6, argument7, argument8, argument9, argument10}); err != nil { 49 | panic(err) 50 | } 51 | 52 | // Allow splitting "lists" of binary expressions. 53 | if boolean1 && boolean2 && boolean3 && boolean4 && boolean5 && boolean6 && boolean7 && boolean8 && boolean9 && boolean10 && boolean11 { 54 | } 55 | // Over 100, and we split in a way that doesn't break "len(" off. 56 | if boolean1 || boolean2 || boolean3 || boolean4 || len(someVeryLongVarName.SomeVeryLongSelector) > 0 { 57 | } 58 | } 59 | 60 | // Note that function declarations have a higher limit of 120. 61 | 62 | // This line goes beyond the limit of 120, but splitting it would leave the 63 | // following line with just 20 non-indentation characters. Not worth it. 64 | func LongButNotWorthSplitting(argument1, argument2, argument3, argument4, argument5, argument6, argument7, argument8, argument9 int) bool { 65 | } 66 | 67 | // This line goes well past the limit and it should be split. 68 | // Note that it has a nested func type in a parameter. 69 | func TooLongWithFuncParam(fn func(int) (int, error), argument1, argument2, argument3, argument4, argument5, argument6, argument7, argument8, argument9, argument10 int) bool { 70 | } 71 | 72 | // This is like LongButNotWorthSplitting, but with a func parameter. 73 | func LongButNotWorthSplitting2(fn func(int) (int, error), argument3, argument4, argument5, argument6, argument7, argument8, argument9 int) bool { 74 | } 75 | 76 | // Never split result parameter lists, as that could easily add confusion with 77 | // extra input parameters. 78 | func NeverSplitResults(argument1, argument2, argument3, argument4, argument5 int) (result1 int, result2, result3, result4, result5, result6, result7, result8 bool) { 79 | } 80 | -- foo.go.golden -- 81 | package p 82 | 83 | func _() { 84 | if err := f(argument1, argument2, argument3, argument4, argument5, argument6, argument7, 85 | argument8, argument9, argument10); err != nil { 86 | panic(err) 87 | } 88 | 89 | // Tiny arguments to ensure the length calculation is right. 90 | if err := f(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, 0, 91 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); err != nil { 92 | panic(err) 93 | } 94 | 95 | // These wouldn't take significantly less horizontal space if split. 96 | f(x, "one single very very very very very very very very very very very very very very very very long literal") 97 | if err := f(x, "one single very very very very very very very very very very very very very very very very long literal"); err != nil { 98 | panic(err) 99 | } 100 | { 101 | { 102 | { 103 | { 104 | println("first", "one single very very very very very very very very very very very very very long literal") 105 | } 106 | } 107 | } 108 | } 109 | 110 | // Allow splitting at the start of sub-lists too. 111 | if err := f(argument1, argument2, argument3, argument4, someComplex{ 112 | argument5, argument6, argument7, argument8, argument9, argument10, 113 | }); err != nil { 114 | panic(err) 115 | } 116 | if err := f(argument1, argument2, argument3, argument4, &someComplex{ 117 | argument5, argument6, argument7, argument8, argument9, argument10, 118 | }); err != nil { 119 | panic(err) 120 | } 121 | if err := f(argument1, argument2, argument3, argument4, []someSlice{ 122 | argument5, argument6, argument7, argument8, argument9, argument10, 123 | }); err != nil { 124 | panic(err) 125 | } 126 | 127 | // Allow splitting "lists" of binary expressions. 128 | if boolean1 && boolean2 && boolean3 && boolean4 && boolean5 && boolean6 && boolean7 && 129 | boolean8 && boolean9 && boolean10 && boolean11 { 130 | } 131 | // Over 100, and we split in a way that doesn't break "len(" off. 132 | if boolean1 || boolean2 || boolean3 || boolean4 || 133 | len(someVeryLongVarName.SomeVeryLongSelector) > 0 { 134 | } 135 | } 136 | 137 | // Note that function declarations have a higher limit of 120. 138 | 139 | // This line goes beyond the limit of 120, but splitting it would leave the 140 | // following line with just 20 non-indentation characters. Not worth it. 141 | func LongButNotWorthSplitting(argument1, argument2, argument3, argument4, argument5, argument6, argument7, argument8, argument9 int) bool { 142 | } 143 | 144 | // This line goes well past the limit and it should be split. 145 | // Note that it has a nested func type in a parameter. 146 | func TooLongWithFuncParam(fn func(int) (int, error), argument1, argument2, argument3, argument4, 147 | argument5, argument6, argument7, argument8, argument9, argument10 int) bool { 148 | } 149 | 150 | // This is like LongButNotWorthSplitting, but with a func parameter. 151 | func LongButNotWorthSplitting2(fn func(int) (int, error), argument3, argument4, argument5, argument6, argument7, argument8, argument9 int) bool { 152 | } 153 | 154 | // Never split result parameter lists, as that could easily add confusion with 155 | // extra input parameters. 156 | func NeverSplitResults(argument1, argument2, argument3, argument4, argument5 int) (result1 int, result2, result3, result4, result5, result6, result7, result8 bool) { 157 | } 158 | -------------------------------------------------------------------------------- /testdata/script/func-newlines.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt -w foo.go 2 | cmp foo.go foo.go.golden 3 | 4 | exec gofumpt -d foo.go.golden 5 | ! stdout . 6 | 7 | -- foo.go -- 8 | package p 9 | 10 | func f1() { 11 | 12 | println("multiple") 13 | 14 | println("statements") 15 | 16 | } 17 | 18 | func f2() { 19 | 20 | // comment directly before 21 | println() 22 | 23 | // comment after 24 | 25 | } 26 | 27 | func _() { 28 | f3 := func() { 29 | 30 | println() 31 | 32 | } 33 | } 34 | 35 | func multilineParams(p1 string, 36 | p2 string) { 37 | 38 | println("body") 39 | 40 | } 41 | 42 | func multilineParamsUnambiguous(p1 string, 43 | p2 string, 44 | ) { 45 | 46 | println("body") 47 | 48 | } 49 | 50 | func multilineParamsListNoReturn( 51 | p1 string, 52 | p2 string, 53 | ) { 54 | 55 | println("body") 56 | 57 | } 58 | 59 | func multilineParamsListReturningNamedSingleValue( 60 | p1 string, 61 | p2 string, 62 | ) (err error) { 63 | 64 | println("body") 65 | return err 66 | 67 | } 68 | 69 | func multilineParamsListReturningSingleValue( 70 | p1 string, 71 | p2 string, 72 | ) error { 73 | 74 | println("body") 75 | return nil 76 | 77 | } 78 | 79 | func multilineParamsListReturningNamedMultiValues( 80 | p1 string, 81 | p2 string, 82 | ) (s string, err error) { 83 | 84 | println("body") 85 | return s, err 86 | 87 | } 88 | 89 | func multilineParamsListReturningMultiValues( 90 | p1 string, 91 | p2 string, 92 | ) (string, error) { 93 | 94 | println("body") 95 | return "", nil 96 | 97 | } 98 | 99 | func multilineParamsListReturningNamedMultiLineValuesList( 100 | p1 string, 101 | p2 string, 102 | ) ( 103 | s string, 104 | err error, 105 | ) { 106 | 107 | println("body") 108 | return s, err 109 | 110 | } 111 | 112 | func multilineParamsListReturningMultiLineValues( 113 | p1 string, 114 | p2 string, 115 | ) ( 116 | string, 117 | error, 118 | ) { 119 | 120 | println("body") 121 | return "", nil 122 | 123 | } 124 | 125 | func multilineParamsOneParamNoReturn( 126 | p1 string, 127 | ) { 128 | 129 | println("body") 130 | 131 | } 132 | 133 | func multilineParamsOneParamReturningNamedSingleValue( 134 | p1 string, 135 | ) (err error) { 136 | 137 | println("body") 138 | return err 139 | 140 | } 141 | 142 | func multilineParamsOneParamReturningSingleValue( 143 | p1 string, 144 | ) error { 145 | 146 | println("body") 147 | return nil 148 | 149 | } 150 | 151 | func multilineParamsOneParamReturningNamedMultiValues( 152 | p1 string, 153 | ) (s string, err error) { 154 | 155 | println("body") 156 | return s, err 157 | 158 | } 159 | 160 | func multilineParamsOneParamReturningMultiValues( 161 | p1 string, 162 | ) (string, error) { 163 | 164 | println("body") 165 | return "", nil 166 | 167 | } 168 | 169 | func multilineParamsOneParamReturningNamedMultiLineValuesList( 170 | p1 string, 171 | ) ( 172 | s string, 173 | err error, 174 | ) { 175 | 176 | println("body") 177 | return s, err 178 | 179 | } 180 | 181 | func multilineParamsOneParamReturningMultiLineValues( 182 | p1 string, 183 | ) ( 184 | string, 185 | error, 186 | ) { 187 | 188 | println("body") 189 | return "", nil 190 | 191 | } 192 | 193 | func multilineResults() (p1 string, 194 | p2 string) { 195 | 196 | println("body") 197 | 198 | } 199 | 200 | func multilineResultsUnambiguous() (p1 string, 201 | p2 string, 202 | ) { 203 | 204 | println("body") 205 | 206 | } 207 | 208 | func multilineNoFields( 209 | ) { 210 | 211 | println("body") 212 | 213 | } 214 | 215 | func f( 216 | foo int, 217 | bar string, 218 | /* baz */) { 219 | 220 | body() 221 | } 222 | 223 | func f2( 224 | foo int, 225 | bar string, 226 | ) ( 227 | string, 228 | error, 229 | /* baz */) { 230 | 231 | return "", nil 232 | } 233 | 234 | func multilineResultsMultipleEmptyLines() (p1 string, 235 | p2 string) { 236 | 237 | 238 | println("body") 239 | 240 | } 241 | 242 | func multilineParamsWithoutEmptyLine(p1 string, 243 | p2 string) { 244 | println("body") 245 | } 246 | 247 | func multilineParamsWithoutEmptyLineWithComment(p1 string, 248 | p2 string) { 249 | // comment 250 | println("body") 251 | } 252 | 253 | // Same as the others above, but with a single result parameter without 254 | // parentheses. This used to cause token.File.Offset crashes. 255 | func f(p1 string, 256 | p2 string) int { 257 | 258 | println("body") 259 | return 0 260 | } 261 | 262 | func a() { 263 | f := func(s string, 264 | b bool, 265 | ) { 266 | // foo 267 | } 268 | } 269 | 270 | func f(p1 string, 271 | p2 string) (int, string, 272 | /* baz */) { 273 | 274 | println("body") 275 | return 0, "" 276 | } 277 | -- foo.go.golden -- 278 | package p 279 | 280 | func f1() { 281 | println("multiple") 282 | 283 | println("statements") 284 | } 285 | 286 | func f2() { 287 | // comment directly before 288 | println() 289 | 290 | // comment after 291 | } 292 | 293 | func _() { 294 | f3 := func() { 295 | println() 296 | } 297 | } 298 | 299 | func multilineParams(p1 string, 300 | p2 string, 301 | ) { 302 | println("body") 303 | } 304 | 305 | func multilineParamsUnambiguous(p1 string, 306 | p2 string, 307 | ) { 308 | println("body") 309 | } 310 | 311 | func multilineParamsListNoReturn( 312 | p1 string, 313 | p2 string, 314 | ) { 315 | println("body") 316 | } 317 | 318 | func multilineParamsListReturningNamedSingleValue( 319 | p1 string, 320 | p2 string, 321 | ) (err error) { 322 | println("body") 323 | return err 324 | } 325 | 326 | func multilineParamsListReturningSingleValue( 327 | p1 string, 328 | p2 string, 329 | ) error { 330 | println("body") 331 | return nil 332 | } 333 | 334 | func multilineParamsListReturningNamedMultiValues( 335 | p1 string, 336 | p2 string, 337 | ) (s string, err error) { 338 | println("body") 339 | return s, err 340 | } 341 | 342 | func multilineParamsListReturningMultiValues( 343 | p1 string, 344 | p2 string, 345 | ) (string, error) { 346 | println("body") 347 | return "", nil 348 | } 349 | 350 | func multilineParamsListReturningNamedMultiLineValuesList( 351 | p1 string, 352 | p2 string, 353 | ) ( 354 | s string, 355 | err error, 356 | ) { 357 | println("body") 358 | return s, err 359 | } 360 | 361 | func multilineParamsListReturningMultiLineValues( 362 | p1 string, 363 | p2 string, 364 | ) ( 365 | string, 366 | error, 367 | ) { 368 | println("body") 369 | return "", nil 370 | } 371 | 372 | func multilineParamsOneParamNoReturn( 373 | p1 string, 374 | ) { 375 | println("body") 376 | } 377 | 378 | func multilineParamsOneParamReturningNamedSingleValue( 379 | p1 string, 380 | ) (err error) { 381 | println("body") 382 | return err 383 | } 384 | 385 | func multilineParamsOneParamReturningSingleValue( 386 | p1 string, 387 | ) error { 388 | println("body") 389 | return nil 390 | } 391 | 392 | func multilineParamsOneParamReturningNamedMultiValues( 393 | p1 string, 394 | ) (s string, err error) { 395 | println("body") 396 | return s, err 397 | } 398 | 399 | func multilineParamsOneParamReturningMultiValues( 400 | p1 string, 401 | ) (string, error) { 402 | println("body") 403 | return "", nil 404 | } 405 | 406 | func multilineParamsOneParamReturningNamedMultiLineValuesList( 407 | p1 string, 408 | ) ( 409 | s string, 410 | err error, 411 | ) { 412 | println("body") 413 | return s, err 414 | } 415 | 416 | func multilineParamsOneParamReturningMultiLineValues( 417 | p1 string, 418 | ) ( 419 | string, 420 | error, 421 | ) { 422 | println("body") 423 | return "", nil 424 | } 425 | 426 | func multilineResults() (p1 string, 427 | p2 string, 428 | ) { 429 | println("body") 430 | } 431 | 432 | func multilineResultsUnambiguous() (p1 string, 433 | p2 string, 434 | ) { 435 | println("body") 436 | } 437 | 438 | func multilineNoFields() { 439 | println("body") 440 | } 441 | 442 | func f( 443 | foo int, 444 | bar string, 445 | /* baz */ 446 | ) { 447 | body() 448 | } 449 | 450 | func f2( 451 | foo int, 452 | bar string, 453 | ) ( 454 | string, 455 | error, 456 | /* baz */ 457 | ) { 458 | return "", nil 459 | } 460 | 461 | func multilineResultsMultipleEmptyLines() (p1 string, 462 | p2 string, 463 | ) { 464 | println("body") 465 | } 466 | 467 | func multilineParamsWithoutEmptyLine(p1 string, 468 | p2 string, 469 | ) { 470 | println("body") 471 | } 472 | 473 | func multilineParamsWithoutEmptyLineWithComment(p1 string, 474 | p2 string, 475 | ) { 476 | // comment 477 | println("body") 478 | } 479 | 480 | // Same as the others above, but with a single result parameter without 481 | // parentheses. This used to cause token.File.Offset crashes. 482 | func f(p1 string, 483 | p2 string, 484 | ) int { 485 | println("body") 486 | return 0 487 | } 488 | 489 | func a() { 490 | f := func(s string, 491 | b bool, 492 | ) { 493 | // foo 494 | } 495 | } 496 | 497 | func f(p1 string, 498 | p2 string) (int, string, 499 | 500 | /* baz */) { 501 | println("body") 502 | return 0, "" 503 | } 504 | -------------------------------------------------------------------------------- /internal/govendor/diff/diff.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package diff 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "sort" 11 | "strings" 12 | ) 13 | 14 | // A pair is a pair of values tracked for both the x and y side of a diff. 15 | // It is typically a pair of line indexes. 16 | type pair struct{ x, y int } 17 | 18 | // Diff returns an anchored diff of the two texts old and new 19 | // in the “unified diff” format. If old and new are identical, 20 | // Diff returns a nil slice (no output). 21 | // 22 | // Unix diff implementations typically look for a diff with 23 | // the smallest number of lines inserted and removed, 24 | // which can in the worst case take time quadratic in the 25 | // number of lines in the texts. As a result, many implementations 26 | // either can be made to run for a long time or cut off the search 27 | // after a predetermined amount of work. 28 | // 29 | // In contrast, this implementation looks for a diff with the 30 | // smallest number of “unique” lines inserted and removed, 31 | // where unique means a line that appears just once in both old and new. 32 | // We call this an “anchored diff” because the unique lines anchor 33 | // the chosen matching regions. An anchored diff is usually clearer 34 | // than a standard diff, because the algorithm does not try to 35 | // reuse unrelated blank lines or closing braces. 36 | // The algorithm also guarantees to run in O(n log n) time 37 | // instead of the standard O(n²) time. 38 | // 39 | // Some systems call this approach a “patience diff,” named for 40 | // the “patience sorting” algorithm, itself named for a solitaire card game. 41 | // We avoid that name for two reasons. First, the name has been used 42 | // for a few different variants of the algorithm, so it is imprecise. 43 | // Second, the name is frequently interpreted as meaning that you have 44 | // to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, 45 | // when in fact the algorithm is faster than the standard one. 46 | func Diff(oldName string, old []byte, newName string, new []byte) []byte { 47 | if bytes.Equal(old, new) { 48 | return nil 49 | } 50 | x := lines(old) 51 | y := lines(new) 52 | 53 | // Print diff header. 54 | var out bytes.Buffer 55 | fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) 56 | fmt.Fprintf(&out, "--- %s\n", oldName) 57 | fmt.Fprintf(&out, "+++ %s\n", newName) 58 | 59 | // Loop over matches to consider, 60 | // expanding each match to include surrounding lines, 61 | // and then printing diff chunks. 62 | // To avoid setup/teardown cases outside the loop, 63 | // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair 64 | // in the sequence of matches. 65 | var ( 66 | done pair // printed up to x[:done.x] and y[:done.y] 67 | chunk pair // start lines of current chunk 68 | count pair // number of lines from each side in current chunk 69 | ctext []string // lines for current chunk 70 | ) 71 | for _, m := range tgs(x, y) { 72 | if m.x < done.x { 73 | // Already handled scanning forward from earlier match. 74 | continue 75 | } 76 | 77 | // Expand matching lines as far as possible, 78 | // establishing that x[start.x:end.x] == y[start.y:end.y]. 79 | // Note that on the first (or last) iteration we may (or definitely do) 80 | // have an empty match: start.x==end.x and start.y==end.y. 81 | start := m 82 | for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] { 83 | start.x-- 84 | start.y-- 85 | } 86 | end := m 87 | for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { 88 | end.x++ 89 | end.y++ 90 | } 91 | 92 | // Emit the mismatched lines before start into this chunk. 93 | // (No effect on first sentinel iteration, when start = {0,0}.) 94 | for _, s := range x[done.x:start.x] { 95 | ctext = append(ctext, "-"+s) 96 | count.x++ 97 | } 98 | for _, s := range y[done.y:start.y] { 99 | ctext = append(ctext, "+"+s) 100 | count.y++ 101 | } 102 | 103 | // If we're not at EOF and have too few common lines, 104 | // the chunk includes all the common lines and continues. 105 | const C = 3 // number of context lines 106 | if (end.x < len(x) || end.y < len(y)) && 107 | (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) { 108 | for _, s := range x[start.x:end.x] { 109 | ctext = append(ctext, " "+s) 110 | count.x++ 111 | count.y++ 112 | } 113 | done = end 114 | continue 115 | } 116 | 117 | // End chunk with common lines for context. 118 | if len(ctext) > 0 { 119 | n := end.x - start.x 120 | if n > C { 121 | n = C 122 | } 123 | for _, s := range x[start.x : start.x+n] { 124 | ctext = append(ctext, " "+s) 125 | count.x++ 126 | count.y++ 127 | } 128 | done = pair{start.x + n, start.y + n} 129 | 130 | // Format and emit chunk. 131 | // Convert line numbers to 1-indexed. 132 | // Special case: empty file shows up as 0,0 not 1,0. 133 | if count.x > 0 { 134 | chunk.x++ 135 | } 136 | if count.y > 0 { 137 | chunk.y++ 138 | } 139 | fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) 140 | for _, s := range ctext { 141 | out.WriteString(s) 142 | } 143 | count.x = 0 144 | count.y = 0 145 | ctext = ctext[:0] 146 | } 147 | 148 | // If we reached EOF, we're done. 149 | if end.x >= len(x) && end.y >= len(y) { 150 | break 151 | } 152 | 153 | // Otherwise start a new chunk. 154 | chunk = pair{end.x - C, end.y - C} 155 | for _, s := range x[chunk.x:end.x] { 156 | ctext = append(ctext, " "+s) 157 | count.x++ 158 | count.y++ 159 | } 160 | done = end 161 | } 162 | 163 | return out.Bytes() 164 | } 165 | 166 | // lines returns the lines in the file x, including newlines. 167 | // If the file does not end in a newline, one is supplied 168 | // along with a warning about the missing newline. 169 | func lines(x []byte) []string { 170 | l := strings.SplitAfter(string(x), "\n") 171 | if l[len(l)-1] == "" { 172 | l = l[:len(l)-1] 173 | } else { 174 | // Treat last line as having a message about the missing newline attached, 175 | // using the same text as BSD/GNU diff (including the leading backslash). 176 | l[len(l)-1] += "\n\\ No newline at end of file\n" 177 | } 178 | return l 179 | } 180 | 181 | // tgs returns the pairs of indexes of the longest common subsequence 182 | // of unique lines in x and y, where a unique line is one that appears 183 | // once in x and once in y. 184 | // 185 | // The longest common subsequence algorithm is as described in 186 | // Thomas G. Szymanski, “A Special Case of the Maximal Common 187 | // Subsequence Problem,” Princeton TR #170 (January 1975), 188 | // available at https://research.swtch.com/tgs170.pdf. 189 | func tgs(x, y []string) []pair { 190 | // Count the number of times each string appears in a and b. 191 | // We only care about 0, 1, many, counted as 0, -1, -2 192 | // for the x side and 0, -4, -8 for the y side. 193 | // Using negative numbers now lets us distinguish positive line numbers later. 194 | m := make(map[string]int) 195 | for _, s := range x { 196 | if c := m[s]; c > -2 { 197 | m[s] = c - 1 198 | } 199 | } 200 | for _, s := range y { 201 | if c := m[s]; c > -8 { 202 | m[s] = c - 4 203 | } 204 | } 205 | 206 | // Now unique strings can be identified by m[s] = -1+-4. 207 | // 208 | // Gather the indexes of those strings in x and y, building: 209 | // xi[i] = increasing indexes of unique strings in x. 210 | // yi[i] = increasing indexes of unique strings in y. 211 | // inv[i] = index j such that x[xi[i]] = y[yi[j]]. 212 | var xi, yi, inv []int 213 | for i, s := range y { 214 | if m[s] == -1+-4 { 215 | m[s] = len(yi) 216 | yi = append(yi, i) 217 | } 218 | } 219 | for i, s := range x { 220 | if j, ok := m[s]; ok && j >= 0 { 221 | xi = append(xi, i) 222 | inv = append(inv, j) 223 | } 224 | } 225 | 226 | // Apply Algorithm A from Szymanski's paper. 227 | // In those terms, A = J = inv and B = [0, n). 228 | // We add sentinel pairs {0,0}, and {len(x),len(y)} 229 | // to the returned sequence, to help the processing loop. 230 | J := inv 231 | n := len(xi) 232 | T := make([]int, n) 233 | L := make([]int, n) 234 | for i := range T { 235 | T[i] = n + 1 236 | } 237 | for i := 0; i < n; i++ { 238 | k := sort.Search(n, func(k int) bool { 239 | return T[k] >= J[i] 240 | }) 241 | T[k] = J[i] 242 | L[i] = k + 1 243 | } 244 | k := 0 245 | for _, v := range L { 246 | if k < v { 247 | k = v 248 | } 249 | } 250 | seq := make([]pair, 2+k) 251 | seq[1+k] = pair{len(x), len(y)} // sentinel at end 252 | lastj := n 253 | for i := n - 1; i >= 0; i-- { 254 | if L[i] == k && J[i] < lastj { 255 | seq[k] = pair{xi[i], yi[J[i]]} 256 | k-- 257 | } 258 | } 259 | seq[0] = pair{0, 0} // sentinel at start 260 | return seq 261 | } 262 | -------------------------------------------------------------------------------- /internal/govendor/go/doc/comment/print.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package comment 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "strings" 11 | ) 12 | 13 | // A Printer is a doc comment printer. 14 | // The fields in the struct can be filled in before calling 15 | // any of the printing methods 16 | // in order to customize the details of the printing process. 17 | type Printer struct { 18 | // HeadingLevel is the nesting level used for 19 | // HTML and Markdown headings. 20 | // If HeadingLevel is zero, it defaults to level 3, 21 | // meaning to use

and ###. 22 | HeadingLevel int 23 | 24 | // HeadingID is a function that computes the heading ID 25 | // (anchor tag) to use for the heading h when generating 26 | // HTML and Markdown. If HeadingID returns an empty string, 27 | // then the heading ID is omitted. 28 | // If HeadingID is nil, h.DefaultID is used. 29 | HeadingID func(h *Heading) string 30 | 31 | // DocLinkURL is a function that computes the URL for the given DocLink. 32 | // If DocLinkURL is nil, then link.DefaultURL(p.DocLinkBaseURL) is used. 33 | DocLinkURL func(link *DocLink) string 34 | 35 | // DocLinkBaseURL is used when DocLinkURL is nil, 36 | // passed to [DocLink.DefaultURL] to construct a DocLink's URL. 37 | // See that method's documentation for details. 38 | DocLinkBaseURL string 39 | 40 | // TextPrefix is a prefix to print at the start of every line 41 | // when generating text output using the Text method. 42 | TextPrefix string 43 | 44 | // TextCodePrefix is the prefix to print at the start of each 45 | // preformatted (code block) line when generating text output, 46 | // instead of (not in addition to) TextPrefix. 47 | // If TextCodePrefix is the empty string, it defaults to TextPrefix+"\t". 48 | TextCodePrefix string 49 | 50 | // TextWidth is the maximum width text line to generate, 51 | // measured in Unicode code points, 52 | // excluding TextPrefix and the newline character. 53 | // If TextWidth is zero, it defaults to 80 minus the number of code points in TextPrefix. 54 | // If TextWidth is negative, there is no limit. 55 | TextWidth int 56 | } 57 | 58 | func (p *Printer) headingLevel() int { 59 | if p.HeadingLevel <= 0 { 60 | return 3 61 | } 62 | return p.HeadingLevel 63 | } 64 | 65 | func (p *Printer) headingID(h *Heading) string { 66 | if p.HeadingID == nil { 67 | return h.DefaultID() 68 | } 69 | return p.HeadingID(h) 70 | } 71 | 72 | func (p *Printer) docLinkURL(link *DocLink) string { 73 | if p.DocLinkURL != nil { 74 | return p.DocLinkURL(link) 75 | } 76 | return link.DefaultURL(p.DocLinkBaseURL) 77 | } 78 | 79 | // DefaultURL constructs and returns the documentation URL for l, 80 | // using baseURL as a prefix for links to other packages. 81 | // 82 | // The possible forms returned by DefaultURL are: 83 | // - baseURL/ImportPath, for a link to another package 84 | // - baseURL/ImportPath#Name, for a link to a const, func, type, or var in another package 85 | // - baseURL/ImportPath#Recv.Name, for a link to a method in another package 86 | // - #Name, for a link to a const, func, type, or var in this package 87 | // - #Recv.Name, for a link to a method in this package 88 | // 89 | // If baseURL ends in a trailing slash, then DefaultURL inserts 90 | // a slash between ImportPath and # in the anchored forms. 91 | // For example, here are some baseURL values and URLs they can generate: 92 | // 93 | // "/pkg/" → "/pkg/math/#Sqrt" 94 | // "/pkg" → "/pkg/math#Sqrt" 95 | // "/" → "/math/#Sqrt" 96 | // "" → "/math#Sqrt" 97 | func (l *DocLink) DefaultURL(baseURL string) string { 98 | if l.ImportPath != "" { 99 | slash := "" 100 | if strings.HasSuffix(baseURL, "/") { 101 | slash = "/" 102 | } else { 103 | baseURL += "/" 104 | } 105 | switch { 106 | case l.Name == "": 107 | return baseURL + l.ImportPath + slash 108 | case l.Recv != "": 109 | return baseURL + l.ImportPath + slash + "#" + l.Recv + "." + l.Name 110 | default: 111 | return baseURL + l.ImportPath + slash + "#" + l.Name 112 | } 113 | } 114 | if l.Recv != "" { 115 | return "#" + l.Recv + "." + l.Name 116 | } 117 | return "#" + l.Name 118 | } 119 | 120 | // DefaultID returns the default anchor ID for the heading h. 121 | // 122 | // The default anchor ID is constructed by converting every 123 | // rune that is not alphanumeric ASCII to an underscore 124 | // and then adding the prefix “hdr-”. 125 | // For example, if the heading text is “Go Doc Comments”, 126 | // the default ID is “hdr-Go_Doc_Comments”. 127 | func (h *Heading) DefaultID() string { 128 | // Note: The “hdr-” prefix is important to avoid DOM clobbering attacks. 129 | // See https://pkg.go.dev/github.com/google/safehtml#Identifier. 130 | var out strings.Builder 131 | var p textPrinter 132 | p.oneLongLine(&out, h.Text) 133 | s := strings.TrimSpace(out.String()) 134 | if s == "" { 135 | return "" 136 | } 137 | out.Reset() 138 | out.WriteString("hdr-") 139 | for _, r := range s { 140 | if r < 0x80 && isIdentASCII(byte(r)) { 141 | out.WriteByte(byte(r)) 142 | } else { 143 | out.WriteByte('_') 144 | } 145 | } 146 | return out.String() 147 | } 148 | 149 | type commentPrinter struct { 150 | *Printer 151 | } 152 | 153 | // Comment returns the standard Go formatting of the [Doc], 154 | // without any comment markers. 155 | func (p *Printer) Comment(d *Doc) []byte { 156 | cp := &commentPrinter{Printer: p} 157 | var out bytes.Buffer 158 | for i, x := range d.Content { 159 | if i > 0 && blankBefore(x) { 160 | out.WriteString("\n") 161 | } 162 | cp.block(&out, x) 163 | } 164 | 165 | // Print one block containing all the link definitions that were used, 166 | // and then a second block containing all the unused ones. 167 | // This makes it easy to clean up the unused ones: gofmt and 168 | // delete the final block. And it's a nice visual signal without 169 | // affecting the way the comment formats for users. 170 | for i := 0; i < 2; i++ { 171 | used := i == 0 172 | first := true 173 | for _, def := range d.Links { 174 | if def.Used == used { 175 | if first { 176 | out.WriteString("\n") 177 | first = false 178 | } 179 | out.WriteString("[") 180 | out.WriteString(def.Text) 181 | out.WriteString("]: ") 182 | out.WriteString(def.URL) 183 | out.WriteString("\n") 184 | } 185 | } 186 | } 187 | 188 | return out.Bytes() 189 | } 190 | 191 | // blankBefore reports whether the block x requires a blank line before it. 192 | // All blocks do, except for Lists that return false from x.BlankBefore(). 193 | func blankBefore(x Block) bool { 194 | if x, ok := x.(*List); ok { 195 | return x.BlankBefore() 196 | } 197 | return true 198 | } 199 | 200 | // block prints the block x to out. 201 | func (p *commentPrinter) block(out *bytes.Buffer, x Block) { 202 | switch x := x.(type) { 203 | default: 204 | fmt.Fprintf(out, "?%T", x) 205 | 206 | case *Paragraph: 207 | p.text(out, "", x.Text) 208 | out.WriteString("\n") 209 | 210 | case *Heading: 211 | out.WriteString("# ") 212 | p.text(out, "", x.Text) 213 | out.WriteString("\n") 214 | 215 | case *Code: 216 | md := x.Text 217 | for md != "" { 218 | var line string 219 | line, md, _ = strings.Cut(md, "\n") 220 | if line != "" { 221 | out.WriteString("\t") 222 | out.WriteString(line) 223 | } 224 | out.WriteString("\n") 225 | } 226 | 227 | case *List: 228 | loose := x.BlankBetween() 229 | for i, item := range x.Items { 230 | if i > 0 && loose { 231 | out.WriteString("\n") 232 | } 233 | out.WriteString(" ") 234 | if item.Number == "" { 235 | out.WriteString(" - ") 236 | } else { 237 | out.WriteString(item.Number) 238 | out.WriteString(". ") 239 | } 240 | for i, blk := range item.Content { 241 | const fourSpace = " " 242 | if i > 0 { 243 | out.WriteString("\n" + fourSpace) 244 | } 245 | p.text(out, fourSpace, blk.(*Paragraph).Text) 246 | out.WriteString("\n") 247 | } 248 | } 249 | } 250 | } 251 | 252 | // text prints the text sequence x to out. 253 | func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) { 254 | for _, t := range x { 255 | switch t := t.(type) { 256 | case Plain: 257 | p.indent(out, indent, string(t)) 258 | case Italic: 259 | p.indent(out, indent, string(t)) 260 | case *Link: 261 | if t.Auto { 262 | p.text(out, indent, t.Text) 263 | } else { 264 | out.WriteString("[") 265 | p.text(out, indent, t.Text) 266 | out.WriteString("]") 267 | } 268 | case *DocLink: 269 | out.WriteString("[") 270 | p.text(out, indent, t.Text) 271 | out.WriteString("]") 272 | } 273 | } 274 | } 275 | 276 | // indent prints s to out, indenting with the indent string 277 | // after each newline in s. 278 | func (p *commentPrinter) indent(out *bytes.Buffer, indent, s string) { 279 | for s != "" { 280 | line, rest, ok := strings.Cut(s, "\n") 281 | out.WriteString(line) 282 | if ok { 283 | out.WriteString("\n") 284 | out.WriteString(indent) 285 | } 286 | s = rest 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.9.1] - 2025-09-07 4 | 5 | This is a bugfix release to address a regression in detecting 6 | comment directives with special characters such as `//golangcitest:config_path`. 7 | 8 | ## [v0.9.0] - 2025-09-02 9 | 10 | This release is based on Go 1.25's gofmt, and requires Go 1.24 or later. 11 | 12 | A new rule is introduced to "clothe" naked returns for the sake of clarity. 13 | While there is nothing wrong with naming results in function signatures, 14 | using lone `return` statements can be confusing to the reader. 15 | 16 | Go 1.25's `ignore` directives in `go.mod` files are now obeyed; 17 | any directories within the module matching any of the patterns 18 | are now omitted when walking directories, such as with `gofumpt -w .`. 19 | 20 | Module information is now loaded via Go's [`x/mod/modfile` package](https://pkg.go.dev/golang.org/x/mod/modfile) 21 | rather than executing `go mod edit -json`, which is way faster. 22 | This should result in moderate speed-ups when formatting many directories. 23 | 24 | ## [v0.8.0] - 2025-04-13 25 | 26 | This release is based on Go 1.24's gofmt, and requires Go 1.23 or later. 27 | 28 | The following changes are included: 29 | 30 | * Fail with `-d` if formatting any file resulted in a diff - #114 31 | * Do not panic when a `go.mod` file is missing a `go` directive - #317 32 | 33 | ## [v0.7.0] - 2024-08-16 34 | 35 | This release is based on Go 1.23.0's gofmt, and requires Go 1.22 or later. 36 | 37 | The following changes are included: 38 | 39 | * Group `internal/...` imported packages as standard library - #307 40 | 41 | ## [v0.6.0] - 2024-01-28 42 | 43 | This release is based on Go 1.21's gofmt, and requires Go 1.20 or later. 44 | 45 | The following changes are included: 46 | 47 | * Support `go` version strings from newer go.mod files - [#280] 48 | * Consider simple error checks even if they use the `=` operator - [#271] 49 | * Ignore `//line` directives to avoid panics - [#288] 50 | 51 | ## [v0.5.0] - 2023-04-09 52 | 53 | This release is based on Go 1.20's gofmt, and requires Go 1.19 or later. 54 | 55 | The biggest change in this release is that we now vendor copies of the packages 56 | `go/format`, `go/printer`, and `go/doc/comment` on top of `cmd/gofmt` itself. 57 | This allows for each gofumpt release to format code in exactly the same way 58 | no matter what Go version is used to build it, as Go versions can change those 59 | three packages in ways that alter formatting behavior. 60 | 61 | This vendoring adds a small amount of duplication when using the 62 | `mvdan.cc/gofumpt/format` library, but it's the only way to make gofumpt 63 | versions consistent in their behavior and formatting, just like gofmt. 64 | 65 | The jump to Go 1.20's `go/printer` should also bring a small performance 66 | improvement, as we contributed patches to make printing about 25% faster: 67 | 68 | * https://go.dev/cl/412555 69 | * https://go.dev/cl/412557 70 | * https://go.dev/cl/424924 71 | 72 | The following changes are included as well: 73 | 74 | * Skip `testdata` dirs by default like we already do for `vendor` - [#260] 75 | * Avoid inserting newlines incorrectly in some func signatures - [#235] 76 | * Avoid joining some comments with the previous line - [#256] 77 | * Fix `gofumpt -version` for release archives - [#253] 78 | 79 | ## [v0.4.0] - 2022-09-27 80 | 81 | This release is based on Go 1.19's gofmt, and requires Go 1.18 or later. 82 | We recommend building gofumpt with Go 1.19 for the best formatting results. 83 | 84 | The jump from Go 1.18 brings diffing in pure Go, removing the need to exec `diff`, 85 | and a small parsing speed-up thanks to `go/parser.SkipObjectResolution`. 86 | 87 | The following formatting fixes are included as well: 88 | 89 | * Allow grouping declarations with comments - [#212] 90 | * Properly measure the length of case clauses - [#217] 91 | * Fix a few crashes found by Go's native fuzzing 92 | 93 | ## [v0.3.1] - 2022-03-21 94 | 95 | This bugfix release resolves a number of issues: 96 | 97 | * Avoid "too many open files" error regression introduced by [v0.3.0] - [#208] 98 | * Use the `go.mod` relative to each Go file when deriving flag defaults - [#211] 99 | * Remove unintentional debug prints when directly formatting files 100 | 101 | ## [v0.3.0] - 2022-02-22 102 | 103 | This is gofumpt's third major release, based on Go 1.18's gofmt. 104 | The jump from Go 1.17's gofmt should bring a noticeable speed-up, 105 | as the tool can now format many files concurrently. 106 | On an 8-core laptop, formatting a large codebase is 4x as fast. 107 | 108 | The following [formatting rules](https://github.com/mvdan/gofumpt#Added-rules) are added: 109 | 110 | * Functions should separate `) {` where the indentation helps readability 111 | * Field lists should not have leading or trailing empty lines 112 | 113 | The following changes are included as well: 114 | 115 | * Generated files are now fully formatted when given as explicit arguments 116 | * Prepare for Go 1.18's module workspaces, which could cause errors 117 | * Import paths sharing a prefix with the current module path are no longer 118 | grouped with standard library imports 119 | * `format.Options` gains a `ModulePath` field per the last bullet point 120 | 121 | ## [v0.2.1] - 2021-12-12 122 | 123 | This bugfix release resolves a number of issues: 124 | 125 | * Add deprecated flags `-s` and `-r` once again, now giving useful errors 126 | * Avoid a panic with certain function declaration styles 127 | * Don't group interface members of different kinds 128 | * Account for leading comments in composite literals 129 | 130 | ## [v0.2.0] - 2021-11-10 131 | 132 | This is gofumpt's second major release, based on Go 1.17's gofmt. 133 | The jump from Go 1.15's gofmt should bring a mild speed-up, 134 | as walking directories with `filepath.WalkDir` uses fewer syscalls. 135 | 136 | gofumports is now removed, after being deprecated in [v0.1.0]. 137 | Its main purpose was IDE integration; it is now recommended to use gopls, 138 | which in turn implements goimports and supports gofumpt natively. 139 | IDEs which don't integrate with gopls (such as GoLand) implement goimports too, 140 | so it is safe to use gofumpt as their "format on save" command. 141 | See the [installation instructions](https://github.com/mvdan/gofumpt#Installation) 142 | for more details. 143 | 144 | The following [formatting rules](https://github.com/mvdan/gofumpt#Added-rules) are added: 145 | 146 | * Composite literals should not have leading or trailing empty lines 147 | * No empty lines following an assignment operator 148 | * Functions using an empty line for readability should use a `) {` line instead 149 | * Remove unnecessary empty lines from interfaces 150 | 151 | Finally, the following changes are made to the gofumpt tool: 152 | 153 | * Initial support for Go 1.18's type parameters is added 154 | * The `-r` flag is removed in favor of `gofmt -r` 155 | * The `-s` flag is removed as it is always enabled 156 | * Vendor directories are skipped unless given as explicit arguments 157 | * The added rules are not applied to generated Go files 158 | * The `format` Go API now also applies the `gofmt -s` simplification 159 | * Add support for `//gofumpt:diagnose` comments 160 | 161 | ## [v0.1.1] - 2021-03-11 162 | 163 | This bugfix release backports fixes for a few issues: 164 | 165 | * Keep leading empty lines in func bodies if they help readability 166 | * Avoid breaking comment alignment on empty field lists 167 | * Add support for `//go-sumtype:` directives 168 | 169 | ## [v0.1.0] - 2021-01-05 170 | 171 | This is gofumpt's first release, based on Go 1.15.x. It solidifies the features 172 | which have worked well for over a year. 173 | 174 | This release will be the last to include `gofumports`, the fork of `goimports` 175 | which applies `gofumpt`'s rules on top of updating the Go import lines. Users 176 | who were relying on `goimports` in their editors or IDEs to apply both `gofumpt` 177 | and `goimports` in a single step should switch to gopls, the official Go 178 | language server. It is supported by many popular editors such as VS Code and 179 | Vim, and already bundles gofumpt support. Instructions are available [in the 180 | README](https://github.com/mvdan/gofumpt). 181 | 182 | `gofumports` also added maintenance work and potential confusion to end users. 183 | In the future, there will only be one way to use `gofumpt` from the command 184 | line. We also have a [Go API](https://pkg.go.dev/mvdan.cc/gofumpt/format) for 185 | those building programs with gofumpt. 186 | 187 | Finally, this release adds the `-version` flag, to print the tool's own version. 188 | The flag will work for "master" builds too. 189 | 190 | [v0.9.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.9.0 191 | [v0.8.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.8.0 192 | [v0.7.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.7.0 193 | 194 | [v0.6.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.6.0 195 | [#271]: https://github.com/mvdan/gofumpt/issues/271 196 | [#280]: https://github.com/mvdan/gofumpt/issues/280 197 | [#288]: https://github.com/mvdan/gofumpt/issues/288 198 | 199 | [v0.5.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.5.0 200 | [#235]: https://github.com/mvdan/gofumpt/issues/235 201 | [#253]: https://github.com/mvdan/gofumpt/issues/253 202 | [#256]: https://github.com/mvdan/gofumpt/issues/256 203 | [#260]: https://github.com/mvdan/gofumpt/issues/260 204 | 205 | [v0.4.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.4.0 206 | [#212]: https://github.com/mvdan/gofumpt/issues/212 207 | [#217]: https://github.com/mvdan/gofumpt/issues/217 208 | 209 | [v0.3.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.3.1 210 | [#208]: https://github.com/mvdan/gofumpt/issues/208 211 | [#211]: https://github.com/mvdan/gofumpt/pull/211 212 | 213 | [v0.3.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.3.0 214 | [v0.2.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.2.1 215 | [v0.2.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.2.0 216 | [v0.1.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.1.1 217 | [v0.1.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.1.0 218 | -------------------------------------------------------------------------------- /internal/govendor/go/doc/comment/text.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package comment 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "sort" 11 | "strings" 12 | "unicode/utf8" 13 | ) 14 | 15 | // A textPrinter holds the state needed for printing a Doc as plain text. 16 | type textPrinter struct { 17 | *Printer 18 | long strings.Builder 19 | prefix string 20 | codePrefix string 21 | width int 22 | } 23 | 24 | // Text returns a textual formatting of the [Doc]. 25 | // See the [Printer] documentation for ways to customize the text output. 26 | func (p *Printer) Text(d *Doc) []byte { 27 | tp := &textPrinter{ 28 | Printer: p, 29 | prefix: p.TextPrefix, 30 | codePrefix: p.TextCodePrefix, 31 | width: p.TextWidth, 32 | } 33 | if tp.codePrefix == "" { 34 | tp.codePrefix = p.TextPrefix + "\t" 35 | } 36 | if tp.width == 0 { 37 | tp.width = 80 - utf8.RuneCountInString(tp.prefix) 38 | } 39 | 40 | var out bytes.Buffer 41 | for i, x := range d.Content { 42 | if i > 0 && blankBefore(x) { 43 | out.WriteString(tp.prefix) 44 | writeNL(&out) 45 | } 46 | tp.block(&out, x) 47 | } 48 | anyUsed := false 49 | for _, def := range d.Links { 50 | if def.Used { 51 | anyUsed = true 52 | break 53 | } 54 | } 55 | if anyUsed { 56 | writeNL(&out) 57 | for _, def := range d.Links { 58 | if def.Used { 59 | fmt.Fprintf(&out, "[%s]: %s\n", def.Text, def.URL) 60 | } 61 | } 62 | } 63 | return out.Bytes() 64 | } 65 | 66 | // writeNL calls out.WriteByte('\n') 67 | // but first trims trailing spaces on the previous line. 68 | func writeNL(out *bytes.Buffer) { 69 | // Trim trailing spaces. 70 | data := out.Bytes() 71 | n := 0 72 | for n < len(data) && (data[len(data)-n-1] == ' ' || data[len(data)-n-1] == '\t') { 73 | n++ 74 | } 75 | if n > 0 { 76 | out.Truncate(len(data) - n) 77 | } 78 | out.WriteByte('\n') 79 | } 80 | 81 | // block prints the block x to out. 82 | func (p *textPrinter) block(out *bytes.Buffer, x Block) { 83 | switch x := x.(type) { 84 | default: 85 | fmt.Fprintf(out, "?%T\n", x) 86 | 87 | case *Paragraph: 88 | out.WriteString(p.prefix) 89 | p.text(out, "", x.Text) 90 | 91 | case *Heading: 92 | out.WriteString(p.prefix) 93 | out.WriteString("# ") 94 | p.text(out, "", x.Text) 95 | 96 | case *Code: 97 | text := x.Text 98 | for text != "" { 99 | var line string 100 | line, text, _ = strings.Cut(text, "\n") 101 | if line != "" { 102 | out.WriteString(p.codePrefix) 103 | out.WriteString(line) 104 | } 105 | writeNL(out) 106 | } 107 | 108 | case *List: 109 | loose := x.BlankBetween() 110 | for i, item := range x.Items { 111 | if i > 0 && loose { 112 | out.WriteString(p.prefix) 113 | writeNL(out) 114 | } 115 | out.WriteString(p.prefix) 116 | out.WriteString(" ") 117 | if item.Number == "" { 118 | out.WriteString(" - ") 119 | } else { 120 | out.WriteString(item.Number) 121 | out.WriteString(". ") 122 | } 123 | for i, blk := range item.Content { 124 | const fourSpace = " " 125 | if i > 0 { 126 | writeNL(out) 127 | out.WriteString(p.prefix) 128 | out.WriteString(fourSpace) 129 | } 130 | p.text(out, fourSpace, blk.(*Paragraph).Text) 131 | } 132 | } 133 | } 134 | } 135 | 136 | // text prints the text sequence x to out. 137 | func (p *textPrinter) text(out *bytes.Buffer, indent string, x []Text) { 138 | p.oneLongLine(&p.long, x) 139 | words := strings.Fields(p.long.String()) 140 | p.long.Reset() 141 | 142 | var seq []int 143 | if p.width < 0 || len(words) == 0 { 144 | seq = []int{0, len(words)} // one long line 145 | } else { 146 | seq = wrap(words, p.width-utf8.RuneCountInString(indent)) 147 | } 148 | for i := 0; i+1 < len(seq); i++ { 149 | if i > 0 { 150 | out.WriteString(p.prefix) 151 | out.WriteString(indent) 152 | } 153 | for j, w := range words[seq[i]:seq[i+1]] { 154 | if j > 0 { 155 | out.WriteString(" ") 156 | } 157 | out.WriteString(w) 158 | } 159 | writeNL(out) 160 | } 161 | } 162 | 163 | // oneLongLine prints the text sequence x to out as one long line, 164 | // without worrying about line wrapping. 165 | // Explicit links have the [ ] dropped to improve readability. 166 | func (p *textPrinter) oneLongLine(out *strings.Builder, x []Text) { 167 | for _, t := range x { 168 | switch t := t.(type) { 169 | case Plain: 170 | out.WriteString(string(t)) 171 | case Italic: 172 | out.WriteString(string(t)) 173 | case *Link: 174 | p.oneLongLine(out, t.Text) 175 | case *DocLink: 176 | p.oneLongLine(out, t.Text) 177 | } 178 | } 179 | } 180 | 181 | // wrap wraps words into lines of at most max runes, 182 | // minimizing the sum of the squares of the leftover lengths 183 | // at the end of each line (except the last, of course), 184 | // with a preference for ending lines at punctuation (.,:;). 185 | // 186 | // The returned slice gives the indexes of the first words 187 | // on each line in the wrapped text with a final entry of len(words). 188 | // Thus the lines are words[seq[0]:seq[1]], words[seq[1]:seq[2]], 189 | // ..., words[seq[len(seq)-2]:seq[len(seq)-1]]. 190 | // 191 | // The implementation runs in O(n log n) time, where n = len(words), 192 | // using the algorithm described in D. S. Hirschberg and L. L. Larmore, 193 | // “[The least weight subsequence problem],” FOCS 1985, pp. 137-143. 194 | // 195 | // [The least weight subsequence problem]: https://doi.org/10.1109/SFCS.1985.60 196 | func wrap(words []string, max int) (seq []int) { 197 | // The algorithm requires that our scoring function be concave, 198 | // meaning that for all i₀ ≤ i₁ < j₀ ≤ j₁, 199 | // weight(i₀, j₀) + weight(i₁, j₁) ≤ weight(i₀, j₁) + weight(i₁, j₀). 200 | // 201 | // Our weights are two-element pairs [hi, lo] 202 | // ordered by elementwise comparison. 203 | // The hi entry counts the weight for lines that are longer than max, 204 | // and the lo entry counts the weight for lines that are not. 205 | // This forces the algorithm to first minimize the number of lines 206 | // that are longer than max, which correspond to lines with 207 | // single very long words. Having done that, it can move on to 208 | // minimizing the lo score, which is more interesting. 209 | // 210 | // The lo score is the sum for each line of the square of the 211 | // number of spaces remaining at the end of the line and a 212 | // penalty of 64 given out for not ending the line in a 213 | // punctuation character (.,:;). 214 | // The penalty is somewhat arbitrarily chosen by trying 215 | // different amounts and judging how nice the wrapped text looks. 216 | // Roughly speaking, using 64 means that we are willing to 217 | // end a line with eight blank spaces in order to end at a 218 | // punctuation character, even if the next word would fit in 219 | // those spaces. 220 | // 221 | // We care about ending in punctuation characters because 222 | // it makes the text easier to skim if not too many sentences 223 | // or phrases begin with a single word on the previous line. 224 | 225 | // A score is the score (also called weight) for a given line. 226 | // add and cmp add and compare scores. 227 | type score struct { 228 | hi int64 229 | lo int64 230 | } 231 | add := func(s, t score) score { return score{s.hi + t.hi, s.lo + t.lo} } 232 | cmp := func(s, t score) int { 233 | switch { 234 | case s.hi < t.hi: 235 | return -1 236 | case s.hi > t.hi: 237 | return +1 238 | case s.lo < t.lo: 239 | return -1 240 | case s.lo > t.lo: 241 | return +1 242 | } 243 | return 0 244 | } 245 | 246 | // total[j] is the total number of runes 247 | // (including separating spaces) in words[:j]. 248 | total := make([]int, len(words)+1) 249 | total[0] = 0 250 | for i, s := range words { 251 | total[1+i] = total[i] + utf8.RuneCountInString(s) + 1 252 | } 253 | 254 | // weight returns weight(i, j). 255 | weight := func(i, j int) score { 256 | // On the last line, there is zero weight for being too short. 257 | n := total[j] - 1 - total[i] 258 | if j == len(words) && n <= max { 259 | return score{0, 0} 260 | } 261 | 262 | // Otherwise the weight is the penalty plus the square of the number of 263 | // characters remaining on the line or by which the line goes over. 264 | // In the latter case, that value goes in the hi part of the score. 265 | // (See note above.) 266 | p := wrapPenalty(words[j-1]) 267 | v := int64(max-n) * int64(max-n) 268 | if n > max { 269 | return score{v, p} 270 | } 271 | return score{0, v + p} 272 | } 273 | 274 | // The rest of this function is “The Basic Algorithm” from 275 | // Hirschberg and Larmore's conference paper, 276 | // using the same names as in the paper. 277 | f := []score{{0, 0}} 278 | g := func(i, j int) score { return add(f[i], weight(i, j)) } 279 | 280 | bridge := func(a, b, c int) bool { 281 | k := c + sort.Search(len(words)+1-c, func(k int) bool { 282 | k += c 283 | return cmp(g(a, k), g(b, k)) > 0 284 | }) 285 | if k > len(words) { 286 | return true 287 | } 288 | return cmp(g(c, k), g(b, k)) <= 0 289 | } 290 | 291 | // d is a one-ended deque implemented as a slice. 292 | d := make([]int, 1, len(words)) 293 | d[0] = 0 294 | bestleft := make([]int, 1, len(words)) 295 | bestleft[0] = -1 296 | for m := 1; m < len(words); m++ { 297 | f = append(f, g(d[0], m)) 298 | bestleft = append(bestleft, d[0]) 299 | for len(d) > 1 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 { 300 | d = d[1:] // “Retire” 301 | } 302 | for len(d) > 1 && bridge(d[len(d)-2], d[len(d)-1], m) { 303 | d = d[:len(d)-1] // “Fire” 304 | } 305 | if cmp(g(m, len(words)), g(d[len(d)-1], len(words))) < 0 { 306 | d = append(d, m) // “Hire” 307 | // The next few lines are not in the paper but are necessary 308 | // to handle two-word inputs correctly. It appears to be 309 | // just a bug in the paper's pseudocode. 310 | if len(d) == 2 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 { 311 | d = d[1:] 312 | } 313 | } 314 | } 315 | bestleft = append(bestleft, d[0]) 316 | 317 | // Recover least weight sequence from bestleft. 318 | n := 1 319 | for m := len(words); m > 0; m = bestleft[m] { 320 | n++ 321 | } 322 | seq = make([]int, n) 323 | for m := len(words); m > 0; m = bestleft[m] { 324 | n-- 325 | seq[n] = m 326 | } 327 | return seq 328 | } 329 | 330 | // wrapPenalty is the penalty for inserting a line break after word s. 331 | func wrapPenalty(s string) int64 { 332 | switch s[len(s)-1] { 333 | case '.', ',', ':', ';': 334 | return 0 335 | } 336 | return 64 337 | } 338 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gofumpt 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/mvdan.cc/gofumpt/format.svg)](https://pkg.go.dev/mvdan.cc/gofumpt/format) 4 | 5 | go install mvdan.cc/gofumpt@latest 6 | 7 | Enforce a stricter format than `gofmt`, while being backwards compatible. 8 | That is, `gofumpt` is happy with a subset of the formats that `gofmt` is happy with. 9 | 10 | The tool is a fork of `gofmt` as of Go 1.25.0, and requires Go 1.24 or later. 11 | It can be used as a drop-in replacement to format your Go code, 12 | and running `gofmt` after `gofumpt` should produce no changes. 13 | For example: 14 | 15 | gofumpt -l -w . 16 | 17 | Some of the Go source files in this repository belong to the Go project. 18 | The project includes copies of `go/printer` and `go/doc/comment` as of Go 1.25.0 19 | to ensure consistent formatting independent of what Go version is being used. 20 | The [added formatting rules](#Added-rules) are implemented in the `format` package. 21 | 22 | `vendor` and `testdata` directories are skipped unless given as explicit arguments. 23 | Similarly, the added rules do not apply to generated Go files unless they are 24 | given as explicit arguments. 25 | 26 | [`ignore` directives](https://go.dev/ref/mod#go-mod-file-ignore) in `go.mod` files are obeyed as well, 27 | unless directories or files within them are given as explicit arguments. 28 | 29 | Finally, note that the `-r` rewrite flag is removed in favor of `gofmt -r`, 30 | and the `-s` flag is hidden as it is always enabled. 31 | 32 | ### Added rules 33 | 34 | **No empty lines following an assignment operator** 35 | 36 |
Example 37 | 38 | ```go 39 | func foo() { 40 | foo := 41 | "bar" 42 | } 43 | ``` 44 | 45 | ```go 46 | func foo() { 47 | foo := "bar" 48 | } 49 | ``` 50 | 51 |
52 | 53 | **No empty lines around function bodies** 54 | 55 |
Example 56 | 57 | ```go 58 | func foo() { 59 | 60 | println("bar") 61 | 62 | } 63 | ``` 64 | 65 | ```go 66 | func foo() { 67 | println("bar") 68 | } 69 | ``` 70 | 71 |
72 | 73 | **Functions should separate `) {` where the indentation helps readability** 74 | 75 |
Example 76 | 77 | ```go 78 | func foo(s string, 79 | i int) { 80 | println("bar") 81 | } 82 | 83 | // With an empty line it's slightly better, but still not great. 84 | func bar(s string, 85 | i int) { 86 | 87 | println("bar") 88 | } 89 | ``` 90 | 91 | ```go 92 | func foo(s string, 93 | i int, 94 | ) { 95 | println("bar") 96 | } 97 | 98 | // With an empty line it's slightly better, but still not great. 99 | func bar(s string, 100 | i int, 101 | ) { 102 | println("bar") 103 | } 104 | ``` 105 | 106 |
107 | 108 | **No empty lines around a lone statement (or comment) in a block** 109 | 110 |
Example 111 | 112 | ```go 113 | if err != nil { 114 | 115 | return err 116 | } 117 | ``` 118 | 119 | ```go 120 | if err != nil { 121 | return err 122 | } 123 | ``` 124 | 125 |
126 | 127 | **No empty lines before a simple error check** 128 | 129 |
Example 130 | 131 | ```go 132 | foo, err := processFoo() 133 | 134 | if err != nil { 135 | return err 136 | } 137 | ``` 138 | 139 | ```go 140 | foo, err := processFoo() 141 | if err != nil { 142 | return err 143 | } 144 | ``` 145 | 146 |
147 | 148 | **Composite literals should use newlines consistently** 149 | 150 |
Example 151 | 152 | ```go 153 | // A newline before or after an element requires newlines for the opening and 154 | // closing braces. 155 | var ints = []int{1, 2, 156 | 3, 4} 157 | 158 | // A newline between consecutive elements requires a newline between all 159 | // elements. 160 | var matrix = [][]int{ 161 | {1}, 162 | {2}, { 163 | 3, 164 | }, 165 | } 166 | ``` 167 | 168 | ```go 169 | var ints = []int{ 170 | 1, 2, 171 | 3, 4, 172 | } 173 | 174 | var matrix = [][]int{ 175 | {1}, 176 | {2}, 177 | { 178 | 3, 179 | }, 180 | } 181 | ``` 182 | 183 |
184 | 185 | **Empty field lists should use a single line** 186 | 187 |
Example 188 | 189 | ```go 190 | var V interface { 191 | } = 3 192 | 193 | type T struct { 194 | } 195 | 196 | func F( 197 | ) 198 | ``` 199 | 200 | ```go 201 | var V interface{} = 3 202 | 203 | type T struct{} 204 | 205 | func F() 206 | ``` 207 | 208 |
209 | 210 | **`std` imports must be in a separate group at the top** 211 | 212 |
Example 213 | 214 | ```go 215 | import ( 216 | "foo.com/bar" 217 | 218 | "io" 219 | 220 | "io/ioutil" 221 | ) 222 | ``` 223 | 224 | ```go 225 | import ( 226 | "io" 227 | "io/ioutil" 228 | 229 | "foo.com/bar" 230 | ) 231 | ``` 232 | 233 |
234 | 235 | **Short case clauses should take a single line** 236 | 237 |
Example 238 | 239 | ```go 240 | switch c { 241 | case 'a', 'b', 242 | 'c', 'd': 243 | } 244 | ``` 245 | 246 | ```go 247 | switch c { 248 | case 'a', 'b', 'c', 'd': 249 | } 250 | ``` 251 | 252 |
253 | 254 | **Multiline top-level declarations must be separated by empty lines** 255 | 256 |
Example 257 | 258 | ```go 259 | func foo() { 260 | println("multiline foo") 261 | } 262 | func bar() { 263 | println("multiline bar") 264 | } 265 | ``` 266 | 267 | ```go 268 | func foo() { 269 | println("multiline foo") 270 | } 271 | 272 | func bar() { 273 | println("multiline bar") 274 | } 275 | ``` 276 | 277 |
278 | 279 | **Single var declarations should not be grouped with parentheses** 280 | 281 |
Example 282 | 283 | ```go 284 | var ( 285 | foo = "bar" 286 | ) 287 | ``` 288 | 289 | ```go 290 | var foo = "bar" 291 | ``` 292 | 293 |
294 | 295 | **Contiguous top-level declarations should be grouped together** 296 | 297 |
Example 298 | 299 | ```go 300 | var nicer = "x" 301 | var with = "y" 302 | var alignment = "z" 303 | ``` 304 | 305 | ```go 306 | var ( 307 | nicer = "x" 308 | with = "y" 309 | alignment = "z" 310 | ) 311 | ``` 312 | 313 |
314 | 315 | **Simple var-declaration statements should use short assignments** 316 | 317 |
Example 318 | 319 | ```go 320 | var s = "somestring" 321 | ``` 322 | 323 | ```go 324 | s := "somestring" 325 | ``` 326 | 327 |
328 | 329 | **The `-s` code simplification flag is enabled by default** 330 | 331 |
Example 332 | 333 | ```go 334 | var _ = [][]int{[]int{1}} 335 | ``` 336 | 337 | ```go 338 | var _ = [][]int{{1}} 339 | ``` 340 | 341 |
342 | 343 | **Octal integer literals should use the `0o` prefix on modules using Go 1.13 and later** 344 | 345 |
Example 346 | 347 | ```go 348 | const perm = 0755 349 | ``` 350 | 351 | ```go 352 | const perm = 0o755 353 | ``` 354 | 355 |
356 | 357 | **Comments which aren't Go directives should start with a whitespace** 358 | 359 |
Example 360 | 361 | ```go 362 | //go:noinline 363 | 364 | //Foo is awesome. 365 | func Foo() {} 366 | ``` 367 | 368 | ```go 369 | //go:noinline 370 | 371 | // Foo is awesome. 372 | func Foo() {} 373 | ``` 374 | 375 |
376 | 377 | **Composite literals should not have leading or trailing empty lines** 378 | 379 |
Example 380 | 381 | ```go 382 | var _ = []string{ 383 | 384 | "foo", 385 | 386 | } 387 | 388 | var _ = map[string]string{ 389 | 390 | "foo": "bar", 391 | 392 | } 393 | ``` 394 | 395 | ```go 396 | var _ = []string{ 397 | "foo", 398 | } 399 | 400 | var _ = map[string]string{ 401 | "foo": "bar", 402 | } 403 | ``` 404 | 405 |
406 | 407 | **Field lists should not have leading or trailing empty lines** 408 | 409 |
Example 410 | 411 | ```go 412 | type Person interface { 413 | 414 | Name() string 415 | 416 | Age() int 417 | 418 | } 419 | 420 | type ZeroFields struct { 421 | 422 | // No fields are needed here. 423 | 424 | } 425 | ``` 426 | 427 | ```go 428 | type Person interface { 429 | Name() string 430 | 431 | Age() int 432 | } 433 | 434 | type ZeroFields struct { 435 | // No fields are needed here. 436 | } 437 | ``` 438 | 439 |
440 | 441 | ### Extra rules behind `-extra` 442 | 443 | **Adjacent parameters with the same type should be grouped together** 444 | 445 |
Example 446 | 447 | ```go 448 | func Foo(bar string, baz string) {} 449 | ``` 450 | 451 | ```go 452 | func Foo(bar, baz string) {} 453 | ``` 454 | 455 |
456 | 457 | **Avoid naked returns for the sake of clarity** 458 | 459 |
Example 460 | 461 | ```go 462 | func Foo() (err error) { 463 | return 464 | } 465 | ``` 466 | 467 | ```go 468 | func Foo() (err error) { 469 | return err 470 | } 471 | ``` 472 | 473 |
474 | 475 | ### Installation 476 | 477 | `gofumpt` is a replacement for `gofmt`, so you can simply `go install` it as 478 | described at the top of this README and use it. 479 | 480 | When using an IDE or editor with Go integration based on `gopls`, 481 | it's best to configure the editor to use the `gofumpt` support built into `gopls`. 482 | 483 | The instructions below show how to set up `gofumpt` for some of the 484 | major editors out there. 485 | 486 | #### Visual Studio Code 487 | 488 | Enable the language server following [the official docs](https://github.com/golang/vscode-go#readme), 489 | and then enable gopls's `gofumpt` option. Note that VS Code will complain about 490 | the `gopls` settings, but they will still work. 491 | 492 | ```json 493 | "go.useLanguageServer": true, 494 | "gopls": { 495 | "formatting.gofumpt": true, 496 | }, 497 | ``` 498 | 499 | #### GoLand 500 | 501 | GoLand doesn't use `gopls` so it should be configured to use `gofumpt` directly. 502 | Once `gofumpt` is installed, follow the steps below: 503 | 504 | - Open **Settings** (File > Settings) 505 | - Open the **Tools** section 506 | - Find the *File Watchers* sub-section 507 | - Click on the `+` on the right side to add a new file watcher 508 | - Choose *Custom Template* 509 | 510 | When a window asks for settings, you can enter the following: 511 | 512 | * File Types: Select all .go files 513 | * Scope: Project Files 514 | * Program: Select your `gofumpt` executable 515 | * Arguments: `-w $FilePath$` 516 | * Output path to refresh: `$FilePath$` 517 | * Working directory: `$ProjectFileDir$` 518 | * Environment variables: `GOROOT=$GOROOT$;GOPATH=$GOPATH$;PATH=$GoBinDirs$` 519 | 520 | To avoid unnecessary runs, you should disable all checkboxes in the *Advanced* section. 521 | 522 | #### Vim 523 | 524 | The configuration depends on the plugin you are using: [vim-go](https://github.com/fatih/vim-go) 525 | or [govim](https://github.com/govim/govim). 526 | 527 | ##### vim-go 528 | 529 | To configure `gopls` to use `gofumpt`: 530 | 531 | ```vim 532 | let g:go_fmt_command="gopls" 533 | let g:go_gopls_gofumpt=1 534 | ``` 535 | 536 | ##### govim 537 | 538 | To configure `gopls` to use `gofumpt`: 539 | 540 | ```vim 541 | call govim#config#Set("Gofumpt", 1) 542 | ``` 543 | 544 | #### Neovim 545 | 546 | When using [`lspconfig`](https://github.com/neovim/nvim-lspconfig), pass the `gofumpt` setting to `gopls`: 547 | 548 | ```lua 549 | require('lspconfig').gopls.setup({ 550 | settings = { 551 | gopls = { 552 | gofumpt = true 553 | } 554 | } 555 | }) 556 | ``` 557 | 558 | #### Emacs 559 | 560 | For [lsp-mode](https://emacs-lsp.github.io/lsp-mode/) users on version 8.0.0 or higher: 561 | 562 | ```elisp 563 | (setq lsp-go-use-gofumpt t) 564 | ``` 565 | 566 | For users of `lsp-mode` before `8.0.0`: 567 | 568 | ```elisp 569 | (lsp-register-custom-settings 570 | '(("gopls.gofumpt" t))) 571 | ``` 572 | 573 | For [eglot](https://github.com/joaotavora/eglot) users: 574 | 575 | ```elisp 576 | (setq-default eglot-workspace-configuration 577 | '((:gopls . ((gofumpt . t))))) 578 | ``` 579 | 580 | #### Helix 581 | 582 | When using the `gopls` language server, modify the Go settings in `~/.config/helix/languages.toml`: 583 | 584 | ```toml 585 | [language-server.gopls.config] 586 | "formatting.gofumpt" = true 587 | ``` 588 | 589 | #### Sublime Text 590 | 591 | With ST4, install the Sublime Text LSP extension according to [the documentation](https://github.com/sublimelsp/LSP), 592 | and enable `gopls`'s `gofumpt` option in the LSP package settings, 593 | including setting `lsp_format_on_save` to `true`. 594 | 595 | ```json 596 | "lsp_format_on_save": true, 597 | "clients": 598 | { 599 | "gopls": 600 | { 601 | "enabled": true, 602 | "initializationOptions": { 603 | "gofumpt": true, 604 | } 605 | } 606 | } 607 | ``` 608 | 609 | ### Zed 610 | For `gofumpt` to be used in Zed, you need to set the `gofumpt` option in the LSP settings. This is done by providing the `"gofumpt": true` in `initialization_options`. 611 | 612 | ```json 613 | "lsp": { 614 | "gopls": { 615 | "initialization_options": { 616 | "gofumpt": true 617 | } 618 | } 619 | } 620 | ``` 621 | 622 | ### Roadmap 623 | 624 | This tool is a place to experiment. In the long term, the features that work 625 | well might be proposed for `gofmt` itself. 626 | 627 | The tool is also compatible with `gofmt` and is aimed to be stable, so you can 628 | rely on it for your code as long as you pin a version of it. 629 | 630 | ### Frequently Asked Questions 631 | 632 | > Why attempt to replace `gofmt` instead of building on top of it? 633 | 634 | Our design is to build on top of `gofmt`, and we'll never add rules which 635 | disagree with its formatting. So we extend `gofmt` rather than compete with it. 636 | 637 | The tool is a modified copy of `gofmt`, for the purpose of allowing its use as a 638 | drop-in replacement in editors and scripts. 639 | 640 | > Why are my module imports being grouped with standard library imports? 641 | 642 | Any import paths that don't start with a domain name like `foo.com` are 643 | effectively [reserved by the Go toolchain](https://github.com/golang/go/issues/32819). 644 | Third party modules should either start with a domain name, 645 | even a local one like `foo.local`, or use [a reserved path prefix](https://github.com/golang/go/issues/37641). 646 | 647 | For backwards compatibility with modules set up before these rules were clear, 648 | `gofumpt` will treat any import path sharing a prefix with the current module 649 | path as third party. For example, if the current module is `mycorp/mod1`, then 650 | all import paths in `mycorp/...` will be considered third party. 651 | 652 | > How can I use `gofumpt` if I already use `goimports` to replace `gofmt`? 653 | 654 | Most editors have replaced the `goimports` program with the same functionality 655 | provided by a language server like `gopls`. This mechanism is significantly 656 | faster and more powerful, since the language server has more information that is 657 | kept up to date, necessary to add missing imports. 658 | 659 | As such, the general recommendation is to let your editor fix your imports - 660 | either via `gopls`, such as VSCode or vim-go, or via their own custom 661 | implementation, such as GoLand. Then follow the install instructions above to 662 | enable the use of `gofumpt` instead of `gofmt`. 663 | 664 | If you want to avoid integrating with `gopls`, and are OK with the overhead of 665 | calling `goimports` from scratch on each save, you should be able to call both 666 | tools; for example, `goimports file.go && gofumpt file.go`. 667 | 668 | ### Contributing 669 | 670 | Issues and pull requests are welcome! Please open an issue to discuss a feature 671 | before sending a pull request. 672 | 673 | We also use the `#gofumpt` channel over at the 674 | [Gophers Slack](https://invite.slack.golangbridge.org/) to chat. 675 | 676 | When reporting a formatting bug, insert a `//gofumpt:diagnose` comment. 677 | The comment will be rewritten to include useful debugging information. 678 | For instance: 679 | 680 | ``` 681 | $ cat f.go 682 | package p 683 | 684 | //gofumpt:diagnose 685 | $ gofumpt f.go 686 | package p 687 | 688 | //gofumpt:diagnose v0.1.1-0.20211103104632-bdfa3b02e50a -lang=go1.16 689 | ``` 690 | 691 | ### License 692 | 693 | Note that much of the code is copied from Go's `gofmt` command. You can tell 694 | which files originate from the Go repository from their copyright headers. Their 695 | license file is `LICENSE.google`. 696 | 697 | `gofumpt`'s original source files are also under the 3-clause BSD license, with 698 | the separate file `LICENSE`. 699 | -------------------------------------------------------------------------------- /gofmt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "errors" 11 | "flag" 12 | "fmt" 13 | "go/ast" 14 | "go/parser" 15 | "go/scanner" 16 | "go/token" 17 | "io" 18 | "io/fs" 19 | "os" 20 | "path/filepath" 21 | "regexp" 22 | "runtime" 23 | "runtime/pprof" 24 | "strings" 25 | "sync" 26 | 27 | "golang.org/x/mod/modfile" 28 | "golang.org/x/sync/semaphore" 29 | 30 | gformat "mvdan.cc/gofumpt/format" 31 | "mvdan.cc/gofumpt/internal/govendor/diff" 32 | "mvdan.cc/gofumpt/internal/govendor/go/printer" 33 | gversion "mvdan.cc/gofumpt/internal/version" 34 | ) 35 | 36 | //go:generate go run gen_govendor.go 37 | //go:generate go run . -w internal/govendor 38 | 39 | var ( 40 | // main operation modes 41 | list = flag.Bool("l", false, "") 42 | write = flag.Bool("w", false, "") 43 | doDiff = flag.Bool("d", false, "") 44 | allErrors = flag.Bool("e", false, "") 45 | 46 | // debugging 47 | cpuprofile = flag.String("cpuprofile", "", "") 48 | 49 | // gofumpt's own flags 50 | langVersion = flag.String("lang", "", "") 51 | modulePath = flag.String("modpath", "", "") 52 | extraRules gformat.Extra 53 | showVersion = flag.Bool("version", false, "") 54 | 55 | // Deprecated 56 | rewriteRule = flag.String("r", "", "") 57 | simplifyAST = flag.Bool("s", false, "") 58 | ) 59 | 60 | func init() { flag.Var(&extraRules, "extra", "") } 61 | 62 | var version = "" 63 | 64 | // Keep these in sync with go/format/format.go. 65 | const ( 66 | tabWidth = 8 67 | printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers 68 | 69 | // printerNormalizeNumbers means to canonicalize number literal prefixes 70 | // and exponents while printing. See https://golang.org/doc/go1.13#gofmt. 71 | // 72 | // This value is defined in go/printer specifically for go/format and cmd/gofmt. 73 | printerNormalizeNumbers = 1 << 30 74 | ) 75 | 76 | // fdSem guards the number of concurrently-open file descriptors. 77 | // 78 | // For now, this is arbitrarily set to 200, based on the observation that many 79 | // platforms default to a kernel limit of 256. Ideally, perhaps we should derive 80 | // it from rlimit on platforms that support that system call. 81 | // 82 | // File descriptors opened from outside of this package are not tracked, 83 | // so this limit may be approximate. 84 | var fdSem = make(chan bool, 200) 85 | 86 | var ( 87 | fileSet = token.NewFileSet() // per process FileSet 88 | parserMode parser.Mode 89 | ) 90 | 91 | func usage() { 92 | fmt.Fprintf(os.Stderr, `usage: gofumpt [flags] [path ...] 93 | -version show version and exit 94 | 95 | -d display diffs instead of rewriting files 96 | -e report all errors (not just the first 10 on different lines) 97 | -l list files whose formatting differs from gofumpt's 98 | -w write result to (source) file instead of stdout 99 | -extra enable extra rules, e.g. -extra=group_params,clothe_returns 100 | 101 | -lang str target Go version in the form "go1.X" (default from go.mod) 102 | -modpath str Go module path containing the source file (default from go.mod) 103 | `) 104 | } 105 | 106 | func initParserMode() { 107 | parserMode = parser.ParseComments | parser.SkipObjectResolution 108 | if *allErrors { 109 | parserMode |= parser.AllErrors 110 | } 111 | } 112 | 113 | func isGoFilename(name string) bool { 114 | return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") 115 | } 116 | 117 | var rxCodeGenerated = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`) 118 | 119 | func isGenerated(file *ast.File) bool { 120 | for _, cg := range file.Comments { 121 | if cg.Pos() > file.Package { 122 | return false 123 | } 124 | for _, line := range cg.List { 125 | if rxCodeGenerated.MatchString(line.Text) { 126 | return true 127 | } 128 | } 129 | } 130 | return false 131 | } 132 | 133 | // A sequencer performs concurrent tasks that may write output, but emits that 134 | // output in a deterministic order. 135 | type sequencer struct { 136 | maxWeight int64 137 | sem *semaphore.Weighted // weighted by input bytes (an approximate proxy for memory overhead) 138 | prev <-chan *reporterState // 1-buffered 139 | } 140 | 141 | // newSequencer returns a sequencer that allows concurrent tasks up to maxWeight 142 | // and writes tasks' output to out and err. 143 | func newSequencer(maxWeight int64, out, err io.Writer) *sequencer { 144 | sem := semaphore.NewWeighted(maxWeight) 145 | prev := make(chan *reporterState, 1) 146 | prev <- &reporterState{out: out, err: err} 147 | return &sequencer{ 148 | maxWeight: maxWeight, 149 | sem: sem, 150 | prev: prev, 151 | } 152 | } 153 | 154 | // exclusive is a weight that can be passed to a sequencer to cause 155 | // a task to be executed without any other concurrent tasks. 156 | const exclusive = -1 157 | 158 | // Add blocks until the sequencer has enough weight to spare, then adds f as a 159 | // task to be executed concurrently. 160 | // 161 | // If the weight is either negative or larger than the sequencer's maximum 162 | // weight, Add blocks until all other tasks have completed, then the task 163 | // executes exclusively (blocking all other calls to Add until it completes). 164 | // 165 | // f may run concurrently in a goroutine, but its output to the passed-in 166 | // reporter will be sequential relative to the other tasks in the sequencer. 167 | // 168 | // If f invokes a method on the reporter, execution of that method may block 169 | // until the previous task has finished. (To maximize concurrency, f should 170 | // avoid invoking the reporter until it has finished any parallelizable work.) 171 | // 172 | // If f returns a non-nil error, that error will be reported after f's output 173 | // (if any) and will cause a nonzero final exit code. 174 | func (s *sequencer) Add(weight int64, f func(*reporter) error) { 175 | if weight < 0 || weight > s.maxWeight { 176 | weight = s.maxWeight 177 | } 178 | if err := s.sem.Acquire(context.TODO(), weight); err != nil { 179 | // Change the task from "execute f" to "report err". 180 | weight = 0 181 | f = func(*reporter) error { return err } 182 | } 183 | 184 | r := &reporter{prev: s.prev} 185 | next := make(chan *reporterState, 1) 186 | s.prev = next 187 | 188 | // Start f in parallel: it can run until it invokes a method on r, at which 189 | // point it will block until the previous task releases the output state. 190 | go func() { 191 | if err := f(r); err != nil { 192 | r.Report(err) 193 | } 194 | next <- r.getState() // Release the next task. 195 | s.sem.Release(weight) 196 | }() 197 | } 198 | 199 | // AddReport prints an error to s after the output of any previously-added 200 | // tasks, causing the final exit code to be nonzero. 201 | func (s *sequencer) AddReport(err error) { 202 | s.Add(0, func(*reporter) error { return err }) 203 | } 204 | 205 | // GetExitCode waits for all previously-added tasks to complete, then returns an 206 | // exit code for the sequence suitable for passing to os.Exit. 207 | func (s *sequencer) GetExitCode() int { 208 | c := make(chan int, 1) 209 | s.Add(0, func(r *reporter) error { 210 | c <- r.ExitCode() 211 | return nil 212 | }) 213 | return <-c 214 | } 215 | 216 | // A reporter reports output, warnings, and errors. 217 | type reporter struct { 218 | prev <-chan *reporterState 219 | state *reporterState 220 | } 221 | 222 | // reporterState carries the state of a reporter instance. 223 | // 224 | // Only one reporter at a time may have access to a reporterState. 225 | type reporterState struct { 226 | out, err io.Writer 227 | exitCode int 228 | } 229 | 230 | // getState blocks until any prior reporters are finished with the reporter 231 | // state, then returns the state for manipulation. 232 | func (r *reporter) getState() *reporterState { 233 | if r.state == nil { 234 | r.state = <-r.prev 235 | } 236 | return r.state 237 | } 238 | 239 | // Warnf emits a warning message to the reporter's error stream, 240 | // without changing its exit code. 241 | func (r *reporter) Warnf(format string, args ...any) { 242 | fmt.Fprintf(r.getState().err, format, args...) 243 | } 244 | 245 | // Write emits a slice to the reporter's output stream. 246 | // 247 | // Any error is returned to the caller, and does not otherwise affect the 248 | // reporter's exit code. 249 | func (r *reporter) Write(p []byte) (int, error) { 250 | return r.getState().out.Write(p) 251 | } 252 | 253 | // Report emits a non-nil error to the reporter's error stream, 254 | // changing its exit code to a nonzero value. 255 | func (r *reporter) Report(err error) { 256 | if err == nil { 257 | panic("Report with nil error") 258 | } 259 | st := r.getState() 260 | switch err.(type) { 261 | case printedDiff: 262 | st.exitCode = 1 263 | default: 264 | scanner.PrintError(st.err, err) 265 | st.exitCode = 2 266 | } 267 | } 268 | 269 | func (r *reporter) ExitCode() int { 270 | return r.getState().exitCode 271 | } 272 | 273 | type printedDiff struct{} 274 | 275 | func (printedDiff) Error() string { return "printed a diff, exiting with status code 1" } 276 | 277 | // If info == nil, we are formatting stdin instead of a file. 278 | // If in == nil, the source is the contents of the file with the given filename. 279 | func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter, explicit bool) error { 280 | src, err := readFile(filename, info, in) 281 | if err != nil { 282 | return err 283 | } 284 | 285 | fileSet := token.NewFileSet() 286 | fragmentOk := false 287 | if info == nil { 288 | // If we are formatting stdin, we accept a program fragment in lieu of a 289 | // complete source file. 290 | fragmentOk = true 291 | } 292 | file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, fragmentOk) 293 | if err != nil { 294 | return err 295 | } 296 | 297 | ast.SortImports(fileSet, file) 298 | 299 | // Apply gofumpt's changes before we print the code in gofumpt's format. 300 | 301 | // If either -lang or -modpath aren't set, fetch them from go.mod. 302 | lang := *langVersion 303 | modpath := *modulePath 304 | if lang == "" || modpath == "" { 305 | path, err := filepath.Abs(filename) 306 | if err != nil { 307 | return err 308 | } 309 | if mod := loadModule(filepath.Dir(path)); mod != nil { 310 | if lang == "" { 311 | if mod.file.Go == nil { 312 | // If the go directive is missing, go 1.16 is assumed. 313 | // https://go.dev/ref/mod#go-mod-file-go 314 | lang = "go1.16" 315 | } else { 316 | lang = "go" + mod.file.Go.Version 317 | } 318 | } 319 | if modpath == "" { 320 | modpath = mod.file.Module.Mod.Path 321 | } 322 | } 323 | } 324 | 325 | // We always apply the gofumpt formatting rules to explicit files, including stdin. 326 | // Otherwise, we don't apply them on generated files. 327 | // We also skip walking vendor directories entirely, but that happens elsewhere. 328 | if explicit || !isGenerated(file) { 329 | gformat.File(fileSet, file, gformat.Options{ 330 | LangVersion: lang, 331 | ModulePath: modpath, 332 | Extra: extraRules, 333 | }) 334 | } 335 | 336 | res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth}) 337 | if err != nil { 338 | return err 339 | } 340 | 341 | if !bytes.Equal(src, res) { 342 | // formatting has changed 343 | if *list { 344 | fmt.Fprintln(r, filename) 345 | } 346 | if *write { 347 | if info == nil { 348 | panic("-w should not have been allowed with stdin") 349 | } 350 | // make a temporary backup before overwriting original 351 | perm := info.Mode().Perm() 352 | bakname, err := backupFile(filename+".", src, perm) 353 | if err != nil { 354 | return err 355 | } 356 | fdSem <- true 357 | err = os.WriteFile(filename, res, perm) 358 | <-fdSem 359 | if err != nil { 360 | os.Rename(bakname, filename) 361 | return err 362 | } 363 | err = os.Remove(bakname) 364 | if err != nil { 365 | return err 366 | } 367 | } 368 | if *doDiff { 369 | newName := filepath.ToSlash(filename) 370 | oldName := newName + ".orig" 371 | r.Write(diff.Diff(oldName, src, newName, res)) 372 | return printedDiff{} 373 | } 374 | } 375 | 376 | if !*list && !*write && !*doDiff { 377 | _, err = r.Write(res) 378 | } 379 | 380 | return err 381 | } 382 | 383 | // readFile reads the contents of filename, described by info. 384 | // If in is non-nil, readFile reads directly from it. 385 | // Otherwise, readFile opens and reads the file itself, 386 | // with the number of concurrently-open files limited by fdSem. 387 | func readFile(filename string, info fs.FileInfo, in io.Reader) ([]byte, error) { 388 | if in == nil { 389 | fdSem <- true 390 | var err error 391 | f, err := os.Open(filename) 392 | if err != nil { 393 | return nil, err 394 | } 395 | in = f 396 | defer func() { 397 | f.Close() 398 | <-fdSem 399 | }() 400 | } 401 | 402 | // Compute the file's size and read its contents with minimal allocations. 403 | // 404 | // If we have the FileInfo from filepath.WalkDir, use it to make 405 | // a buffer of the right size and avoid ReadAll's reallocations. 406 | // 407 | // If the size is unknown (or bogus, or overflows an int), fall back to 408 | // a size-independent ReadAll. 409 | size := -1 410 | if info != nil && info.Mode().IsRegular() && int64(int(info.Size())) == info.Size() { 411 | size = int(info.Size()) 412 | } 413 | if size+1 <= 0 { 414 | // The file is not known to be regular, so we don't have a reliable size for it. 415 | var err error 416 | src, err := io.ReadAll(in) 417 | if err != nil { 418 | return nil, err 419 | } 420 | return src, nil 421 | } 422 | 423 | // We try to read size+1 bytes so that we can detect modifications: if we 424 | // read more than size bytes, then the file was modified concurrently. 425 | // (If that happens, we could, say, append to src to finish the read, or 426 | // proceed with a truncated buffer — but the fact that it changed at all 427 | // indicates a possible race with someone editing the file, so we prefer to 428 | // stop to avoid corrupting it.) 429 | src := make([]byte, size+1) 430 | n, err := io.ReadFull(in, src) 431 | switch err { 432 | case nil, io.EOF, io.ErrUnexpectedEOF: 433 | // io.ReadFull returns io.EOF (for an empty file) or io.ErrUnexpectedEOF 434 | // (for a non-empty file) if the file was changed unexpectedly. Continue 435 | // with comparing file sizes in those cases. 436 | default: 437 | return nil, err 438 | } 439 | if n < size { 440 | return nil, fmt.Errorf("error: size of %s changed during reading (from %d to %d bytes)", filename, size, n) 441 | } else if n > size { 442 | return nil, fmt.Errorf("error: size of %s changed during reading (from %d to >=%d bytes)", filename, size, len(src)) 443 | } 444 | return src[:n], nil 445 | } 446 | 447 | func main() { 448 | // Arbitrarily limit in-flight work to 2MiB times the number of threads. 449 | // 450 | // The actual overhead for the parse tree and output will depend on the 451 | // specifics of the file, but this at least keeps the footprint of the process 452 | // roughly proportional to GOMAXPROCS. 453 | maxWeight := (2 << 20) * int64(runtime.GOMAXPROCS(0)) 454 | s := newSequencer(maxWeight, os.Stdout, os.Stderr) 455 | 456 | // call gofmtMain in a separate function 457 | // so that it can use defer and have them 458 | // run before the exit. 459 | gofmtMain(s) 460 | os.Exit(s.GetExitCode()) 461 | } 462 | 463 | func gofmtMain(s *sequencer) { 464 | // Ensure our parsed files never start with base 1, 465 | // to ensure that using token.NoPos+1 will panic. 466 | fileSet.AddFile("gofumpt_base.go", 1, 10) 467 | 468 | flag.Usage = usage 469 | flag.Parse() 470 | 471 | if *simplifyAST { 472 | fmt.Fprintf(os.Stderr, "warning: -s is deprecated as it is always enabled\n") 473 | } 474 | if *rewriteRule != "" { 475 | fmt.Fprintf(os.Stderr, `the rewrite flag is no longer available; use "gofmt -r" instead`+"\n") 476 | os.Exit(2) 477 | } 478 | 479 | // Print the gofumpt version if the user asks for it. 480 | if *showVersion { 481 | fmt.Println(gversion.String(version)) 482 | return 483 | } 484 | 485 | if *cpuprofile != "" { 486 | fdSem <- true 487 | f, err := os.Create(*cpuprofile) 488 | if err != nil { 489 | s.AddReport(fmt.Errorf("creating cpu profile: %s", err)) 490 | return 491 | } 492 | defer func() { 493 | f.Close() 494 | <-fdSem 495 | }() 496 | pprof.StartCPUProfile(f) 497 | defer pprof.StopCPUProfile() 498 | } 499 | 500 | initParserMode() 501 | 502 | args := flag.Args() 503 | if len(args) == 0 { 504 | if *write { 505 | s.AddReport(fmt.Errorf("error: cannot use -w with standard input")) 506 | return 507 | } 508 | s.Add(0, func(r *reporter) error { 509 | // TODO: test explicit==true 510 | return processFile("", nil, os.Stdin, r, true) 511 | }) 512 | return 513 | } 514 | 515 | for _, arg := range args { 516 | // Walk each given argument as a directory tree. 517 | // If the argument is not a directory, it's always formatted as a Go file. 518 | // If the argument is a directory, we walk it, ignoring non-Go files. 519 | arg = filepath.Clean(arg) // ensure consistency 520 | if err := filepath.WalkDir(arg, func(path string, d fs.DirEntry, err error) error { 521 | explicit := path == arg 522 | switch { 523 | case err != nil: 524 | return err 525 | case d.IsDir(): 526 | if !explicit && shouldIgnore(path) { 527 | return filepath.SkipDir 528 | } 529 | return nil // simply recurse into directories 530 | case explicit: 531 | // non-directories given as explicit arguments are always formatted 532 | case !isGoFilename(d.Name()): 533 | return nil // skip walked non-Go files 534 | } 535 | info, err := d.Info() 536 | if err != nil { 537 | return err 538 | } 539 | s.Add(fileWeight(path, info), func(r *reporter) error { 540 | return processFile(path, info, nil, r, explicit) 541 | }) 542 | return nil 543 | }); err != nil { 544 | s.AddReport(err) 545 | } 546 | } 547 | } 548 | 549 | func shouldIgnore(path string) bool { 550 | switch filepath.Base(path) { 551 | case "vendor", "testdata": 552 | return true 553 | } 554 | path, err := filepath.Abs(path) 555 | if err != nil { 556 | return false // unclear how this could happen; don't ignore in any case 557 | } 558 | mod := loadModule(path) 559 | if mod == nil { 560 | return false // no module file to declare ignore paths 561 | } 562 | relPath, err := filepath.Rel(mod.absDir, path) 563 | if err != nil { 564 | return false // unclear how this could happen; don't ignore in any case 565 | } 566 | relPath = normalizePath(relPath) 567 | for _, ignore := range mod.file.Ignore { 568 | if matchIgnore(ignore.Path, relPath) { 569 | return true 570 | } 571 | } 572 | return false 573 | } 574 | 575 | // normalizePath adds slashes to the front and end of the given path. 576 | func normalizePath(path string) string { 577 | path = filepath.ToSlash(path) // ensure Windows support 578 | if !strings.HasPrefix(path, "/") { 579 | path = "/" + path 580 | } 581 | if !strings.HasSuffix(path, "/") { 582 | path += "/" 583 | } 584 | return path 585 | } 586 | 587 | func matchIgnore(ignore, relPath string) bool { 588 | ignore, rooted := strings.CutPrefix(ignore, "./") 589 | ignore = normalizePath(ignore) 590 | // Note that we only match the directory to be ignored itself, 591 | // and not any directories underneath it. 592 | // This way, using `gofumpt -w ignored` allows `ignored/subdir` to be formatted. 593 | if rooted { 594 | return relPath == ignore 595 | } 596 | return strings.HasSuffix(relPath, ignore) 597 | } 598 | 599 | // A nil entry means the directory is not part of a Go module, 600 | // or a go.mod file was found but it's invalid. 601 | // A non-nil entry means this directory, or a parent, is in a valid Go module. 602 | var cachedModuleByDir sync.Map // map[dirString]*cachedModfile 603 | 604 | type cachedModule struct { 605 | absDir string // the directory where the go.mod file was found 606 | file *modfile.File 607 | } 608 | 609 | func loadModule(dir string) *cachedModule { 610 | if cached, ok := cachedModuleByDir.Load(dir); ok { 611 | mf, _ := cached.(*cachedModule) 612 | return mf 613 | } 614 | mod := func() *cachedModule { 615 | path := filepath.Join(dir, "go.mod") 616 | fdSem <- true 617 | data, err := os.ReadFile(path) 618 | <-fdSem 619 | if errors.Is(err, fs.ErrNotExist) { 620 | parent := filepath.Dir(dir) 621 | if parent == "." { 622 | panic("loadModule was not given an absolute path?") 623 | } 624 | if parent == dir { 625 | return nil // reached the filesystem root 626 | } 627 | return loadModule(parent) // try the parent directory 628 | } 629 | if err != nil { 630 | return nil // some other file reading error 631 | } 632 | file, err := modfile.Parse(filepath.Join(dir, "go.mod"), data, nil) 633 | if err != nil { 634 | return nil // invalid go.mod file 635 | } 636 | return &cachedModule{ 637 | absDir: dir, 638 | file: file, 639 | } 640 | }() 641 | if mod != nil { 642 | cachedModuleByDir.Store(dir, mod) 643 | } else { 644 | cachedModuleByDir.Store(dir, nil) 645 | } 646 | return mod 647 | } 648 | 649 | func fileWeight(path string, info fs.FileInfo) int64 { 650 | if info == nil { 651 | return exclusive 652 | } 653 | if info.Mode().Type() == fs.ModeSymlink { 654 | var err error 655 | info, err = os.Stat(path) 656 | if err != nil { 657 | return exclusive 658 | } 659 | } 660 | if !info.Mode().IsRegular() { 661 | // For non-regular files, FileInfo.Size is system-dependent and thus not a 662 | // reliable indicator of weight. 663 | return exclusive 664 | } 665 | return info.Size() 666 | } 667 | 668 | const chmodSupported = runtime.GOOS != "windows" 669 | 670 | // backupFile writes data to a new file named filename with permissions perm, 671 | // with