LEFT | RIGHT |
(no file at all) | |
| 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, Tabwidt
h: 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 efficently. |
| 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.Parse
Comments) |
| 56 if err != nil { |
| 57 // We should never get here. If we do, provide good diag
nostic. |
| 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: cnod
e.Comments} |
| 66 } |
| 67 } |
| 68 |
| 69 return config.Fprint(dst, fset, node) |
| 70 } |
| 71 |
| 72 // Source formats src in canonical gofmt style and writes the result to dst |
| 73 // or returns 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 formatted src is indented by the same amount as |
| 79 // the first line of src containing code. Imports are not sorted for partial |
| 80 // source files. |
| 81 // |
| 82 func Source(src []byte) ([]byte, error) { |
| 83 fset := token.NewFileSet() |
| 84 node, err := parse(fset, src) |
| 85 if err != nil { |
| 86 return nil, err |
| 87 } |
| 88 |
| 89 var buf bytes.Buffer |
| 90 if file, ok := node.(*ast.File); ok { |
| 91 // Complete source file. |
| 92 ast.SortImports(fset, file) |
| 93 err := config.Fprint(&buf, fset, file) |
| 94 if err != nil { |
| 95 return nil, err |
| 96 } |
| 97 |
| 98 } else { |
| 99 // Partial source file. |
| 100 // Determine and prepend leading space. |
| 101 i, j := 0, 0 |
| 102 for j < len(src) && isSpace(src[j]) { |
| 103 if src[j] == '\n' { |
| 104 i = j + 1 // index of last line in leading space |
| 105 } |
| 106 j++ |
| 107 } |
| 108 buf.Write(src[:i]) |
| 109 |
| 110 // Determine indentation of first code line. |
| 111 // Spaces are ignored unless there are no tabs, |
| 112 // in which case spaces count as one tab. |
| 113 indent := 0 |
| 114 hasSpace := false |
| 115 for _, b := range src[i:j] { |
| 116 switch b { |
| 117 case ' ': |
| 118 hasSpace = true |
| 119 case '\t': |
| 120 indent++ |
| 121 } |
| 122 } |
| 123 if indent == 0 && hasSpace { |
| 124 indent = 1 |
| 125 } |
| 126 |
| 127 // Format the source. |
| 128 cfg := config |
| 129 cfg.Indent = indent |
| 130 err := cfg.Fprint(&buf, fset, node) |
| 131 if err != nil { |
| 132 return nil, err |
| 133 } |
| 134 |
| 135 // Determine and append trailing space. |
| 136 i = len(src) |
| 137 for i > 0 && isSpace(src[i-1]) { |
| 138 i-- |
| 139 } |
| 140 buf.Write(src[i:]) |
| 141 } |
| 142 |
| 143 return buf.Bytes(), nil |
| 144 } |
| 145 |
| 146 func hasUnsortedImports(file *ast.File) bool { |
| 147 for _, d := range file.Decls { |
| 148 d, ok := d.(*ast.GenDecl) |
| 149 if !ok || d.Tok != token.IMPORT { |
| 150 // Not an import declaration, so we're done. |
| 151 // Imports are always first. |
| 152 return false |
| 153 } |
| 154 if d.Lparen.IsValid() { |
| 155 // For now assume all grouped imports are unsorted. |
| 156 // TODO(gri) Should check if they are sorted already. |
| 157 return true |
| 158 } |
| 159 // Ungrouped imports are sorted by default. |
| 160 } |
| 161 return false |
| 162 } |
| 163 |
| 164 func isSpace(b byte) bool { |
| 165 return b == ' ' || b == '\t' || b == '\n' || b == '\r' |
| 166 } |
| 167 |
| 168 func parse(fset *token.FileSet, src []byte) (interface{}, error) { |
| 169 // Try as a complete source file. |
| 170 file, err := parser.ParseFile(fset, "", src, parser.ParseComments) |
| 171 if err == nil { |
| 172 return file, nil |
| 173 } |
| 174 // If the source is missing a package clause, try as a source fragment;
otherwise fail. |
| 175 if !strings.Contains(err.Error(), "expected 'package'") { |
| 176 return nil, err |
| 177 } |
| 178 |
| 179 // Try as a declaration list by prepending a package clause in front of
src. |
| 180 // Use ';' not '\n' to keep line numbers intact. |
| 181 psrc := append([]byte("package p;"), src...) |
| 182 file, err = parser.ParseFile(fset, "", psrc, parser.ParseComments) |
| 183 if err == nil { |
| 184 return file.Decls, nil |
| 185 } |
| 186 // If the source is missing a declaration, try as a statement list; othe
rwise fail. |
| 187 if !strings.Contains(err.Error(), "expected declaration") { |
| 188 return nil, err |
| 189 } |
| 190 |
| 191 // Try as statement list by wrapping a function around src. |
| 192 fsrc := append(append([]byte("package p; func _() {"), src...), '}') |
| 193 file, err = parser.ParseFile(fset, "", fsrc, parser.ParseComments) |
| 194 if err == nil { |
| 195 return file.Decls[0].(*ast.FuncDecl).Body.List, nil |
| 196 } |
| 197 |
| 198 // Failed, and out of options. |
| 199 return nil, err |
| 200 } |
LEFT | RIGHT |