|  | // Copyright 2015 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. | 
|  |  | 
|  | // TODO(gri): This file and the file src/go/format/internal.go are | 
|  | // the same (but for this comment and the package name). Do not modify | 
|  | // one without the other. Determine if we can factor out functionality | 
|  | // in a public API. See also #11844 for context. | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "go/ast" | 
|  | "go/parser" | 
|  | "go/printer" | 
|  | "go/token" | 
|  | "strings" | 
|  | ) | 
|  |  | 
|  | // parse parses src, which was read from the named file, | 
|  | // as a Go source file, declaration, 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 | 
|  | } | 
|  |  | 
|  | // 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 { | 
|  | 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 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 | 
|  | } | 
|  |  | 
|  | // 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. Add an extra '\n' before the '}' | 
|  | // to make sure comments are flushed before the '}'. | 
|  | fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '\n', '}') | 
|  | file, err = parser.ParseFile(fset, filename, fsrc, parserMode) | 
|  | if err == 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 _() {"):] | 
|  | // Remove only the "}\n" suffix: remaining whitespaces will be trimmed anyway | 
|  | src = src[:len(src)-len("}\n")] | 
|  | return bytes.TrimSpace(src) | 
|  | } | 
|  | // Gofmt has also indented the function body one level. | 
|  | // Adjust that with indentAdj. | 
|  | indentAdj = -1 | 
|  | } | 
|  |  | 
|  | // Succeeded, or out of options. | 
|  | return | 
|  | } | 
|  |  | 
|  | // format formats the given package file originally obtained from src | 
|  | // and adjusts the result based on the original source via sourceAdj | 
|  | // and indentAdj. | 
|  | func format( | 
|  | fset *token.FileSet, | 
|  | file *ast.File, | 
|  | sourceAdj func(src []byte, indent int) []byte, | 
|  | indentAdj int, | 
|  | src []byte, | 
|  | cfg printer.Config, | 
|  | ) ([]byte, error) { | 
|  | if sourceAdj == nil { | 
|  | // Complete source file. | 
|  | var buf bytes.Buffer | 
|  | err := cfg.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.Indent = indent + indentAdj | 
|  | var buf bytes.Buffer | 
|  | err := cfg.Fprint(&buf, fset, file) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | out := sourceAdj(buf.Bytes(), cfg.Indent) | 
|  |  | 
|  | // If the adjusted output is empty, the source | 
|  | // was empty but (possibly) for white space. | 
|  | // The result is the incoming source. | 
|  | if len(out) == 0 { | 
|  | return src, nil | 
|  | } | 
|  |  | 
|  | // Otherwise, append output to leading space. | 
|  | res = append(res, out...) | 
|  |  | 
|  | // Determine and append trailing space. | 
|  | i = len(src) | 
|  | for i > 0 && isSpace(src[i-1]) { | 
|  | i-- | 
|  | } | 
|  | return append(res, src[i:]...), nil | 
|  | } | 
|  |  | 
|  | // isSpace reports whether the byte is a space character. | 
|  | // isSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'. | 
|  | func isSpace(b byte) bool { | 
|  | return b == ' ' || b == '\t' || b == '\n' || b == '\r' | 
|  | } |