pax_global_header 0000666 0000000 0000000 00000000064 14555553334 0014526 g ustar 00root root 0000000 0000000 52 comment=636d7a7b9ae6db4704a12cd990b57a7c8f0ede78
gofumpt-0.6.0/ 0000775 0000000 0000000 00000000000 14555553334 0013212 5 ustar 00root root 0000000 0000000 gofumpt-0.6.0/.gitattributes 0000664 0000000 0000000 00000000121 14555553334 0016077 0 ustar 00root root 0000000 0000000 # To prevent CRLF breakages on Windows for fragile files, like testdata.
* -text
gofumpt-0.6.0/.github/ 0000775 0000000 0000000 00000000000 14555553334 0014552 5 ustar 00root root 0000000 0000000 gofumpt-0.6.0/.github/FUNDING.yml 0000664 0000000 0000000 00000000016 14555553334 0016364 0 ustar 00root root 0000000 0000000 github: mvdan
gofumpt-0.6.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14555553334 0016607 5 ustar 00root root 0000000 0000000 gofumpt-0.6.0/.github/workflows/test.yml 0000664 0000000 0000000 00000001370 14555553334 0020312 0 ustar 00root root 0000000 0000000 on: [push, pull_request]
name: Test
jobs:
test:
strategy:
matrix:
go-version: [1.20.x, 1.21.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- uses: actions/checkout@v3
- run: go test ./...
- run: go test -race ./...
# Static checks from this point forward. Only run on one Go version and on
# Linux, since it's the fastest platform, and the tools behave the same.
- if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.21.x'
run: diff <(echo -n) <(gofmt -s -d .)
- if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.21.x'
run: go vet ./...
gofumpt-0.6.0/CHANGELOG.md 0000664 0000000 0000000 00000016536 14555553334 0015036 0 ustar 00root root 0000000 0000000 # Changelog
## [v0.6.0] - 2024-01-28
This release is based on Go 1.21's gofmt, and requires Go 1.20 or later.
The following changes are included:
* Support `go` version strings from newer go.mod files - [#280]
* Consider simple error checks even if they use the `=` operator - [#271]
* Ignore `//line` directives to avoid panics - [#288]
## [v0.5.0] - 2023-04-09
This release is based on Go 1.20's gofmt, and requires Go 1.19 or later.
The biggest change in this release is that we now vendor copies of the packages
`go/format`, `go/printer`, and `go/doc/comment` on top of `cmd/gofmt` itself.
This allows for each gofumpt release to format code in exactly the same way
no matter what Go version is used to build it, as Go versions can change those
three packages in ways that alter formatting behavior.
This vendoring adds a small amount of duplication when using the
`mvdan.cc/gofumpt/format` library, but it's the only way to make gofumpt
versions consistent in their behavior and formatting, just like gofmt.
The jump to Go 1.20's `go/printer` should also bring a small performance
improvement, as we contributed patches to make printing about 25% faster:
* https://go.dev/cl/412555
* https://go.dev/cl/412557
* https://go.dev/cl/424924
The following changes are included as well:
* Skip `testdata` dirs by default like we already do for `vendor` - [#260]
* Avoid inserting newlines incorrectly in some func signatures - [#235]
* Avoid joining some comments with the previous line - [#256]
* Fix `gofumpt -version` for release archives - [#253]
## [v0.4.0] - 2022-09-27
This release is based on Go 1.19's gofmt, and requires Go 1.18 or later.
We recommend building gofumpt with Go 1.19 for the best formatting results.
The jump from Go 1.18 brings diffing in pure Go, removing the need to exec `diff`,
and a small parsing speed-up thanks to `go/parser.SkipObjectResolution`.
The following formatting fixes are included as well:
* Allow grouping declarations with comments - [#212]
* Properly measure the length of case clauses - [#217]
* Fix a few crashes found by Go's native fuzzing
## [v0.3.1] - 2022-03-21
This bugfix release resolves a number of issues:
* Avoid "too many open files" error regression introduced by [v0.3.0] - [#208]
* Use the `go.mod` relative to each Go file when deriving flag defaults - [#211]
* Remove unintentional debug prints when directly formatting files
## [v0.3.0] - 2022-02-22
This is gofumpt's third major release, based on Go 1.18's gofmt.
The jump from Go 1.17's gofmt should bring a noticeable speed-up,
as the tool can now format many files concurrently.
On an 8-core laptop, formatting a large codebase is 4x as fast.
The following [formatting rules](https://github.com/mvdan/gofumpt#Added-rules) are added:
* Functions should separate `) {` where the indentation helps readability
* Field lists should not have leading or trailing empty lines
The following changes are included as well:
* Generated files are now fully formatted when given as explicit arguments
* Prepare for Go 1.18's module workspaces, which could cause errors
* Import paths sharing a prefix with the current module path are no longer
grouped with standard library imports
* `format.Options` gains a `ModulePath` field per the last bullet point
## [v0.2.1] - 2021-12-12
This bugfix release resolves a number of issues:
* Add deprecated flags `-s` and `-r` once again, now giving useful errors
* Avoid a panic with certain function declaration styles
* Don't group interface members of different kinds
* Account for leading comments in composite literals
## [v0.2.0] - 2021-11-10
This is gofumpt's second major release, based on Go 1.17's gofmt.
The jump from Go 1.15's gofmt should bring a mild speed-up,
as walking directories with `filepath.WalkDir` uses fewer syscalls.
gofumports is now removed, after being deprecated in [v0.1.0].
Its main purpose was IDE integration; it is now recommended to use gopls,
which in turn implements goimports and supports gofumpt natively.
IDEs which don't integrate with gopls (such as GoLand) implement goimports too,
so it is safe to use gofumpt as their "format on save" command.
See the [installation instructions](https://github.com/mvdan/gofumpt#Installation)
for more details.
The following [formatting rules](https://github.com/mvdan/gofumpt#Added-rules) are added:
* Composite literals should not have leading or trailing empty lines
* No empty lines following an assignment operator
* Functions using an empty line for readability should use a `) {` line instead
* Remove unnecessary empty lines from interfaces
Finally, the following changes are made to the gofumpt tool:
* Initial support for Go 1.18's type parameters is added
* The `-r` flag is removed in favor of `gofmt -r`
* The `-s` flag is removed as it is always enabled
* Vendor directories are skipped unless given as explicit arguments
* The added rules are not applied to generated Go files
* The `format` Go API now also applies the `gofmt -s` simplification
* Add support for `//gofumpt:diagnose` comments
## [v0.1.1] - 2021-03-11
This bugfix release backports fixes for a few issues:
* Keep leading empty lines in func bodies if they help readability
* Avoid breaking comment alignment on empty field lists
* Add support for `//go-sumtype:` directives
## [v0.1.0] - 2021-01-05
This is gofumpt's first release, based on Go 1.15.x. It solidifies the features
which have worked well for over a year.
This release will be the last to include `gofumports`, the fork of `goimports`
which applies `gofumpt`'s rules on top of updating the Go import lines. Users
who were relying on `goimports` in their editors or IDEs to apply both `gofumpt`
and `goimports` in a single step should switch to gopls, the official Go
language server. It is supported by many popular editors such as VS Code and
Vim, and already bundles gofumpt support. Instructions are available [in the
README](https://github.com/mvdan/gofumpt).
`gofumports` also added maintenance work and potential confusion to end users.
In the future, there will only be one way to use `gofumpt` from the command
line. We also have a [Go API](https://pkg.go.dev/mvdan.cc/gofumpt/format) for
those building programs with gofumpt.
Finally, this release adds the `-version` flag, to print the tool's own version.
The flag will work for "master" builds too.
[v0.6.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.6.0
[#271]: https://github.com/mvdan/gofumpt/issues/271
[#280]: https://github.com/mvdan/gofumpt/issues/280
[#288]: https://github.com/mvdan/gofumpt/issues/288
[v0.5.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.5.0
[#235]: https://github.com/mvdan/gofumpt/issues/235
[#253]: https://github.com/mvdan/gofumpt/issues/253
[#256]: https://github.com/mvdan/gofumpt/issues/256
[#260]: https://github.com/mvdan/gofumpt/issues/260
[v0.4.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.4.0
[#212]: https://github.com/mvdan/gofumpt/issues/212
[#217]: https://github.com/mvdan/gofumpt/issues/217
[v0.3.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.3.1
[#208]: https://github.com/mvdan/gofumpt/issues/208
[#211]: https://github.com/mvdan/gofumpt/pull/211
[v0.3.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.3.0
[v0.2.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.2.1
[v0.2.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.2.0
[v0.1.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.1.1
[v0.1.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.1.0
gofumpt-0.6.0/LICENSE 0000664 0000000 0000000 00000002720 14555553334 0014220 0 ustar 00root root 0000000 0000000 Copyright (c) 2019, Daniel Martí. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
gofumpt-0.6.0/LICENSE.google 0000664 0000000 0000000 00000002707 14555553334 0015500 0 ustar 00root root 0000000 0000000 Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
gofumpt-0.6.0/README.md 0000664 0000000 0000000 00000030104 14555553334 0014467 0 ustar 00root root 0000000 0000000 # gofumpt
[](https://pkg.go.dev/mvdan.cc/gofumpt/format)
go install mvdan.cc/gofumpt@latest
Enforce a stricter format than `gofmt`, while being backwards compatible.
That is, `gofumpt` is happy with a subset of the formats that `gofmt` is happy with.
The tool is a fork of `gofmt` as of Go 1.21, and requires Go 1.20 or later.
It can be used as a drop-in replacement to format your Go code,
and running `gofmt` after `gofumpt` should produce no changes.
For example:
gofumpt -l -w .
Some of the Go source files in this repository belong to the Go project.
The project includes copies of `go/printer` and `go/doc/comment` as of Go 1.21
to ensure consistent formatting independent of what Go version is being used.
The [added formatting rules](#Added-rules) are implemented in the `format` package.
`vendor` and `testdata` directories are skipped unless given as explicit arguments.
Similarly, the added rules do not apply to generated Go files unless they are
given as explicit arguments.
Finally, note that the `-r` rewrite flag is removed in favor of `gofmt -r`,
and the `-s` flag is hidden as it is always enabled.
### Added rules
**No empty lines following an assignment operator**
Example
```go
func foo() {
foo :=
"bar"
}
```
```go
func foo() {
foo := "bar"
}
```
**No empty lines around function bodies**
Example
```go
func foo() {
println("bar")
}
```
```go
func foo() {
println("bar")
}
```
**Functions should separate `) {` where the indentation helps readability**
Example
```go
func foo(s string,
i int) {
println("bar")
}
// With an empty line it's slightly better, but still not great.
func bar(s string,
i int) {
println("bar")
}
```
```go
func foo(s string,
i int,
) {
println("bar")
}
// With an empty line it's slightly better, but still not great.
func bar(s string,
i int,
) {
println("bar")
}
```
**No empty lines around a lone statement (or comment) in a block**
Example
```go
if err != nil {
return err
}
```
```go
if err != nil {
return err
}
```
**No empty lines before a simple error check**
Example
```go
foo, err := processFoo()
if err != nil {
return err
}
```
```go
foo, err := processFoo()
if err != nil {
return err
}
```
**Composite literals should use newlines consistently**
Example
```go
// A newline before or after an element requires newlines for the opening and
// closing braces.
var ints = []int{1, 2,
3, 4}
// A newline between consecutive elements requires a newline between all
// elements.
var matrix = [][]int{
{1},
{2}, {
3,
},
}
```
```go
var ints = []int{
1, 2,
3, 4,
}
var matrix = [][]int{
{1},
{2},
{
3,
},
}
```
**Empty field lists should use a single line**
Example
```go
var V interface {
} = 3
type T struct {
}
func F(
)
```
```go
var V interface{} = 3
type T struct{}
func F()
```
**`std` imports must be in a separate group at the top**
Example
```go
import (
"foo.com/bar"
"io"
"io/ioutil"
)
```
```go
import (
"io"
"io/ioutil"
"foo.com/bar"
)
```
**Short case clauses should take a single line**
Example
```go
switch c {
case 'a', 'b',
'c', 'd':
}
```
```go
switch c {
case 'a', 'b', 'c', 'd':
}
```
**Multiline top-level declarations must be separated by empty lines**
Example
```go
func foo() {
println("multiline foo")
}
func bar() {
println("multiline bar")
}
```
```go
func foo() {
println("multiline foo")
}
func bar() {
println("multiline bar")
}
```
**Single var declarations should not be grouped with parentheses**
Example
```go
var (
foo = "bar"
)
```
```go
var foo = "bar"
```
**Contiguous top-level declarations should be grouped together**
Example
```go
var nicer = "x"
var with = "y"
var alignment = "z"
```
```go
var (
nicer = "x"
with = "y"
alignment = "z"
)
```
**Simple var-declaration statements should use short assignments**
Example
```go
var s = "somestring"
```
```go
s := "somestring"
```
**The `-s` code simplification flag is enabled by default**
Example
```go
var _ = [][]int{[]int{1}}
```
```go
var _ = [][]int{{1}}
```
**Octal integer literals should use the `0o` prefix on modules using Go 1.13 and later**
Example
```go
const perm = 0755
```
```go
const perm = 0o755
```
**Comments which aren't Go directives should start with a whitespace**
Example
```go
//go:noinline
//Foo is awesome.
func Foo() {}
```
```go
//go:noinline
// Foo is awesome.
func Foo() {}
```
**Composite literals should not have leading or trailing empty lines**
Example
```go
var _ = []string{
"foo",
}
var _ = map[string]string{
"foo": "bar",
}
```
```go
var _ = []string{
"foo",
}
var _ = map[string]string{
"foo": "bar",
}
```
**Field lists should not have leading or trailing empty lines**
Example
```go
type Person interface {
Name() string
Age() int
}
type ZeroFields struct {
// No fields are needed here.
}
```
```go
type Person interface {
Name() string
Age() int
}
type ZeroFields struct {
// No fields are needed here.
}
```
#### Extra rules behind `-extra`
**Adjacent parameters with the same type should be grouped together**
Example
```go
func Foo(bar string, baz string) {}
```
```go
func Foo(bar, baz string) {}
```
### Installation
`gofumpt` is a replacement for `gofmt`, so you can simply `go install` it as
described at the top of this README and use it.
When using an IDE or editor with Go integration based on `gopls`,
it's best to configure the editor to use the `gofumpt` support built into `gopls`.
The instructions below show how to set up `gofumpt` for some of the
major editors out there.
#### Visual Studio Code
Enable the language server following [the official docs](https://github.com/golang/vscode-go#readme),
and then enable gopls's `gofumpt` option. Note that VS Code will complain about
the `gopls` settings, but they will still work.
```json
"go.useLanguageServer": true,
"gopls": {
"formatting.gofumpt": true,
},
```
#### GoLand
GoLand doesn't use `gopls` so it should be configured to use `gofumpt` directly.
Once `gofumpt` is installed, follow the steps below:
- Open **Settings** (File > Settings)
- Open the **Tools** section
- Find the *File Watchers* sub-section
- Click on the `+` on the right side to add a new file watcher
- Choose *Custom Template*
When a window asks for settings, you can enter the following:
* File Types: Select all .go files
* Scope: Project Files
* Program: Select your `gofumpt` executable
* Arguments: `-w $FilePath$`
* Output path to refresh: `$FilePath$`
* Working directory: `$ProjectFileDir$`
* Environment variables: `GOROOT=$GOROOT$;GOPATH=$GOPATH$;PATH=$GoBinDirs$`
To avoid unnecessary runs, you should disable all checkboxes in the *Advanced* section.
#### Vim
The configuration depends on the plugin you are using: [vim-go](https://github.com/fatih/vim-go)
or [govim](https://github.com/govim/govim).
##### vim-go
To configure `gopls` to use `gofumpt`:
```vim
let g:go_fmt_command="gopls"
let g:go_gopls_gofumpt=1
```
##### govim
To configure `gopls` to use `gofumpt`:
```vim
call govim#config#Set("Gofumpt", 1)
```
#### Neovim
When using [`lspconfig`](https://github.com/neovim/nvim-lspconfig), pass the `gofumpt` setting to `gopls`:
```lua
require('lspconfig').gopls.setup({
settings = {
gopls = {
gofumpt = true
}
}
})
```
#### Emacs
For [lsp-mode](https://emacs-lsp.github.io/lsp-mode/) users on version 8.0.0 or higher:
```elisp
(setq lsp-go-use-gofumpt t)
```
For users of `lsp-mode` before `8.0.0`:
```elisp
(lsp-register-custom-settings
'(("gopls.gofumpt" t)))
```
For [eglot](https://github.com/joaotavora/eglot) users:
```elisp
(setq-default eglot-workspace-configuration
'((:gopls . ((gofumpt . t)))))
```
#### Helix
When using the `gopls` language server, modify the Go settings in `~/.config/helix/languages.toml`:
```toml
[language-server.gopls.config]
"formatting.gofumpt" = true
```
#### Sublime Text
With ST4, install the Sublime Text LSP extension according to [the documentation](https://github.com/sublimelsp/LSP),
and enable `gopls`'s `gofumpt` option in the LSP package settings,
including setting `lsp_format_on_save` to `true`.
```json
"lsp_format_on_save": true,
"clients":
{
"gopls":
{
"enabled": true,
"initializationOptions": {
"gofumpt": true,
}
}
}
```
### Roadmap
This tool is a place to experiment. In the long term, the features that work
well might be proposed for `gofmt` itself.
The tool is also compatible with `gofmt` and is aimed to be stable, so you can
rely on it for your code as long as you pin a version of it.
### Frequently Asked Questions
> Why attempt to replace `gofmt` instead of building on top of it?
Our design is to build on top of `gofmt`, and we'll never add rules which
disagree with its formatting. So we extend `gofmt` rather than compete with it.
The tool is a modified copy of `gofmt`, for the purpose of allowing its use as a
drop-in replacement in editors and scripts.
> Why are my module imports being grouped with standard library imports?
Any import paths that don't start with a domain name like `foo.com` are
effectively [reserved by the Go toolchain](https://github.com/golang/go/issues/32819).
Third party modules should either start with a domain name,
even a local one like `foo.local`, or use [a reserved path prefix](https://github.com/golang/go/issues/37641).
For backwards compatibility with modules set up before these rules were clear,
`gofumpt` will treat any import path sharing a prefix with the current module
path as third party. For example, if the current module is `mycorp/mod1`, then
all import paths in `mycorp/...` will be considered third party.
> How can I use `gofumpt` if I already use `goimports` to replace `gofmt`?
Most editors have replaced the `goimports` program with the same functionality
provided by a language server like `gopls`. This mechanism is significantly
faster and more powerful, since the language server has more information that is
kept up to date, necessary to add missing imports.
As such, the general recommendation is to let your editor fix your imports -
either via `gopls`, such as VSCode or vim-go, or via their own custom
implementation, such as GoLand. Then follow the install instructions above to
enable the use of `gofumpt` instead of `gofmt`.
If you want to avoid integrating with `gopls`, and are OK with the overhead of
calling `goimports` from scratch on each save, you should be able to call both
tools; for example, `goimports file.go && gofumpt file.go`.
### Contributing
Issues and pull requests are welcome! Please open an issue to discuss a feature
before sending a pull request.
We also use the `#gofumpt` channel over at the
[Gophers Slack](https://invite.slack.golangbridge.org/) to chat.
When reporting a formatting bug, insert a `//gofumpt:diagnose` comment.
The comment will be rewritten to include useful debugging information.
For instance:
```
$ cat f.go
package p
//gofumpt:diagnose
$ gofumpt f.go
package p
//gofumpt:diagnose v0.1.1-0.20211103104632-bdfa3b02e50a -lang=v1.16
```
### License
Note that much of the code is copied from Go's `gofmt` command. You can tell
which files originate from the Go repository from their copyright headers. Their
license file is `LICENSE.google`.
`gofumpt`'s original source files are also under the 3-clause BSD license, with
the separate file `LICENSE`.
gofumpt-0.6.0/doc.go 0000664 0000000 0000000 00000000301 14555553334 0014300 0 ustar 00root root 0000000 0000000 // Copyright (c) 2023, Daniel Martí
// See LICENSE for licensing information
// gofumpt enforces a stricter format than gofmt, while being backwards compatible.
package main
gofumpt-0.6.0/format/ 0000775 0000000 0000000 00000000000 14555553334 0014502 5 ustar 00root root 0000000 0000000 gofumpt-0.6.0/format/format.go 0000664 0000000 0000000 00000077176 14555553334 0016343 0 ustar 00root root 0000000 0000000 // Copyright (c) 2019, Daniel Martí
// See LICENSE for licensing information
// Package format exposes gofumpt's formatting in an API similar to go/format.
// In general, the APIs are only guaranteed to work well when the input source
// is in canonical gofmt format.
package format
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/google/go-cmp/cmp"
"golang.org/x/mod/semver"
"golang.org/x/tools/go/ast/astutil"
"mvdan.cc/gofumpt/internal/govendor/go/format"
"mvdan.cc/gofumpt/internal/version"
)
// Options is the set of formatting options which affect gofumpt.
type Options struct {
// TODO: link to the go/version docs once Go 1.22 is out.
// The old semver docs said:
//
// LangVersion is treated as a semantic version, which may start with a "v"
// prefix. Like Go versions, it may also be incomplete; "1.14" is equivalent
// to "1.14.0". When empty, it is equivalent to "v1", to not use language
// features which could break programs.
// LangVersion is the Go version a piece of code is written in.
// The version is used to decide whether to apply formatting
// rules which require new language features.
// When inside a Go module, LangVersion should typically be:
//
// go mod edit -json | jq -r '.Go'
LangVersion string
// ModulePath corresponds to the Go module path which contains the source
// code being formatted. When inside a Go module, ModulePath should be:
//
// go mod edit -json | jq -r '.Module.Path'
//
// ModulePath is used for formatting decisions like what import paths are
// considered to be not part of the standard library. When empty, the source
// is formatted as if it weren't inside a module.
ModulePath string
// ExtraRules enables extra formatting rules, such as grouping function
// parameters with repeated types together.
ExtraRules bool
}
// Source formats src in gofumpt's format, assuming that src holds a valid Go
// source file.
func Source(src []byte, opts Options) ([]byte, error) {
fset := token.NewFileSet()
// Ensure our parsed files never start with base 1,
// to ensure that using token.NoPos+1 will panic.
fset.AddFile("gofumpt_base.go", 1, 10)
file, err := parser.ParseFile(fset, "", src, parser.SkipObjectResolution|parser.ParseComments)
if err != nil {
return nil, err
}
File(fset, file, opts)
var buf bytes.Buffer
if err := format.Node(&buf, fset, file); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var rxGoVersionMajorMinor = regexp.MustCompile(`^(v|go)?([1-9]+)\.([0-9]+)`)
// File modifies a file and fset in place to follow gofumpt's format. The
// changes might include manipulating adding or removing newlines in fset,
// modifying the position of nodes, or modifying literal values.
func File(fset *token.FileSet, file *ast.File, opts Options) {
simplify(file)
// TODO: replace this hacky mess with go/version once we can rely on Go 1.22,
// as well as replacing our uses of the semver package.
// In particular, we likely want to allow any of 1.21, 1.21.2, or go1.21rc3,
// but we can rely on go/version.Lang to validate and normalize.
if opts.LangVersion == "" {
opts.LangVersion = "v1.0"
}
m := rxGoVersionMajorMinor.FindStringSubmatch(opts.LangVersion)
if m == nil {
panic(fmt.Sprintf("invalid Go version: %q", opts.LangVersion))
}
opts.LangVersion = "v" + m[2] + "." + m[3]
f := &fumpter{
file: fset.File(file.Pos()),
fset: fset,
astFile: file,
Options: opts,
minSplitFactor: 0.4,
}
var topFuncType *ast.FuncType
pre := func(c *astutil.Cursor) bool {
f.applyPre(c)
switch node := c.Node().(type) {
case *ast.FuncDecl:
topFuncType = node.Type
case *ast.FieldList:
ft, _ := c.Parent().(*ast.FuncType)
if ft == nil || ft != topFuncType {
break
}
// For top-level function declaration parameters,
// require the line split to be longer.
// This avoids func lines which are a bit too short,
// and allows func lines which are a bit longer.
//
// We don't just increase longLineLimit,
// as we still want splits at around the same place.
if ft.Params == node {
f.minSplitFactor = 0.6
}
// Don't split result parameters into multiple lines,
// as that can be easily confused for input parameters.
// TODO: consider the same for single-line func calls in
// if statements.
// TODO: perhaps just use a higher factor, like 0.8.
if ft.Results == node {
f.minSplitFactor = 1000
}
case *ast.BlockStmt:
f.blockLevel++
}
return true
}
post := func(c *astutil.Cursor) bool {
f.applyPost(c)
// Reset minSplitFactor and blockLevel.
switch node := c.Node().(type) {
case *ast.FuncType:
if node == topFuncType {
f.minSplitFactor = 0.4
}
case *ast.BlockStmt:
f.blockLevel--
}
return true
}
astutil.Apply(file, pre, post)
}
// Multiline nodes which could easily fit on a single line under this many bytes
// may be collapsed onto a single line.
const shortLineLimit = 60
// Single-line nodes which take over this many bytes, and could easily be split
// into two lines of at least its minSplitFactor factor, may be split.
const longLineLimit = 100
var rxOctalInteger = regexp.MustCompile(`\A0[0-7_]+\z`)
type fumpter struct {
Options
file *token.File
fset *token.FileSet
astFile *ast.File
// blockLevel is the number of indentation blocks we're currently under.
// It is used to approximate the levels of indentation a line will end
// up with.
blockLevel int
minSplitFactor float64
}
func (f *fumpter) commentsBetween(p1, p2 token.Pos) []*ast.CommentGroup {
comments := f.astFile.Comments
i1 := sort.Search(len(comments), func(i int) bool {
return comments[i].Pos() >= p1
})
comments = comments[i1:]
i2 := sort.Search(len(comments), func(i int) bool {
return comments[i].Pos() >= p2
})
comments = comments[:i2]
return comments
}
func (f *fumpter) inlineComment(pos token.Pos) *ast.Comment {
comments := f.astFile.Comments
i := sort.Search(len(comments), func(i int) bool {
return comments[i].Pos() >= pos
})
if i >= len(comments) {
return nil
}
line := f.Line(pos)
for _, comment := range comments[i].List {
if f.Line(comment.Pos()) == line {
return comment
}
}
return nil
}
// addNewline is a hack to let us force a newline at a certain position.
func (f *fumpter) addNewline(at token.Pos) {
offset := f.Offset(at)
// TODO: replace with the new Lines method once we require Go 1.21 or later
field := reflect.ValueOf(f.file).Elem().FieldByName("lines")
n := field.Len()
lines := make([]int, 0, n+1)
for i := 0; i < n; i++ {
cur := int(field.Index(i).Int())
if offset == cur {
// This newline already exists; do nothing. Duplicate
// newlines can't exist.
return
}
if offset >= 0 && offset < cur {
lines = append(lines, offset)
offset = -1
}
lines = append(lines, cur)
}
if offset >= 0 {
lines = append(lines, offset)
}
if !f.file.SetLines(lines) {
panic(fmt.Sprintf("could not set lines to %v", lines))
}
}
// removeLines removes all newlines between two positions, so that they end
// up on the same line.
func (f *fumpter) removeLines(fromLine, toLine int) {
for fromLine < toLine {
f.file.MergeLine(fromLine)
toLine--
}
}
// removeLinesBetween is like removeLines, but it leaves one newline between the
// two positions.
func (f *fumpter) removeLinesBetween(from, to token.Pos) {
f.removeLines(f.Line(from)+1, f.Line(to))
}
func (f *fumpter) Position(p token.Pos) token.Position {
return f.file.PositionFor(p, false)
}
func (f *fumpter) Line(p token.Pos) int {
return f.Position(p).Line
}
func (f *fumpter) Offset(p token.Pos) int {
return f.file.Offset(p)
}
type byteCounter int
func (b *byteCounter) Write(p []byte) (n int, err error) {
*b += byteCounter(len(p))
return len(p), nil
}
func (f *fumpter) printLength(node ast.Node) int {
var count byteCounter
if err := format.Node(&count, f.fset, node); err != nil {
panic(fmt.Sprintf("unexpected print error: %v", err))
}
// Add the space taken by an inline comment.
if c := f.inlineComment(node.End()); c != nil {
fmt.Fprintf(&count, " %s", c.Text)
}
// Add an approximation of the indentation level. We can't know the
// number of tabs go/printer will add ahead of time. Trying to print the
// entire top-level declaration would tell us that, but then it's near
// impossible to reliably find our node again.
return int(count) + (f.blockLevel * 8)
}
func (f *fumpter) lineEnd(line int) token.Pos {
if line < 1 {
panic("illegal line number")
}
total := f.file.LineCount()
if line > total {
panic("illegal line number")
}
if line == total {
return f.astFile.End()
}
return f.file.LineStart(line+1) - 1
}
// rxCommentDirective covers all common Go comment directives:
//
// //go: | standard Go directives, like go:noinline
// //some-words: | similar to the syntax above, like lint:ignore or go-sumtype:decl
// //line | inserted line information for cmd/compile
// //export | to mark cgo funcs for exporting
// //extern | C function declarations for gccgo
// //sys(nb)? | syscall function wrapper prototypes
// //nolint | nolint directive for golangci
// //noinspection | noinspection directive for GoLand and friends
// //NOSONAR | NOSONAR directive for SonarQube
//
// Note that the "some-words:" matching expects a letter afterward, such as
// "go:generate", to prevent matching false positives like "https://site".
var rxCommentDirective = regexp.MustCompile(`^([a-z-]+:[a-z]+|line\b|export\b|extern\b|sys(nb)?\b|no(lint|inspection)\b)|NOSONAR\b`)
func (f *fumpter) applyPre(c *astutil.Cursor) {
f.splitLongLine(c)
switch node := c.Node().(type) {
case *ast.File:
// Join contiguous lone var/const/import lines.
// Abort if there are empty lines in between,
// including a leading comment if it's a directive.
newDecls := make([]ast.Decl, 0, len(node.Decls))
for i := 0; i < len(node.Decls); {
newDecls = append(newDecls, node.Decls[i])
start, ok := node.Decls[i].(*ast.GenDecl)
if !ok || isCgoImport(start) || containsAnyDirective(start.Doc) {
i++
continue
}
lastPos := start.Pos()
contLoop:
for i++; i < len(node.Decls); {
cont, ok := node.Decls[i].(*ast.GenDecl)
if !ok || cont.Tok != start.Tok || cont.Lparen != token.NoPos || isCgoImport(cont) {
break
}
// Are there things between these two declarations? e.g. empty lines, comments, directives
// If so, break the chain on empty lines and directives, continue below for comments.
if f.Line(lastPos) < f.Line(cont.Pos())-1 {
// break on empty line
if cont.Doc == nil {
break
}
// break on directive
for i, comment := range cont.Doc.List {
if f.Line(comment.Slash) != f.Line(lastPos)+1+i || rxCommentDirective.MatchString(strings.TrimPrefix(comment.Text, "//")) {
break contLoop
}
}
// continue below for comments
}
start.Specs = append(start.Specs, cont.Specs...)
if c := f.inlineComment(cont.End()); c != nil {
// don't move an inline comment outside
start.Rparen = c.End()
} else {
// so the code below treats the joined
// decl group as multi-line
start.Rparen = cont.End()
}
lastPos = cont.Pos()
i++
}
}
node.Decls = newDecls
// Multiline top-level declarations should be separated by an
// empty line.
// Do this after the joining of lone declarations above,
// as joining single-line declarations makes then multi-line.
var lastMulti bool
var lastEnd token.Pos
for _, decl := range node.Decls {
pos := decl.Pos()
comments := f.commentsBetween(lastEnd, pos)
if len(comments) > 0 {
pos = comments[0].Pos()
}
// Note that we want End-1, as End is the character after the node.
multi := f.Line(pos) < f.Line(decl.End()-1)
if multi && lastMulti && f.Line(lastEnd)+1 == f.Line(pos) {
f.addNewline(lastEnd)
}
lastMulti = multi
lastEnd = decl.End()
}
// Comments aren't nodes, so they're not walked by default.
groupLoop:
for _, group := range node.Comments {
for _, comment := range group.List {
if comment.Text == "//gofumpt:diagnose" || strings.HasPrefix(comment.Text, "//gofumpt:diagnose ") {
slc := []string{
"//gofumpt:diagnose",
"version:",
version.String(""),
"flags:",
"-lang=" + f.LangVersion,
"-modpath=" + f.ModulePath,
}
if f.ExtraRules {
slc = append(slc, "-extra")
}
comment.Text = strings.Join(slc, " ")
}
body := strings.TrimPrefix(comment.Text, "//")
if body == comment.Text {
// /*-style comment
continue groupLoop
}
if rxCommentDirective.MatchString(body) {
// this line is a directive
continue groupLoop
}
r, _ := utf8.DecodeRuneInString(body)
if !unicode.IsLetter(r) && !unicode.IsNumber(r) && !unicode.IsSpace(r) {
// this line could be code like "//{"
continue groupLoop
}
}
// If none of the comment group's lines look like a
// directive or code, add spaces, if needed.
for _, comment := range group.List {
body := strings.TrimPrefix(comment.Text, "//")
r, _ := utf8.DecodeRuneInString(body)
if !unicode.IsSpace(r) {
comment.Text = "// " + body
}
}
}
case *ast.DeclStmt:
decl, ok := node.Decl.(*ast.GenDecl)
if !ok || decl.Tok != token.VAR || len(decl.Specs) != 1 {
break // e.g. const name = "value"
}
spec := decl.Specs[0].(*ast.ValueSpec)
if spec.Type != nil {
break // e.g. var name Type
}
tok := token.ASSIGN
names := make([]ast.Expr, len(spec.Names))
for i, name := range spec.Names {
names[i] = name
if name.Name != "_" {
tok = token.DEFINE
}
}
c.Replace(&ast.AssignStmt{
Lhs: names,
Tok: tok,
Rhs: spec.Values,
})
case *ast.GenDecl:
if node.Tok == token.IMPORT && node.Lparen.IsValid() {
f.joinStdImports(node)
}
// Single var declarations shouldn't use parentheses, unless
// there's a comment on the grouped declaration.
if node.Tok == token.VAR && len(node.Specs) == 1 &&
node.Lparen.IsValid() && node.Doc == nil {
specPos := node.Specs[0].Pos()
specEnd := node.Specs[0].End()
if len(f.commentsBetween(node.TokPos, specPos)) > 0 {
// If the single spec has a comment on the line above,
// the comment must go before the entire declaration now.
node.TokPos = specPos
} else {
f.removeLines(f.Line(node.TokPos), f.Line(specPos))
}
if len(f.commentsBetween(specEnd, node.Rparen)) > 0 {
// Leave one newline to not force a comment on the next line to
// become an inline comment.
f.removeLines(f.Line(specEnd)+1, f.Line(node.Rparen))
} else {
f.removeLines(f.Line(specEnd), f.Line(node.Rparen))
}
// Remove the parentheses. go/printer will automatically
// get rid of the newlines.
node.Lparen = token.NoPos
node.Rparen = token.NoPos
}
case *ast.InterfaceType:
if len(node.Methods.List) > 0 {
method := node.Methods.List[0]
removeToPos := method.Pos()
if comments := f.commentsBetween(node.Interface, method.Pos()); len(comments) > 0 {
// only remove leading line upto the first comment
removeToPos = comments[0].Pos()
}
// remove leading lines if they exist
f.removeLines(f.Line(node.Interface)+1, f.Line(removeToPos))
}
case *ast.BlockStmt:
f.stmts(node.List)
comments := f.commentsBetween(node.Lbrace, node.Rbrace)
if len(node.List) == 0 && len(comments) == 0 {
f.removeLinesBetween(node.Lbrace, node.Rbrace)
break
}
var sign *ast.FuncType
var cond ast.Expr
switch parent := c.Parent().(type) {
case *ast.FuncDecl:
sign = parent.Type
case *ast.FuncLit:
sign = parent.Type
case *ast.IfStmt:
cond = parent.Cond
case *ast.ForStmt:
cond = parent.Cond
}
if len(node.List) > 1 && sign == nil {
// only if we have a single statement, or if
// it's a func body.
break
}
var bodyPos, bodyEnd token.Pos
if len(node.List) > 0 {
bodyPos = node.List[0].Pos()
bodyEnd = node.List[len(node.List)-1].End()
}
if len(comments) > 0 {
if pos := comments[0].Pos(); !bodyPos.IsValid() || pos < bodyPos {
bodyPos = pos
}
if pos := comments[len(comments)-1].End(); !bodyPos.IsValid() || pos > bodyEnd {
bodyEnd = pos
}
}
f.removeLinesBetween(bodyEnd, node.Rbrace)
if cond != nil && f.Line(cond.Pos()) != f.Line(cond.End()) {
// The body is preceded by a multi-line condition, so an
// empty line can help readability.
return
}
if sign != nil {
endLine := f.Line(sign.End())
if f.Line(sign.Pos()) != endLine {
handleMultiLine := func(fl *ast.FieldList) {
// Refuse to insert a newline before the closing token
// if the list is empty or all in one line.
if fl == nil || len(fl.List) == 0 {
return
}
fieldOpeningLine := f.Line(fl.Opening)
fieldClosingLine := f.Line(fl.Closing)
if fieldOpeningLine == fieldClosingLine {
return
}
lastFieldEnd := fl.List[len(fl.List)-1].End()
lastFieldLine := f.Line(lastFieldEnd)
isLastFieldOnFieldClosingLine := lastFieldLine == fieldClosingLine
isLastFieldOnSigClosingLine := lastFieldLine == endLine
var isLastCommentGrpOnFieldClosingLine, isLastCommentGrpOnSigClosingLine bool
if comments := f.commentsBetween(lastFieldEnd, fl.Closing); len(comments) > 0 {
lastCommentGrp := comments[len(comments)-1]
lastCommentGrpLine := f.Line(lastCommentGrp.End())
isLastCommentGrpOnFieldClosingLine = lastCommentGrpLine == fieldClosingLine
isLastCommentGrpOnSigClosingLine = lastCommentGrpLine == endLine
}
// is there a comment grp/last field, field closing and sig closing on the same line?
if (isLastFieldOnFieldClosingLine && isLastFieldOnSigClosingLine) ||
(isLastCommentGrpOnFieldClosingLine && isLastCommentGrpOnSigClosingLine) {
fl.Closing += 1
f.addNewline(fl.Closing)
}
}
handleMultiLine(sign.Params)
if sign.Results != nil && len(sign.Results.List) > 0 {
lastResultLine := f.Line(sign.Results.List[len(sign.Results.List)-1].End())
isLastResultOnParamClosingLine := sign.Params != nil && lastResultLine == f.Line(sign.Params.Closing)
if !isLastResultOnParamClosingLine {
handleMultiLine(sign.Results)
}
}
}
}
f.removeLinesBetween(node.Lbrace, bodyPos)
case *ast.CaseClause:
f.stmts(node.Body)
openLine := f.Line(node.Case)
closeLine := f.Line(node.Colon)
if openLine == closeLine {
// nothing to do
break
}
if len(f.commentsBetween(node.Case, node.Colon)) > 0 {
// don't move comments
break
}
// check the length excluding the body
nodeWithoutBody := &ast.CaseClause{
Case: node.Case,
List: node.List,
Colon: node.Colon,
}
if f.printLength(nodeWithoutBody) > shortLineLimit {
// too long to collapse
break
}
f.removeLines(openLine, closeLine)
case *ast.CommClause:
f.stmts(node.Body)
case *ast.FieldList:
numFields := node.NumFields()
comments := f.commentsBetween(node.Pos(), node.End())
if numFields == 0 && len(comments) == 0 {
// Empty field lists should not contain a newline.
// Do not join the two lines if the first has an inline
// comment, as that can result in broken formatting.
openLine := f.Line(node.Pos())
closeLine := f.Line(node.End())
f.removeLines(openLine, closeLine)
} else {
// Remove lines before first comment/field and lines after last
// comment/field
var bodyPos, bodyEnd token.Pos
if numFields > 0 {
bodyPos = node.List[0].Pos()
bodyEnd = node.List[len(node.List)-1].End()
}
if len(comments) > 0 {
if pos := comments[0].Pos(); !bodyPos.IsValid() || pos < bodyPos {
bodyPos = pos
}
if pos := comments[len(comments)-1].End(); !bodyPos.IsValid() || pos > bodyEnd {
bodyEnd = pos
}
}
f.removeLinesBetween(node.Pos(), bodyPos)
f.removeLinesBetween(bodyEnd, node.End())
}
// Merging adjacent fields (e.g. parameters) is disabled by default.
if !f.ExtraRules {
break
}
switch c.Parent().(type) {
case *ast.FuncDecl, *ast.FuncType, *ast.InterfaceType:
node.List = f.mergeAdjacentFields(node.List)
c.Replace(node)
case *ast.StructType:
// Do not merge adjacent fields in structs.
}
case *ast.BasicLit:
// Octal number literals were introduced in 1.13.
if semver.Compare(f.LangVersion, "v1.13") >= 0 {
if node.Kind == token.INT && rxOctalInteger.MatchString(node.Value) {
node.Value = "0o" + node.Value[1:]
c.Replace(node)
}
}
case *ast.AssignStmt:
// Only remove lines between the assignment token and the first right-hand side expression
f.removeLines(f.Line(node.TokPos), f.Line(node.Rhs[0].Pos()))
}
}
func (f *fumpter) applyPost(c *astutil.Cursor) {
switch node := c.Node().(type) {
// Adding newlines to composite literals happens as a "post" step, so
// that we can take into account whether "pre" steps added any newlines
// that would affect us here.
case *ast.CompositeLit:
if len(node.Elts) == 0 {
// doesn't have elements
break
}
openLine := f.Line(node.Lbrace)
closeLine := f.Line(node.Rbrace)
if openLine == closeLine {
// all in a single line
break
}
newlineAroundElems := false
newlineBetweenElems := false
lastEnd := node.Lbrace
lastLine := openLine
for i, elem := range node.Elts {
pos := elem.Pos()
comments := f.commentsBetween(lastEnd, pos)
if len(comments) > 0 {
pos = comments[0].Pos()
}
if curLine := f.Line(pos); curLine > lastLine {
if i == 0 {
newlineAroundElems = true
// remove leading lines if they exist
f.removeLines(openLine+1, curLine)
} else {
newlineBetweenElems = true
}
}
lastEnd = elem.End()
lastLine = f.Line(lastEnd)
}
if closeLine > lastLine {
newlineAroundElems = true
}
if newlineBetweenElems || newlineAroundElems {
first := node.Elts[0]
if openLine == f.Line(first.Pos()) {
// We want the newline right after the brace.
f.addNewline(node.Lbrace + 1)
closeLine = f.Line(node.Rbrace)
}
last := node.Elts[len(node.Elts)-1]
if closeLine == f.Line(last.End()) {
// We want the newline right before the brace.
f.addNewline(node.Rbrace)
}
}
// If there's a newline between any consecutive elements, there
// must be a newline between all composite literal elements.
if !newlineBetweenElems {
break
}
for i1, elem1 := range node.Elts {
i2 := i1 + 1
if i2 >= len(node.Elts) {
break
}
elem2 := node.Elts[i2]
// TODO: do we care about &{}?
_, ok1 := elem1.(*ast.CompositeLit)
_, ok2 := elem2.(*ast.CompositeLit)
if !ok1 && !ok2 {
continue
}
if f.Line(elem1.End()) == f.Line(elem2.Pos()) {
f.addNewline(elem1.End())
}
}
}
}
func (f *fumpter) splitLongLine(c *astutil.Cursor) {
if os.Getenv("GOFUMPT_SPLIT_LONG_LINES") != "on" {
// By default, this feature is turned off.
// Turn it on by setting GOFUMPT_SPLIT_LONG_LINES=on.
return
}
node := c.Node()
if node == nil {
return
}
newlinePos := node.Pos()
start := f.Position(node.Pos())
end := f.Position(node.End())
// If the node is already split in multiple lines, there's nothing to do.
if start.Line != end.Line {
return
}
// Only split at the start of the current node if it's part of a list.
if _, ok := c.Parent().(*ast.BinaryExpr); ok {
// Chains of binary expressions are considered lists, too.
} else if c.Index() >= 0 {
// For the rest of the nodes, we're in a list if c.Index() >= 0.
} else {
return
}
// Like in printLength, add an approximation of the indentation level.
// Since any existing tabs were already counted as one column, multiply
// the level by 7.
startCol := start.Column + f.blockLevel*7
endCol := end.Column + f.blockLevel*7
// If this is a composite literal,
// and we were going to insert a newline before the entire literal,
// insert the newline before the first element instead.
// Since we'll add a newline after the last element too,
// this format is generally going to be nicer.
if comp := isComposite(node); comp != nil && len(comp.Elts) > 0 {
newlinePos = comp.Elts[0].Pos()
}
// If this is a function call,
// and we were to add a newline before the first argument,
// prefer adding the newline before the entire call.
// End-of-line parentheses aren't very nice, as we don't put their
// counterparts at the start of a line too.
// We do this by using the average of the two starting positions.
if call, _ := node.(*ast.CallExpr); call != nil && len(call.Args) > 0 {
first := f.Position(call.Args[0].Pos())
startCol += (first.Column - start.Column) / 2
}
// If the start position is too short, we definitely won't split the line.
if startCol <= shortLineLimit {
return
}
lineEnd := f.Position(f.lineEnd(start.Line))
// firstLength and secondLength are the split line lengths, excluding
// indentation.
firstLength := start.Column - f.blockLevel
if firstLength < 0 {
panic("negative length")
}
secondLength := lineEnd.Column - start.Column
if secondLength < 0 {
panic("negative length")
}
// If the line ends past the long line limit,
// and both splits are estimated to take at least minSplitFactor of the limit,
// then split the line.
minSplitLength := int(f.minSplitFactor * longLineLimit)
if endCol > longLineLimit &&
firstLength >= minSplitLength && secondLength >= minSplitLength {
f.addNewline(newlinePos)
}
}
func isComposite(node ast.Node) *ast.CompositeLit {
switch node := node.(type) {
case *ast.CompositeLit:
return node
case *ast.UnaryExpr:
return isComposite(node.X) // e.g. &T{}
default:
return nil
}
}
func (f *fumpter) stmts(list []ast.Stmt) {
for i, stmt := range list {
ifs, ok := stmt.(*ast.IfStmt)
if !ok || i < 1 {
continue // not an if following another statement
}
as, ok := list[i-1].(*ast.AssignStmt)
if !ok || (as.Tok != token.DEFINE && as.Tok != token.ASSIGN) ||
!identEqual(as.Lhs[len(as.Lhs)-1], "err") {
continue // not ", err :=" nor ", err ="
}
be, ok := ifs.Cond.(*ast.BinaryExpr)
if !ok || ifs.Init != nil || ifs.Else != nil {
continue // complex if
}
if be.Op != token.NEQ || !identEqual(be.X, "err") ||
!identEqual(be.Y, "nil") {
continue // not "err != nil"
}
f.removeLinesBetween(as.End(), ifs.Pos())
}
}
func identEqual(expr ast.Expr, name string) bool {
id, ok := expr.(*ast.Ident)
return ok && id.Name == name
}
// isCgoImport returns true if the declaration is simply:
//
// import "C"
//
// or the equivalent:
//
// import `C`
//
// Note that parentheses do not affect the result.
func isCgoImport(decl *ast.GenDecl) bool {
if decl.Tok != token.IMPORT || len(decl.Specs) != 1 {
return false
}
spec := decl.Specs[0].(*ast.ImportSpec)
v, err := strconv.Unquote(spec.Path.Value)
if err != nil {
panic(err) // should never error
}
return v == "C"
}
// joinStdImports ensures that all standard library imports are together and at
// the top of the imports list.
func (f *fumpter) joinStdImports(d *ast.GenDecl) {
var std, other []ast.Spec
firstGroup := true
lastEnd := d.Pos()
needsSort := false
// If ModulePath is "foo/bar", we assume "foo/..." is not part of std.
// Users shouldn't declare modules that may collide with std this way,
// but historically some private codebases have done so.
// This is a relatively harmless way to make gofumpt compatible with them,
// as it changes nothing for the common external module paths.
var modulePrefix string
if f.ModulePath == "" {
// Nothing to do.
} else if i := strings.IndexByte(f.ModulePath, '/'); i != -1 {
// ModulePath is "foo/bar", so we use "foo" as the prefix.
modulePrefix = f.ModulePath[:i]
} else {
// ModulePath is "foo", so we use "foo" as the prefix.
modulePrefix = f.ModulePath
}
for i, spec := range d.Specs {
spec := spec.(*ast.ImportSpec)
if coms := f.commentsBetween(lastEnd, spec.Pos()); len(coms) > 0 {
lastEnd = coms[len(coms)-1].End()
}
if i > 0 && firstGroup && f.Line(spec.Pos()) > f.Line(lastEnd)+1 {
firstGroup = false
} else {
// We're still in the first group, update lastEnd.
lastEnd = spec.End()
}
path, err := strconv.Unquote(spec.Path.Value)
if err != nil {
panic(err) // should never error
}
periodIndex := strings.IndexByte(path, '.')
slashIndex := strings.IndexByte(path, '/')
switch {
// Imports with a period in the first path element are third party.
// Note that this includes "foo.com" and excludes "foo/bar.com/baz".
case periodIndex > 0 && (slashIndex == -1 || periodIndex < slashIndex),
// "test" and "example" are reserved as per golang.org/issue/37641.
// "internal" is unreachable.
strings.HasPrefix(path, "test/"),
strings.HasPrefix(path, "example/"),
strings.HasPrefix(path, "internal/"),
// See if we match modulePrefix; see its documentation above.
// We match either exactly or with a slash suffix,
// so that the prefix "foo" for "foo/..." does not match "foobar".
path == modulePrefix || strings.HasPrefix(path, modulePrefix+"/"),
// To be conservative, if an import has a name or an inline
// comment, and isn't part of the top group, treat it as non-std.
!firstGroup && (spec.Name != nil || spec.Comment != nil):
other = append(other, spec)
continue
}
// If we're moving this std import further up, reset its
// position, to avoid breaking comments.
if !firstGroup || len(other) > 0 {
setPos(reflect.ValueOf(spec), d.Pos())
needsSort = true
}
std = append(std, spec)
}
// Ensure there is an empty line between std imports and other imports.
if len(std) > 0 && len(other) > 0 && f.Line(std[len(std)-1].End())+1 >= f.Line(other[0].Pos()) {
// We add two newlines, as that's necessary in some edge cases.
// For example, if the std and non-std imports were together and
// without indentation, adding one newline isn't enough. Two
// empty lines will be printed as one by go/printer, anyway.
f.addNewline(other[0].Pos() - 1)
f.addNewline(other[0].Pos())
}
// Finally, join the imports, keeping std at the top.
d.Specs = append(std, other...)
// If we moved any std imports to the first group, we need to sort them
// again.
if needsSort {
ast.SortImports(f.fset, f.astFile)
}
}
// mergeAdjacentFields returns fields with adjacent fields merged if possible.
func (f *fumpter) mergeAdjacentFields(fields []*ast.Field) []*ast.Field {
// If there are less than two fields then there is nothing to merge.
if len(fields) < 2 {
return fields
}
// Otherwise, iterate over adjacent pairs of fields, merging if possible,
// and mutating fields. Elements of fields may be mutated (if merged with
// following fields), discarded (if merged with a preceding field), or left
// unchanged.
i := 0
for j := 1; j < len(fields); j++ {
if f.shouldMergeAdjacentFields(fields[i], fields[j]) {
fields[i].Names = append(fields[i].Names, fields[j].Names...)
} else {
i++
fields[i] = fields[j]
}
}
return fields[:i+1]
}
func (f *fumpter) shouldMergeAdjacentFields(f1, f2 *ast.Field) bool {
if len(f1.Names) == 0 || len(f2.Names) == 0 {
// Both must have names for the merge to work.
return false
}
if f.Line(f1.Pos()) != f.Line(f2.Pos()) {
// Trust the user if they used separate lines.
return false
}
// Only merge if the types are equal.
opt := cmp.Comparer(func(x, y token.Pos) bool { return true })
return cmp.Equal(f1.Type, f2.Type, opt)
}
var posType = reflect.TypeOf(token.NoPos)
// setPos recursively sets all position fields in the node v to pos.
func setPos(v reflect.Value, pos token.Pos) {
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if !v.IsValid() {
return
}
if v.Type() == posType {
v.Set(reflect.ValueOf(pos))
}
if v.Kind() == reflect.Struct {
for i := 0; i < v.NumField(); i++ {
setPos(v.Field(i), pos)
}
}
}
func containsAnyDirective(group *ast.CommentGroup) bool {
if group == nil {
return false
}
for _, comment := range group.List {
body := strings.TrimPrefix(comment.Text, "//")
if rxCommentDirective.MatchString(body) {
return true
}
}
return false
}
gofumpt-0.6.0/format/format_test.go 0000664 0000000 0000000 00000001040 14555553334 0017353 0 ustar 00root root 0000000 0000000 // Copyright (c) 2021, Daniel Martí
// See LICENSE for licensing information
package format_test
import (
"testing"
qt "github.com/frankban/quicktest"
"mvdan.cc/gofumpt/format"
)
func TestSourceIncludesSimplify(t *testing.T) {
t.Parallel()
in := []byte(`
package p
var ()
func f() {
for _ = range v {
}
}
`[1:])
want := []byte(`
package p
func f() {
for range v {
}
}
`[1:])
got, err := format.Source(in, format.Options{})
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, string(got), qt.Equals, string(want))
}
gofumpt-0.6.0/format/fuzz_test.go 0000664 0000000 0000000 00000003411 14555553334 0017065 0 ustar 00root root 0000000 0000000 // Copyright (c) 2021, Daniel Martí
// See LICENSE for licensing information
package format
import (
"errors"
"fmt"
"go/scanner"
"path/filepath"
"strings"
"testing"
qt "github.com/frankban/quicktest"
"golang.org/x/tools/txtar"
)
func FuzzFormat(f *testing.F) {
// Initialize the corpus with the Go files from our test scripts.
paths, err := filepath.Glob(filepath.Join("..", "testdata", "script", "*.txtar"))
qt.Assert(f, err, qt.IsNil)
qt.Assert(f, paths, qt.Not(qt.HasLen), 0)
for _, path := range paths {
archive, err := txtar.ParseFile(path)
qt.Assert(f, err, qt.IsNil)
for _, file := range archive.Files {
f.Logf("adding %s from %s", file.Name, path)
if strings.HasSuffix(file.Name, ".go") || strings.Contains(file.Name, ".go.") {
f.Add(string(file.Data), int8(18), false) // -lang=1.18
f.Add(string(file.Data), int8(1), false) // -lang=1.1
f.Add(string(file.Data), int8(18), true) // -lang=1.18 -extra
}
}
}
f.Fuzz(func(t *testing.T, src string,
majorVersion int8, // Empty version if negative, 1.N otherwise.
extraRules bool,
) {
// TODO: also fuzz Options.ModulePath
opts := Options{ExtraRules: extraRules}
if majorVersion >= 0 {
opts.LangVersion = fmt.Sprintf("1.%d", majorVersion)
}
orig := []byte(src)
formatted, err := Source(orig, opts)
if errors.As(err, &scanner.ErrorList{}) {
return // invalid syntax from parsing
}
qt.Assert(t, err, qt.IsNil)
_ = formatted
// TODO: verify that the result is idempotent
// TODO: verify that, if the input was valid Go 1.N syntax,
// so is the output (how? go/parser lacks an option)
// TODO: check calling format.Node directly as well
qt.Assert(t, string(orig), qt.Equals, src,
qt.Commentf("input source bytes were modified"))
})
}
gofumpt-0.6.0/format/rewrite.go 0000664 0000000 0000000 00000005776 14555553334 0016531 0 ustar 00root root 0000000 0000000 // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package format
import (
"go/ast"
"go/token"
"reflect"
"unicode"
"unicode/utf8"
)
// Values/types for special cases.
var (
identType = reflect.TypeOf((*ast.Ident)(nil))
objectPtrType = reflect.TypeOf((*ast.Object)(nil))
positionType = reflect.TypeOf(token.NoPos)
callExprType = reflect.TypeOf((*ast.CallExpr)(nil))
)
func isWildcard(s string) bool {
rune, size := utf8.DecodeRuneInString(s)
return size == len(s) && unicode.IsLower(rune)
}
// match reports whether pattern matches val,
// recording wildcard submatches in m.
// If m == nil, match checks whether pattern == val.
func match(m map[string]reflect.Value, pattern, val reflect.Value) bool {
// Wildcard matches any expression. If it appears multiple
// times in the pattern, it must match the same expression
// each time.
if m != nil && pattern.IsValid() && pattern.Type() == identType {
name := pattern.Interface().(*ast.Ident).Name
if isWildcard(name) && val.IsValid() {
// wildcards only match valid (non-nil) expressions.
if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() {
if old, ok := m[name]; ok {
return match(nil, old, val)
}
m[name] = val
return true
}
}
}
// Otherwise, pattern and val must match recursively.
if !pattern.IsValid() || !val.IsValid() {
return !pattern.IsValid() && !val.IsValid()
}
if pattern.Type() != val.Type() {
return false
}
// Special cases.
switch pattern.Type() {
case identType:
// For identifiers, only the names need to match
// (and none of the other *ast.Object information).
// This is a common case, handle it all here instead
// of recursing down any further via reflection.
p := pattern.Interface().(*ast.Ident)
v := val.Interface().(*ast.Ident)
return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name
case objectPtrType, positionType:
// object pointers and token positions always match
return true
case callExprType:
// For calls, the Ellipsis fields (token.Position) must
// match since that is how f(x) and f(x...) are different.
// Check them here but fall through for the remaining fields.
p := pattern.Interface().(*ast.CallExpr)
v := val.Interface().(*ast.CallExpr)
if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() {
return false
}
}
p := reflect.Indirect(pattern)
v := reflect.Indirect(val)
if !p.IsValid() || !v.IsValid() {
return !p.IsValid() && !v.IsValid()
}
switch p.Kind() {
case reflect.Slice:
if p.Len() != v.Len() {
return false
}
for i := 0; i < p.Len(); i++ {
if !match(m, p.Index(i), v.Index(i)) {
return false
}
}
return true
case reflect.Struct:
for i := 0; i < p.NumField(); i++ {
if !match(m, p.Field(i), v.Field(i)) {
return false
}
}
return true
case reflect.Interface:
return match(m, p.Elem(), v.Elem())
}
// Handle token integers, etc.
return p.Interface() == v.Interface()
}
gofumpt-0.6.0/format/simplify.go 0000664 0000000 0000000 00000011410 14555553334 0016662 0 ustar 00root root 0000000 0000000 // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package format
import (
"go/ast"
"go/token"
"reflect"
)
type simplifier struct{}
func (s simplifier) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.CompositeLit:
// array, slice, and map composite literals may be simplified
outer := n
var keyType, eltType ast.Expr
switch typ := outer.Type.(type) {
case *ast.ArrayType:
eltType = typ.Elt
case *ast.MapType:
keyType = typ.Key
eltType = typ.Value
}
if eltType != nil {
var ktyp reflect.Value
if keyType != nil {
ktyp = reflect.ValueOf(keyType)
}
typ := reflect.ValueOf(eltType)
for i, x := range outer.Elts {
px := &outer.Elts[i]
// look at value of indexed/named elements
if t, ok := x.(*ast.KeyValueExpr); ok {
if keyType != nil {
s.simplifyLiteral(ktyp, keyType, t.Key, &t.Key)
}
x = t.Value
px = &t.Value
}
s.simplifyLiteral(typ, eltType, x, px)
}
// node was simplified - stop walk (there are no subnodes to simplify)
return nil
}
case *ast.SliceExpr:
// a slice expression of the form: s[a:len(s)]
// can be simplified to: s[a:]
// if s is "simple enough" (for now we only accept identifiers)
//
// Note: This may not be correct because len may have been redeclared in
// the same package. However, this is extremely unlikely and so far
// (April 2022, after years of supporting this rewrite feature)
// has never come up, so let's keep it working as is (see also #15153).
//
// Also note that this code used to use go/ast's object tracking,
// which was removed in exchange for go/parser.Mode.SkipObjectResolution.
// False positives are extremely unlikely as described above,
// and go/ast's object tracking is incomplete in any case.
if n.Max != nil {
// - 3-index slices always require the 2nd and 3rd index
break
}
if s, _ := n.X.(*ast.Ident); s != nil {
// the array/slice object is a single identifier
if call, _ := n.High.(*ast.CallExpr); call != nil && len(call.Args) == 1 && !call.Ellipsis.IsValid() {
// the high expression is a function call with a single argument
if fun, _ := call.Fun.(*ast.Ident); fun != nil && fun.Name == "len" {
// the function called is "len"
if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Name == s.Name {
// the len argument is the array/slice object
n.High = nil
}
}
}
}
// Note: We could also simplify slice expressions of the form s[0:b] to s[:b]
// but we leave them as is since sometimes we want to be very explicit
// about the lower bound.
// An example where the 0 helps:
// x, y, z := b[0:2], b[2:4], b[4:6]
// An example where it does not:
// x, y := b[:n], b[n:]
case *ast.RangeStmt:
// - a range of the form: for x, _ = range v {...}
// can be simplified to: for x = range v {...}
// - a range of the form: for _ = range v {...}
// can be simplified to: for range v {...}
if isBlank(n.Value) {
n.Value = nil
}
if isBlank(n.Key) && n.Value == nil {
n.Key = nil
}
}
return s
}
func (s simplifier) simplifyLiteral(typ reflect.Value, astType, x ast.Expr, px *ast.Expr) {
ast.Walk(s, x) // simplify x
// if the element is a composite literal and its literal type
// matches the outer literal's element type exactly, the inner
// literal type may be omitted
if inner, ok := x.(*ast.CompositeLit); ok {
if match(nil, typ, reflect.ValueOf(inner.Type)) {
inner.Type = nil
}
}
// if the outer literal's element type is a pointer type *T
// and the element is & of a composite literal of type T,
// the inner &T may be omitted.
if ptr, ok := astType.(*ast.StarExpr); ok {
if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND {
if inner, ok := addr.X.(*ast.CompositeLit); ok {
if match(nil, reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) {
inner.Type = nil // drop T
*px = inner // drop &
}
}
}
}
}
func isBlank(x ast.Expr) bool {
ident, ok := x.(*ast.Ident)
return ok && ident.Name == "_"
}
func simplify(f *ast.File) {
// remove empty declarations such as "const ()", etc
removeEmptyDeclGroups(f)
var s simplifier
ast.Walk(s, f)
}
func removeEmptyDeclGroups(f *ast.File) {
i := 0
for _, d := range f.Decls {
if g, ok := d.(*ast.GenDecl); !ok || !isEmpty(f, g) {
f.Decls[i] = d
i++
}
}
f.Decls = f.Decls[:i]
}
func isEmpty(f *ast.File, g *ast.GenDecl) bool {
if g.Doc != nil || g.Specs != nil {
return false
}
for _, c := range f.Comments {
// if there is a comment in the declaration, it is not considered empty
if g.Pos() <= c.Pos() && c.End() <= g.End() {
return false
}
}
return true
}
gofumpt-0.6.0/format/testdata/ 0000775 0000000 0000000 00000000000 14555553334 0016313 5 ustar 00root root 0000000 0000000 gofumpt-0.6.0/format/testdata/fuzz/ 0000775 0000000 0000000 00000000000 14555553334 0017311 5 ustar 00root root 0000000 0000000 gofumpt-0.6.0/format/testdata/fuzz/FuzzFormat/ 0000775 0000000 0000000 00000000000 14555553334 0021420 5 ustar 00root root 0000000 0000000 18c862f09f82fe57f536e7ab4b1bd63daecc2cba8189530bb0eb77b8cef6f798 0000664 0000000 0000000 00000000154 14555553334 0032555 0 ustar 00root root 0000000 0000000 gofumpt-0.6.0/format/testdata/fuzz/FuzzFormat go test fuzz v1
string("package A\nfunc A000000000(A000000000000,\nA00000000)(){\"\"}")
int8(62)
bool(true)
948d1d5be3c838b207d345a3ac57e97bb3b77788cb5039a65994967490c49baa 0000664 0000000 0000000 00000000113 14555553334 0031762 0 ustar 00root root 0000000 0000000 gofumpt-0.6.0/format/testdata/fuzz/FuzzFormat go test fuzz v1
string("package A\nvar A A\nvar A A")
int8(18)
bool(false)
gofumpt-0.6.0/gen_govendor.go 0000664 0000000 0000000 00000003620 14555553334 0016216 0 ustar 00root root 0000000 0000000 // Copyright (c) 2019, Daniel Martí
// See LICENSE for licensing information
//go:build ignore
package main
import (
"bytes"
"encoding/json"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
)
var (
modulePath = "mvdan.cc/gofumpt"
vendorDir = filepath.Join("internal", "govendor")
)
// All the packages which affect the formatting behavior.
var toVendor = []string{
"go/format",
"go/printer",
"go/doc/comment",
"internal/diff",
}
func main() {
catch(os.RemoveAll(vendorDir))
catch(os.MkdirAll(vendorDir, 0o777))
out, err := exec.Command("go", "env", "GOVERSION").Output()
catch(err)
catch(os.WriteFile(filepath.Join(vendorDir, "version.txt"), out, 0o666))
oldnew := []string{
"//go:generate", "//disabled go:generate",
}
for _, pkgPath := range toVendor {
oldnew = append(oldnew, pkgPath, path.Join(modulePath, vendorDir, pkgPath))
}
replacer := strings.NewReplacer(oldnew...)
listArgs := append([]string{"list", "-json"}, toVendor...)
out, err = exec.Command("go", listArgs...).Output()
catch(err)
type Package struct {
Dir string
ImportPath string
GoFiles []string
}
dec := json.NewDecoder(bytes.NewReader(out))
for {
var pkg Package
err := dec.Decode(&pkg)
if err == io.EOF {
break
}
catch(err)
// Otherwise we can't import it.
dstPkg := strings.TrimPrefix(pkg.ImportPath, "internal/")
dstDir := filepath.Join(vendorDir, filepath.FromSlash(dstPkg))
catch(os.MkdirAll(dstDir, 0o777))
// TODO: if the packages start using build tags like GOOS or GOARCH,
// we will need to vendor IgnoredGoFiles as well.
for _, goFile := range pkg.GoFiles {
srcBytes, err := os.ReadFile(filepath.Join(pkg.Dir, goFile))
catch(err)
src := replacer.Replace(string(srcBytes))
dst := filepath.Join(dstDir, goFile)
catch(os.WriteFile(dst, []byte(src), 0o666))
}
}
}
func catch(err error) {
if err != nil {
panic(err)
}
}
gofumpt-0.6.0/go.mod 0000664 0000000 0000000 00000000546 14555553334 0014325 0 ustar 00root root 0000000 0000000 module mvdan.cc/gofumpt
go 1.20
require (
github.com/frankban/quicktest v1.14.6
github.com/google/go-cmp v0.6.0
github.com/rogpeppe/go-internal v1.12.0
golang.org/x/mod v0.14.0
golang.org/x/sync v0.6.0
golang.org/x/sys v0.16.0
golang.org/x/tools v0.17.0
)
require (
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
)
gofumpt-0.6.0/go.sum 0000664 0000000 0000000 00000003452 14555553334 0014351 0 ustar 00root root 0000000 0000000 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
gofumpt-0.6.0/gofmt.go 0000664 0000000 0000000 00000042222 14555553334 0014657 0 ustar 00root root 0000000 0000000 // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/scanner"
"go/token"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"runtime/pprof"
"strings"
"sync"
"golang.org/x/sync/semaphore"
gformat "mvdan.cc/gofumpt/format"
"mvdan.cc/gofumpt/internal/govendor/diff"
"mvdan.cc/gofumpt/internal/govendor/go/printer"
gversion "mvdan.cc/gofumpt/internal/version"
)
//go:generate go run gen_govendor.go
//go:generate go run . -w internal/govendor
var (
// main operation modes
list = flag.Bool("l", false, "")
write = flag.Bool("w", false, "")
doDiff = flag.Bool("d", false, "")
allErrors = flag.Bool("e", false, "")
// debugging
cpuprofile = flag.String("cpuprofile", "", "")
// gofumpt's own flags
langVersion = flag.String("lang", "", "")
modulePath = flag.String("modpath", "", "")
extraRules = flag.Bool("extra", false, "")
showVersion = flag.Bool("version", false, "")
// DEPRECATED
rewriteRule = flag.String("r", "", "")
simplifyAST = flag.Bool("s", false, "")
)
var version = ""
// Keep these in sync with go/format/format.go.
const (
tabWidth = 8
printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers
// printerNormalizeNumbers means to canonicalize number literal prefixes
// and exponents while printing. See https://golang.org/doc/go1.13#gofmt.
//
// This value is defined in go/printer specifically for go/format and cmd/gofmt.
printerNormalizeNumbers = 1 << 30
)
// fdSem guards the number of concurrently-open file descriptors.
//
// For now, this is arbitrarily set to 200, based on the observation that many
// platforms default to a kernel limit of 256. Ideally, perhaps we should derive
// it from rlimit on platforms that support that system call.
//
// File descriptors opened from outside of this package are not tracked,
// so this limit may be approximate.
var fdSem = make(chan bool, 200)
var (
fileSet = token.NewFileSet() // per process FileSet
parserMode parser.Mode
)
func usage() {
fmt.Fprintf(os.Stderr, `usage: gofumpt [flags] [path ...]
-version show version and exit
-d display diffs instead of rewriting files
-e report all errors (not just the first 10 on different lines)
-l list files whose formatting differs from gofumpt's
-w write result to (source) file instead of stdout
-extra enable extra rules which should be vetted by a human
-lang str target Go version in the form "1.X" (default from go.mod)
-modpath str Go module path containing the source file (default from go.mod)
`)
}
func initParserMode() {
parserMode = parser.ParseComments | parser.SkipObjectResolution
if *allErrors {
parserMode |= parser.AllErrors
}
}
func isGoFile(f fs.DirEntry) bool {
// ignore non-Go files
name := f.Name()
return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && !f.IsDir()
}
var rxCodeGenerated = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
func isGenerated(file *ast.File) bool {
for _, cg := range file.Comments {
if cg.Pos() > file.Package {
return false
}
for _, line := range cg.List {
if rxCodeGenerated.MatchString(line.Text) {
return true
}
}
}
return false
}
// A sequencer performs concurrent tasks that may write output, but emits that
// output in a deterministic order.
type sequencer struct {
maxWeight int64
sem *semaphore.Weighted // weighted by input bytes (an approximate proxy for memory overhead)
prev <-chan *reporterState // 1-buffered
}
// newSequencer returns a sequencer that allows concurrent tasks up to maxWeight
// and writes tasks' output to out and err.
func newSequencer(maxWeight int64, out, err io.Writer) *sequencer {
sem := semaphore.NewWeighted(maxWeight)
prev := make(chan *reporterState, 1)
prev <- &reporterState{out: out, err: err}
return &sequencer{
maxWeight: maxWeight,
sem: sem,
prev: prev,
}
}
// exclusive is a weight that can be passed to a sequencer to cause
// a task to be executed without any other concurrent tasks.
const exclusive = -1
// Add blocks until the sequencer has enough weight to spare, then adds f as a
// task to be executed concurrently.
//
// If the weight is either negative or larger than the sequencer's maximum
// weight, Add blocks until all other tasks have completed, then the task
// executes exclusively (blocking all other calls to Add until it completes).
//
// f may run concurrently in a goroutine, but its output to the passed-in
// reporter will be sequential relative to the other tasks in the sequencer.
//
// If f invokes a method on the reporter, execution of that method may block
// until the previous task has finished. (To maximize concurrency, f should
// avoid invoking the reporter until it has finished any parallelizable work.)
//
// If f returns a non-nil error, that error will be reported after f's output
// (if any) and will cause a nonzero final exit code.
func (s *sequencer) Add(weight int64, f func(*reporter) error) {
if weight < 0 || weight > s.maxWeight {
weight = s.maxWeight
}
if err := s.sem.Acquire(context.TODO(), weight); err != nil {
// Change the task from "execute f" to "report err".
weight = 0
f = func(*reporter) error { return err }
}
r := &reporter{prev: s.prev}
next := make(chan *reporterState, 1)
s.prev = next
// Start f in parallel: it can run until it invokes a method on r, at which
// point it will block until the previous task releases the output state.
go func() {
if err := f(r); err != nil {
r.Report(err)
}
next <- r.getState() // Release the next task.
s.sem.Release(weight)
}()
}
// AddReport prints an error to s after the output of any previously-added
// tasks, causing the final exit code to be nonzero.
func (s *sequencer) AddReport(err error) {
s.Add(0, func(*reporter) error { return err })
}
// GetExitCode waits for all previously-added tasks to complete, then returns an
// exit code for the sequence suitable for passing to os.Exit.
func (s *sequencer) GetExitCode() int {
c := make(chan int, 1)
s.Add(0, func(r *reporter) error {
c <- r.ExitCode()
return nil
})
return <-c
}
// A reporter reports output, warnings, and errors.
type reporter struct {
prev <-chan *reporterState
state *reporterState
}
// reporterState carries the state of a reporter instance.
//
// Only one reporter at a time may have access to a reporterState.
type reporterState struct {
out, err io.Writer
exitCode int
}
// getState blocks until any prior reporters are finished with the reporter
// state, then returns the state for manipulation.
func (r *reporter) getState() *reporterState {
if r.state == nil {
r.state = <-r.prev
}
return r.state
}
// Warnf emits a warning message to the reporter's error stream,
// without changing its exit code.
func (r *reporter) Warnf(format string, args ...any) {
fmt.Fprintf(r.getState().err, format, args...)
}
// Write emits a slice to the reporter's output stream.
//
// Any error is returned to the caller, and does not otherwise affect the
// reporter's exit code.
func (r *reporter) Write(p []byte) (int, error) {
return r.getState().out.Write(p)
}
// Report emits a non-nil error to the reporter's error stream,
// changing its exit code to a nonzero value.
func (r *reporter) Report(err error) {
if err == nil {
panic("Report with nil error")
}
st := r.getState()
scanner.PrintError(st.err, err)
st.exitCode = 2
}
func (r *reporter) ExitCode() int {
return r.getState().exitCode
}
// If info == nil, we are formatting stdin instead of a file.
// If in == nil, the source is the contents of the file with the given filename.
func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter, explicit bool) error {
src, err := readFile(filename, info, in)
if err != nil {
return err
}
fileSet := token.NewFileSet()
fragmentOk := false
if info == nil {
// If we are formatting stdin, we accept a program fragment in lieu of a
// complete source file.
fragmentOk = true
}
file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, fragmentOk)
if err != nil {
return err
}
ast.SortImports(fileSet, file)
// Apply gofumpt's changes before we print the code in gofumpt's format.
// If either -lang or -modpath aren't set, fetch them from go.mod.
lang := *langVersion
modpath := *modulePath
if lang == "" || modpath == "" {
dir := filepath.Dir(filename)
mod, ok := moduleCacheByDir.Load(dir)
if ok && mod != nil {
mod := mod.(*module)
if lang == "" {
lang = mod.Go
}
if modpath == "" {
modpath = mod.Module.Path
}
}
}
// We always apply the gofumpt formatting rules to explicit files, including stdin.
// Otherwise, we don't apply them on generated files.
// We also skip walking vendor directories entirely, but that happens elsewhere.
if explicit || !isGenerated(file) {
gformat.File(fileSet, file, gformat.Options{
LangVersion: lang,
ModulePath: modpath,
ExtraRules: *extraRules,
})
}
res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth})
if err != nil {
return err
}
if !bytes.Equal(src, res) {
// formatting has changed
if *list {
fmt.Fprintln(r, filename)
}
if *write {
if info == nil {
panic("-w should not have been allowed with stdin")
}
// make a temporary backup before overwriting original
perm := info.Mode().Perm()
bakname, err := backupFile(filename+".", src, perm)
if err != nil {
return err
}
fdSem <- true
err = os.WriteFile(filename, res, perm)
<-fdSem
if err != nil {
os.Rename(bakname, filename)
return err
}
err = os.Remove(bakname)
if err != nil {
return err
}
}
if *doDiff {
newName := filepath.ToSlash(filename)
oldName := newName + ".orig"
r.Write(diff.Diff(oldName, src, newName, res))
}
}
if !*list && !*write && !*doDiff {
_, err = r.Write(res)
}
return err
}
// readFile reads the contents of filename, described by info.
// If in is non-nil, readFile reads directly from it.
// Otherwise, readFile opens and reads the file itself,
// with the number of concurrently-open files limited by fdSem.
func readFile(filename string, info fs.FileInfo, in io.Reader) ([]byte, error) {
if in == nil {
fdSem <- true
var err error
f, err := os.Open(filename)
if err != nil {
return nil, err
}
in = f
defer func() {
f.Close()
<-fdSem
}()
}
// Compute the file's size and read its contents with minimal allocations.
//
// If we have the FileInfo from filepath.WalkDir, use it to make
// a buffer of the right size and avoid ReadAll's reallocations.
//
// If the size is unknown (or bogus, or overflows an int), fall back to
// a size-independent ReadAll.
size := -1
if info != nil && info.Mode().IsRegular() && int64(int(info.Size())) == info.Size() {
size = int(info.Size())
}
if size+1 <= 0 {
// The file is not known to be regular, so we don't have a reliable size for it.
var err error
src, err := io.ReadAll(in)
if err != nil {
return nil, err
}
return src, nil
}
// We try to read size+1 bytes so that we can detect modifications: if we
// read more than size bytes, then the file was modified concurrently.
// (If that happens, we could, say, append to src to finish the read, or
// proceed with a truncated buffer — but the fact that it changed at all
// indicates a possible race with someone editing the file, so we prefer to
// stop to avoid corrupting it.)
src := make([]byte, size+1)
n, err := io.ReadFull(in, src)
switch err {
case nil, io.EOF, io.ErrUnexpectedEOF:
// io.ReadFull returns io.EOF (for an empty file) or io.ErrUnexpectedEOF
// (for a non-empty file) if the file was changed unexpectedly. Continue
// with comparing file sizes in those cases.
default:
return nil, err
}
if n < size {
return nil, fmt.Errorf("error: size of %s changed during reading (from %d to %d bytes)", filename, size, n)
} else if n > size {
return nil, fmt.Errorf("error: size of %s changed during reading (from %d to >=%d bytes)", filename, size, len(src))
}
return src[:n], nil
}
func main() { os.Exit(main1()) }
func main1() int {
// Arbitrarily limit in-flight work to 2MiB times the number of threads.
//
// The actual overhead for the parse tree and output will depend on the
// specifics of the file, but this at least keeps the footprint of the process
// roughly proportional to GOMAXPROCS.
maxWeight := (2 << 20) * int64(runtime.GOMAXPROCS(0))
s := newSequencer(maxWeight, os.Stdout, os.Stderr)
// call gofmtMain in a separate function
// so that it can use defer and have them
// run before the exit.
gofmtMain(s)
return s.GetExitCode()
}
func gofmtMain(s *sequencer) {
// Ensure our parsed files never start with base 1,
// to ensure that using token.NoPos+1 will panic.
fileSet.AddFile("gofumpt_base.go", 1, 10)
flag.Usage = usage
flag.Parse()
if *simplifyAST {
fmt.Fprintf(os.Stderr, "warning: -s is deprecated as it is always enabled\n")
}
if *rewriteRule != "" {
fmt.Fprintf(os.Stderr, `the rewrite flag is no longer available; use "gofmt -r" instead`+"\n")
os.Exit(2)
}
// Print the gofumpt version if the user asks for it.
if *showVersion {
fmt.Println(gversion.String(version))
return
}
if *cpuprofile != "" {
fdSem <- true
f, err := os.Create(*cpuprofile)
if err != nil {
s.AddReport(fmt.Errorf("creating cpu profile: %s", err))
return
}
defer func() {
f.Close()
<-fdSem
}()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
initParserMode()
args := flag.Args()
if len(args) == 0 {
if *write {
s.AddReport(fmt.Errorf("error: cannot use -w with standard input"))
return
}
s.Add(0, func(r *reporter) error {
// TODO: test explicit==true
return processFile("", nil, os.Stdin, r, true)
})
return
}
for _, arg := range args {
switch info, err := os.Stat(arg); {
case err != nil:
s.AddReport(err)
case !info.IsDir():
// Non-directory arguments are always formatted.
arg := arg
s.Add(fileWeight(arg, info), func(r *reporter) error {
return processFile(arg, info, nil, r, true)
})
default:
// Directories are walked, ignoring non-Go files.
err := filepath.WalkDir(arg, func(path string, f fs.DirEntry, err error) error {
// vendor and testdata directories are skipped,
// unless they are explicitly passed as an argument.
base := filepath.Base(path)
if path != arg && (base == "vendor" || base == "testdata") {
return filepath.SkipDir
}
if err != nil || !isGoFile(f) {
return err
}
info, err := f.Info()
if err != nil {
s.AddReport(err)
return nil
}
s.Add(fileWeight(path, info), func(r *reporter) error {
return processFile(path, info, nil, r, false)
})
return nil
})
if err != nil {
s.AddReport(err)
}
}
}
}
type module struct {
Go string
Module struct {
Path string
}
}
func loadModuleInfo(dir string) any {
cmd := exec.Command("go", "mod", "edit", "-json")
cmd.Dir = dir
// Spawning "go mod edit" will open files by design,
// such as the named pipe to obtain stdout.
// TODO(mvdan): if we run into "too many open files" errors again in the
// future, we probably need to turn fdSem into a weighted semaphore so this
// operation can acquire a weight larger than 1.
fdSem <- true
out, err := cmd.Output()
defer func() { <-fdSem }()
if err != nil || len(out) == 0 {
return nil
}
mod := new(module)
if err := json.Unmarshal(out, mod); err != nil {
return nil
}
return mod
}
// Written to by fileWeight, read from fileWeight and processFile.
// A present but nil value means that loading the module info failed.
// Note that we don't require the keys to be absolute directories,
// so duplicates are possible. The same can happen with symlinks.
var moduleCacheByDir sync.Map // map[dirString]*module
func fileWeight(path string, info fs.FileInfo) int64 {
dir := filepath.Dir(path)
if _, ok := moduleCacheByDir.Load(dir); !ok {
moduleCacheByDir.Store(dir, loadModuleInfo(dir))
}
if info == nil {
return exclusive
}
if info.Mode().Type() == fs.ModeSymlink {
var err error
info, err = os.Stat(path)
if err != nil {
return exclusive
}
}
if !info.Mode().IsRegular() {
// For non-regular files, FileInfo.Size is system-dependent and thus not a
// reliable indicator of weight.
return exclusive
}
return info.Size()
}
const chmodSupported = runtime.GOOS != "windows"
// backupFile writes data to a new file named filename with permissions perm,
// with