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" |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
75 // 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 |
76 // 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. |
77 // | 77 // |
78 // 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 |
79 // 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 |
80 // 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 |
81 // 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. |
82 // | 82 // |
83 func Source(src []byte) ([]byte, error) { | 83 func Source(src []byte) ([]byte, error) { |
84 fset := token.NewFileSet() | 84 fset := token.NewFileSet() |
85 » file, adjust, err := parse(fset, "", src, true) | 85 » file, sourceAdj, indentAdj, err := parse(fset, "", src, true) |
86 if err != nil { | 86 if err != nil { |
87 return nil, err | 87 return nil, err |
88 } | 88 } |
89 | 89 |
90 » var res []byte | 90 » return format(fset, file, sourceAdj, indentAdj, src) |
91 » if adjust == nil { | |
92 » » // Complete source file. | |
93 » » ast.SortImports(fset, file) | |
94 » » var buf bytes.Buffer | |
95 » » err := config.Fprint(&buf, fset, file) | |
96 » » if err != nil { | |
97 » » » return nil, err | |
98 » » } | |
99 » » res = buf.Bytes() | |
100 | |
101 » } else { | |
102 » » // Partial source file. | |
103 » » // Determine and prepend leading space. | |
104 » » i, j := 0, 0 | |
105 » » for ; j < len(src) && isSpace(src[j]); j++ { | |
106 » » » if src[j] == '\n' { | |
107 » » » » i = j + 1 // index of last line in leading space | |
108 » » » } | |
109 » » } | |
110 » » res = append(res, src[:i]...) | |
111 | |
112 » » // Determine and prepend indentation of first code line. | |
113 » » // Spaces are ignored unless there are no tabs, | |
114 » » // in which case spaces count as one tab. | |
115 » » indent := 0 | |
116 » » hasSpace := false | |
117 » » for _, b := range src[i:j] { | |
118 » » » switch b { | |
119 » » » case ' ': | |
120 » » » » hasSpace = true | |
121 » » » case '\t': | |
122 » » » » indent++ | |
123 » » » } | |
124 » » } | |
125 » » if indent == 0 && hasSpace { | |
126 » » » indent = 1 | |
127 » » } | |
128 » » for i := 0; i < indent; i++ { | |
129 » » » res = append(res, '\t') | |
130 » » } | |
131 | |
132 » » // Format the source. | |
133 » » // Write it without any leading and trailing space. | |
134 » » cfg := config | |
135 » » cfg.Indent = indent | |
136 » » var buf bytes.Buffer | |
137 » » err := cfg.Fprint(&buf, fset, file) | |
138 » » if err != nil { | |
139 » » » return nil, err | |
140 » » } | |
141 » » res = append(res, adjust(buf.Bytes(), indent)...) | |
142 | |
143 » » // Determine and append trailing space. | |
144 » » i = len(src) | |
145 » » for i > 0 && isSpace(src[i-1]) { | |
146 » » » i-- | |
147 » » } | |
148 » » res = append(res, src[i:]...) | |
149 » } | |
150 | |
151 » return res, nil | |
152 } | 91 } |
153 | 92 |
154 func hasUnsortedImports(file *ast.File) bool { | 93 func hasUnsortedImports(file *ast.File) bool { |
155 for _, d := range file.Decls { | 94 for _, d := range file.Decls { |
156 d, ok := d.(*ast.GenDecl) | 95 d, ok := d.(*ast.GenDecl) |
157 if !ok || d.Tok != token.IMPORT { | 96 if !ok || d.Tok != token.IMPORT { |
158 // Not an import declaration, so we're done. | 97 // Not an import declaration, so we're done. |
159 // Imports are always first. | 98 // Imports are always first. |
160 return false | 99 return false |
161 } | 100 } |
162 if d.Lparen.IsValid() { | 101 if d.Lparen.IsValid() { |
163 // For now assume all grouped imports are unsorted. | 102 // For now assume all grouped imports are unsorted. |
164 // TODO(gri) Should check if they are sorted already. | 103 // TODO(gri) Should check if they are sorted already. |
165 return true | 104 return true |
166 } | 105 } |
167 // Ungrouped imports are sorted by default. | 106 // Ungrouped imports are sorted by default. |
168 } | 107 } |
169 return false | 108 return false |
170 } | 109 } |
171 | 110 |
172 // parse parses src, which was read from filename, | 111 // parse parses src, which was read from filename, |
173 // as a Go source file or statement list. | 112 // as a Go source file or statement list. |
174 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 ) { |
175 // Try as whole source file. | 119 // Try as whole source file. |
176 » file, err := parser.ParseFile(fset, filename, src, parserMode) | 120 » file, err = parser.ParseFile(fset, filename, src, parserMode) |
177 » if err == nil { | 121 » // If there's no error, return. If the error is that the source file di
dn't begin with a |
178 » » return file, nil, nil | 122 » // package line and source fragments are ok, fall through to |
179 » } | |
180 » // If the error is that the source file didn't begin with a | |
181 » // package line and this is standard input, fall through to | |
182 // 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. |
183 » if !stdin || !strings.Contains(err.Error(), "expected 'package'") { | 124 » if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected
'package'") { |
184 » » return nil, nil, err | 125 » » return |
185 } | 126 } |
186 | 127 |
187 // If this is a declaration list, make it a source file | 128 // If this is a declaration list, make it a source file |
188 // by inserting a package clause. | 129 // by inserting a package clause. |
189 // Insert using a ;, not a newline, so that the line numbers | 130 // Insert using a ;, not a newline, so that the line numbers |
190 // in psrc match the ones in src. | 131 // in psrc match the ones in src. |
191 psrc := append([]byte("package p;"), src...) | 132 psrc := append([]byte("package p;"), src...) |
192 file, err = parser.ParseFile(fset, filename, psrc, parserMode) | 133 file, err = parser.ParseFile(fset, filename, psrc, parserMode) |
193 if err == nil { | 134 if err == nil { |
194 » » adjust := func(src []byte, indent int) []byte { | 135 » » sourceAdj = func(src []byte, indent int) []byte { |
195 // Remove the package clause. | 136 // Remove the package clause. |
196 // Gofmt has turned the ; into a \n. | 137 // Gofmt has turned the ; into a \n. |
197 src = src[indent+len("package p\n"):] | 138 src = src[indent+len("package p\n"):] |
198 return bytes.TrimSpace(src) | 139 return bytes.TrimSpace(src) |
199 } | 140 } |
200 » » return file, adjust, nil | 141 » » return |
201 } | 142 } |
202 // 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 |
203 // declaration, fall through to try as a statement list. | 144 // declaration, fall through to try as a statement list. |
204 // Stop and return on any other error. | 145 // Stop and return on any other error. |
205 if !strings.Contains(err.Error(), "expected declaration") { | 146 if !strings.Contains(err.Error(), "expected declaration") { |
206 » » return nil, nil, err | 147 » » return |
207 } | 148 } |
208 | 149 |
209 // If this is a statement list, make it a source file | 150 // If this is a statement list, make it a source file |
210 // by inserting a package clause and turning the list | 151 // by inserting a package clause and turning the list |
211 // into a function body. This handles expressions too. | 152 // into a function body. This handles expressions too. |
212 // Insert using a ;, not a newline, so that the line numbers | 153 // Insert using a ;, not a newline, so that the line numbers |
213 // in fsrc match the ones in src. | 154 // in fsrc match the ones in src. |
214 fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}
') | 155 fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}
') |
215 file, err = parser.ParseFile(fset, filename, fsrc, parserMode) | 156 file, err = parser.ParseFile(fset, filename, fsrc, parserMode) |
216 if err == nil { | 157 if err == nil { |
217 » » 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 » » » } |
218 // Remove the wrapping. | 163 // Remove the wrapping. |
219 // 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. |
220 src = src[2*indent+len("package p\n\nfunc _() {"):] | 166 src = src[2*indent+len("package p\n\nfunc _() {"):] |
221 src = src[:len(src)-(indent+len("\n}\n"))] | 167 src = src[:len(src)-(indent+len("\n}\n"))] |
222 // Gofmt has also indented the function body one level. | |
223 // Remove that indent. | |
224 src = bytes.Replace(src, []byte("\n\t"), []byte("\n"), -
1) | |
225 return bytes.TrimSpace(src) | 168 return bytes.TrimSpace(src) |
226 } | 169 } |
227 » » return file, adjust, nil | 170 » » // Gofmt has also indented the function body one level. |
228 » } | 171 » » // Adjust that with indentAdj. |
229 | 172 » » indentAdj = -1 |
230 » // Failed, and out of options. | 173 » } |
231 » return nil, nil, err | 174 |
| 175 » // Succeeded, or out of options. |
| 176 » return |
| 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 |
232 } | 240 } |
233 | 241 |
234 func isSpace(b byte) bool { | 242 func isSpace(b byte) bool { |
235 return b == ' ' || b == '\t' || b == '\n' || b == '\r' | 243 return b == ' ' || b == '\t' || b == '\n' || b == '\r' |
236 } | 244 } |
LEFT | RIGHT |