Repository: mvdan/gofumpt
Pull request #192 (2022-02-15 16:18:52)

Remove empty lines surrounding the lone comments in struct
Remove empty lines surrounding comments in struct/interfaces. In short, converts
```
type S struct {

    // comment

}
```
to
```
type S struct {
    // comment
}
```




Patch:
diff --git a/README.md b/README.md
index 80dc151..5b83645 100644
--- a/README.md
+++ b/README.md
@@ -11,14 +11,14 @@ 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.
+The [added formatting rules](#Added-rules) are implemented in the `format` package.
 
-Beyond the [added rules below](#Added-rules), the tool differs from gofmt in the
-following ways:
+Note that vendor 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.
 
-* 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`
+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
 
diff --git a/format/format.go b/format/format.go
index 1c87bea..25666db 100644
--- a/format/format.go
+++ b/format/format.go
@@ -69,25 +69,12 @@ func Source(src []byte, opts Options) ([]byte, error) {
 	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' {
@@ -281,15 +268,6 @@ func (f *fumpter) printLength(node ast.Node) int {
 	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")
@@ -584,13 +562,34 @@ func (f *fumpter) applyPre(c *astutil.Cursor) {
 		f.stmts(node.Body)
 
 	case *ast.FieldList:
-		if node.NumFields() == 0 && len(f.commentsBetween(node.Pos(), node.End())) == 0 {
+		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.
diff --git a/gofmt.go b/gofmt.go
index d6677e3..10ad6cb 100644
--- a/gofmt.go
+++ b/gofmt.go
@@ -18,6 +18,7 @@ import (
 	"io/fs"
 	"os"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"runtime/pprof"
 	"strings"
@@ -103,8 +104,24 @@ func isGoFile(f fs.DirEntry) bool {
 	return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
 }
 
+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
+}
+
 // 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 {
+func processFile(filename string, in io.Reader, out io.Writer, stdin, explicit bool) error {
 	var perm os.FileMode = 0o644
 	if in == nil {
 		f, err := os.Open(filename)
@@ -143,10 +160,12 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error
 		}
 	}
 
-	gformat.File(fileSet, file, gformat.Options{
-		LangVersion: *langVersion,
-		ExtraRules:  *extraRules,
-	})
+	if explicit || !isGenerated(file) {
+		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 {
@@ -198,7 +217,7 @@ func visitFile(path string, f fs.DirEntry, err error) error {
 	if err != nil || !isGoFile(f) {
 		return err
 	}
-	if err := processFile(path, nil, os.Stdout, false); err != nil {
+	if err := processFile(path, nil, os.Stdout, false, false); err != nil {
 		report(err)
 	}
 	return nil
@@ -255,7 +274,7 @@ func gofumptMain() {
 			exitCode = 2
 			return
 		}
-		if err := processFile("<standard input>", os.Stdin, os.Stdout, true); err != nil {
+		if err := processFile("<standard input>", os.Stdin, os.Stdout, true, true); err != nil {
 			report(err)
 		}
 		return
@@ -267,7 +286,7 @@ func gofumptMain() {
 			report(err)
 		case !info.IsDir():
 			// Non-directory arguments are always formatted.
-			if err := processFile(arg, nil, os.Stdout, false); err != nil {
+			if err := processFile(arg, nil, os.Stdout, false, true); err != nil {
 				report(err)
 			}
 		default:


Test patch:
diff --git a/testdata/scripts/block-empty.txt b/testdata/scripts/block-empty.txt
index 02a3a38..bd205fa 100644
--- a/testdata/scripts/block-empty.txt
+++ b/testdata/scripts/block-empty.txt
@@ -20,7 +20,42 @@ func f() {
 		// lone comment
 
 	}
+
+	type S struct {
+
+
+		// lone comment
+		
+		
+	}
+
+	type I interface {
+
+
+		// lone comment
+		
+		
+	}
+
+
+}
+
+type SOut struct {
+
+	// lone comment
+	
 }
+
+type IOut interface {
+
+	
+	// lone comment
+
+
+}
+
+
+
 -- foo.go.golden --
 package p
 
@@ -34,4 +69,20 @@ func f() {
 	{
 		// lone comment
 	}
+
+	type S struct {
+		// lone comment
+	}
+
+	type I interface {
+		// lone comment
+	}
+}
+
+type SOut struct {
+	// lone comment
+}
+
+type IOut interface {
+	// lone comment
 }
diff --git a/testdata/scripts/generated.txt b/testdata/scripts/generated.txt
index 1b40ad9..b9b962c 100644
--- a/testdata/scripts/generated.txt
+++ b/testdata/scripts/generated.txt
@@ -1,27 +1,29 @@
-cp foo.go foo.go.orig
-cp foo.go foo.go.golden
-exec gofmt -w foo.go.golden
+gofumpt foo.go
+cmp stdout foo.go.golden
 
-gofumpt -w foo.go
-cmp foo.go foo.go.golden
-
-cp foo.go.orig foo.go
-gofumpt -w .
-cmp foo.go foo.go.golden
+gofumpt -l .
+! stdout .
+! stderr .
 
 -- foo.go --
 // foo is a package about bar.
 
 // Code generated by foo. DO NOT EDIT.
-// versions:
-// 	foo v1.26.0
+
 package foo
 
-type i interface {
+func f() {
+
+	println("body")
+
+}
+-- foo.go.golden --
+// foo is a package about bar.
 
-	a(x int) int
+// Code generated by foo. DO NOT EDIT.
 
-	b(x int) int
+package foo
 
-	c(x int) int
+func f() {
+	println("body")
 }