pax_global_header 0000666 0000000 0000000 00000000064 14143007650 0014512 g ustar 00root root 0000000 0000000 52 comment=dd8ce9a8ddbde6d3e341d57f3653dbb8d6f5a8b8
golang-mvdan-gofumpt-0.2.0/ 0000775 0000000 0000000 00000000000 14143007650 0015542 5 ustar 00root root 0000000 0000000 golang-mvdan-gofumpt-0.2.0/.gitattributes 0000664 0000000 0000000 00000000121 14143007650 0020427 0 ustar 00root root 0000000 0000000 # To prevent CRLF breakages on Windows for fragile files, like testdata.
* -text
golang-mvdan-gofumpt-0.2.0/.github/ 0000775 0000000 0000000 00000000000 14143007650 0017102 5 ustar 00root root 0000000 0000000 golang-mvdan-gofumpt-0.2.0/.github/FUNDING.yml 0000664 0000000 0000000 00000000016 14143007650 0020714 0 ustar 00root root 0000000 0000000 github: mvdan
golang-mvdan-gofumpt-0.2.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14143007650 0021137 5 ustar 00root root 0000000 0000000 golang-mvdan-gofumpt-0.2.0/.github/workflows/test.yml 0000664 0000000 0000000 00000000652 14143007650 0022644 0 ustar 00root root 0000000 0000000 on: [push, pull_request]
name: Test
jobs:
test:
strategy:
matrix:
go-version: [1.16.x, 1.17.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: go test ./...
golang-mvdan-gofumpt-0.2.0/CHANGELOG.md 0000664 0000000 0000000 00000005635 14143007650 0017364 0 ustar 00root root 0000000 0000000 # Changelog
## [0.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 [0.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
## [0.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
## [0.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.
[0.2.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.2.0
[0.1.1]: https://github.com/mvdan/gofumpt/releases/tag/v0.1.1
[0.1.0]: https://github.com/mvdan/gofumpt/releases/tag/v0.1.0
golang-mvdan-gofumpt-0.2.0/LICENSE 0000664 0000000 0000000 00000002720 14143007650 0016550 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.
golang-mvdan-gofumpt-0.2.0/LICENSE.google 0000664 0000000 0000000 00000002707 14143007650 0020030 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.
golang-mvdan-gofumpt-0.2.0/README.md 0000664 0000000 0000000 00000025243 14143007650 0017027 0 ustar 00root root 0000000 0000000 # gofumpt
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 modified fork of `gofmt`, so it can be used as a drop-in
replacement. Running `gofmt` after `gofumpt` should be a no-op. For example:
gofumpt -l -w .
Some of the Go source files in this repository belong to the Go project.
The added formatting rules are in the `format` package.
Beyond the [added rules below](#Added-rules), the tool differs from gofmt in the
following ways:
* Vendor directories are skipped unless given as explicit arguments
* The added rules are not applied to generated Go files
* The `-r` rewrite feature is removed in favor of `gofmt -r`
### Added rules
No empty lines following an assignment operator
example
```
func foo() {
foo :=
"bar"
}
```
```
func foo() {
foo := "bar"
}
```
No empty lines at the beginning or end of a function
example
```
func foo() {
println("bar")
}
```
```
func foo() {
println("bar")
}
```
Functions using an empty line for readability should use a `) {` line instead
example
```
func foo(s string,
i int) {
println("bar")
}
```
```
func foo(s string,
i int,
) {
println("bar")
}
```
No empty lines around a lone statement (or comment) in a block
example
```
if err != nil {
return err
}
```
```
if err != nil {
return err
}
```
No empty lines before a simple error check
example
```
foo, err := processFoo()
if err != nil {
return err
}
```
```
foo, err := processFoo()
if err != nil {
return err
}
```
Composite literals should use newlines consistently
example
```
// 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,
},
}
```
```
var ints = []int{
1, 2,
3, 4,
}
var matrix = [][]int{
{1},
{2},
{
3,
},
}
```
Empty field lists should use a single line
example
```
var V interface {
} = 3
type T struct {
}
func F(
)
```
```
var V interface{} = 3
type T struct{}
func F()
```
`std` imports must be in a separate group at the top
example
```
import (
"foo.com/bar"
"io"
"io/ioutil"
)
```
```
import (
"io"
"io/ioutil"
"foo.com/bar"
)
```
Short case clauses should take a single line
example
```
switch c {
case 'a', 'b',
'c', 'd':
}
```
```
switch c {
case 'a', 'b', 'c', 'd':
}
```
Multiline top-level declarations must be separated by empty lines
example
```
func foo() {
println("multiline foo")
}
func bar() {
println("multiline bar")
}
```
```
func foo() {
println("multiline foo")
}
func bar() {
println("multiline bar")
}
```
Single var declarations should not be grouped with parentheses
example
```
var (
foo = "bar"
)
```
```
var foo = "bar"
```
Contiguous top-level declarations should be grouped together
example
```
var nicer = "x"
var with = "y"
var alignment = "z"
```
```
var (
nicer = "x"
with = "y"
alignment = "z"
)
```
Simple var-declaration statements should use short assignments
example
```
var s = "somestring"
```
```
s := "somestring"
```
The `-s` code simplification flag is enabled by default
example
```
var _ = [][]int{[]int{1}}
```
```
var _ = [][]int{{1}}
```
Octal integer literals should use the `0o` prefix on modules using Go 1.13 and later
example
```
const perm = 0755
```
```
const perm = 0o755
```
Comments which aren't Go directives should start with a whitespace
example
```
//go:noinline
//Foo is awesome.
func Foo() {}
```
```
//go:noinline
// Foo is awesome.
func Foo() {}
```
Composite literals should not have leading or trailing empty lines
example
```
var _ = []string{
"foo",
}
var _ = map[string]string{
"foo": "bar",
}
```
```
var _ = []string{
"foo",
}
var _ = map[string]string{
"foo": "bar",
}
```
Remove unnecessary empty lines from interfaces
example
```
type i interface {
// comment for a
a(x int) int
// comment between a and b
// comment for b
b(x int) int
// comment between b and c
c(x int) int
d(x int) int
// comment for e
e(x int) int
}
```
```
type i interface {
// comment for a
a(x int) int
// comment between a and b
// comment for b
b(x int) int
// comment between b and c
c(x int) int
d(x int) int
// comment for e
e(x int) int
}
```
#### Extra rules behind `-extra`
Adjacent parameters with the same type should be grouped together
example
```
func Foo(bar string, baz string) {}
```
```
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 unecessary runs, you should disable all checkboxes in the *Advanced* section.
#### Vim-go
Ensure you are at least running version
[v1.24](https://github.com/fatih/vim-go/blob/master/CHANGELOG.md#v124---september-15-2020),
and set up `gopls` for formatting code with `gofumpt`:
```vim
let g:go_fmt_command="gopls"
let g:go_gopls_gofumpt=1
```
#### Govim
With a [new enough version of govim](https://github.com/govim/govim/pull/1005),
simply configure `gopls` to use `gofumpt`:
```vim
call govim#config#Set("Gofumpt", 1)
```
#### 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. Otherwise, adding new standard library
packages like `embed` would be a breaking change. See https://github.com/golang/go/issues/32819.
Third party modules should use domain names to avoid conflicts.
If your module is meant for local use only, you can use `foo.local`.
For small example or test modules, `example/...` and `test/...` may be reserved
if a proposal is accepted; see https://github.com/golang/go/issues/37641.
> 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`.
golang-mvdan-gofumpt-0.2.0/format/ 0000775 0000000 0000000 00000000000 14143007650 0017032 5 ustar 00root root 0000000 0000000 golang-mvdan-gofumpt-0.2.0/format/format.go 0000664 0000000 0000000 00000065072 14143007650 0020663 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/format"
"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/version"
)
type Options struct {
// LangVersion corresponds to the Go language 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 generally be specified as the result of:
//
// go list -m -f {{.GoVersion}}
//
// LangVersion is treated as a semantic version, which might start with
// a "v" prefix. Like Go versions, it might 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 string
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()
file, err := parser.ParseFile(fset, "", src, 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 rxCodeGenerated = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
// 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)
for _, cg := range file.Comments {
if cg.Pos() > file.Package {
break
}
for _, line := range cg.List {
if rxCodeGenerated.MatchString(line.Text) {
return
}
}
}
if opts.LangVersion == "" {
opts.LangVersion = "v1"
} else if opts.LangVersion[0] != 'v' {
opts.LangVersion = "v" + opts.LangVersion
}
if !semver.IsValid(opts.LangVersion) {
panic(fmt.Sprintf("invalid semver string: %q", opts.LangVersion))
}
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
*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)
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.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.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))
}
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) tabbedColumn(p token.Pos) int {
col := f.Position(p).Column
// 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.
return col + (f.blockLevel * 7)
}
func (f *fumpter) lineEnd(line int) token.Pos {
if line < 1 {
panic("illegal line number")
}
total := f.LineCount()
if line > total {
panic("illegal line number")
}
if line == total {
return f.astFile.End()
}
return f.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
//
// 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|nolint\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 or comments in between,
// includng a leading comment, which could be 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) || start.Doc != nil {
i++
continue
}
lastPos := start.Pos()
for i++; i < len(node.Decls); {
cont, ok := node.Decls[i].(*ast.GenDecl)
if !ok || cont.Tok != start.Tok || cont.Lparen != token.NoPos ||
f.Line(lastPos) < f.Line(cont.Pos())-1 || isCgoImport(cont) {
break
}
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()
}
multi := f.Line(pos) < f.Line(decl.End())
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.String(),
"-lang=" + f.LangVersion,
}
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 any comment, it must
// go before the entire declaration now.
node.TokPos = specPos
} else {
f.removeLines(f.Line(node.TokPos), f.Line(specPos))
}
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:
var prev *ast.Field
for _, method := range node.Methods.List {
switch {
case prev == nil:
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 len(f.commentsBetween(prev.End(), method.Pos())) > 0:
// continue
default:
f.removeLinesBetween(prev.End(), method.Pos())
}
prev = method
}
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())
paramClosingIsFirstCharOnEndLine := sign.Params != nil &&
f.Position(sign.Params.Closing).Column == 1 &&
f.Line(sign.Params.Closing) == endLine
resultClosingIsFirstCharOnEndLine := sign.Results != nil &&
f.Position(sign.Results.Closing).Column == 1 &&
f.Line(sign.Results.Closing) == endLine
endLineIsIndented := !(paramClosingIsFirstCharOnEndLine || resultClosingIsFirstCharOnEndLine)
if f.Line(sign.Pos()) != endLine && endLineIsIndented {
// is there an empty line?
isThereAnEmptyLine := endLine+1 != f.Line(bodyPos)
// The body is preceded by a multi-line function
// signature, we move the `) {` to avoid the empty line.
switch {
case isThereAnEmptyLine && sign.Results != nil && !resultClosingIsFirstCharOnEndLine:
sign.Results.Closing += 1
f.addNewline(sign.Results.Closing)
case isThereAnEmptyLine && sign.Params != nil && !paramClosingIsFirstCharOnEndLine:
sign.Params.Closing += 1
f.addNewline(sign.Params.Closing)
}
}
}
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
}
if f.printLength(node) > shortLineLimit {
// too long to collapse
break
}
f.removeLines(openLine, closeLine)
case *ast.CommClause:
f.stmts(node.Body)
case *ast.FieldList:
if node.NumFields() == 0 && len(f.commentsBetween(node.Pos(), node.End())) == 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)
}
// 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
lastLine := openLine
for i, elem := range node.Elts {
if elPos := f.Line(elem.Pos()); elPos > lastLine {
if i == 0 {
newlineAroundElems = true
// remove leading lines if they exist
f.removeLines(openLine+1, elPos)
} else {
newlineBetweenElems = true
}
}
lastLine = f.Line(elem.End())
}
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 ||
!identEqual(as.Lhs[len(as.Lhs)-1], "err") {
continue // not "..., 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"
//
// 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)
return spec.Path.Value == `"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
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, _ := strconv.Unquote(spec.Path.Value)
switch {
// Imports with a period are definitely third party.
case strings.Contains(path, "."):
fallthrough
// "test" and "example" are reserved as per golang.org/issue/37641.
// "internal" is unreachable.
case strings.HasPrefix(path, "test/") ||
strings.HasPrefix(path, "example/") ||
strings.HasPrefix(path, "internal/"):
fallthrough
// 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.
case !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 preceeding 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)
}
}
}
golang-mvdan-gofumpt-0.2.0/format/format_test.go 0000664 0000000 0000000 00000001040 14143007650 0021703 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))
}
golang-mvdan-gofumpt-0.2.0/format/rewrite.go 0000664 0000000 0000000 00000005776 14143007650 0021061 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()
}
golang-mvdan-gofumpt-0.2.0/format/simplify.go 0000664 0000000 0000000 00000011250 14143007650 0021214 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 another
// file belonging to the same package. However, this is extremely unlikely
// and so far (April 2016, after years of supporting this rewrite feature)
// has never come up, so let's keep it working as is (see also #15153).
if n.Max != nil {
// - 3-index slices always require the 2nd and 3rd index
break
}
if s, _ := n.X.(*ast.Ident); s != nil && s.Obj != nil {
// the array/slice object is a single, resolved 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" && fun.Obj == nil {
// the function called is "len" and it is not locally defined; and
// because we don't have dot imports, it must be the predefined len()
if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Obj == s.Obj {
// 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
}
golang-mvdan-gofumpt-0.2.0/go.mod 0000664 0000000 0000000 00000000503 14143007650 0016646 0 ustar 00root root 0000000 0000000 module mvdan.cc/gofumpt
go 1.16
require (
github.com/frankban/quicktest v1.14.0
github.com/google/go-cmp v0.5.6
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4
golang.org/x/mod v0.5.1
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267
golang.org/x/tools v0.1.8-0.20211102182255-bb4add04ddef
)
golang-mvdan-gofumpt-0.2.0/go.sum 0000664 0000000 0000000 00000011544 14143007650 0016702 0 ustar 00root root 0000000 0000000 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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 h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w=
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.8-0.20211102182255-bb4add04ddef h1:/DaKawnTFFxdq/mJT3pM+OkeJlq5gc3ZhkbGVYbqOCw=
golang.org/x/tools v0.1.8-0.20211102182255-bb4add04ddef/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
golang-mvdan-gofumpt-0.2.0/gofmt.go 0000664 0000000 0000000 00000017273 14143007650 0017217 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"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/scanner"
"go/token"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
exec "golang.org/x/sys/execabs"
gformat "mvdan.cc/gofumpt/format"
"mvdan.cc/gofumpt/internal/diff"
"mvdan.cc/gofumpt/internal/version"
)
var (
// main operation modes
list = flag.Bool("l", false, "list files whose formatting differs from gofumpt's")
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
allErrors = flag.Bool("e", false, "report all errors (not just the first 10 on different lines)")
// debugging
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file")
// gofumpt's own flags
langVersion = flag.String("lang", "", "target Go version in the form 1.X (default from go.mod)")
extraRules = flag.Bool("extra", false, "enable extra rules which should be vetted by a human")
showVersion = flag.Bool("version", false, "show version and exit")
)
// 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#gofumpt.
//
// This value is defined in go/printer specifically for go/format and cmd/gofumpt.
printerNormalizeNumbers = 1 << 30
)
var (
fileSet = token.NewFileSet() // per process FileSet
exitCode = 0
parserMode parser.Mode
// walkingVendorDir is true if we are explicitly walking a vendor directory.
walkingVendorDir bool
)
func report(err error) {
scanner.PrintError(os.Stderr, err)
exitCode = 2
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: gofumpt [flags] [path ...]\n")
flag.PrintDefaults()
}
func initParserMode() {
parserMode = parser.ParseComments
if *allErrors {
parserMode |= parser.AllErrors
}
}
func isGoFile(f fs.DirEntry) bool {
// ignore non-Go files
name := f.Name()
return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
}
// If in == nil, the source is the contents of the file with the given filename.
func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error {
var perm os.FileMode = 0o644
if in == nil {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return err
}
in = f
perm = fi.Mode().Perm()
}
src, err := io.ReadAll(in)
if err != nil {
return err
}
file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, stdin)
if err != nil {
return err
}
ast.SortImports(fileSet, file)
// Apply gofumpt's changes before we print the code in gofumpt's format.
if *langVersion == "" {
out, err := exec.Command("go", "list", "-m", "-f", "{{.GoVersion}}").Output()
out = bytes.TrimSpace(out)
if err == nil && len(out) > 0 {
*langVersion = string(out)
}
}
gformat.File(fileSet, file, gformat.Options{
LangVersion: *langVersion,
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(out, filename)
}
if *write {
// make a temporary backup before overwriting original
bakname, err := backupFile(filename+".", src, perm)
if err != nil {
return err
}
err = os.WriteFile(filename, res, perm)
if err != nil {
os.Rename(bakname, filename)
return err
}
err = os.Remove(bakname)
if err != nil {
return err
}
}
if *doDiff {
data, err := diffWithReplaceTempFile(src, res, filename)
if err != nil {
return fmt.Errorf("computing diff: %s", err)
}
fmt.Fprintf(out, "diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename))
out.Write(data)
}
}
if !*list && !*write && !*doDiff {
_, err = out.Write(res)
}
return err
}
func visitFile(path string, f fs.DirEntry, err error) error {
if !walkingVendorDir && filepath.Base(path) == "vendor" {
return filepath.SkipDir
}
if err != nil || !isGoFile(f) {
return err
}
if err := processFile(path, nil, os.Stdout, false); err != nil {
report(err)
}
return nil
}
func main() {
// call gofumptMain in a separate function
// so that it can use defer and have them
// run before the exit.
gofumptMain()
os.Exit(exitCode)
}
func gofumptMain() {
flag.Usage = usage
flag.Parse()
// Print the gofumpt version if the user asks for it.
if *showVersion {
version.Print()
return
}
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
fmt.Fprintf(os.Stderr, "creating cpu profile: %s\n", err)
exitCode = 2
return
}
defer f.Close()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
initParserMode()
args := flag.Args()
if len(args) == 0 {
if *write {
fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input")
exitCode = 2
return
}
if err := processFile("", os.Stdin, os.Stdout, true); err != nil {
report(err)
}
return
}
for _, arg := range args {
switch info, err := os.Stat(arg); {
case err != nil:
report(err)
case !info.IsDir():
// Non-directory arguments are always formatted.
if err := processFile(arg, nil, os.Stdout, false); err != nil {
report(err)
}
default:
// Directories are walked, ignoring non-Go files.
walkingVendorDir = filepath.Base(arg) == "vendor"
if err := filepath.WalkDir(arg, visitFile); err != nil {
report(err)
}
}
}
}
func diffWithReplaceTempFile(b1, b2 []byte, filename string) ([]byte, error) {
data, err := diff.Diff("gofumpt", b1, b2)
if len(data) > 0 {
return replaceTempFilename(data, filename)
}
return data, err
}
// replaceTempFilename replaces temporary filenames in diff with actual one.
//
// --- /tmp/gofumpt316145376 2017-02-03 19:13:00.280468375 -0500
// +++ /tmp/gofumpt617882815 2017-02-03 19:13:00.280468375 -0500
// ...
// ->
// --- path/to/file.go.orig 2017-02-03 19:13:00.280468375 -0500
// +++ path/to/file.go 2017-02-03 19:13:00.280468375 -0500
// ...
func replaceTempFilename(diff []byte, filename string) ([]byte, error) {
bs := bytes.SplitN(diff, []byte{'\n'}, 3)
if len(bs) < 3 {
return nil, fmt.Errorf("got unexpected diff for %s", filename)
}
// Preserve timestamps.
var t0, t1 []byte
if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 {
t0 = bs[0][i:]
}
if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 {
t1 = bs[1][i:]
}
// Always print filepath with slash separator.
f := filepath.ToSlash(filename)
bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0))
bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1))
return bytes.Join(bs, []byte{'\n'}), nil
}
const chmodSupported = runtime.GOOS != "windows"
// backupFile writes data to a new file named filename with permissions perm,
// with