github.com/spotify/syslog-redirector-golang@v0.0.0-20140320174030-4859f03d829a/src/pkg/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 // Node formats node in canonical gofmt style and writes the result to dst. 22 // 23 // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl, 24 // []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, 25 // or ast.Stmt. Node does not modify node. Imports are not sorted for 26 // nodes representing partial source files (i.e., if the node is not an 27 // *ast.File or a *printer.CommentedNode not wrapping an *ast.File). 28 // 29 // The function may return early (before the entire result is written) 30 // and return a formatting error, for instance due to an incorrect AST. 31 // 32 func Node(dst io.Writer, fset *token.FileSet, node interface{}) error { 33 // Determine if we have a complete source file (file != nil). 34 var file *ast.File 35 var cnode *printer.CommentedNode 36 switch n := node.(type) { 37 case *ast.File: 38 file = n 39 case *printer.CommentedNode: 40 if f, ok := n.Node.(*ast.File); ok { 41 file = f 42 cnode = n 43 } 44 } 45 46 // Sort imports if necessary. 47 if file != nil && hasUnsortedImports(file) { 48 // Make a copy of the AST because ast.SortImports is destructive. 49 // TODO(gri) Do this more efficiently. 50 var buf bytes.Buffer 51 err := config.Fprint(&buf, fset, file) 52 if err != nil { 53 return err 54 } 55 file, err = parser.ParseFile(fset, "", buf.Bytes(), parser.ParseComments) 56 if err != nil { 57 // We should never get here. If we do, provide good diagnostic. 58 return fmt.Errorf("format.Node internal error (%s)", err) 59 } 60 ast.SortImports(fset, file) 61 62 // Use new file with sorted imports. 63 node = file 64 if cnode != nil { 65 node = &printer.CommentedNode{Node: file, Comments: cnode.Comments} 66 } 67 } 68 69 return config.Fprint(dst, fset, node) 70 } 71 72 // Source formats src in canonical gofmt style and returns the result 73 // or an (I/O or syntax) error. src is expected to be a syntactically 74 // correct Go source file, or a list of Go declarations or statements. 75 // 76 // If src is a partial source file, the leading and trailing space of src 77 // is applied to the result (such that it has the same leading and trailing 78 // space as src), and the result is indented by the same amount as the first 79 // line of src containing code. Imports are not sorted for partial source files. 80 // 81 func Source(src []byte) ([]byte, error) { 82 fset := token.NewFileSet() 83 node, err := parse(fset, src) 84 if err != nil { 85 return nil, err 86 } 87 88 var buf bytes.Buffer 89 if file, ok := node.(*ast.File); ok { 90 // Complete source file. 91 ast.SortImports(fset, file) 92 err := config.Fprint(&buf, fset, file) 93 if err != nil { 94 return nil, err 95 } 96 97 } else { 98 // Partial source file. 99 // Determine and prepend leading space. 100 i, j := 0, 0 101 for j < len(src) && isSpace(src[j]) { 102 if src[j] == '\n' { 103 i = j + 1 // index of last line in leading space 104 } 105 j++ 106 } 107 buf.Write(src[:i]) 108 109 // Determine indentation of first code line. 110 // Spaces are ignored unless there are no tabs, 111 // in which case spaces count as one tab. 112 indent := 0 113 hasSpace := false 114 for _, b := range src[i:j] { 115 switch b { 116 case ' ': 117 hasSpace = true 118 case '\t': 119 indent++ 120 } 121 } 122 if indent == 0 && hasSpace { 123 indent = 1 124 } 125 126 // Format the source. 127 cfg := config 128 cfg.Indent = indent 129 err := cfg.Fprint(&buf, fset, node) 130 if err != nil { 131 return nil, err 132 } 133 134 // Determine and append trailing space. 135 i = len(src) 136 for i > 0 && isSpace(src[i-1]) { 137 i-- 138 } 139 buf.Write(src[i:]) 140 } 141 142 return buf.Bytes(), nil 143 } 144 145 func hasUnsortedImports(file *ast.File) bool { 146 for _, d := range file.Decls { 147 d, ok := d.(*ast.GenDecl) 148 if !ok || d.Tok != token.IMPORT { 149 // Not an import declaration, so we're done. 150 // Imports are always first. 151 return false 152 } 153 if d.Lparen.IsValid() { 154 // For now assume all grouped imports are unsorted. 155 // TODO(gri) Should check if they are sorted already. 156 return true 157 } 158 // Ungrouped imports are sorted by default. 159 } 160 return false 161 } 162 163 func isSpace(b byte) bool { 164 return b == ' ' || b == '\t' || b == '\n' || b == '\r' 165 } 166 167 func parse(fset *token.FileSet, src []byte) (interface{}, error) { 168 // Try as a complete source file. 169 file, err := parser.ParseFile(fset, "", src, parser.ParseComments) 170 if err == nil { 171 return file, nil 172 } 173 // If the source is missing a package clause, try as a source fragment; otherwise fail. 174 if !strings.Contains(err.Error(), "expected 'package'") { 175 return nil, err 176 } 177 178 // Try as a declaration list by prepending a package clause in front of src. 179 // Use ';' not '\n' to keep line numbers intact. 180 psrc := append([]byte("package p;"), src...) 181 file, err = parser.ParseFile(fset, "", psrc, parser.ParseComments) 182 if err == nil { 183 return file.Decls, nil 184 } 185 // If the source is missing a declaration, try as a statement list; otherwise fail. 186 if !strings.Contains(err.Error(), "expected declaration") { 187 return nil, err 188 } 189 190 // Try as statement list by wrapping a function around src. 191 fsrc := append(append([]byte("package p; func _() {"), src...), '}') 192 file, err = parser.ParseFile(fset, "", fsrc, parser.ParseComments) 193 if err == nil { 194 return file.Decls[0].(*ast.FuncDecl).Body.List, nil 195 } 196 197 // Failed, and out of options. 198 return nil, err 199 }