1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package format 6 7 import ( 8 "bytes" 9 "go/ast" 10 "go/parser" 11 "go/printer" 12 "go/token" 13 "strings" 14 ) 15 16 const parserMode = parser.ParseComments 17 18 // Parse parses src, which was read from the named file, 19 // as a Go source file, declaration, or statement list. 20 func Parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( 21 file *ast.File, 22 sourceAdj func(src []byte, indent int) []byte, 23 indentAdj int, 24 err error, 25 ) { 26 // Try as whole source file. 27 file, err = parser.ParseFile(fset, filename, src, parserMode) 28 // If there's no error, return. If the error is that the source file didn't begin with a 29 // package line and source fragments are ok, fall through to 30 // try as a source fragment. Stop and return on any other error. 31 if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") { 32 return 33 } 34 35 // If this is a declaration list, make it a source file 36 // by inserting a package clause. 37 // Insert using a ;, not a newline, so that the line numbers 38 // in psrc match the ones in src. 39 psrc := append([]byte("package p;"), src...) 40 file, err = parser.ParseFile(fset, filename, psrc, parserMode) 41 if err == nil { 42 sourceAdj = func(src []byte, indent int) []byte { 43 // Remove the package clause. 44 // Gofmt has turned the ; into a \n. 45 src = src[indent+len("package p\n"):] 46 return bytes.TrimSpace(src) 47 } 48 return 49 } 50 // If the error is that the source file didn't begin with a 51 // declaration, fall through to try as a statement list. 52 // Stop and return on any other error. 53 if !strings.Contains(err.Error(), "expected declaration") { 54 return 55 } 56 57 // If this is a statement list, make it a source file 58 // by inserting a package clause and turning the list 59 // into a function body. This handles expressions too. 60 // Insert using a ;, not a newline, so that the line numbers 61 // in fsrc match the ones in src. Add an extra '\n' before the '}' 62 // to make sure comments are flushed before the '}'. 63 fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '\n', '}') 64 file, err = parser.ParseFile(fset, filename, fsrc, parserMode) 65 if err == nil { 66 sourceAdj = func(src []byte, indent int) []byte { 67 // Cap adjusted indent to zero. 68 if indent < 0 { 69 indent = 0 70 } 71 // Remove the wrapping. 72 // Gofmt has turned the ; into a \n\n. 73 // There will be two non-blank lines with indent, hence 2*indent. 74 src = src[2*indent+len("package p\n\nfunc _() {"):] 75 // Remove only the "}\n" suffix: remaining whitespaces will be trimmed anyway 76 src = src[:len(src)-len("}\n")] 77 return bytes.TrimSpace(src) 78 } 79 // Gofmt has also indented the function body one level. 80 // Adjust that with indentAdj. 81 indentAdj = -1 82 } 83 84 // Succeeded, or out of options. 85 return 86 } 87 88 // Format formats the given package file originally obtained from src 89 // and adjusts the result based on the original source via sourceAdj 90 // and indentAdj. 91 func Format( 92 fset *token.FileSet, 93 file *ast.File, 94 sourceAdj func(src []byte, indent int) []byte, 95 indentAdj int, 96 src []byte, 97 cfg printer.Config, 98 ) ([]byte, error) { 99 if sourceAdj == nil { 100 // Complete source file. 101 var buf bytes.Buffer 102 err := cfg.Fprint(&buf, fset, file) 103 if err != nil { 104 return nil, err 105 } 106 return buf.Bytes(), nil 107 } 108 109 // Partial source file. 110 // Determine and prepend leading space. 111 i, j := 0, 0 112 for j < len(src) && IsSpace(src[j]) { 113 if src[j] == '\n' { 114 i = j + 1 // byte offset of last line in leading space 115 } 116 j++ 117 } 118 var res []byte 119 res = append(res, src[:i]...) 120 121 // Determine and prepend indentation of first code line. 122 // Spaces are ignored unless there are no tabs, 123 // in which case spaces count as one tab. 124 indent := 0 125 hasSpace := false 126 for _, b := range src[i:j] { 127 switch b { 128 case ' ': 129 hasSpace = true 130 case '\t': 131 indent++ 132 } 133 } 134 if indent == 0 && hasSpace { 135 indent = 1 136 } 137 for i := 0; i < indent; i++ { 138 res = append(res, '\t') 139 } 140 141 // Format the source. 142 // Write it without any leading and trailing space. 143 cfg.Indent = indent + indentAdj 144 var buf bytes.Buffer 145 err := cfg.Fprint(&buf, fset, file) 146 if err != nil { 147 return nil, err 148 } 149 res = append(res, sourceAdj(buf.Bytes(), cfg.Indent)...) 150 151 // Determine and append trailing space. 152 i = len(src) 153 for i > 0 && IsSpace(src[i-1]) { 154 i-- 155 } 156 return append(res, src[i:]...), nil 157 } 158 159 // IsSpace reports whether the byte is a space character. 160 // IsSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'. 161 func IsSpace(b byte) bool { 162 return b == ' ' || b == '\t' || b == '\n' || b == '\r' 163 } 164