LEFT | RIGHT |
1 // Copyright 2012 The Go Authors. All rights reserved. | 1 // Copyright 2012 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 // Package format implements standard formatting of Go source. | 5 // Package format implements standard formatting of Go source. |
6 package format | 6 package format |
7 | 7 |
8 import ( | 8 import ( |
9 "bytes" | 9 "bytes" |
10 "fmt" | 10 "fmt" |
11 "go/ast" | 11 "go/ast" |
12 "go/parser" | 12 "go/parser" |
13 "go/printer" | 13 "go/printer" |
14 "go/token" | 14 "go/token" |
15 "io" | 15 "io" |
16 "strings" | 16 "strings" |
17 ) | 17 ) |
18 | 18 |
19 var ( | 19 var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidt
h: 8} |
20 » config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent,
Tabwidth: 8} | 20 |
21 » parserMode = parser.ParseComments | 21 const parserMode = parser.ParseComments |
22 ) | |
23 | 22 |
24 // Node formats node in canonical gofmt style and writes the result to dst. | 23 // Node formats node in canonical gofmt style and writes the result to dst. |
25 // | 24 // |
26 // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl, | 25 // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl, |
27 // []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, | 26 // []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, |
28 // or ast.Stmt. Node does not modify node. Imports are not sorted for | 27 // or ast.Stmt. Node does not modify node. Imports are not sorted for |
29 // nodes representing partial source files (i.e., if the node is not an | 28 // nodes representing partial source files (i.e., if the node is not an |
30 // *ast.File or a *printer.CommentedNode not wrapping an *ast.File). | 29 // *ast.File or a *printer.CommentedNode not wrapping an *ast.File). |
31 // | 30 // |
32 // The function may return early (before the entire result is written) | 31 // The function may return early (before the entire result is written) |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
76 // or an (I/O or syntax) error. src is expected to be a syntactically | 75 // or an (I/O or syntax) error. src is expected to be a syntactically |
77 // correct Go source file, or a list of Go declarations or statements. | 76 // correct Go source file, or a list of Go declarations or statements. |
78 // | 77 // |
79 // If src is a partial source file, the leading and trailing space of src | 78 // If src is a partial source file, the leading and trailing space of src |
80 // is applied to the result (such that it has the same leading and trailing | 79 // is applied to the result (such that it has the same leading and trailing |
81 // space as src), and the result is indented by the same amount as the first | 80 // space as src), and the result is indented by the same amount as the first |
82 // line of src containing code. Imports are not sorted for partial source files. | 81 // line of src containing code. Imports are not sorted for partial source files. |
83 // | 82 // |
84 func Source(src []byte) ([]byte, error) { | 83 func Source(src []byte) ([]byte, error) { |
85 fset := token.NewFileSet() | 84 fset := token.NewFileSet() |
86 » file, adjust, err := parse(fset, "", src, true) | 85 » file, sourceAdj, indentAdj, err := parse(fset, "", src, true) |
87 if err != nil { | 86 if err != nil { |
88 return nil, err | 87 return nil, err |
89 } | 88 } |
90 | 89 |
91 » var res []byte | 90 » return format(fset, file, sourceAdj, indentAdj, src) |
92 » if adjust == nil { | |
93 » » // Complete source file. | |
94 » » ast.SortImports(fset, file) | |
95 » » var buf bytes.Buffer | |
96 » » err := config.Fprint(&buf, fset, file) | |
97 » » if err != nil { | |
98 » » » return nil, err | |
99 » » } | |
100 | |
101 » » res = buf.Bytes() | |
102 | |
103 » } else { | |
104 » » // Partial source file. | |
105 » » // Determine and prepend leading space. | |
106 » » i, j := 0, 0 | |
107 » » for j < len(src) && isSpace(src[j]) { | |
108 » » » if src[j] == '\n' { | |
109 » » » » i = j + 1 // index of last line in leading space | |
110 » » » } | |
111 » » » j++ | |
112 » » } | |
113 » » res = append(res, src[:i]...) | |
114 | |
115 » » // Determine and prepend indentation of first code line. | |
116 » » // Spaces are ignored unless there are no tabs, | |
117 » » // in which case spaces count as one tab. | |
118 » » indent := 0 | |
119 » » hasSpace := false | |
120 » » for _, b := range src[i:j] { | |
121 » » » switch b { | |
122 » » » case ' ': | |
123 » » » » hasSpace = true | |
124 » » » case '\t': | |
125 » » » » indent++ | |
126 » » » } | |
127 » » } | |
128 » » if indent == 0 && hasSpace { | |
129 » » » indent = 1 | |
130 » » } | |
131 » » for i := 0; i < indent; i++ { | |
132 » » » res = append(res, '\t') | |
133 » » } | |
134 | |
135 » » // Format the source. | |
136 » » // Write it without any leading and trailing space. | |
137 » » cfg := config | |
138 » » cfg.Indent = indent | |
139 » » var buf bytes.Buffer | |
140 » » err := cfg.Fprint(&buf, fset, file) | |
141 » » if err != nil { | |
142 » » » return nil, err | |
143 » » } | |
144 » » res = append(res, adjust(buf.Bytes(), indent)...) | |
145 | |
146 » » // Determine and append trailing space. | |
147 » » i = len(src) | |
148 » » for i > 0 && isSpace(src[i-1]) { | |
149 » » » i-- | |
150 » » } | |
151 » » res = append(res, src[i:]...) | |
152 » } | |
153 | |
154 » return res, nil | |
155 } | 91 } |
156 | 92 |
157 func hasUnsortedImports(file *ast.File) bool { | 93 func hasUnsortedImports(file *ast.File) bool { |
158 for _, d := range file.Decls { | 94 for _, d := range file.Decls { |
159 d, ok := d.(*ast.GenDecl) | 95 d, ok := d.(*ast.GenDecl) |
160 if !ok || d.Tok != token.IMPORT { | 96 if !ok || d.Tok != token.IMPORT { |
161 // Not an import declaration, so we're done. | 97 // Not an import declaration, so we're done. |
162 // Imports are always first. | 98 // Imports are always first. |
163 return false | 99 return false |
164 } | 100 } |
165 if d.Lparen.IsValid() { | 101 if d.Lparen.IsValid() { |
166 // For now assume all grouped imports are unsorted. | 102 // For now assume all grouped imports are unsorted. |
167 // TODO(gri) Should check if they are sorted already. | 103 // TODO(gri) Should check if they are sorted already. |
168 return true | 104 return true |
169 } | 105 } |
170 // Ungrouped imports are sorted by default. | 106 // Ungrouped imports are sorted by default. |
171 } | 107 } |
172 return false | 108 return false |
173 } | 109 } |
174 | 110 |
175 // parse parses src, which was read from filename, | 111 // parse parses src, which was read from filename, |
176 // as a Go source file or statement list. | 112 // as a Go source file or statement list. |
177 func parse(fset *token.FileSet, filename string, src []byte, stdin bool) (*ast.F
ile, func(src []byte, indent int) []byte, error) { | 113 func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( |
| 114 » file *ast.File, |
| 115 » sourceAdj func(src []byte, indent int) []byte, |
| 116 » indentAdj int, |
| 117 » err error, |
| 118 ) { |
178 // Try as whole source file. | 119 // Try as whole source file. |
179 » file, err := parser.ParseFile(fset, filename, src, parserMode) | 120 » file, err = parser.ParseFile(fset, filename, src, parserMode) |
180 » if err == nil { | 121 » // If there's no error, return. If the error is that the source file di
dn't begin with a |
181 » » return file, nil, nil | 122 » // package line and source fragments are ok, fall through to |
182 » } | |
183 » // If the error is that the source file didn't begin with a | |
184 » // package line and this is standard input, fall through to | |
185 // try as a source fragment. Stop and return on any other error. | 123 // try as a source fragment. Stop and return on any other error. |
186 » if !stdin || !strings.Contains(err.Error(), "expected 'package'") { | 124 » if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected
'package'") { |
187 » » return nil, nil, err | 125 » » return |
188 } | 126 } |
189 | 127 |
190 // If this is a declaration list, make it a source file | 128 // If this is a declaration list, make it a source file |
191 // by inserting a package clause. | 129 // by inserting a package clause. |
192 // Insert using a ;, not a newline, so that the line numbers | 130 // Insert using a ;, not a newline, so that the line numbers |
193 // in psrc match the ones in src. | 131 // in psrc match the ones in src. |
194 psrc := append([]byte("package p;"), src...) | 132 psrc := append([]byte("package p;"), src...) |
195 file, err = parser.ParseFile(fset, filename, psrc, parserMode) | 133 file, err = parser.ParseFile(fset, filename, psrc, parserMode) |
196 if err == nil { | 134 if err == nil { |
197 » » adjust := func(src []byte, indent int) []byte { | 135 » » sourceAdj = func(src []byte, indent int) []byte { |
198 // Remove the package clause. | 136 // Remove the package clause. |
199 // Gofmt has turned the ; into a \n. | 137 // Gofmt has turned the ; into a \n. |
200 src = src[indent+len("package p\n"):] | 138 src = src[indent+len("package p\n"):] |
201 » » » return cutSpace(src) | 139 » » » return bytes.TrimSpace(src) |
202 » » } | 140 » » } |
203 » » return file, adjust, nil | 141 » » return |
204 } | 142 } |
205 // If the error is that the source file didn't begin with a | 143 // If the error is that the source file didn't begin with a |
206 // declaration, fall through to try as a statement list. | 144 // declaration, fall through to try as a statement list. |
207 // Stop and return on any other error. | 145 // Stop and return on any other error. |
208 if !strings.Contains(err.Error(), "expected declaration") { | 146 if !strings.Contains(err.Error(), "expected declaration") { |
209 » » return nil, nil, err | 147 » » return |
210 } | 148 } |
211 | 149 |
212 // If this is a statement list, make it a source file | 150 // If this is a statement list, make it a source file |
213 // by inserting a package clause and turning the list | 151 // by inserting a package clause and turning the list |
214 // into a function body. This handles expressions too. | 152 // into a function body. This handles expressions too. |
215 // Insert using a ;, not a newline, so that the line numbers | 153 // Insert using a ;, not a newline, so that the line numbers |
216 // in fsrc match the ones in src. | 154 // in fsrc match the ones in src. |
217 fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}
') | 155 fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}
') |
218 file, err = parser.ParseFile(fset, filename, fsrc, parserMode) | 156 file, err = parser.ParseFile(fset, filename, fsrc, parserMode) |
219 if err == nil { | 157 if err == nil { |
220 » » adjust := func(src []byte, indent int) []byte { | 158 » » sourceAdj = func(src []byte, indent int) []byte { |
| 159 » » » // Cap adjusted indent to zero. |
| 160 » » » if indent < 0 { |
| 161 » » » » indent = 0 |
| 162 » » » } |
221 // Remove the wrapping. | 163 // Remove the wrapping. |
222 // Gofmt has turned the ; into a \n\n. | 164 // Gofmt has turned the ; into a \n\n. |
| 165 // There will be two non-blank lines with indent, hence
2*indent. |
223 src = src[2*indent+len("package p\n\nfunc _() {"):] | 166 src = src[2*indent+len("package p\n\nfunc _() {"):] |
224 src = src[:len(src)-(indent+len("\n}\n"))] | 167 src = src[:len(src)-(indent+len("\n}\n"))] |
225 » » » // Gofmt has also indented the function body one level. | 168 » » » return bytes.TrimSpace(src) |
226 » » » // Remove that indent. | 169 » » } |
227 » » » src = bytes.Replace(src, []byte("\n\t"), []byte("\n"), -
1) | 170 » » // Gofmt has also indented the function body one level. |
228 » » » return cutSpace(src) | 171 » » // Adjust that with indentAdj. |
229 » » } | 172 » » indentAdj = -1 |
230 » » return file, adjust, nil | 173 » } |
231 » } | 174 |
232 | 175 » // Succeeded, or out of options. |
233 » // Failed, and out of options. | 176 » return |
234 » return nil, nil, err | 177 } |
| 178 |
| 179 func format(fset *token.FileSet, file *ast.File, sourceAdj func(src []byte, inde
nt int) []byte, indentAdj int, src []byte) ([]byte, error) { |
| 180 » if sourceAdj == nil { |
| 181 » » // Complete source file. |
| 182 » » ast.SortImports(fset, file) |
| 183 » » var buf bytes.Buffer |
| 184 » » err := config.Fprint(&buf, fset, file) |
| 185 » » if err != nil { |
| 186 » » » return nil, err |
| 187 » » } |
| 188 » » return buf.Bytes(), nil |
| 189 » } |
| 190 |
| 191 » // Partial source file. |
| 192 » // Determine and prepend leading space. |
| 193 » i, j := 0, 0 |
| 194 » for j < len(src) && isSpace(src[j]) { |
| 195 » » if src[j] == '\n' { |
| 196 » » » i = j + 1 // byte offset of last line in leading space |
| 197 » » } |
| 198 » » j++ |
| 199 » } |
| 200 » var res []byte |
| 201 » res = append(res, src[:i]...) |
| 202 |
| 203 » // Determine and prepend indentation of first code line. |
| 204 » // Spaces are ignored unless there are no tabs, |
| 205 » // in which case spaces count as one tab. |
| 206 » indent := 0 |
| 207 » hasSpace := false |
| 208 » for _, b := range src[i:j] { |
| 209 » » switch b { |
| 210 » » case ' ': |
| 211 » » » hasSpace = true |
| 212 » » case '\t': |
| 213 » » » indent++ |
| 214 » » } |
| 215 » } |
| 216 » if indent == 0 && hasSpace { |
| 217 » » indent = 1 |
| 218 » } |
| 219 » for i := 0; i < indent; i++ { |
| 220 » » res = append(res, '\t') |
| 221 » } |
| 222 |
| 223 » // Format the source. |
| 224 » // Write it without any leading and trailing space. |
| 225 » cfg := config |
| 226 » cfg.Indent = indent + indentAdj |
| 227 » var buf bytes.Buffer |
| 228 » err := cfg.Fprint(&buf, fset, file) |
| 229 » if err != nil { |
| 230 » » return nil, err |
| 231 » } |
| 232 » res = append(res, sourceAdj(buf.Bytes(), cfg.Indent)...) |
| 233 |
| 234 » // Determine and append trailing space. |
| 235 » i = len(src) |
| 236 » for i > 0 && isSpace(src[i-1]) { |
| 237 » » i-- |
| 238 » } |
| 239 » return append(res, src[i:]...), nil |
235 } | 240 } |
236 | 241 |
237 func isSpace(b byte) bool { | 242 func isSpace(b byte) bool { |
238 return b == ' ' || b == '\t' || b == '\n' || b == '\r' | 243 return b == ' ' || b == '\t' || b == '\n' || b == '\r' |
239 } | 244 } |
240 | |
241 func cutSpace(b []byte) (middle []byte) { | |
242 i := 0 | |
243 for i < len(b) && isSpace(b[i]) { | |
244 i++ | |
245 } | |
246 j := len(b) | |
247 for j > 0 && isSpace(b[j-1]) { | |
248 j-- | |
249 } | |
250 if i <= j { | |
251 return b[i:j] | |
252 } | |
253 return nil | |
254 } | |
LEFT | RIGHT |