├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── CHANGELOG.md ├── LICENSE ├── LICENSE.google ├── README.md ├── doc.go ├── format ├── format.go ├── format_test.go ├── fuzz_test.go ├── rewrite.go ├── simplify.go └── testdata │ └── fuzz │ └── FuzzFormat │ ├── 18c862f09f82fe57f536e7ab4b1bd63daecc2cba8189530bb0eb77b8cef6f798 │ └── 948d1d5be3c838b207d345a3ac57e97bb3b77788cb5039a65994967490c49baa ├── gen_govendor.go ├── go.mod ├── go.sum ├── gofmt.go ├── internal.go ├── internal ├── govendor │ ├── diff │ │ └── diff.go │ ├── go │ │ ├── doc │ │ │ └── comment │ │ │ │ ├── doc.go │ │ │ │ ├── html.go │ │ │ │ ├── markdown.go │ │ │ │ ├── parse.go │ │ │ │ ├── print.go │ │ │ │ ├── std.go │ │ │ │ └── text.go │ │ ├── format │ │ │ ├── format.go │ │ │ └── internal.go │ │ └── printer │ │ │ ├── comment.go │ │ │ ├── gobuild.go │ │ │ ├── nodes.go │ │ │ └── printer.go │ └── version.txt └── version │ └── version.go ├── main_test.go ├── testdata ├── gofumpt-external │ ├── go.mod │ ├── go.sum │ └── main.go └── script │ ├── assignment-newlines.txtar │ ├── block-empty.txtar │ ├── block-single.txtar │ ├── cgo.txtar │ ├── clothe-returns.txtar │ ├── comment-spaced.txtar │ ├── composite-literals-leading-lines.txtar │ ├── composite-multiline.txtar │ ├── decl-group-many.txtar │ ├── decl-group-single.txtar │ ├── decls-separated.txtar │ ├── deprecated-flags.txtar │ ├── diagnose.txtar │ ├── diff.txtar │ ├── func-merge-parameters.txtar │ ├── func-newlines.txtar │ ├── generated.txtar │ ├── gomod.txtar │ ├── ignore-dirs.txtar │ ├── interface.txtar │ ├── linedirectives.txtar │ ├── long-lines.txtar │ ├── missing-import.txtar │ ├── newline-errcheck.txtar │ ├── octal-literals.txtar │ ├── short-case.txtar │ ├── short-decl.txtar │ ├── simplify.txtar │ ├── std-imports.txtar │ ├── typeparams.txtar │ └── workspaces.txtar └── ulimit_linux_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # To prevent CRLF breakages on Windows for fragile files, like testdata. 2 | * -text 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mvdan 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [1.23.x, 1.24.x] 8 | os: [ubuntu-latest, macos-latest, windows-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - uses: actions/setup-go@v5 12 | with: 13 | go-version: ${{ matrix.go-version }} 14 | - uses: actions/checkout@v4 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.24.x' 21 | run: diff <(echo -n) <(gofmt -s -d .) 22 | - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.24.x' 23 | run: go vet ./... 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.8.0] - 2025-04-13 4 | 5 | This release is based on Go 1.24's gofmt, and requires Go 1.23 or later. 6 | 7 | The following changes are included: 8 | 9 | * Fail with `-d` if formatting any file resulted in a diff - #114 10 | * Do not panic when a `go.mod` file is missing a `go` directive - #317 11 | 12 | ## [v0.7.0] - 2024-08-16 13 | 14 | This release is based on Go 1.23.0's gofmt, and requires Go 1.22 or later. 15 | 16 | The following changes are included: 17 | 18 | * Group `internal/...` imported packages as standard library - #307 19 | 20 | ## [v0.6.0] - 2024-01-28 21 | 22 | This release is based on Go 1.21's gofmt, and requires Go 1.20 or later. 23 | 24 | The following changes are included: 25 | 26 | * Support `go` version strings from newer go.mod files - [#280] 27 | * Consider simple error checks even if they use the `=` operator - [#271] 28 | * Ignore `//line` directives to avoid panics - [#288] 29 | 30 | ## [v0.5.0] - 2023-04-09 31 | 32 | This release is based on Go 1.20's gofmt, and requires Go 1.19 or later. 33 | 34 | The biggest change in this release is that we now vendor copies of the packages 35 | `go/format`, `go/printer`, and `go/doc/comment` on top of `cmd/gofmt` itself. 36 | This allows for each gofumpt release to format code in exactly the same way 37 | no matter what Go version is used to build it, as Go versions can change those 38 | three packages in ways that alter formatting behavior. 39 | 40 | This vendoring adds a small amount of duplication when using the 41 | `mvdan.cc/gofumpt/format` library, but it's the only way to make gofumpt 42 | versions consistent in their behavior and formatting, just like gofmt. 43 | 44 | The jump to Go 1.20's `go/printer` should also bring a small performance 45 | improvement, as we contributed patches to make printing about 25% faster: 46 | 47 | * https://go.dev/cl/412555 48 | * https://go.dev/cl/412557 49 | * https://go.dev/cl/424924 50 | 51 | The following changes are included as well: 52 | 53 | * Skip `testdata` dirs by default like we already do for `vendor` - [#260] 54 | * Avoid inserting newlines incorrectly in some func signatures - [#235] 55 | * Avoid joining some comments with the previous line - [#256] 56 | * Fix `gofumpt -version` for release archives - [#253] 57 | 58 | ## [v0.4.0] - 2022-09-27 59 | 60 | This release is based on Go 1.19's gofmt, and requires Go 1.18 or later. 61 | We recommend building gofumpt with Go 1.19 for the best formatting results. 62 | 63 | The jump from Go 1.18 brings diffing in pure Go, removing the need to exec `diff`, 64 | and a small parsing speed-up thanks to `go/parser.SkipObjectResolution`. 65 | 66 | The following formatting fixes are included as well: 67 | 68 | * Allow grouping declarations with comments - [#212] 69 | * Properly measure the length of case clauses - [#217] 70 | * Fix a few crashes found by Go's native fuzzing 71 | 72 | ## [v0.3.1] - 2022-03-21 73 | 74 | This bugfix release resolves a number of issues: 75 | 76 | * Avoid "too many open files" error regression introduced by [v0.3.0] - [#208] 77 | * Use the `go.mod` relative to each Go file when deriving flag defaults - [#211] 78 | * Remove unintentional debug prints when directly formatting files 79 | 80 | ## [v0.3.0] - 2022-02-22 81 | 82 | This is gofumpt's third major release, based on Go 1.18's gofmt. 83 | The jump from Go 1.17's gofmt should bring a noticeable speed-up, 84 | as the tool can now format many files concurrently. 85 | On an 8-core laptop, formatting a large codebase is 4x as fast. 86 | 87 | The following [formatting rules](https://github.com/mvdan/gofumpt#Added-rules) are added: 88 | 89 | * Functions should separate `) {` where the indentation helps readability 90 | * Field lists should not have leading or trailing empty lines 91 | 92 | The following changes are included as well: 93 | 94 | * Generated files are now fully formatted when given as explicit arguments 95 | * Prepare for Go 1.18's module workspaces, which could cause errors 96 | * Import paths sharing a prefix with the current module path are no longer 97 | grouped with standard library imports 98 | * `format.Options` gains a `ModulePath` field per the last bullet point 99 | 100 | ## [v0.2.1] - 2021-12-12 101 | 102 | This bugfix release resolves a number of issues: 103 | 104 | * Add deprecated flags `-s` and `-r` once again, now giving useful errors 105 | * Avoid a panic with certain function declaration styles 106 | * Don't group interface members of different kinds 107 | * Account for leading comments in composite literals 108 | 109 | ## [v0.2.0] - 2021-11-10 110 | 111 | This is gofumpt's second major release, based on Go 1.17's gofmt. 112 | The jump from Go 1.15's gofmt should bring a mild speed-up, 113 | as walking directories with `filepath.WalkDir` uses fewer syscalls. 114 | 115 | gofumports is now removed, after being deprecated in [v0.1.0]. 116 | Its main purpose was IDE integration; it is now recommended to use gopls, 117 | which in turn implements goimports and supports gofumpt natively. 118 | IDEs which don't integrate with gopls (such as GoLand) implement goimports too, 119 | so it is safe to use gofumpt as their "format on save" command. 120 | See the [installation instructions](https://github.com/mvdan/gofumpt#Installation) 121 | for more details. 122 | 123 | The following [formatting rules](https://github.com/mvdan/gofumpt#Added-rules) are added: 124 | 125 | * Composite literals should not have leading or trailing empty lines 126 | * No empty lines following an assignment operator 127 | * Functions using an empty line for readability should use a `) {` line instead 128 | * Remove unnecessary empty lines from interfaces 129 | 130 | Finally, the following changes are made to the gofumpt tool: 131 | 132 | * Initial support for Go 1.18's type parameters is added 133 | * The `-r` flag is removed in favor of `gofmt -r` 134 | * The `-s` flag is removed as it is always enabled 135 | * Vendor directories are skipped unless given as explicit arguments 136 | * The added rules are not applied to generated Go files 137 | * The `format` Go API now also applies the `gofmt -s` simplification 138 | * Add support for `//gofumpt:diagnose` comments 139 | 140 | ## [v0.1.1] - 2021-03-11 141 | 142 | This bugfix release backports fixes for a few issues: 143 | 144 | * Keep leading empty lines in func bodies if they help readability 145 | * Avoid breaking comment alignment on empty field lists 146 | * Add support for `//go-sumtype:` directives 147 | 148 | ## [v0.1.0] - 2021-01-05 149 | 150 | This is gofumpt's first release, based on Go 1.15.x. It solidifies the features 151 | which have worked well for over a year. 152 | 153 | This release will be the last to include `gofumports`, the fork of `goimports` 154 | which applies `gofumpt`'s rules on top of updating the Go import lines. Users 155 | who were relying on `goimports` in their editors or IDEs to apply both `gofumpt` 156 | and `goimports` in a single step should switch to gopls, the official Go 157 | language server. It is supported by many popular editors such as VS Code and 158 | Vim, and already bundles gofumpt support. Instructions are available [in the 159 | README](https://github.com/mvdan/gofumpt). 160 | 161 | `gofumports` also added maintenance work and potential confusion to end users. 162 | In the future, there will only be one way to use `gofumpt` from the command 163 | line. We also have a [Go API](https://pkg.go.dev/mvdan.cc/gofumpt/format) for 164 | those building programs with gofumpt. 165 | 166 | Finally, this release adds the `-version` flag, to print the tool's own version. 167 | The flag will work for "master" builds too. 168 | 169 | [v0.7.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.7.0 170 | 171 | [v0.6.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.6.0 172 | [#271]: https://github.com/mvdan/gofumpt/issues/271 173 | [#280]: https://github.com/mvdan/gofumpt/issues/280 174 | [#288]: https://github.com/mvdan/gofumpt/issues/288 175 | 176 | [v0.5.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.5.0 177 | [#235]: https://github.com/mvdan/gofumpt/issues/235 178 | [#253]: https://github.com/mvdan/gofumpt/issues/253 179 | [#256]: https://github.com/mvdan/gofumpt/issues/256 180 | [#260]: https://github.com/mvdan/gofumpt/issues/260 181 | 182 | [v0.4.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.4.0 183 | [#212]: https://github.com/mvdan/gofumpt/issues/212 184 | [#217]: https://github.com/mvdan/gofumpt/issues/217 185 | 186 | [v0.3.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.3.1 187 | [#208]: https://github.com/mvdan/gofumpt/issues/208 188 | [#211]: https://github.com/mvdan/gofumpt/pull/211 189 | 190 | [v0.3.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.3.0 191 | [v0.2.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.2.1 192 | [v0.2.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.2.0 193 | [v0.1.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.1.1 194 | [v0.1.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.1.0 195 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.24.0, and requires Go 1.23 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.24.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 | Finally, note that the `-r` rewrite flag is removed in favor of `gofmt -r`, 27 | and the `-s` flag is hidden as it is always enabled. 28 | 29 | ### Added rules 30 | 31 | **No empty lines following an assignment operator** 32 | 33 |
Example 34 | 35 | ```go 36 | func foo() { 37 | foo := 38 | "bar" 39 | } 40 | ``` 41 | 42 | ```go 43 | func foo() { 44 | foo := "bar" 45 | } 46 | ``` 47 | 48 |
49 | 50 | **No empty lines around function bodies** 51 | 52 |
Example 53 | 54 | ```go 55 | func foo() { 56 | 57 | println("bar") 58 | 59 | } 60 | ``` 61 | 62 | ```go 63 | func foo() { 64 | println("bar") 65 | } 66 | ``` 67 | 68 |
69 | 70 | **Functions should separate `) {` where the indentation helps readability** 71 | 72 |
Example 73 | 74 | ```go 75 | func foo(s string, 76 | i int) { 77 | println("bar") 78 | } 79 | 80 | // With an empty line it's slightly better, but still not great. 81 | func bar(s string, 82 | i int) { 83 | 84 | println("bar") 85 | } 86 | ``` 87 | 88 | ```go 89 | func foo(s string, 90 | i int, 91 | ) { 92 | println("bar") 93 | } 94 | 95 | // With an empty line it's slightly better, but still not great. 96 | func bar(s string, 97 | i int, 98 | ) { 99 | println("bar") 100 | } 101 | ``` 102 | 103 |
104 | 105 | **No empty lines around a lone statement (or comment) in a block** 106 | 107 |
Example 108 | 109 | ```go 110 | if err != nil { 111 | 112 | return err 113 | } 114 | ``` 115 | 116 | ```go 117 | if err != nil { 118 | return err 119 | } 120 | ``` 121 | 122 |
123 | 124 | **No empty lines before a simple error check** 125 | 126 |
Example 127 | 128 | ```go 129 | foo, err := processFoo() 130 | 131 | if err != nil { 132 | return err 133 | } 134 | ``` 135 | 136 | ```go 137 | foo, err := processFoo() 138 | if err != nil { 139 | return err 140 | } 141 | ``` 142 | 143 |
144 | 145 | **Composite literals should use newlines consistently** 146 | 147 |
Example 148 | 149 | ```go 150 | // A newline before or after an element requires newlines for the opening and 151 | // closing braces. 152 | var ints = []int{1, 2, 153 | 3, 4} 154 | 155 | // A newline between consecutive elements requires a newline between all 156 | // elements. 157 | var matrix = [][]int{ 158 | {1}, 159 | {2}, { 160 | 3, 161 | }, 162 | } 163 | ``` 164 | 165 | ```go 166 | var ints = []int{ 167 | 1, 2, 168 | 3, 4, 169 | } 170 | 171 | var matrix = [][]int{ 172 | {1}, 173 | {2}, 174 | { 175 | 3, 176 | }, 177 | } 178 | ``` 179 | 180 |
181 | 182 | **Empty field lists should use a single line** 183 | 184 |
Example 185 | 186 | ```go 187 | var V interface { 188 | } = 3 189 | 190 | type T struct { 191 | } 192 | 193 | func F( 194 | ) 195 | ``` 196 | 197 | ```go 198 | var V interface{} = 3 199 | 200 | type T struct{} 201 | 202 | func F() 203 | ``` 204 | 205 |
206 | 207 | **`std` imports must be in a separate group at the top** 208 | 209 |
Example 210 | 211 | ```go 212 | import ( 213 | "foo.com/bar" 214 | 215 | "io" 216 | 217 | "io/ioutil" 218 | ) 219 | ``` 220 | 221 | ```go 222 | import ( 223 | "io" 224 | "io/ioutil" 225 | 226 | "foo.com/bar" 227 | ) 228 | ``` 229 | 230 |
231 | 232 | **Short case clauses should take a single line** 233 | 234 |
Example 235 | 236 | ```go 237 | switch c { 238 | case 'a', 'b', 239 | 'c', 'd': 240 | } 241 | ``` 242 | 243 | ```go 244 | switch c { 245 | case 'a', 'b', 'c', 'd': 246 | } 247 | ``` 248 | 249 |
250 | 251 | **Multiline top-level declarations must be separated by empty lines** 252 | 253 |
Example 254 | 255 | ```go 256 | func foo() { 257 | println("multiline foo") 258 | } 259 | func bar() { 260 | println("multiline bar") 261 | } 262 | ``` 263 | 264 | ```go 265 | func foo() { 266 | println("multiline foo") 267 | } 268 | 269 | func bar() { 270 | println("multiline bar") 271 | } 272 | ``` 273 | 274 |
275 | 276 | **Single var declarations should not be grouped with parentheses** 277 | 278 |
Example 279 | 280 | ```go 281 | var ( 282 | foo = "bar" 283 | ) 284 | ``` 285 | 286 | ```go 287 | var foo = "bar" 288 | ``` 289 | 290 |
291 | 292 | **Contiguous top-level declarations should be grouped together** 293 | 294 |
Example 295 | 296 | ```go 297 | var nicer = "x" 298 | var with = "y" 299 | var alignment = "z" 300 | ``` 301 | 302 | ```go 303 | var ( 304 | nicer = "x" 305 | with = "y" 306 | alignment = "z" 307 | ) 308 | ``` 309 | 310 |
311 | 312 | **Simple var-declaration statements should use short assignments** 313 | 314 |
Example 315 | 316 | ```go 317 | var s = "somestring" 318 | ``` 319 | 320 | ```go 321 | s := "somestring" 322 | ``` 323 | 324 |
325 | 326 | **The `-s` code simplification flag is enabled by default** 327 | 328 |
Example 329 | 330 | ```go 331 | var _ = [][]int{[]int{1}} 332 | ``` 333 | 334 | ```go 335 | var _ = [][]int{{1}} 336 | ``` 337 | 338 |
339 | 340 | **Octal integer literals should use the `0o` prefix on modules using Go 1.13 and later** 341 | 342 |
Example 343 | 344 | ```go 345 | const perm = 0755 346 | ``` 347 | 348 | ```go 349 | const perm = 0o755 350 | ``` 351 | 352 |
353 | 354 | **Comments which aren't Go directives should start with a whitespace** 355 | 356 |
Example 357 | 358 | ```go 359 | //go:noinline 360 | 361 | //Foo is awesome. 362 | func Foo() {} 363 | ``` 364 | 365 | ```go 366 | //go:noinline 367 | 368 | // Foo is awesome. 369 | func Foo() {} 370 | ``` 371 | 372 |
373 | 374 | **Composite literals should not have leading or trailing empty lines** 375 | 376 |
Example 377 | 378 | ```go 379 | var _ = []string{ 380 | 381 | "foo", 382 | 383 | } 384 | 385 | var _ = map[string]string{ 386 | 387 | "foo": "bar", 388 | 389 | } 390 | ``` 391 | 392 | ```go 393 | var _ = []string{ 394 | "foo", 395 | } 396 | 397 | var _ = map[string]string{ 398 | "foo": "bar", 399 | } 400 | ``` 401 | 402 |
403 | 404 | **Field lists should not have leading or trailing empty lines** 405 | 406 |
Example 407 | 408 | ```go 409 | type Person interface { 410 | 411 | Name() string 412 | 413 | Age() int 414 | 415 | } 416 | 417 | type ZeroFields struct { 418 | 419 | // No fields are needed here. 420 | 421 | } 422 | ``` 423 | 424 | ```go 425 | type Person interface { 426 | Name() string 427 | 428 | Age() int 429 | } 430 | 431 | type ZeroFields struct { 432 | // No fields are needed here. 433 | } 434 | ``` 435 | 436 |
437 | 438 | **Avoid naked returns for the sake of clarity** 439 | 440 |
Example 441 | 442 | ```go 443 | func Foo() (err error) { 444 | return 445 | } 446 | ``` 447 | 448 | ```go 449 | func Foo() (err error) { 450 | return err 451 | } 452 | ``` 453 | 454 |
455 | 456 | ### Extra rules behind `-extra` 457 | 458 | **Adjacent parameters with the same type should be grouped together** 459 | 460 |
Example 461 | 462 | ```go 463 | func Foo(bar string, baz string) {} 464 | ``` 465 | 466 | ```go 467 | func Foo(bar, baz string) {} 468 | ``` 469 | 470 |
471 | 472 | ### Installation 473 | 474 | `gofumpt` is a replacement for `gofmt`, so you can simply `go install` it as 475 | described at the top of this README and use it. 476 | 477 | When using an IDE or editor with Go integration based on `gopls`, 478 | it's best to configure the editor to use the `gofumpt` support built into `gopls`. 479 | 480 | The instructions below show how to set up `gofumpt` for some of the 481 | major editors out there. 482 | 483 | #### Visual Studio Code 484 | 485 | Enable the language server following [the official docs](https://github.com/golang/vscode-go#readme), 486 | and then enable gopls's `gofumpt` option. Note that VS Code will complain about 487 | the `gopls` settings, but they will still work. 488 | 489 | ```json 490 | "go.useLanguageServer": true, 491 | "gopls": { 492 | "formatting.gofumpt": true, 493 | }, 494 | ``` 495 | 496 | #### GoLand 497 | 498 | GoLand doesn't use `gopls` so it should be configured to use `gofumpt` directly. 499 | Once `gofumpt` is installed, follow the steps below: 500 | 501 | - Open **Settings** (File > Settings) 502 | - Open the **Tools** section 503 | - Find the *File Watchers* sub-section 504 | - Click on the `+` on the right side to add a new file watcher 505 | - Choose *Custom Template* 506 | 507 | When a window asks for settings, you can enter the following: 508 | 509 | * File Types: Select all .go files 510 | * Scope: Project Files 511 | * Program: Select your `gofumpt` executable 512 | * Arguments: `-w $FilePath$` 513 | * Output path to refresh: `$FilePath$` 514 | * Working directory: `$ProjectFileDir$` 515 | * Environment variables: `GOROOT=$GOROOT$;GOPATH=$GOPATH$;PATH=$GoBinDirs$` 516 | 517 | To avoid unnecessary runs, you should disable all checkboxes in the *Advanced* section. 518 | 519 | #### Vim 520 | 521 | The configuration depends on the plugin you are using: [vim-go](https://github.com/fatih/vim-go) 522 | or [govim](https://github.com/govim/govim). 523 | 524 | ##### vim-go 525 | 526 | To configure `gopls` to use `gofumpt`: 527 | 528 | ```vim 529 | let g:go_fmt_command="gopls" 530 | let g:go_gopls_gofumpt=1 531 | ``` 532 | 533 | ##### govim 534 | 535 | To configure `gopls` to use `gofumpt`: 536 | 537 | ```vim 538 | call govim#config#Set("Gofumpt", 1) 539 | ``` 540 | 541 | #### Neovim 542 | 543 | When using [`lspconfig`](https://github.com/neovim/nvim-lspconfig), pass the `gofumpt` setting to `gopls`: 544 | 545 | ```lua 546 | require('lspconfig').gopls.setup({ 547 | settings = { 548 | gopls = { 549 | gofumpt = true 550 | } 551 | } 552 | }) 553 | ``` 554 | 555 | #### Emacs 556 | 557 | For [lsp-mode](https://emacs-lsp.github.io/lsp-mode/) users on version 8.0.0 or higher: 558 | 559 | ```elisp 560 | (setq lsp-go-use-gofumpt t) 561 | ``` 562 | 563 | For users of `lsp-mode` before `8.0.0`: 564 | 565 | ```elisp 566 | (lsp-register-custom-settings 567 | '(("gopls.gofumpt" t))) 568 | ``` 569 | 570 | For [eglot](https://github.com/joaotavora/eglot) users: 571 | 572 | ```elisp 573 | (setq-default eglot-workspace-configuration 574 | '((:gopls . ((gofumpt . t))))) 575 | ``` 576 | 577 | #### Helix 578 | 579 | When using the `gopls` language server, modify the Go settings in `~/.config/helix/languages.toml`: 580 | 581 | ```toml 582 | [language-server.gopls.config] 583 | "formatting.gofumpt" = true 584 | ``` 585 | 586 | #### Sublime Text 587 | 588 | With ST4, install the Sublime Text LSP extension according to [the documentation](https://github.com/sublimelsp/LSP), 589 | and enable `gopls`'s `gofumpt` option in the LSP package settings, 590 | including setting `lsp_format_on_save` to `true`. 591 | 592 | ```json 593 | "lsp_format_on_save": true, 594 | "clients": 595 | { 596 | "gopls": 597 | { 598 | "enabled": true, 599 | "initializationOptions": { 600 | "gofumpt": true, 601 | } 602 | } 603 | } 604 | ``` 605 | 606 | ### Roadmap 607 | 608 | This tool is a place to experiment. In the long term, the features that work 609 | well might be proposed for `gofmt` itself. 610 | 611 | The tool is also compatible with `gofmt` and is aimed to be stable, so you can 612 | rely on it for your code as long as you pin a version of it. 613 | 614 | ### Frequently Asked Questions 615 | 616 | > Why attempt to replace `gofmt` instead of building on top of it? 617 | 618 | Our design is to build on top of `gofmt`, and we'll never add rules which 619 | disagree with its formatting. So we extend `gofmt` rather than compete with it. 620 | 621 | The tool is a modified copy of `gofmt`, for the purpose of allowing its use as a 622 | drop-in replacement in editors and scripts. 623 | 624 | > Why are my module imports being grouped with standard library imports? 625 | 626 | Any import paths that don't start with a domain name like `foo.com` are 627 | effectively [reserved by the Go toolchain](https://github.com/golang/go/issues/32819). 628 | Third party modules should either start with a domain name, 629 | even a local one like `foo.local`, or use [a reserved path prefix](https://github.com/golang/go/issues/37641). 630 | 631 | For backwards compatibility with modules set up before these rules were clear, 632 | `gofumpt` will treat any import path sharing a prefix with the current module 633 | path as third party. For example, if the current module is `mycorp/mod1`, then 634 | all import paths in `mycorp/...` will be considered third party. 635 | 636 | > How can I use `gofumpt` if I already use `goimports` to replace `gofmt`? 637 | 638 | Most editors have replaced the `goimports` program with the same functionality 639 | provided by a language server like `gopls`. This mechanism is significantly 640 | faster and more powerful, since the language server has more information that is 641 | kept up to date, necessary to add missing imports. 642 | 643 | As such, the general recommendation is to let your editor fix your imports - 644 | either via `gopls`, such as VSCode or vim-go, or via their own custom 645 | implementation, such as GoLand. Then follow the install instructions above to 646 | enable the use of `gofumpt` instead of `gofmt`. 647 | 648 | If you want to avoid integrating with `gopls`, and are OK with the overhead of 649 | calling `goimports` from scratch on each save, you should be able to call both 650 | tools; for example, `goimports file.go && gofumpt file.go`. 651 | 652 | ### Contributing 653 | 654 | Issues and pull requests are welcome! Please open an issue to discuss a feature 655 | before sending a pull request. 656 | 657 | We also use the `#gofumpt` channel over at the 658 | [Gophers Slack](https://invite.slack.golangbridge.org/) to chat. 659 | 660 | When reporting a formatting bug, insert a `//gofumpt:diagnose` comment. 661 | The comment will be rewritten to include useful debugging information. 662 | For instance: 663 | 664 | ``` 665 | $ cat f.go 666 | package p 667 | 668 | //gofumpt:diagnose 669 | $ gofumpt f.go 670 | package p 671 | 672 | //gofumpt:diagnose v0.1.1-0.20211103104632-bdfa3b02e50a -lang=go1.16 673 | ``` 674 | 675 | ### License 676 | 677 | Note that much of the code is copied from Go's `gofmt` command. You can tell 678 | which files originate from the Go repository from their copyright headers. Their 679 | license file is `LICENSE.google`. 680 | 681 | `gofumpt`'s original source files are also under the 3-clause BSD license, with 682 | the separate file `LICENSE`. 683 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mvdan.cc/gofumpt 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/go-quicktest/qt v1.101.0 7 | github.com/google/go-cmp v0.6.0 8 | github.com/rogpeppe/go-internal v1.14.1 9 | golang.org/x/mod v0.24.0 10 | golang.org/x/sync v0.13.0 11 | golang.org/x/sys v0.32.0 12 | golang.org/x/tools v0.32.0 13 | ) 14 | 15 | require ( 16 | github.com/kr/pretty v0.3.1 // indirect 17 | github.com/kr/text v0.2.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 5 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 15 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 16 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 17 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 18 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 19 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 20 | golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= 21 | golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= 22 | -------------------------------------------------------------------------------- /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 | "encoding/json" 11 | "flag" 12 | "fmt" 13 | "go/ast" 14 | "go/parser" 15 | "go/scanner" 16 | "go/token" 17 | "io" 18 | "io/fs" 19 | "os" 20 | "os/exec" 21 | "path/filepath" 22 | "regexp" 23 | "runtime" 24 | "runtime/pprof" 25 | "strings" 26 | "sync" 27 | 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 = flag.Bool("extra", false, "") 53 | showVersion = flag.Bool("version", false, "") 54 | 55 | // DEPRECATED 56 | rewriteRule = flag.String("r", "", "") 57 | simplifyAST = flag.Bool("s", false, "") 58 | ) 59 | 60 | var version = "" 61 | 62 | // Keep these in sync with go/format/format.go. 63 | const ( 64 | tabWidth = 8 65 | printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers 66 | 67 | // printerNormalizeNumbers means to canonicalize number literal prefixes 68 | // and exponents while printing. See https://golang.org/doc/go1.13#gofmt. 69 | // 70 | // This value is defined in go/printer specifically for go/format and cmd/gofmt. 71 | printerNormalizeNumbers = 1 << 30 72 | ) 73 | 74 | // fdSem guards the number of concurrently-open file descriptors. 75 | // 76 | // For now, this is arbitrarily set to 200, based on the observation that many 77 | // platforms default to a kernel limit of 256. Ideally, perhaps we should derive 78 | // it from rlimit on platforms that support that system call. 79 | // 80 | // File descriptors opened from outside of this package are not tracked, 81 | // so this limit may be approximate. 82 | var fdSem = make(chan bool, 200) 83 | 84 | var ( 85 | fileSet = token.NewFileSet() // per process FileSet 86 | parserMode parser.Mode 87 | ) 88 | 89 | func usage() { 90 | fmt.Fprintf(os.Stderr, `usage: gofumpt [flags] [path ...] 91 | -version show version and exit 92 | 93 | -d display diffs instead of rewriting files 94 | -e report all errors (not just the first 10 on different lines) 95 | -l list files whose formatting differs from gofumpt's 96 | -w write result to (source) file instead of stdout 97 | -extra enable extra rules which should be vetted by a human 98 | 99 | -lang str target Go version in the form "go1.X" (default from go.mod) 100 | -modpath str Go module path containing the source file (default from go.mod) 101 | `) 102 | } 103 | 104 | func initParserMode() { 105 | parserMode = parser.ParseComments | parser.SkipObjectResolution 106 | if *allErrors { 107 | parserMode |= parser.AllErrors 108 | } 109 | } 110 | 111 | func isGoFile(f fs.DirEntry) bool { 112 | // ignore non-Go files 113 | name := f.Name() 114 | return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && !f.IsDir() 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 | dir := filepath.Dir(filename) 306 | mod, ok := moduleCacheByDir.Load(dir) 307 | if ok && mod != nil { 308 | mod := mod.(*module) 309 | if mod.Go == "" { 310 | // If the go directive is missing, go 1.16 is assumed. 311 | // https://go.dev/ref/mod#go-mod-file-go 312 | mod.Go = "1.16" 313 | } 314 | if lang == "" { 315 | lang = "go" + mod.Go 316 | } 317 | if modpath == "" { 318 | modpath = mod.Module.Path 319 | } 320 | } 321 | } 322 | 323 | // We always apply the gofumpt formatting rules to explicit files, including stdin. 324 | // Otherwise, we don't apply them on generated files. 325 | // We also skip walking vendor directories entirely, but that happens elsewhere. 326 | if explicit || !isGenerated(file) { 327 | gformat.File(fileSet, file, gformat.Options{ 328 | LangVersion: lang, 329 | ModulePath: modpath, 330 | ExtraRules: *extraRules, 331 | }) 332 | } 333 | 334 | res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth}) 335 | if err != nil { 336 | return err 337 | } 338 | 339 | if !bytes.Equal(src, res) { 340 | // formatting has changed 341 | if *list { 342 | fmt.Fprintln(r, filename) 343 | } 344 | if *write { 345 | if info == nil { 346 | panic("-w should not have been allowed with stdin") 347 | } 348 | // make a temporary backup before overwriting original 349 | perm := info.Mode().Perm() 350 | bakname, err := backupFile(filename+".", src, perm) 351 | if err != nil { 352 | return err 353 | } 354 | fdSem <- true 355 | err = os.WriteFile(filename, res, perm) 356 | <-fdSem 357 | if err != nil { 358 | os.Rename(bakname, filename) 359 | return err 360 | } 361 | err = os.Remove(bakname) 362 | if err != nil { 363 | return err 364 | } 365 | } 366 | if *doDiff { 367 | newName := filepath.ToSlash(filename) 368 | oldName := newName + ".orig" 369 | r.Write(diff.Diff(oldName, src, newName, res)) 370 | return printedDiff{} 371 | } 372 | } 373 | 374 | if !*list && !*write && !*doDiff { 375 | _, err = r.Write(res) 376 | } 377 | 378 | return err 379 | } 380 | 381 | // readFile reads the contents of filename, described by info. 382 | // If in is non-nil, readFile reads directly from it. 383 | // Otherwise, readFile opens and reads the file itself, 384 | // with the number of concurrently-open files limited by fdSem. 385 | func readFile(filename string, info fs.FileInfo, in io.Reader) ([]byte, error) { 386 | if in == nil { 387 | fdSem <- true 388 | var err error 389 | f, err := os.Open(filename) 390 | if err != nil { 391 | return nil, err 392 | } 393 | in = f 394 | defer func() { 395 | f.Close() 396 | <-fdSem 397 | }() 398 | } 399 | 400 | // Compute the file's size and read its contents with minimal allocations. 401 | // 402 | // If we have the FileInfo from filepath.WalkDir, use it to make 403 | // a buffer of the right size and avoid ReadAll's reallocations. 404 | // 405 | // If the size is unknown (or bogus, or overflows an int), fall back to 406 | // a size-independent ReadAll. 407 | size := -1 408 | if info != nil && info.Mode().IsRegular() && int64(int(info.Size())) == info.Size() { 409 | size = int(info.Size()) 410 | } 411 | if size+1 <= 0 { 412 | // The file is not known to be regular, so we don't have a reliable size for it. 413 | var err error 414 | src, err := io.ReadAll(in) 415 | if err != nil { 416 | return nil, err 417 | } 418 | return src, nil 419 | } 420 | 421 | // We try to read size+1 bytes so that we can detect modifications: if we 422 | // read more than size bytes, then the file was modified concurrently. 423 | // (If that happens, we could, say, append to src to finish the read, or 424 | // proceed with a truncated buffer — but the fact that it changed at all 425 | // indicates a possible race with someone editing the file, so we prefer to 426 | // stop to avoid corrupting it.) 427 | src := make([]byte, size+1) 428 | n, err := io.ReadFull(in, src) 429 | switch err { 430 | case nil, io.EOF, io.ErrUnexpectedEOF: 431 | // io.ReadFull returns io.EOF (for an empty file) or io.ErrUnexpectedEOF 432 | // (for a non-empty file) if the file was changed unexpectedly. Continue 433 | // with comparing file sizes in those cases. 434 | default: 435 | return nil, err 436 | } 437 | if n < size { 438 | return nil, fmt.Errorf("error: size of %s changed during reading (from %d to %d bytes)", filename, size, n) 439 | } else if n > size { 440 | return nil, fmt.Errorf("error: size of %s changed during reading (from %d to >=%d bytes)", filename, size, len(src)) 441 | } 442 | return src[:n], nil 443 | } 444 | 445 | func main() { 446 | // Arbitrarily limit in-flight work to 2MiB times the number of threads. 447 | // 448 | // The actual overhead for the parse tree and output will depend on the 449 | // specifics of the file, but this at least keeps the footprint of the process 450 | // roughly proportional to GOMAXPROCS. 451 | maxWeight := (2 << 20) * int64(runtime.GOMAXPROCS(0)) 452 | s := newSequencer(maxWeight, os.Stdout, os.Stderr) 453 | 454 | // call gofmtMain in a separate function 455 | // so that it can use defer and have them 456 | // run before the exit. 457 | gofmtMain(s) 458 | os.Exit(s.GetExitCode()) 459 | } 460 | 461 | func gofmtMain(s *sequencer) { 462 | // Ensure our parsed files never start with base 1, 463 | // to ensure that using token.NoPos+1 will panic. 464 | fileSet.AddFile("gofumpt_base.go", 1, 10) 465 | 466 | flag.Usage = usage 467 | flag.Parse() 468 | 469 | if *simplifyAST { 470 | fmt.Fprintf(os.Stderr, "warning: -s is deprecated as it is always enabled\n") 471 | } 472 | if *rewriteRule != "" { 473 | fmt.Fprintf(os.Stderr, `the rewrite flag is no longer available; use "gofmt -r" instead`+"\n") 474 | os.Exit(2) 475 | } 476 | 477 | // Print the gofumpt version if the user asks for it. 478 | if *showVersion { 479 | fmt.Println(gversion.String(version)) 480 | return 481 | } 482 | 483 | if *cpuprofile != "" { 484 | fdSem <- true 485 | f, err := os.Create(*cpuprofile) 486 | if err != nil { 487 | s.AddReport(fmt.Errorf("creating cpu profile: %s", err)) 488 | return 489 | } 490 | defer func() { 491 | f.Close() 492 | <-fdSem 493 | }() 494 | pprof.StartCPUProfile(f) 495 | defer pprof.StopCPUProfile() 496 | } 497 | 498 | initParserMode() 499 | 500 | args := flag.Args() 501 | if len(args) == 0 { 502 | if *write { 503 | s.AddReport(fmt.Errorf("error: cannot use -w with standard input")) 504 | return 505 | } 506 | s.Add(0, func(r *reporter) error { 507 | // TODO: test explicit==true 508 | return processFile("", nil, os.Stdin, r, true) 509 | }) 510 | return 511 | } 512 | 513 | for _, arg := range args { 514 | switch info, err := os.Stat(arg); { 515 | case err != nil: 516 | s.AddReport(err) 517 | case !info.IsDir(): 518 | // Non-directory arguments are always formatted. 519 | arg := arg 520 | s.Add(fileWeight(arg, info), func(r *reporter) error { 521 | return processFile(arg, info, nil, r, true) 522 | }) 523 | default: 524 | // Directories are walked, ignoring non-Go files. 525 | err := filepath.WalkDir(arg, func(path string, f fs.DirEntry, err error) error { 526 | // vendor and testdata directories are skipped, 527 | // unless they are explicitly passed as an argument. 528 | base := filepath.Base(path) 529 | if path != arg && (base == "vendor" || base == "testdata") { 530 | return filepath.SkipDir 531 | } 532 | 533 | if err != nil || !isGoFile(f) { 534 | return err 535 | } 536 | info, err := f.Info() 537 | if err != nil { 538 | s.AddReport(err) 539 | return nil 540 | } 541 | s.Add(fileWeight(path, info), func(r *reporter) error { 542 | return processFile(path, info, nil, r, false) 543 | }) 544 | return nil 545 | }) 546 | if err != nil { 547 | s.AddReport(err) 548 | } 549 | } 550 | } 551 | } 552 | 553 | type module struct { 554 | Go string 555 | Module struct { 556 | Path string 557 | } 558 | } 559 | 560 | func loadModuleInfo(dir string) any { 561 | cmd := exec.Command("go", "mod", "edit", "-json") 562 | cmd.Dir = dir 563 | 564 | // Spawning "go mod edit" will open files by design, 565 | // such as the named pipe to obtain stdout. 566 | // TODO(mvdan): if we run into "too many open files" errors again in the 567 | // future, we probably need to turn fdSem into a weighted semaphore so this 568 | // operation can acquire a weight larger than 1. 569 | fdSem <- true 570 | out, err := cmd.Output() 571 | defer func() { <-fdSem }() 572 | 573 | if err != nil || len(out) == 0 { 574 | return nil 575 | } 576 | mod := new(module) 577 | if err := json.Unmarshal(out, mod); err != nil { 578 | return nil 579 | } 580 | return mod 581 | } 582 | 583 | // Written to by fileWeight, read from fileWeight and processFile. 584 | // A present but nil value means that loading the module info failed. 585 | // Note that we don't require the keys to be absolute directories, 586 | // so duplicates are possible. The same can happen with symlinks. 587 | var moduleCacheByDir sync.Map // map[dirString]*module 588 | 589 | func fileWeight(path string, info fs.FileInfo) int64 { 590 | dir := filepath.Dir(path) 591 | if _, ok := moduleCacheByDir.Load(dir); !ok { 592 | moduleCacheByDir.Store(dir, loadModuleInfo(dir)) 593 | } 594 | if info == nil { 595 | return exclusive 596 | } 597 | if info.Mode().Type() == fs.ModeSymlink { 598 | var err error 599 | info, err = os.Stat(path) 600 | if err != nil { 601 | return exclusive 602 | } 603 | } 604 | if !info.Mode().IsRegular() { 605 | // For non-regular files, FileInfo.Size is system-dependent and thus not a 606 | // reliable indicator of weight. 607 | return exclusive 608 | } 609 | return info.Size() 610 | } 611 | 612 | const chmodSupported = runtime.GOOS != "windows" 613 | 614 | // backupFile writes data to a new file named filename with permissions perm, 615 | // with 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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 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 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 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 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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /internal/govendor/version.txt: -------------------------------------------------------------------------------- 1 | go1.24.0 2 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package version 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "os" 10 | "runtime" 11 | "runtime/debug" 12 | "time" 13 | 14 | "golang.org/x/mod/module" 15 | ) 16 | 17 | // Note that this is not a main package, so a "var version" will not work with 18 | // our go-cross script which uses -ldflags=main.version=xxx. 19 | 20 | const ourModulePath = "mvdan.cc/gofumpt" 21 | 22 | const fallbackVersion = "(devel)" // to match the default from runtime/debug 23 | 24 | func findModule(info *debug.BuildInfo, modulePath string) *debug.Module { 25 | if info.Main.Path == modulePath { 26 | return &info.Main 27 | } 28 | for _, dep := range info.Deps { 29 | if dep.Path == modulePath { 30 | return dep 31 | } 32 | } 33 | return nil 34 | } 35 | 36 | func gofumptVersion() string { 37 | info, ok := debug.ReadBuildInfo() 38 | if !ok { 39 | return fallbackVersion // no build info available 40 | } 41 | // Note that gofumpt may be used as a library via the format package, 42 | // so we cannot assume it is the main module in the build. 43 | mod := findModule(info, ourModulePath) 44 | if mod == nil { 45 | return fallbackVersion // not found? 46 | } 47 | if mod.Replace != nil { 48 | mod = mod.Replace 49 | } 50 | 51 | // If we found a meaningful version, we are done. 52 | // If gofumpt is not the main module, stop as well, 53 | // as VCS info is only for the main module. 54 | if mod.Version != "(devel)" || mod != &info.Main { 55 | return mod.Version 56 | } 57 | 58 | // Fall back to trying to use VCS information. 59 | // Until https://github.com/golang/go/issues/50603 is implemented, 60 | // manually construct something like a pseudo-version. 61 | // TODO: remove when this code is dead, hopefully in Go 1.20. 62 | 63 | // For the tests, as we don't want the VCS information to change over time. 64 | if v := os.Getenv("GARBLE_TEST_BUILDSETTINGS"); v != "" { 65 | var extra []debug.BuildSetting 66 | if err := json.Unmarshal([]byte(v), &extra); err != nil { 67 | panic(err) 68 | } 69 | info.Settings = append(info.Settings, extra...) 70 | } 71 | 72 | var vcsTime time.Time 73 | var vcsRevision string 74 | for _, setting := range info.Settings { 75 | switch setting.Key { 76 | case "vcs.time": 77 | // If the format is invalid, we'll print a zero timestamp. 78 | vcsTime, _ = time.Parse(time.RFC3339Nano, setting.Value) 79 | case "vcs.revision": 80 | vcsRevision = setting.Value 81 | if len(vcsRevision) > 12 { 82 | vcsRevision = vcsRevision[:12] 83 | } 84 | } 85 | } 86 | if vcsRevision != "" { 87 | return module.PseudoVersion("", "", vcsTime, vcsRevision) 88 | } 89 | return fallbackVersion 90 | } 91 | 92 | func goVersion() string { 93 | // For the tests, as we don't want the Go version to change over time. 94 | if testVersion := os.Getenv("GO_VERSION_TEST"); testVersion != "" { 95 | return testVersion 96 | } 97 | return runtime.Version() 98 | } 99 | 100 | func String(injected string) string { 101 | if injected != "" { 102 | return fmt.Sprintf("%s (%s)", injected, goVersion()) 103 | } 104 | return fmt.Sprintf("%s (%s)", gofumptVersion(), goVersion()) 105 | } 106 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /testdata/gofumpt-external/go.mod: -------------------------------------------------------------------------------- 1 | module test/gofumpt-external 2 | 3 | go 1.23 4 | 5 | require mvdan.cc/gofumpt v0.6.1-0.20240717113859-88a300bbd6dc 6 | 7 | require ( 8 | github.com/google/go-cmp v0.6.0 // indirect 9 | golang.org/x/mod v0.14.0 // indirect 10 | golang.org/x/tools v0.17.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 4 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 10 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 11 | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 12 | golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 13 | golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= 14 | golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= 15 | mvdan.cc/gofumpt v0.6.1-0.20240717113859-88a300bbd6dc h1:XDr9kee1c8z5RRdwJnLWbAJ+x8NdkShxcqHSedJ6l+8= 16 | mvdan.cc/gofumpt v0.6.1-0.20240717113859-88a300bbd6dc/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/clothe-returns.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 foo() (err error) { 11 | if true { 12 | return 13 | } 14 | if false { 15 | return func() (err2 error) { 16 | return 17 | } 18 | } 19 | return 20 | } 21 | 22 | func bar() (_ int, err error) { 23 | return 24 | } 25 | 26 | func baz() (a, b, c int) { 27 | return 28 | } 29 | 30 | func qux() (file string, b int, err error) { 31 | if err == nil { 32 | return 33 | } 34 | 35 | // A comment 36 | return 37 | } 38 | 39 | // quux does quuxy things 40 | func quux() {} 41 | -- foo.go.golden -- 42 | package p 43 | 44 | func foo() (err error) { 45 | if true { 46 | return err 47 | } 48 | if false { 49 | return func() (err2 error) { 50 | return err2 51 | } 52 | } 53 | return err 54 | } 55 | 56 | func bar() (_ int, err error) { 57 | return 58 | } 59 | 60 | func baz() (a, b, c int) { 61 | return a, b, c 62 | } 63 | 64 | func qux() (file string, b int, err error) { 65 | if err == nil { 66 | return file, b, err 67 | } 68 | 69 | // A comment 70 | return file, b, err 71 | } 72 | 73 | // quux does quuxy things 74 | func quux() {} 75 | -------------------------------------------------------------------------------- /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 | //not actually: a directive 36 | 37 | //https://just.one/url 38 | 39 | //TODO: do something 40 | 41 | //export CgoFunc 42 | 43 | //extern open 44 | func c_open(name *byte, mode int, perm int) int 45 | 46 | //line 123 47 | 48 | //sys Unlink(path string) (err error) 49 | 50 | //sysnb Getpid() (pid int) 51 | 52 | //foo is foo. 53 | type foo int 54 | 55 | // comment with a tab. 56 | 57 | // comment with many spaces 58 | 59 | //comment group 60 | //123 numbers too 61 | 62 | // comment group 63 | //123 numbers too 64 | 65 | //{ 66 | //this is probably code 67 | //} 68 | 69 | //////////// 70 | // ascii art 71 | //---------- 72 | 73 | // 74 | -- foo.go.golden -- 75 | //go:build tag 76 | // +build tag 77 | 78 | package p 79 | 80 | //go:generate some command 81 | 82 | //go:unknowndirective 83 | 84 | //lint:disablefoo 85 | 86 | //go-sumtype:decl Foo 87 | 88 | //nolint 89 | 90 | //nolint // explanation 91 | 92 | //nolint:somelinter // explanation 93 | 94 | //NOSONAR 95 | 96 | //NOSONAR // explanation 97 | 98 | //noinspection ALL 99 | 100 | //noinspection foo,bar 101 | 102 | // not actually: a directive 103 | 104 | // https://just.one/url 105 | 106 | // TODO: do something 107 | 108 | //export CgoFunc 109 | 110 | //extern open 111 | func c_open(name *byte, mode int, perm int) int 112 | 113 | //line 123 114 | 115 | //sys Unlink(path string) (err error) 116 | 117 | //sysnb Getpid() (pid int) 118 | 119 | // foo is foo. 120 | type foo int 121 | 122 | // comment with a tab. 123 | 124 | // comment with many spaces 125 | 126 | // comment group 127 | // 123 numbers too 128 | 129 | // comment group 130 | // 123 numbers too 131 | 132 | //{ 133 | //this is probably code 134 | //} 135 | 136 | //////////// 137 | // ascii art 138 | //---------- 139 | 140 | // 141 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/diagnose.txtar: -------------------------------------------------------------------------------- 1 | env GO_VERSION_TEST=go1.18.29 2 | 3 | # First, test a local build of gofumpt resulting from 'git clone'. 4 | # Its version will be inferred from VCS, but since we want a stable test, 5 | # we mock the VCS information. Note that test binaries do not have VCS info. 6 | # Data obtained from a real build while developing. 7 | env GARBLE_TEST_BUILDSETTINGS='[{"Key":"vcs","Value":"git"},{"Key":"vcs.revision","Value":"8dda8068d9f339047fc1777b688afb66a0a0db17"},{"Key":"vcs.time","Value":"2022-07-27T15:58:40Z"},{"Key":"vcs.modified","Value":"true"}]' 8 | exec gofumpt foo.go 9 | cmp stdout foo.go.golden 10 | 11 | exec gofumpt outdated.go 12 | cmp stdout foo.go.golden 13 | 14 | exec gofumpt -extra foo.go 15 | cmp stdout foo.go.golden-extra 16 | 17 | exec gofumpt -lang=go1 foo.go 18 | cmp stdout foo.go.golden-lang 19 | 20 | exec gofumpt -d nochange.go 21 | ! stdout . 22 | 23 | exec gofumpt -d foo.go.golden 24 | ! stdout . 25 | 26 | exec gofumpt -d -extra foo.go.golden-extra 27 | ! stdout . 28 | 29 | # A local build without VCS information will result in a missing version. 30 | env GARBLE_TEST_BUILDSETTINGS='[]' 31 | exec gofumpt foo.go 32 | cmp stdout foo.go.golden-devel 33 | 34 | [short] stop 'the rest of this test builds gofumpt binaries' 35 | 36 | # We want a published version of gofumpt on the public module proxies, 37 | # because that's the only way that its module version will be included. 38 | # Using a directory replace directive will not work. 39 | # This means that any change in how gofumpt reports its own version 40 | # will require two pull requests, the second one updating the test script. 41 | # We could consider using go-internal/goproxytest, but then we would need to 42 | # manually run something like go-internal/cmd/txtar-addmod reguarly. 43 | # Or teach goproxytest to serve a mock version of gofumpt from its local checkout. 44 | # Either way, both are relatively overkill for now. 45 | # Update this pseudo-version to master from time to time as needed. 46 | env GOBIN=${WORK}/bin 47 | env GOFUMPT_PUBLISHED_VERSION=v0.6.1-0.20240717113859-88a300bbd6dc 48 | 49 | # TODO: update these once the library fix hits master 50 | 51 | # gofumpt as the main binary with a real module version. 52 | go install mvdan.cc/gofumpt@${GOFUMPT_PUBLISHED_VERSION} 53 | exec ${GOBIN}/gofumpt foo.go 54 | cmp stdout foo.go.golden-released 55 | 56 | # gofumpt as a library with a real module version. 57 | cd ${GOMOD_DIR}/testdata/gofumpt-external 58 | go install . 59 | cd ${WORK} 60 | stdin foo.go 61 | exec ${GOBIN}/gofumpt-external 62 | cmp stdout foo.go.golden-external 63 | 64 | -- go.mod -- 65 | module test 66 | 67 | go 1.16 68 | -- foo.go -- 69 | package p 70 | 71 | //gofumpt:diagnose 72 | -- outdated.go -- 73 | package p 74 | 75 | //gofumpt:diagnose v0.1.0 76 | -- nochange.go -- 77 | package p 78 | 79 | //gofumpt:diagnosefoobar 80 | -- foo.go.golden -- 81 | package p 82 | 83 | //gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=go1.16 -modpath=test 84 | -- foo.go.golden-devel -- 85 | package p 86 | 87 | //gofumpt:diagnose version: (devel) (go1.18.29) flags: -lang=go1.16 -modpath=test 88 | -- foo.go.golden-extra -- 89 | package p 90 | 91 | //gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=go1.16 -modpath=test -extra 92 | -- foo.go.golden-lang -- 93 | package p 94 | 95 | //gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=go1 -modpath=test 96 | -- foo.go.golden-released -- 97 | package p 98 | 99 | //gofumpt:diagnose version: v0.6.1-0.20240717113859-88a300bbd6dc (go1.18.29) flags: -lang=go1.16 -modpath=test 100 | -- foo.go.golden-external -- 101 | package p 102 | 103 | //gofumpt:diagnose version: v0.6.1-0.20240717113859-88a300bbd6dc (go1.18.29) flags: -lang=go1.16 -modpath= 104 | -------------------------------------------------------------------------------- /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/func-merge-parameters.txtar: -------------------------------------------------------------------------------- 1 | # By default, this rule isn't enabled. 2 | exec gofumpt foo.go 3 | cmp stdout foo.go 4 | 5 | # It's run with -extra. 6 | exec gofumpt -extra foo.go 7 | cmp stdout foo.go.golden 8 | 9 | exec gofumpt -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 | -- foo.go.golden -- 59 | package p 60 | 61 | type f func(x, y int) int 62 | 63 | type i interface { 64 | add(x, y int) 65 | } 66 | 67 | type s struct { 68 | x int 69 | y int 70 | } 71 | 72 | func mergeAdjacent(x, y int) {} 73 | 74 | func mergeThreeAdjacent(x, y, z int) {} 75 | 76 | func mergeOneWithTwo(x, y, z int) {} 77 | 78 | func mergeTwoWithOne(x, y, z int) {} 79 | 80 | func mergeWithComment( 81 | x, y int, // comment 82 | ) 83 | 84 | func mergeAllSyntax(x, y chan []*foo.Bar) {} 85 | 86 | func dontMergeAnonymousParams(int, int) {} 87 | 88 | func dontMergeMultipleLines( 89 | x int, 90 | y int, 91 | ) { 92 | } 93 | 94 | func dontMergeMultipleLines2( 95 | x, 96 | y int, 97 | z int, 98 | ) { 99 | } 100 | 101 | func dontMergeDifferentKinds(format string, args ...string) {} 102 | 103 | func dontMergeDifferentTypesReturn() (n int, err error) {} 104 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/ignore-dirs.txtar: -------------------------------------------------------------------------------- 1 | exec gofumpt orig.go.golden 2 | cp stdout formatted.go.golden 3 | mkdir -p vendor/foo testdata/foo 4 | cp orig.go.golden vendor/foo/foo.go 5 | cp orig.go.golden testdata/foo/foo.go 6 | 7 | # format explicit dirs 8 | exec gofumpt -l vendor testdata 9 | stdout -count=1 'vendor[/\\]foo[/\\]foo.go' 10 | stdout -count=1 'testdata[/\\]foo[/\\]foo.go' 11 | ! stderr . 12 | 13 | # format explicit files 14 | exec gofumpt -l vendor/foo/foo.go testdata/foo/foo.go 15 | stdout -count=1 'vendor[/\\]foo[/\\]foo.go' 16 | stdout -count=1 'testdata[/\\]foo[/\\]foo.go' 17 | ! stderr . 18 | 19 | # ignore implicit dirs via fs walking 20 | exec gofumpt -l . 21 | ! stdout . 22 | ! stderr . 23 | 24 | # format explicit pkg while ignoring rest 25 | mkdir vendor/ignore testdata/ignore 26 | cp orig.go.golden vendor/ignore/ignore.go 27 | cp orig.go.golden testdata/ignore/ignore.go 28 | exec gofumpt -l vendor/foo testdata/foo . 29 | stdout -count=1 'vendor[/\\]foo[/\\]foo.go' 30 | stdout -count=1 'testdata[/\\]foo[/\\]foo.go' 31 | ! stderr . 32 | 33 | # format explicit dirs without clean paths 34 | exec gofumpt -l $WORK//vendor ./testdata/./ 35 | stdout -count=1 'vendor[/\\]foo[/\\]foo.go' 36 | stdout -count=1 'testdata[/\\]foo[/\\]foo.go' 37 | ! stderr . 38 | 39 | -- orig.go.golden -- 40 | package p 41 | 42 | func f() { 43 | if true { 44 | // lone comment 45 | } 46 | { 47 | 48 | } 49 | 50 | { 51 | 52 | // lone comment 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------