github.com/ccccaoqing/test@v0.0.0-20220510085219-3985d23445c0/src/go/format/format.go (about) 1 // Copyright 2012 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 implements standard formatting of Go source. 6 package format 7 8 import ( 9 "bytes" 10 "fmt" 11 "go/ast" 12 "go/parser" 13 "go/printer" 14 "go/token" 15 "io" 16 "strings" 17 ) 18 19 var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8} 20 21 const parserMode = parser.ParseComments 22 23 // Node formats node in canonical gofmt style and writes the result to dst. 24 // 25 // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl, 26 // []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, 27 // or ast.Stmt. Node does not modify node. Imports are not sorted for 28 // nodes representing partial source files (i.e., if the node is not an 29 // *ast.File or a *printer.CommentedNode not wrapping an *ast.File). 30 // 31 // The function may return early (before the entire result is written) 32 // and return a formatting error, for instance due to an incorrect AST. 33 // 34 func Node(dst io.Writer, fset *token.FileSet, node interface{}) error { 35 // Determine if we have a complete source file (file != nil). 36 var file *ast.File 37 var cnode *printer.CommentedNode 38 switch n := node.(type) { 39 case *ast.File: 40 file = n 41 case *printer.CommentedNode: 42 if f, ok := n.Node.(*ast.File); ok { 43 file = f 44 cnode = n 45 } 46 } 47 48 // Sort imports if necessary. 49 if file != nil && hasUnsortedImports(file) { 50 // Make a copy of the AST because ast.SortImports is destructive. 51 // TODO(gri) Do this more efficiently. 52 var buf bytes.Buffer 53 err := config.Fprint(&buf, fset, file) 54 if err != nil { 55 return err 56 } 57 file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode) 58 if err != nil { 59 // We should never get here. If we do, provide good diagnostic. 60 return fmt.Errorf("format.Node internal error (%s)", err) 61 } 62 ast.SortImports(fset, file) 63 64 // Use new file with sorted imports. 65 node = file 66 if cnode != nil { 67 node = &printer.CommentedNode{Node: file, Comments: cnode.Comments} 68 } 69 } 70 71 return config.Fprint(dst, fset, node) 72 } 73 74 // Source formats src in canonical gofmt style and returns the result 75 // or an (I/O or syntax) error. src is expected to be a syntactically 76 // correct Go source file, or a list of Go declarations or statements. 77 // 78 // If src is a partial source file, the leading and trailing space of src 79 // is applied to the result (such that it has the same leading and trailing 80 // space as src), and the result is indented by the same amount as the first 81 // line of src containing code. Imports are not sorted for partial source files. 82 // 83 func Source(src []byte) ([]byte, error) { 84 fset := token.NewFileSet() 85 file, sourceAdj, indentAdj, err := parse(fset, "", src, true) 86 if err != nil { 87 return nil, err 88 } 89 90 if sourceAdj == nil { 91 // Complete source file. 92 // TODO(gri) consider doing this always. 93 ast.SortImports(fset, file) 94 } 95 96 return format(fset, file, sourceAdj, indentAdj, src, config) 97 } 98 99 func hasUnsortedImports(file *ast.File) bool { 100 for _, d := range file.Decls { 101 d, ok := d.(*ast.GenDecl) 102 if !ok || d.Tok != token.IMPORT { 103 // Not an import declaration, so we're done. 104 // Imports are always first. 105 return false 106 } 107 if d.Lparen.IsValid() { 108 // For now assume all grouped imports are unsorted. 109 // TODO(gri) Should check if they are sorted already. 110 return true 111 } 112 // Ungrouped imports are sorted by default. 113 } 114 return false 115 } 116 117 // ---------------------------------------------------------------------------- 118 // Support functions 119 // 120 // The functions parse, format, and isSpace below are identical to the 121 // respective functions in cmd/gofmt/gofmt.go - keep them in sync! 122 // 123 // TODO(gri) Factor out this functionality, eventually. 124 125 // parse parses src, which was read from the named file, 126 // as a Go source file, declaration, or statement list. 127 func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( 128 file *ast.File, 129 sourceAdj func(src []byte, indent int) []byte, 130 indentAdj int, 131 err error, 132 ) { 133 // Try as whole source file. 134 file, err = parser.ParseFile(fset, filename, src, parserMode) 135 // If there's no error, return. If the error is that the source file didn't begin with a 136 // package line and source fragments are ok, fall through to 137 // try as a source fragment. Stop and return on any other error. 138 if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") { 139 return 140 } 141 142 // If this is a declaration list, make it a source file 143 // by inserting a package clause. 144 // Insert using a ;, not a newline, so that the line numbers 145 // in psrc match the ones in src. 146 psrc := append([]byte("package p;"), src...) 147 file, err = parser.ParseFile(fset, filename, psrc, parserMode) 148 if err == nil { 149 sourceAdj = func(src []byte, indent int) []byte { 150 // Remove the package clause. 151 // Gofmt has turned the ; into a \n. 152 src = src[indent+len("package p\n"):] 153 return bytes.TrimSpace(src) 154 } 155 return 156 } 157 // If the error is that the source file didn't begin with a 158 // declaration, fall through to try as a statement list. 159 // Stop and return on any other error. 160 if !strings.Contains(err.Error(), "expected declaration") { 161 return 162 } 163 164 // If this is a statement list, make it a source file 165 // by inserting a package clause and turning the list 166 // into a function body. This handles expressions too. 167 // Insert using a ;, not a newline, so that the line numbers 168 // in fsrc match the ones in src. 169 fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}') 170 file, err = parser.ParseFile(fset, filename, fsrc, parserMode) 171 if err == nil { 172 sourceAdj = func(src []byte, indent int) []byte { 173 // Cap adjusted indent to zero. 174 if indent < 0 { 175 indent = 0 176 } 177 // Remove the wrapping. 178 // Gofmt has turned the ; into a \n\n. 179 // There will be two non-blank lines with indent, hence 2*indent. 180 src = src[2*indent+len("package p\n\nfunc _() {"):] 181 src = src[:len(src)-(indent+len("\n}\n"))] 182 return bytes.TrimSpace(src) 183 } 184 // Gofmt has also indented the function body one level. 185 // Adjust that with indentAdj. 186 indentAdj = -1 187 } 188 189 // Succeeded, or out of options. 190 return 191 } 192 193 // format formats the given package file originally obtained from src 194 // and adjusts the result based on the original source via sourceAdj 195 // and indentAdj. 196 func format( 197 fset *token.FileSet, 198 file *ast.File, 199 sourceAdj func(src []byte, indent int) []byte, 200 indentAdj int, 201 src []byte, 202 cfg printer.Config, 203 ) ([]byte, error) { 204 if sourceAdj == nil { 205 // Complete source file. 206 var buf bytes.Buffer 207 err := cfg.Fprint(&buf, fset, file) 208 if err != nil { 209 return nil, err 210 } 211 return buf.Bytes(), nil 212 } 213 214 // Partial source file. 215 // Determine and prepend leading space. 216 i, j := 0, 0 217 for j < len(src) && isSpace(src[j]) { 218 if src[j] == '\n' { 219 i = j + 1 // byte offset of last line in leading space 220 } 221 j++ 222 } 223 var res []byte 224 res = append(res, src[:i]...) 225 226 // Determine and prepend indentation of first code line. 227 // Spaces are ignored unless there are no tabs, 228 // in which case spaces count as one tab. 229 indent := 0 230 hasSpace := false 231 for _, b := range src[i:j] { 232 switch b { 233 case ' ': 234 hasSpace = true 235 case '\t': 236 indent++ 237 } 238 } 239 if indent == 0 && hasSpace { 240 indent = 1 241 } 242 for i := 0; i < indent; i++ { 243 res = append(res, '\t') 244 } 245 246 // Format the source. 247 // Write it without any leading and trailing space. 248 cfg.Indent = indent + indentAdj 249 var buf bytes.Buffer 250 err := cfg.Fprint(&buf, fset, file) 251 if err != nil { 252 return nil, err 253 } 254 res = append(res, sourceAdj(buf.Bytes(), cfg.Indent)...) 255 256 // Determine and append trailing space. 257 i = len(src) 258 for i > 0 && isSpace(src[i-1]) { 259 i-- 260 } 261 return append(res, src[i:]...), nil 262 } 263 264 func isSpace(b byte) bool { 265 return b == ' ' || b == '\t' || b == '\n' || b == '\r' 266 }