summaryrefslogtreecommitdiff
path: root/src/go
diff options
context:
space:
mode:
Diffstat (limited to 'src/go')
-rw-r--r--src/go/format/format.go207
-rw-r--r--src/go/format/format_test.go6
2 files changed, 131 insertions, 82 deletions
diff --git a/src/go/format/format.go b/src/go/format/format.go
index 3d00a645d..08a9047b9 100644
--- a/src/go/format/format.go
+++ b/src/go/format/format.go
@@ -18,6 +18,8 @@ import (
var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
+const parserMode = parser.ParseComments
+
// Node formats node in canonical gofmt style and writes the result to dst.
//
// The node type must be *ast.File, *printer.CommentedNode, []ast.Decl,
@@ -52,7 +54,7 @@ func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
if err != nil {
return err
}
- file, err = parser.ParseFile(fset, "", buf.Bytes(), parser.ParseComments)
+ file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode)
if err != nil {
// We should never get here. If we do, provide good diagnostic.
return fmt.Errorf("format.Node internal error (%s)", err)
@@ -80,66 +82,12 @@ func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
//
func Source(src []byte) ([]byte, error) {
fset := token.NewFileSet()
- node, err := parse(fset, src)
+ file, sourceAdj, indentAdj, err := parse(fset, "", src, true)
if err != nil {
return nil, err
}
- var buf bytes.Buffer
- if file, ok := node.(*ast.File); ok {
- // Complete source file.
- ast.SortImports(fset, file)
- err := config.Fprint(&buf, fset, file)
- if err != nil {
- return nil, err
- }
-
- } else {
- // Partial source file.
- // Determine and prepend leading space.
- i, j := 0, 0
- for j < len(src) && isSpace(src[j]) {
- if src[j] == '\n' {
- i = j + 1 // index of last line in leading space
- }
- j++
- }
- buf.Write(src[:i])
-
- // Determine indentation of first code line.
- // Spaces are ignored unless there are no tabs,
- // in which case spaces count as one tab.
- indent := 0
- hasSpace := false
- for _, b := range src[i:j] {
- switch b {
- case ' ':
- hasSpace = true
- case '\t':
- indent++
- }
- }
- if indent == 0 && hasSpace {
- indent = 1
- }
-
- // Format the source.
- cfg := config
- cfg.Indent = indent
- err := cfg.Fprint(&buf, fset, node)
- if err != nil {
- return nil, err
- }
-
- // Determine and append trailing space.
- i = len(src)
- for i > 0 && isSpace(src[i-1]) {
- i--
- }
- buf.Write(src[i:])
- }
-
- return buf.Bytes(), nil
+ return format(fset, file, sourceAdj, indentAdj, src)
}
func hasUnsortedImports(file *ast.File) bool {
@@ -160,40 +108,137 @@ func hasUnsortedImports(file *ast.File) bool {
return false
}
-func isSpace(b byte) bool {
- return b == ' ' || b == '\t' || b == '\n' || b == '\r'
-}
+// parse parses src, which was read from filename,
+// as a Go source file or statement list.
+func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) (
+ file *ast.File,
+ sourceAdj func(src []byte, indent int) []byte,
+ indentAdj int,
+ err error,
+) {
+ // Try as whole source file.
+ file, err = parser.ParseFile(fset, filename, src, parserMode)
+ // If there's no error, return. If the error is that the source file didn't begin with a
+ // package line and source fragments are ok, fall through to
+ // try as a source fragment. Stop and return on any other error.
+ if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") {
+ return
+ }
-func parse(fset *token.FileSet, src []byte) (interface{}, error) {
- // Try as a complete source file.
- file, err := parser.ParseFile(fset, "", src, parser.ParseComments)
+ // If this is a declaration list, make it a source file
+ // by inserting a package clause.
+ // Insert using a ;, not a newline, so that the line numbers
+ // in psrc match the ones in src.
+ psrc := append([]byte("package p;"), src...)
+ file, err = parser.ParseFile(fset, filename, psrc, parserMode)
if err == nil {
- return file, nil
+ sourceAdj = func(src []byte, indent int) []byte {
+ // Remove the package clause.
+ // Gofmt has turned the ; into a \n.
+ src = src[indent+len("package p\n"):]
+ return bytes.TrimSpace(src)
+ }
+ return
}
- // If the source is missing a package clause, try as a source fragment; otherwise fail.
- if !strings.Contains(err.Error(), "expected 'package'") {
- return nil, err
+ // If the error is that the source file didn't begin with a
+ // declaration, fall through to try as a statement list.
+ // Stop and return on any other error.
+ if !strings.Contains(err.Error(), "expected declaration") {
+ return
}
- // Try as a declaration list by prepending a package clause in front of src.
- // Use ';' not '\n' to keep line numbers intact.
- psrc := append([]byte("package p;"), src...)
- file, err = parser.ParseFile(fset, "", psrc, parser.ParseComments)
+ // If this is a statement list, make it a source file
+ // by inserting a package clause and turning the list
+ // into a function body. This handles expressions too.
+ // Insert using a ;, not a newline, so that the line numbers
+ // in fsrc match the ones in src.
+ fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}')
+ file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
if err == nil {
- return file.Decls, nil
+ sourceAdj = func(src []byte, indent int) []byte {
+ // Cap adjusted indent to zero.
+ if indent < 0 {
+ indent = 0
+ }
+ // Remove the wrapping.
+ // Gofmt has turned the ; into a \n\n.
+ // There will be two non-blank lines with indent, hence 2*indent.
+ src = src[2*indent+len("package p\n\nfunc _() {"):]
+ src = src[:len(src)-(indent+len("\n}\n"))]
+ return bytes.TrimSpace(src)
+ }
+ // Gofmt has also indented the function body one level.
+ // Adjust that with indentAdj.
+ indentAdj = -1
}
- // If the source is missing a declaration, try as a statement list; otherwise fail.
- if !strings.Contains(err.Error(), "expected declaration") {
+
+ // Succeeded, or out of options.
+ return
+}
+
+func format(fset *token.FileSet, file *ast.File, sourceAdj func(src []byte, indent int) []byte, indentAdj int, src []byte) ([]byte, error) {
+ if sourceAdj == nil {
+ // Complete source file.
+ ast.SortImports(fset, file)
+ var buf bytes.Buffer
+ err := config.Fprint(&buf, fset, file)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+ }
+
+ // Partial source file.
+ // Determine and prepend leading space.
+ i, j := 0, 0
+ for j < len(src) && isSpace(src[j]) {
+ if src[j] == '\n' {
+ i = j + 1 // byte offset of last line in leading space
+ }
+ j++
+ }
+ var res []byte
+ res = append(res, src[:i]...)
+
+ // Determine and prepend indentation of first code line.
+ // Spaces are ignored unless there are no tabs,
+ // in which case spaces count as one tab.
+ indent := 0
+ hasSpace := false
+ for _, b := range src[i:j] {
+ switch b {
+ case ' ':
+ hasSpace = true
+ case '\t':
+ indent++
+ }
+ }
+ if indent == 0 && hasSpace {
+ indent = 1
+ }
+ for i := 0; i < indent; i++ {
+ res = append(res, '\t')
+ }
+
+ // Format the source.
+ // Write it without any leading and trailing space.
+ cfg := config
+ cfg.Indent = indent + indentAdj
+ var buf bytes.Buffer
+ err := cfg.Fprint(&buf, fset, file)
+ if err != nil {
return nil, err
}
+ res = append(res, sourceAdj(buf.Bytes(), cfg.Indent)...)
- // Try as statement list by wrapping a function around src.
- fsrc := append(append([]byte("package p; func _() {"), src...), '}')
- file, err = parser.ParseFile(fset, "", fsrc, parser.ParseComments)
- if err == nil {
- return file.Decls[0].(*ast.FuncDecl).Body.List, nil
+ // Determine and append trailing space.
+ i = len(src)
+ for i > 0 && isSpace(src[i-1]) {
+ i--
}
+ return append(res, src[i:]...), nil
+}
- // Failed, and out of options.
- return nil, err
+func isSpace(b byte) bool {
+ return b == ' ' || b == '\t' || b == '\n' || b == '\r'
}
diff --git a/src/go/format/format_test.go b/src/go/format/format_test.go
index 93f099247..d7846bec6 100644
--- a/src/go/format/format_test.go
+++ b/src/go/format/format_test.go
@@ -87,7 +87,11 @@ var tests = []string{
"\tx := 0\n\tgo f()\n\n\n",
"\n\t\t\n\n\tx := 0\n\tgo f()\n\n\n",
"\n\t\t\n\n\t\t\tx := 0\n\t\t\tgo f()\n\n\n",
- "\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\nfoo\n`\n\n\n", // no indentation inside raw strings
+ "\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\nfoo\n`\n\n\n", // no indentation added inside raw strings
+ "\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\n\t\tfoo\n`\n\n\n", // no indentation removed inside raw strings
+
+ // comments
+ "i := 5 /* Comment */", // Issue 5551.
// erroneous programs
"ERROR1 + 2 +",